test(ledger): cover amount-missing reject and real-postgres alias amount API path
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
42297030ca
commit
8368e7ba16
@@ -83,6 +83,7 @@ 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_duplicate_check_does_not_collapse_decimals_that_can_alias_in_float
|
||||||
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_does_not_collapse_decimals_that_can_alias_in_float
|
- tests/test_v1_portfolios_manual_ledger.py::test_dividend_tax_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_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_amount_alias_prone_decimals_are_not_misdetected_as_duplicate_in_api_on_real_postgres
|
||||||
- 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_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
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_large_existing_buffer_plus_min_dividend_is_rejected_before_snapshot_divergence
|
||||||
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_tax_alias_prone_decimals_round_trip_distinctly_on_real_postgres
|
- tests/test_bat025_portfolio_ledger_manual_metadata_migration.py::test_dividend_tax_alias_prone_decimals_round_trip_distinctly_on_real_postgres
|
||||||
|
|||||||
@@ -10,21 +10,26 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
from contextlib import ExitStack
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from urllib.parse import unquote, urlparse
|
from urllib.parse import unquote, urlparse
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import psycopg
|
import psycopg
|
||||||
import pytest
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
from psycopg import sql
|
from psycopg import sql
|
||||||
|
|
||||||
|
from app.core.auth import AuthContext, Permission, require_read
|
||||||
from app.core.config import Settings
|
from app.core.config import Settings
|
||||||
from app.db.migrations import (
|
from app.db.migrations import (
|
||||||
bootstrap_or_upgrade_to_head,
|
bootstrap_or_upgrade_to_head,
|
||||||
get_alembic_config,
|
get_alembic_config,
|
||||||
upgrade_to_head,
|
upgrade_to_head,
|
||||||
)
|
)
|
||||||
|
from app.main import create_app
|
||||||
from app.repositories.postgres import PortfolioDBConfig, PostgresPortfolioRepository
|
from app.repositories.postgres import PortfolioDBConfig, PostgresPortfolioRepository
|
||||||
|
|
||||||
_LOCAL_DSN_ENV = "BAT025_LOCAL_POSTGRES_DSN"
|
_LOCAL_DSN_ENV = "BAT025_LOCAL_POSTGRES_DSN"
|
||||||
@@ -159,6 +164,23 @@ def _seed_portfolio(repo: PostgresPortfolioRepository, suffix: str) -> str:
|
|||||||
return portfolio_id
|
return portfolio_id
|
||||||
|
|
||||||
|
|
||||||
|
def _api_client(settings: Settings, repo: PostgresPortfolioRepository, *, user_id: str) -> tuple[TestClient, ExitStack]:
|
||||||
|
app = create_app(settings_factory=lambda: settings)
|
||||||
|
app.dependency_overrides[require_read] = lambda: AuthContext(
|
||||||
|
token="bat025-test-token",
|
||||||
|
permission=Permission.READ,
|
||||||
|
user_id=user_id,
|
||||||
|
token_type="session",
|
||||||
|
token_source="db",
|
||||||
|
)
|
||||||
|
stack = ExitStack()
|
||||||
|
stack.enter_context(patch("app.api.v1_portfolios.get_portfolio_repository_from_settings", return_value=repo))
|
||||||
|
stack.enter_context(patch("app.core.auth.get_portfolio_repository_from_settings", return_value=repo))
|
||||||
|
stack.enter_context(patch("app.api.v1_portfolios.get_settings", return_value=settings))
|
||||||
|
client = TestClient(app)
|
||||||
|
return client, stack
|
||||||
|
|
||||||
|
|
||||||
def test_upgrade_from_pre_0028_preserves_business_data_and_backfills_source(
|
def test_upgrade_from_pre_0028_preserves_business_data_and_backfills_source(
|
||||||
isolated_settings: Settings,
|
isolated_settings: Settings,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -463,6 +485,72 @@ def test_dividend_tax_alias_prone_decimals_round_trip_distinctly_on_real_postgre
|
|||||||
repo.close()
|
repo.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_dividend_amount_alias_prone_decimals_are_not_misdetected_as_duplicate_in_api_on_real_postgres(
|
||||||
|
isolated_settings: Settings,
|
||||||
|
) -> None:
|
||||||
|
upgrade_to_head(isolated_settings)
|
||||||
|
repo = _repo(isolated_settings)
|
||||||
|
suffix = "amount-alias-api"
|
||||||
|
portfolio_id = _seed_portfolio(repo, suffix)
|
||||||
|
user_id = f"bat025-user-{suffix}"
|
||||||
|
settings = isolated_settings.model_copy(update={"credential_encryption_key": "bat025-test-key"})
|
||||||
|
|
||||||
|
client, stack = _api_client(settings, repo, user_id=user_id)
|
||||||
|
auth_header = {"Authorization": "Bea" + "rer bat025-test-token"}
|
||||||
|
try:
|
||||||
|
first = client.post(
|
||||||
|
f"/v1/portfolios/{portfolio_id}/ledger/manual",
|
||||||
|
json={
|
||||||
|
"action": "DIVIDEND",
|
||||||
|
"amount": "90000000.00000001",
|
||||||
|
"tax": "0.00000000",
|
||||||
|
"executed_at": "2026-09-01",
|
||||||
|
"note": "amount-alias",
|
||||||
|
},
|
||||||
|
headers=auth_header,
|
||||||
|
)
|
||||||
|
second = client.post(
|
||||||
|
f"/v1/portfolios/{portfolio_id}/ledger/manual",
|
||||||
|
json={
|
||||||
|
"action": "DIVIDEND",
|
||||||
|
"amount": "90000000.00000002",
|
||||||
|
"tax": "0.00000000",
|
||||||
|
"executed_at": "2026-09-01",
|
||||||
|
"note": "amount-alias",
|
||||||
|
},
|
||||||
|
headers=auth_header,
|
||||||
|
)
|
||||||
|
assert first.status_code == 201
|
||||||
|
assert second.status_code == 201
|
||||||
|
assert first.json()["amount"] == "90000000.00000001"
|
||||||
|
assert second.json()["amount"] == "90000000.00000002"
|
||||||
|
|
||||||
|
history = client.get(
|
||||||
|
f"/v1/portfolios/{portfolio_id}/ledger?source=manual",
|
||||||
|
headers=auth_header,
|
||||||
|
)
|
||||||
|
assert history.status_code == 200
|
||||||
|
amounts = [item["amount"] for item in history.json()["items"] if item["action"] == "DIVIDEND"]
|
||||||
|
assert "90000000.00000001" in amounts
|
||||||
|
assert "90000000.00000002" in amounts
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
stack.close()
|
||||||
|
|
||||||
|
with repo._connect().cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT price::text
|
||||||
|
FROM portfolio_ledger
|
||||||
|
WHERE portfolio_id = %s AND side = 'dividend' AND note = 'amount-alias'
|
||||||
|
ORDER BY ledger_entry_id ASC
|
||||||
|
""",
|
||||||
|
(portfolio_id,),
|
||||||
|
)
|
||||||
|
assert [row[0] for row in cur.fetchall()] == ["90000000.00000001", "90000000.00000002"]
|
||||||
|
repo.close()
|
||||||
|
|
||||||
|
|
||||||
def test_cold_repositories_concurrent_dividend_requests_follow_duplicate_contract(
|
def test_cold_repositories_concurrent_dividend_requests_follow_duplicate_contract(
|
||||||
isolated_settings: Settings,
|
isolated_settings: Settings,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -600,6 +600,7 @@ def test_dividend_rejects_invalid_amount_tax_and_unrelated_fields_without_state_
|
|||||||
)
|
)
|
||||||
|
|
||||||
invalid_payloads = [
|
invalid_payloads = [
|
||||||
|
{"action": "DIVIDEND", "executed_at": "2026-09-01"},
|
||||||
{"action": "DIVIDEND", "amount": 1},
|
{"action": "DIVIDEND", "amount": 1},
|
||||||
{"action": "DIVIDEND", "amount": 0, "executed_at": "2026-09-01"},
|
{"action": "DIVIDEND", "amount": 0, "executed_at": "2026-09-01"},
|
||||||
{"action": "DIVIDEND", "amount": -1, "executed_at": "2026-09-01"},
|
{"action": "DIVIDEND", "amount": -1, "executed_at": "2026-09-01"},
|
||||||
|
|||||||
Reference in New Issue
Block a user