Files
cleveragents-core/features/steps/action_model_steps.py

448 lines
16 KiB
Python

"""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"