|
|
|
@@ -0,0 +1,216 @@
|
|
|
|
|
"""Step definitions for TDD Bug #1076 — use_action automation_profile propagation.
|
|
|
|
|
|
|
|
|
|
These steps exercise ``PlanLifecycleService.use_action()`` and verify that
|
|
|
|
|
it resolves the automation profile using the spec's precedence chain
|
|
|
|
|
(plan > action > project > global) and sets the resolved profile as an
|
|
|
|
|
``AutomationProfileRef`` on the created Plan.
|
|
|
|
|
|
|
|
|
|
On ``master`` (before the fix), ``use_action()`` constructs the ``Plan()``
|
|
|
|
|
without passing ``automation_profile`` to the constructor. The Plan's
|
|
|
|
|
``automation_profile`` field is always ``None`` regardless of the Action's
|
|
|
|
|
``automation_profile`` value or any other configuration source.
|
|
|
|
|
|
|
|
|
|
The assertions in these steps will **fail** until the bug is fixed,
|
|
|
|
|
proving the bug exists. The ``@tdd_expected_fail`` tag inverts the
|
|
|
|
|
result so CI passes.
|
|
|
|
|
|
|
|
|
|
Bug #1076 — captures the test for automation_profile propagation from
|
|
|
|
|
the Action to the Plan via use_action(). Uses @tdd_expected_fail until
|
|
|
|
|
the fix in #1076 is merged.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from behave import given, then, when
|
|
|
|
|
from behave.runner import Context
|
|
|
|
|
|
|
|
|
|
from cleveragents.application.services.config_service import ConfigService
|
|
|
|
|
from cleveragents.application.services.plan_lifecycle_service import (
|
|
|
|
|
PlanLifecycleService,
|
|
|
|
|
)
|
|
|
|
|
from cleveragents.config.settings import Settings
|
|
|
|
|
from cleveragents.domain.models.core.plan import (
|
|
|
|
|
AutomationProfileProvenance,
|
|
|
|
|
Plan,
|
|
|
|
|
ProjectLink,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Helpers
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _use_action_on_project(context: Context, project: str) -> None:
|
|
|
|
|
"""Use the current action on *project* and store the resulting plan.
|
|
|
|
|
|
|
|
|
|
Shared by both profiled- and unprofiled-action When steps so that
|
|
|
|
|
the ``use_action`` call-site is defined in exactly one place.
|
|
|
|
|
"""
|
|
|
|
|
links: list[ProjectLink] = [ProjectLink(project_name=project)]
|
|
|
|
|
plan: Plan = context.ap_lifecycle_service.use_action(
|
|
|
|
|
action_name=str(context.ap_action.namespaced_name),
|
|
|
|
|
project_links=links,
|
|
|
|
|
)
|
|
|
|
|
context.ap_plan = plan
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Given steps
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given("a plan lifecycle service for automation profile testing")
|
|
|
|
|
def step_create_lifecycle_service(context: Context) -> None:
|
|
|
|
|
"""Create a PlanLifecycleService instance for automation profile tests."""
|
|
|
|
|
settings: Settings = Settings()
|
|
|
|
|
context.ap_lifecycle_service = PlanLifecycleService(settings=settings)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('an available action "{name}" with automation_profile "{profile}"')
|
|
|
|
|
def step_create_action_with_profile(context: Context, name: str, profile: str) -> None:
|
|
|
|
|
"""Create an available action with a specified automation_profile."""
|
|
|
|
|
context.ap_action = context.ap_lifecycle_service.create_action(
|
|
|
|
|
name=name,
|
|
|
|
|
description=f"Test action {name} with automation profile",
|
|
|
|
|
definition_of_done="Test definition of done",
|
|
|
|
|
strategy_actor="openai/gpt-4",
|
|
|
|
|
execution_actor="openai/gpt-4",
|
|
|
|
|
automation_profile=profile,
|
|
|
|
|
)
|
|
|
|
|
# Guard: verify the Action itself stored the profile (not silently dropped)
|
|
|
|
|
assert context.ap_action.automation_profile == profile, (
|
|
|
|
|
f"Action.automation_profile is {context.ap_action.automation_profile!r}, "
|
|
|
|
|
f"expected {profile!r}. create_action() did not persist the profile "
|
|
|
|
|
f"on the Action model."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('an available action "{name}" without automation_profile')
|
|
|
|
|
def step_create_action_without_profile(context: Context, name: str) -> None:
|
|
|
|
|
"""Create an available action without an automation_profile."""
|
|
|
|
|
context.ap_action = context.ap_lifecycle_service.create_action(
|
|
|
|
|
name=name,
|
|
|
|
|
description=f"Test action {name} without automation profile",
|
|
|
|
|
definition_of_done="Test definition of done",
|
|
|
|
|
strategy_actor="openai/gpt-4",
|
|
|
|
|
execution_actor="openai/gpt-4",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@given('a project-scoped automation_profile "{profile}" for project "{project}"')
|
|
|
|
|
def step_set_project_scoped_profile(
|
|
|
|
|
context: Context, profile: str, project: str
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Configure a project-scoped automation_profile via ConfigService.
|
|
|
|
|
|
|
|
|
|
This sets ``core.automation-profile`` at the project level so that
|
|
|
|
|
the precedence chain (plan > action > **project** > global) has a
|
|
|
|
|
value at level 3. ``use_action()`` should consult this when the
|
|
|
|
|
action itself has no automation_profile.
|
|
|
|
|
"""
|
|
|
|
|
tmpdir: str = tempfile.mkdtemp()
|
|
|
|
|
context.ap_config_tmpdir = tmpdir
|
|
|
|
|
tmp_path: Path = Path(tmpdir)
|
|
|
|
|
config_svc: ConfigService = ConfigService(
|
|
|
|
|
config_dir=tmp_path,
|
|
|
|
|
config_path=tmp_path / "config.toml",
|
|
|
|
|
)
|
|
|
|
|
config_svc.set_project_value(project, "core.automation-profile", profile)
|
|
|
|
|
context.ap_config_service = config_svc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# When steps
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@when('I use the profiled action on project "{project}"')
|
|
|
|
|
def step_use_profiled_action(context: Context, project: str) -> None:
|
|
|
|
|
"""Use the profiled action on a project via use_action()."""
|
|
|
|
|
_use_action_on_project(context, project)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@when('I use the unprofiled action on project "{project}"')
|
|
|
|
|
def step_use_unprofiled_action(context: Context, project: str) -> None:
|
|
|
|
|
"""Use the unprofiled action on a project via use_action()."""
|
|
|
|
|
_use_action_on_project(context, project)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@when('I use the unprofiled action on configured project "{project}"')
|
|
|
|
|
def step_use_unprofiled_action_on_configured_project(
|
|
|
|
|
context: Context, project: str
|
|
|
|
|
) -> None:
|
|
|
|
|
"""Use the unprofiled action on a project that has project-scoped config."""
|
|
|
|
|
_use_action_on_project(context, project)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Then steps
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then("the created plan automation_profile should not be None")
|
|
|
|
|
def step_plan_profile_not_none(context: Context) -> None:
|
|
|
|
|
"""Assert the plan's automation_profile is set.
|
|
|
|
|
|
|
|
|
|
Bug #1076: ``use_action()`` does not pass ``automation_profile`` to
|
|
|
|
|
the ``Plan()`` constructor. The resulting ``plan.automation_profile``
|
|
|
|
|
is always ``None`` regardless of the Action's ``automation_profile``
|
|
|
|
|
value or any other configuration source in the precedence chain
|
|
|
|
|
(plan > action > project > global).
|
|
|
|
|
"""
|
|
|
|
|
plan: Plan = context.ap_plan
|
|
|
|
|
assert plan.automation_profile is not None, (
|
|
|
|
|
"Plan automation_profile is None. "
|
|
|
|
|
"PlanLifecycleService.use_action() does not resolve or "
|
|
|
|
|
"propagate the automation profile from the precedence chain "
|
|
|
|
|
"(plan > action > project > global) to the created Plan "
|
|
|
|
|
"(bug #1076). Expected an AutomationProfileRef, got None."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then('the created plan automation_profile name should be "{expected}"')
|
|
|
|
|
def step_plan_profile_name(context: Context, expected: str) -> None:
|
|
|
|
|
"""Assert the plan's automation_profile has the expected profile name.
|
|
|
|
|
|
|
|
|
|
Bug #1076: Since automation_profile is always None, the profile name
|
|
|
|
|
is never set.
|
|
|
|
|
"""
|
|
|
|
|
plan: Plan = context.ap_plan
|
|
|
|
|
assert plan.automation_profile is not None, (
|
|
|
|
|
"Plan automation_profile is None — cannot verify profile name. "
|
|
|
|
|
"use_action() does not propagate automation_profile (bug #1076)."
|
|
|
|
|
)
|
|
|
|
|
actual: str = plan.automation_profile.profile_name
|
|
|
|
|
assert actual == expected, (
|
|
|
|
|
f"Plan automation_profile.profile_name is '{actual}', "
|
|
|
|
|
f"expected '{expected}'. The precedence resolution in "
|
|
|
|
|
f"use_action() did not resolve the correct profile (bug #1076)."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@then('the created plan automation_profile provenance should be "{expected}"')
|
|
|
|
|
def step_plan_profile_provenance(context: Context, expected: str) -> None:
|
|
|
|
|
"""Assert the plan's automation_profile has the expected provenance.
|
|
|
|
|
|
|
|
|
|
Bug #1076: Since automation_profile is always None, the provenance
|
|
|
|
|
source is never set.
|
|
|
|
|
"""
|
|
|
|
|
plan: Plan = context.ap_plan
|
|
|
|
|
assert plan.automation_profile is not None, (
|
|
|
|
|
"Plan automation_profile is None — cannot verify provenance. "
|
|
|
|
|
"use_action() does not propagate automation_profile (bug #1076)."
|
|
|
|
|
)
|
|
|
|
|
expected_provenance: AutomationProfileProvenance = AutomationProfileProvenance(
|
|
|
|
|
expected
|
|
|
|
|
)
|
|
|
|
|
actual: AutomationProfileProvenance = plan.automation_profile.provenance
|
|
|
|
|
assert actual == expected_provenance, (
|
|
|
|
|
f"Plan automation_profile.provenance is '{actual.value}', "
|
|
|
|
|
f"expected '{expected_provenance.value}'. The precedence "
|
|
|
|
|
f"resolution in use_action() did not record the correct "
|
|
|
|
|
f"provenance source (bug #1076)."
|
|
|
|
|
)
|