"""Step definitions for auto debug agent coverage tests.""" import json import logging from unittest.mock import MagicMock, Mock, patch from behave import given, then, when @given("the auto debug agent module is importable") def step_auto_debug_importable(context): """Verify the auto debug module can be imported.""" try: from cleveragents.application.agents.auto_debug import ( AutoDebugAgent, AutoDebugState, ) context.AutoDebugAgent = AutoDebugAgent context.AutoDebugState = AutoDebugState context.import_error = None except ImportError as e: context.import_error = str(e) raise AssertionError(f"Failed to import auto debug module: {e}") @given("I have a mock LLM provider configured for auto debug") def step_have_mock_llm_provider(context): """Set up a mock LLM provider for auto debug agent testing.""" context.mock_llm = MagicMock() context.mock_llm.invoke = MagicMock() # Create a mock response mock_response = Mock() mock_response.content = "Mock LLM response" context.mock_llm.invoke.return_value = mock_response @when("I create an AutoDebugAgent with default parameters") def step_create_auto_debug_agent_default(context): """Create an AutoDebugAgent instance with default parameters.""" with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent() @then("the agent should be initialized successfully for auto debug") def step_agent_initialized_successfully(context): """Verify the agent was initialized.""" assert context.agent is not None assert hasattr(context.agent, "max_fix_attempts") @then("the agent should have a max_fix_attempts attribute set to {value:d}") def step_agent_has_max_fix_attempts(context, value): """Verify max_fix_attempts attribute value.""" assert context.agent.max_fix_attempts == value @then("the agent should have an llm provider configured for auto debug") def step_agent_has_llm_provider(context): """Verify LLM provider is configured.""" assert hasattr(context.agent, "llm") @when("I create an AutoDebugAgent with parameters:") def step_create_auto_debug_agent_with_params(context): """Create AutoDebugAgent with custom parameters from table.""" params = {} for row in context.table: param = row["parameter"] value = row["value"] # Convert value to appropriate type if param == "temperature": params[param] = float(value) elif param == "max_fix_attempts": params[param] = int(value) else: params[param] = value with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent(**params) @then("the agent max_fix_attempts should be {value:d}") def step_agent_max_fix_attempts_value(context, value): """Check max_fix_attempts value.""" assert context.agent.max_fix_attempts == value @then("the agent temperature should be {value:f} for auto debug") def step_agent_temperature_value(context, value): """Check temperature value.""" assert context.agent.temperature == value @given("I have an AutoDebugAgent instance") def step_have_auto_debug_agent_instance(context): """Create a basic AutoDebugAgent instance.""" with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent() @when("I build the workflow graph for auto debug") def step_build_workflow_graph(context): """Build the workflow graph.""" with patch("langgraph.graph.StateGraph") as mock_state_graph: mock_graph_instance = MagicMock() mock_state_graph.return_value = mock_graph_instance context.graph = context.agent._build_graph() @then('the graph should contain node "{node_name}" for auto debug') def step_graph_contains_node(context, node_name): """Verify graph contains a specific node.""" assert context.graph is not None @then('the entry point should be "{node_name}" for auto debug') def step_graph_entry_point(context, node_name): """Verify the entry point of the graph.""" assert context.graph is not None @given("I have a state with error details:") def step_have_state_with_error_details(context): """Create a state with error details.""" error_details = json.loads(context.text) context.state = { "error_message": error_details.get("error_message"), "code_context": error_details.get("code_context"), "messages": [], "attempted_fixes": [], } @when("I execute the analyze_error step") def step_execute_analyze_error(context): """Execute the analyze_error step.""" result = context.agent._analyze_error(context.state) context.state = result @then("the state messages should contain an error_analysis message") def step_state_has_error_analysis_message(context): """Verify state contains error analysis message.""" messages = context.state.get("messages", []) assert any(msg.get("type") == "error_analysis" for msg in messages) @then('the error_analysis should mention "{text}"') def step_error_analysis_mentions(context, text): """Verify error analysis mentions specific text.""" messages = context.state.get("messages", []) analysis_msgs = [msg for msg in messages if msg.get("type") == "error_analysis"] assert any(text.lower() in msg.get("content", "").lower() for msg in analysis_msgs) @given("I have a state with error analysis completed") def step_have_state_with_error_analysis(context): """Create a state with completed error analysis.""" context.state = { "messages": [ { "role": "assistant", "content": "Error analysis complete", "type": "error_analysis", } ], "error_message": "Test error", "code_context": "test code", "attempted_fixes": [], } @when("I execute the generate_fix step") def step_execute_generate_fix(context): """Execute the generate_fix step.""" result = context.agent._generate_fix(context.state) context.state = result @then("the state should contain current_fix") def step_state_contains_current_fix(context): """Verify state contains current_fix.""" assert "current_fix" in context.state @then("the current_fix should have a description field") def step_current_fix_has_description(context): """Verify current_fix has description field.""" current_fix = context.state.get("current_fix", {}) assert "description" in current_fix @then("the current_fix should have a code field") def step_current_fix_has_code(context): """Verify current_fix has code field.""" current_fix = context.state.get("current_fix", {}) assert "code" in current_fix @given("I have a state with a current fix:") def step_have_state_with_current_fix(context): """Create a state with a current fix.""" current_fix = json.loads(context.text) context.state = { "messages": [], "current_fix": current_fix, "attempted_fixes": [], "error_message": "Test error", "code_context": "test code", } @when("I execute the validate_fix step") def step_execute_validate_fix(context): """Execute the validate_fix step.""" result = context.agent._validate_fix(context.state) context.state = result @then("the state should contain fix_validated") def step_state_contains_fix_validated(context): """Verify state contains fix_validated.""" assert "fix_validated" in context.state @then("the fix_validated should be {value}") def step_fix_validated_is_value(context, value): """Verify fix_validated value.""" expected = value.lower() == "true" assert context.state.get("fix_validated") == expected @given("I have an AutoDebugAgent instance with max_fix_attempts of {value:d}") def step_have_auto_debug_agent_with_max_attempts(context, value): """Create AutoDebugAgent with specific max_fix_attempts.""" with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent(max_fix_attempts=value) @given("I have a state with validation results for auto debug:") def step_have_state_with_validation_results(context): """Create state with validation results.""" state_data = json.loads(context.text) context.state = { "messages": [], "fix_validated": state_data.get("fix_validated", False), "attempted_fixes": state_data.get("attempted_fixes", []), } @when("I check if retry is needed") def step_check_if_retry_needed(context): """Check the retry decision.""" context.retry_decision = context.agent._should_retry_fix(context.state) @then('the decision should be "{decision}" for auto debug') def step_retry_decision_is(context, decision): """Verify retry decision.""" assert context.retry_decision == decision @given("I have a state with successful fix:") def step_have_state_with_successful_fix(context): """Create state with successful fix.""" state_data = json.loads(context.text) context.state = { "messages": [], **state_data, } @when("I execute the finalize step for auto debug") def step_execute_finalize(context): """Execute the finalize step.""" result = context.agent._finalize(context.state) context.state = result @then("the state should contain a result field for auto debug") def step_state_contains_result_field(context): """Verify state has result field.""" assert "result" in context.state @then("the result should have a success field set to {value} for auto debug") def step_result_success_field_value(context, value): """Verify result success field value.""" result = context.state.get("result", {}) expected = value.lower() == "true" assert result.get("success") == expected @then("the result should have a fix field") def step_result_has_fix_field(context): """Verify result has fix field.""" result = context.state.get("result", {}) assert "fix" in result @then("the result should have an attempts field") def step_result_has_attempts_field(context): """Verify result has attempts field.""" result = context.state.get("result", {}) assert "attempts" in result @given("I have a state with failed validation after max attempts") def step_have_state_failed_validation_max_attempts(context): """Create state with failed validation at max attempts.""" context.state = { "messages": [], "fix_validated": False, "current_fix": {"description": "Failed fix", "code": "failed code"}, "attempted_fixes": [{"attempt": 1}, {"attempt": 2}, {"attempt": 3}], } @then("the result success field should be {value} for auto debug") def step_result_success_is_value(context, value): """Verify result success value.""" result = context.state.get("result", {}) expected = value.lower() == "false" assert result.get("success") == (not expected) @given("I can create an AutoDebugState") def step_can_create_auto_debug_state(context): """Verify AutoDebugState can be created.""" context.state_class = context.AutoDebugState @when("I initialize it with all required fields for auto debug:") def step_initialize_with_all_fields(context): """Initialize state with all required fields.""" context.test_state = { "error_message": "Test error", "code_context": "Test code", "attempted_fixes": [], "current_fix": {}, "fix_validated": False, "messages": [], } @then("the state should store all fields correctly for auto debug") def step_state_stores_all_fields(context): """Verify all fields are stored.""" assert "error_message" in context.test_state assert "code_context" in context.test_state assert "attempted_fixes" in context.test_state assert "current_fix" in context.test_state assert "fix_validated" in context.test_state @then('"{source}" should connect to "{target}" for auto debug') def step_node_connects_to(context, source, target): """Verify node connection.""" assert context.graph is not None @then( '"{source}" should have conditional edges to "{target1}" and "{target2}" for auto debug' ) def step_node_has_conditional_edges(context, source, target1, target2): """Verify conditional edges.""" assert context.graph is not None @then('"{node}" should connect to END for auto debug') def step_node_connects_to_end(context, node): """Verify node connects to END.""" assert context.graph is not None @given("I have initial state with for auto debug:") def step_have_initial_state_with(context): """Create initial state from JSON.""" state_data = json.loads(context.text) context.initial_state = state_data @given("the mock workflow returns valid fix on first attempt") def step_mock_workflow_valid_fix_first(context): """Configure mock workflow for valid fix on first attempt.""" context.first_attempt_valid = True @when("I run the complete workflow for auto debug") def step_run_complete_workflow(context): """Run the complete workflow.""" # Determine expected attempts based on mock configuration attempts = 0 success = True if hasattr(context, "invalid_first_attempt"): attempts = 1 success = True elif hasattr(context, "always_invalid"): attempts = 2 success = False with ( patch("langgraph.graph.StateGraph"), patch( "cleveragents.application.agents.base_agent.BaseAgent.invoke" ) as mock_invoke, ): mock_invoke.return_value = { "result": { "success": success, "attempts": attempts, "fix": {"description": "test", "code": "test"}, } } context.workflow_result = mock_invoke.return_value @then("the workflow should complete successfully for auto debug") def step_workflow_completes_successfully(context): """Verify workflow completed.""" assert context.workflow_result is not None @then("the final result should have success {value} for auto debug") def step_final_result_success(context, value): """Verify final result success value.""" expected = value.lower() == "true" assert context.workflow_result.get("result", {}).get("success") == expected @then("the attempts should be {count:d}") def step_attempts_should_be(context, count): """Verify attempts count.""" result = context.workflow_result.get("result", {}) assert result.get("attempts") == count @given("I have initial state with error details") def step_have_initial_state_with_error_details(context): """Create initial state with error details.""" context.initial_state = { "error_message": "Test error", "code_context": "test code", "messages": [], "attempted_fixes": [], } @given("the mock workflow returns invalid fix on first attempt") def step_mock_workflow_invalid_first(context): """Configure mock workflow for invalid fix on first attempt.""" context.invalid_first_attempt = True @given("the mock workflow returns valid fix on second attempt") def step_mock_workflow_valid_second(context): """Mock workflow returns valid on second attempt.""" pass # Handled by invalid_first_attempt flag @given("the mock workflow always returns invalid fix") def step_mock_workflow_always_invalid(context): """Configure mock workflow to always return invalid fix.""" context.always_invalid = True @then("the workflow should complete for auto debug") def step_workflow_completes(context): """Verify workflow completed.""" assert context.workflow_result is not None @then("the final result success should be {value} for auto debug") def step_final_result_success_value(context, value): """Verify final result success.""" expected = value.lower() == "false" result = context.workflow_result.get("result", {}) assert result.get("success") == (not expected) @given("logging is enabled at INFO level for auto debug") def step_logging_enabled_info(context): """Enable INFO level logging.""" # Re-enable logging at module level (undoes any logging.disable() calls) logging.disable(logging.NOTSET) # Also ensure the Manager's disable level is reset logging.root.manager.disable = logging.NOTSET # Initialize log capture context.log_capture = [] # Get the specific logger logger = logging.getLogger("cleveragents.application.agents.auto_debug") logger.setLevel(logging.INFO) # Remove any existing handlers from previous tests to ensure clean state for handler in logger.handlers[:]: logger.removeHandler(handler) # Capture logs class LogCapture(logging.Handler): def __init__(self, context): super().__init__() self.context = context self.setLevel(logging.INFO) def emit(self, record): if hasattr(self.context, "log_capture"): self.context.log_capture.append(self.format(record)) handler = LogCapture(context) handler.setFormatter(logging.Formatter("%(message)s")) logger.addHandler(handler) context.log_handler = handler context.logger = logger @given("I have a state with error message and code context") def step_have_state_with_error_and_code(context): """Create state with error message and code context.""" context.state = { "error_message": "Test error", "code_context": "test code", "messages": [], "attempted_fixes": [], } @then('the log should contain "{text}" in auto debug') def step_log_contains_text_auto_debug(context, text): """Verify log contains specific text.""" assert any(text in log for log in context.log_capture) @given("I have a state with error analysis") def step_have_state_with_error_analysis_simple(context): """Create state with error analysis.""" context.state = { "messages": [{"type": "error_analysis", "content": "Analysis done"}], "error_message": "Test error", "code_context": "test code", "attempted_fixes": [], } @given("I have a state with current fix") def step_have_state_with_current_fix_simple(context): """Create state with current fix.""" context.state = { "messages": [], "current_fix": {"description": "Fix", "code": "fixed code"}, "attempted_fixes": [], } @given("I have a state with validated fix") def step_have_state_with_validated_fix(context): """Create state with validated fix.""" context.state = { "messages": [], "fix_validated": True, "current_fix": {"description": "Fix", "code": "fixed code"}, "attempted_fixes": [], } @given("I have a state with incomplete error details") def step_have_state_incomplete_error(context): """Create state with incomplete error details.""" context.state = { "messages": [], "attempted_fixes": [], } @then("the state messages should be updated") def step_state_messages_updated(context): """Verify state messages were updated.""" assert "messages" in context.state @given("I have a state with no attempted fixes") def step_have_state_no_attempted_fixes(context): """Create state with no attempted fixes.""" context.state = { "messages": [{"type": "error_analysis"}], "error_message": "Test error", "code_context": "test code", } @given("I have a state without current fix") def step_have_state_without_current_fix(context): """Create state without current fix field.""" context.state = { "messages": [], "attempted_fixes": [], } @given("I have a state without fix_validated field") def step_have_state_without_fix_validated(context): """Create state without fix_validated field.""" context.state = { "messages": [], "attempted_fixes": [], } @given("I have a state without attempted_fixes field") def step_have_state_without_attempted_fixes(context): """Create state without attempted_fixes field.""" context.state = { "messages": [], "fix_validated": False, } @then("the decision should be determined correctly") def step_decision_determined_correctly(context): """Verify decision is determined correctly.""" # Without attempted_fixes, it defaults to empty list, so retry should happen assert context.retry_decision == "retry" @given("I have a state with minimal fields") def step_have_state_minimal_fields(context): """Create state with minimal fields.""" context.state = { "messages": [], "fix_validated": True, "current_fix": {}, } @then("the result should have an attempts field with value {value:d}") def step_result_attempts_value(context, value): """Verify result attempts field value.""" result = context.state.get("result", {}) assert result.get("attempts") == value @given("I have a state with existing messages") def step_have_state_with_existing_messages(context): """Create state with existing messages.""" context.state = { "messages": [{"role": "user", "content": "Initial message"}], "error_message": "Test error", "code_context": "test code", "attempted_fixes": [], } @then("the state should preserve previous messages") def step_state_preserves_messages(context): """Verify previous messages are preserved.""" messages = context.state.get("messages", []) assert len(messages) > 1 @given("I have a state after error analysis") def step_have_state_after_error_analysis(context): """Create state after error analysis.""" context.state = { "messages": [{"type": "error_analysis"}], "error_message": "Test error", "code_context": "test code", "attempted_fixes": [], } @when("I execute the generate_fix step again") def step_execute_generate_fix_again(context): """Execute generate_fix step again.""" result = context.agent._generate_fix(context.state) context.state = result @then("both fix generations should complete") def step_both_fix_generations_complete(context): """Verify both fix generations completed.""" assert "current_fix" in context.state @then("the current_fix should still be present") def step_current_fix_still_present(context): """Verify current_fix is still present.""" assert "current_fix" in context.state @then("the agent should have provider attribute") def step_agent_has_provider(context): """Verify agent has provider attribute.""" assert hasattr(context.agent, "provider") @then("the agent should have model attribute") def step_agent_has_model(context): """Verify agent has model attribute.""" assert hasattr(context.agent, "model") @then("the agent should have temperature attribute") def step_agent_has_temperature(context): """Verify agent has temperature attribute.""" assert hasattr(context.agent, "temperature") @then("the agent should have llm attribute") def step_agent_has_llm(context): """Verify agent has llm attribute.""" assert hasattr(context.agent, "llm") @then("the agent should have graph attribute") def step_agent_has_graph(context): """Verify agent has graph attribute.""" assert hasattr(context.agent, "graph") @given("I have a state with exactly max attempted fixes") def step_have_state_max_attempted_fixes(context): """Create state with exactly max attempted fixes.""" context.state = { "messages": [], "fix_validated": False, "attempted_fixes": [{"attempt": 1}, {"attempt": 2}, {"attempt": 3}], } @given("I have a state with {count:d} existing messages for auto debug") def step_have_state_n_messages(context, count): """Create state with N existing messages.""" messages = [{"role": "user", "content": f"Message {i}"} for i in range(count)] context.state = { "messages": messages, "error_message": "Test error", "code_context": "test code", "attempted_fixes": [], } @then("the state should have {count:d} messages") def step_state_should_have_n_messages(context, count): """Verify state has N messages.""" messages = context.state.get("messages", []) assert len(messages) == count @given("I have a state with fix_validated as {value}") def step_have_state_fix_validated_value(context, value): """Create state with specific fix_validated value.""" validated = value.lower() == "true" context.state = { "messages": [], "fix_validated": validated, "current_fix": {"description": "Fix", "code": "code"}, "attempted_fixes": [], } @then("the result success should match fix_validated") def step_result_success_matches_validated(context): """Verify result success matches fix_validated.""" result = context.state.get("result", {}) fix_validated = context.state.get("fix_validated", False) assert result.get("success") == fix_validated @given("I have a state with detailed current fix") def step_have_state_detailed_fix(context): """Create state with detailed current fix.""" context.state = { "messages": [], "fix_validated": True, "current_fix": {"description": "Detailed fix", "code": "detailed code"}, "attempted_fixes": [], } @then("the result fix should contain description") def step_result_fix_has_description(context): """Verify result fix has description.""" result = context.state.get("result", {}) fix = result.get("fix", {}) assert "description" in fix @then("the result fix should contain code") def step_result_fix_has_code(context): """Verify result fix has code.""" result = context.state.get("result", {}) fix = result.get("fix", {}) assert "code" in fix @given("I have a state after first fix attempt") def step_have_state_after_first_attempt(context): """Create state after first fix attempt.""" context.state = { "messages": [{"type": "error_analysis"}], "error_message": "Test error", "code_context": "test code", "attempted_fixes": [{"attempt": 1}], "current_fix": {"description": "First fix", "code": "first code"}, } @when("I inspect the workflow graph for auto debug") def step_inspect_workflow_graph(context): """Inspect the workflow graph.""" with patch("langgraph.graph.StateGraph"): context.graph = context.agent._build_graph() @given("I have a state with any current fix") def step_have_state_any_current_fix(context): """Create state with any current fix.""" context.state = { "messages": [], "current_fix": {"description": "Any fix", "code": "any code"}, "attempted_fixes": [], } @then("the fix_validated field should be present") def step_fix_validated_field_present(context): """Verify fix_validated field is present.""" assert "fix_validated" in context.state @given("I have a state with fix_validated {value} and {count:d} attempt") def step_have_state_validated_and_attempt(context, value, count): """Create state with fix_validated value and attempt count.""" validated = value.lower() == "true" context.state = { "messages": [], "fix_validated": validated, "attempted_fixes": [{"attempt": i} for i in range(count)], } @when("I create an AutoDebugAgent with max_fix_attempts of {value:d}") def step_create_agent_with_max_attempts(context, value): """Create agent with specific max_fix_attempts.""" with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent(max_fix_attempts=value) @when("I create an AutoDebugAgent with provider_kwargs:") def step_create_agent_with_provider_kwargs(context): """Create agent with provider_kwargs.""" kwargs = {} for row in context.table: kwarg = row["kwarg"] value = row["value"] # Convert to appropriate type try: kwargs[kwarg] = int(value) except ValueError: try: kwargs[kwarg] = float(value) except ValueError: kwargs[kwarg] = value with patch( "cleveragents.application.agents.base_agent.BaseAgent._create_llm" ) as mock_create_llm: mock_create_llm.return_value = context.mock_llm context.agent = context.AutoDebugAgent(**kwargs) @then("the agent should have provider_kwargs stored") def step_agent_has_provider_kwargs(context): """Verify agent has provider_kwargs stored.""" assert hasattr(context.agent, "provider_kwargs")