from __future__ import annotations import sys import time from pathlib import Path from fastapi.testclient import TestClient BACKEND_DIR = Path(__file__).resolve().parents[1] if str(BACKEND_DIR) not in sys.path: sys.path.insert(0, str(BACKEND_DIR)) from app import main as main_mod class StubUnifiedCore: def normalize_query(self, message: str, mode: str = "auto"): # noqa: ANN001 raw = str(message or "") normalized = raw.replace("repalcement", "replacement") return { "original_message": raw, "normalized_message": normalized, "changed": raw != normalized, "corrections": [{"from": "repalcement", "to": "replacement"}] if raw != normalized else [], } def handle_message(self, message: str, state: dict, mode: str = "auto", audience: str = "auto", show_citations: bool = True): # noqa: ANN001 return { "assistant": "**Result**\n\nUnified stub answer.", "state": {"mode": mode, "pending": {}}, "prompt_version": "unified-1.0", "sources": [ { "id": "S1", "domain": "router_docs", "doc": "XR60-Datasheet.pdf", "relative_path": "/router_rag_files/01_documents/XR60-Datasheet.pdf", "chunk_id": "chunk-1", "excerpt": "XR60 spec excerpt", } ] if show_citations else [], "files": ["/router_rag_files/01_documents/XR60-Datasheet.pdf"], "effective_audience": "external", "meta": {"domain": "router_docs", "domain_label": "Router docs/specs"}, } def list_files(self): # noqa: ANN201 return {"router_docs": ["01_documents/XR60-Datasheet.pdf"]} def health(self): # noqa: ANN201 return {"ok": True, "domains": {"router_docs": {"ok": True}}} def record_router_workbook_feedback( # noqa: ANN001, ANN201 self, *, request_id: str, verdict: str, detail: str = "", meta: dict | None = None, ): return { "ok": True, "feedback": { "request_id": request_id, "verdict": verdict, "detail": detail, "retrieval_mode": str((meta or {}).get("retrieval_mode") or ""), }, } def handle_router_inventory_import( # noqa: ANN001 self, *, file_bytes: bytes | None, filename: str, pasted_text: str, mode: str = "router_lifecycle", audience: str = "auto", show_citations: bool = True, ): return { "assistant": "**Result**\n\nWorkbook-backed inventory import analyzed 2 row(s).", "state": {}, "prompt_version": "unified-1.0", "sources": [], "files": ["router_workbook.xlsx"], "effective_audience": "external", "meta": { "domain": "router_lifecycle", "domain_label": "Router lifecycle", "retrieval_mode": "deterministic_router_workbook_inventory_import", "router_fleet_view": { "title": "Workbook fleet normalization", "subtitle": "Per-row workbook normalization, lifecycle, and replacement confidence.", "source_label": "Uploaded inventory", "uploaded_filename": filename or "fleet.csv", "row_count": 2, "matched_count": 1, "unmatched_count": 1, "review_required_count": 1, "shown_row_count": 2, "truncated_count": 0, "rows": [], }, }, } def test_knowledgebase_message_endpoint_happy_path(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/message", json={ "message": "Compare RV50X vs XR60 from docs only", "state": {}, "mode": "router_docs", "audience": "auto", "show_citations": True, "request_id": "kb-req-123", }, headers={"x-request-id": "kb-req-header"}, ) assert res.status_code == 200 body = res.json() assert "Unified stub answer" in str(body.get("assistant") or "") assert body.get("meta", {}).get("domain") == "router_docs" assert body.get("meta", {}).get("request_id") == "kb-req-header" assert res.headers.get("x-request-id") == "kb-req-header" assert isinstance(body.get("suggested_replies"), list) assert isinstance(body.get("response_shell"), list) def test_knowledgebase_tabs_default_to_unified() -> None: with TestClient(main_mod.app) as client: res = client.get("/api/ui/tabs") assert res.status_code == 200 body = res.json() assert body.get("knowledgebase") is True assert body.get("pots_pricing_lite") is False def test_knowledgebase_tabs_expose_pots_pricing_lite_with_estimator_fallback(monkeypatch) -> None: monkeypatch.setenv("MASTERS_TOOLKIT_TAB_POTS_SAVINGS_ESTIMATOR_ENABLED", "true") with TestClient(main_mod.app) as client: res = client.get("/api/ui/tabs") assert res.status_code == 200 body = res.json() assert body.get("pots_estimator") is True assert body.get("pots_pricing_lite") is True def test_knowledgebase_message_hard_timeout_returns_guidance(monkeypatch) -> None: class SlowUnifiedCore: def handle_message(self, message: str, state: dict, mode: str = "auto", audience: str = "auto", show_citations: bool = True): # noqa: ANN001 time.sleep(0.05) return { "assistant": "late answer", "state": {}, "sources": [], "files": [], "meta": {"domain": "knowledgebase"}, } monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: SlowUnifiedCore()) monkeypatch.setenv("MASTERS_TOOLKIT_CHAT_HARD_TIMEOUT_S", "0.01") monkeypatch.setenv("UNIFIED_KB_FALLBACK_EXTRA_BUDGET_S", "0") with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/message", json={ "message": "very long synthesis request", "state": {"mode": "auto"}, "mode": "auto", "audience": "auto", "show_citations": True, }, ) assert res.status_code == 200 body = res.json() assert "paused this response" in str(body.get("assistant") or "").lower() assert body.get("meta", {}).get("retrieval_mode") == "hard_timeout_guidance" assert body.get("state", {}).get("pending", {}).get("type") == "clarify_speed" def test_knowledgebase_normalize_endpoint(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/normalize", json={"message": "need repalcement options", "mode": "auto"}, ) assert res.status_code == 200 body = res.json() assert body.get("changed") is True assert "replacement" in str(body.get("normalized_message") or "") def test_router_lifecycle_normalize_chat_message_preserves_distinct_customer_names() -> None: main_mod._get_knowledgebase_core.cache_clear() msg = "McDonalds 24 AER2200\nMcDonald's 12 MG52\nMcDonald 49 XR60" normalized, meta = main_mod._normalize_chat_message(msg, mode_hint="router_lifecycle") assert normalized == msg assert meta.get("changed") is False assert meta.get("corrections") == [] def test_router_lifecycle_normalize_chat_message_skips_instruction_wrapped_inventory_spellcheck() -> None: main_mod._get_knowledgebase_core.cache_clear() msg = ( "Normalize this list and give row-by-row lifecycle plus replacements: " "McDonalds 24 AER2200, McDonald's 12 MG52, McDonald 49 XR60" ) normalized, meta = main_mod._normalize_chat_message(msg, mode_hint="router_lifecycle") assert normalized == msg assert meta.get("changed") is False assert meta.get("corrections") == [] def test_knowledgebase_control_reset_endpoint(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/control", json={"action": "reset", "state": {"pending": {"type": "x"}}, "mode": "auto"}, headers={"x-request-id": "kb-control-1"}, ) assert res.status_code == 200 body = res.json() assert "Unified stub answer" in str(body.get("assistant") or "") assert body.get("meta", {}).get("request_id") == "kb-control-1" assert body.get("meta", {}).get("control_action") == "reset" assert res.headers.get("x-request-id") == "kb-control-1" def test_knowledgebase_router_inventory_import_endpoint(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/router_workbook/inventory/import", files={"file": ("fleet.csv", b"Customer,Model,Quantity\nDarden,Current-500,5\n", "text/csv")}, data={ "mode": "router_lifecycle", "audience": "auto", "show_citations": "true", }, ) assert res.status_code == 200 body = res.json() assert "Workbook-backed inventory import analyzed" in str(body.get("assistant") or "") assert body.get("meta", {}).get("retrieval_mode") == "deterministic_router_workbook_inventory_import" assert body.get("meta", {}).get("router_fleet_view", {}).get("uploaded_filename") == "fleet.csv" def test_knowledgebase_router_workbook_feedback_endpoint(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: res = client.post( "/api/knowledgebase/router_workbook/feedback", json={ "request_id": "kb-req-123", "verdict": "needs_followup", "detail": "This answer still needs a stronger compare explanation.", "meta": { "retrieval_mode": "deterministic_router_workbook_compare", "router_intelligence_intent": "compare", }, }, ) assert res.status_code == 200 body = res.json() assert body.get("ok") is True assert body.get("feedback", {}).get("request_id") == "kb-req-123" assert body.get("feedback", {}).get("verdict") == "needs_followup" def test_knowledgebase_telemetry_tracks_control_and_messages(monkeypatch) -> None: monkeypatch.setattr(main_mod, "_get_knowledgebase_core", lambda: StubUnifiedCore()) with TestClient(main_mod.app) as client: msg = client.post( "/api/knowledgebase/message", json={"message": "hello", "state": {}, "mode": "auto", "request_id": "telemetry-msg"}, ) ctl = client.post( "/api/knowledgebase/control", json={"action": "reset", "state": {}, "mode": "auto", "request_id": "telemetry-ctl"}, ) tel = client.get("/api/knowledgebase/telemetry") assert msg.status_code == 200 assert ctl.status_code == 200 assert tel.status_code == 200 body = tel.json() assert int(body.get("message_count") or 0) >= 1 assert int(body.get("control_count") or 0) >= 1 def test_get_knowledgebase_core_wires_rapid_router_catalog_provider(monkeypatch) -> None: import app.knowledgebase.core as kb_core_mod captured: dict = {} class FakeUnifiedCore: def __init__(self, **kwargs): # noqa: ANN003 captured.update(kwargs) class StubRapidRouterCore: def get_store_for_client(self): # noqa: ANN201 return {"config": {}, "products": [{"id": "stub_router"}]} def get_catalog_status(self): # noqa: ANN201 return {"loaded": True, "product_count": 1, "latest_import": {"filename": "stub_router_catalog.xlsx"}} monkeypatch.setattr(main_mod, "_get_router_rag_core", lambda: object()) monkeypatch.setattr(main_mod, "_get_router_core", lambda: object()) monkeypatch.setattr(main_mod, "_get_masters_core", lambda: object()) monkeypatch.setattr(main_mod, "_get_pots_core", lambda: object()) monkeypatch.setattr(main_mod, "_get_rapid_router_core", lambda: StubRapidRouterCore()) monkeypatch.setattr(kb_core_mod, "UnifiedKnowledgebaseCore", FakeUnifiedCore) main_mod._get_knowledgebase_core.cache_clear() try: core = main_mod._get_knowledgebase_core() assert isinstance(core, FakeUnifiedCore) provider = captured.get("rapid_router_catalog_provider") assert callable(provider) data = provider() assert isinstance(data, dict) assert data.get("products", [{}])[0].get("id") == "stub_router" workbook_provider = captured.get("rapid_router_intelligence_provider") assert callable(workbook_provider) workbook_core = workbook_provider() assert isinstance(workbook_core, StubRapidRouterCore) assert workbook_core.get_catalog_status().get("loaded") is True finally: main_mod._get_knowledgebase_core.cache_clear() def test_maybe_bootstrap_rapid_router_catalog_loads_detected_workbook(tmp_path, monkeypatch) -> None: workbook_path = tmp_path / "device_master_source_of_truth_v26_site_survey_integrated_export.xlsx" workbook_path.write_bytes(b"fake-workbook") loaded: dict[str, object] = {} class StubRapidRouterCore: def get_catalog_status(self): # noqa: ANN201 return {"loaded": False} def load_catalog_workbook(self, *, workbook_bytes: bytes, filename: str): # noqa: ANN201 loaded["bytes"] = workbook_bytes loaded["filename"] = filename return {"ok": True} monkeypatch.setattr( main_mod, "_find_rapid_router_catalog_workbook_candidate", lambda: { "found": True, "path": workbook_path, "filename": workbook_path.name, "searched": True, "searched_roots": [str(workbook_path.parent)], }, ) main_mod._set_rapid_router_catalog_bootstrap_status( found=False, path="", filename="", searched=False, load_attempted=False, load_succeeded=False, already_loaded=False, error="", ) main_mod._maybe_bootstrap_rapid_router_catalog(StubRapidRouterCore()) assert loaded["bytes"] == b"fake-workbook" assert loaded["filename"] == workbook_path.name status = main_mod._rapid_router_catalog_bootstrap_status() assert status["found"] is True assert status["path"] == str(workbook_path) assert status["load_attempted"] is True assert status["load_succeeded"] is True