forked from HAL9000/cleveragents-core
55aee7cf22
The step_register_skills_table step called add_skill in a loop but only committed once at the end. Because SkillRepository.create() obtains a new session per call and only flushes (never commits), the intermediate sessions could be garbage-collected before the final commit, rolling back their transactions on the shared SQLite :memory: connection. Moving _commit_pending inside the loop ensures each skill is durably committed before the next session is created. ISSUES CLOSED: #418
613 lines
22 KiB
Python
613 lines
22 KiB
Python
"""Step definitions for plan_executor.py coverage tests.
|
|
|
|
Targets uncovered lines: 144, 233-235, 273, 483, 490.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
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,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
PLAN_ID = "01HABCDE12345678901234PLAN"
|
|
|
|
|
|
def _make_mock_plan(
|
|
*,
|
|
phase: PlanPhase = PlanPhase.STRATEGIZE,
|
|
state: ProcessingState = ProcessingState.QUEUED,
|
|
definition_of_done: str | None = "Step one\nStep two",
|
|
decision_root_id: str | None = "01HROOT000000000000000ROOT",
|
|
invariants: list[PlanInvariant] | None = None,
|
|
) -> MagicMock:
|
|
"""Build a mock plan object with sensible defaults."""
|
|
plan = MagicMock()
|
|
plan.phase = phase
|
|
plan.state = state
|
|
plan.processing_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
|
|
return plan
|
|
|
|
|
|
def _make_lifecycle(plan: Any | None = None) -> MagicMock:
|
|
"""Build a mock lifecycle service returning *plan* on get_plan."""
|
|
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
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# StrategizeStubActor._parse_steps (line 144)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a StrategizeStubActor instance for coverage")
|
|
def step_given_strategize_stub(context: Context) -> None:
|
|
context.strategize_actor = StrategizeStubActor()
|
|
|
|
|
|
@when("I call _parse_steps with an empty string")
|
|
def step_parse_steps_empty(context: Context) -> None:
|
|
context.parsed_steps = context.strategize_actor._parse_steps("")
|
|
|
|
|
|
@when("I call _parse_steps with whitespace only")
|
|
def step_parse_steps_whitespace(context: Context) -> None:
|
|
context.parsed_steps = context.strategize_actor._parse_steps(" \n\t ")
|
|
|
|
|
|
@then("the parsed steps should be the default fallback")
|
|
def step_check_default_steps(context: Context) -> None:
|
|
assert context.parsed_steps == ["Complete the plan objectives"], (
|
|
f"Expected default fallback, got {context.parsed_steps}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# StrategizeStubActor.execute happy paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I execute the strategize stub with a valid plan and definition of done")
|
|
def step_exec_strategize_valid(context: Context) -> None:
|
|
context.strategize_result = context.strategize_actor.execute(
|
|
plan_id=PLAN_ID,
|
|
definition_of_done="First step\nSecond step",
|
|
)
|
|
|
|
|
|
@then("the strategize result should contain decisions matching the steps")
|
|
def step_check_strategize_decisions(context: Context) -> None:
|
|
result: StrategizeResult = context.strategize_result
|
|
assert len(result.decisions) == 2
|
|
assert result.decisions[0].step_text == "First step"
|
|
assert result.decisions[1].step_text == "Second step"
|
|
assert result.decision_root_id == result.decisions[0].decision_id
|
|
|
|
|
|
@when("I execute the strategize stub with a None definition of done")
|
|
def step_exec_strategize_none_dod(context: Context) -> None:
|
|
context.strategize_result = context.strategize_actor.execute(
|
|
plan_id=PLAN_ID,
|
|
definition_of_done=None,
|
|
)
|
|
|
|
|
|
@then("the strategize result should contain the default decision")
|
|
def step_check_default_decision(context: Context) -> None:
|
|
result: StrategizeResult = context.strategize_result
|
|
assert len(result.decisions) == 1
|
|
assert result.decisions[0].step_text == "Complete the plan objectives"
|
|
|
|
|
|
@when("I execute the strategize stub with an empty plan_id")
|
|
def step_exec_strategize_empty_plan_id(context: Context) -> None:
|
|
try:
|
|
context.strategize_actor.execute(plan_id="", definition_of_done="x")
|
|
context.raised_exception = None
|
|
except Exception as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then('a plan executor ValidationError should be raised containing "{text}"')
|
|
def step_check_validation_error(context: Context, text: str) -> None:
|
|
assert context.raised_exception is not None, (
|
|
"Expected an exception but none was raised"
|
|
)
|
|
assert isinstance(context.raised_exception, ValidationError), (
|
|
f"Expected ValidationError, got {type(context.raised_exception).__name__}"
|
|
)
|
|
assert text in str(context.raised_exception), (
|
|
f"Expected '{text}' in '{context.raised_exception}'"
|
|
)
|
|
|
|
|
|
@when("I execute the strategize stub with invariants")
|
|
def step_exec_strategize_with_invariants(context: Context) -> None:
|
|
invariants = [
|
|
PlanInvariant(text="Must be safe", source=InvariantSource.PLAN),
|
|
PlanInvariant(text="Must be fast", source=InvariantSource.PROJECT),
|
|
]
|
|
context.strategize_result = context.strategize_actor.execute(
|
|
plan_id=PLAN_ID,
|
|
definition_of_done="Do it",
|
|
invariants=invariants,
|
|
)
|
|
|
|
|
|
@then("the strategize result should contain invariant records")
|
|
def step_check_invariant_records(context: Context) -> None:
|
|
result: StrategizeResult = context.strategize_result
|
|
assert len(result.invariant_records) == 2
|
|
assert result.invariant_records[0]["text"] == "Must be safe"
|
|
assert result.invariant_records[1]["source"] == "project"
|
|
|
|
|
|
@when("I execute the strategize stub with a stream callback")
|
|
def step_exec_strategize_with_callback(context: Context) -> None:
|
|
context.stream_events = []
|
|
|
|
def _cb(event: str, data: dict) -> None:
|
|
context.stream_events.append((event, data))
|
|
|
|
context.strategize_result = context.strategize_actor.execute(
|
|
plan_id=PLAN_ID,
|
|
definition_of_done="A step",
|
|
stream_callback=_cb,
|
|
)
|
|
|
|
|
|
@then("the stream callback should have been called with strategize events")
|
|
def step_check_stream_events(context: Context) -> None:
|
|
event_names = [e[0] for e in context.stream_events]
|
|
assert "strategize_started" in event_names
|
|
assert "strategize_decisions" in event_names
|
|
assert "strategize_complete" in event_names
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ExecuteStubActor
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("an ExecuteStubActor instance for coverage")
|
|
def step_given_execute_stub(context: Context) -> None:
|
|
context.execute_actor = ExecuteStubActor()
|
|
|
|
|
|
@when("I execute the execute stub with decisions")
|
|
def step_exec_execute_stub(context: Context) -> None:
|
|
decisions = [
|
|
StrategyDecision(
|
|
decision_id="01HDEC0000000000000000DEC0",
|
|
step_text="Do the thing",
|
|
sequence=0,
|
|
),
|
|
]
|
|
context.execute_result = context.execute_actor.execute(
|
|
plan_id=PLAN_ID,
|
|
decisions=decisions,
|
|
)
|
|
|
|
|
|
@then("the execute result should contain a changeset id")
|
|
def step_check_execute_changeset(context: Context) -> None:
|
|
result: ExecuteResult = context.execute_result
|
|
assert result.changeset_id is not None
|
|
assert len(result.changeset_id) > 0
|
|
|
|
|
|
@when("I execute the execute stub with an empty plan_id")
|
|
def step_exec_execute_stub_empty_plan_id(context: Context) -> None:
|
|
try:
|
|
context.execute_actor.execute(plan_id="", decisions=[])
|
|
context.raised_exception = None
|
|
except Exception as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor construction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I construct a PlanExecutor with None lifecycle service")
|
|
def step_construct_none_lifecycle(context: Context) -> None:
|
|
try:
|
|
PlanExecutor(lifecycle_service=None)
|
|
context.raised_exception = None
|
|
except Exception as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@given("a fresh mock lifecycle for plan executor coverage")
|
|
def step_given_fresh_mock_lifecycle(context: Context) -> None:
|
|
context.lifecycle = _make_lifecycle()
|
|
|
|
|
|
@when("I construct a PlanExecutor without execution context")
|
|
def step_construct_no_ctx(context: Context) -> None:
|
|
context.executor = PlanExecutor(
|
|
lifecycle_service=context.lifecycle,
|
|
execution_context=None,
|
|
)
|
|
|
|
|
|
@then("plan executor has_runtime should be False")
|
|
def step_check_no_runtime(context: Context) -> None:
|
|
assert context.executor.has_runtime is False
|
|
|
|
|
|
@then("plan executor changeset_store should be None")
|
|
def step_check_no_changeset_store(context: Context) -> None:
|
|
assert context.executor.changeset_store is None
|
|
|
|
|
|
@then("plan executor execution_context should be None")
|
|
def step_check_no_execution_context(context: Context) -> None:
|
|
assert context.executor.execution_context is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor.run_strategize happy path
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with a plan in Strategize phase for executor coverage")
|
|
def step_given_lifecycle_strategize(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.STRATEGIZE,
|
|
state=ProcessingState.QUEUED,
|
|
definition_of_done="Build the widget",
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@given("a PlanExecutor using that lifecycle for coverage")
|
|
def step_given_executor(context: Context) -> None:
|
|
context.executor = PlanExecutor(lifecycle_service=context.lifecycle)
|
|
|
|
|
|
@when("I call run_strategize on the PlanExecutor for coverage")
|
|
def step_run_strategize(context: Context) -> None:
|
|
context.strategize_result = context.executor.run_strategize(context.plan_id)
|
|
|
|
|
|
@then("the strategize result should be returned successfully")
|
|
def step_check_strategize_ok(context: Context) -> None:
|
|
assert isinstance(context.strategize_result, StrategizeResult)
|
|
assert len(context.strategize_result.decisions) > 0
|
|
|
|
|
|
@then("the lifecycle should have called start_strategize for coverage")
|
|
def step_check_start_strategize(context: Context) -> None:
|
|
context.lifecycle.start_strategize.assert_called_once_with(context.plan_id)
|
|
|
|
|
|
@then("the lifecycle should have called complete_strategize for coverage")
|
|
def step_check_complete_strategize(context: Context) -> None:
|
|
context.lifecycle.complete_strategize.assert_called_once_with(context.plan_id)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor.run_strategize exception path (line 273)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with a plan in Strategize phase that will fail during execute")
|
|
def step_given_lifecycle_strategize_fail(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.STRATEGIZE,
|
|
state=ProcessingState.QUEUED,
|
|
definition_of_done="Build the widget",
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@given("a PlanExecutor using that failing lifecycle for coverage")
|
|
def step_given_executor_failing(context: Context) -> None:
|
|
context.executor = PlanExecutor(lifecycle_service=context.lifecycle)
|
|
# Patch the internal strategize actor to raise
|
|
context.executor._strategize_actor = MagicMock()
|
|
context.executor._strategize_actor.execute.side_effect = RuntimeError(
|
|
"Boom in strategize"
|
|
)
|
|
|
|
|
|
@when("I call run_strategize expecting an exception for coverage")
|
|
def step_run_strategize_fail(context: Context) -> None:
|
|
try:
|
|
context.executor.run_strategize(context.plan_id)
|
|
context.raised_exception = None
|
|
except RuntimeError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("the lifecycle should have called fail_strategize for coverage")
|
|
def step_check_fail_strategize(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected an exception"
|
|
context.lifecycle.fail_strategize.assert_called_once()
|
|
call_args = context.lifecycle.fail_strategize.call_args
|
|
assert context.plan_id in call_args[0]
|
|
assert "RuntimeError" in call_args[0][1]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor.run_strategize wrong phase
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with a plan in Execute phase for executor coverage")
|
|
def step_given_lifecycle_execute_phase(context: Context) -> None:
|
|
plan = _make_mock_plan(phase=PlanPhase.EXECUTE)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@when("I call run_strategize expecting a PlanError for coverage")
|
|
def step_run_strategize_wrong_phase(context: Context) -> None:
|
|
try:
|
|
context.executor.run_strategize(context.plan_id)
|
|
context.raised_exception = None
|
|
except PlanError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("a PlanError should be raised about wrong phase for coverage")
|
|
def step_check_wrong_phase_error(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected a PlanError"
|
|
assert isinstance(context.raised_exception, PlanError)
|
|
assert "not in Strategize phase" in str(context.raised_exception)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor._guard_execute: decision_root_id is None (lines 233-235)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with an execute-phase plan missing decision_root_id")
|
|
def step_given_lifecycle_no_root(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.EXECUTE,
|
|
state=ProcessingState.QUEUED,
|
|
decision_root_id=None,
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@when("I call guard_execute for the plan for coverage")
|
|
def step_call_guard_execute(context: Context) -> None:
|
|
try:
|
|
context.executor._guard_execute(context.plan_id)
|
|
context.raised_exception = None
|
|
except PlanError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("a PlanError should be raised about missing decision tree")
|
|
def step_check_no_decision_tree(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected a PlanError"
|
|
assert isinstance(context.raised_exception, PlanError)
|
|
assert "no decision tree" in str(context.raised_exception)
|
|
|
|
|
|
@given("a mock lifecycle with a plan in Strategize phase for guard coverage")
|
|
def step_given_lifecycle_strategize_for_guard(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.STRATEGIZE,
|
|
state=ProcessingState.QUEUED,
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@when("I call guard_execute expecting wrong phase for coverage")
|
|
def step_call_guard_execute_wrong_phase(context: Context) -> None:
|
|
try:
|
|
context.executor._guard_execute(context.plan_id)
|
|
context.raised_exception = None
|
|
except PlanError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("a PlanError should be raised about not in Execute phase")
|
|
def step_check_not_execute_phase(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected a PlanError"
|
|
assert isinstance(context.raised_exception, PlanError)
|
|
assert "not in Execute phase" in str(context.raised_exception)
|
|
|
|
|
|
@given("a mock lifecycle with an execute-phase plan in processing state for coverage")
|
|
def step_given_lifecycle_processing(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.EXECUTE,
|
|
state=ProcessingState.PROCESSING,
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@when("I call guard_execute expecting wrong state for coverage")
|
|
def step_call_guard_execute_wrong_state(context: Context) -> None:
|
|
try:
|
|
context.executor._guard_execute(context.plan_id)
|
|
context.raised_exception = None
|
|
except PlanError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("a PlanError should be raised about not queued")
|
|
def step_check_not_queued(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected a PlanError"
|
|
assert isinstance(context.raised_exception, PlanError)
|
|
assert "not queued" in str(context.raised_exception)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor._run_execute_with_stub happy path
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with a fully configured execute-phase plan for coverage")
|
|
def step_given_lifecycle_full_execute(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.EXECUTE,
|
|
state=ProcessingState.QUEUED,
|
|
definition_of_done="Implement feature\nWrite tests",
|
|
decision_root_id="01HROOT000000000000000ROOT",
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@given("a PlanExecutor using that lifecycle without runtime for coverage")
|
|
def step_given_executor_no_runtime(context: Context) -> None:
|
|
context.executor = PlanExecutor(
|
|
lifecycle_service=context.lifecycle,
|
|
execution_context=None,
|
|
)
|
|
|
|
|
|
@when("I call run_execute on the PlanExecutor for coverage")
|
|
def step_run_execute(context: Context) -> None:
|
|
context.execute_result = context.executor.run_execute(context.plan_id)
|
|
|
|
|
|
@then("the execute result should be returned successfully")
|
|
def step_check_execute_ok(context: Context) -> None:
|
|
assert isinstance(context.execute_result, ExecuteResult)
|
|
assert context.execute_result.changeset_id is not None
|
|
|
|
|
|
@then("the lifecycle should have called start_execute for coverage")
|
|
def step_check_start_execute(context: Context) -> None:
|
|
context.lifecycle.start_execute.assert_called_once_with(context.plan_id)
|
|
|
|
|
|
@then("the lifecycle should have called complete_execute for coverage")
|
|
def step_check_complete_execute(context: Context) -> None:
|
|
context.lifecycle.complete_execute.assert_called_once_with(context.plan_id)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor._run_execute_with_stub exception path (lines 483, 490)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock lifecycle with an execute-phase plan that will fail during stub execute")
|
|
def step_given_lifecycle_stub_fail(context: Context) -> None:
|
|
plan = _make_mock_plan(
|
|
phase=PlanPhase.EXECUTE,
|
|
state=ProcessingState.QUEUED,
|
|
definition_of_done="Something",
|
|
decision_root_id="01HROOT000000000000000ROOT",
|
|
)
|
|
context.lifecycle = _make_lifecycle(plan)
|
|
context.plan_id = PLAN_ID
|
|
|
|
|
|
@given("a PlanExecutor using that failing stub lifecycle for coverage")
|
|
def step_given_executor_failing_stub(context: Context) -> None:
|
|
context.executor = PlanExecutor(
|
|
lifecycle_service=context.lifecycle,
|
|
execution_context=None,
|
|
)
|
|
# Patch the internal execute actor to raise
|
|
context.executor._execute_actor = MagicMock()
|
|
context.executor._execute_actor.execute.side_effect = RuntimeError(
|
|
"Boom in execute"
|
|
)
|
|
|
|
|
|
@when("I call run_execute expecting an exception from stub for coverage")
|
|
def step_run_execute_fail(context: Context) -> None:
|
|
try:
|
|
context.executor.run_execute(context.plan_id)
|
|
context.raised_exception = None
|
|
except RuntimeError as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
@then("the lifecycle should have called fail_execute for coverage")
|
|
def step_check_fail_execute(context: Context) -> None:
|
|
assert context.raised_exception is not None, "Expected an exception"
|
|
context.lifecycle.fail_execute.assert_called_once()
|
|
call_args = context.lifecycle.fail_execute.call_args
|
|
assert context.plan_id in call_args[0]
|
|
assert "RuntimeError" in call_args[0][1]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor.run_execute validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I call run_execute with an empty plan_id for coverage")
|
|
def step_run_execute_empty_plan_id(context: Context) -> None:
|
|
try:
|
|
context.executor.run_execute("")
|
|
context.raised_exception = None
|
|
except Exception as exc:
|
|
context.raised_exception = exc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PlanExecutor._build_decisions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I call build_decisions for the plan for coverage")
|
|
def step_call_build_decisions(context: Context) -> None:
|
|
plan = context.lifecycle.get_plan(context.plan_id)
|
|
context.built_decisions = context.executor._build_decisions(plan)
|
|
|
|
|
|
@then("the decisions should match the plan definition of done steps")
|
|
def step_check_built_decisions(context: Context) -> None:
|
|
decisions = context.built_decisions
|
|
assert len(decisions) == 2
|
|
assert decisions[0].step_text == "Implement feature"
|
|
assert decisions[1].step_text == "Write tests"
|
|
assert decisions[0].parent_id is None
|
|
assert decisions[1].parent_id is not None
|