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
635 lines
23 KiB
Python
635 lines
23 KiB
Python
"""Step definitions for repl_cli_coverage_boost.feature.
|
|
|
|
These steps target specific uncovered lines in
|
|
cleveragents/cli/commands/repl.py:
|
|
- Line 222: _build_reference_catalog file-count break
|
|
- Lines 331-332: _expand_references category match, no fuzzy match
|
|
- Line 360: _reference_suggestions bare @-sign token skipped
|
|
- Lines 423-424: _print_persona_list empty list
|
|
- Lines 467, 469: _parse_persona_create unknown flag / missing --actor
|
|
- Lines 506-507: /persona set missing name
|
|
- Lines 529-530: /persona pick no personas
|
|
- Lines 558-559: /persona delete missing name
|
|
- Lines 566-567: /persona delete not found
|
|
- Lines 579-580: /persona export missing name
|
|
- Lines 584-585: /persona export not found
|
|
- Lines 386-389, 404-408: _run_shell_command edge cases
|
|
"""
|
|
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
|
|
from cleveragents.cli.commands.repl import (
|
|
_MAX_REFERENCE_FILES,
|
|
_best_fuzzy_match,
|
|
_build_reference_catalog,
|
|
_compose_prompt,
|
|
_expand_references,
|
|
_find_reference_candidates,
|
|
_get_prompt_context,
|
|
_handle_slash_command,
|
|
_parse_persona_create,
|
|
_print_persona_list,
|
|
_reference_cache,
|
|
_reference_suggestions,
|
|
_ReplSessionState,
|
|
_run_shell_command,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
@given("the repl module is imported")
|
|
def step_repl_module_imported(context):
|
|
"""Ensure the REPL module is importable."""
|
|
assert _build_reference_catalog is not None
|
|
assert _expand_references is not None
|
|
assert _handle_slash_command is not None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _build_reference_catalog: file limit break (line 222)
|
|
# ---------------------------------------------------------------------------
|
|
@given("a temporary directory with more files than the reference limit")
|
|
def step_create_many_files(context):
|
|
"""Create a temp dir with more than _MAX_REFERENCE_FILES files."""
|
|
context.tmpdir = tempfile.mkdtemp()
|
|
count = _MAX_REFERENCE_FILES + 50
|
|
for i in range(count):
|
|
Path(context.tmpdir, f"file_{i:04d}.txt").touch()
|
|
context.created_file_count = count
|
|
|
|
|
|
@when("I build the reference catalog from that directory")
|
|
def step_build_catalog_many_files(context):
|
|
"""Build the reference catalog with cwd set to the temp dir."""
|
|
# Clear cache to force rebuild
|
|
_reference_cache["cwd"] = None
|
|
_reference_cache["created_at"] = 0.0
|
|
_reference_cache["catalog"] = None
|
|
with patch(
|
|
"cleveragents.cli.commands.repl.Path.cwd", return_value=Path(context.tmpdir)
|
|
):
|
|
context.catalog_result = _build_reference_catalog()
|
|
|
|
|
|
@then("the catalog file list should be capped at the maximum")
|
|
def step_verify_catalog_capped(context):
|
|
"""Verify that the catalog doesn't exceed the max."""
|
|
file_count = len(context.catalog_result.get("file", []))
|
|
assert file_count <= _MAX_REFERENCE_FILES, (
|
|
f"Expected at most {_MAX_REFERENCE_FILES} files, got {file_count}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _expand_references: category match but no fuzzy match (lines 331-332)
|
|
# ---------------------------------------------------------------------------
|
|
@given("a reference catalog with an empty actor list")
|
|
def step_catalog_empty_actor(context):
|
|
"""Create a catalog with an empty actor category."""
|
|
context.test_catalog = {
|
|
"file": ["readme.md"],
|
|
"actor": [],
|
|
"tool": [],
|
|
"skill": [],
|
|
"project": [],
|
|
"plan": [],
|
|
}
|
|
|
|
|
|
@when('I expand the reference "{token}" using that catalog')
|
|
def step_expand_reference_token(context, token):
|
|
"""Expand references in a line using the test catalog."""
|
|
context.expanded_line = _expand_references(token, catalog=context.test_catalog)
|
|
|
|
|
|
@then('the token should remain unchanged as "{expected}"')
|
|
def step_verify_token_unchanged(context, expected):
|
|
"""Verify the token was not changed."""
|
|
assert context.expanded_line == expected, (
|
|
f"Expected '{expected}', got '{context.expanded_line}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _reference_suggestions: bare @-sign (line 360)
|
|
# ---------------------------------------------------------------------------
|
|
@given("a reference catalog with some files")
|
|
def step_catalog_with_files(context):
|
|
"""Create a catalog with some file entries."""
|
|
context.test_catalog = {
|
|
"file": ["readme.md", "src/main.py"],
|
|
"actor": ["ns/coder"],
|
|
"tool": [],
|
|
"skill": [],
|
|
"project": [],
|
|
"plan": [],
|
|
}
|
|
|
|
|
|
@when('I request suggestions for a line containing only "@"')
|
|
def step_suggestions_bare_at(context):
|
|
"""Request suggestions for a line with just '@'."""
|
|
context.suggestions = _reference_suggestions("@", catalog=context.test_catalog)
|
|
|
|
|
|
@then("no suggestions should be returned")
|
|
def step_verify_no_suggestions(context):
|
|
"""Verify empty suggestions."""
|
|
assert context.suggestions == [], f"Expected empty list, got {context.suggestions}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _print_persona_list: empty list (lines 423-424)
|
|
# ---------------------------------------------------------------------------
|
|
@given("a mock persona registry that returns no personas")
|
|
def step_mock_registry_no_personas(context):
|
|
"""Create a mock PersonaRegistry that returns an empty list."""
|
|
context.mock_registry = MagicMock()
|
|
context.mock_registry.list_personas.return_value = []
|
|
|
|
|
|
@when("I call print persona list")
|
|
def step_call_print_persona_list(context):
|
|
"""Call _print_persona_list with the mock registry."""
|
|
sessions = {"default": _ReplSessionState(name="default", active_persona="default")}
|
|
_print_persona_list(context.mock_registry, sessions, "default")
|
|
|
|
|
|
@then("the output should contain a no-personas warning")
|
|
def step_verify_no_personas_warning(context):
|
|
"""Verify _print_persona_list was called without errors on empty list."""
|
|
# The function prints to console; if it reached here it means
|
|
# lines 423-424 were executed (early return on empty list).
|
|
context.mock_registry.list_personas.assert_called_once()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_persona_create: unknown flags (line 467)
|
|
# ---------------------------------------------------------------------------
|
|
@when('I parse persona create with tokens "{token_str}"')
|
|
def step_parse_persona_create_tokens(context, token_str):
|
|
"""Parse persona create from a token string.
|
|
|
|
_parse_persona_create expects the full token list including 'persona'
|
|
at index 0, so we prepend it.
|
|
"""
|
|
tokens = ["persona", *shlex.split(token_str)]
|
|
try:
|
|
context.parsed_persona = _parse_persona_create(tokens)
|
|
context.parse_error = None
|
|
except ValueError as exc:
|
|
context.parsed_persona = None
|
|
context.parse_error = exc
|
|
|
|
|
|
@then('the resulting persona should have name "{name}" and actor "{actor}"')
|
|
def step_verify_persona_name_actor(context, name, actor):
|
|
"""Verify the persona was parsed correctly."""
|
|
assert context.parse_error is None, f"Unexpected error: {context.parse_error}"
|
|
assert context.parsed_persona.name == name
|
|
assert context.parsed_persona.actor == actor
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_persona_create: missing --actor (line 469)
|
|
# ---------------------------------------------------------------------------
|
|
@then("a ValueError about missing actor should be raised")
|
|
def step_verify_missing_actor_error(context):
|
|
"""Verify the ValueError about missing --actor."""
|
|
assert context.parse_error is not None
|
|
assert "actor" in str(context.parse_error).lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_persona_create: too few tokens (line 434)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I parse persona create with only two tokens")
|
|
def step_parse_persona_create_few_tokens(context):
|
|
"""Try to parse persona create with insufficient tokens (< 4 total)."""
|
|
try:
|
|
context.parsed_persona = _parse_persona_create(["persona", "create", "name"])
|
|
context.parse_error = None
|
|
except ValueError as exc:
|
|
context.parsed_persona = None
|
|
context.parse_error = exc
|
|
|
|
|
|
@then("a ValueError about usage should be raised")
|
|
def step_verify_usage_error(context):
|
|
"""Verify the ValueError about usage."""
|
|
assert context.parse_error is not None
|
|
assert "usage" in str(context.parse_error).lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_persona_create: --cycle-order invalid (lines 463-464)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I parse persona create with a non-integer cycle-order")
|
|
def step_parse_persona_create_bad_cycle_order(context):
|
|
"""Try to parse with a non-integer --cycle-order."""
|
|
tokens = [
|
|
"persona",
|
|
"create",
|
|
"mybot",
|
|
"--actor",
|
|
"ns/bot",
|
|
"--cycle-order",
|
|
"abc",
|
|
]
|
|
try:
|
|
context.parsed_persona = _parse_persona_create(tokens)
|
|
context.parse_error = None
|
|
except ValueError as exc:
|
|
context.parsed_persona = None
|
|
context.parse_error = exc
|
|
|
|
|
|
@then("a ValueError about cycle-order should be raised")
|
|
def step_verify_cycle_order_error(context):
|
|
"""Verify the ValueError about cycle-order."""
|
|
assert context.parse_error is not None
|
|
assert (
|
|
"cycle-order" in str(context.parse_error).lower()
|
|
or "cycle_order" in str(context.parse_error).lower()
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers for /persona slash commands
|
|
# ---------------------------------------------------------------------------
|
|
def _make_sessions():
|
|
"""Create a default sessions dict."""
|
|
return {"default": _ReplSessionState(name="default", active_persona="default")}
|
|
|
|
|
|
@given("a mock persona registry for slash commands")
|
|
def step_mock_registry_for_slash(context):
|
|
"""Create a standard mock registry for slash command tests."""
|
|
context.mock_registry = MagicMock()
|
|
context.mock_registry.list_personas.return_value = []
|
|
context.mock_registry.get_persona.return_value = None
|
|
context.mock_registry.delete_persona.return_value = False
|
|
context.sessions = _make_sessions()
|
|
context.current_session = "default"
|
|
|
|
|
|
@given("a mock persona registry that returns no personas for slash commands")
|
|
def step_mock_registry_no_personas_slash(context):
|
|
"""Create a mock registry with no personas for pick."""
|
|
context.mock_registry = MagicMock()
|
|
context.mock_registry.list_personas.return_value = []
|
|
context.sessions = _make_sessions()
|
|
context.current_session = "default"
|
|
|
|
|
|
@given("a mock persona registry that cannot delete for slash commands")
|
|
def step_mock_registry_cannot_delete(context):
|
|
"""Create a mock registry where delete returns False."""
|
|
context.mock_registry = MagicMock()
|
|
context.mock_registry.delete_persona.return_value = False
|
|
context.sessions = _make_sessions()
|
|
context.current_session = "default"
|
|
|
|
|
|
@given("a mock persona registry that cannot find persona for slash commands")
|
|
def step_mock_registry_cannot_find(context):
|
|
"""Create a mock registry where get_persona returns None."""
|
|
context.mock_registry = MagicMock()
|
|
context.mock_registry.get_persona.return_value = None
|
|
context.sessions = _make_sessions()
|
|
context.current_session = "default"
|
|
|
|
|
|
@when('I handle slash command "{cmd}"')
|
|
def step_handle_slash_command(context, cmd):
|
|
"""Handle a slash command."""
|
|
exit_code, session = _handle_slash_command(
|
|
cmd,
|
|
registry=context.mock_registry,
|
|
sessions=context.sessions,
|
|
current_session=context.current_session,
|
|
)
|
|
context.slash_exit_code = exit_code
|
|
context.current_session = session
|
|
|
|
|
|
@then("the repl slash exit code should be {code:d}")
|
|
def step_verify_exit_code(context, code):
|
|
"""Verify the exit code from slash command."""
|
|
assert context.slash_exit_code == code, (
|
|
f"Expected exit code {code}, got {context.slash_exit_code}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _run_shell_command: empty text (lines 386-387)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I run a shell command with empty text")
|
|
def step_run_shell_empty(context):
|
|
"""Run a shell command with empty text."""
|
|
context.shell_exit_code = _run_shell_command("")
|
|
|
|
|
|
@then("the shell exit code should be {code:d}")
|
|
def step_verify_shell_exit_code(context, code):
|
|
"""Verify the shell command exit code."""
|
|
assert context.shell_exit_code == code, (
|
|
f"Expected shell exit code {code}, got {context.shell_exit_code}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _run_shell_command: disabled (lines 388-389)
|
|
# ---------------------------------------------------------------------------
|
|
@given("shell mode is disabled via environment")
|
|
def step_disable_shell_mode(context):
|
|
"""Set CLEVERAGENTS_DISABLE_SHELL_MODE=1."""
|
|
context.old_disable_shell = os.environ.get("CLEVERAGENTS_DISABLE_SHELL_MODE")
|
|
os.environ["CLEVERAGENTS_DISABLE_SHELL_MODE"] = "1"
|
|
|
|
def restore():
|
|
if context.old_disable_shell is None:
|
|
os.environ.pop("CLEVERAGENTS_DISABLE_SHELL_MODE", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_DISABLE_SHELL_MODE"] = context.old_disable_shell
|
|
|
|
context.add_cleanup(restore)
|
|
|
|
|
|
@when('I run a shell command with text "{cmd}"')
|
|
def step_run_shell_with_text(context, cmd):
|
|
"""Run a shell command with given text."""
|
|
context.shell_exit_code = _run_shell_command(cmd)
|
|
|
|
|
|
@then("the shell mode disabled message is shown")
|
|
def step_verify_shell_disabled_msg(context):
|
|
"""Verify that the disabled message was shown (exit code 2 suffices)."""
|
|
assert context.shell_exit_code == 2
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _run_shell_command: timeout (lines 404-408)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I run a shell command that will time out")
|
|
def step_run_shell_timeout(context):
|
|
"""Run a shell command that times out by mocking subprocess.run."""
|
|
with patch(
|
|
"cleveragents.cli.commands.repl.subprocess.run",
|
|
side_effect=subprocess.TimeoutExpired(cmd="sleep 999", timeout=30),
|
|
):
|
|
context.shell_exit_code = _run_shell_command("sleep 999")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _compose_prompt
|
|
# ---------------------------------------------------------------------------
|
|
@when(
|
|
'I compose a prompt with base "{base}" and session "{session}" and persona "{persona}"'
|
|
)
|
|
def step_compose_prompt(context, base, session, persona):
|
|
"""Call _compose_prompt."""
|
|
state = _ReplSessionState(name=session, active_persona=persona)
|
|
context.composed_prompt = _compose_prompt(base + "> ", state)
|
|
|
|
|
|
@then("the prompt should contain session and persona markers")
|
|
def step_verify_prompt_markers(context):
|
|
"""Verify the composed prompt contains session and persona info."""
|
|
assert "[" in context.composed_prompt
|
|
assert "]" in context.composed_prompt
|
|
assert "s1" in context.composed_prompt
|
|
assert "coder" in context.composed_prompt
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _get_prompt_context
|
|
# ---------------------------------------------------------------------------
|
|
@given('rclcb CLEVERAGENTS_PROJECT is set to "{value}"')
|
|
def step_set_project(context, value):
|
|
"""Set the CLEVERAGENTS_PROJECT env var."""
|
|
context.old_project = os.environ.get("CLEVERAGENTS_PROJECT")
|
|
os.environ["CLEVERAGENTS_PROJECT"] = value
|
|
|
|
def restore():
|
|
if context.old_project is None:
|
|
os.environ.pop("CLEVERAGENTS_PROJECT", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_PROJECT"] = context.old_project
|
|
|
|
context.add_cleanup(restore)
|
|
|
|
|
|
@given('CLEVERAGENTS_PLAN is set to "{value}"')
|
|
def step_set_plan(context, value):
|
|
"""Set the CLEVERAGENTS_PLAN env var."""
|
|
context.old_plan = os.environ.get("CLEVERAGENTS_PLAN")
|
|
os.environ["CLEVERAGENTS_PLAN"] = value
|
|
|
|
def restore():
|
|
if context.old_plan is None:
|
|
os.environ.pop("CLEVERAGENTS_PLAN", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_PLAN"] = context.old_plan
|
|
|
|
context.add_cleanup(restore)
|
|
|
|
|
|
@given("CLEVERAGENTS_PROJECT is not set")
|
|
def step_unset_project(context):
|
|
"""Ensure CLEVERAGENTS_PROJECT is unset."""
|
|
context.old_project = os.environ.pop("CLEVERAGENTS_PROJECT", None)
|
|
|
|
def restore():
|
|
if context.old_project is not None:
|
|
os.environ["CLEVERAGENTS_PROJECT"] = context.old_project
|
|
|
|
context.add_cleanup(restore)
|
|
|
|
|
|
@given("CLEVERAGENTS_PLAN is not set")
|
|
def step_unset_plan(context):
|
|
"""Ensure CLEVERAGENTS_PLAN is unset."""
|
|
context.old_plan = os.environ.pop("CLEVERAGENTS_PLAN", None)
|
|
|
|
def restore():
|
|
if context.old_plan is not None:
|
|
os.environ["CLEVERAGENTS_PLAN"] = context.old_plan
|
|
|
|
context.add_cleanup(restore)
|
|
|
|
|
|
@when("I get the prompt context")
|
|
def step_get_prompt_context(context):
|
|
"""Call _get_prompt_context."""
|
|
context.prompt_context = _get_prompt_context()
|
|
|
|
|
|
@then('the prompt context should contain "{expected}"')
|
|
def step_verify_prompt_context_contains(context, expected):
|
|
"""Verify prompt context contains expected string."""
|
|
assert expected in context.prompt_context, (
|
|
f"Expected '{expected}' in '{context.prompt_context}'"
|
|
)
|
|
|
|
|
|
@then('the prompt context should be the default "{expected}"')
|
|
def step_verify_prompt_context_default(context, expected):
|
|
"""Verify prompt context is the default."""
|
|
assert context.prompt_context == expected, (
|
|
f"Expected '{expected}', got '{context.prompt_context}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _find_reference_candidates: empty query
|
|
# ---------------------------------------------------------------------------
|
|
@given("a list of reference options")
|
|
def step_create_reference_options(context):
|
|
"""Create a list of reference options."""
|
|
context.ref_options = ["alpha.py", "beta.py", "gamma.py", "delta.py"]
|
|
|
|
|
|
@when("I find candidates with an empty query")
|
|
def step_find_candidates_empty(context):
|
|
"""Call _find_reference_candidates with empty query."""
|
|
context.ref_candidates = _find_reference_candidates(
|
|
"", context.ref_options, limit=3
|
|
)
|
|
|
|
|
|
@then("the first items from the list should be returned")
|
|
def step_verify_first_items(context):
|
|
"""Verify the first N items are returned."""
|
|
assert context.ref_candidates == ["alpha.py", "beta.py", "gamma.py"]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _best_fuzzy_match: substring branch
|
|
# ---------------------------------------------------------------------------
|
|
@given('a list of options including "{option}"')
|
|
def step_create_options_list(context, option):
|
|
"""Create an options list containing the given option."""
|
|
context.fuzzy_options = ["short", option, "other-thing"]
|
|
|
|
|
|
@when('I fuzzy match with query "{query}"')
|
|
def step_fuzzy_match(context, query):
|
|
"""Call _best_fuzzy_match."""
|
|
context.fuzzy_result = _best_fuzzy_match(query, context.fuzzy_options)
|
|
|
|
|
|
@then('the match should be "{expected}"')
|
|
def step_verify_fuzzy_match(context, expected):
|
|
"""Verify fuzzy match result."""
|
|
assert context.fuzzy_result == expected, (
|
|
f"Expected '{expected}', got '{context.fuzzy_result}'"
|
|
)
|
|
|
|
|
|
@then("the match should be None")
|
|
def step_verify_fuzzy_match_none(context):
|
|
"""Verify fuzzy match returned None."""
|
|
assert context.fuzzy_result is None, f"Expected None, got '{context.fuzzy_result}'"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _reference_suggestions: category-prefixed token
|
|
# ---------------------------------------------------------------------------
|
|
@given('a reference catalog with actors "{actor1}" and "{actor2}"')
|
|
def step_catalog_with_actors(context, actor1, actor2):
|
|
"""Create a catalog with specific actors."""
|
|
context.test_catalog = {
|
|
"file": [],
|
|
"actor": [actor1, actor2],
|
|
"tool": [],
|
|
"skill": [],
|
|
"project": [],
|
|
"plan": [],
|
|
}
|
|
|
|
|
|
@when('I request suggestions for a line containing "{token}"')
|
|
def step_suggestions_for_token(context, token):
|
|
"""Request suggestions for a line."""
|
|
context.suggestions = _reference_suggestions(token, catalog=context.test_catalog)
|
|
|
|
|
|
@then("suggestions should include actor-prefixed candidates")
|
|
def step_verify_actor_suggestions(context):
|
|
"""Verify suggestions include @actor: prefixed values."""
|
|
assert len(context.suggestions) > 0, "Expected at least one suggestion"
|
|
assert any(s.startswith("@actor:") for s in context.suggestions), (
|
|
f"Expected @actor: prefixed suggestions in {context.suggestions}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _expand_references: escaped at-sign
|
|
# ---------------------------------------------------------------------------
|
|
@when("I expand a line containing a backslash-escaped at-sign token")
|
|
def step_expand_escaped_at(context):
|
|
"""Expand a line with \\@ to verify the backslash is stripped."""
|
|
context.expanded_line = _expand_references(
|
|
"\\@literal", catalog=context.test_catalog
|
|
)
|
|
|
|
|
|
@then("the escaped at-sign should be stripped to a bare at-sign")
|
|
def step_verify_unescaped(context):
|
|
"""Verify the expanded line has the backslash removed."""
|
|
assert context.expanded_line == "@literal", (
|
|
f"Expected '@literal', got '{context.expanded_line!r}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# _parse_persona_create: all optional flags
|
|
# ---------------------------------------------------------------------------
|
|
@when("I parse persona create with all flags")
|
|
def step_parse_persona_create_all_flags(context):
|
|
"""Parse persona create with all optional flags set."""
|
|
tokens = [
|
|
"persona",
|
|
"create",
|
|
"fullbot",
|
|
"--actor",
|
|
"ns/fullbot",
|
|
"--description",
|
|
"A full bot",
|
|
"--icon",
|
|
"robot",
|
|
"--greeting",
|
|
"Hello!",
|
|
"--cycle-order",
|
|
"5",
|
|
]
|
|
try:
|
|
context.parsed_persona = _parse_persona_create(tokens)
|
|
context.parse_error = None
|
|
except ValueError as exc:
|
|
context.parsed_persona = None
|
|
context.parse_error = exc
|
|
|
|
|
|
@then("the persona should have description icon greeting and cycle-order set")
|
|
def step_verify_all_flags_parsed(context):
|
|
"""Verify all optional fields were parsed."""
|
|
assert context.parse_error is None, f"Unexpected error: {context.parse_error}"
|
|
p = context.parsed_persona
|
|
assert p.name == "fullbot"
|
|
assert p.actor == "ns/fullbot"
|
|
assert p.description == "A full bot"
|
|
assert p.icon == "robot"
|
|
assert p.greeting == "Hello!"
|
|
assert p.cycle_order == 5
|