"""Step definitions for Plan Lifecycle Service tests.""" from behave import given, then, when from behave.runner import Context from cleveragents.application.services.plan_lifecycle_service import ( ActionNotAvailableError, InvalidPhaseTransitionError, PlanLifecycleService, PlanNotReadyError, ) from cleveragents.config.settings import Settings from cleveragents.core.exceptions import ( BusinessRuleViolation, NotFoundError, PlanError, ValidationError, ) from cleveragents.domain.models.core.action import ( ActionArgument, ArgumentRequirement, ArgumentType, ) from cleveragents.domain.models.core.plan import ( ActionState, NamespacedName, Plan, PlanIdentity, PlanPhase, ) @given("I have a plan lifecycle service") def step_create_lifecycle_service(context: Context) -> None: """Create a plan lifecycle service instance.""" settings = Settings() context.lifecycle_service = PlanLifecycleService(settings=settings) context.error = None # Action Creation Steps @when('I create an action with name "{name}" and definition "{definition}"') def step_create_action_simple(context: Context, name: str, definition: str) -> None: """Create an action with simple parameters.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done=definition, strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) @when('I create an action with name "{name}" definition "{definition}" and arguments') def step_create_action_with_arguments( context: Context, name: str, definition: str ) -> None: """Create an action with arguments from table.""" arguments = [] for row in context.table: arg = ActionArgument( name=row["name"], arg_type=ArgumentType(row["type"]), requirement=ArgumentRequirement(row["requirement"]), description=row["description"], ) arguments.append(arg) context.action = context.lifecycle_service.create_action( name=name, definition_of_done=definition, strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", arguments=arguments, ) @when('I try to create an action with invalid name "{name}"') def step_try_create_action_invalid_name(context: Context, name: str) -> None: """Try to create an action with an invalid name.""" context.error = None context.action = None try: context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) except ValidationError as e: context.error = e @then("the action should be created successfully") def step_check_action_created(context: Context) -> None: """Verify action was created.""" assert context.action is not None assert context.action.action_id is not None @then('the lifecycle action state should be "{expected}"') def step_check_action_state_lifecycle(context: Context, expected: str) -> None: """Check action state.""" actual = context.action.state.value assert actual == expected, f"Expected state '{expected}', got '{actual}'" @then('the lifecycle action namespace should be "{expected}"') def step_check_action_namespace_lifecycle(context: Context, expected: str) -> None: """Check action namespace.""" actual = context.action.namespaced_name.namespace assert actual == expected, f"Expected namespace '{expected}', got '{actual}'" @then("the action should have {count:d} argument") def step_check_action_argument_count(context: Context, count: int) -> None: """Check action argument count.""" actual = len(context.action.arguments) assert actual == count, f"Expected {count} arguments, got {actual}" # Action State Management Steps @given('I have a draft action "{name}"') def step_create_draft_action(context: Context, name: str) -> None: """Create a draft action.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) @given('I have an available action "{name}"') def step_create_available_action(context: Context, name: str) -> None: """Create an available action.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) @given('I have an archived action "{name}"') def step_create_archived_action(context: Context, name: str) -> None: """Create an archived action.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) context.lifecycle_service.archive_action(context.action.action_id) @given('I have an available non-reusable action "{name}"') def step_create_non_reusable_action(context: Context, name: str) -> None: """Create a non-reusable available action.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", reusable=False, ) context.lifecycle_service.make_action_available(context.action.action_id) @given('I have an available action with required argument "{arg_string}"') def step_create_action_with_required_arg(context: Context, arg_string: str) -> None: """Create an action with a required argument.""" arg = ActionArgument.parse(arg_string) context.action = context.lifecycle_service.create_action( name="local/test-action", definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", arguments=[arg], ) context.lifecycle_service.make_action_available(context.action.action_id) @when("I make the action available") def step_make_action_available(context: Context) -> None: """Make action available.""" context.action = context.lifecycle_service.make_action_available( context.action.action_id ) @when("I try to make the action available") def step_try_make_action_available(context: Context) -> None: """Try to make action available (may fail).""" context.error = None try: context.action = context.lifecycle_service.make_action_available( context.action.action_id ) except BusinessRuleViolation as e: context.error = e @when("I archive the action") def step_archive_action(context: Context) -> None: """Archive action.""" context.action = context.lifecycle_service.archive_action(context.action.action_id) # Action Retrieval Steps @given('I have created an action "{name}"') def step_create_named_action(context: Context, name: str) -> None: """Create an action and store its ID.""" context.action = context.lifecycle_service.create_action( name=name, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.action_id = context.action.action_id @given('I have created actions "{name1}" and "{name2}"') def step_create_two_actions(context: Context, name1: str, name2: str) -> None: """Create two actions.""" context.lifecycle_service.create_action( name=name1, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.create_action( name=name2, definition_of_done="Test definition", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) @when("I get the action by ID") def step_get_action_by_id(context: Context) -> None: """Get action by ID.""" context.retrieved_action = context.lifecycle_service.get_action(context.action_id) @when('I get the action by name "{name}"') def step_get_action_by_name(context: Context, name: str) -> None: """Get action by name.""" context.retrieved_action = context.lifecycle_service.get_action_by_name(name) @when('I try to get the action by missing ID "{action_id}"') def step_try_get_action_missing_id(context: Context, action_id: str) -> None: """Try to get action by a missing ID.""" context.error = None context.retrieved_action = None try: context.retrieved_action = context.lifecycle_service.get_action(action_id) except NotFoundError as e: context.error = e @when('I try to get the action by missing name "{name}"') def step_try_get_action_missing_name(context: Context, name: str) -> None: """Try to get action by a missing name.""" context.error = None context.retrieved_action = None try: context.retrieved_action = context.lifecycle_service.get_action_by_name(name) except NotFoundError as e: context.error = e @then("the action should be returned") def step_check_action_returned(context: Context) -> None: """Verify action was returned.""" assert context.retrieved_action is not None assert context.retrieved_action.action_id == context.action_id @when("I list all actions") def step_list_all_actions(context: Context) -> None: """List all actions.""" context.action_list = context.lifecycle_service.list_actions() @when('I list actions in namespace "{namespace}"') def step_list_actions_in_namespace(context: Context, namespace: str) -> None: """List actions filtered by namespace.""" context.action_list = context.lifecycle_service.list_actions(namespace=namespace) @when('I list actions in state "{state}"') def step_list_actions_in_state(context: Context, state: str) -> None: """List actions filtered by state.""" context.action_list = context.lifecycle_service.list_actions( state=ActionState(state) ) @then("I should see {count:d} actions") def step_check_action_list_count(context: Context, count: int) -> None: """Check action list count.""" actual = len(context.action_list) assert actual == count, f"Expected {count} actions, got {actual}" @then("I should see {count:d} action") def step_check_action_list_count_singular(context: Context, count: int) -> None: """Check action list count (singular form).""" actual = len(context.action_list) assert actual == count, f"Expected {count} action(s), got {actual}" # Use Action Steps @when('I use the action on project "{project_id}"') def step_use_action(context: Context, project_id: str) -> None: """Use action to create a plan.""" context.plan = context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=[project_id], ) @when("I try to use the action without providing arguments") def step_try_use_action_without_args(context: Context) -> None: """Try to use action without arguments.""" context.error = None try: context.plan = context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=["project-123"], ) except ValidationError as e: context.error = e @when('I try to use the action on project "{project_id}"') def step_try_use_action(context: Context, project_id: str) -> None: """Try to use action (may fail).""" context.error = None try: context.plan = context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=[project_id], ) except (ActionNotAvailableError, ValidationError) as e: context.error = e @then("a plan should be created") def step_check_plan_created(context: Context) -> None: """Verify plan was created.""" assert context.plan is not None assert context.plan.identity.plan_id is not None @then('the lifecycle plan phase should be "{expected}"') def step_check_plan_phase_lifecycle(context: Context, expected: str) -> None: """Check plan phase.""" actual = context.plan.phase.value assert actual == expected, f"Expected phase '{expected}', got '{actual}'" @then('the lifecycle plan processing state should be "{expected}"') def step_check_plan_processing_state(context: Context, expected: str) -> None: """Check plan processing state.""" assert context.plan.processing_state is not None actual = context.plan.processing_state.value assert actual == expected, f"Expected processing state '{expected}', got '{actual}'" @then('the plan should reference project "{project_id}"') def step_check_plan_project(context: Context, project_id: str) -> None: """Check plan references project.""" assert project_id in context.plan.project_ids @then('a validation error should be raised with message "{expected_msg}"') def step_check_validation_error_message(context: Context, expected_msg: str) -> None: """Check validation error message.""" assert context.error is not None assert expected_msg in str(context.error) @then("a lifecycle validation error should be raised") def step_check_lifecycle_validation_error(context: Context) -> None: """Check lifecycle validation error.""" assert context.error is not None assert isinstance(context.error, ValidationError) @then("an action not available error should be raised") def step_check_action_not_available_error(context: Context) -> None: """Check action not available error.""" assert context.error is not None assert isinstance(context.error, ActionNotAvailableError) @then("a business rule violation should be raised") def step_check_business_rule_violation(context: Context) -> None: """Check business rule violation error.""" assert context.error is not None assert isinstance(context.error, BusinessRuleViolation) @then("a not found error should be raised") def step_check_not_found_error(context: Context) -> None: """Check not found error.""" assert context.error is not None assert isinstance(context.error, NotFoundError) @when('I try to get the plan by missing ID "{plan_id}"') def step_try_get_plan_missing_id(context: Context, plan_id: str) -> None: """Try to get a plan by a missing ID.""" context.error = None context.plan = None try: context.plan = context.lifecycle_service.get_plan(plan_id) except NotFoundError as e: context.error = e # Plan State Setup Steps @given("I have a plan in strategize phase with queued state") def step_create_plan_strategize_queued(context: Context) -> None: """Create a plan in strategize phase with queued state.""" # Create an available action first context.action = context.lifecycle_service.create_action( name="local/test-action", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) context.plan = context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=["project-123"], ) @given("I have a plan in strategize phase with processing state") def step_create_plan_strategize_processing(context: Context) -> None: """Create a plan in strategize phase with processing state.""" step_create_plan_strategize_queued(context) context.plan = context.lifecycle_service.start_strategize( context.plan.identity.plan_id ) @given("I have a plan in strategize phase with complete state") def step_create_plan_strategize_complete(context: Context) -> None: """Create a plan in strategize phase with complete state.""" step_create_plan_strategize_processing(context) context.plan = context.lifecycle_service.complete_strategize( context.plan.identity.plan_id ) @given("I have a plan in execute phase with queued state") def step_create_plan_execute_queued(context: Context) -> None: """Create a plan in execute phase with queued state.""" step_create_plan_strategize_complete(context) context.plan = context.lifecycle_service.execute_plan(context.plan.identity.plan_id) @given("I have a plan in execute phase with processing state") def step_create_plan_execute_processing(context: Context) -> None: """Create a plan in execute phase with processing state.""" step_create_plan_execute_queued(context) context.plan = context.lifecycle_service.start_execute( context.plan.identity.plan_id ) @given("I have a plan in execute phase with complete state") def step_create_plan_execute_complete(context: Context) -> None: """Create a plan in execute phase with complete state.""" step_create_plan_execute_processing(context) context.plan = context.lifecycle_service.complete_execute( context.plan.identity.plan_id ) @given("I have a plan in apply phase with queued state") def step_create_plan_apply_queued(context: Context) -> None: """Create a plan in apply phase with queued state.""" step_create_plan_execute_complete(context) context.plan = context.lifecycle_service.apply_plan(context.plan.identity.plan_id) @given("I have a plan in apply phase with processing state") def step_create_plan_apply_processing(context: Context) -> None: """Create a plan in apply phase with processing state.""" step_create_plan_execute_complete(context) context.plan = context.lifecycle_service.apply_plan(context.plan.identity.plan_id) context.plan = context.lifecycle_service.start_apply(context.plan.identity.plan_id) @given("I have a plan in applied phase") def step_create_plan_applied(context: Context) -> None: """Create a plan in applied (terminal) phase.""" step_create_plan_apply_processing(context) context.plan = context.lifecycle_service.complete_apply( context.plan.identity.plan_id ) @given("I have a plan in action phase") def step_create_plan_action_phase(context: Context) -> None: """Create a plan directly in action phase (for testing).""" # Create an action but don't use it - just store the plan in action phase manually context.action = context.lifecycle_service.create_action( name="local/test-action", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) # Create a plan directly in ACTION phase plan_id = context.lifecycle_service._generate_ulid() context.plan = Plan( identity=PlanIdentity(plan_id=plan_id), namespaced_name=NamespacedName(namespace="local", name="test-plan"), description="Test plan", phase=PlanPhase.ACTION, action_state=ActionState.DRAFT, ) context.lifecycle_service._plans[plan_id] = context.plan # Phase Transition Steps @when("I start strategize") def step_start_strategize(context: Context) -> None: """Start strategize processing.""" context.plan = context.lifecycle_service.start_strategize( context.plan.identity.plan_id ) @when("I try to start strategize") def step_try_start_strategize(context: Context) -> None: """Try to start strategize processing (may fail).""" context.error = None try: context.plan = context.lifecycle_service.start_strategize( context.plan.identity.plan_id ) except (PlanError, PlanNotReadyError) as e: context.error = e @when("I complete strategize") def step_complete_strategize(context: Context) -> None: """Complete strategize.""" context.plan = context.lifecycle_service.complete_strategize( context.plan.identity.plan_id ) @when("I try to complete strategize") def step_try_complete_strategize(context: Context) -> None: """Try to complete strategize (may fail).""" context.error = None try: context.plan = context.lifecycle_service.complete_strategize( context.plan.identity.plan_id ) except PlanError as e: context.error = e @when('strategize fails with error "{error_msg}"') def step_fail_strategize(context: Context, error_msg: str) -> None: """Fail strategize.""" context.plan = context.lifecycle_service.fail_strategize( context.plan.identity.plan_id, error_msg ) @when("I execute the plan") def step_execute_plan(context: Context) -> None: """Execute plan (transition from Strategize to Execute).""" context.plan = context.lifecycle_service.execute_plan(context.plan.identity.plan_id) @when("I try to execute the plan") def step_try_execute_plan(context: Context) -> None: """Try to execute plan (may fail).""" context.error = None try: context.plan = context.lifecycle_service.execute_plan( context.plan.identity.plan_id ) except (InvalidPhaseTransitionError, PlanNotReadyError) as e: context.error = e @when("I try to start execute") def step_try_start_execute(context: Context) -> None: """Try to start execute processing (may fail).""" context.error = None try: context.plan = context.lifecycle_service.start_execute( context.plan.identity.plan_id ) except (PlanError, PlanNotReadyError) as e: context.error = e @when("I start execute") def step_start_execute(context: Context) -> None: """Start execute processing.""" context.plan = context.lifecycle_service.start_execute( context.plan.identity.plan_id ) @when("I complete execute") def step_complete_execute(context: Context) -> None: """Complete execute.""" context.plan = context.lifecycle_service.complete_execute( context.plan.identity.plan_id ) @when("I try to complete execute") def step_try_complete_execute(context: Context) -> None: """Try to complete execute (may fail).""" context.error = None try: context.plan = context.lifecycle_service.complete_execute( context.plan.identity.plan_id ) except PlanError as e: context.error = e @when('execute fails with error "{error_msg}"') def step_fail_execute(context: Context, error_msg: str) -> None: """Fail execute.""" context.plan = context.lifecycle_service.fail_execute( context.plan.identity.plan_id, error_msg ) @when("I lifecycle apply the plan") def step_apply_plan(context: Context) -> None: """Apply plan (transition from Execute to Apply).""" context.plan = context.lifecycle_service.apply_plan(context.plan.identity.plan_id) @when("I try to lifecycle apply the plan") def step_try_apply_plan(context: Context) -> None: """Try to apply plan (may fail).""" context.error = None try: context.plan = context.lifecycle_service.apply_plan( context.plan.identity.plan_id ) except (InvalidPhaseTransitionError, PlanNotReadyError) as e: context.error = e @when("I try to start apply") def step_try_start_apply(context: Context) -> None: """Try to start apply processing (may fail).""" context.error = None try: context.plan = context.lifecycle_service.start_apply( context.plan.identity.plan_id ) except (PlanError, PlanNotReadyError) as e: context.error = e @when("I complete apply") def step_complete_apply(context: Context) -> None: """Complete apply.""" context.plan = context.lifecycle_service.complete_apply( context.plan.identity.plan_id ) @when("I try to complete apply") def step_try_complete_apply(context: Context) -> None: """Try to complete apply (may fail).""" context.error = None try: context.plan = context.lifecycle_service.complete_apply( context.plan.identity.plan_id ) except PlanError as e: context.error = e @when('apply fails with error "{error_msg}"') def step_fail_apply(context: Context, error_msg: str) -> None: """Fail apply.""" context.plan = context.lifecycle_service.fail_apply( context.plan.identity.plan_id, error_msg ) # Timestamp Checks @then("the strategize started timestamp should be set") def step_check_strategize_started(context: Context) -> None: """Check strategize started timestamp.""" assert context.plan.timestamps.strategize_started_at is not None @then("the strategize completed timestamp should be set") def step_check_strategize_completed(context: Context) -> None: """Check strategize completed timestamp.""" assert context.plan.timestamps.strategize_completed_at is not None @then("the execute started timestamp should be set") def step_check_execute_started(context: Context) -> None: """Check execute started timestamp.""" assert context.plan.timestamps.execute_started_at is not None @then("the execute completed timestamp should be set") def step_check_execute_completed(context: Context) -> None: """Check execute completed timestamp.""" assert context.plan.timestamps.execute_completed_at is not None @then("the applied timestamp should be set") def step_check_applied_timestamp(context: Context) -> None: """Check applied timestamp.""" assert context.plan.timestamps.applied_at is not None # Note: "the plan should be terminal" step is defined in plan_model_steps.py @then('the lifecycle plan error message should be "{expected}"') def step_check_plan_error_message(context: Context, expected: str) -> None: """Check plan error message.""" assert context.plan.error_message == expected @when( 'I create an invalid phase transition error from "{from_phase}" to "{to_phase}" with message "{message}"' ) def step_create_invalid_transition_error( context: Context, from_phase: str, to_phase: str, message: str ) -> None: """Create an invalid phase transition error with a custom message.""" context.error = InvalidPhaseTransitionError( PlanPhase(from_phase), PlanPhase(to_phase), message ) # Error Checks @then("a plan not ready error should be raised") def step_check_plan_not_ready_error(context: Context) -> None: """Check plan not ready error.""" assert context.error is not None assert isinstance(context.error, PlanNotReadyError) @then("an invalid phase transition error should be raised") def step_check_invalid_transition_error(context: Context) -> None: """Check invalid phase transition error.""" assert context.error is not None assert isinstance(context.error, InvalidPhaseTransitionError) @then('the invalid phase transition error should include message "{message}"') def step_check_invalid_transition_message(context: Context, message: str) -> None: """Check invalid phase transition error message.""" assert context.error is not None assert isinstance(context.error, InvalidPhaseTransitionError) assert message == str(context.error) @then( 'the invalid transition error should reference from phase "{from_phase}" and to phase "{to_phase}"' ) def step_check_invalid_transition_phases( context: Context, from_phase: str, to_phase: str ) -> None: """Check invalid phase transition error phases.""" assert context.error is not None assert isinstance(context.error, InvalidPhaseTransitionError) assert context.error.from_phase == PlanPhase(from_phase) assert context.error.to_phase == PlanPhase(to_phase) @then("a plan error should be raised") def step_check_plan_error(context: Context) -> None: """Check plan error.""" assert context.error is not None assert isinstance(context.error, PlanError) # Cancel Steps @when('I cancel the plan with reason "{reason}"') def step_cancel_plan(context: Context, reason: str) -> None: """Cancel plan.""" context.plan = context.lifecycle_service.cancel_plan( context.plan.identity.plan_id, reason ) @when("I try to cancel the plan") def step_try_cancel_plan(context: Context) -> None: """Try to cancel plan (may fail).""" context.error = None try: context.plan = context.lifecycle_service.cancel_plan( context.plan.identity.plan_id ) except PlanError as e: context.error = e # List Plans Steps @given("I have plans in different phases") def step_create_plans_different_phases(context: Context) -> None: """Create plans in different phases.""" # Create one plan in strategize context.action = context.lifecycle_service.create_action( name="local/action-1", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=["project-1"], ) # Create one plan in execute context.action2 = context.lifecycle_service.create_action( name="local/action-2", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action2.action_id) plan2 = context.lifecycle_service.use_action( action_id=context.action2.action_id, project_ids=["project-2"], ) context.lifecycle_service.start_strategize(plan2.identity.plan_id) context.lifecycle_service.complete_strategize(plan2.identity.plan_id) context.lifecycle_service.execute_plan(plan2.identity.plan_id) @given("I have plans for different projects") def step_create_plans_different_projects(context: Context) -> None: """Create plans for different projects.""" context.action = context.lifecycle_service.create_action( name="local/action-1", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=["project-123"], ) context.action2 = context.lifecycle_service.create_action( name="local/action-2", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action2.action_id) context.lifecycle_service.use_action( action_id=context.action2.action_id, project_ids=["project-456"], ) @given("I have plans in different namespaces") def step_create_plans_different_namespaces(context: Context) -> None: """Create plans in different namespaces.""" context.action = context.lifecycle_service.create_action( name="local/action-1", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action.action_id) context.lifecycle_service.use_action( action_id=context.action.action_id, project_ids=["project-local"], ) context.action2 = context.lifecycle_service.create_action( name="myorg/action-2", definition_of_done="Test", strategy_actor="openai/gpt-4", execution_actor="openai/gpt-4", ) context.lifecycle_service.make_action_available(context.action2.action_id) context.lifecycle_service.use_action( action_id=context.action2.action_id, project_ids=["project-org"], ) @when("I list plans in execute phase") def step_list_plans_execute_phase(context: Context) -> None: """List plans in execute phase.""" context.plan_list = context.lifecycle_service.list_plans(phase=PlanPhase.EXECUTE) @when('I list plans for project "{project_id}"') def step_list_plans_for_project(context: Context, project_id: str) -> None: """List plans for project.""" context.plan_list = context.lifecycle_service.list_plans(project_id=project_id) @when('I list plans in namespace "{namespace}"') def step_list_plans_in_namespace(context: Context, namespace: str) -> None: """List plans for namespace.""" context.plan_list = context.lifecycle_service.list_plans(namespace=namespace) @then("I should see only execute phase plans") def step_check_only_execute_plans(context: Context) -> None: """Check only execute phase plans returned.""" assert len(context.plan_list) > 0 for plan in context.plan_list: assert plan.phase == PlanPhase.EXECUTE @then("I should see only plans for that project") def step_check_only_project_plans(context: Context) -> None: """Check only plans for specific project returned.""" assert len(context.plan_list) > 0 @then("I should see {count:d} plans") def step_check_plan_list_count(context: Context, count: int) -> None: """Check plan list count.""" actual = len(context.plan_list) assert actual == count, f"Expected {count} plans, got {actual}" @then("I should see {count:d} plan") def step_check_plan_list_count_singular(context: Context, count: int) -> None: """Check plan list count (singular form).""" actual = len(context.plan_list) assert actual == count, f"Expected {count} plan(s), got {actual}"