forked from cleveragents/cleveragents-core
448 lines
16 KiB
Python
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"
|