from datetime import UTC, datetime from pathlib import Path from fastapi.testclient import TestClient from app import main as main_module from app.demo import DEMO_ROOT_CAUSE_NAME, build_demo_records from app.models import HealthStatus, ProbeResult, ServiceCreate, ServiceKind from app.store import Store def _client(tmp_path: Path) -> tuple[Store, TestClient]: store = Store(str(tmp_path / "servicemap.db")) main_module.store = store return store, TestClient(main_module.app) def _assert_acyclic(graph: dict[str, set[str]]) -> None: visiting: set[str] = set() visited: set[str] = set() def visit(service_id: str) -> None: assert service_id not in visiting if service_id in visited: return visiting.add(service_id) for dependency_id in graph[service_id]: visit(dependency_id) visiting.remove(service_id) visited.add(service_id) for service_id in graph: visit(service_id) def test_demo_seeds_topology_health_history_availability_and_incident(tmp_path: Path) -> None: store, client = _client(tmp_path) response = client.post("/api/v1/demo") assert response.status_code == 200 result = response.json() assert result["active"] is True assert result["synthetic"] is True assert result["scenario_name"] == "Northstar Platform" assert result["total"] == 9 snapshots = client.get("/api/v1/services").json() assert len(snapshots) == 9 by_name = {item["service"]["name"]: item for item in snapshots} assert set(by_name) == { "Public Endpoint", "Reverse Proxy", "Web Frontend", "Auth Service", "API Gateway", "PostgreSQL", "Redis", "Background Worker", "Object Storage", } ids = {item["service"]["id"] for item in snapshots} assert all(set(item["service"]["depends_on"]).issubset(ids) for item in snapshots) assert by_name[DEMO_ROOT_CAUSE_NAME]["latest_probe"]["status"] == HealthStatus.DOWN assert by_name[DEMO_ROOT_CAUSE_NAME]["effective_status"] == HealthStatus.DOWN assert all(item["effective_status"] != HealthStatus.UNKNOWN for item in snapshots) semantic = client.get("/api/v1/topology/semantic").json() assert {item["name"] for item in semantic["domains"]} == {"Edge", "Application", "Data"} assert {item["name"] for item in semantic["applications"]} == { "Public Gateway", "Northstar App", "Data Platform", "Worker Platform", } incidents = client.get("/api/v1/incidents").json() assert incidents["active"] is True assert incidents["candidates"][0]["service_name"] == DEMO_ROOT_CAUSE_NAME assert set(incidents["candidates"][0]["affected_service_names"]) == { "API Gateway", "Web Frontend", "Reverse Proxy", "Public Endpoint", "Background Worker", } postgres_id = by_name[DEMO_ROOT_CAUSE_NAME]["service"]["id"] history = client.get(f"/api/v1/services/{postgres_id}/history?limit=50").json() assert len(history) == 24 availability = client.get(f"/api/v1/services/{postgres_id}/availability").json() assert availability["total_checks"] == 24 assert availability["uptime_percent"] < 100 assert availability["down_checks"] == 2 assert store.demo_active() is True def test_demo_reset_is_deterministic_and_exit_restores_topology_and_probes(tmp_path: Path) -> None: store, client = _client(tmp_path) original = store.create_service( ServiceCreate(name="Existing API", kind=ServiceKind.HTTP, url="https://existing.test") ) original_probe = ProbeResult( id="existing-probe", service_id=original.id, status=HealthStatus.HEALTHY, latency_ms=12.5, detail="existing data", checked_at=datetime.now(UTC), ) store.save_probe(original_probe) original_service_json = client.get(f"/api/v1/services/{original.id}").json() first = client.post("/api/v1/demo").json() first_services = client.get("/api/v1/services").json() first_statuses = { item["service"]["name"]: (item["latest_probe"]["status"], item["effective_status"]) for item in first_services } assert first["restored"] is False assert client.get(f"/api/v1/services/{original.id}").status_code == 404 reset = client.post("/api/v1/demo/reset").json() reset_services = client.get("/api/v1/services").json() reset_statuses = { item["service"]["name"]: (item["latest_probe"]["status"], item["effective_status"]) for item in reset_services } assert reset["active"] is True assert reset["root_cause_service_name"] == DEMO_ROOT_CAUSE_NAME assert reset_statuses == first_statuses assert len(reset_services) == 9 exited = client.post("/api/v1/demo/exit").json() assert exited["active"] is False assert exited["restored"] is True assert client.get("/api/v1/demo").json() == { "active": False, "synthetic": False, "scenario_name": None, "service_count": 0, "root_cause_service_id": None, "root_cause_service_name": None, "affected_service_ids": [], "affected_service_names": [], "affected_count": 0, } assert client.get(f"/api/v1/services/{original.id}").json() == original_service_json assert store.probe_history(original.id)[0].id == original_probe.id with store._connect() as db: # regression assertion for FK-clean restoration orphan_count = db.execute( "SELECT COUNT(*) FROM probe_results p LEFT JOIN services s ON s.id = p.service_id WHERE s.id IS NULL" ).fetchone()[0] assert orphan_count == 0 orphan_evidence_count = db.execute( "SELECT COUNT(*) FROM health_evidence e LEFT JOIN services s ON s.id = e.service_id " "WHERE e.service_id IS NOT NULL AND s.id IS NULL" ).fetchone()[0] orphan_link_count = db.execute( "SELECT COUNT(*) FROM health_evidence_service_links link LEFT JOIN health_evidence e " "ON e.id = link.evidence_id WHERE e.id IS NULL" ).fetchone()[0] assert orphan_evidence_count == 0 assert orphan_link_count == 0 def test_demo_probe_endpoints_remain_external_service_free(tmp_path: Path) -> None: _, client = _client(tmp_path) client.post("/api/v1/demo") snapshots = client.get("/api/v1/services").json() postgres_id = next(item["service"]["id"] for item in snapshots if item["service"]["name"] == DEMO_ROOT_CAUSE_NAME) assert client.post(f"/api/v1/services/{postgres_id}/probe").status_code == 409 assert client.post("/api/v1/probe-all").json() == {"requested": 0, "completed": 0, "results": []} def test_demo_records_have_valid_graph_and_stable_ids() -> None: fixed_now = datetime(2026, 9, 12, 10, 0, tzinfo=UTC) services_a, probes_a = build_demo_records(fixed_now) services_b, probes_b = build_demo_records(fixed_now) assert [service.model_dump(mode="json") for service in services_a] == [service.model_dump(mode="json") for service in services_b] assert [probe.model_dump(mode="json") for probe in probes_a] == [probe.model_dump(mode="json") for probe in probes_b] assert len(probes_a) == 9 * 24 graph = {service.id: set(service.depends_on) for service in services_a} _assert_acyclic(graph)