|
|
|
@@ -0,0 +1,174 @@
|
|
|
|
|
"""Step definitions for plan_lifecycle_bug9061.feature.
|
|
|
|
|
|
|
|
|
|
Verifies that PlanLifecycleService.start_strategize() records a
|
|
|
|
|
prompt_definition decision as the root (not strategy_choice).
|
|
|
|
|
|
|
|
|
|
Bug: #9061 - Plan lifecycle records strategy_choice as root decision
|
|
|
|
|
instead of prompt_definition during Strategize phase.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
|
|
|
|
from behave import given, then, when
|
|
|
|
|
from behave.runner import Context
|
|
|
|
|
|
|
|
|
|
from cleveragents.application.services.plan_lifecycle_service import (
|
|
|
|
|
PlanLifecycleService,
|
|
|
|
|
)
|
|
|
|
|
from cleveragents.config.settings import Settings
|
|
|
|
|
from cleveragents.domain.models.core.plan import (
|
|
|
|
|
ProjectLink,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Background
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given("I have a fresh plan lifecycle service for bug 9061")
|
|
|
|
|
def step_create_fresh_service_bug9061(context: Context) -> None:
|
|
|
|
|
"""Create a clean PlanLifecycleService for bug 9061 tests."""
|
|
|
|
|
Settings._instance = None
|
|
|
|
|
settings = Settings()
|
|
|
|
|
context.service = PlanLifecycleService(settings=settings)
|
|
|
|
|
context.error = None
|
|
|
|
|
context.captured_calls = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _create_action_bug9061(
|
|
|
|
|
context: Context, name: str, description: str | None = None
|
|
|
|
|
) -> object:
|
|
|
|
|
"""Create a basic action with sensible defaults."""
|
|
|
|
|
return context.service.create_action(
|
|
|
|
|
name=name,
|
|
|
|
|
description=description or f"Action {name}",
|
|
|
|
|
definition_of_done="Tests pass",
|
|
|
|
|
strategy_actor="openai/gpt-4",
|
|
|
|
|
execution_actor="openai/gpt-4",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given("a plan lifecycle service with a capturing decision service for bug 9061")
|
|
|
|
|
def step_create_service_with_capturing_ds(context: Context) -> None:
|
|
|
|
|
"""Create a PlanLifecycleService with a DecisionService that captures calls."""
|
|
|
|
|
Settings._instance = None
|
|
|
|
|
settings = Settings()
|
|
|
|
|
mock_ds = MagicMock()
|
|
|
|
|
context.captured_calls = []
|
|
|
|
|
|
|
|
|
|
def _capture_record_decision(**kwargs: object) -> MagicMock:
|
|
|
|
|
context.captured_calls.append(dict(kwargs))
|
|
|
|
|
result = MagicMock()
|
|
|
|
|
result.decision_id = "01ABCDEFGHIJKLMNOPQRSTUVWX"
|
|
|
|
|
result.decision_type = kwargs.get("decision_type")
|
|
|
|
|
result.parent_decision_id = kwargs.get("parent_decision_id")
|
|
|
|
|
result.question = kwargs.get("question")
|
|
|
|
|
result.chosen_option = kwargs.get("chosen_option")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
mock_ds.record_decision.side_effect = _capture_record_decision
|
|
|
|
|
context.service = PlanLifecycleService(
|
|
|
|
|
settings=settings,
|
|
|
|
|
decision_service=mock_ds,
|
|
|
|
|
)
|
|
|
|
|
context.mock_decision_service = mock_ds
|
|
|
|
|
context.error = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('an action "{name}" exists for bug 9061')
|
|
|
|
|
def step_create_named_action_bug9061(context: Context, name: str) -> None:
|
|
|
|
|
"""Create an action with the given name."""
|
|
|
|
|
context.action = _create_action_bug9061(context, name)
|
|
|
|
|
context.plan_description = context.action.description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('an action "{name}" exists for bug 9061 with description "{description}"')
|
|
|
|
|
def step_create_named_action_with_desc_bug9061(
|
|
|
|
|
context: Context, name: str, description: str
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Create an action with the given name and description."""
|
|
|
|
|
context.action = _create_action_bug9061(context, name, description=description)
|
|
|
|
|
context.plan_description = description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('a plan created from "{action_name}" for bug 9061')
|
|
|
|
|
def step_create_plan_from_action_bug9061(context: Context, action_name: str) -> None:
|
|
|
|
|
"""Create a plan from the named action."""
|
|
|
|
|
context.plan = context.service.use_action(
|
|
|
|
|
action_name=action_name,
|
|
|
|
|
project_links=[ProjectLink(project_name="proj-bug9061")],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@when("I start strategize for bug 9061")
|
|
|
|
|
def step_start_strategize_bug9061(context: Context) -> None:
|
|
|
|
|
"""Start strategize phase."""
|
|
|
|
|
context.error = None
|
|
|
|
|
try:
|
|
|
|
|
pid = context.plan.identity.plan_id
|
|
|
|
|
context.plan = context.service.start_strategize(pid)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
context.error = e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then('the bug9061 recorded decision type should be "{expected_type}"')
|
|
|
|
|
def step_verify_recorded_decision_type(context: Context, expected_type: str) -> None:
|
|
|
|
|
"""Verify the first recorded decision has the expected type."""
|
|
|
|
|
assert context.error is None, f"Unexpected error: {context.error}"
|
|
|
|
|
assert context.captured_calls, "No decision was recorded"
|
|
|
|
|
first_call = context.captured_calls[0]
|
|
|
|
|
actual_type = str(first_call.get("decision_type", ""))
|
|
|
|
|
assert actual_type == expected_type, (
|
|
|
|
|
f"Expected decision_type={expected_type!r}, got {actual_type!r}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then("the bug9061 recorded decision should have no parent (root)")
|
|
|
|
|
def step_verify_recorded_decision_no_parent(context: Context) -> None:
|
|
|
|
|
"""Verify the first recorded decision has no parent (is root)."""
|
|
|
|
|
assert context.captured_calls, "No decision was recorded"
|
|
|
|
|
first_call = context.captured_calls[0]
|
|
|
|
|
parent_id = first_call.get("parent_decision_id")
|
|
|
|
|
assert parent_id is None, (
|
|
|
|
|
f"Expected parent_decision_id=None (root), got {parent_id!r}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then('the bug9061 recorded decision question should be "{expected_question}"')
|
|
|
|
|
def step_verify_recorded_decision_question(
|
|
|
|
|
context: Context, expected_question: str
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Verify the first recorded decision has the expected question."""
|
|
|
|
|
assert context.captured_calls, "No decision was recorded"
|
|
|
|
|
first_call = context.captured_calls[0]
|
|
|
|
|
actual_question = first_call.get("question", "")
|
|
|
|
|
assert actual_question == expected_question, (
|
|
|
|
|
f"Expected question={expected_question!r}, got {actual_question!r}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then("the bug9061 recorded decision chosen_option should equal the plan description")
|
|
|
|
|
def step_verify_chosen_option_equals_description(
|
|
|
|
|
context: Context,
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Verify the first recorded decision chosen_option equals the plan description."""
|
|
|
|
|
assert context.captured_calls, "No decision was recorded"
|
|
|
|
|
first_call = context.captured_calls[0]
|
|
|
|
|
actual_chosen = first_call.get("chosen_option", "")
|
|
|
|
|
expected = context.plan.description
|
|
|
|
|
assert actual_chosen == expected, (
|
|
|
|
|
f"Expected chosen_option={expected!r}, got {actual_chosen!r}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then('the bug9061 recorded decision chosen_option should be "{expected_option}"')
|
|
|
|
|
def step_verify_recorded_decision_chosen_option(
|
|
|
|
|
context: Context, expected_option: str
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Verify the first recorded decision chosen_option matches expected value."""
|
|
|
|
|
assert context.captured_calls, "No decision was recorded"
|
|
|
|
|
first_call = context.captured_calls[0]
|
|
|
|
|
actual_chosen = first_call.get("chosen_option", "")
|
|
|
|
|
assert actual_chosen == expected_option, (
|
|
|
|
|
f"Expected chosen_option={expected_option!r}, got {actual_chosen!r}"
|
|
|
|
|
)
|