"""End-to-end persistence API tests: analyze -> recent / review-queue / review / feedback / analytics (Phase 6). Uses in-memory SQLite + a fake pipeline.""" import pytest pytest.importorskip("fastapi") pytest.importorskip("httpx") from fastapi.testclient import TestClient # noqa: E402 from backend.app import create_app # noqa: E402 from backend.core.config import SETTINGS # noqa: E402 class _Engine: index_size = 44160 duplicate_threshold = 0.7623 class _Artifacts: tag_list = list(range(28)) dept_prototypes = {f"D{i}": object() for i in range(10)} class RoutingFakePipeline: """Routes by text: text containing 'review' -> HUMAN_REVIEW, else AUTO_ROUTE.""" def __init__(self): self.artifacts = _Artifacts() self.duplicate_engine = _Engine() self.routing_sbert = object() def process_ticket(self, text, register=True, translate=True): review = "review" in text.lower() route = "HUMAN_REVIEW" if review else "AUTO_ROUTE" return { "ticket_id": f"t{abs(hash(text)) % 100000000:08d}", "status": "NOT DUPLICATE", "route": route, "department": "Technical_Support", "priority": "critical" if review else "high", "priority_confidence": float("nan") if review else 0.82, "confidence": 0.74 if review else 0.96, "review": review, "tags": "network_issue (0.93)", "message": "ok", "latency": 12.3, "is_duplicate": False, "duplicate_score": 0.21, "duplicate_text": None, "duplicate_matched_id": None, "explanation": "Ticket processed.", "explanation_struct": { "routing": {"plain": "p", "evidence": {"gate_rule": "margin_pass"}, "forensics": {}}, "duplicate": None, "priority": {"plain": "p", "evidence": {}, "forensics": {}}, }, "original_text": text, "routing_text": text, "detected_language": "en", "translated_text": text, "translation_applied": False, "routing": { "recommended_department": "Technical_Support", "margin": 0.2, "entropy": 1.6, "top_tag_votes": [ {"tag": "network_issue", "score": 0.93, "department": "Technical_Support"} ], }, } @pytest.fixture def client(db_factory): app = create_app(pipeline=RoutingFakePipeline(), session_factory=db_factory) return TestClient(app) def test_analyze_persists_and_lists_recent(client): r = client.post("/analyze-ticket", json={"text": "server is down"}) assert r.status_code == 200 ticket_id = r.json()["ticket_id"] recent = client.get("/tickets/recent").json() assert any(t["ticket_id"] == ticket_id for t in recent) detail = client.get(f"/tickets/{ticket_id}").json() assert detail["ticket_id"] == ticket_id assert detail["route"] == "AUTO_ROUTE" def test_ticket_detail_404(client): assert client.get("/tickets/doesnotexist").status_code == 404 def test_review_queue_flow(client): # A review-routed ticket lands in the queue. r = client.post("/analyze-ticket", json={"text": "please review this escalation"}) ticket_id = r.json()["ticket_id"] queue = client.get("/review-queue").json() assert any(item["ticket_id"] == ticket_id for item in queue) # Submit an override. review = client.post( f"/tickets/{ticket_id}/review", json={"action": "overridden", "final_department": "IT_Support"}, ) assert review.status_code == 200 assert review.json()["final_department"] == "IT_Support" # Queue is now empty; feedback recorded. assert client.get("/review-queue").json() == [] feedback = client.get("/feedback").json() assert any( f["ticket_id"] == ticket_id and f["final_department"] == "IT_Support" for f in feedback ) def test_review_bad_action_400(client): r = client.post("/analyze-ticket", json={"text": "needs review now"}) ticket_id = r.json()["ticket_id"] bad = client.post(f"/tickets/{ticket_id}/review", json={"action": "nonsense"}) assert bad.status_code == 400 def test_review_missing_ticket_404(client): r = client.post("/tickets/ghost/review", json={"action": "approved"}) assert r.status_code == 404 def test_analytics_summary(client): client.post("/analyze-ticket", json={"text": "server down one"}) client.post("/analyze-ticket", json={"text": "please review two"}) summary = client.get("/analytics/summary").json() assert summary["total_tickets"] == 2 assert summary["route_mode_counts"]["AUTO_ROUTE"] == 1 assert summary["route_mode_counts"]["HUMAN_REVIEW"] == 1 assert summary["language_counts"]["en"] == 2 def test_analytics_monitoring(client): # Two auto-routed (conf 0.96) and one review-routed (conf 0.74) ticket. client.post("/analyze-ticket", json={"text": "server down one"}) client.post("/analyze-ticket", json={"text": "disk full two"}) r = client.post("/analyze-ticket", json={"text": "please review three"}) review_id = r.json()["ticket_id"] client.post( f"/tickets/{review_id}/review", json={"action": "overridden", "final_department": "IT_Support"}, ) mon = client.get("/analytics/monitoring").json() assert mon["total_tickets"] == 3 hist = mon["confidence_histogram"] assert len(hist["bins"]) == 10 # The fake pipeline emits gate_rule="margin_pass" for every ticket. assert hist["thresholds"]["hybrid_floor"] == SETTINGS.hybrid_floor assert mon["gate_rule_counts"]["margin_pass"] == 3 # Auto tickets at 0.96 -> top bin; review ticket at 0.74 -> bin 7. assert hist["series"]["AUTO_ROUTE"][9] == 2 assert hist["series"]["HUMAN_REVIEW"][7] == 1 # The override produced a predicted->final flow link and one reroute. flow = {(f["predicted"], f["final"]): f["count"] for f in mon["predicted_vs_final"]} assert flow[("Technical_Support", "IT_Support")] == 1 assert mon["routing_accuracy"]["total_reviewed"] == 1 assert mon["routing_accuracy"]["changes"] == 1