test(acms): add direct _read_file and add_command traversal coverage

Adds a focused feature file that directly exercises the diff-coverage
gaps reported by the coverage gate across 13 prior attempts:

  acms/index.py  lines 597-612  (_read_file: stat + size-check + read_text)
  context.py     lines 111-112  (add_command: traverser invocation)

Three scenarios, each targeting specific line ranges:
1. _read_file with default traverser covers lines 597-598, 611-612
2. _read_file with max_file_size=1 covers lines 602-607
3. add_command with real ChunkedFileTraverser covers lines 111-112
   (also transitively covers 597-612 via _read_file)

ISSUES CLOSED: #9982
This commit is contained in:
2026-06-18 09:53:19 -04:00
committed by Forgejo
parent 34c24f48ad
commit b891b081a7
2 changed files with 104 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
Feature: ChunkedFileTraverser _read_file and add_command traversal coverage
Direct coverage for diff lines that the parallel test runner may not
reach via the acms_context_add.feature scenarios:
acms/index.py lines 597-612 (_read_file stat + size-check + read_text)
context.py lines 111-112 (add_command traverser invocation)
Background:
Given a temp file exists for read file coverage
Scenario: _read_file returns AcmsIndexEntry for a normal readable file
When I directly call _read_file with default traverser
Then the direct read file result should be an AcmsIndexEntry
Scenario: _read_file returns None when file exceeds max_file_size
When I directly call _read_file with max_file_size 1
Then the direct read file result should be None
Scenario: add_command invokes the real traverser for an existing path
When I call add_command with the real traverser and a mocked container
Then the mocked add_to_context should have been called
@@ -0,0 +1,84 @@
"""Step definitions for direct _read_file and add_command traversal coverage.
Targets diff-coverage gaps:
acms/index.py lines 597-612 (_read_file stat/size-check/read_text paths)
context.py lines 111-112 (add_command traverser invocation)
Step names are prefixed with ``rf_`` / ``ac_`` context attributes and use
unique step text to avoid AmbiguousStep conflicts with existing steps.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.acms.index import AcmsIndexEntry, ChunkedFileTraverser
@given("a temp file exists for read file coverage")
def step_rf_temp_file(context: Context) -> None:
"""Create a small temp file used by all scenarios in this feature."""
context.rf_tmpdir = tempfile.mkdtemp()
path = Path(context.rf_tmpdir) / "sample.py"
path.write_text("# sample content\n", encoding="utf-8")
context.rf_path = path
@when("I directly call _read_file with default traverser")
def step_rf_direct_default(context: Context) -> None:
"""Call _read_file directly — exercises lines 597-598 and 611-612."""
traverser = ChunkedFileTraverser()
context.rf_result = traverser._read_file(context.rf_path)
@when("I directly call _read_file with max_file_size 1")
def step_rf_direct_max_size(context: Context) -> None:
"""Call _read_file with max_file_size=1 — exercises lines 602-607."""
traverser = ChunkedFileTraverser(max_file_size=1)
context.rf_result = traverser._read_file(context.rf_path)
@then("the direct read file result should be an AcmsIndexEntry")
def step_rf_result_is_entry(context: Context) -> None:
assert isinstance(context.rf_result, AcmsIndexEntry), (
f"Expected AcmsIndexEntry, got {type(context.rf_result)!r}"
)
@then("the direct read file result should be None")
def step_rf_result_is_none(context: Context) -> None:
assert context.rf_result is None, (
f"Expected None from _read_file, got {context.rf_result!r}"
)
@when("I call add_command with the real traverser and a mocked container")
def step_ac_call_add_command_real_traverser(context: Context) -> None:
"""Call add_command with the REAL ChunkedFileTraverser — exercises lines 111-112."""
from cleveragents.cli.commands.context import add_command
container = MagicMock()
context_service = MagicMock()
context_service.add_to_context.return_value = ([context.rf_path], [])
container.context_service.return_value = context_service
project_service = MagicMock()
project_service.get_current_project.return_value = MagicMock()
container.project_service.return_value = project_service
with patch(
"cleveragents.application.container.get_container",
return_value=container,
):
add_command([str(context.rf_path)])
context.ac_context_service = context_service
@then("the mocked add_to_context should have been called")
def step_ac_add_to_context_called(context: Context) -> None:
context.ac_context_service.add_to_context.assert_called_once()