b82a9b6962
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / security (push) Waiting to run
CI / quality (push) Waiting to run
CI / unit_tests (push) Waiting to run
CI / integration_tests (push) Waiting to run
CI / e2e_tests (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / benchmark-regression (push) Blocked by required conditions
CI / benchmark-publish (push) Waiting to run
CI / build (push) Waiting to run
CI / docker (push) Blocked by required conditions
CI / helm (push) Waiting to run
CI / status-check (push) Blocked by required conditions
Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
241 lines
8.8 KiB
Python
241 lines
8.8 KiB
Python
"""Step definitions for tui_commands_coverage.feature.
|
|
|
|
Targets uncovered lines in cleveragents.tui.commands:
|
|
- Lines 22-31: TuiCommandRouter.handle() method
|
|
- Lines 34-42: TuiCommandRouter._persona_command() method
|
|
- Lines 46-48: TuiCommandRouter._session_command() method
|
|
- Lines 51-64: run_tui() function (headless and non-headless)
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
|
|
from cleveragents.tui.commands import TuiCommandRouter, run_tui
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lightweight fakes for PersonaRegistry and PersonaState
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass
|
|
class FakePersona:
|
|
"""Minimal persona stand-in."""
|
|
|
|
name: str
|
|
|
|
|
|
@dataclass
|
|
class FakePersonaRegistry:
|
|
"""Registry that returns a canned list of personas."""
|
|
|
|
_personas: list[FakePersona] = field(default_factory=list)
|
|
personas_dir: Path = field(default_factory=lambda: Path("/tmp/fake-personas"))
|
|
|
|
def list_personas(self) -> list[FakePersona]:
|
|
return list(self._personas)
|
|
|
|
|
|
@dataclass
|
|
class FakePersonaState:
|
|
"""State that tracks set_active_persona calls."""
|
|
|
|
_active: dict[str, FakePersona] = field(default_factory=dict)
|
|
|
|
def set_active_persona(self, session_id: str, name: str) -> FakePersona:
|
|
persona = FakePersona(name=name)
|
|
self._active[session_id] = persona
|
|
return persona
|
|
|
|
def active_name(self, session_id: str) -> str:
|
|
p = self._active.get(session_id)
|
|
return p.name if p else "default"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
@given("the TUI commands module is imported")
|
|
def step_tui_commands_imported(context):
|
|
"""Verify the commands module is importable."""
|
|
assert TuiCommandRouter is not None
|
|
assert run_tui is not None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Router construction helpers
|
|
# ---------------------------------------------------------------------------
|
|
@given("a TuiCommandRouter with a mock registry and state")
|
|
def step_router_with_mock_deps(context):
|
|
"""Create a router with an empty registry and basic state."""
|
|
context.registry = FakePersonaRegistry()
|
|
context.state = FakePersonaState()
|
|
context.router = TuiCommandRouter(
|
|
persona_registry=context.registry, # type: ignore[arg-type]
|
|
persona_state=context.state, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@given("a TuiCommandRouter with a mock registry and state that supports set")
|
|
def step_router_with_set_support(context):
|
|
"""Create a router whose state can set an active persona."""
|
|
context.registry = FakePersonaRegistry()
|
|
context.state = FakePersonaState()
|
|
context.router = TuiCommandRouter(
|
|
persona_registry=context.registry, # type: ignore[arg-type]
|
|
persona_state=context.state, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@given("a TuiCommandRouter with a mock registry containing two personas")
|
|
def step_router_with_two_personas(context):
|
|
"""Create a router whose registry returns two personas."""
|
|
context.registry = FakePersonaRegistry(
|
|
_personas=[FakePersona(name="Alice"), FakePersona(name="Bob")]
|
|
)
|
|
context.state = FakePersonaState()
|
|
context.router = TuiCommandRouter(
|
|
persona_registry=context.registry, # type: ignore[arg-type]
|
|
persona_state=context.state, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
@given("a TuiCommandRouter with an empty mock registry")
|
|
def step_router_with_empty_registry(context):
|
|
"""Create a router whose registry returns no personas."""
|
|
context.registry = FakePersonaRegistry(_personas=[])
|
|
context.state = FakePersonaState()
|
|
context.router = TuiCommandRouter(
|
|
persona_registry=context.registry, # type: ignore[arg-type]
|
|
persona_state=context.state, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# handle() invocation
|
|
# ---------------------------------------------------------------------------
|
|
@when('I call handle with raw input "{raw}"')
|
|
def step_call_handle(context, raw):
|
|
"""Invoke TuiCommandRouter.handle() with the given raw string."""
|
|
context.handle_result = context.router.handle(raw, session_id="test-session")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# handle() assertions
|
|
# ---------------------------------------------------------------------------
|
|
@then('the handle result should be "{expected}"')
|
|
def step_handle_result_exact(context, expected):
|
|
assert context.handle_result == expected, (
|
|
f"Expected {expected!r}, got {context.handle_result!r}"
|
|
)
|
|
|
|
|
|
@then("the handle result should contain persona listing")
|
|
def step_handle_result_persona_listing(context):
|
|
"""An empty registry returns 'No personas'."""
|
|
assert context.handle_result == "No personas" or isinstance(
|
|
context.handle_result, str
|
|
)
|
|
|
|
|
|
@then('the handle result should start with "{prefix}"')
|
|
def step_handle_result_starts_with(context, prefix):
|
|
assert context.handle_result.startswith(prefix), (
|
|
f"Expected result to start with {prefix!r}, got {context.handle_result!r}"
|
|
)
|
|
|
|
|
|
@then('the handle result should contain "{substring}"')
|
|
def step_handle_result_contains(context, substring):
|
|
assert substring in context.handle_result, (
|
|
f"Expected {substring!r} in result, got {context.handle_result!r}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# run_tui() headless scenario
|
|
# ---------------------------------------------------------------------------
|
|
@when("I call run_tui in headless mode with mocked dependencies")
|
|
def step_run_tui_headless(context):
|
|
"""Call run_tui(headless=True) with fully mocked container."""
|
|
fake_personas_dir = Path("/tmp/fake-tui-headless-personas")
|
|
|
|
mock_registry = MagicMock()
|
|
mock_registry.list_personas.return_value = []
|
|
mock_registry.personas_dir = fake_personas_dir
|
|
mock_registry.ensure_default.return_value = FakePersona(name="default")
|
|
mock_registry.get_last_persona.return_value = None
|
|
|
|
mock_state = MagicMock()
|
|
mock_state.active_name.return_value = "default"
|
|
|
|
mock_container = MagicMock()
|
|
mock_container.persona_registry.return_value = mock_registry
|
|
mock_container.persona_state.return_value = mock_state
|
|
|
|
captured = StringIO()
|
|
with (
|
|
patch("cleveragents.tui.commands.get_container", return_value=mock_container),
|
|
patch("cleveragents.tui.commands.PersonaRegistry", return_value=mock_registry),
|
|
patch("cleveragents.tui.commands.PersonaState", return_value=mock_state),
|
|
patch("builtins.print", side_effect=lambda *a, **kw: captured.write(str(a[0]))),
|
|
):
|
|
context.run_tui_rc = run_tui(headless=True)
|
|
context.run_tui_output = captured.getvalue()
|
|
|
|
|
|
@when("I call run_tui in non-headless mode with mocked dependencies")
|
|
def step_run_tui_non_headless(context):
|
|
"""Call run_tui(headless=False) with a mocked app."""
|
|
mock_registry = MagicMock()
|
|
mock_state = MagicMock()
|
|
|
|
mock_container = MagicMock()
|
|
mock_container.persona_registry.return_value = mock_registry
|
|
mock_container.persona_state.return_value = mock_state
|
|
|
|
mock_app = MagicMock()
|
|
mock_app.run = MagicMock()
|
|
|
|
with (
|
|
patch("cleveragents.tui.commands.get_container", return_value=mock_container),
|
|
patch("cleveragents.tui.commands.PersonaRegistry", return_value=mock_registry),
|
|
patch("cleveragents.tui.commands.PersonaState", return_value=mock_state),
|
|
patch(
|
|
"cleveragents.tui.commands.CleverAgentsTuiApp",
|
|
return_value=mock_app,
|
|
),
|
|
):
|
|
context.run_tui_rc = run_tui(headless=False)
|
|
context.mock_app = mock_app
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# run_tui() assertions
|
|
# ---------------------------------------------------------------------------
|
|
@then("the run_tui return code should be 0")
|
|
def step_verify_run_tui_rc(context):
|
|
assert context.run_tui_rc == 0, f"Expected return code 0, got {context.run_tui_rc}"
|
|
|
|
|
|
@then("the printed JSON should contain textual_available key")
|
|
def step_verify_json_textual_available(context):
|
|
payload = json.loads(context.run_tui_output)
|
|
assert "textual_available" in payload, (
|
|
f"Expected 'textual_available' key in {payload}"
|
|
)
|
|
|
|
|
|
@then("the printed JSON should contain help key")
|
|
def step_verify_json_help(context):
|
|
payload = json.loads(context.run_tui_output)
|
|
assert "help" in payload, f"Expected 'help' key in {payload}"
|
|
|
|
|
|
@then("the mocked app run method should have been called")
|
|
def step_verify_app_run_called(context):
|
|
context.mock_app.run.assert_called_once()
|