Files
cleveragents-core/features/steps/auto_debug_integration_steps.py
T
Jeffrey Phillips Freeman e273b52769
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 29s
CI / security (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m54s
CI / build (pull_request) Successful in 16s
CI / unit_tests (pull_request) Successful in 12m9s
CI / docker (pull_request) Successful in 43s
CI / coverage (pull_request) Successful in 7m34s
CI / lint (push) Successful in 13s
CI / typecheck (push) Successful in 30s
CI / security (push) Successful in 22s
CI / quality (push) Successful in 17s
CI / integration_tests (push) Successful in 5m18s
CI / build (push) Successful in 16s
CI / unit_tests (push) Successful in 10m53s
CI / coverage (push) Successful in 7m49s
CI / docker (push) Successful in 39s
feat(db): add projects and project links tables
2026-02-16 12:18:51 -05:00

245 lines
9.5 KiB
Python

"""Step definitions for AutoDebug integration tests."""
import os
from unittest.mock import MagicMock, Mock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.application.container import get_container, reset_container
@given('I have a current plan with instruction "{instruction}"')
def step_create_plan_with_instruction(context: Context, instruction: str) -> None:
"""Create a plan with given instruction."""
from cleveragents.cli.commands.plan import tell_command
# Ensure we have a project first
if not hasattr(context, "project"):
container = get_container()
project_service = container.project_service()
context.project = project_service.get_current_project()
tell_command(prompt=instruction, name=None)
# Get the plan from container
container = get_container()
plan_service = container.plan_service()
context.plan = plan_service.get_current_plan(context.project)
@given("the AI provider will generate code with a syntax error")
def step_ai_generates_syntax_error(context: Context) -> None:
"""Configure mock AI to generate code with syntax error."""
context.mock_error_type = "syntax"
context.mock_build_error = "SyntaxError: invalid syntax on line 5"
context.mock_generated_code = (
"def foo():\n print('hello'\n" # Missing closing paren
)
@given("the AutoDebugAgent can fix the syntax error")
def step_autodebug_can_fix_syntax(context: Context) -> None:
"""Configure AutoDebugAgent to successfully fix syntax error."""
context.autodebug_can_fix = True
context.autodebug_fix_code = "def foo():\n print('hello')\n"
@given("the AI provider will generate code with an import error")
def step_ai_generates_import_error(context: Context) -> None:
"""Configure mock AI to generate code with import error."""
context.mock_error_type = "import"
context.mock_build_error = "ImportError: No module named 'nonexistent'"
context.mock_generated_code = "import nonexistent\n\ndef foo():\n pass\n"
@given("the AutoDebugAgent can fix the import error")
def step_autodebug_can_fix_import(context: Context) -> None:
"""Configure AutoDebugAgent to successfully fix import error."""
context.autodebug_can_fix = True
context.autodebug_fix_code = "# Removed bad import\n\ndef foo():\n pass\n"
@given("the AI provider will generate code with an unfixable error")
def step_ai_generates_unfixable_error(context: Context) -> None:
"""Configure mock AI to generate code with unfixable error."""
context.mock_error_type = "unfixable"
context.mock_build_error = "RuntimeError: System configuration error"
context.mock_generated_code = "# This error cannot be fixed by code changes\n"
@given("the AutoDebugAgent cannot fix the error")
def step_autodebug_cannot_fix(context: Context) -> None:
"""Configure AutoDebugAgent to fail at fixing error."""
context.autodebug_can_fix = False
@given("the AI provider will generate code with a validation error")
def step_ai_generates_validation_error(context: Context) -> None:
"""Configure mock AI to generate code with validation error."""
context.mock_error_type = "validation"
context.mock_build_error = "ValidationError: Code does not meet requirements"
context.mock_generated_code = "# Code that fails validation\n"
@when("I run auto_debug_build with max attempts {max_attempts:d}")
def step_run_auto_debug_build(context: Context, max_attempts: int) -> None:
"""Run auto_debug_build with specified max attempts."""
from cleveragents.application.container import get_container
container = get_container()
plan_service = container.plan_service()
# Mock the build_plan to simulate failure
original_build = plan_service.build_plan
def mock_build(project, progress_callback=None):
if hasattr(context, "mock_build_error") and not getattr(
context, "build_succeeded", False
):
# First attempt fails
if getattr(context, "autodebug_can_fix", False):
# Mark that next attempt should succeed
context.build_succeeded = True
raise Exception(context.mock_build_error)
# Subsequent attempts succeed if AutoDebug can fix
return original_build(project, progress_callback=progress_callback)
# Create mock LLM that returns deterministic responses
mock_llm = MagicMock()
mock_response = Mock()
mock_response.content = "Mock LLM response"
mock_llm.invoke = MagicMock(return_value=mock_response)
# Patch build_plan and the LLM creation
with (
patch.object(plan_service, "build_plan", side_effect=mock_build),
patch(
"langchain_community.llms.FakeListLLM",
return_value=mock_llm,
),
):
try:
success, changes, error = plan_service.auto_debug_build(
project=context.project, max_attempts=max_attempts
)
context.auto_debug_success = success
context.auto_debug_changes = changes
context.auto_debug_error = error
except Exception as e:
context.auto_debug_success = False
context.auto_debug_error = str(e)
context.auto_debug_changes = []
@then("the build should succeed after debugging")
def step_build_succeeds(context: Context) -> None:
"""Verify build succeeded after auto-debug."""
assert context.auto_debug_success, f"Build failed: {context.auto_debug_error}"
@then("the build should fail after {attempts:d} attempts")
def step_build_fails_after_attempts(context: Context, attempts: int) -> None:
"""Verify build failed after specified attempts."""
assert not context.auto_debug_success, "Build should have failed but succeeded"
@then("debug attempts should be saved to database")
def step_debug_attempts_saved(context: Context) -> None:
"""Verify debug attempts were saved to database."""
from cleveragents.application.container import get_container
container = get_container()
with container.unit_of_work().transaction() as uow:
attempts = uow.debug_attempts.get_for_plan(context.plan.id)
assert len(attempts) > 0, "No debug attempts saved to database"
context.saved_debug_attempts = attempts
@then("the final attempt should be marked as successful")
def step_final_attempt_successful(context: Context) -> None:
"""Verify final debug attempt is marked successful."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
# Find the highest attempt number
max_attempt = max(a.attempt_number for a in context.saved_debug_attempts)
final_attempt = next(
a for a in context.saved_debug_attempts if a.attempt_number == max_attempt
)
assert final_attempt.success, "Final attempt not marked as successful"
@then("at least {count:d} debug attempt should exist")
def step_at_least_n_attempts(context: Context, count: int) -> None:
"""Verify at least N debug attempts exist."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
assert len(context.saved_debug_attempts) >= count, (
f"Expected at least {count} attempts, got {len(context.saved_debug_attempts)}"
)
@then("all {count:d} debug attempts should be marked as unsuccessful")
def step_all_attempts_unsuccessful(context: Context, count: int) -> None:
"""Verify all attempts are marked unsuccessful."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
assert len(context.saved_debug_attempts) == count, (
f"Expected {count} attempts, got {len(context.saved_debug_attempts)}"
)
for attempt in context.saved_debug_attempts:
assert not attempt.success, (
f"Attempt {attempt.attempt_number} should be unsuccessful"
)
@then("each attempt should have an error message")
def step_attempts_have_error_messages(context: Context) -> None:
"""Verify each attempt has an error message."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
for attempt in context.saved_debug_attempts:
assert attempt.error_message, (
f"Attempt {attempt.attempt_number} missing error message"
)
@then("each attempt should have an attempt number")
def step_attempts_have_numbers(context: Context) -> None:
"""Verify each attempt has an attempt number."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
for attempt in context.saved_debug_attempts:
assert attempt.attempt_number is not None, "Attempt missing attempt number"
assert attempt.attempt_number >= 1, (
f"Invalid attempt number: {attempt.attempt_number}"
)
@then("each attempt should have a timestamp")
def step_attempts_have_timestamps(context: Context) -> None:
"""Verify each attempt has a timestamp."""
assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
for attempt in context.saved_debug_attempts:
assert attempt.created_at, f"Attempt {attempt.attempt_number} missing timestamp"
def after_scenario(context: Context, scenario) -> None:
"""Clean up after each scenario."""
# Change back to original directory
if hasattr(context, "original_cwd"):
os.chdir(context.original_cwd)
# Clean up temp directory
if hasattr(context, "test_dir"):
import shutil
shutil.rmtree(context.test_dir, ignore_errors=True)
# Reset container
reset_container()
# Clean up environment
os.environ.pop("CLEVERAGENTS_TESTING_USE_MOCK_AI", None)