personadesk-api / tests /test_analytics.py
Legal-i's picture
Initial PersonaDesk API push to Space
45fcd6e
"""Analytics โ€” in-your-control metrics from implementation_plan/06 ยง5."""
from tests._helpers import (
auth_headers,
create_creator,
create_persona,
enable_widget,
seed_approved_clip,
signup,
)
def _start_and_send(client, persona, text: str) -> str:
r = client.post("/widget/sessions", json={"public_key": persona["public_key"]})
sid = r.json()["id"]
client.post(f"/widget/sessions/{sid}/message", json={"text": text})
return sid
def test_analytics_basic_counts(client, fake_storage, no_redis):
token = signup(client, "an1@example.com")
creator = create_creator(client, token)
persona = create_persona(client, token, creator["id"])
enable_widget(client, token, persona["id"])
seed_approved_clip(client, token, persona["id"], intent_tag="pricing",
transcript="ื”ืžื—ื™ืจ X")
# 2 sessions: one served (pricing), one fallback (oos)
_start_and_send(client, persona, "ื›ืžื” ื–ื” ืขื•ืœื”?")
_start_and_send(client, persona, "ืžื™ ื’ื™ืœื” ืืช ืืžืจื™ืงื”")
r = client.get(
f"/personas/{persona['id']}/analytics", headers=auth_headers(token)
)
assert r.status_code == 200
body = r.json()
assert body["persona_id"] == persona["id"]
assert body["sessions_started"] == 2
assert body["sessions_completed"] == 2
assert body["avg_questions_per_session"] == 1.0
# 1 served + 1 fallback โ†’ fallback_rate = 0.5
assert body["fallback_rate"] == 0.5
# Reasons present (out_of_scope from the second message)
assert "out_of_scope" in body["fallback_rate_by_reason"]
# missing_topics โ€” only pricing is covered, the rest of core_intents are missing
assert "who_you_are" in body["missing_topics"]
assert "pricing" not in body["missing_topics"]
def test_analytics_lead_conversion(client, fake_storage, no_redis):
token = signup(client, "an2@example.com")
creator = create_creator(client, token)
persona = create_persona(client, token, creator["id"])
enable_widget(client, token, persona["id"])
# One session, one lead captured at the end
sid = _start_and_send(client, persona, "ืžื™ ื’ื™ืœื” ืืช ืืžืจื™ืงื”")
client.post(
f"/widget/sessions/{sid}/lead",
json={"name": "X", "contact": "x@x.io", "question_context": "ctx"},
)
r = client.get(
f"/personas/{persona['id']}/analytics", headers=auth_headers(token)
)
body = r.json()
assert body["leads_count"] == 1
assert body["sessions_completed"] == 1
assert body["lead_conversion_rate"] == 1.0
def test_analytics_cross_tenant_403(client, fake_storage, no_redis):
token_a = signup(client, "alice-a@example.com", org="AnA")
token_b = signup(client, "bob-a@example.com", org="AnB")
creator = create_creator(client, token_a)
persona = create_persona(client, token_a, creator["id"])
r = client.get(
f"/personas/{persona['id']}/analytics", headers=auth_headers(token_b)
)
assert r.status_code == 403
def test_analytics_top_intents_orders_by_count(client, fake_storage, no_redis):
token = signup(client, "an3@example.com")
creator = create_creator(client, token)
persona = create_persona(client, token, creator["id"])
enable_widget(client, token, persona["id"])
seed_approved_clip(client, token, persona["id"], intent_tag="pricing",
transcript="A")
seed_approved_clip(client, token, persona["id"], intent_tag="how_it_works",
transcript="B", filename="b.mp4")
# 3 pricing questions, 1 how_it_works
for _ in range(3):
_start_and_send(client, persona, "ื›ืžื” ื–ื” ืขื•ืœื”?")
_start_and_send(client, persona, "ืื™ืš ื”ืชื”ืœื™ืš ืขื•ื‘ื“?")
r = client.get(
f"/personas/{persona['id']}/analytics", headers=auth_headers(token)
)
top = r.json()["top_intents"]
assert top[0]["intent"] == "pricing"
assert top[0]["count"] == 3
intents = {row["intent"]: row["count"] for row in top}
assert intents.get("how_it_works") == 1