Files
temp/tests/features/steps/deferred_template_coverage_steps.py
T

688 lines
26 KiB
Python

"""
Step definitions for deferred template coverage tests.
"""
import copy
from unittest.mock import MagicMock
from unittest.mock import Mock
from unittest.mock import patch
import yaml
from behave import given
from behave import then
from behave import when
from cleveragents.templates.deferred_template import DeferredTemplate
from cleveragents.templates.deferred_template import apply_deferred_templates
from cleveragents.templates.deferred_template import process_template_definition
@given("I have a clean test environment for deferred templates")
def step_given_clean_test_environment(context):
"""Initialize clean test environment."""
# Initialize context attributes to avoid KeyError issues
# Note: context.result is already initialized by environment.py
if not hasattr(context, "deferred_template"):
context.deferred_template = None
if not hasattr(context, "template_string"):
context.template_string = None
if not hasattr(context, "template_context"):
context.template_context = {}
if not hasattr(context, "yaml_dict"):
context.yaml_dict = {}
if not hasattr(context, "definition"):
context.definition = {}
if not hasattr(context, "config"):
context.config = {}
if not hasattr(context, "rendered_result"):
context.rendered_result = None
if not hasattr(context, "target_key"):
context.target_key = None
if not hasattr(context, "mock_processor"):
context.mock_processor = None
if not hasattr(context, "mock_processor_class"):
context.mock_processor_class = None
if not hasattr(context, "mock_deferred"):
context.mock_deferred = None
if not hasattr(context, "mock_deferred_components"):
context.mock_deferred_components = None
if not hasattr(context, "mock_deferred_routing"):
context.mock_deferred_routing = None
@given("I have a simple template string with Jinja2 syntax")
def step_given_simple_template_string(context):
"""Create a simple template string."""
context.template_string = """
test_section:
name: "{{ test_name }}"
value: "{{ test_value }}"
"""
@given("I have a template string with template variables")
def step_given_template_string_with_variables(context):
"""Create a template string with variables."""
context.template_string = """
config:
agent_name: "{{ agent_name }}"
model: "{{ model_name }}"
temperature: {{ temperature }}
"""
@given("I have a complex template string with multiple variables")
def step_given_complex_template_string(context):
"""Create a complex template string with multiple variables."""
context.template_string = """
components:
- name: "{{ component1_name }}"
type: "{{ component1_type }}"
config:
setting1: "{{ setting1 }}"
setting2: {{ setting2 }}
- name: "{{ component2_name }}"
type: "{{ component2_type }}"
routing:
strategy: "{{ routing_strategy }}"
rules:
- condition: "{{ rule_condition }}"
target: "{{ rule_target }}"
"""
@given("I have a template context with variable values")
def step_given_template_context(context):
"""Create a template context."""
context.template_context = {
"agent_name": "test_agent",
"model_name": "gpt-4",
"temperature": 0.7,
}
@given("I have a comprehensive template context")
def step_given_comprehensive_template_context(context):
"""Create a comprehensive template context."""
context.template_context = {
"component1_name": "processor",
"component1_type": "llm",
"setting1": "value1",
"setting2": 42,
"component2_name": "validator",
"component2_type": "tool",
"routing_strategy": "round_robin",
"rule_condition": "input.type == 'query'",
"rule_target": "processor",
}
@given("I have a YAML dictionary without the target key")
def step_given_yaml_dict_without_key(context):
"""Create a YAML dictionary without the target key."""
context.yaml_dict = {
"other_section": {"value": "test"},
"another_section": "simple_value",
}
context.target_key = "missing_key"
@given("I have a YAML dictionary with a section without template syntax")
def step_given_yaml_dict_without_template_syntax(context):
"""Create a YAML dictionary with a section without template syntax."""
context.yaml_dict = {
"test_section": {"name": "static_name", "value": "static_value", "number": 123}
}
context.target_key = "test_section"
@given("I have a YAML dictionary with a section containing Jinja2 curly braces")
def step_given_yaml_dict_with_curly_braces(context):
"""Create a YAML dictionary with Jinja2 curly braces."""
context.yaml_dict = {
"template_section": {
"name": "{{ template_name }}",
"value": "{{ template_value }}",
"static": "not_template",
}
}
context.target_key = "template_section"
@given("I have a YAML dictionary with a section containing Jinja2 control structures")
def step_given_yaml_dict_with_control_structures(context):
"""Create a YAML dictionary with Jinja2 control structures."""
context.yaml_dict = {
"control_section": {
"items": "{% for item in items %}{{ item }}{% endfor %}",
"conditional": "{% if condition %}{{ value }}{% endif %}",
}
}
context.target_key = "control_section"
@given("I have a template definition without template sections")
def step_given_definition_without_template_sections(context):
"""Create a template definition without template sections."""
context.definition = {
"name": "test_template",
"description": "A test template",
"metadata": {"version": "1.0"},
"other_data": ["item1", "item2"],
}
@given("I have a template definition with sections but no template syntax")
def step_given_definition_with_sections_no_syntax(context):
"""Create a template definition with sections but no template syntax."""
context.definition = {
"name": "test_template",
"components": {"agent1": {"type": "llm", "model": "gpt-4"}},
"nodes": {"node1": {"type": "processor"}},
"routing": {"strategy": "sequential"},
}
@given("I have a template definition with components containing template syntax")
def step_given_definition_with_components_template_syntax(context):
"""Create a template definition with components containing template syntax."""
context.definition = {
"name": "test_template",
"components": {
"agent1": {
"type": "{{ agent_type }}",
"model": "{{ model_name }}",
"config": {"temperature": "{{ temperature }}"},
},
"agent2": {"type": "tool", "name": "{{ tool_name }}"},
},
"other_section": {"static": "value"},
}
@given("I have a template definition with nodes containing template syntax")
def step_given_definition_with_nodes_template_syntax(context):
"""Create a template definition with nodes containing template syntax."""
context.definition = {
"name": "test_template",
"nodes": {
"start_node": {"type": "{{ start_type }}", "config": "{{ start_config }}"},
"end_node": {
"condition": "{% if end_condition %}{{ end_value }}{% endif %}"
},
},
}
@given("I have a template definition with edges containing template syntax")
def step_given_definition_with_edges_template_syntax(context):
"""Create a template definition with edges containing template syntax."""
context.definition = {
"name": "test_template",
"edges": {
"edge1": {
"from": "{{ source_node }}",
"to": "{{ target_node }}",
"condition": "{% if edge_condition %}{{ edge_logic }}{% endif %}",
}
},
}
@given("I have a template definition with operators containing template syntax")
def step_given_definition_with_operators_template_syntax(context):
"""Create a template definition with operators containing template syntax."""
context.definition = {
"name": "test_template",
"operators": {
"op1": {"type": "{{ operator_type }}", "config": "{{ operator_config }}"}
},
}
@given("I have a template definition with routing containing template syntax")
def step_given_definition_with_routing_template_syntax(context):
"""Create a template definition with routing containing template syntax."""
context.definition = {
"name": "test_template",
"routing": {
"strategy": "{{ routing_strategy }}",
"rules": "{% for rule in routing_rules %}{{ rule }}{% endfor %}",
},
}
@given("I have a template definition with multiple sections containing template syntax")
def step_given_definition_with_multiple_sections_template_syntax(context):
"""Create a template definition with multiple sections containing template syntax."""
context.definition = {
"name": "test_template",
"components": {"agent1": {"type": "{{ agent_type }}"}},
"nodes": {"node1": {"config": "{{ node_config }}"}},
"edges": {"edge1": {"condition": "{% if condition %}{{ value }}{% endif %}"}},
"operators": {"op1": {"type": "{{ op_type }}"}},
"routing": {"strategy": "{{ strategy }}"},
"static_section": {"value": "no_template"},
}
@given("I have a configuration without deferred templates")
def step_given_config_without_deferred_templates(context):
"""Create a configuration without deferred templates."""
# Store in test data instead of context directly
context.test_config = {
"name": "test_config",
"components": {"agent1": {"type": "llm"}},
"routing": {"strategy": "sequential"},
}
context.test_template_context = {"unused": "value"}
@given("I have a configuration with deferred template markers")
def step_given_config_with_deferred_markers(context):
"""Create a configuration with deferred template markers."""
# Create mock DeferredTemplate instances
mock_deferred_components = Mock(spec=DeferredTemplate)
mock_deferred_routing = Mock(spec=DeferredTemplate)
# Set up render return values
mock_deferred_components.render.return_value = {
"components": {"agent1": {"type": "llm", "model": "gpt-4"}}
}
mock_deferred_routing.render.return_value = {"routing": {"strategy": "round_robin"}}
context.test_config = {
"name": "test_config",
"components": {"_deferred": True},
"_deferred_components": mock_deferred_components,
"routing": {"_deferred": True},
"_deferred_routing": mock_deferred_routing,
"static_section": {"value": "unchanged"},
}
context.mock_deferred_components = mock_deferred_components
context.mock_deferred_routing = mock_deferred_routing
@given("I have DeferredTemplate instances in the configuration")
def step_given_deferred_template_instances(context):
"""Ensure DeferredTemplate instances are properly set up."""
# This step is mainly for clarity - instances are set up in previous step
pass
@given("I have a generic template context")
def step_given_generic_template_context(context):
"""Create a generic template context."""
context.template_context = {"test_var": "test_value", "number_var": 42}
@given(
"I have a configuration with deferred templates that render to nested dictionaries"
)
def step_given_config_with_nested_rendered_content(context):
"""Create a configuration with deferred templates that render nested content."""
mock_deferred = Mock(spec=DeferredTemplate)
mock_deferred.render.return_value = {
"components": {
"nested_agent": {
"type": "llm",
"config": {"model": "gpt-4", "temperature": 0.7},
}
}
}
context.test_config = {
"name": "test_config",
"components": {"_deferred": True},
"_deferred_components": mock_deferred,
}
context.mock_deferred = mock_deferred
@given("I have a configuration with deferred templates that render to direct content")
def step_given_config_with_direct_rendered_content(context):
"""Create a configuration with deferred templates that render direct content."""
mock_deferred = Mock(spec=DeferredTemplate)
mock_deferred.render.return_value = {
"strategy": "direct_value",
"config": {"setting": "direct_setting"},
}
context.test_config = {
"name": "test_config",
"routing": {"_deferred": True},
"_deferred_routing": mock_deferred,
}
context.mock_deferred = mock_deferred
@when("I create a DeferredTemplate instance")
def step_when_create_deferred_template(context):
"""Create a DeferredTemplate instance."""
with patch(
"cleveragents.templates.deferred_template.YAMLTemplateProcessor"
) as mock_processor_class:
mock_processor = Mock()
mock_processor_class.return_value = mock_processor
context.mock_processor = mock_processor
context.mock_processor_class = mock_processor_class
context.deferred_template = DeferredTemplate(context.template_string)
@when("I render the deferred template with context")
def step_when_render_deferred_template(context):
"""Render the deferred template with context."""
with patch(
"cleveragents.templates.deferred_template.YAMLTemplateProcessor"
) as mock_processor_class:
mock_processor = Mock()
mock_processor_class.return_value = mock_processor
mock_processor.process_string.return_value = {"rendered": "content"}
context.deferred_template = DeferredTemplate(context.template_string)
context.rendered_result = context.deferred_template.render(
context.template_context
)
context.mock_processor = mock_processor
@when("I call from_yaml_section with the missing key")
def step_when_call_from_yaml_section_missing_key(context):
"""Call from_yaml_section with a missing key."""
context.result = DeferredTemplate.from_yaml_section(
context.yaml_dict, context.target_key
)
@when("I call from_yaml_section with that key")
def step_when_call_from_yaml_section_with_key(context):
"""Call from_yaml_section with the target key."""
context.result = DeferredTemplate.from_yaml_section(
context.yaml_dict, context.target_key
)
@when("I call process_template_definition")
def step_when_call_process_template_definition(context):
"""Call process_template_definition."""
context.result = process_template_definition(context.definition)
@when("I call apply_deferred_templates")
def step_when_call_apply_deferred_templates(context):
"""Call apply_deferred_templates."""
# Get config and template_context from either test_ prefixed attributes or regular attributes
config = getattr(context, "test_config", getattr(context, "config", {}))
template_context = getattr(
context, "test_template_context", getattr(context, "template_context", {})
)
context.result = apply_deferred_templates(config, template_context)
@then("the template should be stored correctly")
def step_then_template_stored_correctly(context):
"""Verify the template is stored correctly."""
assert context.deferred_template is not None
assert context.deferred_template.template_str == context.template_string
@then("the YAMLTemplateProcessor should be initialized")
def step_then_yaml_template_processor_initialized(context):
"""Verify YAMLTemplateProcessor is initialized."""
context.mock_processor_class.assert_called_once()
assert context.deferred_template.processor == context.mock_processor
@then("the template should be processed using YAMLTemplateProcessor")
def step_then_template_processed_using_processor(context):
"""Verify the template is processed using YAMLTemplateProcessor."""
context.mock_processor.process_string.assert_called_once_with(
context.template_string, context.template_context
)
@then("the result should contain the rendered content")
def step_then_result_contains_rendered_content(context):
"""Verify the result contains rendered content."""
assert context.rendered_result == {"rendered": "content"}
@then("the template should be fully processed")
def step_then_template_fully_processed(context):
"""Verify the template is fully processed."""
assert context.rendered_result is not None
context.mock_processor.process_string.assert_called_once()
@then("all variables should be substituted correctly")
def step_then_variables_substituted_correctly(context):
"""Verify all variables are substituted correctly."""
# The mock processor was called with the correct context
context.mock_processor.process_string.assert_called_with(
context.template_string, context.template_context
)
@then("it should return None")
def step_then_should_return_none(context):
"""Verify the result is None."""
assert context.result is None
@then("it should return a DeferredTemplate instance")
def step_then_should_return_deferred_template(context):
"""Verify a DeferredTemplate instance is returned."""
assert isinstance(context.result, DeferredTemplate)
@then("the template string should contain the YAML section")
def step_then_template_string_contains_yaml_section(context):
"""Verify the template string contains the YAML section."""
expected_yaml = yaml.dump(
{context.target_key: context.yaml_dict[context.target_key]},
default_flow_style=False,
)
assert context.result.template_str == expected_yaml
@then("the template string should contain the control structures")
def step_then_template_string_contains_control_structures(context):
"""Verify the template string contains control structures."""
assert (
"{% for" in context.result.template_str
or "{% if" in context.result.template_str
)
expected_yaml = yaml.dump(
{context.target_key: context.yaml_dict[context.target_key]},
default_flow_style=False,
)
assert context.result.template_str == expected_yaml
@then("it should return the original definition unchanged")
def step_then_return_original_definition_unchanged(context):
"""Verify the original definition is returned unchanged."""
assert context.result == context.definition
# Verify it's not the same object (should be a copy)
assert context.result is not context.definition
@then("it should create a deferred template for components")
def step_then_create_deferred_template_components(context):
"""Verify a deferred template is created for components."""
assert "_deferred_components" in context.result
assert isinstance(context.result["_deferred_components"], DeferredTemplate)
@then("the original components should be replaced with a placeholder")
def step_then_components_replaced_with_placeholder(context):
"""Verify components are replaced with placeholder."""
assert context.result["components"] == {"_deferred": True}
@then("the deferred template should be stored with the correct key")
def step_then_deferred_template_stored_correct_key(context):
"""Verify the deferred template is stored with correct key."""
assert "_deferred_components" in context.result
deferred_template = context.result["_deferred_components"]
# Verify the template contains component names and template syntax
assert (
"agent1" in deferred_template.template_str
or "agent2" in deferred_template.template_str
)
assert (
"{{" in deferred_template.template_str
and "}}" in deferred_template.template_str
)
@then("it should create a deferred template for nodes")
def step_then_create_deferred_template_nodes(context):
"""Verify a deferred template is created for nodes."""
assert "_deferred_nodes" in context.result
assert isinstance(context.result["_deferred_nodes"], DeferredTemplate)
@then("the original nodes should be replaced with a placeholder")
def step_then_nodes_replaced_with_placeholder(context):
"""Verify nodes are replaced with placeholder."""
assert context.result["nodes"] == {"_deferred": True}
@then("it should create a deferred template for edges")
def step_then_create_deferred_template_edges(context):
"""Verify a deferred template is created for edges."""
assert "_deferred_edges" in context.result
assert isinstance(context.result["_deferred_edges"], DeferredTemplate)
@then("the original edges should be replaced with a placeholder")
def step_then_edges_replaced_with_placeholder(context):
"""Verify edges are replaced with placeholder."""
assert context.result["edges"] == {"_deferred": True}
@then("it should create a deferred template for operators")
def step_then_create_deferred_template_operators(context):
"""Verify a deferred template is created for operators."""
assert "_deferred_operators" in context.result
assert isinstance(context.result["_deferred_operators"], DeferredTemplate)
@then("the original operators should be replaced with a placeholder")
def step_then_operators_replaced_with_placeholder(context):
"""Verify operators are replaced with placeholder."""
assert context.result["operators"] == {"_deferred": True}
@then("it should create a deferred template for routing")
def step_then_create_deferred_template_routing(context):
"""Verify a deferred template is created for routing."""
assert "_deferred_routing" in context.result
assert isinstance(context.result["_deferred_routing"], DeferredTemplate)
@then("the original routing should be replaced with a placeholder")
def step_then_routing_replaced_with_placeholder(context):
"""Verify routing is replaced with placeholder."""
assert context.result["routing"] == {"_deferred": True}
@then("it should create deferred templates for all applicable sections")
def step_then_create_deferred_templates_all_sections(context):
"""Verify deferred templates are created for all applicable sections."""
expected_sections = ["components", "nodes", "edges", "operators", "routing"]
for section in expected_sections:
assert f"_deferred_{section}" in context.result
assert isinstance(context.result[f"_deferred_{section}"], DeferredTemplate)
@then("all original sections should be replaced with placeholders")
def step_then_all_sections_replaced_with_placeholders(context):
"""Verify all original sections are replaced with placeholders."""
expected_sections = ["components", "nodes", "edges", "operators", "routing"]
for section in expected_sections:
assert context.result[section] == {"_deferred": True}
@then("the deferred templates should be rendered")
def step_then_deferred_templates_rendered(context):
"""Verify deferred templates are rendered."""
context.mock_deferred_components.render.assert_called_once_with(
context.template_context
)
context.mock_deferred_routing.render.assert_called_once_with(
context.template_context
)
@then("the deferred markers should be removed")
def step_then_deferred_markers_removed(context):
"""Verify deferred markers are removed."""
assert "_deferred_components" not in context.result
assert "_deferred_routing" not in context.result
@then("the rendered content should replace the placeholders")
def step_then_rendered_content_replaces_placeholders(context):
"""Verify rendered content replaces placeholders."""
expected_components = {"agent1": {"type": "llm", "model": "gpt-4"}}
expected_routing = {"strategy": "round_robin"}
assert context.result["components"] == expected_components
assert context.result["routing"] == expected_routing
assert context.result["static_section"] == {"value": "unchanged"}
@then("the nested content should be extracted correctly")
def step_then_nested_content_extracted_correctly(context):
"""Verify nested content is extracted correctly."""
context.mock_deferred.render.assert_called_once_with(context.template_context)
expected_components = {
"nested_agent": {
"type": "llm",
"config": {"model": "gpt-4", "temperature": 0.7},
}
}
assert context.result["components"] == expected_components
@then("the actual section should be populated with the nested content")
def step_then_actual_section_populated_with_nested_content(context):
"""Verify the actual section is populated with nested content."""
# Verify the _deferred marker is removed
assert "_deferred_components" not in context.result
# Verify the components section has the nested content
assert "nested_agent" in context.result["components"]
@then("the direct content should be used as-is")
def step_then_direct_content_used_as_is(context):
"""Verify direct content is used as-is."""
context.mock_deferred.render.assert_called_once_with(context.template_context)
expected_routing = {
"strategy": "direct_value",
"config": {"setting": "direct_setting"},
}
assert context.result["routing"] == expected_routing
@then("the configuration should be updated correctly")
def step_then_configuration_updated_correctly(context):
"""Verify the configuration is updated correctly."""
# Verify the _deferred marker is removed
assert "_deferred_routing" not in context.result
# Verify the routing section has the direct content
assert context.result["routing"]["strategy"] == "direct_value"
assert context.result["routing"]["config"]["setting"] == "direct_setting"
@then("it should return the original configuration unchanged")
def step_then_return_original_configuration_unchanged(context):
"""Verify the original configuration is returned unchanged."""
config = getattr(context, "test_config", getattr(context, "config", {}))
assert context.result == config
# Verify it's not the same object (should be a copy)
assert context.result is not config