ITARS_Ver-2.0 / tests /test_api_persistence.py
Eklavya73's picture
fix(gate): activate two-stage confidence gate (audit K2 / F3)
497f442
Raw
History Blame Contribute Delete
6.29 kB
"""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