forked from HAL9000/cleveragents-core
350 lines
13 KiB
Python
350 lines
13 KiB
Python
"""Step definitions for Action model branch coverage tests.
|
|
|
|
These steps specifically target untested branches in action.py,
|
|
including the happy paths for float, boolean, and list argument
|
|
type validation, as well as metadata fields and parse coverage.
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.domain.models.core.action import (
|
|
Action,
|
|
ActionArgument,
|
|
ActionState,
|
|
ArgumentRequirement,
|
|
ArgumentType,
|
|
)
|
|
from cleveragents.domain.models.core.plan import NamespacedName
|
|
|
|
|
|
def _create_default_action_bc(
|
|
name: str = "local/test-action",
|
|
definition_of_done: str = "Tests pass",
|
|
**kwargs: Any,
|
|
) -> Action:
|
|
"""Helper to create an action with defaults for branch coverage tests."""
|
|
parsed_name = NamespacedName.parse(name)
|
|
return Action(
|
|
namespaced_name=parsed_name,
|
|
description=kwargs.pop("description", "Default test action"),
|
|
definition_of_done=definition_of_done,
|
|
strategy_actor=kwargs.pop("strategy_actor", "openai/gpt-4"),
|
|
execution_actor=kwargs.pop("execution_actor", "openai/gpt-4"),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
# --- Given steps ---
|
|
|
|
|
|
@given('I have an action with a float argument named "{name}"')
|
|
def step_given_action_with_float_arg(context: Context, name: str) -> None:
|
|
"""Create an action with a required float argument."""
|
|
arg = ActionArgument(
|
|
name=name,
|
|
arg_type=ArgumentType.FLOAT,
|
|
requirement=ArgumentRequirement.REQUIRED,
|
|
description="A float argument",
|
|
)
|
|
context.action = _create_default_action_bc(arguments=[arg])
|
|
|
|
|
|
@given('I have an action with a boolean argument named "{name}"')
|
|
def step_given_action_with_boolean_arg(context: Context, name: str) -> None:
|
|
"""Create an action with a required boolean argument."""
|
|
arg = ActionArgument(
|
|
name=name,
|
|
arg_type=ArgumentType.BOOLEAN,
|
|
requirement=ArgumentRequirement.REQUIRED,
|
|
description="A boolean argument",
|
|
)
|
|
context.action = _create_default_action_bc(arguments=[arg])
|
|
|
|
|
|
@given('I have an action with a list argument named "{name}"')
|
|
def step_given_action_with_list_arg(context: Context, name: str) -> None:
|
|
"""Create an action with a required list argument."""
|
|
arg = ActionArgument(
|
|
name=name,
|
|
arg_type=ArgumentType.LIST,
|
|
requirement=ArgumentRequirement.REQUIRED,
|
|
description="A list argument",
|
|
)
|
|
context.action = _create_default_action_bc(arguments=[arg])
|
|
|
|
|
|
@given('I have an action with a string argument named "{name}"')
|
|
def step_given_action_with_string_arg(context: Context, name: str) -> None:
|
|
"""Create an action with a required string argument."""
|
|
arg = ActionArgument(
|
|
name=name,
|
|
arg_type=ArgumentType.STRING,
|
|
requirement=ArgumentRequirement.REQUIRED,
|
|
description="A string argument",
|
|
)
|
|
context.action = _create_default_action_bc(arguments=[arg])
|
|
|
|
|
|
@given(
|
|
'I have an action with a bounded float argument "{name}" min {min_val} max {max_val}'
|
|
)
|
|
def step_given_action_with_bounded_float_arg(
|
|
context: Context, name: str, min_val: str, max_val: str
|
|
) -> None:
|
|
"""Create an action with a float argument that has min/max bounds."""
|
|
arg = ActionArgument(
|
|
name=name,
|
|
arg_type=ArgumentType.FLOAT,
|
|
requirement=ArgumentRequirement.REQUIRED,
|
|
description="A bounded float argument",
|
|
min_value=float(min_val),
|
|
max_value=float(max_val),
|
|
)
|
|
context.action = _create_default_action_bc(arguments=[arg])
|
|
|
|
|
|
@given("I have an action with mixed argument types")
|
|
def step_given_action_with_mixed_args(context: Context) -> None:
|
|
"""Create an action with arguments of different types from the table."""
|
|
args = []
|
|
assert context.table is not None, "Expected table data"
|
|
for row in context.table:
|
|
arg = ActionArgument(
|
|
name=row["name"],
|
|
arg_type=ArgumentType(row["type"]),
|
|
requirement=ArgumentRequirement(row["requirement"]),
|
|
description=f"{row['name']} argument",
|
|
)
|
|
args.append(arg)
|
|
context.action = _create_default_action_bc(arguments=args)
|
|
|
|
|
|
@given('I have a parsed action argument from "{arg_string}"')
|
|
def step_given_parsed_action_argument(context: Context, arg_string: str) -> None:
|
|
"""Parse an action argument and store it."""
|
|
context.bc_argument = ActionArgument.parse(arg_string)
|
|
|
|
|
|
@given("I have a newly created action in available state")
|
|
def step_given_newly_created_available_action(context: Context) -> None:
|
|
"""Create a fresh action that starts in available state."""
|
|
context.action = _create_default_action_bc()
|
|
assert context.action.state == ActionState.AVAILABLE
|
|
|
|
|
|
# --- When steps ---
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as float {value}')
|
|
def step_when_validate_with_float(context: Context, name: str, value: str) -> None:
|
|
"""Validate arguments providing a float value."""
|
|
context.validation_errors = context.action.validate_arguments({name: float(value)})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as integer {value:d}')
|
|
def step_when_validate_with_integer_for_float(
|
|
context: Context, name: str, value: int
|
|
) -> None:
|
|
"""Validate arguments providing an int value (valid for float arg)."""
|
|
context.validation_errors = context.action.validate_arguments({name: value})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as boolean true')
|
|
def step_when_validate_with_boolean_true(context: Context, name: str) -> None:
|
|
"""Validate arguments providing True."""
|
|
context.validation_errors = context.action.validate_arguments({name: True})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as boolean false')
|
|
def step_when_validate_with_boolean_false(context: Context, name: str) -> None:
|
|
"""Validate arguments providing False."""
|
|
context.validation_errors = context.action.validate_arguments({name: False})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as list "{csv_value}"')
|
|
def step_when_validate_with_list(context: Context, name: str, csv_value: str) -> None:
|
|
"""Validate arguments providing a list (from comma-separated string)."""
|
|
list_value = [item.strip() for item in csv_value.split(",")]
|
|
context.validation_errors = context.action.validate_arguments({name: list_value})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as an empty list')
|
|
def step_when_validate_with_empty_list(context: Context, name: str) -> None:
|
|
"""Validate arguments providing an empty list."""
|
|
context.validation_errors = context.action.validate_arguments({name: []})
|
|
|
|
|
|
@when('I validate the action arguments with "{name}" as string "{value}"')
|
|
def step_when_validate_with_string(context: Context, name: str, value: str) -> None:
|
|
"""Validate arguments providing a string value."""
|
|
context.validation_errors = context.action.validate_arguments({name: value})
|
|
|
|
|
|
@when("I validate the action with all mixed arguments provided correctly")
|
|
def step_when_validate_mixed_args(context: Context) -> None:
|
|
"""Validate with correct values for each argument type."""
|
|
context.validation_errors = context.action.validate_arguments(
|
|
{
|
|
"label": "test-label",
|
|
"count": 10,
|
|
"ratio": 3.14,
|
|
"enabled": True,
|
|
"tags": ["alpha", "beta"],
|
|
}
|
|
)
|
|
|
|
|
|
@when('I parse an action argument definition "{arg_string}"')
|
|
def step_when_parse_arg_definition(context: Context, arg_string: str) -> None:
|
|
"""Parse an action argument definition string."""
|
|
context.bc_argument = ActionArgument.parse(arg_string)
|
|
|
|
|
|
@when('I create an action with tags "{tags_csv}" and created_by "{created_by}"')
|
|
def step_when_create_action_with_tags_and_creator(
|
|
context: Context, tags_csv: str, created_by: str
|
|
) -> None:
|
|
"""Create an action with specified tags and created_by."""
|
|
tags = [t.strip() for t in tags_csv.split(",")]
|
|
context.action = _create_default_action_bc(tags=tags, created_by=created_by)
|
|
|
|
|
|
@when('I create an action with description "{desc}" and long description "{long_desc}"')
|
|
def step_when_create_action_with_descriptions(
|
|
context: Context, desc: str, long_desc: str
|
|
) -> None:
|
|
"""Create an action with description and long description."""
|
|
context.action = _create_default_action_bc(
|
|
description=desc,
|
|
long_description=long_desc,
|
|
)
|
|
|
|
|
|
@when('I create an action with apply_actor "{actor}"')
|
|
def step_when_create_action_with_apply_actor(context: Context, actor: str) -> None:
|
|
"""Create an action with an apply actor."""
|
|
context.action = _create_default_action_bc(apply_actor=actor)
|
|
|
|
|
|
@when('I change the action argument description to "{new_desc}"')
|
|
def step_when_change_argument_description(context: Context, new_desc: str) -> None:
|
|
"""Change the description field on the stored argument (tests validate_assignment)."""
|
|
context.bc_argument.description = new_desc
|
|
|
|
|
|
@when('I change the action state to "{new_state}"')
|
|
def step_when_change_action_state(context: Context, new_state: str) -> None:
|
|
"""Change the action state via assignment (tests validate_assignment)."""
|
|
context.action.state = ActionState(new_state)
|
|
|
|
|
|
# --- Then steps ---
|
|
|
|
|
|
@then("the action argument validation should pass with no errors")
|
|
def step_then_validation_passes(context: Context) -> None:
|
|
"""Assert no validation errors."""
|
|
assert len(context.validation_errors) == 0, (
|
|
f"Expected no validation errors, got: {context.validation_errors}"
|
|
)
|
|
|
|
|
|
@then('the parsed argument name should be "{expected}"')
|
|
def step_then_parsed_arg_name(context: Context, expected: str) -> None:
|
|
"""Check parsed argument name."""
|
|
assert context.bc_argument.name == expected, (
|
|
f"Expected name '{expected}', got '{context.bc_argument.name}'"
|
|
)
|
|
|
|
|
|
@then('the parsed argument type should be "{expected}"')
|
|
def step_then_parsed_arg_type(context: Context, expected: str) -> None:
|
|
"""Check parsed argument type."""
|
|
actual = context.bc_argument.arg_type.value
|
|
assert actual == expected, f"Expected type '{expected}', got '{actual}'"
|
|
|
|
|
|
@then("the parsed argument should be required")
|
|
def step_then_parsed_arg_required(context: Context) -> None:
|
|
"""Check parsed argument is required."""
|
|
assert context.bc_argument.requirement == ArgumentRequirement.REQUIRED, (
|
|
f"Expected required, got {context.bc_argument.requirement}"
|
|
)
|
|
|
|
|
|
@then("the parsed argument should be optional")
|
|
def step_then_parsed_arg_optional(context: Context) -> None:
|
|
"""Check parsed argument is optional."""
|
|
assert context.bc_argument.requirement == ArgumentRequirement.OPTIONAL, (
|
|
f"Expected optional, got {context.bc_argument.requirement}"
|
|
)
|
|
|
|
|
|
@then('the parsed argument description should be "{expected}"')
|
|
def step_then_parsed_arg_description(context: Context, expected: str) -> None:
|
|
"""Check parsed argument description."""
|
|
assert context.bc_argument.description == expected, (
|
|
f"Expected description '{expected}', got '{context.bc_argument.description}'"
|
|
)
|
|
|
|
|
|
@then("the action should have {count:d} tags")
|
|
def step_then_action_has_n_tags(context: Context, count: int) -> None:
|
|
"""Check the number of tags on the action."""
|
|
actual = len(context.action.tags)
|
|
assert actual == count, f"Expected {count} tags, got {actual}"
|
|
|
|
|
|
@then('the action created_by should be "{expected}"')
|
|
def step_then_action_created_by(context: Context, expected: str) -> None:
|
|
"""Check the action's created_by field."""
|
|
assert context.action.created_by == expected, (
|
|
f"Expected created_by '{expected}', got '{context.action.created_by}'"
|
|
)
|
|
|
|
|
|
@then('the action description should be "{expected}"')
|
|
def step_then_action_description(context: Context, expected: str) -> None:
|
|
"""Check the action's description."""
|
|
assert context.action.description == expected, (
|
|
f"Expected description '{expected}', got '{context.action.description}'"
|
|
)
|
|
|
|
|
|
@then('the action long description should contain "{fragment}"')
|
|
def step_then_action_long_description_contains(context: Context, fragment: str) -> None:
|
|
"""Check the action's long description contains the fragment."""
|
|
assert context.action.long_description is not None, (
|
|
"Expected long_description to be set"
|
|
)
|
|
assert fragment in context.action.long_description, (
|
|
f"Expected long_description to contain '{fragment}', "
|
|
f"got '{context.action.long_description}'"
|
|
)
|
|
|
|
|
|
@then('the action apply_actor should be "{expected}"')
|
|
def step_then_action_apply_actor(context: Context, expected: str) -> None:
|
|
"""Check the action's apply_actor field."""
|
|
assert context.action.apply_actor == expected, (
|
|
f"Expected apply_actor '{expected}', got '{context.action.apply_actor}'"
|
|
)
|
|
|
|
|
|
@then('the action argument description should now be "{expected}"')
|
|
def step_then_arg_description_updated(context: Context, expected: str) -> None:
|
|
"""Check the argument description was updated."""
|
|
assert context.bc_argument.description == expected, (
|
|
f"Expected description '{expected}', got '{context.bc_argument.description}'"
|
|
)
|
|
|
|
|
|
@then('the action current state should be "{expected}"')
|
|
def step_then_action_current_state(context: Context, expected: str) -> None:
|
|
"""Check the action's current state value."""
|
|
actual = context.action.state.value
|
|
assert actual == expected, f"Expected state '{expected}', got '{actual}'"
|