"""Step definitions for Action domain model tests.""" from typing import Any from behave import given, then, when from behave.runner import Context from pydantic import ValidationError from cleveragents.domain.models.core.action import ( Action, ActionArgument, ArgumentRequirement, ArgumentType, ) from cleveragents.domain.models.core.plan import ActionState, NamespacedName # ActionArgument Parsing Steps @when('I parse the action argument "{arg_string}"') def step_parse_action_argument(context: Context, arg_string: str) -> None: """Parse an action argument string.""" context.argument = ActionArgument.parse(arg_string) context.parse_error = None @when('I try to parse the action argument "{arg_string}"') def step_try_parse_action_argument(context: Context, arg_string: str) -> None: """Attempt to parse an action argument string that might fail.""" context.parse_error = None try: context.argument = ActionArgument.parse(arg_string) except ValueError as e: context.parse_error = e @then('the argument name should be "{expected}"') def step_check_argument_name(context: Context, expected: str) -> None: """Check the argument name matches expected.""" assert context.argument.name == expected, ( f"Expected name '{expected}', got '{context.argument.name}'" ) @then('the argument type should be "{expected}"') def step_check_argument_type(context: Context, expected: str) -> None: """Check the argument type matches expected.""" actual = context.argument.arg_type.value assert actual == expected, f"Expected type '{expected}', got '{actual}'" @then("the argument should be required") def step_check_argument_required(context: Context) -> None: """Check the argument is required.""" assert context.argument.requirement == ArgumentRequirement.REQUIRED, ( f"Expected argument to be required, got {context.argument.requirement}" ) @then("the argument should be optional") def step_check_argument_optional(context: Context) -> None: """Check the argument is optional.""" assert context.argument.requirement == ArgumentRequirement.OPTIONAL, ( f"Expected argument to be optional, got {context.argument.requirement}" ) @then('the argument description should be "{expected}"') def step_check_argument_description(context: Context, expected: str) -> None: """Check the argument description matches expected.""" assert context.argument.description == expected, ( f"Expected description '{expected}', got '{context.argument.description}'" ) @then("the argument description should be empty") def step_check_argument_description_empty(context: Context) -> None: """Check the argument description is empty.""" assert context.argument.description == "", ( f"Expected empty description, got '{context.argument.description}'" ) @then("an argument parse error should be raised") def step_check_argument_parse_error(context: Context) -> None: """Verify that a parse error was raised.""" assert context.parse_error is not None, "Expected a parse error" @when('I try to create an action argument with invalid name "{name}"') def step_try_create_argument_with_invalid_name(context: Context, name: str) -> None: """Attempt to create an action argument with an invalid identifier name.""" context.error = None try: context.argument = ActionArgument( name=name, arg_type=ArgumentType.STRING, requirement=ArgumentRequirement.REQUIRED, description="Invalid name", ) except ValidationError as e: context.error = e @when("I stringify the action argument") def step_stringify_action_argument(context: Context) -> None: """Convert the action argument to its definition string.""" context.argument_string = str(context.argument) @then('the argument definition string should be "{expected}"') def step_check_argument_definition_string(context: Context, expected: str) -> None: """Verify the string representation of the action argument.""" assert context.argument_string == expected, ( f"Expected definition string '{expected}', got '{context.argument_string}'" ) # Action Creation Steps def _create_default_action( name: str = "local/test-action", definition_of_done: str = "Tests pass", **kwargs: Any, ) -> Action: """Helper to create an action with defaults.""" parsed_name = NamespacedName.parse(name) return Action( action_id="01ARZ3NDEKTSV4RRFFQ69G5FAV", namespaced_name=parsed_name, definition_of_done=definition_of_done, strategy_actor=kwargs.get("strategy_actor", "openai/gpt-4"), execution_actor=kwargs.get("execution_actor", "openai/gpt-4"), **{ k: v for k, v in kwargs.items() if k not in ("strategy_actor", "execution_actor") }, ) @when('I create an action with name "{name}" and definition of done "{dod}"') def step_create_action_with_name_dod(context: Context, name: str, dod: str) -> None: """Create an action with specified name and definition of done.""" context.action = _create_default_action(name=name, definition_of_done=dod) @when("I create a new action") def step_create_new_action(context: Context) -> None: """Create a new action with default values.""" context.action = _create_default_action() @given("I have action arguments") def step_have_action_arguments(context: Context) -> None: """Store action arguments from table.""" context.action_arguments = [] assert context.table is not None, "Expected table data for action arguments" for row in context.table: arg = ActionArgument( name=row["name"], arg_type=ArgumentType(row["type"]), requirement=ArgumentRequirement(row["requirement"]), description=row["description"], ) context.action_arguments.append(arg) @when("I create an action with these arguments") def step_create_action_with_arguments(context: Context) -> None: """Create an action with the stored arguments.""" context.action = _create_default_action(arguments=context.action_arguments) @then("the action should be created") def step_check_action_created(context: Context) -> None: """Verify the action was created.""" assert context.action is not None, "Action should be created" @then('the action namespace should be "{expected}"') def step_check_action_namespace(context: Context, expected: str) -> None: """Check the action namespace matches expected.""" actual = context.action.namespaced_name.namespace assert actual == expected, f"Expected namespace '{expected}', got '{actual}'" @then('the action name should be "{expected}"') def step_check_action_name(context: Context, expected: str) -> None: """Check the action name matches expected.""" actual = context.action.namespaced_name.name assert actual == expected, f"Expected name '{expected}', got '{actual}'" @then('the action state should be "{expected}"') def step_check_action_state(context: Context, expected: str) -> None: """Check the action state matches expected.""" actual = context.action.state.value assert actual == expected, f"Expected state '{expected}', got '{actual}'" @then("the action should have {count:d} arguments") def step_check_argument_count(context: Context, count: int) -> None: """Check the number of arguments.""" actual = len(context.action.arguments) assert actual == count, f"Expected {count} arguments, got {actual}" @then("the action should have {count:d} required argument") def step_check_required_argument_count(context: Context, count: int) -> None: """Check the number of required arguments.""" actual = len(context.action.required_arguments) assert actual == count, f"Expected {count} required arguments, got {actual}" @then("the action should have {count:d} optional argument") def step_check_optional_argument_count(context: Context, count: int) -> None: """Check the number of optional arguments.""" actual = len(context.action.optional_arguments) assert actual == count, f"Expected {count} optional arguments, got {actual}" # Argument Validation Steps @given('I have an action with required argument "{arg_string}"') def step_have_action_with_required_arg(context: Context, arg_string: str) -> None: """Create an action with a required argument.""" arg = ActionArgument.parse(arg_string) context.action = _create_default_action(arguments=[arg]) @when("I validate arguments with target {value:d}") def step_validate_arguments_with_target(context: Context, value: int) -> None: """Validate arguments with target value.""" context.errors = context.action.validate_arguments({"target": value}) @when("I validate arguments without target") def step_validate_arguments_without_target(context: Context) -> None: """Validate arguments without providing target.""" context.errors = context.action.validate_arguments({}) @when('I validate arguments with target {value:d} and extra "{extra}"') def step_validate_arguments_with_extra( context: Context, value: int, extra: str ) -> None: """Validate arguments with an extra unknown argument.""" context.errors = context.action.validate_arguments( { "target": value, "extra": extra, } ) @when('I validate arguments with target as string "{value}"') def step_validate_arguments_with_string_target(context: Context, value: str) -> None: """Validate arguments with target as wrong type.""" context.errors = context.action.validate_arguments({"target": value}) @given( 'I have an action with bounded integer argument "{name}" min {min_value:d} max {max_value:d}' ) def step_have_action_with_bounded_integer( context: Context, name: str, min_value: int, max_value: int ) -> None: """Create an action with a bounded integer argument.""" arg = ActionArgument( name=name, arg_type=ArgumentType.INTEGER, requirement=ArgumentRequirement.REQUIRED, description="Bounded integer argument", min_value=min_value, max_value=max_value, ) context.action = _create_default_action(arguments=[arg]) @given('I have an action with argument "{name}" of type "{arg_type}"') def step_have_action_with_argument_type( context: Context, name: str, arg_type: str ) -> None: """Create an action with an argument of a specific type.""" arg = ActionArgument( name=name, arg_type=ArgumentType(arg_type), requirement=ArgumentRequirement.REQUIRED, description=f"{arg_type} argument", ) context.action = _create_default_action(arguments=[arg]) @when('I validate arguments with "{name}" set to {value:d}') def step_validate_arguments_with_named_int( context: Context, name: str, value: int ) -> None: """Validate arguments with a named integer value.""" context.errors = context.action.validate_arguments({name: value}) @when('I validate arguments with "{name}" set to "{value}"') def step_validate_arguments_with_named_string( context: Context, name: str, value: str ) -> None: """Validate arguments with a named string value.""" context.errors = context.action.validate_arguments({name: value}) @then("the validation should pass") def step_check_validation_pass(context: Context) -> None: """Check that validation passed.""" assert len(context.errors) == 0, ( f"Expected validation to pass, got errors: {context.errors}" ) @then('the validation should fail with "{expected_msg}"') def step_check_validation_fail(context: Context, expected_msg: str) -> None: """Check that validation failed with expected message.""" assert len(context.errors) > 0, "Expected validation errors" error_text = " ".join(context.errors) assert expected_msg in error_text, ( f"Expected error containing '{expected_msg}', got: {context.errors}" ) # Action State Steps @given("I have a draft action") def step_have_draft_action(context: Context) -> None: """Create an action in draft state.""" context.action = _create_default_action() context.action.state = ActionState.DRAFT @given("I have an available action") def step_have_available_action(context: Context) -> None: """Create an action in available state.""" context.action = _create_default_action() context.action.state = ActionState.AVAILABLE @when("I set the action state to available") def step_set_action_available(context: Context) -> None: """Set action state to available.""" context.action.state = ActionState.AVAILABLE @when("I set the action state to archived") def step_set_action_archived(context: Context) -> None: """Set action state to archived.""" context.action.state = ActionState.ARCHIVED # Actor Configuration Steps @when("I try to create an action without strategy actor") def step_try_create_action_without_strategy_actor(context: Context) -> None: """Attempt to create an action without strategy actor.""" context.error = None try: data = { "action_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV", "namespaced_name": NamespacedName.parse("local/test"), "definition_of_done": "Test", # Missing strategy_actor "execution_actor": "openai/gpt-4", } context.action = Action.model_validate(data) except ValidationError as e: context.error = e context.action = None # Note: "a validation error should be raised" step is defined in domain_models_steps.py @when('I create an action with review actor "{actor}"') def step_create_action_with_review_actor(context: Context, actor: str) -> None: """Create an action with a review actor.""" context.action = _create_default_action(review_actor=actor) @when('I create an action with estimation actor "{actor}"') def step_create_action_with_estimation_actor(context: Context, actor: str) -> None: """Create an action with an estimation actor.""" context.action = _create_default_action(estimation_actor=actor) @then("the action should have a review actor") def step_check_has_review_actor(context: Context) -> None: """Check the action has a review actor.""" assert context.action.review_actor is not None, "Expected review actor" @then("the action should have an estimation actor") def step_check_has_estimation_actor(context: Context) -> None: """Check the action has an estimation actor.""" assert context.action.estimation_actor is not None, "Expected estimation actor" # Reusability Steps @when("I create a non-reusable action") def step_create_non_reusable_action(context: Context) -> None: """Create a non-reusable action.""" context.action = _create_default_action(reusable=False) @then("the action should be reusable") def step_check_action_reusable(context: Context) -> None: """Check the action is reusable.""" assert context.action.reusable is True, "Expected action to be reusable" @then("the action should not be reusable") def step_check_action_not_reusable(context: Context) -> None: """Check the action is not reusable.""" assert context.action.reusable is False, "Expected action to not be reusable" # Read-only Steps @when("I create a read-only action") def step_create_read_only_action(context: Context) -> None: """Create a read-only action.""" context.action = _create_default_action(read_only=True) @then("the action should be read-only") def step_check_action_read_only(context: Context) -> None: """Check the action is read-only.""" assert context.action.read_only is True, "Expected action to be read-only" @then("the action should not be read-only") def step_check_action_not_read_only(context: Context) -> None: """Check the action is not read-only.""" assert context.action.read_only is False, "Expected action to not be read-only"