Files
portfolio-hub/tests/test_portfolio_ledger_aggregates.py
T
genichin 93840800fa feat(portfolios): expose ledger investment aggregates
Add reversal-aware grouped manual-ledger aggregates to portfolio list responses and align the single-portfolio return summary with the same semantics.\n\nRefs #23
2026-08-13 13:23:31 +09:00

79 lines
2.8 KiB
Python

"""Repository contract tests for portfolio ledger return aggregates."""
from unittest.mock import MagicMock, patch
from app.repositories.postgres import PostgresPortfolioRepository
def _repo_with_cursor(rows: list[tuple[object, ...]]):
repo = PostgresPortfolioRepository.__new__(PostgresPortfolioRepository)
repo.ensure_schema = MagicMock()
cursor = MagicMock()
cursor.__enter__.return_value = cursor
cursor.__exit__.return_value = None
cursor.fetchall.return_value = rows
connection = MagicMock()
connection.cursor.return_value = cursor
return repo, connection, cursor
def test_get_return_summaries_empty_ids_skips_database() -> None:
repo = PostgresPortfolioRepository.__new__(PostgresPortfolioRepository)
repo.ensure_schema = MagicMock()
with patch.object(repo, "_connect") as connect:
result = repo.get_return_summaries([])
assert result == {}
connect.assert_not_called()
repo.ensure_schema.assert_not_called()
def test_get_return_summaries_groups_portfolios_and_excludes_reversed_sources() -> None:
repo, connection, cursor = _repo_with_cursor(
[
("p-001", 500.0, 125.0, 40.0),
("p-002", 200.0, 0.0, 5.0),
]
)
with patch.object(repo, "_connect", return_value=connection):
result = repo.get_return_summaries(["p-001", "p-002"])
assert result == {
"p-001": {
"total_invested": 500.0,
"total_withdrawn": 125.0,
"net_contributed_capital": 375.0,
"cumulative_profit_withdrawn": 40.0,
},
"p-002": {
"total_invested": 200.0,
"total_withdrawn": 0.0,
"net_contributed_capital": 200.0,
"cumulative_profit_withdrawn": 5.0,
},
}
cursor.execute.assert_called_once()
sql, params = cursor.execute.call_args.args
normalized_sql = " ".join(sql.split()).lower()
assert "group by ledger.portfolio_id" in normalized_sql
assert "not exists" in normalized_sql
assert "reversal.target_ledger_entry_id = ledger.ledger_entry_id" in normalized_sql
assert "ledger.source = 'manual'" in normalized_sql
assert "ledger.side in ('invest', 'withdraw', 'profit')" in normalized_sql
assert params == (["p-001", "p-002"],)
def test_get_return_summary_delegates_to_shared_batch_semantics() -> None:
repo = PostgresPortfolioRepository.__new__(PostgresPortfolioRepository)
expected = {
"total_invested": 300.0,
"total_withdrawn": 100.0,
"net_contributed_capital": 200.0,
"cumulative_profit_withdrawn": 50.0,
}
repo.get_return_summaries = MagicMock(return_value={"p-001": expected})
assert repo.get_return_summary("p-001") == expected
repo.get_return_summaries.assert_called_once_with(["p-001"])