forked from HAL9000/cleveragents-core
31472b5413
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate. ISSUES CLOSED: #1232
199 lines
7.2 KiB
Python
199 lines
7.2 KiB
Python
"""Step definitions for CLI main.py coverage round 3 (lines 104-110, 582-590, 593+)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 104-110: _register_subcommands exception handler
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("cmcov3 the _subcommands_registered flag is reset to False")
|
|
def step_reset_subcommands_flag(context: Any) -> None:
|
|
"""Reset the module-level flag so _register_subcommands runs fresh."""
|
|
import importlib
|
|
|
|
mod = importlib.import_module("cleveragents.cli.main")
|
|
context.cmcov3_mod = mod
|
|
context.cmcov3_orig_registered = mod._subcommands_registered
|
|
mod._subcommands_registered = False
|
|
|
|
# Restore the flag after the scenario to avoid polluting other tests.
|
|
def _restore() -> None:
|
|
mod._subcommands_registered = context.cmcov3_orig_registered
|
|
|
|
context.add_cleanup(_restore)
|
|
|
|
|
|
@when("cmcov3 _register_subcommands is called and an import raises an error")
|
|
def step_register_subcommands_import_error(context: Any) -> None:
|
|
"""Patch a subcommand import so _register_subcommands hits its except block."""
|
|
import builtins
|
|
|
|
mod = context.cmcov3_mod
|
|
mock_err_console = MagicMock()
|
|
original_import = builtins.__import__
|
|
|
|
def failing_import(name: str, *args: Any, **kwargs: Any) -> Any:
|
|
if name == "cleveragents.cli.commands":
|
|
raise ImportError("cmcov3 forced import failure")
|
|
return original_import(name, *args, **kwargs)
|
|
|
|
patcher_console = patch.object(
|
|
mod, "get_err_console", return_value=mock_err_console
|
|
)
|
|
patcher_console.start()
|
|
context.add_cleanup(patcher_console.stop)
|
|
|
|
patcher_builtins = patch.object(builtins, "__import__", side_effect=failing_import)
|
|
patcher_builtins.start()
|
|
|
|
try:
|
|
mod._register_subcommands()
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
finally:
|
|
patcher_builtins.stop()
|
|
|
|
context.cmcov3_err_console = mock_err_console
|
|
|
|
|
|
@then("cmcov3 the error console should have printed the failure message")
|
|
def step_check_failure_message(context: Any) -> None:
|
|
"""Verify the error console received the failure message."""
|
|
mock_console = context.cmcov3_err_console
|
|
assert mock_console.print.called, "Expected err console print to be called"
|
|
all_calls = " ".join(str(c) for c in mock_console.print.call_args_list)
|
|
assert "Failed to register subcommands" in all_calls, (
|
|
f"Expected 'Failed to register subcommands' in output, got: {all_calls}"
|
|
)
|
|
|
|
|
|
@then("cmcov3 the _subcommands_registered flag should still be False")
|
|
def step_check_flag_still_false(context: Any) -> None:
|
|
"""The flag must remain False since registration failed."""
|
|
mod = context.cmcov3_mod
|
|
assert mod._subcommands_registered is False, (
|
|
f"Expected _subcommands_registered to be False, got {mod._subcommands_registered}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 582-590: completion command with subprocess.run
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('cmcov3 subprocess.run is mocked to return stdout "{stdout_text}"')
|
|
def step_mock_subprocess_with_stdout(context: Any, stdout_text: str) -> None:
|
|
"""Set up a mock for subprocess.run that returns the given stdout."""
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = stdout_text
|
|
mock_result.stderr = ""
|
|
mock_result.returncode = 0
|
|
|
|
patcher = patch("subprocess.run", return_value=mock_result)
|
|
context.cmcov3_subprocess_mock = patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@given("cmcov3 subprocess.run is mocked to return empty stdout")
|
|
def step_mock_subprocess_empty_stdout(context: Any) -> None:
|
|
"""Set up a mock for subprocess.run that returns empty stdout."""
|
|
mock_result = MagicMock()
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = ""
|
|
mock_result.returncode = 0
|
|
|
|
patcher = patch("subprocess.run", return_value=mock_result)
|
|
context.cmcov3_subprocess_mock = patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@when('cmcov3 the completion command is invoked with shell "{shell}"')
|
|
def step_invoke_completion(context: Any, shell: str) -> None:
|
|
"""Invoke the completion CLI command with the given shell argument."""
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import app
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(app, ["completion", shell])
|
|
context.cmcov3_cli_result = result
|
|
|
|
|
|
@then('cmcov3 the output should contain "{expected}"')
|
|
def step_check_output_contains(context: Any, expected: str) -> None:
|
|
"""Verify the CLI output contains the expected text."""
|
|
output = context.cmcov3_cli_result.output
|
|
assert expected in output, (
|
|
f"Expected output to contain {expected!r}, got: {output!r}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 593+: convert_exit_code function
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with None")
|
|
def step_convert_exit_code_none(context: Any) -> None:
|
|
"""Call convert_exit_code with None."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(None)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with an object having exit_code {code:d}")
|
|
def step_convert_exit_code_object(context: Any, code: int) -> None:
|
|
"""Call convert_exit_code with an object that has an exit_code attribute."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
obj = MagicMock()
|
|
obj.exit_code = code
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(obj)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with integer {code:d}")
|
|
def step_convert_exit_code_int(context: Any, code: int) -> None:
|
|
"""Call convert_exit_code with an integer."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code(code)
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@when("cmcov3 convert_exit_code is called with a non-convertible value")
|
|
def step_convert_exit_code_nonconvertible(context: Any) -> None:
|
|
"""Call convert_exit_code with a value that cannot be converted to int."""
|
|
from cleveragents.cli.main import convert_exit_code
|
|
|
|
try:
|
|
context.cmcov3_exit_code_result = convert_exit_code("not_a_number")
|
|
except Exception as exc:
|
|
context.cmcov3_error = exc
|
|
|
|
|
|
@then("cmcov3 the exit code result should be {expected:d}")
|
|
def step_check_exit_code_result(context: Any, expected: int) -> None:
|
|
"""Verify convert_exit_code returned the expected value."""
|
|
if hasattr(context, "cmcov3_error"):
|
|
raise AssertionError(
|
|
f"convert_exit_code raised an exception: {context.cmcov3_error}"
|
|
)
|
|
actual = context.cmcov3_exit_code_result
|
|
assert actual == expected, (
|
|
f"Expected convert_exit_code to return {expected}, got {actual}"
|
|
)
|