"""Step definitions for the CleverAgents CLI feature - Fixed for Typer.""" from __future__ import annotations import contextlib import io import sys from collections.abc import Sequence from typing import Any from unittest.mock import MagicMock, patch import click from behave import then, when from typer.testing import CliRunner from cleveragents.__main__ import run from cleveragents.cli import app, cli, convert_exit_code, main from cleveragents.platform import ensure_cli_importable def _run_cli(context: Any, args: Sequence[str]) -> None: buffer = io.StringIO() with contextlib.redirect_stdout(buffer): exit_code = main(list(args)) context.result = { "exit_code": exit_code, "output": buffer.getvalue(), } # Also set context.output for compatibility with shared steps context.output = buffer.getvalue() context.exit_code = exit_code @when('I run the CleverAgents CLI with "--help"') def step_run_help(context: Any) -> None: _run_cli(context, ["--help"]) # Removed specific @then - using generic one from cli_coverage_steps.py instead @when('I run the CleverAgents CLI with "--version"') def step_run_version(context: Any) -> None: _run_cli(context, ["--version"]) # Removed specific @then - using generic one from cli_coverage_steps.py instead # New generic steps for other commands @when('I run the CleverAgents CLI with "{args}"') def step_run_cli_with_args(context, args): """Execute the CLI with specific arguments.""" runner = CliRunner() result = runner.invoke(app, args.split()) # Combine stdout and stderr for error checking output = result.stdout if result.stderr: output += result.stderr context.result = { "exit_code": result.exit_code, "output": output, "runner_result": result, } # Also set context.output for compatibility with shared steps context.output = output context.exit_code = result.exit_code # Removed duplicate @then - using generic one from cli_coverage_steps.py instead @then("the command should fail") def step_command_fails(context): """Verify the command failed.""" assert context.result["exit_code"] != 0, ( f"Expected non-zero exit code, got {context.result['exit_code']}" ) @then('the error should contain "{text}"') def step_error_should_contain(context, text): """Verify the error output contains expected text.""" # For Typer, errors may also be in stdout output = context.result.get("output", "") assert text.lower() in output.lower(), ( f"Expected '{text}' not found in output: {output}" ) # Exit code conversion steps @when("I convert exit code {code}") def step_convert_exit_code(context, code): """Convert an exit code.""" if code == "None": context.converted_code = convert_exit_code(None) else: context.converted_code = convert_exit_code(int(code)) @then("the result should be {expected}") def step_result_should_be(context, expected): """Verify the conversion result.""" assert context.converted_code == int(expected) @when("I convert a Click Exit exception with code {code}") def step_convert_click_exit(context, code): """Convert a Click Exit exception.""" exit_exc = click.exceptions.Exit(int(code)) context.converted_code = convert_exit_code(exit_exc) # Main function steps @when('I call main with arguments "{args}"') def step_call_main_with_args(context, args): """Call main function with arguments.""" # Directly call main function context.main_result = main(args.split()) @then("main should process the version flag") def step_main_processes_version(context): """Verify main processed the version flag.""" assert context.main_result == 0 @when("I call main with no arguments") def step_call_main_no_args(context): """Call main with no arguments.""" # Capture output to check if help is displayed buffer = io.StringIO() err_buffer = io.StringIO() with contextlib.redirect_stdout(buffer), contextlib.redirect_stderr(err_buffer): try: context.main_result = main([]) context.main_output = buffer.getvalue() context.main_stderr = err_buffer.getvalue() except SystemExit as e: context.main_result = e.code context.main_output = buffer.getvalue() context.main_stderr = err_buffer.getvalue() @then("main should display help") def step_main_displays_help(context): """Verify main displays help.""" # Check that help text was displayed output = context.main_output + context.main_stderr assert "Usage:" in output or "Commands:" in output or "help" in output.lower(), ( f"Expected help output, got: {output}" ) @when("I call main and CLI raises SystemExit {code}") def step_main_system_exit(context, code): """Call main with SystemExit.""" # We need to mock at a lower level since Typer doesn't use __call__ directly # Mock the version command to raise SystemExit from cleveragents.cli import main as cli_main_module # Use an invalid command that will trigger error handling # but mock the error handling to raise SystemExit with our code with patch("cleveragents.cli.main.err_console.print") as mock_print: # Make the function raise SystemExit when error is printed def raise_exit(*args, **kwargs): raise SystemExit(int(code)) mock_print.side_effect = raise_exit buffer = io.StringIO() with contextlib.redirect_stdout(buffer): # Use an invalid command to trigger the error path context.main_result = main(["invalid-test-command-xyz"]) @then("main should return {expected}") def step_main_returns_code(context, expected): """Verify main returns expected code.""" assert context.main_result == int(expected), ( f"Expected {expected}, got {context.main_result}" )