diff --git a/features/acms_read_file_coverage.feature b/features/acms_read_file_coverage.feature new file mode 100644 index 000000000..c96b16347 --- /dev/null +++ b/features/acms_read_file_coverage.feature @@ -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 diff --git a/features/steps/acms_read_file_coverage_steps.py b/features/steps/acms_read_file_coverage_steps.py new file mode 100644 index 000000000..4a98ab96a --- /dev/null +++ b/features/steps/acms_read_file_coverage_steps.py @@ -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()