"""Behave steps for TUI persona system.""" from __future__ import annotations import shutil import tempfile import threading from pathlib import Path from behave import given, then, when from behave.runner import Context from cleveragents.tui.persona.registry import PersonaRegistry from cleveragents.tui.persona.schema import Persona, PersonaPreset from cleveragents.tui.persona.state import PersonaState def _registry_for_temp_dir(path: Path) -> PersonaRegistry: return PersonaRegistry(config_dir=path) @given("a temporary TUI persona registry") def step_temp_registry(context: Context) -> None: temp_dir = Path(tempfile.mkdtemp()) context.tui_persona_dir = temp_dir context.tui_registry = _registry_for_temp_dir(temp_dir) context.add_cleanup(lambda: shutil.rmtree(str(temp_dir), ignore_errors=True)) @when('I build a TUI persona named "{name}" with actor "{actor}"') def step_build_persona(context: Context, name: str, actor: str) -> None: context.tui_persona = Persona(name=name, actor=actor) @then("the persona should include exactly one default preset") def step_default_preset(context: Context) -> None: defaults = [ item for item in context.tui_persona.argument_presets if item.name == "default" ] assert len(defaults) == 1 @when('I save a TUI persona named "{name}" with actor "{actor}"') def step_save_persona(context: Context, name: str, actor: str) -> None: persona = Persona(name=name, actor=actor) context.tui_registry.save(persona) @given('I save a TUI persona named "{name}" with actor "{actor}"') def step_given_save_persona(context: Context, name: str, actor: str) -> None: step_save_persona(context, name, actor) @given( 'I save TUI persona "{name}" with actor "{actor}" and extra preset "{preset_name}"' ) def step_save_persona_with_extra_preset( context: Context, name: str, actor: str, preset_name: str ) -> None: persona = Persona( name=name, actor=actor, argument_presets=[ PersonaPreset(name="default", display="default", overrides={}), PersonaPreset( name=preset_name, display=preset_name, overrides={"mode": "x"} ), ], ) context.tui_registry.save(persona) @given( 'I save a TUI persona named "{name}" with actor "{actor}" and cycle order {cycle:d}' ) def step_save_persona_cycle( context: Context, name: str, actor: str, cycle: int ) -> None: persona = Persona(name=name, actor=actor, cycle_order=cycle) context.tui_registry.save(persona) @when( 'I try to save a TUI persona named "{name}" with actor "{actor}" and cycle order {cycle:d}' ) def step_save_persona_cycle_fail( context: Context, name: str, actor: str, cycle: int ) -> None: context.tui_cycle_error = None try: context.tui_registry.save(Persona(name=name, actor=actor, cycle_order=cycle)) except Exception as exc: context.tui_cycle_error = exc @then('loading persona "{name}" should succeed') def step_loading_persona(context: Context, name: str) -> None: context.tui_loaded_persona = context.tui_registry.get(name) assert context.tui_loaded_persona is not None @then('the loaded persona actor should be "{actor}"') def step_loaded_actor(context: Context, actor: str) -> None: assert context.tui_loaded_persona.actor == actor @then("saving the second persona should fail with cycle order error") def step_cycle_error(context: Context) -> None: assert context.tui_cycle_error is not None assert "cycle_order" in str(context.tui_cycle_error) @when('I set active persona to "{persona_name}" for session "{session_id}"') def step_set_active_persona( context: Context, persona_name: str, session_id: str ) -> None: if not hasattr(context, "tui_state"): context.tui_state = PersonaState(registry=context.tui_registry) context.tui_state.set_active_persona(session_id, persona_name) @then('active persona for session "{session_id}" should be "{persona_name}"') def step_active_persona(context: Context, session_id: str, persona_name: str) -> None: persona = context.tui_state.active_persona(session_id) assert persona.name == persona_name @when('I cycle persona preset for session "{session_id}"') def step_cycle_preset(context: Context, session_id: str) -> None: if not hasattr(context, "tui_state"): context.tui_state = PersonaState(registry=context.tui_registry) context.tui_state.cycle_preset(session_id) @then('current preset for session "{session_id}" should be "{preset_name}"') def step_current_preset(context: Context, session_id: str, preset_name: str) -> None: assert context.tui_state.current_preset(session_id) == preset_name @given('I create malformed TUI persona file "{name}"') def step_create_malformed_persona_file(context: Context, name: str) -> None: malformed = context.tui_registry.personas_dir / f"{name}.yaml" context.tui_registry.ensure_dirs() malformed.write_text("name: broken\nactor:\n- not-a-string\n", encoding="utf-8") @when("I list TUI personas") def step_list_tui_personas(context: Context) -> None: context.tui_list_error = None context.tui_persona_names = [] try: personas = context.tui_registry.list_personas() context.tui_persona_names = [persona.name for persona in personas] except Exception as exc: context.tui_list_error = exc @then('TUI persona list should include "{name}"') def step_tui_persona_list_includes(context: Context, name: str) -> None: assert context.tui_list_error is None assert name in context.tui_persona_names @when('I export TUI persona "{name}" to "{output_path}"') def step_export_tui_persona(context: Context, name: str, output_path: str) -> None: context.tui_export_error = None try: context.tui_registry.export_persona(name, Path(output_path)) except Exception as exc: context.tui_export_error = exc @then('TUI export should fail with "{message}"') def step_tui_export_fails(context: Context, message: str) -> None: assert context.tui_export_error is not None assert message in str(context.tui_export_error) @when('I import TUI persona from "{input_path}"') def step_import_tui_persona(context: Context, input_path: str) -> None: context.tui_import_error = None try: context.tui_registry.import_persona(Path(input_path)) except Exception as exc: context.tui_import_error = exc @then('TUI import should fail with "{message}"') def step_tui_import_fails(context: Context, message: str) -> None: assert context.tui_import_error is not None assert message in str(context.tui_import_error) @when("I concurrently set TUI last persona names") def step_concurrent_set_last_persona_names(context: Context) -> None: context.tui_concurrency_errors = [] def _worker(persona_name: str) -> None: try: context.tui_registry.set_last_persona(persona_name) except Exception as exc: # pragma: no cover - assertion handled after join context.tui_concurrency_errors.append(exc) threads: list[threading.Thread] = [] for row in context.table: thread = threading.Thread(target=_worker, args=(row["name"],)) threads.append(thread) thread.start() for thread in threads: thread.join() @then("TUI concurrent update should complete without errors") def step_tui_concurrent_update_no_errors(context: Context) -> None: assert context.tui_concurrency_errors == []