"""Step definitions for plan_executor_edge_cases_coverage.feature. Targets uncovered lines and branches in plan_executor.py: - Line 378 / branch 377→378: _try_rollback_to_last_checkpoint with non-empty checkpoints - Lines 410-411: _resolve_sandbox_for_checkpoint → _SandboxRootProxy fallback - Lines 161-163 / branches 160→161, 162→163: _parse_steps non-empty path - Lines 417-419: run_strategize function definition All step names use the 'edge3' prefix to avoid collisions with existing step files. """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch from behave import given, then, when from behave.runner import Context from cleveragents.application.services.plan_executor import ( ExecuteResult, PlanExecutor, ) from cleveragents.domain.models.core.plan import ( PlanPhase, PlanTimestamps, ProcessingState, ) from cleveragents.infrastructure.sandbox.checkpoint import SandboxCheckpoint # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- EDGE3_PLAN_ID = "01KEDGE3PLANID00000000PLN" EDGE3_ROOT_ID = "01KEDGE3ROOTID00000000RTD" EDGE3_SANDBOX_ROOT = "/tmp/edge3-sandbox" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _edge3_make_plan( *, phase: PlanPhase = PlanPhase.EXECUTE, state: ProcessingState = ProcessingState.QUEUED, definition_of_done: str | None = "Implement feature\nWrite tests", decision_root_id: str | None = EDGE3_ROOT_ID, ) -> MagicMock: """Build a mock plan object with sensible defaults.""" plan = MagicMock() plan.phase = phase plan.state = state plan.definition_of_done = definition_of_done plan.decision_root_id = decision_root_id plan.invariants = [] plan.timestamps = PlanTimestamps() plan.changeset_id = None plan.sandbox_refs = [] plan.error_details = None plan.read_only = False return plan def _edge3_make_lifecycle(plan: Any | None = None) -> MagicMock: """Build a mock lifecycle service.""" lcs = MagicMock() if plan is not None: lcs.get_plan.return_value = plan lcs.start_strategize = MagicMock() lcs.complete_strategize = MagicMock() lcs.fail_strategize = MagicMock() lcs.start_execute = MagicMock() lcs.complete_execute = MagicMock() lcs.fail_execute = MagicMock() lcs._commit_plan = MagicMock() return lcs def _edge3_make_checkpoint_mock() -> SandboxCheckpoint: """Create a mock SandboxCheckpoint.""" from datetime import UTC, datetime return SandboxCheckpoint( checkpoint_id="01KEDGE3CPID0000000000CP", sandbox_id="root-EDGE3PLAN", plan_id="EDGE3PLAN", phase="pre_execute", created_at=datetime.now(tz=UTC), metadata={}, snapshot_path="/tmp/edge3-snapshot", ) # --------------------------------------------------------------------------- # _resolve_sandbox_for_checkpoint → _SandboxRootProxy path # --------------------------------------------------------------------------- @given( "an edge3 PlanExecutor with checkpoint manager and sandbox root but no execution context" ) def step_edge3_given_executor_sandbox_root_no_ctx(context: Context) -> None: """Create PlanExecutor with checkpoint_manager and sandbox_root, no exec context.""" context.edge3_checkpoint_mgr = MagicMock() context.edge3_executor = PlanExecutor( lifecycle_service=_edge3_make_lifecycle(), sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=None, checkpoint_manager=context.edge3_checkpoint_mgr, ) @given( "an edge3 PlanExecutor with checkpoint manager and sandbox root and context without sandbox manager" ) def step_edge3_given_executor_ctx_no_sbmgr(context: Context) -> None: """Create PlanExecutor with exec context that has no sandbox_manager attr.""" context.edge3_checkpoint_mgr = MagicMock() # Execution context without sandbox_manager attribute exec_ctx = MagicMock(spec=[]) # empty spec → no attributes context.edge3_executor = PlanExecutor( lifecycle_service=_edge3_make_lifecycle(), sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=exec_ctx, checkpoint_manager=context.edge3_checkpoint_mgr, ) @given( "an edge3 PlanExecutor with checkpoint manager and sandbox root and context with empty sandboxes" ) def step_edge3_given_executor_ctx_empty_sandboxes(context: Context) -> None: """Create PlanExecutor with exec context whose sandbox_manager returns [].""" context.edge3_checkpoint_mgr = MagicMock() exec_ctx = MagicMock() exec_ctx.sandbox_manager = MagicMock() exec_ctx.sandbox_manager.list_sandboxes.return_value = [] context.edge3_executor = PlanExecutor( lifecycle_service=_edge3_make_lifecycle(), sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=exec_ctx, checkpoint_manager=context.edge3_checkpoint_mgr, ) @given( "an edge3 PlanExecutor with checkpoint manager but no sandbox root and no execution context" ) def step_edge3_given_executor_no_sandbox_no_ctx(context: Context) -> None: """Create PlanExecutor with checkpoint_manager but no sandbox_root and no exec context.""" context.edge3_checkpoint_mgr = MagicMock() context.edge3_executor = PlanExecutor( lifecycle_service=_edge3_make_lifecycle(), sandbox_root=None, execution_context=None, checkpoint_manager=context.edge3_checkpoint_mgr, ) @when('I edge3 resolve sandbox for checkpoint with plan id "{plan_id}"') def step_edge3_resolve_sandbox(context: Context, plan_id: str) -> None: """Call _resolve_sandbox_for_checkpoint directly.""" context.edge3_resolved_sandbox = ( context.edge3_executor._resolve_sandbox_for_checkpoint(plan_id) ) @then("the edge3 resolved sandbox should not be None") def step_edge3_resolved_not_none(context: Context) -> None: """Verify resolved sandbox is not None.""" assert context.edge3_resolved_sandbox is not None, ( "Expected a sandbox-like object but got None" ) @then("the edge3 resolved sandbox should have a synthetic sandbox id") def step_edge3_resolved_has_sandbox_id(context: Context) -> None: """Verify the resolved sandbox has a sandbox_id starting with 'root-'.""" sid = context.edge3_resolved_sandbox.sandbox_id assert sid.startswith("root-"), ( f"Expected sandbox_id starting with 'root-', got '{sid}'" ) @then("the edge3 resolved sandbox context should have the sandbox path") def step_edge3_resolved_has_sandbox_path(context: Context) -> None: """Verify the resolved sandbox context has the correct sandbox_path.""" ctx = context.edge3_resolved_sandbox.context assert ctx is not None, "Expected context to be non-None" assert ctx.sandbox_path == EDGE3_SANDBOX_ROOT, ( f"Expected sandbox_path='{EDGE3_SANDBOX_ROOT}', got '{ctx.sandbox_path}'" ) @then("the edge3 resolved sandbox should be None") def step_edge3_resolved_is_none(context: Context) -> None: """Verify resolved sandbox is None.""" assert context.edge3_resolved_sandbox is None, ( f"Expected None but got {context.edge3_resolved_sandbox}" ) # --------------------------------------------------------------------------- # _try_rollback_to_last_checkpoint with non-empty checkpoints # --------------------------------------------------------------------------- @given( "the edge3 checkpoint manager has existing checkpoints that rollback successfully" ) def step_edge3_given_checkpoints_rollback_ok(context: Context) -> None: """Configure checkpoint_manager to return checkpoints and succeed on rollback.""" cp = _edge3_make_checkpoint_mock() context.edge3_checkpoint_mgr.list_checkpoints.return_value = [cp] context.edge3_checkpoint_mgr.rollback_to.return_value = True @given("the edge3 checkpoint manager has checkpoints but rollback raises an exception") def step_edge3_given_checkpoints_rollback_raises(context: Context) -> None: """Configure checkpoint_manager with checkpoints but rollback_to raises.""" cp = _edge3_make_checkpoint_mock() context.edge3_checkpoint_mgr.list_checkpoints.return_value = [cp] context.edge3_checkpoint_mgr.rollback_to.side_effect = RuntimeError( "edge3 rollback boom" ) @given("the edge3 checkpoint manager returns no checkpoints") def step_edge3_given_no_checkpoints(context: Context) -> None: """Configure checkpoint_manager to return empty checkpoints list.""" context.edge3_checkpoint_mgr.list_checkpoints.return_value = [] @when('I edge3 try rollback to last checkpoint for plan "{plan_id}"') def step_edge3_try_rollback(context: Context, plan_id: str) -> None: """Call _try_rollback_to_last_checkpoint directly.""" context.edge3_rollback_result = ( context.edge3_executor._try_rollback_to_last_checkpoint(plan_id) ) @then("the edge3 rollback result should be True") def step_edge3_rollback_true(context: Context) -> None: """Verify rollback returned True.""" assert context.edge3_rollback_result is True, ( f"Expected True, got {context.edge3_rollback_result}" ) @then("the edge3 rollback result should be False") def step_edge3_rollback_false(context: Context) -> None: """Verify rollback returned False.""" assert context.edge3_rollback_result is False, ( f"Expected False, got {context.edge3_rollback_result}" ) # --------------------------------------------------------------------------- # _try_create_checkpoint via _SandboxRootProxy # --------------------------------------------------------------------------- @given("the edge3 checkpoint manager accepts checkpoint creation") def step_edge3_given_checkpoint_create_ok(context: Context) -> None: """Configure checkpoint_manager.create_checkpoint to return a checkpoint.""" cp = _edge3_make_checkpoint_mock() context.edge3_checkpoint_mgr.create_checkpoint.return_value = cp @given("the edge3 checkpoint manager raises on create_checkpoint") def step_edge3_given_checkpoint_create_fails(context: Context) -> None: """Configure checkpoint_manager.create_checkpoint to raise.""" context.edge3_checkpoint_mgr.create_checkpoint.side_effect = RuntimeError( "edge3 create boom" ) @when('I edge3 try create checkpoint for plan "{plan_id}" with phase "{phase}"') def step_edge3_try_create_checkpoint( context: Context, plan_id: str, phase: str ) -> None: """Call _try_create_checkpoint directly.""" context.edge3_checkpoint_result = context.edge3_executor._try_create_checkpoint( plan_id, phase ) @then("the edge3 checkpoint result should not be None") def step_edge3_checkpoint_not_none(context: Context) -> None: """Verify checkpoint creation returned a checkpoint.""" assert context.edge3_checkpoint_result is not None, ( "Expected a SandboxCheckpoint but got None" ) @then("the edge3 checkpoint result should be None") def step_edge3_checkpoint_is_none(context: Context) -> None: """Verify checkpoint creation returned None (non-fatal failure).""" assert context.edge3_checkpoint_result is None, ( f"Expected None but got {context.edge3_checkpoint_result}" ) @then("the edge3 checkpoint manager should have been called with sandbox path metadata") def step_edge3_checkpoint_called_with_path(context: Context) -> None: """Verify create_checkpoint was called with sandbox_path in metadata.""" context.edge3_checkpoint_mgr.create_checkpoint.assert_called_once() call_kwargs = context.edge3_checkpoint_mgr.create_checkpoint.call_args # Keyword arg 'metadata' or positional metadata = call_kwargs.kwargs.get("metadata") or call_kwargs[1].get("metadata", {}) assert "sandbox_path" in metadata, ( f"Expected 'sandbox_path' in metadata, got keys: {list(metadata.keys())}" ) assert metadata["sandbox_path"] == EDGE3_SANDBOX_ROOT, ( f"Expected sandbox_path='{EDGE3_SANDBOX_ROOT}', got '{metadata['sandbox_path']}'" ) # --------------------------------------------------------------------------- # Stub execute with checkpoint rollback on failure # --------------------------------------------------------------------------- @given("an edge3 mock lifecycle service for execute") def step_edge3_given_lifecycle_execute(context: Context) -> None: """Create a mock lifecycle service for execute scenarios.""" context.edge3_lifecycle = _edge3_make_lifecycle() context.edge3_plan_id = EDGE3_PLAN_ID @given("an edge3 plan in Execute-Queued state with decision root") def step_edge3_given_plan_execute_queued(context: Context) -> None: """Set up a plan in Execute-Queued state with decision root.""" plan = _edge3_make_plan( phase=PlanPhase.EXECUTE, state=ProcessingState.QUEUED, definition_of_done="Implement feature\nWrite tests", decision_root_id=EDGE3_ROOT_ID, ) context.edge3_lifecycle.get_plan.return_value = plan context.edge3_mock_plan = plan @given( "an edge3 PlanExecutor with checkpoint manager sandbox root and failing execute actor" ) def step_edge3_given_executor_failing_stub_with_cp(context: Context) -> None: """Create PlanExecutor with checkpoint manager, sandbox root, and a failing execute actor.""" context.edge3_checkpoint_mgr = MagicMock() context.edge3_executor = PlanExecutor( lifecycle_service=context.edge3_lifecycle, sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=None, checkpoint_manager=context.edge3_checkpoint_mgr, ) # Make execute actor always fail context.edge3_executor._execute_actor = MagicMock() context.edge3_executor._execute_actor.execute.side_effect = RuntimeError( "edge3 stub execute boom" ) @when("I edge3 call run execute expecting failure") def step_edge3_run_execute_fail(context: Context) -> None: """Call run_execute expecting an exception.""" context.edge3_raised = None try: context.edge3_executor.run_execute(context.edge3_plan_id) except Exception as exc: context.edge3_raised = exc @then("an edge3 exception should have been raised") def step_edge3_exception_raised(context: Context) -> None: """Verify an exception was raised.""" assert context.edge3_raised is not None, "Expected an exception but none was raised" @then("the edge3 checkpoint manager rollback_to should have been called") def step_edge3_rollback_called(context: Context) -> None: """Verify checkpoint_manager.rollback_to was called.""" context.edge3_checkpoint_mgr.rollback_to.assert_called_once() @then("the edge3 lifecycle should have called fail_execute for edge3") def step_edge3_check_fail_execute(context: Context) -> None: """Verify lifecycle.fail_execute was called.""" context.edge3_lifecycle.fail_execute.assert_called_once() call_args = context.edge3_lifecycle.fail_execute.call_args[0] assert context.edge3_plan_id in call_args, ( f"Expected plan_id in fail_execute args, got {call_args}" ) # --------------------------------------------------------------------------- # Runtime execute with checkpoint rollback on failure # --------------------------------------------------------------------------- @given("an edge3 mock execution context for runtime") def step_edge3_given_exec_ctx_runtime(context: Context) -> None: """Create a mock execution context for runtime mode.""" from cleveragents.application.services.plan_execution_context import ( PlanExecutionContext, ) context.edge3_exec_ctx = PlanExecutionContext(plan_id=EDGE3_PLAN_ID) @given( "an edge3 PlanExecutor with runtime context checkpoint manager sandbox root and failing runtime actor" ) def step_edge3_given_executor_runtime_failing_with_cp(context: Context) -> None: """Create PlanExecutor with runtime context, checkpoint manager, and failing runtime.""" from cleveragents.application.services.plan_execution_context import ( RuntimeExecuteActor, ) from cleveragents.tool.registry import ToolRegistry from cleveragents.tool.runner import ToolRunner context.edge3_checkpoint_mgr = MagicMock() runner = ToolRunner(registry=ToolRegistry()) context.edge3_executor = PlanExecutor( lifecycle_service=context.edge3_lifecycle, tool_runner=runner, sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=context.edge3_exec_ctx, checkpoint_manager=context.edge3_checkpoint_mgr, ) # Patch RuntimeExecuteActor.execute to raise patcher = patch.object( RuntimeExecuteActor, "execute", side_effect=RuntimeError("edge3 runtime execute boom"), ) patcher.start() if not hasattr(context, "_cleanup_handlers"): context._cleanup_handlers = [] context._cleanup_handlers.append(patcher.stop) # --------------------------------------------------------------------------- # _parse_steps non-empty path exercised via run_strategize # --------------------------------------------------------------------------- @given("an edge3 mock lifecycle service for strategize") def step_edge3_given_lifecycle_strategize(context: Context) -> None: """Create a mock lifecycle service for strategize scenarios.""" context.edge3_lifecycle = _edge3_make_lifecycle() context.edge3_plan_id = EDGE3_PLAN_ID @given('an edge3 plan in Strategize phase with multi-line definition "{defn}"') def step_edge3_given_plan_strategize_multiline(context: Context, defn: str) -> None: """Set up a plan in Strategize phase with a multi-line definition.""" raw = defn.replace("\\n", "\n") plan = _edge3_make_plan( phase=PlanPhase.STRATEGIZE, state=ProcessingState.QUEUED, definition_of_done=raw, decision_root_id=None, ) context.edge3_lifecycle.get_plan.return_value = plan context.edge3_mock_plan = plan @given("an edge3 plan in Strategize phase with empty definition") def step_edge3_given_plan_strategize_empty_defn(context: Context) -> None: """Set up a plan in Strategize phase with empty definition_of_done.""" plan = _edge3_make_plan( phase=PlanPhase.STRATEGIZE, state=ProcessingState.QUEUED, definition_of_done="", decision_root_id=None, ) context.edge3_lifecycle.get_plan.return_value = plan context.edge3_mock_plan = plan @given("an edge3 PlanExecutor for strategize without execution context") def step_edge3_given_executor_strategize_no_ctx(context: Context) -> None: """Create a PlanExecutor for strategize without execution context.""" context.edge3_executor = PlanExecutor( lifecycle_service=context.edge3_lifecycle, execution_context=None, ) @when("I edge3 call run strategize successfully") def step_edge3_run_strategize(context: Context) -> None: """Call run_strategize and store the result.""" context.edge3_raised = None try: context.edge3_strat_result = context.edge3_executor.run_strategize( context.edge3_plan_id ) except Exception as exc: context.edge3_raised = exc @then("the edge3 strategize result should have {n:d} decisions") def step_edge3_check_decision_count(context: Context, n: int) -> None: """Verify the strategize result decision count.""" assert context.edge3_raised is None, f"Unexpected error: {context.edge3_raised}" actual = len(context.edge3_strat_result.decisions) assert actual == n, f"Expected {n} decisions, got {actual}" @then("the edge3 lifecycle should have called complete_strategize for edge3") def step_edge3_check_complete_strategize(context: Context) -> None: """Verify complete_strategize was called.""" context.edge3_lifecycle.complete_strategize.assert_called_once_with( context.edge3_plan_id ) # --------------------------------------------------------------------------- # Successful stub execute with checkpoint creation via sandbox root proxy # --------------------------------------------------------------------------- @given( "an edge3 PlanExecutor with checkpoint manager and sandbox root for stub execute" ) def step_edge3_given_executor_cp_sandbox_stub(context: Context) -> None: """Create PlanExecutor with checkpoint manager and sandbox root for stub execute.""" context.edge3_checkpoint_mgr = MagicMock() context.edge3_executor = PlanExecutor( lifecycle_service=context.edge3_lifecycle, sandbox_root=EDGE3_SANDBOX_ROOT, execution_context=None, checkpoint_manager=context.edge3_checkpoint_mgr, ) @when("I edge3 call run execute successfully") def step_edge3_run_execute_success(context: Context) -> None: """Call run_execute expecting success.""" context.edge3_raised = None try: context.edge3_exec_result = context.edge3_executor.run_execute( context.edge3_plan_id ) except Exception as exc: context.edge3_raised = exc @then("the edge3 execute result should be an ExecuteResult") def step_edge3_check_exec_result_type(context: Context) -> None: """Verify the result is an ExecuteResult.""" assert context.edge3_raised is None, f"Unexpected error: {context.edge3_raised}" assert isinstance(context.edge3_exec_result, ExecuteResult), ( f"Expected ExecuteResult, got {type(context.edge3_exec_result).__name__}" ) @then( "the edge3 checkpoint manager create_checkpoint should have been called at least twice" ) def step_edge3_checkpoint_create_called_twice(context: Context) -> None: """Verify create_checkpoint was called at least twice (pre and post execute).""" call_count = context.edge3_checkpoint_mgr.create_checkpoint.call_count assert call_count >= 2, ( f"Expected create_checkpoint called >= 2 times, got {call_count}" )