from sqlalchemy import select from app.core.db import SessionLocal from app.models.outbox import OutboxEvent from app.seed_loader import reset_and_seed from app.services import demo_manifest from app.services.knowledge import KnowledgeHealth def test_demo_manifest_is_public(client): # No login call at all -- the demo-entry screen and badge need this before any # session exists. response = client.get("/api/v1/demo/manifest") assert response.status_code == 200 def test_demo_manifest_shape(client): body = client.get("/api/v1/demo/manifest").json() assert body["organization_name"] == "Northstar Mobility" assert body["demo_mode"] is True assert body["synthetic_data"] is True assert body["allow_reset"] is True assert body["timezone"] == "Europe/Brussels" assert body["guide_available"] is True assert set(body["required_roles"]) == {"operations_manager", "rental_employee"} assert body["last_reset_at"] is not None assert body["anchor_date"] is not None scenario_ids = {s["id"] for s in body["scenarios"]} assert scenario_ids == { "return-anomaly", "duplicate-customer", "booking-overlap", "automation-retry", "knowledge-question", } integration_keys = {i["key"] for i in body["integrations"]} assert integration_keys == {"n8n", "ragcore", "mcp_hub"} def test_demo_manifest_scenarios_ready_after_fresh_reset(client): db = SessionLocal() try: reset_and_seed(db) finally: db.close() body = client.get("/api/v1/demo/manifest").json() scenarios = {s["id"]: s for s in body["scenarios"]} for scenario_id, scenario in scenarios.items(): assert scenario["ready"] is True, f"{scenario_id} should be ready right after a reset" assert scenario["blocked_reason_code"] is None assert scenario["start_path"] def test_demo_manifest_ragcore_labelled_as_demo_mode_not_live(client): body = client.get("/api/v1/demo/manifest").json() ragcore = next(i for i in body["integrations"] if i["key"] == "ragcore") assert ragcore["status_code"] == "demoMode" def test_automation_scenario_requires_the_exact_prepared_failure(): db = SessionLocal() try: reset_and_seed(db) event = db.scalar( select(OutboxEvent).where(OutboxEvent.event_id == demo_manifest._FAILED_DEMO_EVENT_ID) ) assert event is not None event.last_error_code = "connectionError" db.flush() scenario = next( item for item in demo_manifest._scenarios(db) if item.id == "automation-retry" ) assert scenario.ready is False finally: db.rollback() db.close() def test_knowledge_scenario_rejects_available_provider_with_verified_empty_corpus( client, monkeypatch ): class EmptyKnowledgeProvider: def health(self, language="en-GB"): return KnowledgeHealth( provider="ragcore", available=True, detail="Ready, but no indexed documents.", tenant="fleet-ops", workspace="mobilityops", collection="procedures", document_count=0, source_document_count=4, reported_synced_document_count=None, reported_failed_document_count=None, last_sync_at=None, statistics_state="verified", ) monkeypatch.setattr(demo_manifest, "get_knowledge_provider", EmptyKnowledgeProvider) body = client.get("/api/v1/demo/manifest").json() scenario = next(item for item in body["scenarios"] if item["id"] == "knowledge-question") assert scenario["ready"] is False assert scenario["blocked_reason_code"] == "knowledgeUnavailable" ragcore = next(item for item in body["integrations"] if item["key"] == "ragcore") assert ragcore["status_code"] == "unavailable" def test_knowledge_scenario_rejects_local_sources_when_index_count_is_unverified( client, monkeypatch ): class SourceAwareKnowledgeProvider: def health(self, language="en-GB"): return KnowledgeHealth( provider="ragcore", available=True, detail="Ready; index count endpoint is unavailable.", tenant="fleet-ops", workspace="mobilityops", collection="procedures", document_count=None, source_document_count=4, reported_synced_document_count=None, reported_failed_document_count=None, last_sync_at=None, statistics_state="not_reported", ) monkeypatch.setattr(demo_manifest, "get_knowledge_provider", SourceAwareKnowledgeProvider) body = client.get("/api/v1/demo/manifest").json() scenario = next(item for item in body["scenarios"] if item["id"] == "knowledge-question") assert scenario["ready"] is False assert scenario["blocked_reason_code"] == "knowledgeUnavailable" ragcore = next(item for item in body["integrations"] if item["key"] == "ragcore") assert ragcore["status_code"] == "unavailable" def test_knowledge_scenario_requires_provider_availability_even_with_documents(): health = KnowledgeHealth( provider="ragcore", available=False, detail="Provider is unreachable.", tenant="fleet-ops", workspace="mobilityops", collection="procedures", document_count=4, source_document_count=4, reported_synced_document_count=None, reported_failed_document_count=None, last_sync_at=None, statistics_state="verified", ) assert demo_manifest._knowledge_scenario_ready(health) is False