"""Integration tests for the Query API — all 17 frontend-facing endpoints. Validates every GET endpoint the frontend calls against the live sandbox with deterministic seed data. Uses the ``query_client`` and ``seed_ids`` fixtures from conftest.py. """ import pytest pytestmark = pytest.mark.asyncio # --------------------------------------------------------------------------- # 1–3 Companies # --------------------------------------------------------------------------- class TestQueryAPICompanies: """Endpoints: /api/companies, /api/companies/{id}, /api/companies/{id}/sources.""" async def test_list_companies(self, query_client, seed_ids): """GET /api/companies — expect at least 5 seeded companies.""" resp = await query_client.get("/api/companies") assert resp.status_code == 200 data = resp.json() assert len(data) >= 5 tickers = {c["ticker"] for c in data} assert {"AAPL", "MSFT", "JPM", "JNJ", "XOM"} <= tickers # Every company row must have core fields for c in data: assert "id" in c assert "legal_name" in c assert "sector" in c async def test_get_company(self, query_client, seed_ids): """GET /api/companies/{id} — detail for AAPL.""" company_id = seed_ids["companies"]["AAPL"] resp = await query_client.get(f"/api/companies/{company_id}") assert resp.status_code == 200 data = resp.json() assert data["ticker"] == "AAPL" assert data["legal_name"] == "Apple Inc" assert "aliases" in data assert "active_source_count" in data async def test_list_company_sources(self, query_client, seed_ids): """GET /api/companies/{id}/sources — AAPL has at least 1 source.""" company_id = seed_ids["companies"]["AAPL"] resp = await query_client.get(f"/api/companies/{company_id}/sources") assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) assert len(data) >= 1 assert "source_type" in data[0] # --------------------------------------------------------------------------- # 4–5 Documents # --------------------------------------------------------------------------- class TestQueryAPIDocuments: """Endpoints: /api/documents, /api/documents/{id}.""" async def test_list_documents(self, query_client, seed_ids): """GET /api/documents — expect at least 10 seeded documents.""" resp = await query_client.get("/api/documents") assert resp.status_code == 200 data = resp.json() assert len(data) >= 10 for doc in data: assert "id" in doc assert "document_type" in doc assert "title" in doc async def test_get_document(self, query_client, seed_ids): """GET /api/documents/{id} — detail with intelligence.""" doc_id = seed_ids["documents"]["DOC_01"] resp = await query_client.get(f"/api/documents/{doc_id}") assert resp.status_code == 200 data = resp.json() assert data["id"] == doc_id assert "title" in data assert "document_type" in data # Intelligence extraction should be present for seeded docs assert "intelligence" in data assert "company_mentions" in data # --------------------------------------------------------------------------- # 6–7 Trends # --------------------------------------------------------------------------- class TestQueryAPITrends: """Endpoints: /api/trends, /api/trends/{id}.""" async def test_list_trends(self, query_client, seed_ids): """GET /api/trends — expect at least 5 seeded trend windows.""" resp = await query_client.get("/api/trends") assert resp.status_code == 200 data = resp.json() assert len(data) >= 5 for t in data: assert "id" in t assert "trend_direction" in t assert "confidence" in t async def test_get_trend(self, query_client, seed_ids): """GET /api/trends/{id} — detail for first seeded trend.""" trend_id = seed_ids["trends"]["TREND_01"] resp = await query_client.get(f"/api/trends/{trend_id}") assert resp.status_code == 200 data = resp.json() assert data["id"] == trend_id assert data["trend_direction"] in ("bullish", "bearish", "mixed") assert 0 <= data["confidence"] <= 1 # --------------------------------------------------------------------------- # 8–9 Recommendations # --------------------------------------------------------------------------- class TestQueryAPIRecommendations: """Endpoints: /api/recommendations, /api/recommendations/{id}.""" async def test_list_recommendations(self, query_client, seed_ids): """GET /api/recommendations — expect at least 5 seeded recs.""" resp = await query_client.get("/api/recommendations", params={"latest": "false"}) assert resp.status_code == 200 data = resp.json() assert len(data) >= 5 for r in data: assert "id" in r assert "ticker" in r assert "action" in r assert "confidence" in r async def test_get_recommendation(self, query_client, seed_ids): """GET /api/recommendations/{id} — detail with evidence.""" rec_id = seed_ids["recommendations"]["REC_01"] resp = await query_client.get(f"/api/recommendations/{rec_id}") assert resp.status_code == 200 data = resp.json() assert data["id"] == rec_id assert "ticker" in data assert "thesis" in data assert "evidence" in data assert "risk_evaluation" in data # --------------------------------------------------------------------------- # 10–11 Orders # --------------------------------------------------------------------------- class TestQueryAPIOrders: """Endpoints: /api/orders, /api/orders/{id}.""" async def test_list_orders(self, query_client, seed_ids): """GET /api/orders — expect at least 3 seeded orders.""" resp = await query_client.get("/api/orders") assert resp.status_code == 200 data = resp.json() assert len(data) >= 3 statuses = {o["status"] for o in data} # Seed has filled, pending, cancelled assert len(statuses) >= 2 for o in data: assert "id" in o assert "ticker" in o assert "side" in o async def test_get_order(self, query_client, seed_ids): """GET /api/orders/{id} — detail with events and audit trail.""" order_id = seed_ids["orders"]["ORDER_01"] resp = await query_client.get(f"/api/orders/{order_id}") assert resp.status_code == 200 data = resp.json() assert data["id"] == order_id assert "ticker" in data assert "events" in data assert isinstance(data["events"], list) assert "audit_trail" in data # --------------------------------------------------------------------------- # 12 Positions # --------------------------------------------------------------------------- class TestQueryAPIPositions: """Endpoint: /api/positions.""" async def test_list_positions(self, query_client, seed_ids): """GET /api/positions — expect at least 2 seeded positions.""" resp = await query_client.get("/api/positions") assert resp.status_code == 200 data = resp.json() assert len(data) >= 2 for p in data: assert "id" in p assert "ticker" in p assert "quantity" in p assert "unrealized_pnl" in p # --------------------------------------------------------------------------- # 13 Pipeline Health # --------------------------------------------------------------------------- class TestQueryAPIOps: """Endpoints: /api/ops/pipeline/health, /api/ops/ingestion/summary, /api/ops/sources/coverage-gaps.""" async def test_pipeline_health(self, query_client, seed_ids): """GET /api/ops/pipeline/health — returns structured health data.""" resp = await query_client.get("/api/ops/pipeline/health") assert resp.status_code == 200 data = resp.json() assert "hours" in data assert "document_stages" in data assert "parsing" in data assert "extraction" in data assert "aggregation" in data assert "queue_depths" in data # ----------------------------------------------------------------------- # 14 Ingestion Summary # ----------------------------------------------------------------------- async def test_ingestion_summary(self, query_client, seed_ids): """GET /api/ops/ingestion/summary — returns ingestion stats.""" resp = await query_client.get("/api/ops/ingestion/summary") assert resp.status_code == 200 data = resp.json() assert "hours" in data assert "total_runs" in data assert "by_source_type" in data assert isinstance(data["by_source_type"], list) # ----------------------------------------------------------------------- # 15 Coverage Gaps # ----------------------------------------------------------------------- async def test_coverage_gaps(self, query_client, seed_ids): """GET /api/ops/sources/coverage-gaps — returns gap analysis.""" resp = await query_client.get("/api/ops/sources/coverage-gaps") assert resp.status_code == 200 data = resp.json() assert "missing_source_types" in data assert "stale_sources" in data assert isinstance(data["missing_source_types"], list) assert isinstance(data["stale_sources"], list) # --------------------------------------------------------------------------- # 16–17 Agents & Variants # --------------------------------------------------------------------------- class TestQueryAPIAgents: """Endpoints: /api/agents, /api/agents/{id}/variants.""" async def test_list_agents(self, query_client, seed_ids): """GET /api/agents — expect at least 3 seeded agents.""" resp = await query_client.get("/api/agents") assert resp.status_code == 200 data = resp.json() assert len(data) >= 3 for a in data: assert "id" in a assert "name" in a assert "slug" in a async def test_list_agent_variants(self, query_client, seed_ids): """GET /api/agents/{id}/variants — variants for the extractor agent.""" agent_id = seed_ids["agents"]["extractor"] resp = await query_client.get(f"/api/agents/{agent_id}/variants") assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) assert len(data) >= 1 for v in data: assert "id" in v assert "variant_name" in v assert v["agent_id"] == agent_id