Files
temp/features/steps/repo_indexing_utils_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

471 lines
16 KiB
Python

"""Step definitions for repo_indexing_utils_coverage.feature.
These steps target specific uncovered lines in repo_indexing_utils.py:
- Line 118: detect_language Dockerfile special-case return
- Line 175: _glob_match with '**' or '/' patterns (PurePosixPath.match)
- Lines 266, 268, 269: directory filtering in walk_and_index
- Line 274: hidden file skip
- Line 281: symlink skip
- Lines 285-286: ValueError from relative_to
- Lines 292-296: path > 1024 chars skip
- Lines 306-307: stat() OSError skip
- Line 312: non-regular file (FIFO) skip
- Lines 338-342: max_file_count cutoff
"""
import os
import tempfile
from pathlib import Path
from unittest.mock import patch
from behave import given, then, when
from cleveragents.application.services.repo_indexing_utils import (
detect_language,
matches_policy,
walk_and_index,
)
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the repo indexing utils module is imported")
def step_module_imported(context):
"""Ensure the module is importable."""
assert detect_language is not None
assert matches_policy is not None
assert walk_and_index is not None
# ---------------------------------------------------------------------------
# detect_language: Dockerfile (line 118)
# ---------------------------------------------------------------------------
@when('I detect the language for path "{path}"')
def step_detect_language(context, path):
context.detected_language = detect_language(path)
@then('the detected language should be "{expected}"')
def step_verify_language(context, expected):
assert context.detected_language == expected, (
f"Expected '{expected}', got '{context.detected_language}'"
)
# ---------------------------------------------------------------------------
# matches_policy: recursive / path globs (line 175)
# ---------------------------------------------------------------------------
@when('I check matches_policy for "{rel_path}" with include glob "{pattern}"')
def step_check_matches_policy(context, rel_path, pattern):
context.policy_result = matches_policy(
rel_path,
include_globs=(pattern,),
exclude_globs=(),
)
@then("the path should match the policy")
def step_path_matches(context):
assert context.policy_result is True, "Expected path to match policy"
@then("the path should not match the policy")
def step_path_not_matches(context):
assert context.policy_result is False, "Expected path NOT to match policy"
# ---------------------------------------------------------------------------
# walk_and_index: hidden / special directories filtered (lines 266-269)
# ---------------------------------------------------------------------------
@given("a temporary directory with hidden and special subdirectories")
def step_create_dir_with_hidden_and_special(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir = tmpdir
# Hidden directory — should be skipped
os.makedirs(os.path.join(tmpdir, ".git"))
with open(os.path.join(tmpdir, ".git", "config"), "w") as f:
f.write("gitconfig")
# __pycache__ — should be skipped
os.makedirs(os.path.join(tmpdir, "__pycache__"))
with open(os.path.join(tmpdir, "__pycache__", "mod.pyc"), "w") as f:
f.write("bytecode")
# node_modules — should be skipped
os.makedirs(os.path.join(tmpdir, "node_modules"))
with open(os.path.join(tmpdir, "node_modules", "pkg.js"), "w") as f:
f.write("js")
# venv — should be skipped
os.makedirs(os.path.join(tmpdir, "venv"))
with open(os.path.join(tmpdir, "venv", "activate.py"), "w") as f:
f.write("activate")
# Visible directory with a real file — should be indexed
os.makedirs(os.path.join(tmpdir, "src"))
with open(os.path.join(tmpdir, "src", "main.py"), "w") as f:
f.write("print('hello')")
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index on the temporary directory")
def step_walk_and_index_tmpdir(context):
context.walk_results = walk_and_index(
Path(context.tmpdir),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("only files from visible non-special directories should be indexed")
def step_verify_only_visible_dirs(context):
paths = [r.path for r in context.walk_results]
# The only file should be src/main.py
assert len(paths) == 1, f"Expected 1 file, got {len(paths)}: {paths}"
assert paths[0] == os.path.join("src", "main.py"), (
f"Expected 'src/main.py', got '{paths[0]}'"
)
# ---------------------------------------------------------------------------
# walk_and_index: hidden files skipped (line 274)
# ---------------------------------------------------------------------------
@given("a temporary directory with hidden files")
def step_create_dir_with_hidden_files(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_hidden = tmpdir
# Hidden file — should be skipped
with open(os.path.join(tmpdir, ".hidden_config"), "w") as f:
f.write("secret")
# Normal file — should be indexed
with open(os.path.join(tmpdir, "visible.py"), "w") as f:
f.write("x = 1")
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index on the directory with hidden files")
def step_walk_hidden_files(context):
context.walk_results = walk_and_index(
Path(context.tmpdir_hidden),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("hidden files should not appear in the results")
def step_verify_no_hidden_files(context):
paths = [r.path for r in context.walk_results]
hidden = [p for p in paths if os.path.basename(p).startswith(".")]
assert len(hidden) == 0, f"Hidden files found: {hidden}"
assert "visible.py" in paths, f"Expected visible.py in {paths}"
# ---------------------------------------------------------------------------
# walk_and_index: symlinks skipped (line 281)
# ---------------------------------------------------------------------------
@given("a temporary directory with a symlink to a file")
def step_create_dir_with_symlink(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_symlink = tmpdir
# Real file
real_file = os.path.join(tmpdir, "real.py")
with open(real_file, "w") as f:
f.write("real content")
# Symlink pointing to the real file
link_path = os.path.join(tmpdir, "link_to_real.py")
os.symlink(real_file, link_path)
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index on the directory with symlinks")
def step_walk_symlinks(context):
context.walk_results = walk_and_index(
Path(context.tmpdir_symlink),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("symlinked files should not appear in the results")
def step_verify_no_symlinks(context):
paths = [r.path for r in context.walk_results]
assert "link_to_real.py" not in paths, (
f"Symlink should be excluded but found in: {paths}"
)
assert "real.py" in paths, f"Expected real.py in {paths}"
# ---------------------------------------------------------------------------
# walk_and_index: ValueError from relative_to (lines 285-286)
# ---------------------------------------------------------------------------
@given("a temporary directory with a file that will cause a relative_to ValueError")
def step_create_dir_for_relative_to_error(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_relto = tmpdir
with open(os.path.join(tmpdir, "normal.py"), "w") as f:
f.write("x = 1")
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index with mocked relative_to failure")
def step_walk_with_relative_to_mock(context):
"""Patch Path.relative_to so the first call raises ValueError,
but subsequent calls (or the resolve() call) work normally."""
original_relative_to = Path.relative_to
call_count = {"n": 0}
def patched_relative_to(self, other):
call_count["n"] += 1
# Fail for the first real file encountered to exercise lines 285-286
if call_count["n"] == 1:
raise ValueError("mocked relative_to failure")
return original_relative_to(self, other)
with patch.object(Path, "relative_to", patched_relative_to):
context.walk_results = walk_and_index(
Path(context.tmpdir_relto),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("the file causing ValueError should be skipped gracefully")
def step_verify_relative_to_skip(context):
# The function should complete without raising; the problematic file
# is simply skipped. Result may be empty (the only file was skipped).
assert isinstance(context.walk_results, list)
# ---------------------------------------------------------------------------
# walk_and_index: path > 1024 chars (lines 292-296)
# ---------------------------------------------------------------------------
@given("a temporary directory with a deeply nested file exceeding 1024 char path")
def step_create_deeply_nested(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_deep = tmpdir
# Build a path whose relative portion > 1024 chars.
# Use 60-char directory names nested ~20 levels deep → ~1200 chars.
nested = tmpdir
for i in range(20):
segment = f"d{'x' * 58}{i:01d}" # 60 chars each
nested = os.path.join(nested, segment)
os.makedirs(nested, exist_ok=True)
deep_file = os.path.join(nested, "deep_file.py")
with open(deep_file, "w") as f:
f.write("deep = True")
# Also add a normal file at root level
with open(os.path.join(tmpdir, "shallow.py"), "w") as f:
f.write("shallow = True")
context.deep_file_rel = str(Path(deep_file).relative_to(Path(tmpdir).resolve()))
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index on the deeply nested directory")
def step_walk_deeply_nested(context):
context.walk_results = walk_and_index(
Path(context.tmpdir_deep),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("the deeply nested file should be skipped")
def step_verify_deep_file_skipped(context):
paths = [r.path for r in context.walk_results]
# The deep file path exceeds 1024 chars so should be excluded
long_paths = [p for p in paths if len(p) > 1024]
assert len(long_paths) == 0, f"Long paths should be excluded: {long_paths}"
# shallow.py should still be there
assert "shallow.py" in paths, f"Expected shallow.py in {paths}"
# ---------------------------------------------------------------------------
# walk_and_index: stat() OSError (lines 306-307)
# ---------------------------------------------------------------------------
@given("a temporary directory with a file that will cause a stat OSError")
def step_create_dir_for_stat_error(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_stat = tmpdir
with open(os.path.join(tmpdir, "goodfile.py"), "w") as f:
f.write("good = True")
with open(os.path.join(tmpdir, "badfile.py"), "w") as f:
f.write("bad = True")
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index with mocked stat failure")
def step_walk_with_stat_mock(context):
"""Patch Path.stat so it raises OSError for badfile.py.
Only intercept calls with follow_symlinks=True (the default),
so that is_symlink() → lstat() → stat(follow_symlinks=False)
still works normally.
"""
original_stat = Path.stat
def patched_stat(self, *args, **kwargs):
follow = kwargs.get("follow_symlinks", True)
if self.name == "badfile.py" and follow:
raise OSError("mocked stat failure")
return original_stat(self, *args, **kwargs)
with patch.object(Path, "stat", patched_stat):
context.walk_results = walk_and_index(
Path(context.tmpdir_stat),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("the file causing stat OSError should be skipped gracefully")
def step_verify_stat_error_skip(context):
paths = [r.path for r in context.walk_results]
assert "badfile.py" not in paths, (
f"badfile.py should be skipped due to OSError, got: {paths}"
)
assert "goodfile.py" in paths, f"Expected goodfile.py in {paths}"
# ---------------------------------------------------------------------------
# walk_and_index: non-regular file / FIFO (line 312)
# ---------------------------------------------------------------------------
@given("a temporary directory with a FIFO named pipe")
def step_create_dir_with_fifo(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_fifo = tmpdir
# Regular file
with open(os.path.join(tmpdir, "regular.py"), "w") as f:
f.write("regular = True")
# FIFO (named pipe) — should be skipped by S_ISREG check
fifo_path = os.path.join(tmpdir, "myfifo")
os.mkfifo(fifo_path)
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index on the directory with the FIFO")
def step_walk_fifo(context):
context.walk_results = walk_and_index(
Path(context.tmpdir_fifo),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
)
@then("the FIFO should not appear in the results")
def step_verify_no_fifo(context):
paths = [r.path for r in context.walk_results]
assert "myfifo" not in paths, f"FIFO should be excluded, got: {paths}"
assert "regular.py" in paths, f"Expected regular.py in {paths}"
# ---------------------------------------------------------------------------
# walk_and_index: max_file_count cutoff (lines 338-342)
# ---------------------------------------------------------------------------
@given("a temporary directory with five regular files")
def step_create_dir_with_five_files(context):
tmpdir = tempfile.mkdtemp()
context.tmpdir_count = tmpdir
for i in range(5):
with open(os.path.join(tmpdir, f"file_{i:02d}.py"), "w") as f:
f.write(f"x = {i}")
def cleanup():
import shutil
shutil.rmtree(tmpdir, ignore_errors=True)
context.add_cleanup(cleanup)
@when("I run walk_and_index with max_file_count of {count:d}")
def step_walk_with_max_count(context, count):
context.walk_results = walk_and_index(
Path(context.tmpdir_count),
include_globs=(),
exclude_globs=(),
max_file_size=None,
max_total_size=None,
max_file_count=count,
)
@then("only {count:d} file records should be returned")
def step_verify_file_count(context, count):
actual = len(context.walk_results)
assert actual == count, (
f"Expected {count} records, got {actual}: "
f"{[r.path for r in context.walk_results]}"
)