Files
cleveragents-core/features/steps/cli_steps.py
T
2025-11-05 15:11:03 -05:00

184 lines
5.8 KiB
Python

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