Files
CoreRasurae 8f986c1e31 test(coverage): add coverage scenarios for pre-existing code paths
Additional BDD scenarios covering registry resolver errors, cache
TTL expiry, runtime dispatch normalization, template base edge cases,
validation actor coverage gaps, and YAML Jinja loader deferred rendering.
2026-06-23 10:49:16 +01:00

1201 lines
42 KiB
Python

"""
Step definitions for Template Parameter Validation and Variable Rendering BDD tests.
"""
from unittest.mock import Mock
from behave import given, then, when
from cleveractors.templates.base import (
BaseTemplate,
ComponentReference,
InstantiationContext,
TemplateParameter,
TemplateType,
)
@given("I have a clean test environment for templates")
def step_clean_template_environment(context):
"""Set up clean test environment for templates."""
context.template_types = None
context.template_param = None
context.validation_result = None
context.error = None
context.component_ref = None
context.context_instance = None
context.template_instance = None
context.components = {}
context.params = {}
context.result = None
# TemplateType enum tests
@when("I access all TemplateType enum values")
def step_access_template_types(context):
"""Access all TemplateType enum values."""
try:
context.template_types = {
"AGENT": TemplateType.AGENT,
"GRAPH": TemplateType.GRAPH,
"STREAM": TemplateType.STREAM,
}
except Exception as e:
context.error = e
@then("all template types should be available")
def step_verify_template_types_available(context):
"""Verify all template types are available."""
assert context.error is None
assert context.template_types is not None
assert "AGENT" in context.template_types
assert "GRAPH" in context.template_types
assert "STREAM" in context.template_types
@then("enum values should have correct string representations")
def step_verify_enum_string_representations(context):
"""Verify enum string representations."""
assert context.template_types["AGENT"].value == "agent"
assert context.template_types["GRAPH"].value == "graph"
assert context.template_types["STREAM"].value == "stream"
# TemplateParameter tests - string type
@given("I have a string template parameter with default value")
def step_string_template_parameter(context):
"""Create string template parameter."""
context.template_param = TemplateParameter(
name="test_param",
default="default_value",
type="string",
description="Test string parameter",
)
@when("I validate different string values")
def step_validate_string_values(context):
"""Validate different string values."""
context.results = {}
try:
# Test normal string
context.results["normal"] = context.template_param.validate("test_string")
# Test None (should use default)
context.results["none"] = context.template_param.validate(None)
# Test number conversion to string
context.results["number"] = context.template_param.validate(123)
except Exception as e:
context.error = e
@then("string validation should work correctly")
def step_verify_string_validation(context):
"""Verify string validation works."""
assert context.error is None
assert context.results["normal"] == "test_string"
assert context.results["number"] == "123"
@then("default values should be used when None")
def step_verify_default_values(context):
"""Verify default values are used for None."""
assert context.results["none"] == "default_value"
# TemplateParameter tests - int type
@given("I have an integer template parameter")
def step_integer_template_parameter(context):
"""Create integer template parameter."""
context.template_param = TemplateParameter(name="int_param", type="int", default=42)
@when("I validate integer values including conversions")
def step_validate_integer_values(context):
"""Validate integer values."""
context.results = {}
context.errors = {}
try:
# Test normal int
context.results["normal"] = context.template_param.validate(100)
# Test string conversion
context.results["string_convert"] = context.template_param.validate("200")
# Test None (default)
context.results["none"] = context.template_param.validate(None)
except Exception as e:
context.error = e
# Test invalid conversion
try:
context.template_param.validate("invalid_int")
except ValueError as e:
context.errors["invalid"] = e
@then("integer validation should work correctly")
def step_verify_integer_validation(context):
"""Verify integer validation."""
assert context.error is None
assert context.results["normal"] == 100
assert context.results["string_convert"] == 200
assert context.results["none"] == 42
@then("invalid integers should raise errors")
def step_verify_invalid_integers_error(context):
"""Verify invalid integers raise errors."""
assert "invalid" in context.errors
assert isinstance(context.errors["invalid"], ValueError)
# TemplateParameter tests - float type
@given("I have a float template parameter")
def step_float_template_parameter(context):
"""Create float template parameter."""
context.template_param = TemplateParameter(
name="float_param", type="float", default=3.14
)
@when("I validate float values including conversions")
def step_validate_float_values(context):
"""Validate float values."""
context.results = {}
try:
# Test normal float
context.results["normal"] = context.template_param.validate(2.5)
# Test string conversion
context.results["string_convert"] = context.template_param.validate("1.5")
# Test int conversion
context.results["int_convert"] = context.template_param.validate(5)
# Test None (default)
context.results["none"] = context.template_param.validate(None)
except Exception as e:
context.error = e
@then("float validation should work correctly")
def step_verify_float_validation(context):
"""Verify float validation."""
assert context.error is None
assert context.results["normal"] == 2.5
assert context.results["string_convert"] == 1.5
assert context.results["int_convert"] == 5.0
assert context.results["none"] == 3.14
# TemplateParameter tests - boolean type
@given("I have a boolean template parameter")
def step_boolean_template_parameter(context):
"""Create boolean template parameter."""
context.template_param = TemplateParameter(
name="bool_param", type="boolean", default=False
)
@when("I validate boolean values including string conversions")
def step_validate_boolean_values(context):
"""Validate boolean values."""
context.results = {}
try:
# Test normal bool
context.results["normal_true"] = context.template_param.validate(True)
context.results["normal_false"] = context.template_param.validate(False)
# Test string conversions
context.results["string_true"] = context.template_param.validate("true")
context.results["string_yes"] = context.template_param.validate("yes")
context.results["string_1"] = context.template_param.validate("1")
context.results["string_on"] = context.template_param.validate("on")
context.results["string_false"] = context.template_param.validate("false")
context.results["string_no"] = context.template_param.validate("no")
# Test other types
context.results["int_1"] = context.template_param.validate(1)
context.results["int_0"] = context.template_param.validate(0)
# Test None (default)
context.results["none"] = context.template_param.validate(None)
except Exception as e:
context.error = e
@then("boolean validation should work correctly")
def step_verify_boolean_validation(context):
"""Verify boolean validation."""
assert context.error is None
assert context.results["normal_true"] is True
assert context.results["normal_false"] is False
assert context.results["int_1"] is True
assert context.results["int_0"] is False
assert context.results["none"] is False
@then("string boolean values should convert properly")
def step_verify_string_boolean_conversion(context):
"""Verify string boolean conversion."""
assert context.results["string_true"] is True
assert context.results["string_yes"] is True
assert context.results["string_1"] is True
assert context.results["string_on"] is True
assert context.results["string_false"] is False
assert context.results["string_no"] is False
# TemplateParameter tests - enum type
@given("I have an enum template parameter with allowed values")
def step_enum_template_parameter(context):
"""Create enum template parameter."""
context.template_param = TemplateParameter(
name="enum_param",
type="enum",
values=["option1", "option2", "option3"],
default="option1",
)
@when("I validate enum values")
def step_validate_enum_values(context):
"""Validate enum values."""
context.results = {}
context.errors = {}
try:
# Test valid values
context.results["valid1"] = context.template_param.validate("option1")
context.results["valid2"] = context.template_param.validate("option2")
# Test None (default)
context.results["none"] = context.template_param.validate(None)
except Exception as e:
context.error = e
# Test invalid value
try:
context.template_param.validate("invalid_option")
except ValueError as e:
context.errors["invalid"] = e
@then("valid enum values should pass")
def step_verify_valid_enum_values(context):
"""Verify valid enum values pass."""
assert context.error is None
assert context.results["valid1"] == "option1"
assert context.results["valid2"] == "option2"
assert context.results["none"] == "option1"
@then("invalid enum values should raise errors")
def step_verify_invalid_enum_error(context):
"""Verify invalid enum values raise errors."""
assert "invalid" in context.errors
assert isinstance(context.errors["invalid"], ValueError)
assert "must be one of" in str(context.errors["invalid"])
# TemplateParameter tests - list type
@given("I have a list template parameter")
def step_list_template_parameter(context):
"""Create list template parameter."""
context.template_param = TemplateParameter(
name="list_param", type="list", default=[]
)
@when("I validate list values including single value conversion")
def step_validate_list_values(context):
"""Validate list values."""
context.results = {}
try:
# Test normal list
context.results["normal"] = context.template_param.validate([1, 2, 3])
# Test single value conversion
context.results["single"] = context.template_param.validate("single_item")
# Test None (default)
context.results["none"] = context.template_param.validate(None)
except Exception as e:
context.error = e
@then("list validation should work correctly")
def step_verify_list_validation(context):
"""Verify list validation."""
assert context.error is None
assert context.results["normal"] == [1, 2, 3]
assert context.results["none"] == []
@then("single values should convert to lists")
def step_verify_single_value_conversion(context):
"""Verify single values convert to lists."""
assert context.results["single"] == ["single_item"]
# TemplateParameter tests - dict and reference types
@given("I have parameters of different reference types")
def step_reference_type_parameters(context):
"""Create parameters of different reference types."""
context.params = {
"dict": TemplateParameter(name="dict_param", type="dict"),
"agent_ref": TemplateParameter(name="agent_ref_param", type="agent_ref"),
"component_ref": TemplateParameter(name="comp_ref_param", type="component_ref"),
"unknown": TemplateParameter(name="unknown_param", type="unknown_type"),
}
@when("I validate dict, agent_ref, and component_ref values")
def step_validate_reference_values(context):
"""Validate reference type values."""
context.results = {}
try:
# Test dict type
test_dict = {"key": "value"}
context.results["dict"] = context.params["dict"].validate(test_dict)
# Test agent_ref type
agent_ref = {"type": "agent", "name": "test_agent"}
context.results["agent_ref"] = context.params["agent_ref"].validate(agent_ref)
# Test component_ref type
comp_ref = {"type": "component", "name": "test_comp"}
context.results["component_ref"] = context.params["component_ref"].validate(
comp_ref
)
# Test unknown type (should pass through)
context.results["unknown"] = context.params["unknown"].validate("any_value")
except Exception as e:
context.error = e
@then("reference type validation should work correctly")
def step_verify_reference_validation(context):
"""Verify reference type validation."""
assert context.error is None
assert context.results["dict"] == {"key": "value"}
assert context.results["agent_ref"] == {"type": "agent", "name": "test_agent"}
assert context.results["component_ref"] == {
"type": "component",
"name": "test_comp",
}
assert context.results["unknown"] == "any_value"
# TemplateParameter tests - required parameter
@given("I have a required template parameter")
def step_required_template_parameter(context):
"""Create required template parameter."""
context.template_param = TemplateParameter(
name="required_param", type="string", required=True
)
@when("I validate with None value")
def step_validate_none_required(context):
"""Validate None value on required parameter."""
try:
context.result = context.template_param.validate(None)
except ValueError as e:
context.error = e
@then("a ValueError should be raised for missing required parameter")
def step_verify_required_error(context):
"""Verify ValueError for missing required parameter."""
assert context.error is not None
assert isinstance(context.error, ValueError)
assert "Required parameter" in str(context.error)
# ComponentReference tests
@given("I have component references of different types")
def step_component_references(context):
"""Create component references."""
context.component_refs = {
"agent": ComponentReference("agent", "test_agent", {"param": "value"}),
"graph": ComponentReference("graph", "test_graph"),
"stream": ComponentReference("stream", "test_stream", {"config": "test"}),
}
@when("I create ComponentReference instances")
def step_create_component_references(context):
"""Create ComponentReference instances."""
try:
# Already created in given step
context.result = "success"
except Exception as e:
context.error = e
@then("component references should be created correctly")
def step_verify_component_references(context):
"""Verify component references are created correctly."""
assert context.error is None
assert context.result == "success"
agent_ref = context.component_refs["agent"]
assert agent_ref.ref_type == "agent"
assert agent_ref.ref_name == "test_agent"
assert agent_ref.ref_params == {"param": "value"}
graph_ref = context.component_refs["graph"]
assert graph_ref.ref_type == "graph"
assert graph_ref.ref_name == "test_graph"
assert graph_ref.ref_params == {}
@then("reference properties should be accessible")
def step_verify_reference_properties(context):
"""Verify reference properties are accessible."""
stream_ref = context.component_refs["stream"]
assert stream_ref.ref_type == "stream"
assert stream_ref.ref_name == "test_stream"
assert stream_ref.ref_params == {"config": "test"}
# InstantiationContext tests
@given("I have an InstantiationContext")
def step_instantiation_context(context):
"""Create InstantiationContext."""
context.context_instance = InstantiationContext()
@when("I add components of different types")
def step_add_components(context):
"""Add components to context."""
try:
context.context_instance.add_component(
"agent", "agent1", {"type": "test_agent"}
)
context.context_instance.add_component(
"graph", "graph1", {"type": "test_graph"}
)
context.context_instance.add_component(
"stream", "stream1", {"type": "test_stream"}
)
except Exception as e:
context.error = e
@then("components should be stored correctly")
def step_verify_components_stored(context):
"""Verify components are stored correctly."""
assert context.error is None
components = context.context_instance.components
assert "agent1" in components["agents"]
assert "graph1" in components["graphs"]
assert "stream1" in components["streams"]
@then("component retrieval should work")
def step_verify_component_retrieval(context):
"""Verify component retrieval works."""
components = context.context_instance.components
assert components["agents"]["agent1"]["type"] == "test_agent"
assert components["graphs"]["graph1"]["type"] == "test_graph"
assert components["streams"]["stream1"]["type"] == "test_stream"
# InstantiationContext with parent tests
@given("I have a parent InstantiationContext with components")
def step_parent_context_with_components(context):
"""Create parent context with components."""
context.parent_context = InstantiationContext()
context.parent_context.add_component("agent", "parent_agent", {"type": "parent"})
@given("I have a child context")
def step_child_context(context):
"""Create child context."""
context.child_context = InstantiationContext(parent=context.parent_context)
context.child_context.add_component("agent", "child_agent", {"type": "child"})
@when("I try to resolve references in child context")
def step_resolve_references_child(context):
"""Resolve references in child context."""
try:
# Create references
parent_ref = ComponentReference("agent", "parent_agent")
child_ref = ComponentReference("agent", "child_agent")
missing_ref = ComponentReference("agent", "missing_agent")
# Test resolution
context.results = {
"parent": context.child_context.resolve_reference(parent_ref),
"child": context.child_context.resolve_reference(child_ref),
"missing": context.child_context.resolve_reference(missing_ref),
}
except Exception as e:
context.error = e
@then("child context should check parent for references")
def step_verify_parent_resolution(context):
"""Verify child context checks parent for references."""
assert context.error is None
assert context.results["parent"]["type"] == "parent"
assert context.results["child"]["type"] == "child"
assert context.results["missing"] is None
# InstantiationContext reference resolution tests
@given("I have an InstantiationContext with components")
def step_context_with_components(context):
"""Create context with components."""
context.context_instance = InstantiationContext()
context.context_instance.add_component("agent", "existing_agent", {"data": "test"})
@when("I resolve component references")
def step_resolve_component_references(context):
"""Resolve component references."""
try:
existing_ref = ComponentReference("agent", "existing_agent")
missing_ref = ComponentReference("agent", "missing_agent")
context.results = {
"existing": context.context_instance.resolve_reference(existing_ref),
"missing": context.context_instance.resolve_reference(missing_ref),
}
except Exception as e:
context.error = e
@then("existing references should be resolved correctly")
def step_verify_existing_resolution(context):
"""Verify existing references resolve correctly."""
assert context.error is None
assert context.results["existing"]["data"] == "test"
@then("missing references should return None")
def step_verify_missing_return_none(context):
"""Verify missing references return None."""
assert context.results["missing"] is None
# InstantiationContext pending references tests
@when("I resolve references that don't exist yet")
def step_resolve_pending_references(context):
"""Resolve references that don't exist yet."""
try:
pending_ref = ComponentReference("graph", "pending_graph")
result = context.context_instance.resolve_reference(pending_ref)
context.pending_result = result
# Check pending list
context.pending_count = len(context.context_instance._pending_resolutions)
except Exception as e:
context.error = e
@then("references should be added to pending list")
def step_verify_pending_list(context):
"""Verify references are added to pending list."""
assert context.error is None
assert context.pending_result is None
assert context.pending_count > 0
@then("resolve_pending should handle unresolved references")
def step_verify_resolve_pending(context):
"""Verify resolve_pending handles unresolved references."""
try:
context.context_instance.resolve_pending()
except ValueError as e:
context.pending_error = e
# Should raise error for unresolved references
assert hasattr(context, "pending_error")
assert "Cannot resolve references" in str(context.pending_error)
# InstantiationContext get_all_components tests
@given("I have an InstantiationContext with various components")
def step_context_various_components(context):
"""Create context with various components."""
context.context_instance = InstantiationContext()
context.context_instance.add_component("agent", "agent1", {"type": "llm"})
context.context_instance.add_component("graph", "graph1", {"nodes": ["a", "b"]})
context.original_components = context.context_instance.components
@when("I get all components")
def step_get_all_components(context):
"""Get all components."""
try:
context.all_components = context.context_instance.get_all_components()
except Exception as e:
context.error = e
@then("a deep copy of all components should be returned")
def step_verify_deep_copy_components(context):
"""Verify deep copy of components."""
assert context.error is None
assert context.all_components == context.original_components
# Verify it's a deep copy by modifying original
context.original_components["agents"]["agent1"]["modified"] = True
assert "modified" not in context.all_components["agents"]["agent1"]
# Concrete BaseTemplate implementation for testing
class TestTemplate(BaseTemplate):
def instantiate(self, params, registry, context):
return {"template": self.name, "params": params}
# BaseTemplate parameter parsing tests - dict format
@given("I have a template definition with dict format parameters")
def step_template_dict_params(context):
"""Create template with dict format parameters."""
context.template_definition = {
"parameters": {
"param1": {
"type": "string",
"default": "default1",
"description": "First parameter",
},
"param2": {"type": "int", "required": True},
"param3": "simple_default", # Simple value format
}
}
@when("I create a BaseTemplate instance")
def step_create_template_instance(context):
"""Create BaseTemplate instance."""
try:
context.template_instance = TestTemplate(
"test_template", TemplateType.AGENT, context.template_definition
)
except Exception as e:
context.error = e
@then("parameters should be parsed correctly from dict format")
def step_verify_dict_params_parsing(context):
"""Verify dict format parameters parsing."""
assert context.error is None
params = context.template_instance.parameters
assert "param1" in params
assert params["param1"].type == "string"
assert params["param1"].default == "default1"
assert params["param1"].description == "First parameter"
assert "param2" in params
assert params["param2"].type == "int"
assert params["param2"].required is True
assert "param3" in params
assert params["param3"].default == "simple_default"
# BaseTemplate parameter parsing tests - list format
@given("I have a template definition with list format parameters")
def step_template_list_params(context):
"""Create template with list format parameters."""
context.template_definition = {
"parameters": [
{"name": "param1", "type": "string", "default": "list_default"},
{"name": "param2", "type": "boolean", "required": True},
"simple_param", # Simple parameter name
]
}
@then("parameters should be parsed correctly from list format")
def step_verify_list_params_parsing(context):
"""Verify list format parameters parsing."""
assert context.error is None
params = context.template_instance.parameters
assert "param1" in params
assert params["param1"].type == "string"
assert params["param1"].default == "list_default"
assert "param2" in params
assert params["param2"].type == "boolean"
assert params["param2"].required is True
assert "simple_param" in params
assert params["simple_param"].name == "simple_param"
# BaseTemplate parameter parsing tests - mixed formats
@given("I have a template definition with mixed parameter formats")
def step_template_mixed_params(context):
"""Create template with mixed parameter formats."""
context.template_definition = {
"parameters": {"dict_param": {"type": "float", "default": 3.14}},
"other_config": "not_parameters",
}
@then("all parameter formats should be parsed correctly")
def step_verify_mixed_params_parsing(context):
"""Verify mixed format parameters parsing."""
assert context.error is None
params = context.template_instance.parameters
assert "dict_param" in params
assert params["dict_param"].type == "float"
assert params["dict_param"].default == 3.14
# BaseTemplate validate_params tests
@given("I have a BaseTemplate with various parameter types")
def step_template_various_param_types(context):
"""Create template with various parameter types."""
context.template_definition = {
"parameters": {
"string_param": {"type": "string", "default": "default"},
"int_param": {"type": "int", "required": True},
"bool_param": {"type": "boolean", "default": False},
}
}
context.template_instance = TestTemplate(
"test_template", TemplateType.AGENT, context.template_definition
)
@when("I validate parameters with different values")
def step_validate_various_params(context):
"""Validate parameters with different values."""
try:
input_params = {
"string_param": "custom_string",
"int_param": 42,
"extra_param": "extra_value", # Not in definition
}
context.validated_params = context.template_instance.validate_params(
input_params
)
except Exception as e:
context.error = e
@then("parameter validation should work correctly")
def step_verify_param_validation(context):
"""Verify parameter validation works correctly."""
assert context.error is None
params = context.validated_params
assert params["string_param"] == "custom_string"
assert params["int_param"] == 42
assert params["bool_param"] is False # Default value
@then("extra parameters should be included")
def step_verify_extra_params_included(context):
"""Verify extra parameters are included."""
assert context.validated_params["extra_param"] == "extra_value"
# BaseTemplate _apply_template_vars tests - string templates
@given("I have a BaseTemplate instance")
def step_basic_template_instance(context):
"""Create basic BaseTemplate instance."""
context.template_instance = TestTemplate("test_template", TemplateType.AGENT, {})
@when("I apply template variables to string templates")
def step_apply_string_templates(context):
"""Apply template variables to string templates."""
try:
test_params = {"name": "test", "count": 5, "enabled": True}
context.results = {
"simple": context.template_instance._apply_template_vars(
"Hello {{ name }}", test_params
),
"complex": context.template_instance._apply_template_vars(
"Count: {{ count }}", test_params
),
"boolean_true": context.template_instance._apply_template_vars(
"{{ enabled }}", test_params
),
"boolean_false": context.template_instance._apply_template_vars(
"{{ not enabled }}", test_params
),
"boolean_string_true": context.template_instance._apply_template_vars(
"true", test_params
),
"boolean_string_false": context.template_instance._apply_template_vars(
"false", test_params
),
"no_template": context.template_instance._apply_template_vars(
"plain string", test_params
),
}
except Exception as e:
context.error = e
@then("Jinja2 templates should be rendered correctly")
def step_verify_jinja_rendering(context):
"""Verify Jinja2 templates are rendered correctly."""
assert context.error is None
assert context.results["simple"] == "Hello test"
assert context.results["complex"] == "Count: 5"
assert context.results["no_template"] == "plain string"
@then("boolean strings should convert to boolean values")
def step_verify_boolean_string_conversion(context):
"""Verify boolean strings convert to boolean values."""
# Template-rendered booleans should convert
assert context.results["boolean_true"] is True
assert context.results["boolean_false"] is False
# Plain boolean strings should remain as strings (only templated ones convert)
assert context.results["boolean_string_true"] == "true"
assert context.results["boolean_string_false"] == "false"
# BaseTemplate _apply_template_vars tests - JSON parsing
@when("I apply template variables that result in JSON-like strings")
def step_apply_json_templates(context):
"""Apply template variables that result in JSON-like strings."""
try:
test_params = {"items": ["a", "b", "c"], "config": {"key": "value"}}
context.results = {
"list": context.template_instance._apply_template_vars(
"{{ items }}", test_params
),
"dict": context.template_instance._apply_template_vars(
"{{ config }}", test_params
),
"malformed": context.template_instance._apply_template_vars(
"[invalid json", test_params
),
}
except Exception as e:
context.error = e
@then("JSON structures should be parsed correctly")
def step_verify_json_parsing(context):
"""Verify JSON structures are parsed correctly."""
assert context.error is None
assert isinstance(context.results["list"], list)
assert isinstance(context.results["dict"], dict)
@then("malformed JSON should fall back to string")
def step_verify_json_fallback(context):
"""Verify malformed JSON falls back to string."""
assert isinstance(context.results["malformed"], str)
# BaseTemplate _apply_template_vars tests - number parsing
@when("I apply template variables that result in numbers")
def step_apply_number_templates(context):
"""Apply template variables that result in numbers."""
try:
test_params = {"int_val": 42, "float_val": 3.14}
context.results = {
"integer": context.template_instance._apply_template_vars(
"{{ int_val }}", test_params
),
"float": context.template_instance._apply_template_vars(
"{{ float_val }}", test_params
),
"rendered_int": context.template_instance._apply_template_vars(
"{{ 42 }}", test_params
),
"rendered_float": context.template_instance._apply_template_vars(
"{{ 3.14 }}", test_params
),
"string_int": context.template_instance._apply_template_vars(
"42", test_params
),
"string_float": context.template_instance._apply_template_vars(
"3.14", test_params
),
}
except Exception as e:
context.error = e
@then("integers and floats should be parsed correctly")
def step_verify_number_parsing(context):
"""Verify integers and floats are parsed correctly."""
assert context.error is None
# Template-rendered numbers should convert to int/float
assert context.results["integer"] == 42
assert context.results["float"] == 3.14
assert context.results["rendered_int"] == 42
assert context.results["rendered_float"] == 3.14
# Plain number strings should remain as strings (only templated ones convert)
assert context.results["string_int"] == "42"
assert context.results["string_float"] == "3.14"
# BaseTemplate _apply_template_vars tests - dict processing
@when("I apply template variables to dictionary structures")
def step_apply_dict_templates(context):
"""Apply template variables to dictionary structures."""
try:
test_params = {"key1": "value1", "key2": "value2"}
test_dict = {
"static_key": "static_value",
"{{ key1 }}": "templated_key",
"normal_key": "{{ key2 }}",
"nested": {"inner_key": "{{ key1 }}"},
}
context.result = context.template_instance._apply_template_vars(
test_dict, test_params
)
except Exception as e:
context.error = e
@then("dictionary keys and values should be processed recursively")
def step_verify_dict_processing(context):
"""Verify dictionary processing."""
assert context.error is None
result = context.result
assert result["static_key"] == "static_value"
assert result["value1"] == "templated_key" # Key was templated
assert result["normal_key"] == "value2" # Value was templated
assert result["nested"]["inner_key"] == "value1" # Nested processing
@then("None values should be filtered out")
def step_verify_none_filtering(context):
"""Verify None values are filtered out."""
if hasattr(context, "results"):
assert context.results["excluded"] is None, (
"False condition block should return None"
)
else:
assert context.result is not None, (
"Result should not be None; None filtering means no None in output"
)
# BaseTemplate _apply_template_vars tests - conditional blocks
@given("I have a BaseTemplate instance with conditional blocks")
def step_template_conditional_blocks(context):
"""Create template with conditional blocks."""
context.template_instance = TestTemplate("test_template", TemplateType.AGENT, {})
@when("I apply template variables to conditional structures")
def step_apply_conditional_templates(context):
"""Apply template variables to conditional structures."""
try:
test_params = {"show_section": True, "hide_section": False}
# Test conditional block that should be included
conditional_true = {"{% if show_section %}": "content: included"}
# Test conditional block that should be excluded
conditional_false = {"{% if hide_section %}": "content: excluded"}
context.results = {
"included": context.template_instance._apply_template_vars(
conditional_true, test_params
),
"excluded": context.template_instance._apply_template_vars(
conditional_false, test_params
),
}
except Exception as e:
context.error = e
@then("conditional blocks should be evaluated correctly")
def step_verify_conditional_evaluation(context):
"""Verify conditional blocks are evaluated correctly."""
assert context.error is None
# The included condition should return parsed content
assert context.results["included"] is not None
@then("false conditions should return None")
def step_verify_false_conditions_none(context):
"""Verify false conditions return None."""
assert context.results["excluded"] is None
# BaseTemplate _apply_template_vars tests - list processing
@when("I apply template variables to list structures")
def step_apply_list_templates(context):
"""Apply template variables to list structures."""
try:
test_params = {"item1": "first", "item2": "second", "show": True, "hide": False}
test_list = [
"{{ item1 }}",
"{{ item2 }}",
{"{% if show %}": "included_item"},
{"{% if hide %}": "excluded_item"},
"static_item",
]
context.result = context.template_instance._apply_template_vars(
test_list, test_params
)
except Exception as e:
context.error = e
@then("list items should be processed recursively")
def step_verify_list_processing(context):
"""Verify list processing."""
assert context.error is None
result = context.result
assert "first" in result
assert "second" in result
assert "static_item" in result
@then("None values should be filtered from lists")
def step_verify_list_none_filtering(context):
"""Verify None values are filtered from lists."""
# None values from false conditions should be filtered out
assert None not in context.result
# BaseTemplate _apply_template_vars tests - error handling
@when("I apply template variables with invalid templates")
def step_apply_invalid_templates(context):
"""Apply template variables with invalid templates."""
try:
test_params = {"valid": "test"}
invalid_template = "{{ invalid_syntax }" # Missing closing }}
context.result = context.template_instance._apply_template_vars(
invalid_template, test_params
)
context.error_handled = True
except Exception as e:
context.error = e
@then("template errors should be handled gracefully")
def step_verify_error_handling(context):
"""Verify template errors are handled gracefully."""
assert hasattr(context, "error_handled")
assert context.error_handled is True
@then("original values should be returned on error")
def step_verify_original_on_error(context):
"""Verify original values are returned on error."""
assert context.result == "{{ invalid_syntax }"
# BaseTemplate _merge_params tests
@when("I merge parameter dictionaries with overrides")
def step_merge_params(context):
"""Merge parameter dictionaries with overrides."""
try:
base_params = {
"base_param": "base_value",
"override_me": "original",
"template_param": "{{ base_param }}",
}
override_params = {
"override_me": "overridden",
"new_param": "new_value",
"templated_override": "Value: {{ base_param }}",
}
context.result = context.template_instance._merge_params(
base_params, override_params
)
except Exception as e:
context.error = e
@then("parameters should be merged correctly")
def step_verify_params_merge(context):
"""Verify parameters are merged correctly."""
assert context.error is None
result = context.result
assert result["base_param"] == "base_value"
assert result["override_me"] == "overridden"
assert result["new_param"] == "new_value"
@then("override values should be template-processed")
def step_verify_override_template_processing(context):
"""Verify override values are template-processed."""
assert context.result["templated_override"] == "Value: base_value"
# Error conditions and edge cases
@given("I have various edge case scenarios")
def step_edge_case_scenarios(context):
"""Set up edge case scenarios."""
context.edge_cases = {
"empty_template": TestTemplate("empty", TemplateType.STREAM, {}),
"no_params_def": TestTemplate(
"no_params", TemplateType.GRAPH, {"config": "test"}
),
}
@when("I test error conditions")
def step_test_error_conditions(context):
"""Test various error conditions."""
context.errors = {}
# Test ComponentReference resolve with missing context method
try:
ref = ComponentReference("agent", "test")
mock_context = Mock()
mock_context.resolve_reference.side_effect = ValueError("Test error")
ref.resolve(mock_context)
except ValueError as e:
context.errors["resolve_error"] = e
@then("appropriate errors should be raised")
def step_verify_appropriate_errors(context):
"""Verify appropriate errors are raised."""
assert "resolve_error" in context.errors
assert isinstance(context.errors["resolve_error"], ValueError)
@then("edge cases should be handled correctly")
def step_verify_edge_cases(context):
"""Verify edge cases are handled correctly."""
# Empty template should work
empty_template = context.edge_cases["empty_template"]
assert empty_template.parameters == {}
# Template without parameters section should work
no_params = context.edge_cases["no_params_def"]
assert no_params.parameters == {}
@when("I instantiate a GenericTemplate whose applied vars produce a non-dict result")
def step_gt_nondict_result(context):
from unittest.mock import MagicMock
from cleveractors.templates.base import InstantiationContext, TemplateType
from cleveractors.templates.generic_template import GenericTemplate
gt = GenericTemplate(
name="test", template_type=TemplateType.ACTOR, definition={"k": "{{ v }}"}
)
gt._apply_template_vars = lambda d, p: ["non", "dict", "result"]
ctx = InstantiationContext()
context.gt_result = gt.instantiate(
params={"v": "x"}, registry=MagicMock(), context=ctx
)
@then("the result should be a dict with definition key wrapping the non-dict value")
def step_gt_assert_nondict_wrapped(context):
assert isinstance(context.gt_result, dict)
assert "definition" in context.gt_result