Files
temp/features/steps/tui_reference_parser_coverage_steps.py
T
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

248 lines
9.5 KiB
Python

"""Step definitions for tui_reference_parser_coverage.feature.
These steps target specific uncovered lines in reference_parser.py:
- Lines 59-60: ValueError fallback in _catalog file listing
- Lines 113-114: Escaped \\@ token pass-through
- Lines 123-126: Category parsing from @category:query syntax
- Lines 129-135: Typed reference resolves successfully
- Lines 139-141: Typed reference fails to resolve (unresolved)
- Lines 160-161: Untyped reference fails across all categories
"""
from pathlib import Path
from unittest.mock import patch
from behave import given, then, when
from cleveragents.tui.input import reference_parser as rp_module
from cleveragents.tui.input.reference_parser import (
_REFERENCE_TYPES,
_catalog,
_catalog_cache,
parse_references,
)
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the reference parser module is imported")
def step_reference_parser_imported(context):
"""Verify the module is importable."""
assert parse_references is not None
assert _catalog is not None
# ---------------------------------------------------------------------------
# Helpers - mock catalog setup
# ---------------------------------------------------------------------------
def _empty_catalog():
"""Return a catalog with empty lists for every category."""
return {cat: [] for cat in _REFERENCE_TYPES}
def _make_catalog(**overrides):
"""Return an empty catalog with specific category overrides applied."""
catalog = _empty_catalog()
catalog.update(overrides)
return catalog
# ---------------------------------------------------------------------------
# Given steps - mock catalog variants
# ---------------------------------------------------------------------------
@given("the catalog is mocked with minimal data")
def step_catalog_mocked_minimal(context):
"""Prepare a minimal catalog mock that returns no entries."""
context.catalog_patch = patch.object(
rp_module, "_catalog", return_value=_empty_catalog()
)
context.catalog_patch.start()
context.add_cleanup(context.catalog_patch.stop)
@given('the catalog is mocked with resource entries "{entry1}" and "{entry2}"')
def step_catalog_mocked_resources(context, entry1, entry2):
"""Mock the catalog to contain specific resource entries."""
catalog = _make_catalog(resource=sorted([entry1, entry2]))
context.catalog_patch = patch.object(rp_module, "_catalog", return_value=catalog)
context.catalog_patch.start()
context.add_cleanup(context.catalog_patch.stop)
@given('the catalog is mocked with plan entries "{entry1}" and "{entry2}"')
def step_catalog_mocked_plans(context, entry1, entry2):
"""Mock the catalog to contain specific plan entries."""
catalog = _make_catalog(plan=sorted([entry1, entry2]))
context.catalog_patch = patch.object(rp_module, "_catalog", return_value=catalog)
context.catalog_patch.start()
context.add_cleanup(context.catalog_patch.stop)
@given('the catalog is mocked with actor entries "{entry1}" and "{entry2}"')
def step_catalog_mocked_actors(context, entry1, entry2):
"""Mock the catalog to contain specific actor entries."""
catalog = _make_catalog(actor=sorted([entry1, entry2]))
context.catalog_patch = patch.object(rp_module, "_catalog", return_value=catalog)
context.catalog_patch.start()
context.add_cleanup(context.catalog_patch.stop)
@given("the catalog is mocked with empty lists for all categories")
def step_catalog_mocked_empty(context):
"""Mock the catalog to return empty lists for every category."""
context.catalog_patch = patch.object(
rp_module, "_catalog", return_value=_empty_catalog()
)
context.catalog_patch.start()
context.add_cleanup(context.catalog_patch.stop)
# ---------------------------------------------------------------------------
# Given steps - catalog cache / os.walk mocking for lines 59-60
# ---------------------------------------------------------------------------
@given("the catalog cache is invalidated")
def step_invalidate_catalog_cache(context):
"""Reset the module-level catalog cache so _catalog() rebuilds."""
context.original_cache = {
"cwd": _catalog_cache.get("cwd"),
"created_at": _catalog_cache.get("created_at"),
"catalog": _catalog_cache.get("catalog"),
}
_catalog_cache["cwd"] = None
_catalog_cache["created_at"] = 0.0
_catalog_cache["catalog"] = None
def restore_cache():
_catalog_cache["cwd"] = context.original_cache["cwd"]
_catalog_cache["created_at"] = context.original_cache["created_at"]
_catalog_cache["catalog"] = context.original_cache["catalog"]
context.add_cleanup(restore_cache)
@given("os.walk is mocked to return a path outside the working directory")
def step_mock_os_walk_outside_cwd(context):
"""Mock os.walk to yield a directory outside cwd.
When Path.relative_to(cwd) is called on a file from an outside dir,
it raises ValueError, triggering the fallback on lines 59-60.
"""
outside_root = "/outside/foreign/dir"
mock_filenames = ["external_file.txt"]
def fake_walk(top, followlinks=False):
# First yield the real cwd with nothing, then yield the outside path
yield outside_root, [], mock_filenames
context.walk_patch = patch.object(rp_module.os, "walk", side_effect=fake_walk)
context.walk_patch.start()
# Also mock the actors/tools/skills dirs so they don't touch the real FS
context.is_dir_patch = patch.object(Path, "is_dir", return_value=False)
context.is_dir_patch.start()
def cleanup():
context.walk_patch.stop()
context.is_dir_patch.stop()
context.add_cleanup(cleanup)
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when('I parse references from the line "{line}"')
def step_parse_references(context, line):
"""Call parse_references with the given line."""
context.parse_result = parse_references(line)
@when("I build the catalog")
def step_build_catalog(context):
"""Call _catalog() directly to exercise the file-listing code."""
context.catalog_result = _catalog()
# ---------------------------------------------------------------------------
# Then steps - expanded line
# ---------------------------------------------------------------------------
@then('the expanded line should be "{expected}"')
def step_expanded_line_equals(context, expected):
"""Verify the expanded line matches exactly."""
actual = context.parse_result.expanded_line
assert actual == expected, f"Expected '{expected}', got '{actual}'"
@then('the expanded line should contain "{fragment}"')
def step_expanded_line_contains(context, fragment):
"""Verify the expanded line contains a fragment."""
actual = context.parse_result.expanded_line
assert fragment in actual, f"Expected '{fragment}' in '{actual}'"
# ---------------------------------------------------------------------------
# Then steps - matches
# ---------------------------------------------------------------------------
@then("there should be no resolved matches")
def step_no_matches(context):
"""Verify no references were resolved."""
assert len(context.parse_result.matches) == 0, (
f"Expected 0 matches, got {context.parse_result.matches}"
)
@then("there should be {count:d} resolved match")
def step_match_count(context, count):
"""Verify the exact number of resolved matches."""
actual = len(context.parse_result.matches)
assert actual == count, (
f"Expected {count} match(es), got {actual}: {context.parse_result.matches}"
)
@then('the resolved match category should be "{category}"')
def step_match_category(context, category):
"""Verify the first resolved match has the expected category."""
match = context.parse_result.matches[0]
assert match.category == category, (
f"Expected category '{category}', got '{match.category}'"
)
# ---------------------------------------------------------------------------
# Then steps - unresolved tokens
# ---------------------------------------------------------------------------
@then("there should be no unresolved tokens")
def step_no_unresolved(context):
"""Verify no tokens were left unresolved."""
assert len(context.parse_result.unresolved_tokens) == 0, (
f"Expected 0 unresolved, got {context.parse_result.unresolved_tokens}"
)
@then('"{token}" should be in the unresolved tokens')
def step_token_unresolved(context, token):
"""Verify a specific token is listed as unresolved."""
assert token in context.parse_result.unresolved_tokens, (
f"Expected '{token}' in unresolved list "
f"{context.parse_result.unresolved_tokens}"
)
# ---------------------------------------------------------------------------
# Then steps - catalog ValueError fallback (lines 59-60)
# ---------------------------------------------------------------------------
@then("the catalog resource list should contain the absolute fallback path")
def step_catalog_has_absolute_path(context):
"""Verify that the catalog collected the file using its absolute path.
When relative_to(cwd) raises ValueError, the code falls back to
``(root_path / filename).as_posix()`` -- the absolute path.
"""
resources = context.catalog_result.get("resource", [])
expected = "/outside/foreign/dir/external_file.txt"
assert expected in resources, (
f"Expected '{expected}' in resource list, got {resources}"
)