Files
temp/features/steps/action_model_branch_coverage_steps.py

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