"""Step definitions for plan_executor_coverage.feature. Comprehensive coverage of plan_executor.py including _parse_steps branches, ExecuteStubActor with tool_runner/sandbox/stream/read_only, PlanExecutor runtime execution path, error recovery retry logic, and result model edge cases. """ 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, ExecuteStubActor, PlanExecutor, StrategizeResult, StrategizeStubActor, StrategyDecision, ) from cleveragents.core.exceptions import PlanError, ValidationError from cleveragents.domain.models.core.plan import ( InvariantSource, PlanInvariant, PlanPhase, PlanTimestamps, ProcessingState, ) # ---------------------------------------------------------------------- # Constants # ---------------------------------------------------------------------- COV2_PLAN_ID = "01KCOV2PLANID000000000PLAN" COV2_ROOT_ID = "01KCOV2ROOTID000000000ROOT" # ---------------------------------------------------------------------- # Helpers # ---------------------------------------------------------------------- def _cov2_make_plan( *, phase: PlanPhase = PlanPhase.STRATEGIZE, state: ProcessingState = ProcessingState.QUEUED, definition_of_done: str | None = "Step one\nStep two", decision_root_id: str | None = COV2_ROOT_ID, invariants: list[PlanInvariant] | None = None, read_only: bool = False, ) -> 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 = invariants or [] plan.timestamps = PlanTimestamps() plan.changeset_id = None plan.sandbox_refs = [] plan.error_details = None plan.read_only = read_only return plan def _cov2_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 _cov2_make_decisions(count: int = 2) -> list[StrategyDecision]: """Build a list of StrategyDecision instances.""" root_id = "01KCOV2DEC000000000000DEC0" decisions: list[StrategyDecision] = [] for i in range(count): decisions.append( StrategyDecision( decision_id=root_id if i == 0 else f"01KCOV2DEC00000000000{i:05d}", step_text=f"Step {i + 1}", sequence=i, parent_id=root_id if i > 0 else None, ) ) return decisions # ---------------------------------------------------------------------- # StrategizeStubActor._parse_steps # ---------------------------------------------------------------------- @given("a fresh StrategizeStubActor for cov2") def step_cov2_given_strategize_actor(context: Context) -> None: """Create a fresh StrategizeStubActor.""" context.cov2_actor = StrategizeStubActor() @when('I cov2 parse steps from "{text}"') def step_cov2_parse_steps(context: Context, text: str) -> None: """Parse steps from the given text.""" raw = text.encode("utf-8").decode("unicode_escape") context.cov2_parsed = StrategizeStubActor._parse_steps(raw) @when("I cov2 parse steps from an empty string") def step_cov2_parse_steps_empty(context: Context) -> None: """Parse steps from an empty string.""" context.cov2_parsed = StrategizeStubActor._parse_steps("") @then('the cov2 parsed steps count should be "{n}"') def step_cov2_check_parsed_count(context: Context, n: str) -> None: """Verify parsed steps list has expected count.""" assert len(context.cov2_parsed) == int(n), ( f"Expected {n} steps, got {len(context.cov2_parsed)}: {context.cov2_parsed}" ) @then('the cov2 parsed step at index "{idx}" should be "{expected}"') def step_cov2_check_parsed_at_index(context: Context, idx: str, expected: str) -> None: """Verify a specific parsed step by index.""" actual = context.cov2_parsed[int(idx)] assert actual == expected, f"Expected '{expected}' at index {idx}, got '{actual}'" @then("the cov2 parsed steps should be the default objective") def step_cov2_check_parsed_default(context: Context) -> None: """Verify parsed steps are the default fallback.""" assert context.cov2_parsed == ["Complete the plan objectives"], ( f"Expected default, got {context.cov2_parsed}" ) # ---------------------------------------------------------------------- # StrategizeStubActor.execute # ---------------------------------------------------------------------- @when('I cov2 execute strategize with plan_id "{pid}" and definition "{defn}"') def step_cov2_exec_strategize(context: Context, pid: str, defn: str) -> None: """Execute strategize with given plan_id and definition.""" raw_defn = defn.replace("\\n", "\n") try: context.cov2_strat_result = context.cov2_actor.execute( plan_id=pid, definition_of_done=raw_defn ) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @when("I cov2 execute strategize with an empty plan_id") def step_cov2_exec_strategize_empty_pid(context: Context) -> None: """Execute strategize with empty plan_id.""" try: context.cov2_actor.execute(plan_id="", definition_of_done="x") context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @when('I cov2 execute strategize with plan_id "{pid}" and no definition') def step_cov2_exec_strategize_no_defn(context: Context, pid: str) -> None: """Execute strategize with None definition_of_done.""" context.cov2_strat_result = context.cov2_actor.execute( plan_id=pid, definition_of_done=None ) context.cov2_raised = None @when( 'I cov2 execute strategize with plan_id "{pid}" and definition "{defn}" without callback' ) def step_cov2_exec_strategize_no_cb(context: Context, pid: str, defn: str) -> None: """Execute strategize without a stream callback.""" raw_defn = defn.replace("\\n", "\n") context.cov2_strat_result = context.cov2_actor.execute( plan_id=pid, definition_of_done=raw_defn, stream_callback=None ) context.cov2_raised = None @when("I cov2 execute strategize with invariants") def step_cov2_exec_strategize_invariants(context: Context) -> None: """Execute strategize with invariant records.""" invariants = [ PlanInvariant(text="Must be safe", source=InvariantSource.PLAN), PlanInvariant(text="Must be fast", source=InvariantSource.PROJECT), ] context.cov2_strat_result = context.cov2_actor.execute( plan_id=COV2_PLAN_ID, definition_of_done="Do it", invariants=invariants ) context.cov2_raised = None @given("a cov2 stream event collector") def step_cov2_given_stream_collector(context: Context) -> None: """Create a stream event collector list.""" context.cov2_stream_events = [] @when("I cov2 execute strategize with stream callback") def step_cov2_exec_strategize_with_cb(context: Context) -> None: """Execute strategize with a stream callback.""" def _cb(event: str, data: dict[str, Any]) -> None: context.cov2_stream_events.append((event, data)) context.cov2_strat_result = context.cov2_actor.execute( plan_id=COV2_PLAN_ID, definition_of_done="A step", stream_callback=_cb, ) context.cov2_raised = None @then('a cov2 ValidationError should be raised with message containing "{text}"') def step_cov2_check_validation_error(context: Context, text: str) -> None: """Verify a ValidationError was raised containing the expected text.""" assert context.cov2_raised is not None, "Expected an exception but none was raised" assert isinstance(context.cov2_raised, ValidationError), ( f"Expected ValidationError, got {type(context.cov2_raised).__name__}" ) assert text in str(context.cov2_raised), ( f"Expected '{text}' in '{context.cov2_raised}'" ) @then('the cov2 strategize result should have "{n}" decisions') def step_cov2_check_decision_count(context: Context, n: str) -> None: """Verify the strategize result decision count.""" assert len(context.cov2_strat_result.decisions) == int(n), ( f"Expected {n} decisions, got {len(context.cov2_strat_result.decisions)}" ) @then('the cov2 first decision text should be "{text}"') def step_cov2_check_first_decision_text(context: Context, text: str) -> None: """Verify the first decision step_text.""" actual = context.cov2_strat_result.decisions[0].step_text assert actual == text, f"Expected '{text}', got '{actual}'" @then("the cov2 root decision should have no parent") def step_cov2_check_root_no_parent(context: Context) -> None: """Verify the root decision has no parent_id.""" root = context.cov2_strat_result.decisions[0] assert root.parent_id is None, f"Expected None parent, got {root.parent_id}" @then("the cov2 non-root decisions should reference root as parent") def step_cov2_check_children_parent(context: Context) -> None: """Verify non-root decisions reference the root decision id.""" root_id = context.cov2_strat_result.decision_root_id for d in context.cov2_strat_result.decisions[1:]: assert d.parent_id == root_id, ( f"Decision {d.decision_id} parent={d.parent_id}, expected {root_id}" ) @then('the cov2 strategize result should have "{n}" invariant records') def step_cov2_check_invariant_count(context: Context, n: str) -> None: """Verify the number of invariant records.""" assert len(context.cov2_strat_result.invariant_records) == int(n), ( f"Expected {n} invariant records, got {len(context.cov2_strat_result.invariant_records)}" ) @then("each cov2 invariant record should have enforced set to true") def step_cov2_check_invariant_enforced(context: Context) -> None: """Verify each invariant record is enforced.""" for rec in context.cov2_strat_result.invariant_records: assert rec["enforced"] is True, f"Expected enforced=True, got {rec}" @then('the cov2 stream events should include "{event_name}"') def step_cov2_check_stream_event(context: Context, event_name: str) -> None: """Verify a specific event was emitted to the stream collector.""" names = [e[0] for e in context.cov2_stream_events] assert event_name in names, f"Expected '{event_name}' in {names}" # ---------------------------------------------------------------------- # ExecuteStubActor # ---------------------------------------------------------------------- @given("a fresh ExecuteStubActor for cov2") def step_cov2_given_execute_actor(context: Context) -> None: """Create a fresh ExecuteStubActor.""" context.cov2_exec_actor = ExecuteStubActor() @when("I cov2 execute execute-stub with empty plan_id") def step_cov2_exec_execute_empty_pid(context: Context) -> None: """Execute the execute stub with an empty plan_id.""" try: context.cov2_exec_actor.execute(plan_id="", decisions=[]) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 mock tool runner that discovers 3 tools") def step_cov2_given_tool_runner(context: Context) -> None: """Create a mock ToolRunner whose discover() returns 3 items.""" runner = MagicMock() runner.discover.return_value = [MagicMock(), MagicMock(), MagicMock()] context.cov2_tool_runner = runner @when("I cov2 execute execute-stub with tool_runner and 2 decisions") def step_cov2_exec_with_tool_runner(context: Context) -> None: """Execute the execute stub with a tool_runner and 2 decisions.""" decisions = _cov2_make_decisions(2) context.cov2_exec_result = context.cov2_exec_actor.execute( plan_id=COV2_PLAN_ID, decisions=decisions, tool_runner=context.cov2_tool_runner, ) context.cov2_raised = None @then('the cov2 execute result tool_calls_count should be "{n}"') def step_cov2_check_tool_calls(context: Context, n: str) -> None: """Verify the tool_calls_count on the execute result.""" assert context.cov2_exec_result.tool_calls_count == int(n), ( f"Expected {n}, got {context.cov2_exec_result.tool_calls_count}" ) @when('I cov2 execute execute-stub with sandbox_root "{path}"') def step_cov2_exec_with_sandbox(context: Context, path: str) -> None: """Execute the execute stub with a sandbox_root.""" decisions = _cov2_make_decisions(1) context.cov2_exec_result = context.cov2_exec_actor.execute( plan_id=COV2_PLAN_ID, decisions=decisions, sandbox_root=path ) context.cov2_raised = None @then('the cov2 execute result sandbox_refs should contain "{path}"') def step_cov2_check_sandbox_refs(context: Context, path: str) -> None: """Verify sandbox_refs contains the expected path.""" assert path in context.cov2_exec_result.sandbox_refs, ( f"Expected '{path}' in {context.cov2_exec_result.sandbox_refs}" ) @when("I cov2 execute execute-stub without sandbox_root") def step_cov2_exec_no_sandbox(context: Context) -> None: """Execute the execute stub without sandbox_root.""" decisions = _cov2_make_decisions(1) context.cov2_exec_result = context.cov2_exec_actor.execute( plan_id=COV2_PLAN_ID, decisions=decisions, sandbox_root=None ) context.cov2_raised = None @then("the cov2 execute result sandbox_refs should be empty") def step_cov2_check_sandbox_empty(context: Context) -> None: """Verify sandbox_refs is empty.""" assert context.cov2_exec_result.sandbox_refs == [], ( f"Expected empty, got {context.cov2_exec_result.sandbox_refs}" ) @when("I cov2 execute execute-stub with 2 decisions and stream callback") def step_cov2_exec_with_stream(context: Context) -> None: """Execute the execute stub with stream callback and 2 decisions.""" decisions = _cov2_make_decisions(2) def _cb(event: str, data: dict[str, Any]) -> None: context.cov2_stream_events.append((event, data)) context.cov2_exec_result = context.cov2_exec_actor.execute( plan_id=COV2_PLAN_ID, decisions=decisions, stream_callback=_cb ) context.cov2_raised = None @when("I cov2 execute execute-stub with read_only true") def step_cov2_exec_read_only(context: Context) -> None: """Execute the execute stub with read_only=True.""" decisions = _cov2_make_decisions(1) context.cov2_exec_result = context.cov2_exec_actor.execute( plan_id=COV2_PLAN_ID, decisions=decisions, read_only=True ) context.cov2_raised = None @then("the cov2 execute result should have a valid changeset_id") def step_cov2_check_exec_changeset_id(context: Context) -> None: """Verify the execute result has a non-empty changeset_id.""" assert context.cov2_exec_result.changeset_id, ( f"Expected non-empty changeset_id, got '{context.cov2_exec_result.changeset_id}'" ) # ---------------------------------------------------------------------- # PlanExecutor.__init__ # ---------------------------------------------------------------------- @when("I cov2 construct PlanExecutor with None lifecycle") def step_cov2_construct_none_lifecycle(context: Context) -> None: """Attempt to construct PlanExecutor with None lifecycle.""" try: PlanExecutor(lifecycle_service=None) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 mock lifecycle service") def step_cov2_given_lifecycle(context: Context) -> None: """Create a mock lifecycle service.""" context.cov2_lifecycle = _cov2_make_lifecycle() context.cov2_plan_id = COV2_PLAN_ID @when("I cov2 construct PlanExecutor with that lifecycle") def step_cov2_construct_with_lifecycle(context: Context) -> None: """Construct PlanExecutor with the mock lifecycle.""" try: context.cov2_executor = PlanExecutor(lifecycle_service=context.cov2_lifecycle) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @then("the cov2 PlanExecutor should be initialised without error") def step_cov2_check_init_ok(context: Context) -> None: """Verify no error on construction.""" assert context.cov2_raised is None, f"Unexpected error: {context.cov2_raised}" assert context.cov2_executor is not None # ---------------------------------------------------------------------- # PlanExecutor properties # ---------------------------------------------------------------------- @given("a cov2 PlanExecutor without execution context") def step_cov2_given_executor_no_ctx(context: Context) -> None: """Create a PlanExecutor without execution context.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None ) @given("a cov2 mock execution context") def step_cov2_given_exec_ctx(context: Context) -> None: """Create a mock PlanExecutionContext.""" from cleveragents.application.services.plan_execution_context import ( PlanExecutionContext, ) context.cov2_exec_ctx = PlanExecutionContext(plan_id=COV2_PLAN_ID) @given("a cov2 PlanExecutor with execution context") def step_cov2_given_executor_with_ctx(context: Context) -> None: """Create a PlanExecutor with execution context.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=context.cov2_exec_ctx, ) @given("a cov2 mock execution context with changeset store") def step_cov2_given_exec_ctx_store(context: Context) -> None: """Create a mock execution context with a changeset store.""" context.cov2_mock_store = MagicMock() context.cov2_exec_ctx = MagicMock() context.cov2_exec_ctx.changeset_store = context.cov2_mock_store context.cov2_exec_ctx.decision_root_id = None @given("a cov2 PlanExecutor with that execution context") def step_cov2_given_executor_with_mock_ctx(context: Context) -> None: """Create a PlanExecutor with the mock execution context.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=context.cov2_exec_ctx, ) @then('the cov2 executor has_runtime should be "{val}"') def step_cov2_check_has_runtime(context: Context, val: str) -> None: """Verify has_runtime property.""" expected = val == "True" assert context.cov2_executor.has_runtime is expected, ( f"Expected has_runtime={expected}, got {context.cov2_executor.has_runtime}" ) @then("the cov2 executor changeset_store should be None") def step_cov2_check_changeset_store_none(context: Context) -> None: """Verify changeset_store is None.""" assert context.cov2_executor.changeset_store is None @then("the cov2 executor changeset_store should be the mock store") def step_cov2_check_changeset_store_mock(context: Context) -> None: """Verify changeset_store is the mock store.""" assert context.cov2_executor.changeset_store is context.cov2_mock_store @then("the cov2 executor execution_context should be None") def step_cov2_check_exec_ctx_none(context: Context) -> None: """Verify execution_context is None.""" assert context.cov2_executor.execution_context is None @then("the cov2 executor execution_context should not be None") def step_cov2_check_exec_ctx_not_none(context: Context) -> None: """Verify execution_context is not None.""" assert context.cov2_executor.execution_context is not None # ---------------------------------------------------------------------- # PlanExecutor.run_strategize - guards and happy path # ---------------------------------------------------------------------- @when("I cov2 call run_strategize with empty plan_id") def step_cov2_run_strategize_empty(context: Context) -> None: """Call run_strategize with empty plan_id.""" try: context.cov2_executor.run_strategize("") context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 plan in Execute phase") def step_cov2_given_plan_execute(context: Context) -> None: """Set up a plan in Execute phase.""" plan = _cov2_make_plan(phase=PlanPhase.EXECUTE) context.cov2_lifecycle.get_plan.return_value = plan context.cov2_mock_plan = plan @given('a cov2 plan in Strategize phase with definition "{defn}"') def step_cov2_given_plan_strategize(context: Context, defn: str) -> None: """Set up a plan in Strategize phase with given definition.""" raw = defn.replace("\\n", "\n") plan = _cov2_make_plan( phase=PlanPhase.STRATEGIZE, definition_of_done=raw, ) context.cov2_lifecycle.get_plan.return_value = plan context.cov2_mock_plan = plan @when("I cov2 call run_strategize") def step_cov2_run_strategize(context: Context) -> None: """Call run_strategize.""" try: context.cov2_strat_run_result = context.cov2_executor.run_strategize( context.cov2_plan_id ) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @then('a cov2 PlanError should be raised containing "{text}"') def step_cov2_check_plan_error(context: Context, text: str) -> None: """Verify a PlanError was raised containing the expected text.""" assert context.cov2_raised is not None, "Expected PlanError but none was raised" assert isinstance(context.cov2_raised, PlanError), ( f"Expected PlanError, got {type(context.cov2_raised).__name__}: {context.cov2_raised}" ) assert text in str(context.cov2_raised), ( f"Expected '{text}' in '{context.cov2_raised}'" ) @then("the cov2 strategize run result should be a StrategizeResult") def step_cov2_check_strat_result_type(context: Context) -> None: """Verify the result is a StrategizeResult.""" assert isinstance(context.cov2_strat_run_result, StrategizeResult), ( f"Expected StrategizeResult, got {type(context.cov2_strat_run_result).__name__}" ) @then("the cov2 lifecycle should have called start_strategize") def step_cov2_check_start_strategize(context: Context) -> None: """Verify start_strategize was called.""" context.cov2_lifecycle.start_strategize.assert_called_once_with( context.cov2_plan_id ) @then("the cov2 lifecycle should have called complete_strategize") def step_cov2_check_complete_strategize(context: Context) -> None: """Verify complete_strategize was called.""" context.cov2_lifecycle.complete_strategize.assert_called_once_with( context.cov2_plan_id ) @then("the cov2 lifecycle should have called _commit_plan") def step_cov2_check_commit_plan(context: Context) -> None: """Verify _commit_plan was called.""" assert context.cov2_lifecycle._commit_plan.called @then("the cov2 execution context decision_root_id should be set") def step_cov2_check_ctx_root_id(context: Context) -> None: """Verify the execution context decision_root_id was set.""" assert context.cov2_raised is None, f"Unexpected error: {context.cov2_raised}" assert context.cov2_exec_ctx.decision_root_id is not None, ( "Expected decision_root_id to be set" ) # ---------------------------------------------------------------------- # PlanExecutor.run_strategize - exception path # ---------------------------------------------------------------------- @given("a cov2 PlanExecutor with failing strategize actor") def step_cov2_given_executor_failing_strat(context: Context) -> None: """Create a PlanExecutor whose strategize actor raises.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None ) context.cov2_executor._strategize_actor = MagicMock() context.cov2_executor._strategize_actor.execute.side_effect = RuntimeError( "cov2 boom in strategize" ) @given("a cov2 mock error recovery service") def step_cov2_given_error_recovery(context: Context) -> None: """Create a mock error recovery service.""" context.cov2_error_recovery = MagicMock() context.cov2_error_recovery.max_retries = 1 context.cov2_error_recovery.record_error = MagicMock() context.cov2_error_recovery.should_retry = MagicMock(return_value=False) @given("a cov2 PlanExecutor with error recovery and failing strategize actor") def step_cov2_given_executor_recovery_failing_strat(context: Context) -> None: """Create a PlanExecutor with error recovery and a failing strategize actor.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None, error_recovery_service=context.cov2_error_recovery, ) context.cov2_executor._strategize_actor = MagicMock() context.cov2_executor._strategize_actor.execute.side_effect = RuntimeError( "cov2 boom in strategize" ) @when("I cov2 call run_strategize expecting exception") def step_cov2_run_strategize_expect_exc(context: Context) -> None: """Call run_strategize expecting an exception.""" try: context.cov2_executor.run_strategize(context.cov2_plan_id) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @then("a cov2 RuntimeError should have been raised") def step_cov2_check_runtime_error(context: Context) -> None: """Verify a RuntimeError was raised.""" assert context.cov2_raised is not None, "Expected an exception" assert isinstance(context.cov2_raised, RuntimeError), ( f"Expected RuntimeError, got {type(context.cov2_raised).__name__}" ) @then("the cov2 lifecycle should have called fail_strategize") def step_cov2_check_fail_strategize(context: Context) -> None: """Verify fail_strategize was called.""" context.cov2_lifecycle.fail_strategize.assert_called_once() args = context.cov2_lifecycle.fail_strategize.call_args[0] assert context.cov2_plan_id in args @then("the cov2 plan error_details should contain exception_type") def step_cov2_check_error_details(context: Context) -> None: """Verify error_details was set with exception_type.""" plan = context.cov2_lifecycle.get_plan.return_value assert plan.error_details is not None if isinstance(plan.error_details, dict): assert "exception_type" in plan.error_details @then("the cov2 error recovery should have recorded a strategize error") def step_cov2_check_recovery_strategize(context: Context) -> None: """Verify error_recovery.record_error was called for strategize.""" context.cov2_error_recovery.record_error.assert_called_once() call_kwargs = context.cov2_error_recovery.record_error.call_args assert call_kwargs[1].get( "phase", call_kwargs[0][1] if len(call_kwargs[0]) > 1 else None ) == "strategize" or "strategize" in str(call_kwargs) # ---------------------------------------------------------------------- # PlanExecutor._guard_execute # ---------------------------------------------------------------------- @when("I cov2 call guard_execute") def step_cov2_call_guard_execute(context: Context) -> None: """Call _guard_execute.""" try: context.cov2_executor._guard_execute(context.cov2_plan_id) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 plan in Execute phase but Processing state") def step_cov2_given_plan_exec_processing(context: Context) -> None: """Set up a plan in Execute phase but Processing state.""" plan = _cov2_make_plan(phase=PlanPhase.EXECUTE, state=ProcessingState.PROCESSING) context.cov2_lifecycle.get_plan.return_value = plan context.cov2_mock_plan = plan @given("a cov2 plan in Execute-Queued state without decision root") def step_cov2_given_plan_exec_no_root(context: Context) -> None: """Set up a plan in Execute-Queued state without decision_root_id.""" plan = _cov2_make_plan( phase=PlanPhase.EXECUTE, state=ProcessingState.QUEUED, decision_root_id=None, ) context.cov2_lifecycle.get_plan.return_value = plan context.cov2_mock_plan = plan # ---------------------------------------------------------------------- # PlanExecutor.run_execute - routing and validation # ---------------------------------------------------------------------- @when("I cov2 call run_execute with empty plan_id") def step_cov2_run_execute_empty(context: Context) -> None: """Call run_execute with empty plan_id.""" try: context.cov2_executor.run_execute("") context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 plan in Execute-Queued state with decision root and definition") def step_cov2_given_plan_exec_full(context: Context) -> None: """Set up a fully valid plan for execute phase.""" plan = _cov2_make_plan( phase=PlanPhase.EXECUTE, state=ProcessingState.QUEUED, definition_of_done="Implement feature\nWrite tests", decision_root_id=COV2_ROOT_ID, ) context.cov2_lifecycle.get_plan.return_value = plan context.cov2_mock_plan = plan @when("I cov2 call run_execute") def step_cov2_run_execute(context: Context) -> None: """Call run_execute.""" try: context.cov2_exec_run_result = context.cov2_executor.run_execute( context.cov2_plan_id ) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @then("the cov2 execute run result should be an ExecuteResult") def step_cov2_check_exec_result_type(context: Context) -> None: """Verify the result is an ExecuteResult.""" assert context.cov2_raised is None, f"Unexpected error: {context.cov2_raised}" assert isinstance(context.cov2_exec_run_result, ExecuteResult), ( f"Expected ExecuteResult, got {type(context.cov2_exec_run_result).__name__}" ) @then("the cov2 lifecycle should have called complete_execute") def step_cov2_check_complete_execute(context: Context) -> None: """Verify complete_execute was called.""" context.cov2_lifecycle.complete_execute.assert_called_once_with( context.cov2_plan_id ) # ---------------------------------------------------------------------- # PlanExecutor._run_execute_with_runtime # ---------------------------------------------------------------------- @given("a cov2 PlanExecutor with execution context and tool runner") def step_cov2_given_executor_runtime(context: Context) -> None: """Create a PlanExecutor with execution context and tool runner.""" from cleveragents.tool.registry import ToolRegistry from cleveragents.tool.runner import ToolRunner runner = ToolRunner(registry=ToolRegistry()) context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=context.cov2_exec_ctx, tool_runner=runner, ) @then("the cov2 execute run result should be a RuntimeExecuteResult") def step_cov2_check_runtime_result_type(context: Context) -> None: """Verify the result is a RuntimeExecuteResult.""" from cleveragents.application.services.plan_execution_context import ( RuntimeExecuteResult, ) assert context.cov2_raised is None, f"Unexpected error: {context.cov2_raised}" assert isinstance(context.cov2_exec_run_result, RuntimeExecuteResult), ( f"Expected RuntimeExecuteResult, got {type(context.cov2_exec_run_result).__name__}" ) @given("a cov2 PlanExecutor with execution context but no tool runner") def step_cov2_given_executor_runtime_no_runner(context: Context) -> None: """Create a PlanExecutor with execution context but no tool_runner.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=context.cov2_exec_ctx, tool_runner=None, ) @when("I cov2 call run_execute expecting exception") def step_cov2_run_execute_expect_exc(context: Context) -> None: """Call run_execute expecting an exception.""" try: context.cov2_exec_run_result = context.cov2_executor.run_execute( context.cov2_plan_id ) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @given("a cov2 PlanExecutor with execution context and tool runner and failing runtime") def step_cov2_given_executor_runtime_failing(context: Context) -> None: """Create a PlanExecutor with runtime that will fail via patched RuntimeExecuteActor.""" from cleveragents.application.services.plan_execution_context import ( RuntimeExecuteActor, ) from cleveragents.tool.registry import ToolRegistry from cleveragents.tool.runner import ToolRunner runner = ToolRunner(registry=ToolRegistry()) context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=context.cov2_exec_ctx, tool_runner=runner, ) # Patch RuntimeExecuteActor.execute so the real _run_execute_with_runtime # try/except catches the error and calls fail_execute. _orig_init = RuntimeExecuteActor.__init__ patcher = patch.object( RuntimeExecuteActor, "execute", side_effect=RuntimeError("cov2 boom in runtime execute"), ) patcher.start() # Register cleanup if not hasattr(context, "_cleanup_handlers"): context._cleanup_handlers = [] context._cleanup_handlers.append(patcher.stop) @then("the cov2 lifecycle should have called fail_execute") def step_cov2_check_fail_execute(context: Context) -> None: """Verify fail_execute was called.""" context.cov2_lifecycle.fail_execute.assert_called() # ---------------------------------------------------------------------- # PlanExecutor._run_execute_with_stub - retry logic # ---------------------------------------------------------------------- @given("a cov2 PlanExecutor with failing execute actor and no recovery") def step_cov2_given_executor_failing_stub_no_recovery(context: Context) -> None: """Create a PlanExecutor with a failing execute actor and no recovery.""" context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None, error_recovery_service=None, ) context.cov2_executor._execute_actor = MagicMock() context.cov2_executor._execute_actor.execute.side_effect = RuntimeError( "cov2 boom in stub execute" ) @given("a cov2 PlanExecutor with execute actor that fails once then succeeds") def step_cov2_given_executor_retry_success(context: Context) -> None: """Create a PlanExecutor with error recovery where first attempt fails, second succeeds.""" error_recovery = MagicMock() error_recovery.max_retries = 2 error_recovery.record_error = MagicMock() error_recovery.should_retry = MagicMock(return_value=True) context.cov2_error_recovery = error_recovery context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None, error_recovery_service=error_recovery, ) # First call raises, second call succeeds real_actor = ExecuteStubActor() call_count = {"n": 0} original_execute = real_actor.execute def _flaky_execute(**kwargs: Any) -> Any: call_count["n"] += 1 if call_count["n"] == 1: raise RuntimeError("cov2 transient error") return original_execute(**kwargs) context.cov2_executor._execute_actor = MagicMock() context.cov2_executor._execute_actor.execute.side_effect = _flaky_execute @then("the cov2 error recovery should have recorded an execute error") def step_cov2_check_recovery_execute(context: Context) -> None: """Verify error_recovery.record_error was called.""" context.cov2_error_recovery.record_error.assert_called() @given("a cov2 PlanExecutor with always-failing execute actor and exhausted retries") def step_cov2_given_executor_exhausted_retries(context: Context) -> None: """Create a PlanExecutor with error recovery that exhausts retries.""" error_recovery = MagicMock() error_recovery.max_retries = 1 # First should_retry True, second False error_recovery.should_retry = MagicMock(side_effect=[True, False]) error_recovery.record_error = MagicMock() context.cov2_error_recovery = error_recovery context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None, error_recovery_service=error_recovery, ) context.cov2_executor._execute_actor = MagicMock() context.cov2_executor._execute_actor.execute.side_effect = RuntimeError( "cov2 persistent failure" ) @given("a cov2 PlanExecutor with failing execute actor and recovery denying retry") def step_cov2_given_executor_no_retry(context: Context) -> None: """Create a PlanExecutor with error recovery that denies retry.""" error_recovery = MagicMock() error_recovery.max_retries = 2 error_recovery.should_retry = MagicMock(return_value=False) error_recovery.record_error = MagicMock() context.cov2_error_recovery = error_recovery context.cov2_executor = PlanExecutor( lifecycle_service=context.cov2_lifecycle, execution_context=None, error_recovery_service=error_recovery, ) context.cov2_executor._execute_actor = MagicMock() context.cov2_executor._execute_actor.execute.side_effect = RuntimeError( "cov2 no retry error" ) # ---------------------------------------------------------------------- # PlanExecutor._build_decisions # ---------------------------------------------------------------------- @when("I cov2 call build_decisions") def step_cov2_call_build_decisions(context: Context) -> None: """Call _build_decisions on the mock plan.""" plan = context.cov2_lifecycle.get_plan(context.cov2_plan_id) context.cov2_built_decisions = context.cov2_executor._build_decisions(plan) @then('the cov2 built decisions should have "{n}" entries') def step_cov2_check_built_count(context: Context, n: str) -> None: """Verify the number of built decisions.""" assert len(context.cov2_built_decisions) == int(n), ( f"Expected {n} decisions, got {len(context.cov2_built_decisions)}" ) @then("the cov2 first built decision should use the plan decision_root_id") def step_cov2_check_first_built(context: Context) -> None: """Verify the first decision uses the plan's decision_root_id.""" first = context.cov2_built_decisions[0] assert first.decision_id == COV2_ROOT_ID, ( f"Expected {COV2_ROOT_ID}, got {first.decision_id}" ) assert first.parent_id is None @then("the cov2 second built decision should have root as parent") def step_cov2_check_second_built(context: Context) -> None: """Verify the second decision has root as parent.""" second = context.cov2_built_decisions[1] assert second.parent_id == COV2_ROOT_ID, ( f"Expected parent {COV2_ROOT_ID}, got {second.parent_id}" ) # ---------------------------------------------------------------------- # Result model edge cases # ---------------------------------------------------------------------- @when("I cov2 create a StrategyDecision with negative sequence") def step_cov2_create_bad_decision(context: Context) -> None: """Try to create a StrategyDecision with negative sequence.""" try: StrategyDecision( decision_id="01KTEST00000000000000TEST0", step_text="Test step", sequence=-1, ) context.cov2_raised = None except Exception as exc: context.cov2_raised = exc @then("a cov2 validation error should be raised for the model") def step_cov2_check_model_validation(context: Context) -> None: """Verify a validation error was raised.""" assert context.cov2_raised is not None, "Expected a validation error" @when("I cov2 create an ExecuteResult with minimal fields") def step_cov2_create_minimal_exec_result(context: Context) -> None: """Create an ExecuteResult with only required fields.""" from cleveragents.tool.builtins.changeset import ChangeSet context.cov2_minimal_result = ExecuteResult( changeset_id="01KTEST00000000000000CSID0", changeset=ChangeSet(plan_id=COV2_PLAN_ID), ) @then('the cov2 ExecuteResult tool_calls_count should default to "{n}"') def step_cov2_check_default_tool_calls(context: Context, n: str) -> None: """Verify tool_calls_count defaults correctly.""" assert context.cov2_minimal_result.tool_calls_count == int(n), ( f"Expected {n}, got {context.cov2_minimal_result.tool_calls_count}" ) @then("the cov2 ExecuteResult sandbox_refs should default to empty list") def step_cov2_check_default_sandbox(context: Context) -> None: """Verify sandbox_refs defaults to empty list.""" assert context.cov2_minimal_result.sandbox_refs == [], ( f"Expected [], got {context.cov2_minimal_result.sandbox_refs}" )