Files
smart-support/backend/tests/unit/replay/test_models.py
Yaojia Wang 33db5aeb10 feat: complete phase 4 -- conversation replay API + analytics dashboard
- Replay models: StepType enum, ReplayStep, ReplayPage frozen dataclasses
- Checkpoint transformer: PostgresSaver JSONB -> structured timeline steps
- Replay API: GET /api/conversations (paginated), GET /api/replay/{thread_id}
- Analytics models: AgentUsage, InterruptStats, AnalyticsResult
- Analytics event recorder: Protocol + PostgresAnalyticsRecorder + NoOp
- Analytics queries: resolution_rate, agent_usage, escalation_rate, cost, interrupts
- Analytics API: GET /api/analytics?range=Xd with envelope response
- DB migration: analytics_events table + conversations column additions
- 74 new tests, 399 total passing, 92.87% coverage
2026-03-31 13:35:45 +02:00

135 lines
4.2 KiB
Python

"""Unit tests for app.replay.models."""
from __future__ import annotations
import pytest
pytestmark = pytest.mark.unit
class TestStepType:
def test_all_step_types_exist(self) -> None:
from app.replay.models import StepType
assert StepType.user_message
assert StepType.supervisor_routing
assert StepType.tool_call
assert StepType.tool_result
assert StepType.agent_response
assert StepType.interrupt
def test_step_type_values(self) -> None:
from app.replay.models import StepType
assert StepType.user_message.value == "user_message"
assert StepType.tool_call.value == "tool_call"
assert StepType.agent_response.value == "agent_response"
class TestReplayStep:
def test_minimal_replay_step(self) -> None:
from app.replay.models import ReplayStep, StepType
step = ReplayStep(step=1, type=StepType.user_message, timestamp="2026-01-01T00:00:00Z")
assert step.step == 1
assert step.type == StepType.user_message
assert step.timestamp == "2026-01-01T00:00:00Z"
assert step.content == ""
assert step.agent is None
assert step.tool is None
assert step.params is None
assert step.result is None
assert step.reasoning is None
assert step.tokens is None
assert step.duration_ms is None
def test_full_replay_step(self) -> None:
from app.replay.models import ReplayStep, StepType
step = ReplayStep(
step=2,
type=StepType.tool_call,
timestamp="2026-01-01T00:00:01Z",
content="calling get_order",
agent="order_agent",
tool="get_order_status",
params={"order_id": "ORD-123"},
result={"status": "shipped"},
reasoning="user asked about order",
tokens=50,
duration_ms=200,
)
assert step.step == 2
assert step.agent == "order_agent"
assert step.tool == "get_order_status"
assert step.params == {"order_id": "ORD-123"}
assert step.tokens == 50
def test_replay_step_is_frozen(self) -> None:
from app.replay.models import ReplayStep, StepType
step = ReplayStep(step=1, type=StepType.user_message, timestamp="2026-01-01T00:00:00Z")
with pytest.raises((AttributeError, TypeError)):
step.step = 99 # type: ignore[misc]
def test_replay_step_params_is_immutable_copy(self) -> None:
from app.replay.models import ReplayStep, StepType
params = {"key": "value"}
step = ReplayStep(
step=1,
type=StepType.tool_call,
timestamp="2026-01-01T00:00:00Z",
params=params,
)
# Modifying original dict should not affect step
params["new_key"] = "new_value"
assert "new_key" not in (step.params or {})
class TestReplayPage:
def test_replay_page_construction(self) -> None:
from app.replay.models import ReplayPage, ReplayStep, StepType
steps = (
ReplayStep(step=1, type=StepType.user_message, timestamp="2026-01-01T00:00:00Z"),
ReplayStep(step=2, type=StepType.agent_response, timestamp="2026-01-01T00:00:01Z"),
)
page = ReplayPage(
thread_id="thread-123",
total_steps=2,
page=1,
per_page=20,
steps=steps,
)
assert page.thread_id == "thread-123"
assert page.total_steps == 2
assert page.page == 1
assert page.per_page == 20
assert len(page.steps) == 2
def test_replay_page_is_frozen(self) -> None:
from app.replay.models import ReplayPage
page = ReplayPage(
thread_id="t1",
total_steps=0,
page=1,
per_page=20,
steps=(),
)
with pytest.raises((AttributeError, TypeError)):
page.page = 2 # type: ignore[misc]
def test_replay_page_empty_steps(self) -> None:
from app.replay.models import ReplayPage
page = ReplayPage(
thread_id="t1",
total_steps=0,
page=1,
per_page=20,
steps=(),
)
assert page.steps == ()