"""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, persona_state=context.state, ) @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, persona_state=context.state, ) @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, persona_state=context.state, ) @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, persona_state=context.state, ) # --------------------------------------------------------------------------- # 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}" ) # --------------------------------------------------------------------------- # 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()