c85c0068a2
- Replace all datetime.utcnow() with datetime.now(tz=timezone.utc) across 8 files - Fix 12 failing tests to match current implementation behavior - Fix pytest_plugins in non-top-level conftest (moved to root conftest.py) - Auto-fix 189 lint issues (import sorting, unused imports) - Add CI/CD pipeline infrastructure (ARC, ArgoCD, Kargo manifests) - Add values-beta.yaml and values-paper.yaml for staged deployments - Update GitHub Actions workflow to use self-hosted-gremlin runners - Add integration-test job to CI pipeline Result: 1596 passed, 0 failed, 0 warnings
431 lines
15 KiB
Python
431 lines
15 KiB
Python
"""Frontend data dependency tests — verify every page's API calls return valid data.
|
|
|
|
Each test function represents one frontend page and calls all the API
|
|
endpoints that page depends on. For each endpoint we assert:
|
|
• HTTP 200
|
|
• Response is non-empty (list has items, or dict has expected keys)
|
|
|
|
Uses ``query_client``, ``registry_client``, ``trading_client``, and
|
|
``seed_ids`` fixtures from conftest.py.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1 Home
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestHomePage:
|
|
"""Home page depends on: companies, pipeline health, ingestion summary, recommendations."""
|
|
|
|
async def test_home_page_deps(self, query_client, seed_ids):
|
|
# /api/companies
|
|
resp = await query_client.get("/api/companies")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
# /api/ops/pipeline/health
|
|
resp = await query_client.get("/api/ops/pipeline/health")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "document_stages" in data
|
|
|
|
# /api/ops/ingestion/summary
|
|
resp = await query_client.get("/api/ops/ingestion/summary")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "total_runs" in data
|
|
|
|
# /api/recommendations
|
|
resp = await query_client.get("/api/recommendations")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2 Companies
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCompaniesPage:
|
|
"""Companies list page depends on: companies (query API)."""
|
|
|
|
async def test_companies_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/companies")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 5
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3 CompanyDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCompanyDetailPage:
|
|
"""CompanyDetail depends on: company, sources, trends, recommendations,
|
|
competitors (registry), exposure (registry), macro-impacts."""
|
|
|
|
async def test_company_detail_page_deps(
|
|
self, query_client, registry_client, seed_ids,
|
|
):
|
|
company_id = seed_ids["companies"]["AAPL"]
|
|
|
|
# /api/companies/{id}
|
|
resp = await query_client.get(f"/api/companies/{company_id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["ticker"] == "AAPL"
|
|
|
|
# /api/companies/{id}/sources
|
|
resp = await query_client.get(f"/api/companies/{company_id}/sources")
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
# /api/trends?ticker=AAPL
|
|
resp = await query_client.get("/api/trends", params={"ticker": "AAPL"})
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
# /api/recommendations?ticker=AAPL
|
|
resp = await query_client.get("/api/recommendations", params={"ticker": "AAPL"})
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
# /companies/{id}/competitors (registry — no /api/ prefix)
|
|
resp = await registry_client.get(f"/companies/{company_id}/competitors")
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
# /companies/{id}/exposure (registry — no /api/ prefix)
|
|
resp = await registry_client.get(f"/companies/{company_id}/exposure")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "company_id" in data
|
|
|
|
# /api/companies/AAPL/macro-impacts (query API via /api/macro/impacts/AAPL)
|
|
resp = await query_client.get("/api/macro/impacts/AAPL")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4 Documents
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestDocumentsPage:
|
|
"""Documents list page depends on: documents."""
|
|
|
|
async def test_documents_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/documents")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5 DocumentDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestDocumentDetailPage:
|
|
"""DocumentDetail depends on: documents/{id}."""
|
|
|
|
async def test_document_detail_page_deps(self, query_client, seed_ids):
|
|
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
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6 Trends
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTrendsPage:
|
|
"""Trends list page depends on: trends."""
|
|
|
|
async def test_trends_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/trends")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7 TrendDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTrendDetailPage:
|
|
"""TrendDetail depends on: trends/{id}, trends/{id}/projection."""
|
|
|
|
async def test_trend_detail_page_deps(self, query_client, seed_ids):
|
|
trend_id = seed_ids["trends"]["TREND_01"]
|
|
|
|
# /api/trends/{id}
|
|
resp = await query_client.get(f"/api/trends/{trend_id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == trend_id
|
|
assert "trend_direction" in data
|
|
|
|
# /api/trends/{id}/projection
|
|
resp = await query_client.get(f"/api/trends/{trend_id}/projection")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict)
|
|
assert "projected_direction" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8 Recommendations
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRecommendationsPage:
|
|
"""Recommendations list page depends on: recommendations."""
|
|
|
|
async def test_recommendations_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/recommendations")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 9 RecommendationDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRecommendationDetailPage:
|
|
"""RecommendationDetail depends on: recommendations/{id}."""
|
|
|
|
async def test_recommendation_detail_page_deps(self, query_client, seed_ids):
|
|
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 "evidence" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 10 Orders
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOrdersPage:
|
|
"""Orders list page depends on: orders."""
|
|
|
|
async def test_orders_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/orders")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 11 OrderDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOrderDetailPage:
|
|
"""OrderDetail depends on: orders/{id}."""
|
|
|
|
async def test_order_detail_page_deps(self, query_client, seed_ids):
|
|
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 "events" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 12 Positions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestPositionsPage:
|
|
"""Positions page depends on: positions."""
|
|
|
|
async def test_positions_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/positions")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 13 GlobalEvents
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGlobalEventsPage:
|
|
"""GlobalEvents page depends on: macro/events."""
|
|
|
|
async def test_global_events_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/macro/events")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 14 GlobalEventDetail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGlobalEventDetailPage:
|
|
"""GlobalEventDetail depends on: macro/events/{id}."""
|
|
|
|
async def test_global_event_detail_page_deps(self, query_client, seed_ids):
|
|
event_id = seed_ids["global_events"]["EVT_01"]
|
|
resp = await query_client.get(f"/api/macro/events/{event_id}")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["id"] == event_id
|
|
assert "summary" in data
|
|
assert "impacts" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 15 OpsPipeline
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOpsPipelinePage:
|
|
"""OpsPipeline page depends on: ops/pipeline/health."""
|
|
|
|
async def test_ops_pipeline_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/ops/pipeline/health")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "document_stages" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 16 OpsIngestion
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOpsIngestionPage:
|
|
"""OpsIngestion page depends on: ingestion summary + throughput."""
|
|
|
|
async def test_ops_ingestion_page_deps(self, query_client, seed_ids):
|
|
# /api/ops/ingestion/summary
|
|
resp = await query_client.get("/api/ops/ingestion/summary")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "total_runs" in data
|
|
|
|
# /api/ops/ingestion/throughput
|
|
resp = await query_client.get("/api/ops/ingestion/throughput")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 17 OpsCoverage
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOpsCoveragePage:
|
|
"""OpsCoverage page depends on: ops/sources/coverage-gaps."""
|
|
|
|
async def test_ops_coverage_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/ops/sources/coverage-gaps")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict)
|
|
assert "missing_source_types" in data
|
|
assert "stale_sources" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 18 Agents
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestAgentsPage:
|
|
"""Agents page depends on: agents."""
|
|
|
|
async def test_agents_page_deps(self, query_client, seed_ids):
|
|
resp = await query_client.get("/api/agents")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list) and len(data) >= 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 19 TradingEngine
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTradingEnginePage:
|
|
"""TradingEngine page depends on: trading status, metrics, decisions."""
|
|
|
|
async def test_trading_engine_page_deps(self, trading_client, seed_ids):
|
|
# /api/trading/status
|
|
resp = await trading_client.get("/api/trading/status")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "enabled" in data
|
|
|
|
# /api/trading/metrics
|
|
resp = await trading_client.get("/api/trading/metrics")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "total_portfolio_value" in data
|
|
|
|
# /api/trading/decisions
|
|
resp = await trading_client.get("/api/trading/decisions")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 20 Trading
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTradingPage:
|
|
"""Trading page depends on: trading status."""
|
|
|
|
async def test_trading_page_deps(self, trading_client, seed_ids):
|
|
resp = await trading_client.get("/api/trading/status")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, dict) and "enabled" in data
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 21 Watchlists
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestWatchlistsPage:
|
|
"""Watchlists page depends on: /watchlists (registry client)."""
|
|
|
|
async def test_watchlists_page_deps(self, registry_client, seed_ids):
|
|
resp = await registry_client.get("/watchlists")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
# Watchlists may be empty — just verify 200 + list type
|
|
assert isinstance(data, list)
|