Files
cleverclaude-core/features/steps/cli_steps.py
T
2025-08-10 12:00:13 -04:00

264 lines
9.6 KiB
Python

"""Step definitions for CleverClaude CLI features."""
import os
import subprocess
import tempfile
from pathlib import Path
from behave import given, then, when
from click.testing import CliRunner
from hypothesis import given as hypothesis_given
from hypothesis import strategies as st
from cleverclaude.cli.main import app
@given("the CleverClaude CLI is available")
def step_cli_available(_context):
"""Ensure CleverClaude CLI is importable."""
_context.runner = CliRunner()
assert _context.runner is not None
@given("I have a test environment")
def step_test_environment(_context):
"""Set up test environment."""
# Use the test context from environment.py
assert hasattr(_context, "test_context")
@given("I have an empty directory")
def step_empty_directory(_context):
"""Create an empty test directory."""
_context.test_dir = Path(tempfile.mkdtemp(prefix="cleverclaude_cli_test_"))
os.chdir(_context.test_dir)
@given("I have a target directory {dirname}")
def step_target_directory(_context, dirname):
"""Create a target directory."""
_context.test_dir = Path(tempfile.mkdtemp(prefix="cleverclaude_cli_test_"))
_context.target_dir = _context.test_dir / dirname
os.chdir(_context.test_dir)
@given("I have a directory with existing files")
def step_directory_with_files(_context):
"""Create a directory with existing files."""
_context.test_dir = Path(tempfile.mkdtemp(prefix="cleverclaude_cli_test_"))
os.chdir(_context.test_dir)
# Create some existing files
(_context.test_dir / "existing_file.txt").write_text("This file already exists")
(_context.test_dir / "README.md").write_text("# Existing Project")
@given("I have an initialized CleverClaude project")
def step_initialized_project(_context):
"""Create an initialized CleverClaude project."""
_context.test_dir = Path(tempfile.mkdtemp(prefix="cleverclaude_cli_test_"))
os.chdir(_context.test_dir)
# Run init command to set up project
result = _context.runner.invoke(app, ["init"])
assert result.exit_code == 0
@given("CleverClaude is running")
def step_cleverclaude_running(_context):
"""Ensure CleverClaude system is running for testing."""
# This would start a test instance of CleverClaude
# For now, we'll mock this
_context.cleverclaude_running = True
@when('I run "{command}"')
def step_run_command(context, command):
"""Execute a CLI command."""
parts = command.split()
# Handle different command formats
if parts[0] == "cleverclaude":
args = parts[1:] # Remove "cleverclaude"
context.result = context.runner.invoke(app, args)
elif len(parts) >= 3 and parts[0] == "python" and parts[1] == "-m" and parts[2] == "cleverclaude":
args = parts[3:] # Remove "python -m cleverclaude"
context.result = context.runner.invoke(app, args)
else:
# Direct subprocess call for integration testing
try:
result = subprocess.run(
parts, capture_output=True, text=True, timeout=30, cwd=getattr(context, "test_dir", None)
)
# Create a mock result object
class MockResult:
def __init__(self, returncode, stdout, stderr):
self.exit_code = returncode
self.output = stdout + stderr
context.result = MockResult(result.returncode, result.stdout, result.stderr)
except subprocess.TimeoutExpired:
class MockResult:
def __init__(self):
self.exit_code = 124 # Timeout exit code
self.output = "Command timed out"
context.result = MockResult()
@when("I start the orchestration system in test mode")
def step_start_orchestration(_context):
"""Start CleverClaude orchestration in test mode."""
# This would involve starting the system asynchronously
# For testing, we'll simulate this
_context.orchestration_started = True
_context.result = type("MockResult", (), {"exit_code": 0, "output": "System started successfully"})()
@then("the exit code should be {code:d}")
def step_check_exit_code(context, code):
"""Verify exit code."""
assert context.result.exit_code == code, f"Expected exit code {code}, got {context.result.exit_code}"
@then('the output should contain "{text}"')
def step_output_contains(context, text):
"""Check if output contains text."""
assert text in context.result.output, f"Output does not contain '{text}'. Output was: {context.result.output}"
@then('the directory "{dirname}" should exist')
def step_directory_exists(context, dirname):
"""Check if directory exists."""
test_dir = getattr(context, "test_dir", Path.cwd())
dir_path = test_dir / dirname
assert dir_path.exists() and dir_path.is_dir(), f"Directory '{dirname}' does not exist at {test_dir}"
@then('the file "{filename}" should exist')
def step_file_exists(context, filename):
"""Check if file exists."""
test_dir = getattr(context, "test_dir", Path.cwd())
file_path = test_dir / filename
assert file_path.exists() and file_path.is_file(), f"File '{filename}' does not exist at {test_dir}"
@then('the file "{filename}" should contain "{text}"')
def step_file_contains(context, filename, text):
"""Check if file contains specific text."""
test_dir = getattr(context, "test_dir", Path.cwd())
file_path = test_dir / filename
assert file_path.exists(), f"File '{filename}' does not exist"
content = file_path.read_text()
assert text in content, f"File '{filename}' does not contain '{text}'"
@then("the system should initialize successfully")
def step_system_initializes(_context):
"""Verify system initialization."""
assert getattr(_context, "orchestration_started", False), "System did not start"
@then("the agent manager should be running")
def step_agent_manager_running(_context):
"""Verify agent manager is running."""
# This would check if the agent manager is actually running
# For testing, we'll assume success if orchestration started
assert getattr(_context, "orchestration_started", False), "Agent manager not running"
@then("the API server should be accessible")
def step_api_server_accessible(_context):
"""Verify API server is accessible."""
# This would check if the API server is responding
# For testing, we'll simulate this
assert getattr(_context, "orchestration_started", False), "API server not accessible"
@then("the output should contain system health information")
def step_output_contains_health_info(context):
"""Check if output contains system health information."""
health_indicators = ["status", "health", "running", "active"]
output_lower = context.result.output.lower()
assert any(indicator in output_lower for indicator in health_indicators), "No health information found in output"
@then("the output should contain agent count")
def step_output_contains_agent_count(context):
"""Check if output contains agent count information."""
output_lower = context.result.output.lower()
agent_indicators = ["agent", "count", "total", "active"]
assert any(indicator in output_lower for indicator in agent_indicators), (
"No agent count information found in output"
)
@then("the output should contain memory usage")
def step_output_contains_memory_usage(context):
"""Check if output contains memory usage information."""
output_lower = context.result.output.lower()
memory_indicators = ["memory", "usage", "ram", "heap"]
assert any(indicator in output_lower for indicator in memory_indicators), (
"No memory usage information found in output"
)
@then("the output should contain command-specific help")
def step_output_contains_help(context):
"""Check if output contains command-specific help."""
help_indicators = ["help", "usage", "options", "commands"]
output_lower = context.result.output.lower()
assert any(indicator in output_lower for indicator in help_indicators), "No help information found in output"
@when("I fuzz test the CLI with random invalid arguments")
def step_fuzz_cli_invalid(context):
"""Fuzz test the CLI with random invalid arguments."""
runner = context.runner
results = []
@hypothesis_given(
st.lists(
st.one_of(
st.text(min_size=1, max_size=50), st.integers(), st.floats(allow_nan=False, allow_infinity=False)
),
min_size=1,
max_size=10,
)
)
def test_random_invalid_args(args):
# Convert all args to strings
str_args = [str(arg) for arg in args]
try:
result = runner.invoke(app, str_args)
results.append(result)
# Should either succeed (exit code 0) or fail gracefully (non-zero but not crash)
assert result.exit_code in [0, 1, 2], f"Unexpected exit code: {result.exit_code}"
except Exception as e:
# Should not raise unhandled exceptions
raise AssertionError(f"CLI crashed with unhandled exception: {e}") from e
# Run the hypothesis test
test_random_invalid_args()
context.fuzz_results = results
@then("all invocations should either succeed or fail gracefully")
def step_all_succeed_or_fail_gracefully(context):
"""Verify all fuzz test invocations succeeded or failed gracefully."""
assert hasattr(context, "fuzz_results"), "No fuzz test results found"
# If we get here, hypothesis didn't raise any assertion errors
@then("no invocation should crash the system")
def step_no_crashes(_context):
"""Verify no invocations crashed the system."""
# This is verified by the fuzz test above - if we reach here, no crashes occurred
pass