Files
ServiceMap-Public/backend/tests/test_demo.py
T

187 lines
7.3 KiB
Python

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)