204 lines
5.7 KiB
Python
204 lines
5.7 KiB
Python
"""Tests for AGUIConsoleRenderer."""
|
|
|
|
import pytest
|
|
from pydantic import BaseModel
|
|
from rich.console import Console
|
|
|
|
from haiku.rag.graph.agui.cli_renderer import AGUIConsoleRenderer
|
|
from haiku.rag.graph.agui.events import (
|
|
emit_activity,
|
|
emit_run_error,
|
|
emit_run_finished,
|
|
emit_run_started,
|
|
emit_state_delta,
|
|
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-test-run-id"),
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_step_started("plan"),
|
|
emit_step_finished("plan"),
|
|
emit_activity("m1", "planning", {"message": "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_multiple_snapshots():
|
|
"""Test that renderer handles multiple snapshots without errors."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_state_snapshot(SimpleState(value=2)),
|
|
]
|
|
|
|
# Should render both snapshots without error
|
|
result = await renderer.render(async_gen(events))
|
|
assert result is None # No run finished event
|
|
|
|
|
|
@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", {"message": "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_state_snapshots():
|
|
"""Test that state snapshots are rendered."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_state_snapshot(SimpleState(value=1)),
|
|
emit_state_snapshot(SimpleState(value=2)),
|
|
emit_run_finished("t1", "r1", {"done": True}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"done": True}
|
|
|
|
|
|
@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_snapshot_then_delta():
|
|
"""Test that renderer handles snapshot followed by deltas."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
state1 = SimpleState(value=1)
|
|
state2 = SimpleState(value=2)
|
|
state3 = SimpleState(value=3)
|
|
|
|
events = [
|
|
emit_state_snapshot(state1), # Initial snapshot
|
|
emit_state_delta(state1, state2), # Delta to value=2
|
|
emit_state_delta(state2, state3), # Delta to value=3
|
|
emit_run_finished("t1", "r1", {"complete": True}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"complete": True}
|
|
|
|
|
|
@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"}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_state_delta():
|
|
"""Test that renderer renders state deltas."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
state1 = SimpleState(value=1)
|
|
state2 = SimpleState(value=2)
|
|
|
|
events = [
|
|
emit_state_snapshot(state1), # Initial state
|
|
emit_state_delta(state1, state2), # Delta update
|
|
emit_run_finished("t1", "r1", None),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_state_delta_without_initial():
|
|
"""Test that renderer handles delta without initial state gracefully."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
state1 = SimpleState(value=1)
|
|
state2 = SimpleState(value=2)
|
|
|
|
# Send delta without initial snapshot - should still render it
|
|
events = [
|
|
emit_state_delta(state1, state2),
|
|
emit_run_finished("t1", "r1", {"ok": True}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"ok": True}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_renderer_lifecycle_events():
|
|
"""Test that lifecycle events are rendered properly."""
|
|
renderer = AGUIConsoleRenderer()
|
|
|
|
events = [
|
|
emit_run_started("thread-123", "run-abc-def-ghi"), # Long run ID
|
|
emit_step_started("search_one"),
|
|
emit_step_finished("search_one"),
|
|
emit_step_started("analyze_insights"),
|
|
emit_step_finished("analyze_insights"),
|
|
emit_run_finished("thread-123", "run-abc-def-ghi", {"complete": True}),
|
|
]
|
|
|
|
result = await renderer.render(async_gen(events))
|
|
assert result == {"complete": True}
|