"""Real PostgreSQL semantics for all-source portfolio return aggregates. Run with PORTFOLIOHUB_RUN_LEDGER_AGGREGATE_POSTGRES=1 against a disposable/local PostgreSQL endpoint. Normal unit-test environments skip this module. """ from __future__ import annotations import os from unittest.mock import MagicMock import psycopg import pytest from app.repositories.postgres import PostgresPortfolioRepository pytestmark = pytest.mark.skipif( os.getenv("PORTFOLIOHUB_RUN_LEDGER_AGGREGATE_POSTGRES") != "1", reason="set PORTFOLIOHUB_RUN_LEDGER_AGGREGATE_POSTGRES=1 to run real PostgreSQL tests", ) def _connect() -> psycopg.Connection: return psycopg.connect( host=os.getenv("TIMESCALEDB_HOST", "127.0.0.1"), port=int(os.getenv("TIMESCALEDB_PORT", "5432")), dbname=os.getenv("TIMESCALEDB_NAME", "portfolio_hub_migration_test"), user=os.getenv("TIMESCALEDB_USER", "portfolio_hub_user"), password=os.getenv("TIMESCALEDB_PASSWORD", "change-me"), sslmode=os.getenv("TIMESCALEDB_SSLMODE", "prefer"), ) def test_all_sources_mixed_sides_and_cross_source_reversals() -> None: connection = _connect() try: with connection.cursor() as cursor: cursor.execute( """ CREATE TEMP TABLE portfolio_ledger ( ledger_entry_id BIGINT PRIMARY KEY, portfolio_id TEXT NOT NULL, price DOUBLE PRECISION NOT NULL, source TEXT NOT NULL, side TEXT NOT NULL, target_ledger_entry_id BIGINT ) """ ) cursor.executemany( """ INSERT INTO portfolio_ledger ( ledger_entry_id, portfolio_id, price, source, side, target_ledger_entry_id ) VALUES (%s, %s, %s, %s, %s, %s) """, [ (1, "broker-only", 700, "broker", "invest", None), (2, "mixed", 1000, "manual", "invest", None), (3, "mixed", 500, "broker", "invest", None), (4, "mixed", 200, "broker", "withdraw", None), (5, "mixed", 50, "manual", "profit", None), # A manual reversal excludes a broker original. (6, "mixed", 300, "broker", "invest", None), (7, "mixed", 300, "manual", "reversal", 6), # A broker reversal excludes a manual original. (8, "mixed", 100, "manual", "withdraw", None), (9, "mixed", 100, "broker", "reversal", 8), # Non-capital sides are never inferred as INVEST. (10, "mixed", 800_000, "broker", "buffer_out", None), ], ) repo = PostgresPortfolioRepository.__new__(PostgresPortfolioRepository) repo.ensure_schema = MagicMock() repo._connect = MagicMock(return_value=connection) summaries = repo.get_return_summaries(["broker-only", "mixed"]) assert summaries == { "broker-only": { "total_invested": 700.0, "total_withdrawn": 0.0, "net_contributed_capital": 700.0, "cumulative_profit_withdrawn": 0.0, }, "mixed": { "total_invested": 1500.0, "total_withdrawn": 200.0, "net_contributed_capital": 1300.0, "cumulative_profit_withdrawn": 50.0, }, } repo.ensure_schema.assert_called_once_with() finally: connection.close()