4f2aa4189c
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 3m20s
CI / typecheck (pull_request) Successful in 3m56s
CI / build (pull_request) Successful in 37s
CI / security (pull_request) Successful in 4m2s
CI / quality (pull_request) Successful in 4m8s
CI / unit_tests (pull_request) Failing after 4m13s
CI / docker (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 7m10s
CI / e2e_tests (pull_request) Successful in 9m34s
CI / coverage (pull_request) Successful in 11m1s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-regression (pull_request) Has been cancelled
Click/Typer CliRunner.Result.stderr is a property that raises ValueError when stderr was not separately captured (mix_stderr=True is the default). Wrap all result.stderr accesses in try/except to handle this gracefully.
220 lines
7.1 KiB
Python
220 lines
7.1 KiB
Python
"""Step definitions for the CleverAgents CLI feature - Fixed for Typer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import io
|
|
from collections.abc import Sequence
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
import click
|
|
from behave import then, when
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.main import (
|
|
app,
|
|
convert_exit_code,
|
|
ensure_cli_commands_registered,
|
|
main,
|
|
)
|
|
|
|
|
|
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.command_output = (
|
|
buffer.getvalue()
|
|
) # For cli_plan_context_commands_steps compatibility
|
|
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."""
|
|
ensure_cli_commands_registered()
|
|
runner = CliRunner()
|
|
result = runner.invoke(app, args.split())
|
|
# Combine stdout and stderr for error checking
|
|
output = result.stdout
|
|
try:
|
|
if result.stderr:
|
|
output += result.stderr
|
|
except (ValueError, AttributeError):
|
|
pass
|
|
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.command_output = output # For cli_plan_context_commands_steps compatibility
|
|
context.exit_code = result.exit_code
|
|
|
|
|
|
@then('the CLI output should contain "{text}"')
|
|
def step_cli_output_contains(context, text):
|
|
output = getattr(context, "output", "") or context.result.get("output", "")
|
|
assert text in output, f"Expected '{text}' in output: {output}"
|
|
|
|
|
|
@then('the CLI output should not contain "{text}"')
|
|
def step_cli_output_not_contains(context, text):
|
|
output = getattr(context, "output", "") or context.result.get("output", "")
|
|
assert text not in output, f"Did not expect '{text}' in output: {output}"
|
|
|
|
|
|
# Removed duplicate @then - using generic one from cli_coverage_steps.py instead
|
|
|
|
|
|
@then("the CLI command should fail")
|
|
def step_cli_command_fails(context):
|
|
"""Verify the CLI 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
|
|
@then("the exit code should be {expected:d}")
|
|
def step_exit_code_should_be(context, expected):
|
|
"""Check exit code."""
|
|
# Check multiple possible attributes for backward compatibility
|
|
if hasattr(context, "last_exit_code"):
|
|
actual = context.last_exit_code
|
|
elif hasattr(context, "exit_code"):
|
|
actual = context.exit_code
|
|
elif hasattr(context, "result") and isinstance(context.result, dict):
|
|
actual = context.result.get("exit_code", 0)
|
|
else:
|
|
actual = 0
|
|
assert actual == expected, f"Expected exit code {expected}, got {actual}"
|
|
|
|
|
|
@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
|
|
|
|
# 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}"
|
|
)
|