test(req-001): tighten AC coverage and remove markdown trailing whitespace
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
64975033e6
commit
7aa3de524a
@@ -3,71 +3,106 @@
|
|||||||
Test ID: TC-REQ-001-001
|
Test ID: TC-REQ-001-001
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.01-AC01
|
- REQ-001.01-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_updates_only_profit_buffer_and_records_executed_at_and_tax`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_defaults_to_zero_when_omitted`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_updates_only_profit_buffer_and_records_executed_at_and_tax
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_defaults_to_zero_when_omitted
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_tax_omitted_persists_zero_on_real_postgres
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-002
|
Test ID: TC-REQ-001-002
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.01-AC02
|
- REQ-001.01-AC02
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_requires_executed_at_and_rejects_fee_field`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_rejects_sub_minimum_numeric_precision`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_rejects_amount_exceeding_numeric_20_8`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_requires_executed_at_and_rejects_fee_field
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_rejects_sub_minimum_numeric_precision
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_rejects_amount_exceeding_numeric_20_8
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_rejects_invalid_amount_tax_and_unrelated_fields_without_state_change
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-003
|
Test ID: TC-REQ-001-003
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.02-AC01
|
- REQ-001.02-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_updates_only_profit_buffer_and_records_executed_at_and_tax`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_defaults_to_zero_when_omitted`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_updates_only_profit_buffer_and_records_executed_at_and_tax
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_defaults_to_zero_when_omitted
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-004
|
Test ID: TC-REQ-001-004
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.03-AC01
|
- REQ-001.03-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_executed_at_uses_utc_midnight_independent_of_server_timezone`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_executed_at_uses_utc_midnight_independent_of_server_timezone
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-005
|
Test ID: TC-REQ-001-005
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.04-AC01
|
- REQ-001.04-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_unified_ledger_includes_dividend_fields`, `tests/test_v1_portfolios_manual_ledger.py::test_unified_ledger_openapi_contains_nullable_amount`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_unified_ledger_includes_dividend_fields
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_unified_ledger_openapi_contains_nullable_amount
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-006
|
Test ID: TC-REQ-001-006
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.05-AC01
|
- REQ-001.05-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_duplicate_requires_confirmation`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_with_different_decimal_amount_is_not_treated_as_duplicate`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_duplicate_requires_confirmation
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_with_different_decimal_amount_is_not_treated_as_duplicate
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-007
|
Test ID: TC-REQ-001-007
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.05-AC02
|
- REQ-001.05-AC02
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_duplicate_requires_confirmation`, `tests/test_v1_portfolios_manual_ledger.py::test_reversed_dividend_is_excluded_from_future_duplicate_candidate_matching`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_duplicate_requires_confirmation
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_reversed_dividend_is_excluded_from_future_duplicate_candidate_matching
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-008
|
Test ID: TC-REQ-001-008
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.05-AC03
|
- REQ-001.05-AC03
|
||||||
Test reference: `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_cold_repositories_concurrent_dividend_requests_follow_duplicate_contract`
|
Test reference:
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_cold_repositories_concurrent_dividend_requests_follow_duplicate_contract
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-009
|
Test ID: TC-REQ-001-009
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.06-AC01
|
- REQ-001.06-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_reversal_decreases_profit_buffer`, `tests/test_v1_portfolios_manual_ledger.py::test_duplicate_reversal_is_rejected_without_state_change`, `tests/test_v1_portfolios_manual_ledger.py::test_dividend_reversal_insufficient_buffer_is_rejected_without_state_change`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_reversal_decreases_profit_buffer
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_duplicate_reversal_is_rejected_without_state_change
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_reversal_insufficient_buffer_is_rejected_without_state_change
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-010
|
Test ID: TC-REQ-001-010
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.07-AC01
|
- REQ-001.07-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_dividend_with_different_decimal_amount_is_not_treated_as_duplicate`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_large_existing_buffer_plus_min_dividend_is_rejected_before_snapshot_divergence`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_duplicate_check_does_not_collapse_decimals_that_can_alias_in_float
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_with_different_decimal_amount_is_not_treated_as_duplicate
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_precision_and_reversal_round_trip_on_real_postgres
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_large_existing_buffer_plus_min_dividend_is_rejected_before_snapshot_divergence
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-011
|
Test ID: TC-REQ-001-011
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.08-AC01
|
- REQ-001.08-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_confirm_duplicate_false_is_accepted_for_non_dividend_actions`, `tests/test_v1_portfolios_manual_ledger.py::test_confirm_duplicate_true_is_rejected_for_non_dividend_actions`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_existing_high_precision_snapshot_still_allows_legacy_manual_actions`, `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_large_invest_and_large_dividend_tax_metadata_are_accepted_when_snapshot_is_safe`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_confirm_duplicate_false_is_accepted_for_non_dividend_actions
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_confirm_duplicate_true_is_rejected_for_non_dividend_actions
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_existing_high_precision_snapshot_still_allows_legacy_manual_actions
|
||||||
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_large_invest_and_large_dividend_tax_metadata_are_accepted_when_snapshot_is_safe
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|
||||||
Test ID: TC-REQ-001-012
|
Test ID: TC-REQ-001-012
|
||||||
Verifies:
|
Verifies:
|
||||||
- REQ-001.09-AC01
|
- REQ-001.09-AC01
|
||||||
Test reference: `tests/test_v1_portfolios_manual_ledger.py::test_manual_write_rejects_service_token`, `tests/test_v1_portfolios_manual_ledger.py::test_owner_mismatch_returns_403`, `tests/test_v1_portfolios_manual_ledger.py::test_daemon_managed_requires_confirmation_then_records_evidence`, `tests/test_v1_portfolios_manual_ledger.py::test_manual_ledger_conflict_message_is_sanitized`, `tests/test_v1_portfolios_manual_ledger.py::test_manual_ledger_conflict_secret_is_not_rendered_in_logs`
|
Test reference:
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_manual_write_rejects_service_token
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_owner_mismatch_returns_403
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_daemon_managed_requires_confirmation_then_records_evidence
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_manual_ledger_conflict_message_is_sanitized
|
||||||
|
- tests/test_v1_portfolios_manual_ledger.py::test_manual_ledger_conflict_secret_is_not_rendered_in_logs
|
||||||
Automated: true
|
Automated: true
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
## Evidence Batch: local API regression run
|
## Evidence Batch: local API regression run
|
||||||
|
|
||||||
Requirement revision: `REQ-001@1.0`
|
Requirement revision: `REQ-001@1.0`
|
||||||
Commit: `1015b465bb64a5cfe9271044d43d079152c81e20`
|
Commit: `pending-local-commit`
|
||||||
Environment: `local sandbox / pytest / fake repository`
|
Environment: `local sandbox / pytest / fake repository`
|
||||||
Executed at: `2026-09-09T12:05:39Z`
|
Executed at: `2026-09-09T12:16:43Z`
|
||||||
Run selector: `pytest -q -o addopts='' tests/test_v1_portfolios_manual_ledger.py`
|
Run selector: `pytest -q -o addopts='' tests/test_v1_portfolios_manual_ledger.py`
|
||||||
Run result: `33 passed`
|
Run result: `35 passed`
|
||||||
|
|
||||||
Test ID: TC-REQ-001-001
|
|
||||||
Result: PASS
|
|
||||||
|
|
||||||
Test ID: TC-REQ-001-002
|
Test ID: TC-REQ-001-002
|
||||||
Result: PASS
|
Result: PASS
|
||||||
|
|
||||||
|
Test ID: TC-REQ-001-003
|
||||||
|
Result: PASS
|
||||||
|
|
||||||
Test ID: TC-REQ-001-004
|
Test ID: TC-REQ-001-004
|
||||||
Result: PASS
|
Result: PASS
|
||||||
|
|
||||||
@@ -27,6 +27,12 @@ Result: PASS
|
|||||||
Test ID: TC-REQ-001-007
|
Test ID: TC-REQ-001-007
|
||||||
Result: PASS
|
Result: PASS
|
||||||
|
|
||||||
|
Test ID: TC-REQ-001-009
|
||||||
|
Result: PASS
|
||||||
|
|
||||||
|
Test ID: TC-REQ-001-010
|
||||||
|
Result: PASS
|
||||||
|
|
||||||
Test ID: TC-REQ-001-012
|
Test ID: TC-REQ-001-012
|
||||||
Result: PASS
|
Result: PASS
|
||||||
|
|
||||||
@@ -37,7 +43,7 @@ Commit: `f193aa6da33428783b91c1144fc8b9de5b3fb4bb`
|
|||||||
Environment: `disposable PostgreSQL / independent re-review`
|
Environment: `disposable PostgreSQL / independent re-review`
|
||||||
Executed at: `2026-09-09T12:05:39Z`
|
Executed at: `2026-09-09T12:05:39Z`
|
||||||
Run selector: `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py (module)`
|
Run selector: `tests/test_bat025_portfolio_ledger_manual_metadata_migration.py (module)`
|
||||||
Run result: `7 passed, 2 failed` (`2 failed`는 기존 stale migration-head baseline)
|
Run result: `7 passed, 2 failed` (`2 failed` are pre-existing stale migration-head baseline)
|
||||||
|
|
||||||
Test ID: TC-REQ-001-003
|
Test ID: TC-REQ-001-003
|
||||||
Result: PASS
|
Result: PASS
|
||||||
@@ -53,3 +59,15 @@ Result: PASS
|
|||||||
|
|
||||||
Test ID: TC-REQ-001-011
|
Test ID: TC-REQ-001-011
|
||||||
Result: PASS
|
Result: PASS
|
||||||
|
|
||||||
|
## Evidence Batch: local real PostgreSQL availability
|
||||||
|
|
||||||
|
Requirement revision: `REQ-001@1.0`
|
||||||
|
Commit: `pending-local-commit`
|
||||||
|
Environment: `local sandbox / PostgreSQL DSN not configured`
|
||||||
|
Executed at: `2026-09-09T12:16:43Z`
|
||||||
|
Run selector: `pytest -q -o addopts='' tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_tax_omitted_persists_zero_on_real_postgres`
|
||||||
|
Run result: `1 skipped`
|
||||||
|
|
||||||
|
Test ID: TC-REQ-001-001
|
||||||
|
Result: BLOCKED
|
||||||
|
|||||||
@@ -313,6 +313,28 @@ def test_dividend_precision_and_reversal_round_trip_on_real_postgres(
|
|||||||
repo.close()
|
repo.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dividend_tax_omitted_persists_zero_on_real_postgres(
|
||||||
|
isolated_settings: Settings,
|
||||||
|
) -> None:
|
||||||
|
"""Omitted tax should persist as zero in PostgreSQL."""
|
||||||
|
upgrade_to_head(isolated_settings)
|
||||||
|
repo = _repo(isolated_settings)
|
||||||
|
portfolio_id = _seed_portfolio(repo, "dividend-tax-omitted")
|
||||||
|
|
||||||
|
dividend = repo.record_manual_ledger(
|
||||||
|
portfolio_id,
|
||||||
|
action="DIVIDEND",
|
||||||
|
amount=Decimal("1.00000000"),
|
||||||
|
executed_at=datetime(2026, 9, 1, 0, 0, 0, tzinfo=UTC),
|
||||||
|
note="tax-omitted",
|
||||||
|
)
|
||||||
|
assert dividend.tax_amount == 0.0
|
||||||
|
with repo._connect().cursor() as cur:
|
||||||
|
cur.execute("SELECT tax_amount::text FROM portfolio_ledger WHERE ledger_entry_id = %s", (dividend.ledger_entry_id,))
|
||||||
|
assert cur.fetchone()[0] == "0.00000000"
|
||||||
|
repo.close()
|
||||||
|
|
||||||
|
|
||||||
def test_existing_high_precision_snapshot_still_allows_legacy_manual_actions(
|
def test_existing_high_precision_snapshot_still_allows_legacy_manual_actions(
|
||||||
isolated_settings: Settings,
|
isolated_settings: Settings,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ class _FakeRepo:
|
|||||||
self.principal = principal or _token_principal()
|
self.principal = principal or _token_principal()
|
||||||
self.entries: list[LedgerEntry] = []
|
self.entries: list[LedgerEntry] = []
|
||||||
self._next_id = 1
|
self._next_id = 1
|
||||||
|
self._dividend_amounts: dict[int, Decimal] = {}
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
pass
|
pass
|
||||||
@@ -86,7 +88,9 @@ class _FakeRepo:
|
|||||||
def record_manual_ledger(self, portfolio_id: str, **kwargs: Any) -> LedgerEntry:
|
def record_manual_ledger(self, portfolio_id: str, **kwargs: Any) -> LedgerEntry:
|
||||||
self.get(portfolio_id)
|
self.get(portfolio_id)
|
||||||
action = kwargs["action"]
|
action = kwargs["action"]
|
||||||
amount = float(kwargs.get("amount") or 0.0)
|
amount_raw = kwargs.get("amount")
|
||||||
|
amount_decimal = Decimal(str(amount_raw if amount_raw is not None else 0))
|
||||||
|
amount = float(amount_decimal)
|
||||||
quantity = float(kwargs.get("quantity") or 0.0)
|
quantity = float(kwargs.get("quantity") or 0.0)
|
||||||
price = float(kwargs.get("price") or 0.0)
|
price = float(kwargs.get("price") or 0.0)
|
||||||
fee = float(kwargs.get("fee") or 0.0)
|
fee = float(kwargs.get("fee") or 0.0)
|
||||||
@@ -141,7 +145,7 @@ class _FakeRepo:
|
|||||||
if e.action == "DIVIDEND"
|
if e.action == "DIVIDEND"
|
||||||
and e.ledger_entry_id not in reversed_ids
|
and e.ledger_entry_id not in reversed_ids
|
||||||
and e.executed_at == executed_at
|
and e.executed_at == executed_at
|
||||||
and abs(float(e.price) - amount) < 1e-9
|
and self._dividend_amounts.get(e.ledger_entry_id, Decimal(str(e.price))) == amount_decimal
|
||||||
and e.note == note
|
and e.note == note
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
@@ -211,6 +215,8 @@ class _FakeRepo:
|
|||||||
)
|
)
|
||||||
self._next_id += 1
|
self._next_id += 1
|
||||||
self.entries.append(entry)
|
self.entries.append(entry)
|
||||||
|
if action == "DIVIDEND":
|
||||||
|
self._dividend_amounts[entry.ledger_entry_id] = amount_decimal
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def list_manual_ledger_entries(self, portfolio_id: str, limit: int = 50) -> list[LedgerEntry]:
|
def list_manual_ledger_entries(self, portfolio_id: str, limit: int = 50) -> list[LedgerEntry]:
|
||||||
@@ -576,6 +582,42 @@ def test_dividend_requires_executed_at_and_rejects_fee_field() -> None:
|
|||||||
assert invalid_fee.status_code == 422
|
assert invalid_fee.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
|
def test_dividend_rejects_invalid_amount_tax_and_unrelated_fields_without_state_change() -> None:
|
||||||
|
repo = _FakeRepo(_portfolio(cash=1000, quantity=10, price=10, buffer=500))
|
||||||
|
client = _make_client(repo)
|
||||||
|
baseline = (
|
||||||
|
repo.portfolio.cash_balance,
|
||||||
|
repo.portfolio.asset_quantity,
|
||||||
|
repo.portfolio.profit_buffer,
|
||||||
|
len(repo.entries),
|
||||||
|
)
|
||||||
|
|
||||||
|
invalid_payloads = [
|
||||||
|
{"action": "DIVIDEND", "amount": 0, "executed_at": "2026-09-01"},
|
||||||
|
{"action": "DIVIDEND", "amount": -1, "executed_at": "2026-09-01"},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "tax": -1, "executed_at": "2026-09-01"},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "tax": "0.000000001", "executed_at": "2026-09-01"},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "tax": "1000000000000.00000000", "executed_at": "2026-09-01"},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "executed_at": "2026-09-01", "quantity": 1},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "executed_at": "2026-09-01", "price": 1},
|
||||||
|
{"action": "DIVIDEND", "amount": 1, "executed_at": "2026-09-01", "target_ledger_entry_id": 1},
|
||||||
|
]
|
||||||
|
|
||||||
|
for payload in invalid_payloads:
|
||||||
|
resp = client.post(
|
||||||
|
f"/v1/portfolios/{_PORTFOLIO_ID}/ledger/manual",
|
||||||
|
json=payload,
|
||||||
|
headers=_auth(),
|
||||||
|
)
|
||||||
|
assert resp.status_code == 422
|
||||||
|
assert (
|
||||||
|
repo.portfolio.cash_balance,
|
||||||
|
repo.portfolio.asset_quantity,
|
||||||
|
repo.portfolio.profit_buffer,
|
||||||
|
len(repo.entries),
|
||||||
|
) == baseline
|
||||||
|
|
||||||
|
|
||||||
def test_dividend_rejects_sub_minimum_numeric_precision() -> None:
|
def test_dividend_rejects_sub_minimum_numeric_precision() -> None:
|
||||||
repo = _FakeRepo(_portfolio())
|
repo = _FakeRepo(_portfolio())
|
||||||
client = _make_client(repo)
|
client = _make_client(repo)
|
||||||
@@ -653,6 +695,25 @@ def test_dividend_with_different_decimal_amount_is_not_treated_as_duplicate() ->
|
|||||||
assert second.status_code == 201
|
assert second.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
|
def test_dividend_duplicate_check_does_not_collapse_decimals_that_can_alias_in_float() -> None:
|
||||||
|
repo = _FakeRepo(_portfolio())
|
||||||
|
client = _make_client(repo)
|
||||||
|
|
||||||
|
first = client.post(
|
||||||
|
f"/v1/portfolios/{_PORTFOLIO_ID}/ledger/manual",
|
||||||
|
json={"action": "DIVIDEND", "amount": "90000000.00000001", "executed_at": "2026-09-01", "note": "Q3"},
|
||||||
|
headers=_auth(),
|
||||||
|
)
|
||||||
|
assert first.status_code == 201
|
||||||
|
|
||||||
|
second = client.post(
|
||||||
|
f"/v1/portfolios/{_PORTFOLIO_ID}/ledger/manual",
|
||||||
|
json={"action": "DIVIDEND", "amount": "90000000.00000002", "executed_at": "2026-09-01", "note": "Q3"},
|
||||||
|
headers=_auth(),
|
||||||
|
)
|
||||||
|
assert second.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
def test_reversed_dividend_is_excluded_from_future_duplicate_candidate_matching() -> None:
|
def test_reversed_dividend_is_excluded_from_future_duplicate_candidate_matching() -> None:
|
||||||
repo = _FakeRepo(_portfolio())
|
repo = _FakeRepo(_portfolio())
|
||||||
client = _make_client(repo)
|
client = _make_client(repo)
|
||||||
|
|||||||
Reference in New Issue
Block a user