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

646 lines
25 KiB
Python

"""
Step definitions for deferred template coverage tests.
"""
from unittest.mock import Mock, patch
import yaml
from behave import given, then, when
from cleveragents.templates.deferred_template import (
DeferredTemplate,
apply_deferred_templates,
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