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
245 lines
9.5 KiB
Python
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)
|