"""Step definitions for CLI main shortcut coverage scenarios.""" from __future__ import annotations import ast from typing import Any from unittest.mock import patch import typer from behave import then, when from typer.testing import CliRunner from cleveragents.cli.main import app, main # Map shortcut names to their underlying implementation targets. SHORTCUT_TARGETS = { "tell": "cleveragents.cli.commands.plan.tell", "build": "cleveragents.cli.commands.plan.build", "apply": "cleveragents.cli.commands.plan.lifecycle_apply_plan", "context-load": "cleveragents.cli.commands.context.context_add", "context-add": "cleveragents.cli.commands.context.context_add", } @when('I execute the CLI shortcut "{shortcut}" with argument list {arg_literal}') def step_execute_cli_shortcut(context: Any, shortcut: str, arg_literal: str) -> None: """Invoke a CLI shortcut while patching the underlying implementation.""" if shortcut not in SHORTCUT_TARGETS: raise AssertionError(f"Unhandled CLI shortcut: {shortcut}") try: parsed_args = ast.literal_eval(arg_literal) except Exception as exc: # pragma: no cover - defensive guard raise AssertionError( f"Argument list must be a valid Python list literal: {arg_literal}" ) from exc if not isinstance(parsed_args, list): raise AssertionError(f"Argument list must evaluate to a list: {arg_literal}") runner = CliRunner() target = SHORTCUT_TARGETS[shortcut] with patch(target) as mock_target: result = runner.invoke(app, [shortcut, *parsed_args]) if not mock_target.called: raise AssertionError(f"Expected shortcut '{shortcut}' to call {target}") # Record results for subsequent assertions. context.shortcut_name = shortcut context.shortcut_result = result if ( mock_target.call_args ): # pragma: no branch - call_args always present given the assertion above context.shortcut_call_args = list(mock_target.call_args.args) context.shortcut_call_kwargs = dict(mock_target.call_args.kwargs) else: # pragma: no cover - defensive guard context.shortcut_call_args = [] context.shortcut_call_kwargs = {} @then("the shortcut should exit with code {expected:d}") def step_shortcut_exit_code(context: Any, expected: int) -> None: """Ensure the CLI shortcut returned the expected exit code.""" result = getattr(context, "shortcut_result", None) if result is None: raise AssertionError("Shortcut result was not captured") assert result.exit_code == expected, ( f"Unexpected exit code for {context.shortcut_name}: " f"expected {expected}, got {result.exit_code}" ) @then("the shortcut should forward keyword arguments {expected_literal}") def step_shortcut_forward_kwargs(context: Any, expected_literal: str) -> None: """Verify the underlying command received the correct keyword arguments.""" try: expected_kwargs = ast.literal_eval(expected_literal) except Exception as exc: # pragma: no cover - defensive guard raise AssertionError( f"Expected keyword arguments must be a valid Python dict literal: {expected_literal}" ) from exc if not isinstance(expected_kwargs, dict): raise AssertionError( f"Expected keyword arguments must evaluate to a dict: {expected_literal}" ) actual_kwargs = getattr(context, "shortcut_call_kwargs", None) assert actual_kwargs == expected_kwargs, ( f"Forwarded kwargs mismatch for {context.shortcut_name}: " f"expected {expected_kwargs}, got {actual_kwargs}" ) @when('I execute main with stubbed return "{mode}" and value {value}') def step_execute_main_with_stub(context: Any, mode: str, value: str) -> None: """Run main() with the Typer app patched to return specific results.""" try: numeric_value = int(value) except ValueError as exc: raise AssertionError( f"Value parameter must be an integer literal, got: {value}" ) from exc call_tracker: dict[str, Any] = {"called": False} def stubbed_app(args: list[str], standalone_mode: bool = False) -> Any: call_tracker["called"] = True call_tracker["args"] = list(args) call_tracker["standalone_mode"] = standalone_mode if mode == "exit": return typer.Exit(numeric_value) if mode == "abort": return typer.Abort() if mode == "integer": return numeric_value if mode == "none": return None if mode == "raise_abort": raise typer.Abort() raise AssertionError(f"Unhandled stubbed mode: {mode}") with patch("cleveragents.cli.main.app", new=stubbed_app): context.main_result = main(["--help"]) context.stub_tracker = call_tracker if not call_tracker.get("called"): raise AssertionError("Patched Typer app was not invoked by main()") @then("the Typer app should have received {arg_literal} with standalone mode disabled") def step_verify_stub_invocation(context: Any, arg_literal: str) -> None: """Ensure the patched Typer app was called with expected arguments.""" try: expected_args = ast.literal_eval(arg_literal) except Exception as exc: # pragma: no cover - defensive guard raise AssertionError( f"Expected argument list must be a valid Python literal: {arg_literal}" ) from exc if not isinstance(expected_args, list): raise AssertionError( f"Expected argument list must evaluate to a list: {arg_literal}" ) tracker = getattr(context, "stub_tracker", None) if tracker is None: raise AssertionError("No stub tracker data found on context") actual_args = tracker.get("args") assert actual_args == expected_args, ( f"Patched Typer app received args {actual_args}, expected {expected_args}" ) standalone_mode = tracker.get("standalone_mode") assert standalone_mode is False, ( "Expected standalone_mode to be False for main() invocation" )