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
@@ -10,21 +10,26 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import threading
|
||||
from contextlib import ExitStack
|
||||
from datetime import UTC, datetime
|
||||
from decimal import Decimal
|
||||
from urllib.parse import unquote, urlparse
|
||||
from uuid import uuid4
|
||||
from unittest.mock import patch
|
||||
|
||||
import psycopg
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from psycopg import sql
|
||||
|
||||
from app.core.auth import AuthContext, Permission, require_read
|
||||
from app.core.config import Settings
|
||||
from app.db.migrations import (
|
||||
bootstrap_or_upgrade_to_head,
|
||||
get_alembic_config,
|
||||
upgrade_to_head,
|
||||
)
|
||||
from app.main import create_app
|
||||
from app.repositories.postgres import PortfolioDBConfig, PostgresPortfolioRepository
|
||||
|
||||
_LOCAL_DSN_ENV = "BAT025_LOCAL_POSTGRES_DSN"
|
||||
@@ -159,6 +164,23 @@ def _seed_portfolio(repo: PostgresPortfolioRepository, suffix: str) -> str:
|
||||
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(
|
||||
isolated_settings: Settings,
|
||||
) -> None:
|
||||
@@ -463,6 +485,72 @@ def test_dividend_tax_alias_prone_decimals_round_trip_distinctly_on_real_postgre
|
||||
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(
|
||||
isolated_settings: Settings,
|
||||
) -> None:
|
||||
|
||||
@@ -600,6 +600,7 @@ def test_dividend_rejects_invalid_amount_tax_and_unrelated_fields_without_state_
|
||||
)
|
||||
|
||||
invalid_payloads = [
|
||||
{"action": "DIVIDEND", "executed_at": "2026-09-01"},
|
||||
{"action": "DIVIDEND", "amount": 1},
|
||||
{"action": "DIVIDEND", "amount": 0, "executed_at": "2026-09-01"},
|
||||
{"action": "DIVIDEND", "amount": -1, "executed_at": "2026-09-01"},
|
||||
|
||||
Reference in New Issue
Block a user