Files
placeholder/features/steps/plan_executor_new_coverage_steps.py
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

666 lines
23 KiB
Python

"""Step definitions for plan_executor_new_coverage.feature.
Covers uncovered branches and lines in plan_executor.py:
- PlanExecutor.__init__ with None lifecycle_service
- has_runtime / changeset_store properties
- run_strategize guard branches (wrong phase, empty plan_id, exception path)
- _guard_execute branches (wrong phase, wrong state, None decision_root_id)
- run_execute routing (stub vs runtime)
- StrategizeStubActor / ExecuteStubActor with and without stream_callback
"""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
from behave import given, then, when
from ulid import ULID
from cleveragents.application.services.plan_execution_context import (
PlanExecutionContext,
RuntimeExecuteResult,
)
from cleveragents.application.services.plan_executor import (
ExecuteResult,
ExecuteStubActor,
PlanExecutor,
StrategizeStubActor,
StrategyDecision,
)
from cleveragents.core.exceptions import PlanError, ValidationError
from cleveragents.domain.models.core.plan import PlanPhase, ProcessingState
from cleveragents.tool.registry import ToolRegistry
from cleveragents.tool.runner import ToolRunner
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_plan_mock(
phase: PlanPhase = PlanPhase.STRATEGIZE,
state: ProcessingState = ProcessingState.QUEUED,
decision_root_id: str | None = None,
definition_of_done: str = "Step one\nStep two",
invariants: list | None = None,
) -> MagicMock:
"""Create a mock Plan object with the given attributes."""
plan = MagicMock()
plan.phase = phase
plan.state = state
plan.decision_root_id = decision_root_id
plan.definition_of_done = definition_of_done
plan.invariants = invariants or []
plan.timestamps = MagicMock()
plan.error_details = {}
plan.changeset_id = None
plan.sandbox_refs = []
plan.namespaced_name = "test/plan"
plan.identity = MagicMock()
return plan
def _make_lifecycle(**overrides: Any) -> MagicMock:
"""Create a mock lifecycle service with default methods."""
lc = MagicMock()
lc.start_strategize = MagicMock()
lc.complete_strategize = MagicMock()
lc.fail_strategize = MagicMock()
lc.start_execute = MagicMock()
lc.complete_execute = MagicMock()
lc.fail_execute = MagicMock()
lc._commit_plan = MagicMock()
for key, value in overrides.items():
setattr(lc, key, value)
return lc
def _make_decisions(count: int = 2) -> list[StrategyDecision]:
root_id = str(ULID())
decisions = []
for i in range(count):
decisions.append(
StrategyDecision(
decision_id=root_id if i == 0 else str(ULID()),
step_text=f"Step {i + 1}",
sequence=i,
parent_id=root_id if i > 0 else None,
)
)
return decisions
# ---------------------------------------------------------------------------
# PlanExecutor __init__ validation
# ---------------------------------------------------------------------------
@when("I construct a PlanExecutor with None lifecycle_service")
def step_construct_pe_none_lc(ctx: Any) -> None:
ctx.pe_error = None
try:
PlanExecutor(lifecycle_service=None)
except ValidationError as exc:
ctx.pe_error = exc
@then('a PE ValidationError should be raised containing "{text}"')
def step_assert_pe_validation_error_contains(ctx: Any, text: str) -> None:
assert ctx.pe_error is not None, "Expected a ValidationError but none was raised"
assert isinstance(ctx.pe_error, ValidationError), (
f"Expected ValidationError, got {type(ctx.pe_error).__name__}"
)
assert text in str(ctx.pe_error), (
f"Expected '{text}' in error message, got: {ctx.pe_error}"
)
@given("a mock plan lifecycle service for plan executor")
def step_given_mock_lifecycle(ctx: Any) -> None:
ctx.lifecycle = _make_lifecycle()
ctx.plan_id = str(ULID())
@when("I construct a PlanExecutor with the mock lifecycle service")
def step_construct_pe_with_lc(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.executor = PlanExecutor(lifecycle_service=ctx.lifecycle)
except Exception as exc:
ctx.pe_error = exc
@then("the PlanExecutor should be created without error")
def step_assert_no_error(ctx: Any) -> None:
assert ctx.pe_error is None, f"Unexpected error: {ctx.pe_error}"
assert ctx.executor is not None
# ---------------------------------------------------------------------------
# has_runtime property
# ---------------------------------------------------------------------------
@given("a mock execution context")
def step_given_mock_exec_ctx(ctx: Any) -> None:
ctx.exec_ctx = PlanExecutionContext(plan_id=ctx.plan_id)
@when(
"I construct a PlanExecutor with the mock lifecycle service and execution context"
)
def step_construct_pe_with_ctx(ctx: Any) -> None:
ctx.executor = PlanExecutor(
lifecycle_service=ctx.lifecycle,
execution_context=ctx.exec_ctx,
)
@then("the executor has_runtime property should be True")
def step_assert_has_runtime_true(ctx: Any) -> None:
assert ctx.executor.has_runtime is True
@then("the executor has_runtime property should be False")
def step_assert_has_runtime_false(ctx: Any) -> None:
assert ctx.executor.has_runtime is False
# ---------------------------------------------------------------------------
# changeset_store property
# ---------------------------------------------------------------------------
@given("a mock execution context with a changeset store")
def step_given_exec_ctx_with_store(ctx: Any) -> None:
ctx.mock_store = MagicMock()
ctx.exec_ctx = MagicMock()
ctx.exec_ctx.changeset_store = ctx.mock_store
ctx.exec_ctx.decision_root_id = None
@then("the executor changeset_store should be the mock store")
def step_assert_changeset_store_is_mock(ctx: Any) -> None:
assert ctx.executor.changeset_store is ctx.mock_store
@then("the executor changeset_store should be None")
def step_assert_changeset_store_none(ctx: Any) -> None:
assert ctx.executor.changeset_store is None
# ---------------------------------------------------------------------------
# run_strategize guard: wrong phase
# ---------------------------------------------------------------------------
@given("a PlanExecutor with the mock lifecycle service")
def step_given_pe_with_lc(ctx: Any) -> None:
ctx.executor = PlanExecutor(lifecycle_service=ctx.lifecycle)
@given("the lifecycle returns a plan in EXECUTE phase")
def step_lifecycle_returns_execute_phase(ctx: Any) -> None:
plan = _make_plan_mock(phase=PlanPhase.EXECUTE)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
@when("I attempt to run strategize on the executor")
def step_attempt_run_strategize(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.executor.run_strategize(ctx.plan_id)
except (PlanError, ValidationError) as exc:
ctx.pe_error = exc
@then('a PE PlanError should be raised containing "{text}"')
def step_assert_pe_plan_error_contains(ctx: Any, text: str) -> None:
assert ctx.pe_error is not None, "Expected a PlanError but none was raised"
assert isinstance(ctx.pe_error, PlanError), (
f"Expected PlanError, got {type(ctx.pe_error).__name__}"
)
assert text in str(ctx.pe_error), (
f"Expected '{text}' in error message, got: {ctx.pe_error}"
)
# ---------------------------------------------------------------------------
# run_strategize guard: empty plan_id
# ---------------------------------------------------------------------------
@when("I attempt to run strategize with an empty plan_id")
def step_attempt_strategize_empty_plan_id(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.executor.run_strategize("")
except (ValidationError, PlanError) as exc:
ctx.pe_error = exc
# ---------------------------------------------------------------------------
# run_strategize exception handling
# ---------------------------------------------------------------------------
@given("the lifecycle returns a plan in STRATEGIZE phase")
def step_lifecycle_returns_strategize_phase(ctx: Any) -> None:
plan = _make_plan_mock(
phase=PlanPhase.STRATEGIZE,
definition_of_done="Step one\nStep two",
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
ctx.mock_plan = plan
@given("the lifecycle start_strategize will raise a RuntimeError")
def step_lifecycle_start_strategize_raises(ctx: Any) -> None:
"""Make the strategize actor itself raise inside the try block.
start_strategize is called *before* the try, so we need to make
the actor.execute raise instead to hit the except branch (line 314).
"""
ctx._patch_strategize_actor_to_fail = True
@when("I attempt to run strategize and expect an exception")
def step_attempt_strategize_expect_exception(ctx: Any) -> None:
# If the actor should fail, patch it before running
if getattr(ctx, "_patch_strategize_actor_to_fail", False):
ctx.executor._strategize_actor.execute = MagicMock(
side_effect=RuntimeError("simulated actor failure")
)
if getattr(ctx, "_patch_actor_to_fail", False):
ctx.executor._strategize_actor.execute = MagicMock(
side_effect=RuntimeError("simulated actor failure")
)
ctx.pe_error = None
ctx.raised_exception = None
try:
ctx.executor.run_strategize(ctx.plan_id)
except Exception as exc:
ctx.pe_error = exc
ctx.raised_exception = exc
@then("fail_strategize should have been called with the plan_id")
def step_assert_fail_strategize_called(ctx: Any) -> None:
ctx.lifecycle.fail_strategize.assert_called()
call_args = ctx.lifecycle.fail_strategize.call_args
assert ctx.plan_id == call_args[0][0], (
f"Expected plan_id={ctx.plan_id}, got {call_args[0][0]}"
)
@then("the raised exception should be a RuntimeError")
def step_assert_raised_runtime_error(ctx: Any) -> None:
assert ctx.raised_exception is not None, "No exception was raised"
assert isinstance(ctx.raised_exception, RuntimeError), (
f"Expected RuntimeError, got {type(ctx.raised_exception).__name__}"
)
# ---------------------------------------------------------------------------
# run_strategize exception during actor execute (except branch line 314)
# ---------------------------------------------------------------------------
@given(
"the lifecycle returns a plan in STRATEGIZE phase that fails during actor execute"
)
def step_lifecycle_strategize_fails_in_actor(ctx: Any) -> None:
"""Set up lifecycle so start_strategize succeeds but actor.execute raises."""
plan = _make_plan_mock(
phase=PlanPhase.STRATEGIZE,
definition_of_done="Step one\nStep two",
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
ctx.lifecycle.start_strategize = MagicMock() # succeeds
ctx.mock_plan = plan
ctx._patch_actor_to_fail = True
@then("the plan error_details should contain the exception_type")
def step_assert_error_details_exception_type(ctx: Any) -> None:
plan = ctx.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
# ---------------------------------------------------------------------------
# _guard_execute: wrong phase
# ---------------------------------------------------------------------------
@given("the lifecycle returns a plan in STRATEGIZE phase for execute guard")
def step_lifecycle_returns_strategize_for_execute(ctx: Any) -> None:
plan = _make_plan_mock(phase=PlanPhase.STRATEGIZE)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
@when("I attempt to run execute on the executor")
def step_attempt_run_execute(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.executor.run_execute(ctx.plan_id)
except (PlanError, ValidationError) as exc:
ctx.pe_error = exc
# ---------------------------------------------------------------------------
# _guard_execute: wrong state
# ---------------------------------------------------------------------------
@given("the lifecycle returns a plan in EXECUTE phase with PROCESSING state")
def step_lifecycle_returns_execute_processing(ctx: Any) -> None:
plan = _make_plan_mock(
phase=PlanPhase.EXECUTE,
state=ProcessingState.PROCESSING,
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
# ---------------------------------------------------------------------------
# _guard_execute: None decision_root_id
# ---------------------------------------------------------------------------
@given(
"the lifecycle returns a plan in EXECUTE phase with QUEUED state but no decision root"
)
def step_lifecycle_returns_execute_queued_no_root(ctx: Any) -> None:
plan = _make_plan_mock(
phase=PlanPhase.EXECUTE,
state=ProcessingState.QUEUED,
decision_root_id=None,
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
# ---------------------------------------------------------------------------
# run_execute routing to stub
# ---------------------------------------------------------------------------
@given("a PlanExecutor with the mock lifecycle service and no execution context")
def step_given_pe_no_ctx(ctx: Any) -> None:
ctx.executor = PlanExecutor(lifecycle_service=ctx.lifecycle)
@given("the lifecycle returns a fully valid plan for stub execution")
def step_lifecycle_returns_valid_plan_for_stub(ctx: Any) -> None:
root_id = str(ULID())
plan = _make_plan_mock(
phase=PlanPhase.EXECUTE,
state=ProcessingState.QUEUED,
decision_root_id=root_id,
definition_of_done="Do thing one\nDo thing two",
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
ctx.mock_plan = plan
@when("I run execute on the executor")
def step_run_execute(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.result = ctx.executor.run_execute(ctx.plan_id)
except Exception as exc:
ctx.pe_error = exc
@then("the plan executor result should be an ExecuteResult")
def step_assert_execute_result_type(ctx: Any) -> None:
assert ctx.pe_error is None, f"Unexpected error: {ctx.pe_error}"
assert isinstance(ctx.result, ExecuteResult), (
f"Expected ExecuteResult, got {type(ctx.result).__name__}"
)
@then("complete_execute should have been called")
def step_assert_complete_execute_called(ctx: Any) -> None:
ctx.lifecycle.complete_execute.assert_called_once()
# ---------------------------------------------------------------------------
# run_execute routing to runtime
# ---------------------------------------------------------------------------
@given(
"a PlanExecutor with the mock lifecycle service and execution context and tool runner"
)
def step_given_pe_with_ctx_and_runner(ctx: Any) -> None:
ctx.tool_runner = ToolRunner(registry=ToolRegistry())
ctx.executor = PlanExecutor(
lifecycle_service=ctx.lifecycle,
tool_runner=ctx.tool_runner,
execution_context=ctx.exec_ctx,
)
@given("the lifecycle returns a fully valid plan for runtime execution")
def step_lifecycle_returns_valid_plan_for_runtime(ctx: Any) -> None:
root_id = str(ULID())
plan = _make_plan_mock(
phase=PlanPhase.EXECUTE,
state=ProcessingState.QUEUED,
decision_root_id=root_id,
definition_of_done="Do thing one\nDo thing two",
)
ctx.lifecycle.get_plan = MagicMock(return_value=plan)
ctx.mock_plan = plan
@then("the plan executor result should be a RuntimeExecuteResult")
def step_assert_runtime_execute_result_type(ctx: Any) -> None:
assert ctx.pe_error is None, f"Unexpected error: {ctx.pe_error}"
assert isinstance(ctx.result, RuntimeExecuteResult), (
f"Expected RuntimeExecuteResult, got {type(ctx.result).__name__}"
)
# ---------------------------------------------------------------------------
# StrategizeStubActor with stream_callback
# ---------------------------------------------------------------------------
@given("a stream event collector")
def step_given_stream_collector(ctx: Any) -> None:
ctx.stream_events = []
ctx.stream_callback = lambda event_type, data: ctx.stream_events.append(
(event_type, data)
)
@when("I execute the StrategizeStubActor with a plan_id and stream callback")
def step_exec_strategize_actor_with_cb(ctx: Any) -> None:
actor = StrategizeStubActor()
plan_id = str(ULID())
ctx.actor_plan_id = plan_id
ctx.strategize_result = actor.execute(
plan_id=plan_id,
definition_of_done="Step A\nStep B",
stream_callback=ctx.stream_callback,
)
@then('the collector should contain a "{event_type}" event')
def step_assert_collector_contains_event(ctx: Any, event_type: str) -> None:
event_types = [e[0] for e in ctx.stream_events]
assert event_type in event_types, (
f"Expected '{event_type}' in events, got: {event_types}"
)
@when("I execute the StrategizeStubActor with a plan_id and no callback")
def step_exec_strategize_actor_no_cb(ctx: Any) -> None:
actor = StrategizeStubActor()
plan_id = str(ULID())
ctx.strategize_result = actor.execute(
plan_id=plan_id,
definition_of_done="Step A\nStep B",
stream_callback=None,
)
@then("the strategize result should have a valid decision_root_id")
def step_assert_strategize_result_root_id(ctx: Any) -> None:
assert ctx.strategize_result.decision_root_id
assert len(ctx.strategize_result.decision_root_id) == 26
@then("the strategize result should have at least one decision")
def step_assert_strategize_result_has_decisions(ctx: Any) -> None:
assert len(ctx.strategize_result.decisions) >= 1
# ---------------------------------------------------------------------------
# ExecuteStubActor with stream_callback
# ---------------------------------------------------------------------------
@given("a list of two strategy decisions")
def step_given_two_decisions(ctx: Any) -> None:
ctx.decisions = _make_decisions(2)
@when("I execute the ExecuteStubActor with the decisions and stream callback")
def step_exec_execute_actor_with_cb(ctx: Any) -> None:
actor = ExecuteStubActor()
plan_id = str(ULID())
ctx.actor_plan_id = plan_id
ctx.execute_result = actor.execute(
plan_id=plan_id,
decisions=ctx.decisions,
stream_callback=ctx.stream_callback,
)
@then('the collector should contain an "{event_type}" event')
def step_assert_collector_contains_event_an(ctx: Any, event_type: str) -> None:
event_types = [e[0] for e in ctx.stream_events]
assert event_type in event_types, (
f"Expected '{event_type}' in events, got: {event_types}"
)
@when("I execute the ExecuteStubActor with the decisions and no callback")
def step_exec_execute_actor_no_cb(ctx: Any) -> None:
actor = ExecuteStubActor()
plan_id = str(ULID())
ctx.execute_result = actor.execute(
plan_id=plan_id,
decisions=ctx.decisions,
stream_callback=None,
)
@then("the execute result should have a valid changeset_id")
def step_assert_execute_result_changeset_id(ctx: Any) -> None:
assert ctx.execute_result.changeset_id
assert len(ctx.execute_result.changeset_id) == 26
@then("the execute result tool_calls_count should be zero")
def step_assert_execute_result_tool_calls_zero(ctx: Any) -> None:
assert ctx.execute_result.tool_calls_count == 0
# ---------------------------------------------------------------------------
# run_strategize happy path with stream_callback
# ---------------------------------------------------------------------------
@when("I run strategize with the stream callback")
def step_run_strategize_with_callback(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.strategize_result = ctx.executor.run_strategize(
ctx.plan_id,
stream_callback=ctx.stream_callback,
)
except Exception as exc:
ctx.pe_error = exc
@then("the strategize result should have decisions")
def step_assert_result_has_decisions(ctx: Any) -> None:
assert ctx.pe_error is None, f"Unexpected error: {ctx.pe_error}"
assert len(ctx.strategize_result.decisions) >= 1
@then("complete_strategize should have been called")
def step_assert_complete_strategize_called(ctx: Any) -> None:
ctx.lifecycle.complete_strategize.assert_called_once()
# ---------------------------------------------------------------------------
# PlanExecutor with execution context (for run_strategize + context scenario)
# ---------------------------------------------------------------------------
@given("a PlanExecutor with the mock lifecycle and execution context")
def step_given_pe_with_lc_and_ctx(ctx: Any) -> None:
ctx.executor = PlanExecutor(
lifecycle_service=ctx.lifecycle,
execution_context=ctx.exec_ctx,
)
# ---------------------------------------------------------------------------
# run_strategize sets execution_context.decision_root_id
# ---------------------------------------------------------------------------
@when("I run strategize successfully")
def step_run_strategize_successfully(ctx: Any) -> None:
ctx.pe_error = None
try:
ctx.strategize_result = ctx.executor.run_strategize(ctx.plan_id)
except Exception as exc:
ctx.pe_error = exc
@then("the execution context decision_root_id should be set")
def step_assert_exec_ctx_decision_root_id_set(ctx: Any) -> None:
assert ctx.pe_error is None, f"Unexpected error: {ctx.pe_error}"
assert ctx.exec_ctx.decision_root_id is not None
assert ctx.exec_ctx.decision_root_id == ctx.strategize_result.decision_root_id
# ---------------------------------------------------------------------------
# run_execute stub exception path
# ---------------------------------------------------------------------------
@given("the stub execute actor is patched to raise an error")
def step_patch_stub_execute_to_fail(ctx: Any) -> None:
ctx.executor._execute_actor.execute = MagicMock(
side_effect=RuntimeError("simulated stub execution failure")
)
@when("I attempt to run execute and expect an exception")
def step_attempt_run_execute_expect_exception(ctx: Any) -> None:
ctx.pe_error = None
ctx.raised_exception = None
try:
ctx.result = ctx.executor.run_execute(ctx.plan_id)
except Exception as exc:
ctx.pe_error = exc
ctx.raised_exception = exc
@then("fail_execute should have been called with the plan_id")
def step_assert_fail_execute_called(ctx: Any) -> None:
ctx.lifecycle.fail_execute.assert_called()
call_args = ctx.lifecycle.fail_execute.call_args
assert ctx.plan_id == call_args[0][0], (
f"Expected plan_id={ctx.plan_id}, got {call_args[0][0]}"
)