Files
temp/features/steps/tui_persona_state_coverage_steps.py
freemo 051ee7c290 test(coverage): add Behave BDD tests to improve coverage across 52 source files
Added 52 new .feature files and corresponding _steps.py files targeting
previously uncovered code paths in the following areas:

- TUI layer: app, commands, persona (state/schema/registry), widgets,
  input (shell_exec, reference_parser)
- Application services: plan lifecycle/service/executor, session,
  project, repo indexing, correction, checkpoint, actor, llm_actors,
  strategy coordinator, resource file watcher, service retry wiring
- CLI commands: session, resource, repl, plan, db, automation_profile
- Domain models: retry_policy, resource_type, cost_budget,
  docker_compose_analyzer, detail_level, _sql_string_aware,
  _postgresql_helpers
- Core: circuit_breaker, retry_service_patterns
- Infrastructure: repositories, transaction_sandbox, strategy_registry,
  plugins/loader, container
- Config: settings
- Agents: plan_generation, context_analysis, auto_debug
- A2A: facade

All new tests follow the Behave/Gherkin BDD standard. Resolved step
definition collisions with unique prefixes. Fixed Alembic fileConfig
logger disabling issue (disable_existing_loggers=False).

ISSUES CLOSED: #1068
2026-03-20 21:22:10 +00:00

356 lines
14 KiB
Python

