def test_integration_status_requires_operations_manager(employee_client): response = employee_client.get("/api/v1/integrations/status") assert response.status_code == 403 def test_integration_status_requires_authentication(client): response = client.get("/api/v1/integrations/status") assert response.status_code == 401 def test_integration_status_reflects_seeded_mixed_outcomes(ops_client): response = ops_client.get("/api/v1/integrations/status") assert response.status_code == 200 body = response.json() n8n = body["n8n"] assert n8n["dispatch_enabled"] is True assert n8n["succeeded"] >= 1 assert n8n["failed"] >= 1 # The seed deliberately carries both failed and succeeded events, so a single most- # recent-event read would misreport health -- the aggregate must call this "degraded", # not "operational" or "unavailable". assert n8n["state"] == "degraded" assert n8n["latest_success_at"] is not None assert n8n["latest_failure_at"] is not None mcp_hub = body["mcp_hub"] assert mcp_hub["registration_enabled"] is False assert mcp_hub["state"] == "not_configured" def test_integration_status_is_operational_once_all_failed_events_resolved(ops_client): failed = ops_client.get("/api/v1/workflows", params={"status": "failed"}).json() for run in failed: retried = ops_client.post(f"/api/v1/workflows/{run['event_id']}/retry") assert retried.status_code == 200 response = ops_client.get("/api/v1/integrations/status") body = response.json()["n8n"] assert body["failed"] == 0 assert body["state"] == "operational" def test_integration_status_lists_all_four_canonical_workflows(ops_client): body = ops_client.get("/api/v1/integrations/status").json()["n8n"] assert body["expected_workflow_count"] == 4 names = {w["name"] for w in body["workflows"]} assert names == { "Fleet Ops — Vehicle Return Orchestration", "Fleet Ops — Scheduled Data Quality Scan", "Fleet Ops — RAGcore Procedure Sync", "Fleet Ops — Workflow Error Handler", } ragcore_sync = next(w for w in body["workflows"] if "RAGcore" in w["name"]) assert ragcore_sync["built"] is False assert ragcore_sync["last_seen_at"] is None def test_integration_status_scheduled_scan_evidence_only_counts_service_runs(client, ops_client): from app.core.config import get_settings settings = get_settings() before = ops_client.get("/api/v1/integrations/status").json()["n8n"] scan_workflow = next( w for w in before["workflows"] if w["name"].endswith("Scheduled Data Quality Scan") ) assert scan_workflow["last_seen_at"] is None scan = client.post( "/api/v1/integrations/n8n/scheduled-scan", headers={"X-Service-Token": settings.n8n_callback_token}, ) assert scan.status_code == 200 after = ops_client.get("/api/v1/integrations/status").json()["n8n"] scan_workflow = next( w for w in after["workflows"] if w["name"].endswith("Scheduled Data Quality Scan") ) assert scan_workflow["last_seen_at"] is not None assert after["known_workflow_count"] > before["known_workflow_count"] def test_integration_status_reflects_error_handler_registrations(client, ops_client): import uuid from app.core.config import get_settings settings = get_settings() before = ops_client.get("/api/v1/integrations/status").json()["n8n"] execution_id = str(uuid.uuid4()) report = client.post( "/api/v1/integrations/n8n/workflow-error", json={ "workflow_id": "mobilityops-return-processing", "workflow_name": "Fleet Ops — Vehicle Return Orchestration", "execution_id": execution_id, "failed_at": "2026-08-04T10:15:00Z", "error_category": "httpError", "error_summary": "Simulated failure for status test", "trigger_context": "webhook", "attempt": 1, }, headers={"X-Service-Token": settings.n8n_callback_token}, ) assert report.status_code == 200 after = ops_client.get("/api/v1/integrations/status").json()["n8n"] assert ( after["error_handler"]["total_failures_registered"] == before["error_handler"]["total_failures_registered"] + 1 ) assert after["error_handler"]["latest_failure_workflow"] == ( "Fleet Ops — Vehicle Return Orchestration" ) handler_workflow = next( w for w in after["workflows"] if w["name"].endswith("Workflow Error Handler") ) assert handler_workflow["last_seen_at"] is not None