"""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}" )