feat: beta API integration test suite — 85 new tests across 6 modules

Extends integration test coverage from 108 to 193 tests for the beta gate.

New test modules:
- test_query_api_extended.py (33 tests): documents, evidence, macro/competitive, ops/admin, agents, analytics
- test_registry_write_paths.py (16 tests): write paths, validation, duplicates, competitor/exposure CRUD
- test_risk_approval_lifecycle.py (8 tests): evaluation edge cases, full approval lifecycle
- test_trading_extended.py (12 tests): config round-trips, decision filtering, override validation
- test_cross_service_roundtrip.py (4 tests): cross-service data consistency
- test_error_handling.py (12 tests): 404s, 422s, empty states, health checks

Seed script extended with watchlists, approvals, lockouts, notifications,
ingestion runs, saved queries, and daily risk snapshots.
This commit is contained in:
Celes Renata
2026-04-20 02:34:19 +00:00
parent 8f67d326c9
commit 898f89926d
12 changed files with 2129 additions and 0 deletions
+199
View File
@@ -194,6 +194,36 @@ AUDIT_01 = UUID("00000000-0000-4000-ab00-000000000001")
AUDIT_02 = UUID("00000000-0000-4000-ab00-000000000002")
AUDIT_03 = UUID("00000000-0000-4000-ab00-000000000003")
# Watchlists
WATCHLIST_01 = UUID("00000000-0000-4000-ac00-000000000001")
WATCHLIST_02 = UUID("00000000-0000-4000-ac00-000000000002")
# Watchlist members
WL_MEMBER_01 = UUID("00000000-0000-4000-ac10-000000000001")
WL_MEMBER_02 = UUID("00000000-0000-4000-ac10-000000000002")
WL_MEMBER_03 = UUID("00000000-0000-4000-ac10-000000000003")
# Operator Approvals
APPROVAL_PENDING = UUID("00000000-0000-4000-ad00-000000000001")
APPROVAL_APPROVED = UUID("00000000-0000-4000-ad00-000000000002")
# Symbol Lockouts
LOCKOUT_ACTIVE = UUID("00000000-0000-4000-ae00-000000000001")
LOCKOUT_EXPIRED = UUID("00000000-0000-4000-ae00-000000000002")
# Notifications
NOTIFICATION_01 = UUID("00000000-0000-4000-af00-000000000001")
# Ingestion Runs
INGESTION_RUN_01 = UUID("00000000-0000-4000-b300-000000000001")
INGESTION_RUN_02 = UUID("00000000-0000-4000-b300-000000000002")
# Saved Queries
SAVED_QUERY_01 = UUID("00000000-0000-4000-b400-000000000001")
# Daily Risk Snapshot
RISK_SNAPSHOT_01 = UUID("00000000-0000-4000-b500-000000000001")
# ── Exported lookup dicts for test imports ────────────────────
SEED_COMPANY_IDS = {
@@ -259,6 +289,26 @@ SEED_TRADING_DECISION_ID = str(TRADING_DECISION_01)
SEED_PORTFOLIO_SNAPSHOT_ID = str(PORTFOLIO_SNAP_01)
SEED_RISK_CONFIG_ID = str(RISK_CONFIG_01)
SEED_WATCHLIST_IDS = {
"WL_01": str(WATCHLIST_01),
"WL_02": str(WATCHLIST_02),
}
SEED_APPROVAL_IDS = {
"PENDING": str(APPROVAL_PENDING),
"APPROVED": str(APPROVAL_APPROVED),
}
SEED_LOCKOUT_IDS = {
"ACTIVE": str(LOCKOUT_ACTIVE),
"EXPIRED": str(LOCKOUT_EXPIRED),
}
SEED_NOTIFICATION_IDS = {"NOTIF_01": str(NOTIFICATION_01)}
SEED_INGESTION_RUN_IDS = {
"RUN_01": str(INGESTION_RUN_01),
"RUN_02": str(INGESTION_RUN_02),
}
SEED_SAVED_QUERY_IDS = {"SQ_01": str(SAVED_QUERY_01)}
SEED_RISK_SNAPSHOT_IDS = {"SNAP_01": str(RISK_SNAPSHOT_01)}
# ── Seed function ─────────────────────────────────────────────
@@ -303,6 +353,13 @@ async def seed() -> None:
await _seed_agent_performance_log(conn)
await _seed_risk_configs(conn)
await _seed_audit_events(conn)
await _seed_watchlists(conn)
await _seed_operator_approvals(conn)
await _seed_symbol_lockouts(conn)
await _seed_notifications(conn)
await _seed_ingestion_runs(conn)
await _seed_saved_queries(conn)
await _seed_daily_risk_snapshots(conn)
finally:
await conn.close()
@@ -990,6 +1047,148 @@ async def _seed_audit_events(conn: asyncpg.Connection) -> None:
)
# ── Watchlists ────────────────────────────────────────────────
async def _seed_watchlists(conn: asyncpg.Connection) -> None:
watchlists = [
(WATCHLIST_01, "Tech Leaders", "Top technology companies", True, BASE_TS, BASE_TS),
(WATCHLIST_02, "Value Picks", "Undervalued large caps", True, BASE_TS, BASE_TS),
]
await conn.executemany(
"""INSERT INTO watchlists (id, name, description, active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT DO NOTHING""",
watchlists,
)
members = [
(WL_MEMBER_01, WATCHLIST_01, COMPANY_AAPL, BASE_TS),
(WL_MEMBER_02, WATCHLIST_01, COMPANY_MSFT, BASE_TS),
(WL_MEMBER_03, WATCHLIST_02, COMPANY_JPM, BASE_TS),
]
await conn.executemany(
"""INSERT INTO watchlist_members (id, watchlist_id, company_id, added_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT DO NOTHING""",
members,
)
# ── Operator Approvals ────────────────────────────────────────
async def _seed_operator_approvals(conn: asyncpg.Connection) -> None:
approvals = [
(APPROVAL_PENDING, json.dumps({"ticker": "AAPL", "side": "buy", "qty": 5}),
REC_01, "AAPL", "buy", 5, 927.50, "pending", RISK_EVAL_01,
"system", None, None,
BASE_TS + timedelta(hours=24), # expires in 24h
BASE_TS, None, BASE_TS, BASE_TS),
(APPROVAL_APPROVED, json.dumps({"ticker": "MSFT", "side": "buy", "qty": 3}),
REC_02, "MSFT", "buy", 3, 1230.00, "approved", None,
"system", "test-operator", "Approved for paper trading",
BASE_TS + timedelta(hours=24),
BASE_TS - timedelta(hours=2), BASE_TS - timedelta(hours=1),
BASE_TS, BASE_TS),
]
await conn.executemany(
"""INSERT INTO operator_approvals
(id, order_job, recommendation_id, ticker, side, quantity, estimated_value,
status, risk_evaluation_id, requested_by, reviewed_by, review_note,
expires_at, requested_at, reviewed_at, created_at, updated_at)
VALUES ($1, $2::jsonb, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
ON CONFLICT DO NOTHING""",
approvals,
)
# ── Symbol Lockouts ───────────────────────────────────────────
async def _seed_symbol_lockouts(conn: asyncpg.Connection) -> None:
lockouts = [
(LOCKOUT_ACTIVE, "AAPL", "news_shock", "Earnings volatility cooldown",
BASE_TS + timedelta(days=7), BASE_TS),
(LOCKOUT_EXPIRED, "XOM", "cooldown", "Post-trade cooldown period",
BASE_TS - timedelta(days=1), BASE_TS - timedelta(days=3)),
]
await conn.executemany(
"""INSERT INTO symbol_lockouts (id, ticker, lockout_type, reason, expires_at, created_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT DO NOTHING""",
lockouts,
)
# ── Notifications ─────────────────────────────────────────────
async def _seed_notifications(conn: asyncpg.Connection) -> None:
await conn.execute(
"""INSERT INTO notifications
(id, channel, event_type, message, delivery_status, retry_count, error_message, created_at, delivered_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT DO NOTHING""",
NOTIFICATION_01, "email", "order.filled",
"Order filled: AAPL buy 10 shares at $185.50",
"delivered", 0, None, BASE_TS, BASE_TS + timedelta(seconds=5),
)
# ── Ingestion Runs ────────────────────────────────────────────
async def _seed_ingestion_runs(conn: asyncpg.Connection) -> None:
runs = [
(INGESTION_RUN_01, SOURCE_AAPL, COMPANY_AAPL, "news", "completed",
BASE_TS - timedelta(hours=2), BASE_TS - timedelta(hours=1, minutes=55),
15, 8, None, 0, None),
(INGESTION_RUN_02, SOURCE_JPM, COMPANY_JPM, "filing", "failed",
BASE_TS - timedelta(hours=1), None,
0, 0, "Connection timeout to SEC EDGAR", 2,
BASE_TS + timedelta(hours=1)),
]
await conn.executemany(
"""INSERT INTO ingestion_runs
(id, source_id, company_id, source_type, status, started_at, completed_at,
items_fetched, items_new, error_message, retry_count, next_retry_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
ON CONFLICT DO NOTHING""",
runs,
)
# ── Saved Queries ─────────────────────────────────────────────
async def _seed_saved_queries(conn: asyncpg.Connection) -> None:
await conn.execute(
"""INSERT INTO saved_queries (id, name, description, sql_text, created_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT DO NOTHING""",
SAVED_QUERY_01, "Top Recommendations",
"Shows highest confidence recommendations",
"SELECT ticker, action, confidence FROM recommendations ORDER BY confidence DESC LIMIT 10",
"operator", BASE_TS, BASE_TS,
)
# ── Daily Risk Snapshots ──────────────────────────────────────
async def _seed_daily_risk_snapshots(conn: asyncpg.Connection) -> None:
await conn.execute(
"""INSERT INTO daily_risk_snapshots
(id, account_id, snapshot_date, portfolio_value, daily_pnl,
daily_trade_count, positions_by_sector, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9)
ON CONFLICT DO NOTHING""",
RISK_SNAPSHOT_01, "PAPER-001", BASE_DATE, 12500.00, 150.25,
4, json.dumps({"Technology": 0.45, "Financial Services": 0.30, "Energy": 0.25}),
BASE_TS, BASE_TS,
)
# ── Entry point ───────────────────────────────────────────────
if __name__ == "__main__":