forked from HAL9000/cleveragents-core
051ee7c290
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
356 lines
14 KiB
Python
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"
|