"""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) def test_list_content_joined_to_string(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ _make_row( [ { "type": "human", "content": [ {"text": "Hello"}, {"text": " world"}, ], } ] ) ] steps = transform_checkpoints(rows) assert len(steps) == 1 assert steps[0].content == "Hello world" def test_checkpoint_as_string_skipped(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ { "thread_id": "t1", "checkpoint_id": "cp1", "checkpoint": "not-a-dict", "metadata": {}, } ] steps = transform_checkpoints(rows) assert steps == [] def test_channel_values_not_dict_skipped(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ { "thread_id": "t1", "checkpoint_id": "cp1", "checkpoint": {"channel_values": "bad"}, "metadata": {}, } ] steps = transform_checkpoints(rows) assert steps == [] def test_tool_result_valid_json_parsed(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ _make_row( [ { "type": "tool", "content": '{"order_id": "123", "status": "shipped"}', "name": "get_order_status", } ] ) ] steps = transform_checkpoints(rows) assert len(steps) == 1 assert steps[0].result == {"order_id": "123", "status": "shipped"} def test_tool_result_invalid_json_wrapped(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ _make_row( [ { "type": "tool", "content": "not valid json", "name": "some_tool", } ] ) ] steps = transform_checkpoints(rows) assert len(steps) == 1 assert steps[0].result == {"raw": "not valid json"} def test_malformed_message_skipped_gracefully(self) -> None: from app.replay.transformer import transform_checkpoints rows = [ _make_row( [ {"type": "human", "content": "Good message"}, 42, # not a dict -- will raise in _step_from_message {"type": "ai", "content": "Response", "tool_calls": []}, ] ) ] steps = transform_checkpoints(rows) # The malformed message is skipped; the other two produce steps. assert len(steps) == 2 assert steps[0].step == 1 assert steps[1].step == 2