f528d6c3a8
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 26s
CI / security (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 15s
CI / integration_tests (pull_request) Successful in 4m23s
CI / build (pull_request) Successful in 16s
CI / unit_tests (pull_request) Successful in 8m7s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 6m21s
Step A2.beta
440 lines
17 KiB
Python
440 lines
17 KiB
Python
"""Step definitions for Automation Levels BDD tests.
|
|
|
|
Tests the automation level system implemented in Stage A6:
|
|
- AutomationLevel enum (MANUAL, REVIEW_BEFORE_APPLY, FULL_AUTOMATION)
|
|
- PlanLifecycleService automation methods (should_auto_progress, auto_progress,
|
|
pause_plan, resume_plan, set_plan_automation_level)
|
|
- Settings integration (default_automation_level, _resolve_automation_level)
|
|
"""
|
|
|
|
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.core.exceptions import PlanError
|
|
from cleveragents.domain.models.core.plan import AutomationLevel, ProjectLink
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _create_service_with_automation(context: Context, level: str = "manual") -> None:
|
|
"""Create a PlanLifecycleService with a specific default automation level.
|
|
|
|
Note: pydantic-settings ``BaseSettings`` does not accept keyword overrides
|
|
for fields that use ``validation_alias``, so we set the attribute after
|
|
construction.
|
|
"""
|
|
settings = Settings()
|
|
settings.default_automation_level = level
|
|
context.auto_service = PlanLifecycleService(settings=settings)
|
|
context.auto_error = None
|
|
|
|
|
|
def _create_available_action(context: Context, name: str = "local/auto-action") -> str:
|
|
"""Create and make available an action, return its namespaced name."""
|
|
action = context.auto_service.create_action(
|
|
name=name,
|
|
description="Automation test action",
|
|
definition_of_done="Automation test definition",
|
|
strategy_actor="openai/gpt-4",
|
|
execution_actor="openai/gpt-4",
|
|
)
|
|
action_name = str(action.namespaced_name)
|
|
return action_name
|
|
|
|
|
|
def _create_plan_at_phase(
|
|
context: Context,
|
|
automation_level: str,
|
|
phase: str,
|
|
state: str,
|
|
) -> None:
|
|
"""Create a plan and advance it to the requested phase/state.
|
|
|
|
Also sets the automation level on the plan after creation.
|
|
"""
|
|
action_name = _create_available_action(context)
|
|
plan = context.auto_service.use_action(
|
|
action_name=action_name,
|
|
project_links=[ProjectLink(project_name="project-auto")],
|
|
automation_level=AutomationLevel.MANUAL, # Always start manual to control progression
|
|
)
|
|
plan_id = plan.identity.plan_id
|
|
|
|
# Advance through phases as needed
|
|
if phase in ("strategize",):
|
|
# Plan is already in strategize/queued after use_action
|
|
if state == "processing":
|
|
context.auto_service.start_strategize(plan_id)
|
|
elif state == "complete":
|
|
context.auto_service.start_strategize(plan_id)
|
|
# Temporarily ensure manual so complete_strategize doesn't auto-progress
|
|
context.auto_service.set_plan_automation_level(
|
|
plan_id, AutomationLevel.MANUAL
|
|
)
|
|
context.auto_service.complete_strategize(plan_id)
|
|
elif phase in ("execute",):
|
|
context.auto_service.start_strategize(plan_id)
|
|
context.auto_service.set_plan_automation_level(plan_id, AutomationLevel.MANUAL)
|
|
context.auto_service.complete_strategize(plan_id)
|
|
context.auto_service.execute_plan(plan_id)
|
|
if state == "processing":
|
|
context.auto_service.start_execute(plan_id)
|
|
elif state == "complete":
|
|
context.auto_service.start_execute(plan_id)
|
|
context.auto_service.set_plan_automation_level(
|
|
plan_id, AutomationLevel.MANUAL
|
|
)
|
|
context.auto_service.complete_execute(plan_id)
|
|
elif phase in ("apply",):
|
|
context.auto_service.start_strategize(plan_id)
|
|
context.auto_service.set_plan_automation_level(plan_id, AutomationLevel.MANUAL)
|
|
context.auto_service.complete_strategize(plan_id)
|
|
context.auto_service.execute_plan(plan_id)
|
|
context.auto_service.start_execute(plan_id)
|
|
context.auto_service.set_plan_automation_level(plan_id, AutomationLevel.MANUAL)
|
|
context.auto_service.complete_execute(plan_id)
|
|
context.auto_service.apply_plan(plan_id)
|
|
if state == "processing":
|
|
context.auto_service.start_apply(plan_id)
|
|
|
|
# Now set the desired automation level
|
|
resolved_level = AutomationLevel(automation_level)
|
|
context.auto_service.set_plan_automation_level(plan_id, resolved_level)
|
|
|
|
# Re-fetch the plan
|
|
context.auto_plan = context.auto_service.get_plan(plan_id)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("I have a plan lifecycle service with automation level support")
|
|
def step_create_automation_service(context: Context) -> None:
|
|
"""Create a PlanLifecycleService for automation tests."""
|
|
_create_service_with_automation(context, "manual")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Plan creation at specific phase/state with automation level
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given(
|
|
'I have a plan with automation level "{level}" in strategize phase with processing state'
|
|
)
|
|
def step_plan_auto_strategize_processing(context: Context, level: str) -> None:
|
|
"""Create plan at strategize/processing with given automation level."""
|
|
_create_plan_at_phase(context, level, "strategize", "processing")
|
|
|
|
|
|
@given(
|
|
'I have a plan with automation level "{level}" in execute phase with processing state'
|
|
)
|
|
def step_plan_auto_execute_processing(context: Context, level: str) -> None:
|
|
"""Create plan at execute/processing with given automation level."""
|
|
_create_plan_at_phase(context, level, "execute", "processing")
|
|
|
|
|
|
@given(
|
|
'I have a plan with automation level "{level}" in strategize phase with queued state'
|
|
)
|
|
def step_plan_auto_strategize_queued(context: Context, level: str) -> None:
|
|
"""Create plan at strategize/queued with given automation level."""
|
|
_create_plan_at_phase(context, level, "strategize", "queued")
|
|
|
|
|
|
@given(
|
|
'I have a plan with automation level "{level}" in strategize phase with complete state for query'
|
|
)
|
|
def step_plan_auto_strategize_complete_query(context: Context, level: str) -> None:
|
|
"""Create plan at strategize/complete for should_auto_progress query."""
|
|
_create_plan_at_phase(context, level, "strategize", "complete")
|
|
|
|
|
|
@given(
|
|
'I have a plan with automation level "{level}" in execute phase with complete state for query'
|
|
)
|
|
def step_plan_auto_execute_complete_query(context: Context, level: str) -> None:
|
|
"""Create plan at execute/complete for should_auto_progress query."""
|
|
_create_plan_at_phase(context, level, "execute", "complete")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When: complete_strategize / complete_execute on automated plan
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I complete strategize on the automated plan")
|
|
def step_complete_strategize_auto(context: Context) -> None:
|
|
"""Complete strategize and let auto_progress run."""
|
|
context.auto_plan = context.auto_service.complete_strategize(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
|
|
|
|
@when("I complete execute on the automated plan")
|
|
def step_complete_execute_auto(context: Context) -> None:
|
|
"""Complete execute and let auto_progress run."""
|
|
context.auto_plan = context.auto_service.complete_execute(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then: check automated plan phase and state
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the automated plan phase should be "{expected}"')
|
|
def step_check_auto_plan_phase(context: Context, expected: str) -> None:
|
|
"""Check the automated plan's current phase."""
|
|
actual = context.auto_plan.phase.value
|
|
assert actual == expected, (
|
|
f"Expected automated plan phase '{expected}', got '{actual}'"
|
|
)
|
|
|
|
|
|
@then('the automated plan processing state should be "{expected}"')
|
|
def step_check_auto_plan_state(context: Context, expected: str) -> None:
|
|
"""Check the automated plan's current processing state."""
|
|
assert context.auto_plan.state is not None
|
|
actual = context.auto_plan.state.value
|
|
assert actual == expected, (
|
|
f"Expected automated plan processing state '{expected}', got '{actual}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Plan-level override tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('the global automation level is "{level}"')
|
|
def step_set_global_automation_level(context: Context, level: str) -> None:
|
|
"""Set the global automation level via settings."""
|
|
_create_service_with_automation(context, level)
|
|
|
|
|
|
@given('I have a plan created with explicit automation level "{level}"')
|
|
def step_create_plan_explicit_level(context: Context, level: str) -> None:
|
|
"""Create a plan with an explicit automation level."""
|
|
action_name = _create_available_action(context)
|
|
context.auto_plan = context.auto_service.use_action(
|
|
action_name=action_name,
|
|
project_links=[ProjectLink(project_name="project-explicit")],
|
|
automation_level=AutomationLevel(level),
|
|
)
|
|
|
|
|
|
@given("I have an available action for automation tests")
|
|
def step_create_available_action_for_auto(context: Context) -> None:
|
|
"""Create an available action for use in automation tests."""
|
|
context.auto_action_name = _create_available_action(context)
|
|
|
|
|
|
@when('I use the action with automation level "{level}" on project "{project_id}"')
|
|
def step_use_action_with_level(context: Context, level: str, project_id: str) -> None:
|
|
"""Use an action with an explicit automation level."""
|
|
context.auto_plan = context.auto_service.use_action(
|
|
action_name=context.auto_action_name,
|
|
project_links=[ProjectLink(project_name=project_id)],
|
|
automation_level=AutomationLevel(level),
|
|
)
|
|
|
|
|
|
@when('I use the action without explicit automation level on project "{project_id}"')
|
|
def step_use_action_without_level(context: Context, project_id: str) -> None:
|
|
"""Use an action without specifying automation level (uses global default)."""
|
|
context.auto_plan = context.auto_service.use_action(
|
|
action_name=context.auto_action_name,
|
|
project_links=[ProjectLink(project_name=project_id)],
|
|
)
|
|
|
|
|
|
@then('the plan automation level should be "{expected}"')
|
|
def step_check_plan_automation_level(context: Context, expected: str) -> None:
|
|
"""Check the plan's automation level."""
|
|
actual = context.auto_plan.automation_level.value
|
|
assert actual == expected, (
|
|
f"Expected plan automation level '{expected}', got '{actual}'"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Change automation level mid-plan
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I set the plan automation level to "{level}"')
|
|
def step_set_plan_automation_level(context: Context, level: str) -> None:
|
|
"""Set automation level on existing plan."""
|
|
context.auto_plan = context.auto_service.set_plan_automation_level(
|
|
context.auto_plan.identity.plan_id,
|
|
AutomationLevel(level),
|
|
)
|
|
|
|
|
|
@given("I have a terminal plan for automation tests")
|
|
def step_create_terminal_plan_for_auto(context: Context) -> None:
|
|
"""Create a terminal (applied) plan."""
|
|
action_name = _create_available_action(context)
|
|
plan = context.auto_service.use_action(
|
|
action_name=action_name,
|
|
project_links=[ProjectLink(project_name="project-terminal")],
|
|
automation_level=AutomationLevel.MANUAL,
|
|
)
|
|
plan_id = plan.identity.plan_id
|
|
context.auto_service.start_strategize(plan_id)
|
|
context.auto_service.complete_strategize(plan_id)
|
|
context.auto_service.execute_plan(plan_id)
|
|
context.auto_service.start_execute(plan_id)
|
|
context.auto_service.complete_execute(plan_id)
|
|
context.auto_service.apply_plan(plan_id)
|
|
context.auto_service.start_apply(plan_id)
|
|
context.auto_service.complete_apply(plan_id)
|
|
context.auto_plan = context.auto_service.get_plan(plan_id)
|
|
|
|
|
|
@when('I try to set the plan automation level to "{level}"')
|
|
def step_try_set_plan_automation_level(context: Context, level: str) -> None:
|
|
"""Try to set automation level (may fail)."""
|
|
context.auto_error = None
|
|
try:
|
|
context.auto_plan = context.auto_service.set_plan_automation_level(
|
|
context.auto_plan.identity.plan_id,
|
|
AutomationLevel(level),
|
|
)
|
|
except PlanError as e:
|
|
context.auto_error = e
|
|
|
|
|
|
@then("a plan error should be raised for automation")
|
|
def step_check_plan_error_automation(context: Context) -> None:
|
|
"""Check that a plan error was raised."""
|
|
assert context.auto_error is not None, "Expected a PlanError to be raised"
|
|
assert isinstance(context.auto_error, PlanError)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# should_auto_progress query
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("should_auto_progress should return false")
|
|
def step_check_should_not_auto_progress(context: Context) -> None:
|
|
"""Verify should_auto_progress returns False."""
|
|
result = context.auto_service.should_auto_progress(context.auto_plan)
|
|
assert result is False, f"Expected should_auto_progress=False, got {result}"
|
|
|
|
|
|
@then("should_auto_progress should return true")
|
|
def step_check_should_auto_progress(context: Context) -> None:
|
|
"""Verify should_auto_progress returns True."""
|
|
result = context.auto_service.should_auto_progress(context.auto_plan)
|
|
assert result is True, f"Expected should_auto_progress=True, got {result}"
|
|
|
|
|
|
@then("should_auto_progress on terminal plan should return false")
|
|
def step_check_should_not_auto_progress_terminal(context: Context) -> None:
|
|
"""Verify should_auto_progress returns False for terminal plan."""
|
|
result = context.auto_service.should_auto_progress(context.auto_plan)
|
|
assert result is False, (
|
|
f"Expected should_auto_progress=False for terminal plan, got {result}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pause and Resume
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I pause the plan")
|
|
def step_pause_plan(context: Context) -> None:
|
|
"""Pause the plan."""
|
|
context.auto_plan = context.auto_service.pause_plan(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
|
|
|
|
@when("I try to pause the terminal plan")
|
|
def step_try_pause_terminal_plan(context: Context) -> None:
|
|
"""Try to pause a terminal plan (should fail)."""
|
|
context.auto_error = None
|
|
try:
|
|
context.auto_plan = context.auto_service.pause_plan(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
except PlanError as e:
|
|
context.auto_error = e
|
|
|
|
|
|
@given('I have a paused plan that was previously "{level}" in strategize phase')
|
|
def step_create_paused_plan(context: Context, level: str) -> None:
|
|
"""Create a plan, set automation level, then pause it."""
|
|
_create_plan_at_phase(context, level, "strategize", "queued")
|
|
context.auto_service.pause_plan(context.auto_plan.identity.plan_id)
|
|
context.auto_plan = context.auto_service.get_plan(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
|
|
|
|
@when('I resume the plan with level "{level}"')
|
|
def step_resume_plan_with_level(context: Context, level: str) -> None:
|
|
"""Resume the plan with a specific automation level."""
|
|
context.auto_plan = context.auto_service.resume_plan(
|
|
context.auto_plan.identity.plan_id,
|
|
automation_level=AutomationLevel(level),
|
|
)
|
|
|
|
|
|
@when("I resume the plan without explicit level")
|
|
def step_resume_plan_without_level(context: Context) -> None:
|
|
"""Resume the plan without specifying level (defaults to REVIEW_BEFORE_APPLY)."""
|
|
context.auto_plan = context.auto_service.resume_plan(
|
|
context.auto_plan.identity.plan_id,
|
|
)
|
|
|
|
|
|
@when("I try to resume the terminal plan")
|
|
def step_try_resume_terminal_plan(context: Context) -> None:
|
|
"""Try to resume a terminal plan (should fail)."""
|
|
context.auto_error = None
|
|
try:
|
|
context.auto_plan = context.auto_service.resume_plan(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
except PlanError as e:
|
|
context.auto_error = e
|
|
|
|
|
|
@given("I have a paused plan in strategize phase with complete state")
|
|
def step_create_paused_plan_strategize_complete(context: Context) -> None:
|
|
"""Create a plan at strategize/complete and pause it."""
|
|
_create_plan_at_phase(context, "full_automation", "strategize", "complete")
|
|
context.auto_service.pause_plan(context.auto_plan.identity.plan_id)
|
|
context.auto_plan = context.auto_service.get_plan(
|
|
context.auto_plan.identity.plan_id
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Settings resolution
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the resolved default automation level should be "{expected}"')
|
|
def step_check_resolved_default(context: Context, expected: str) -> None:
|
|
"""Check the resolved default automation level from settings."""
|
|
resolved = context.auto_service._resolve_automation_level()
|
|
assert resolved.value == expected, (
|
|
f"Expected resolved level '{expected}', got '{resolved.value}'"
|
|
)
|