Files
cleveragents-core/features/steps/cli_main_cov3_steps.py
T
HAL9000 b747f1aab1
CI / benchmark-publish (pull_request) Has been skipped
CI / push-validation (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 42s
CI / build (pull_request) Successful in 59s
CI / lint (pull_request) Successful in 1m7s
CI / typecheck (pull_request) Successful in 1m30s
CI / quality (pull_request) Successful in 1m31s
CI / security (pull_request) Successful in 1m39s
CI / integration_tests (pull_request) Successful in 4m6s
CI / unit_tests (pull_request) Successful in 4m32s
CI / e2e_tests (pull_request) Successful in 4m54s
CI / docker (pull_request) Successful in 1m28s
CI / coverage (pull_request) Successful in 11m22s
CI / status-check (push) Blocked by required conditions
CI / status-check (pull_request) Successful in 4s
CI / benchmark-publish (push) Failing after 53s
CI / lint (push) Successful in 59s
CI / quality (push) Successful in 1m18s
CI / build (push) Successful in 36s
CI / typecheck (push) Successful in 1m29s
CI / security (push) Successful in 1m37s
CI / helm (push) Successful in 35s
CI / push-validation (push) Successful in 20s
CI / integration_tests (push) Successful in 3m28s
CI / e2e_tests (push) Successful in 3m32s
CI / unit_tests (push) Successful in 4m39s
CI / coverage (push) Has started running
CI / docker (push) Successful in 1m56s
style: reformat cli_main_cov3_steps.py for ruff
The get_err_console patch.object call was being split across lines
in a way ruff did not approve; format it the way ruff expects.
2026-04-30 19:40:33 +00:00

221 lines
8.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.
Also evicts cleveragents.cli.commands and all its sub-packages from
sys.modules so the __import__ patch actually takes effect — the module is
normally cached from the eager _register_subcommands() call at import time.
"""
import sys
import builtins
mod = context.cmcov3_mod
mock_err_console = MagicMock()
original_import = builtins.__import__
# Evict cleveragents.cli.commands and all its sub-packages/modules from
# sys.modules so the __import__ patch takes effect.
module_name = "cleveragents.cli.commands"
original_modules = {
key: sys.modules[key]
for key in list(sys.modules)
if key == module_name or key.startswith(module_name + ".")
}
for key in original_modules:
del sys.modules[key]
def failing_import(name: str, *args: Any, **kwargs: Any) -> Any:
if name == module_name:
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()
context.add_cleanup(patcher_builtins.stop)
# Use BaseException to also catch SystemExit which is raised by
# _register_subcommands() on import failure (SystemExit extends
# BaseException, not Exception, so except Exception would not catch it).
try:
mod._register_subcommands()
except BaseException as exc:
context.cmcov3_error = exc
# Restore original sys.modules entries for other scenarios.
context.add_cleanup(lambda: sys.modules.update(original_modules))
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}"
)