Files
smart-support/backend/tests/unit/replay/test_transformer.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

156 lines
5.4 KiB
Python

"""Unit tests for app.replay.transformer."""
from __future__ import annotations
import pytest
pytestmark = pytest.mark.unit
def _make_row(messages: list[dict], metadata: dict | None = None) -> dict:
"""Helper to build a checkpoint row with the given messages."""
return {
"thread_id": "thread-abc",
"checkpoint_id": "cp-001",
"checkpoint": {"channel_values": {"messages": messages}},
"metadata": metadata or {},
}
class TestTransformCheckpoints:
def test_empty_rows_returns_empty_list(self) -> None:
from app.replay.transformer import transform_checkpoints
result = transform_checkpoints([])
assert result == []
def test_human_message_produces_user_message_step(self) -> None:
from app.replay.models import StepType
from app.replay.transformer import transform_checkpoints
rows = [_make_row([{"type": "human", "content": "Hello, I need help"}])]
steps = transform_checkpoints(rows)
assert len(steps) == 1
assert steps[0].type == StepType.user_message
assert steps[0].content == "Hello, I need help"
assert steps[0].step == 1
def test_ai_message_with_content_produces_agent_response(self) -> None:
from app.replay.models import StepType
from app.replay.transformer import transform_checkpoints
rows = [
_make_row(
[{"type": "ai", "content": "I can help you with that.", "tool_calls": []}],
metadata={"writes": {"some_agent": "response"}},
)
]
steps = transform_checkpoints(rows)
assert len(steps) == 1
assert steps[0].type == StepType.agent_response
assert steps[0].content == "I can help you with that."
def test_ai_message_with_tool_calls_produces_tool_call_step(self) -> None:
from app.replay.models import StepType
from app.replay.transformer import transform_checkpoints
rows = [
_make_row(
[
{
"type": "ai",
"content": "",
"tool_calls": [
{
"name": "get_order_status",
"args": {"order_id": "ORD-123"},
"id": "call_abc",
}
],
}
]
)
]
steps = transform_checkpoints(rows)
assert len(steps) == 1
assert steps[0].type == StepType.tool_call
assert steps[0].tool == "get_order_status"
assert steps[0].params == {"order_id": "ORD-123"}
def test_tool_message_produces_tool_result_step(self) -> None:
from app.replay.models import StepType
from app.replay.transformer import transform_checkpoints
rows = [
_make_row(
[
{
"type": "tool",
"content": '{"status": "shipped"}',
"name": "get_order_status",
}
]
)
]
steps = transform_checkpoints(rows)
assert len(steps) == 1
assert steps[0].type == StepType.tool_result
assert steps[0].tool == "get_order_status"
def test_multiple_messages_sequential_steps(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [
_make_row(
[
{"type": "human", "content": "Help"},
{"type": "ai", "content": "Sure!", "tool_calls": []},
]
)
]
steps = transform_checkpoints(rows)
assert len(steps) == 2
assert steps[0].step == 1
assert steps[1].step == 2
def test_unknown_message_type_skipped(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [_make_row([{"type": "unknown_type", "content": "test"}])]
steps = transform_checkpoints(rows)
# Should not crash; unknown types may be skipped
assert isinstance(steps, list)
def test_row_missing_checkpoint_skipped(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [{"thread_id": "t1", "checkpoint_id": "cp1", "checkpoint": None, "metadata": {}}]
steps = transform_checkpoints(rows)
assert isinstance(steps, list)
def test_row_missing_messages_key_skipped(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [{"thread_id": "t1", "checkpoint_id": "cp1", "checkpoint": {}, "metadata": {}}]
steps = transform_checkpoints(rows)
assert isinstance(steps, list)
def test_multiple_rows_steps_are_continuous(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [
_make_row([{"type": "human", "content": "Q1"}]),
_make_row([{"type": "ai", "content": "A1", "tool_calls": []}]),
]
steps = transform_checkpoints(rows)
assert len(steps) == 2
assert steps[0].step == 1
assert steps[1].step == 2
def test_timestamps_are_strings(self) -> None:
from app.replay.transformer import transform_checkpoints
rows = [_make_row([{"type": "human", "content": "Hi"}])]
steps = transform_checkpoints(rows)
assert isinstance(steps[0].timestamp, str)