158 lines
4.2 KiB
Python
158 lines
4.2 KiB
Python
"""Tests for AGUIConsoleRenderer."""
|
|
|
|
import pytest
|
|
from pydantic import BaseModel
|
|
from rich.console import Console
|
|
|
|
from haiku.rag.agui.cli_renderer import AGUIConsoleRenderer
|
|
from haiku.rag.agui.events import (
|
|
emit_activity,
|
|
emit_run_error,
|
|
emit_run_finished,
|
|
emit_run_started,
|
|
emit_state_snapshot,
|
|
emit_step_finished,
|
|
emit_step_started,
|
|
emit_text_message,
|
|
)
|
|
|
|
|
|
class SimpleState(BaseModel):
|
|
"""Simple state for testing."""
|
|
|
|
value: int
|
|
|
|
|
|
async def async_gen(items):
|
|
"""Helper to create async generator from list."""
|
|
for item in items:
|
|
yield item
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_basic_flow():
|
|
"""Test basic event rendering flow."""
|
|
console = Console(file=None, force_terminal=False) # Don't actually print
|
|
renderer = AGUIConsoleRenderer(console)
|
|
|
|
events = [
|
|
emit_run_started("t1", "r1"),
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_step_started("plan"),
|
|
emit_activity("m1", "planning", "Planning research"),
|
|
emit_run_finished("t1", "r1", {"status": "complete"}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
|
|
assert result == {"status": "complete"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_state_diff():
|
|
"""Test that renderer computes state diffs correctly."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
# First state
|
|
old_state = {"value": 1, "text": "old", "nested": {"a": 1, "b": 2}}
|
|
# Second state with changes
|
|
new_state = {"value": 2, "text": "old", "nested": {"a": 1, "b": 3, "c": 4}}
|
|
|
|
diff = renderer._compute_diff(old_state, new_state)
|
|
|
|
assert diff == {
|
|
"value": 2,
|
|
"nested": {"b": 3, "c": 4}, # Only changed/new fields in nested
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_state_diff_no_changes():
|
|
"""Test that no diff is computed when states are equal."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
state = {"value": 1, "text": "test"}
|
|
diff = renderer._compute_diff(state, state)
|
|
|
|
assert diff == {}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_handles_all_event_types():
|
|
"""Test that renderer handles all event types without errors."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_run_started("t1", "r1"),
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_step_started("step1"),
|
|
emit_step_finished("step1"),
|
|
emit_text_message("message"),
|
|
emit_activity("m1", "work", "Working"),
|
|
emit_run_error("error occurred"),
|
|
emit_run_finished("t1", "r1", {"result": "done"}),
|
|
]
|
|
|
|
# Should not raise any exceptions
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"result": "done"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_initial_state():
|
|
"""Test that initial state is rendered."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_state_snapshot(SimpleState(value=2)),
|
|
]
|
|
|
|
await renderer.render(async_gen(events))
|
|
|
|
# After processing, internal state should be the last state
|
|
assert renderer._state == {"value": 2}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_no_result():
|
|
"""Test renderer when no RUN_FINISHED event."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_run_started("t1", "r1"),
|
|
emit_step_started("step1"),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_nested_state_diff():
|
|
"""Test nested state diff computation."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
old = {"level1": {"level2": {"value": 1, "text": "old"}, "other": "same"}}
|
|
|
|
new = {"level1": {"level2": {"value": 2, "text": "old"}, "other": "same"}}
|
|
|
|
diff = renderer._compute_diff(old, new)
|
|
|
|
# Should only show the changed nested value
|
|
assert diff == {"level1": {"level2": {"value": 2}}}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_with_empty_state():
|
|
"""Test renderer handles empty state gracefully."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
# Create a state with no changes
|
|
events = [
|
|
emit_step_started("step1"), # No state snapshot
|
|
emit_run_finished("t1", "r1", {"result": "ok"}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"result": "ok"}
|