fix: make corporate actions atomic and recoverable
This commit is contained in:
@@ -7,7 +7,7 @@ import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.api import v1_portfolios
|
||||
from app.domain.models import CorporateAction
|
||||
from app.domain.models import CorporateAction, Portfolio
|
||||
from app.main import create_app
|
||||
from app.repositories.postgres import PostgresPortfolioRepository
|
||||
|
||||
@@ -20,6 +20,46 @@ class MissingPortfolioRepo:
|
||||
pass
|
||||
|
||||
|
||||
class ResumeCaptureRepo:
|
||||
def __init__(self):
|
||||
self.resume_partial = None
|
||||
|
||||
def get(self, portfolio_id):
|
||||
return Portfolio(
|
||||
portfolio_id=portfolio_id,
|
||||
broker="kis",
|
||||
status="enabled",
|
||||
name="대교우B",
|
||||
symbol="019685",
|
||||
asset_id="asset-1",
|
||||
owner_user_id="u1",
|
||||
cash_balance=400060.6249,
|
||||
asset_quantity=726,
|
||||
price=643,
|
||||
profit_buffer=0,
|
||||
)
|
||||
|
||||
def record_corporate_action(
|
||||
self,
|
||||
portfolio_id,
|
||||
*,
|
||||
action,
|
||||
idempotency_key,
|
||||
payload_hash,
|
||||
resume_partial=False,
|
||||
):
|
||||
self.resume_partial = resume_partial
|
||||
action.average_price_before = Decimal("1086.23414594")
|
||||
action.average_price_after = Decimal("2172.46829187")
|
||||
action.total_cost_basis = Decimal("1577211.97990000")
|
||||
action.ledger_entry_id = 7
|
||||
action.recorded_at = datetime(2026, 9, 13, tzinfo=UTC)
|
||||
return action
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_create_corporate_action_maps_postgres_missing_portfolio_to_404(monkeypatch):
|
||||
app = create_app()
|
||||
repo = MissingPortfolioRepo()
|
||||
@@ -35,6 +75,31 @@ def test_create_corporate_action_maps_postgres_missing_portfolio_to_404(monkeypa
|
||||
assert response.json()["detail"] == "Portfolio not found"
|
||||
|
||||
|
||||
def test_corporate_action_api_passes_explicit_resume_mode(monkeypatch):
|
||||
app = create_app()
|
||||
repo = ResumeCaptureRepo()
|
||||
monkeypatch.setattr(v1_portfolios, "_get_repo", lambda: repo)
|
||||
app.dependency_overrides[v1_portfolios.require_trade_write] = lambda: type(
|
||||
"Auth", (), {"user_id": "u1", "permission": "trade_write"}
|
||||
)()
|
||||
|
||||
response = TestClient(app).post(
|
||||
"/v1/portfolios/pf-1/corporate-actions",
|
||||
headers={"Idempotency-Key": "resume-1"},
|
||||
json={
|
||||
"effective_date": "2026-09-10",
|
||||
"quantity_before": "1452",
|
||||
"quantity_after": "726",
|
||||
"evidence_ref": "KIS 019685 2026-09-10",
|
||||
"resume_partial": True,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json()["quantity_after"] == "726"
|
||||
assert repo.resume_partial is True
|
||||
|
||||
|
||||
def _action(asset_id="asset-1"):
|
||||
return CorporateAction("ca-1", "pf-1", asset_id, "stock_consolidation", date(2026, 1, 1), Decimal(2), Decimal(1), Decimal(0), Decimal(0), Decimal(0), Decimal(10), Decimal(0), "ev", "idem", "hash", 0)
|
||||
|
||||
@@ -47,7 +112,12 @@ def _repo_for_row(asset_id):
|
||||
conn.__enter__.return_value = conn
|
||||
conn.cursor.return_value.__enter__.return_value = cur
|
||||
repo._connect = MagicMock(return_value=conn)
|
||||
cur.fetchone.side_effect = [(Decimal(2), Decimal(10), Decimal(0), asset_id, "enabled"), None, None, None, (7, date(2026, 1, 1))]
|
||||
cur.fetchone.side_effect = [
|
||||
(Decimal(2), Decimal(10), Decimal(0), asset_id, "enabled", Decimal(643)),
|
||||
None,
|
||||
None,
|
||||
(7, date(2026, 1, 1)),
|
||||
]
|
||||
cur.fetchall.return_value = [("buy", Decimal(2), Decimal(0), Decimal(0), Decimal(0), datetime(2025, 12, 31, tzinfo=UTC))]
|
||||
return repo
|
||||
|
||||
@@ -74,8 +144,7 @@ def test_record_corporate_action_accepts_daekyo_rounding_residue():
|
||||
conn.cursor.return_value.__enter__.return_value = cur
|
||||
repo._connect = MagicMock(return_value=conn)
|
||||
cur.fetchone.side_effect = [
|
||||
(Decimal(1452), Decimal(10), Decimal(0), "asset-1", "enabled"),
|
||||
None,
|
||||
(Decimal(1452), Decimal(10), Decimal(0), "asset-1", "enabled", Decimal(643)),
|
||||
None,
|
||||
None,
|
||||
(7, datetime(2026, 9, 13, tzinfo=UTC)),
|
||||
|
||||
Reference in New Issue
Block a user