"""Step definitions for tui_persona_state_coverage.feature.
These steps target specific uncovered lines in tui/persona/state.py:
- Lines 20-21: _resolve_default_name() body
- Line 25: active_name() cache-miss branch
- Lines 32-33: active_persona() fallback to ensure_default
- Line 39: set_active_persona() ValueError for unknown persona
- Line 48: current_preset() cache-miss sets "default"
- Lines 54-55: cycle_preset() when persona has no argument_presets
- Lines 59-60: cycle_preset() when current preset not in names list
- Lines 67-69: effective_arguments() delegation
"""
from types import SimpleNamespace
from unittest.mock import MagicMock
from behave import given, then, when
from cleveragents.tui.persona.schema import Persona, PersonaPreset
from cleveragents.tui.persona.state import PersonaState
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_persona(
name: str,
actor: str = "ns/actor",
presets: list[PersonaPreset] | None = None,
base_arguments: dict | None = None,
) -> Persona:
"""Build a real Persona, optionally with custom presets."""
kwargs: dict = {"name": name, "actor": actor}
if presets is not None:
kwargs["argument_presets"] = presets
if base_arguments is not None:
kwargs["base_arguments"] = base_arguments
return Persona(**kwargs)
def _make_mock_registry(
default_persona: Persona | None = None,
last_persona: str | None = None,
get_map: dict[str, Persona | None] | None = None,
) -> MagicMock:
"""Return a MagicMock that quacks like PersonaRegistry."""
registry = MagicMock()
dp = default_persona or _make_persona("default")
registry.ensure_default.return_value = dp
registry.get_last_persona.return_value = last_persona
registry.set_last_persona = MagicMock()
_get_map = get_map or {}
def _get_side_effect(name: str) -> Persona | None:
return _get_map.get(name)
registry.get.side_effect = _get_side_effect
return registry
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("a mock persona registry is prepared")
def step_prepare_mock_registry(context):
"""Prepare a default mock registry; scenarios override as needed."""
context.mock_registry = _make_mock_registry()
context.state = PersonaState(registry=context.mock_registry)
# ---------------------------------------------------------------------------
# Scenario: active_name resolves default when session is new (lines 20-21, 25)
# ---------------------------------------------------------------------------
@when('I request the active name for a new session "{session_id}"')
def step_request_active_name_new_session(context, session_id):
context.result_active_name = context.state.active_name(session_id)
@then("the active name should be the resolved default persona name")
def step_verify_resolved_default_name(context):
# _resolve_default_name calls ensure_default() then get_last_persona()
# With last_persona returning None, it falls back to default.name
assert context.result_active_name == "default"
context.mock_registry.ensure_default.assert_called()
context.mock_registry.get_last_persona.assert_called()
# ---------------------------------------------------------------------------
# Scenario: active_name returns cached name on second call
# ---------------------------------------------------------------------------
@given('session "{session_id}" already has active persona "{name}"')
def step_session_has_active_persona(context, session_id, name):
context.state.active_by_session[session_id] = name
@then('the active name should be "{expected}"')
def step_verify_active_name(context, expected):
assert context.result_active_name == expected
# ---------------------------------------------------------------------------
# Scenario: active_persona falls back to default (lines 32-33)
# ---------------------------------------------------------------------------
@given('session "{session_id}" is bound to a persona name not in the registry')
def step_session_bound_to_missing_persona(context, session_id):
# Set session to a name that registry.get() will return None for
context.state.active_by_session[session_id] = "vanished"
# Registry get returns None for "vanished", ensure_default returns default
default_persona = _make_persona("default")
context.mock_registry = _make_mock_registry(
default_persona=default_persona,
get_map={}, # "vanished" not in map → returns None
)
context.state = PersonaState(
registry=context.mock_registry,
active_by_session={session_id: "vanished"},
)
@when('I request the active persona for session "{session_id}"')
def step_request_active_persona(context, session_id):
context.result_persona = context.state.active_persona(session_id)
context.last_session_id = session_id
@then("the persona should be the registry default")
def step_verify_persona_is_default(context):
assert context.result_persona.name == "default"
@then("the session should be rebound to the default persona name")
def step_verify_session_rebound(context):
assert context.state.active_by_session[context.last_session_id] == "default"
# ---------------------------------------------------------------------------
# Scenario: active_persona returns the looked-up persona when found
# ---------------------------------------------------------------------------
@given('session "{session_id}" is bound to a persona name that exists in the registry')
def step_session_bound_to_existing_persona(context, session_id):
found_persona = _make_persona("researcher", actor="ns/researcher")
context.mock_registry = _make_mock_registry(
get_map={"researcher": found_persona},
)
context.state = PersonaState(
registry=context.mock_registry,
active_by_session={session_id: "researcher"},
)
@then("the persona should be the one from the registry")
def step_verify_persona_from_registry(context):
assert context.result_persona.name == "researcher"
# ---------------------------------------------------------------------------
# Scenario: set_active_persona skips preset init when session already has one
# ---------------------------------------------------------------------------
@given('the preset for session "{session_id}" is already set to "{preset_name}"')
def step_preset_already_set(context, session_id, preset_name):
persona = _make_persona("coder", actor="ns/coder")
context.mock_registry = _make_mock_registry(get_map={"coder": persona})
context.state = PersonaState(
registry=context.mock_registry,
preset_by_session={session_id: preset_name},
)
context._preset_preconfigured = True
@then('the preset for session "{session_id}" should still be "{expected}"')
def step_verify_preset_unchanged(context, session_id, expected):
assert context.state.preset_by_session[session_id] == expected
# ---------------------------------------------------------------------------
# Scenario: set_active_persona raises ValueError (line 39)
# ---------------------------------------------------------------------------
@when('I try to set an unknown persona "{name}" for session "{session_id}"')
def step_set_unknown_persona(context, name, session_id):
try:
context.state.set_active_persona(session_id, name)
context.caught_error = None
except ValueError as exc:
context.caught_error = exc
@then('tpscov a ValueError should be raised with message containing "{fragment}"')
def step_verify_value_error(context, fragment):
assert context.caught_error is not None, "Expected ValueError but none was raised"
assert fragment in str(context.caught_error), (
f"Expected '{fragment}' in '{context.caught_error}'"
)
# ---------------------------------------------------------------------------
# Scenario: set_active_persona sets and returns a known persona
# ---------------------------------------------------------------------------
@when('I set persona "{name}" for session "{session_id}"')
def step_set_known_persona(context, name, session_id):
# If a preceding Given step already prepared a state with the right
# registry (e.g. with preset_by_session pre-populated), reuse it.
# Otherwise build a fresh state for the persona.
if not getattr(context, "_preset_preconfigured", False):
persona = _make_persona(name, actor="ns/coder")
context.mock_registry = _make_mock_registry(get_map={name: persona})
context.state = PersonaState(registry=context.mock_registry)
context.result_persona = context.state.set_active_persona(session_id, name)
context.last_session_id = session_id
@then('the returned persona name should be "{expected}"')
def step_verify_returned_persona_name(context, expected):
assert context.result_persona.name == expected
@then('session "{session_id}" should have active persona "{expected}"')
def step_verify_session_active_persona(context, session_id, expected):
assert context.state.active_by_session[session_id] == expected
@then('the registry last persona should be set to "{expected}"')
def step_verify_last_persona_set(context, expected):
context.mock_registry.set_last_persona.assert_called_with(expected)
# ---------------------------------------------------------------------------
# Scenario: current_preset returns default for a new session (line 48)
# ---------------------------------------------------------------------------
@when('I request the current preset for new session "{session_id}"')
def step_request_current_preset(context, session_id):
context.result_preset = context.state.current_preset(session_id)
@then('the current preset should be "{expected}"')
def step_verify_current_preset(context, expected):
assert context.result_preset == expected
# ---------------------------------------------------------------------------
# Scenario: cycle_preset with no argument presets (lines 54-55)
# ---------------------------------------------------------------------------
@given('a persona with no argument presets is active for session "{session_id}"')
def step_persona_no_presets_active(context, session_id):
# Build a persona-like object with empty argument_presets to hit lines 54-55.
# The real Persona validator always adds a default preset, so we use a
# SimpleNamespace stand-in that active_persona will return.
fake_persona = SimpleNamespace(
name="bare",
argument_presets=[],
base_arguments={},
)
fake_persona.effective_arguments = lambda preset: {}
registry = MagicMock()
registry.get.return_value = fake_persona
registry.ensure_default.return_value = fake_persona
context.state = PersonaState(
registry=registry,
active_by_session={session_id: "bare"},
)
@when('I cycle the preset for session "{session_id}"')
def step_cycle_preset(context, session_id):
context.result_cycled_preset = context.state.cycle_preset(session_id)
@then('the cycled preset should be "{expected}"')
def step_verify_cycled_preset(context, expected):
assert context.result_cycled_preset == expected
# ---------------------------------------------------------------------------
# Scenario: cycle_preset when current preset not in names list (lines 59-60)
# ---------------------------------------------------------------------------
@given('a persona with presets "{preset_csv}" is active for session "{session_id}"')
def step_persona_with_presets_active(context, preset_csv, session_id):
preset_names = [n.strip() for n in preset_csv.split(",")]
presets = []
for pname in preset_names:
overrides = {} if pname == "default" else {"mode": pname}
presets.append(PersonaPreset(name=pname, display=pname, overrides=overrides))
persona = _make_persona("multi", presets=presets, base_arguments={"x": 1})
registry = MagicMock()
registry.get.return_value = persona
registry.ensure_default.return_value = persona
context.state = PersonaState(
registry=registry,
active_by_session={session_id: "multi"},
)
context.active_persona_for_test = persona
@given('the current preset for session "{session_id}" is "{preset_name}"')
def step_set_current_preset(context, session_id, preset_name):
context.state.preset_by_session[session_id] = preset_name
# ---------------------------------------------------------------------------
# Scenario: effective_arguments delegates (lines 67-68-69)
# ---------------------------------------------------------------------------
@given('a persona with base arguments and presets is active for session "{session_id}"')
def step_persona_with_base_args_and_presets(context, session_id):
presets = [
PersonaPreset(name="default", display="default", overrides={}),
PersonaPreset(name="turbo", display="Turbo", overrides={"speed": "fast"}),
]
persona = _make_persona(
"worker", presets=presets, base_arguments={"model": "gpt-4", "temp": 0.7}
)
registry = MagicMock()
registry.get.return_value = persona
registry.ensure_default.return_value = persona
context.state = PersonaState(
registry=registry,
active_by_session={session_id: "worker"},
preset_by_session={session_id: "turbo"},
)
@when('I request the effective arguments for session "{session_id}"')
def step_request_effective_arguments(context, session_id):
context.result_eff_args = context.state.effective_arguments(session_id)
@then("the effective arguments should merge base and preset overrides")
def step_verify_effective_arguments(context):
assert context.result_eff_args["model"] == "gpt-4"
assert context.result_eff_args["temp"] == 0.7
assert context.result_eff_args["speed"] == "fast"