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:
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user