Files
MobilityOps/backend/tests/test_integration_status.py
T
NuklearRabbitandClaude Sonnet 5 727c19a779 M9: MCP Hub locale/correlation propagation, real Hub health check, fix stale test image
Fixed two concrete gaps in the MCP knowledge-search endpoint: no locale field
existed at all (now nl-BE/en-GB/fr-BE, wired to the knowledge provider's
existing language param), and the correlation ID was always freshly minted,
ignoring any inbound X-Correlation-Id header. Added a shared dependency and
applied it to all four MCP endpoints so Fleet Ops's own audit log preserves
the Hub's real correlation ID end to end.

MCP_HUB_BASE_URL/MCP_PROVIDER_ID were declared in .env.example but never read
anywhere. Since the Hub's own registration is catalog-driven (it never needs
Fleet Ops to push a registration call), wired mcp_hub_base_url for a real Hub
reachability health check instead of an unneeded self-registration call.

Renamed Fleet Ops's own internal audit tool labels mobilityops_* -> fleet_ops_*
(mirrored in contracts/mcp-tools.json with mobilityops_* kept as deprecated
aliases); documented that the live Hub connector's own dotted tool namespace
is a separate, Hub-owned naming layer, deliberately not touched.

Automation page's MCP card now shows real evidence (last tool/client/count/
timestamp, honest no-evidence state) instead of just the registration flag.

Also fixed a real methodology gap found mid-session: compose.yaml's api
service has no bind mount, so `docker compose run --rm api` silently tests a
stale image until rebuilt. Re-ran every local gate after rebuilding; fixed one
genuinely stale test assertion and two lint line-length errors surfaced by
that rebuild. 176 tests passing, ruff clean, mypy clean (50 files).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 13:30:24 +02:00

123 lines
4.7 KiB
Python

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"])
# All 4 canonical workflows are built (RAGcore Procedure Sync has all 6 nodes saved
# live); it has no *run* evidence yet since it is deliberately left unpublished.
assert ragcore_sync["built"] is True
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