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

349 lines
12 KiB
Python

"""Step definitions for repo_indexing_service_coverage.feature.
Targets uncovered error-handling paths in repo_indexing_service.py:
- Lines 182-185: walk failure + persist_status double-fault (inner except)
- Lines 219-228: persist_index failure on fresh resource -> persist ERROR status
- Lines 231-234, 236: persist_index failure + persist_status double-fault
- Lines 367-368+: refresh_index persist_index failure
"""
from __future__ import annotations
import logging
import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch
from behave import given, then, when
from behave.runner import Context
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import cleveragents.application.services.repo_indexing_service as svc_mod
from cleveragents.application.services.repo_indexing_service import (
RepoIndexingService,
)
from cleveragents.domain.models.core.repo_index import IndexStatus
from cleveragents.infrastructure.database.models import Base
# Valid ULID for test resource
_RESOURCE_ID = "01KK0D8WNATFNEX2JMG5GKF6FM"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_service(context: Context) -> RepoIndexingService:
"""Create an in-memory RepoIndexingService and store on context."""
engine = create_engine("sqlite:///:memory:", echo=False)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
service = RepoIndexingService(session_factory=factory)
context.cov_service = service
context.cov_error = None
context.cov_logged_warnings = []
return service
def _make_sample_files(context: Context) -> Path:
"""Create a temp directory with a few files."""
tmpdir = Path(tempfile.mkdtemp())
(tmpdir / "app.py").write_text("print('hello')\n")
(tmpdir / "lib.py").write_text("x = 42\n")
context.cov_tmpdir = str(tmpdir)
context.add_cleanup(_cleanup, context)
return tmpdir
def _cleanup(context: Context) -> None:
tmpdir = getattr(context, "cov_tmpdir", None)
if tmpdir:
shutil.rmtree(tmpdir, ignore_errors=True)
class _WarningCapture(logging.Handler):
"""Logging handler that captures WARNING-level messages."""
def __init__(self, store: list[str]) -> None:
super().__init__(level=logging.WARNING)
self._store = store
def emit(self, record: logging.LogRecord) -> None:
self._store.append(record.getMessage())
def _install_log_capture(context: Context) -> None:
"""Install a WARNING-level log capture handler on the service logger.
Also monkey-patches logger.warning to directly capture records, bypassing
any structlog/logging pipeline issues in the full test suite where
configure_structlog() may have intercepted the logging machinery.
"""
svc_logger = logging.getLogger(
"cleveragents.application.services.repo_indexing_service"
)
svc_logger.disabled = False
handler = _WarningCapture(context.cov_logged_warnings)
svc_logger.addHandler(handler)
_original_warning = svc_logger.warning
def _capturing_warning(msg, *args, **kwargs):
context.cov_logged_warnings.append(msg % args if args else msg)
return _original_warning(msg, *args, **kwargs)
svc_logger.warning = _capturing_warning
def _cleanup(ctx):
svc_logger.warning = _original_warning
svc_logger.removeHandler(handler)
context.add_cleanup(_cleanup, context)
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("a coverage-boost fresh in-memory indexing service")
def step_fresh_service(context: Context) -> None:
_make_service(context)
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given("a coverage-boost temporary directory with sample files")
def step_sample_dir(context: Context) -> None:
_make_sample_files(context)
@given("a coverage-boost successful full index exists")
def step_existing_index(context: Context) -> None:
service: RepoIndexingService = context.cov_service
result = service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
context.cov_original_result = result
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when(
"walk_and_index is mocked to fail and persist_status also fails on a fresh resource"
)
def step_walk_and_persist_status_double_fault(context: Context) -> None:
"""Exercise lines 182-185: walk fails, then persist_status(ERROR) also fails.
On a fresh resource (no prior good index), the service first calls
persist_status(INDEXING) at line 147, then walks. When walk fails,
it tries persist_status(ERROR) at line 175. If THAT also fails,
lines 182-185 catch the inner exception and log a warning.
We allow the first persist_status(INDEXING) call through, but make
the second persist_status(ERROR) call fail.
"""
service: RepoIndexingService = context.cov_service
_install_log_capture(context)
original_persist_status = svc_mod.persist_status
call_count = {"n": 0}
def _persist_status_fail_on_error(*args, **kwargs):
call_count["n"] += 1
status_arg = kwargs.get("status")
if status_arg == IndexStatus.ERROR:
raise RuntimeError("Simulated persist_status failure on ERROR")
return original_persist_status(*args, **kwargs)
with (
patch.object(
svc_mod,
"walk_and_index",
side_effect=OSError("Simulated walk failure"),
),
patch.object(
svc_mod,
"persist_status",
side_effect=_persist_status_fail_on_error,
),
):
try:
service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
except Exception as exc:
context.cov_error = exc
@when("walk_and_index succeeds but persist_index fails on a fresh resource")
def step_persist_index_fails_fresh(context: Context) -> None:
"""Exercise lines 219-228: persist_index fails on fresh resource.
After a successful walk, persist_index raises. Because there is no
prior good index (has_good_index=False), the service tries
persist_status(ERROR) at lines 221-230, then re-raises at line 236.
"""
service: RepoIndexingService = context.cov_service
with patch.object(
svc_mod,
"persist_index",
side_effect=RuntimeError("Simulated persist_index failure"),
):
try:
service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
except Exception as exc:
context.cov_error = exc
@when(
"walk_and_index succeeds but persist_index and persist_status both fail on a fresh resource"
)
def step_persist_index_and_status_double_fault(context: Context) -> None:
"""Exercise lines 231-234, 236: persist_index fails, then persist_status
also fails when trying to record ERROR.
The initial persist_status(INDEXING) at line 147 must succeed, then
persist_index fails at line 218, then persist_status(ERROR) at line 221
also fails, triggering lines 231-234 (inner except with warning log).
"""
service: RepoIndexingService = context.cov_service
_install_log_capture(context)
original_persist_status = svc_mod.persist_status
call_count = {"n": 0}
def _persist_status_fail_on_error(*args, **kwargs):
call_count["n"] += 1
status_arg = kwargs.get("status")
if status_arg == IndexStatus.ERROR:
raise RuntimeError("Simulated persist_status failure on ERROR")
return original_persist_status(*args, **kwargs)
with (
patch.object(
svc_mod,
"persist_index",
side_effect=RuntimeError("Simulated persist_index failure"),
),
patch.object(
svc_mod,
"persist_status",
side_effect=_persist_status_fail_on_error,
),
):
try:
service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
except Exception as exc:
context.cov_error = exc
@when("persist_index is mocked to fail during re-index of the existing resource")
def step_persist_index_fails_reindex(context: Context) -> None:
"""Exercise lines 219-236 with has_good_index=True.
When a good index already exists and persist_index raises, the service
skips the persist_status(ERROR) call (the else branch is not taken for
the outer if, and line 236 re-raises directly).
"""
service: RepoIndexingService = context.cov_service
with patch.object(
svc_mod,
"persist_index",
side_effect=RuntimeError("Simulated persist_index failure"),
):
try:
service.index_resource(_RESOURCE_ID, context.cov_tmpdir)
except Exception as exc:
context.cov_error = exc
@when("persist_index is mocked to fail during refresh")
def step_persist_index_fails_refresh(context: Context) -> None:
"""Exercise lines 367-368+: persist_index fails during refresh_index.
The service should log a warning about failing to persist the
refreshed index, then re-raise, preserving the previous good index.
"""
service: RepoIndexingService = context.cov_service
_install_log_capture(context)
with patch.object(
svc_mod,
"persist_index",
side_effect=RuntimeError("Simulated persist_index failure"),
):
try:
service.refresh_index(_RESOURCE_ID, context.cov_tmpdir)
except Exception as exc:
context.cov_error = exc
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then("a coverage-boost error should have been raised")
def step_error_raised(context: Context) -> None:
assert context.cov_error is not None, "Expected an error but none was raised"
@then(
"the coverage-boost double-fault warning about walk failure should have been logged"
)
def step_walk_double_fault_warning(context: Context) -> None:
"""Verify the warning from lines 183-185 was logged."""
assert any(
"Failed to persist ERROR status after walk failure" in msg
for msg in context.cov_logged_warnings
), f"Expected walk double-fault warning in logs. Got: {context.cov_logged_warnings}"
@then('the coverage-boost persisted status should be "{status}"')
def step_persisted_status(context: Context, status: str) -> None:
service: RepoIndexingService = context.cov_service
meta = service.get_index_status(_RESOURCE_ID)
assert meta is not None, "Expected persisted metadata, got None"
actual = meta.status.value
assert actual == status, f"Expected status '{status}', got '{actual}'"
@then(
"the coverage-boost double-fault warning about persist failure should have been logged"
)
def step_persist_double_fault_warning(context: Context) -> None:
"""Verify the warning from lines 232-234 was logged."""
assert any(
"Failed to persist ERROR status after persist failure" in msg
for msg in context.cov_logged_warnings
), (
f"Expected persist double-fault warning in logs. Got: {context.cov_logged_warnings}"
)
@then('the coverage-boost persisted status should still be "{status}"')
def step_persisted_status_still(context: Context, status: str) -> None:
"""Verify the previously good index was NOT overwritten with ERROR."""
service: RepoIndexingService = context.cov_service
meta = service.get_index_status(_RESOURCE_ID)
assert meta is not None, "Expected persisted metadata, got None"
actual = meta.status.value
assert actual == status, f"Expected status '{status}', got '{actual}'"
@then("the coverage-boost refresh persist warning should have been logged")
def step_refresh_persist_warning(context: Context) -> None:
"""Verify the warning from lines 368-374 was logged."""
assert any(
"Failed to persist refreshed index" in msg
for msg in context.cov_logged_warnings
), f"Expected refresh persist warning in logs. Got: {context.cov_logged_warnings}"