forked from HAL9000/cleveragents-core
519 lines
18 KiB
Python
519 lines
18 KiB
Python
"""Step definitions for YAML template engine coverage gaps."""
|
|
|
|
import json
|
|
from typing import Any
|
|
from typing import Dict
|
|
from typing import List
|
|
|
|
import yaml
|
|
from behave import given
|
|
from behave import then
|
|
from behave import when
|
|
|
|
from cleveragents.templates.yaml_template_engine import YAMLTemplateEngine
|
|
|
|
|
|
@given("I have YAML content that triggers postprocessing line splitting")
|
|
def step_yaml_postprocessing_line_splitting(context):
|
|
"""Create YAML content that triggers line splitting."""
|
|
context.yaml_content = """config:
|
|
combined: value1 key2: value2
|
|
normal: single_value"""
|
|
|
|
|
|
@when("I test the postprocessing method directly")
|
|
def step_test_postprocessing_directly(context):
|
|
"""Test postprocessing method directly."""
|
|
context.processed = context.yaml_engine._postprocess_rendered_yaml(
|
|
context.yaml_content
|
|
)
|
|
|
|
|
|
@then("the line splitting logic should be executed")
|
|
def step_check_line_splitting_executed(context):
|
|
"""Check line splitting logic is executed."""
|
|
# The method should process the content (may or may not change it)
|
|
assert isinstance(context.processed, str)
|
|
|
|
|
|
@then("problematic lines should be restructured")
|
|
def step_check_problematic_lines_restructured(context):
|
|
"""Check problematic lines are restructured."""
|
|
# Lines with multiple values should be processed
|
|
lines = context.processed.split("\n")
|
|
assert len(lines) >= 2
|
|
|
|
|
|
@given("I have YAML with multiple colons that need fixing")
|
|
def step_yaml_multiple_colons_fixing(context):
|
|
"""Create YAML with multiple colons."""
|
|
context.yaml_content = """problematic:
|
|
key1: value1: key2: value2: key3: value3
|
|
normal_key: normal_value"""
|
|
|
|
|
|
@when("I test the fix common issues method directly")
|
|
def step_test_fix_common_issues_directly(context):
|
|
"""Test fix common issues method directly."""
|
|
context.fixed = context.yaml_engine._fix_common_yaml_issues(context.yaml_content)
|
|
|
|
|
|
@then("multiple colon lines should be processed")
|
|
def step_check_multiple_colon_lines_processed(context):
|
|
"""Check multiple colon lines are processed."""
|
|
# Should attempt to fix multiple colon issues
|
|
assert isinstance(context.fixed, str)
|
|
lines = context.fixed.split("\n")
|
|
assert len(lines) >= 2
|
|
|
|
|
|
@then("tokens should be properly separated")
|
|
def step_check_tokens_separated(context):
|
|
"""Check tokens are properly separated."""
|
|
# Fixed version should handle token separation
|
|
assert len(context.fixed) > 0
|
|
|
|
|
|
@then("indentation should be maintained")
|
|
def step_check_indentation_maintained(context):
|
|
"""Check indentation is maintained."""
|
|
lines = context.fixed.split("\n")
|
|
has_indentation = any(line.startswith(" ") for line in lines if line.strip())
|
|
# Should maintain some form of structure
|
|
assert has_indentation or len([l for l in lines if l.strip()]) <= 2
|
|
|
|
|
|
@given("I have data with None values for sum filter")
|
|
def step_data_none_values_sum_filter(context):
|
|
"""Create data with None values for sum filter."""
|
|
context.test_data = [1, 2, None, 4, None, 5]
|
|
|
|
|
|
@when("I test the sum filter directly with None values")
|
|
def step_test_sum_filter_none_values(context):
|
|
"""Test sum filter directly with None values."""
|
|
context.sum_result = context.yaml_engine._sum_filter(context.test_data)
|
|
|
|
|
|
@then("None values should be filtered out by sum filter")
|
|
def step_check_none_values_filtered(context):
|
|
"""Check None values are filtered out."""
|
|
# Sum should be 1+2+4+5 = 12 (None values filtered)
|
|
assert context.sum_result == 12
|
|
|
|
|
|
@then("remaining values should be summed correctly")
|
|
def step_check_remaining_values_summed(context):
|
|
"""Check remaining values are summed correctly."""
|
|
assert context.sum_result == 12
|
|
|
|
|
|
@given("I have objects with missing attributes for sum filter")
|
|
def step_objects_missing_attributes_sum(context):
|
|
"""Create objects with missing attributes."""
|
|
context.test_objects = [
|
|
{"value": 10},
|
|
{"value": 20},
|
|
{"name": "no_value"}, # Missing 'value' attribute
|
|
{"value": 30},
|
|
]
|
|
|
|
|
|
@when("I test the sum filter with attribute parameter")
|
|
def step_test_sum_filter_attribute_param(context):
|
|
"""Test sum filter with attribute parameter."""
|
|
context.sum_attr_result = context.yaml_engine._sum_filter(
|
|
context.test_objects, "value"
|
|
)
|
|
|
|
|
|
@then("missing attributes should be treated as zero")
|
|
def step_check_missing_attributes_zero(context):
|
|
"""Check missing attributes are treated as zero."""
|
|
# Should sum 10+20+0+30 = 60
|
|
assert context.sum_attr_result == 60
|
|
|
|
|
|
@then("available attributes should be summed")
|
|
def step_check_available_attributes_summed(context):
|
|
"""Check available attributes are summed."""
|
|
assert context.sum_attr_result == 60
|
|
|
|
|
|
@given("I have data for testing all selectattr operators")
|
|
def step_data_all_selectattr_operators(context):
|
|
"""Create data for testing all selectattr operators."""
|
|
context.selectattr_data = [
|
|
{"score": 95, "status": "active", "level": "A"},
|
|
{"score": 45, "status": "inactive", "level": "B"},
|
|
{"score": 80, "status": "active", "level": "A"},
|
|
{"score": 50, "status": "pending", "level": "C"},
|
|
{"score": 85, "status": "active", "level": "A"},
|
|
]
|
|
|
|
|
|
@when("I test selectattr filter with greater than operator")
|
|
def step_test_selectattr_greater_than(context):
|
|
"""Test selectattr with greater than operator."""
|
|
context.gt_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "score", ">", 80
|
|
)
|
|
|
|
|
|
@when("I test selectattr filter with less than operator")
|
|
def step_test_selectattr_less_than(context):
|
|
"""Test selectattr with less than operator."""
|
|
context.lt_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "score", "<", 60
|
|
)
|
|
|
|
|
|
@when("I test selectattr filter with greater equal operator")
|
|
def step_test_selectattr_greater_equal(context):
|
|
"""Test selectattr with greater equal operator."""
|
|
context.ge_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "score", ">=", 80
|
|
)
|
|
|
|
|
|
@when("I test selectattr filter with less equal operator")
|
|
def step_test_selectattr_less_equal(context):
|
|
"""Test selectattr with less equal operator."""
|
|
context.le_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "score", "<=", 50
|
|
)
|
|
|
|
|
|
@when("I test selectattr filter with not equal operator")
|
|
def step_test_selectattr_not_equal(context):
|
|
"""Test selectattr with not equal operator."""
|
|
context.ne_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "status", "!=", "active"
|
|
)
|
|
|
|
|
|
@when("I test selectattr filter with unknown operator")
|
|
def step_test_selectattr_unknown_operator(context):
|
|
"""Test selectattr with unknown operator."""
|
|
context.unknown_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_data, "score", "unknown_op", 50
|
|
)
|
|
|
|
|
|
@then("all operators should work correctly")
|
|
def step_check_all_operators_work(context):
|
|
"""Check all operators work correctly."""
|
|
assert len(context.gt_result) == 2 # scores > 80: 95, 85
|
|
assert len(context.lt_result) == 2 # scores < 60: 45, 50
|
|
assert len(context.ge_result) == 3 # scores >= 80: 95, 80, 85
|
|
assert len(context.le_result) == 2 # scores <= 50: 45, 50
|
|
assert len(context.ne_result) == 2 # status != 'active': inactive, pending
|
|
|
|
|
|
@then("unknown operators should default to equality")
|
|
def step_check_unknown_operators_default(context):
|
|
"""Check unknown operators default to equality."""
|
|
assert len(context.unknown_result) == 1 # score == 50: only one
|
|
|
|
|
|
@given("I have objects with missing attributes for selectattr")
|
|
def step_objects_missing_attributes_selectattr(context):
|
|
"""Create objects with missing attributes."""
|
|
context.selectattr_missing_data = [
|
|
{"name": "obj1", "score": 85},
|
|
{"name": "obj2"}, # Missing score
|
|
{"name": "obj3", "score": None}, # None score
|
|
{"name": "obj4", "score": 90},
|
|
]
|
|
|
|
|
|
@when("I test selectattr filter on missing attributes")
|
|
def step_test_selectattr_missing_attributes(context):
|
|
"""Test selectattr filter on missing attributes."""
|
|
# Test with attribute that doesn't exist on all objects
|
|
context.missing_attr_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_missing_data, "missing_attr", "==", "value"
|
|
)
|
|
|
|
# Test filtering on score attribute where some objects don't have it
|
|
context.score_filter_result = context.yaml_engine._selectattr_filter(
|
|
context.selectattr_missing_data, "score", ">", 80
|
|
)
|
|
|
|
|
|
@then("objects without the attribute should be handled gracefully")
|
|
def step_check_objects_without_attribute_handled(context):
|
|
"""Check objects without attribute are handled gracefully."""
|
|
assert len(context.missing_attr_result) == 0 # No objects have 'missing_attr'
|
|
|
|
|
|
@then("None values should be properly filtered")
|
|
def step_check_none_values_properly_filtered(context):
|
|
"""Check None values are properly filtered."""
|
|
# Should only include objects with score > 80 and not None
|
|
assert len(context.score_filter_result) == 2 # obj1(85) and obj4(90)
|
|
|
|
|
|
@given("I have YAML with various template blocks")
|
|
def step_yaml_various_template_blocks(context):
|
|
"""Create YAML with various template blocks."""
|
|
context.yaml_content = """root:
|
|
static_key: static_value
|
|
{% for item in items %}
|
|
{{ item.name }}:
|
|
value: {{ item.value }}
|
|
{% if item.enabled %}
|
|
enabled_feature: true
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% macro test_macro() %}
|
|
macro_content: value
|
|
{% endmacro %}
|
|
inline_template: {{ simple_var }}"""
|
|
|
|
|
|
@when("I analyze the YAML structure for templates")
|
|
def step_analyze_yaml_structure_templates(context):
|
|
"""Analyze YAML structure for templates."""
|
|
context.structure = context.yaml_engine._analyze_yaml_structure(
|
|
context.yaml_content
|
|
)
|
|
|
|
|
|
@then("template blocks should be identified correctly")
|
|
def step_check_template_blocks_identified(context):
|
|
"""Check template blocks are identified correctly."""
|
|
assert "template_blocks" in context.structure
|
|
blocks = context.structure["template_blocks"]
|
|
assert len(blocks) >= 2 # Should find for, if, and possibly macro blocks
|
|
|
|
|
|
@then("block types should be detected")
|
|
def step_check_block_types_detected(context):
|
|
"""Check block types are detected."""
|
|
blocks = context.structure["template_blocks"]
|
|
block_types = [block.get("type") for block in blocks]
|
|
assert "for" in block_types
|
|
|
|
|
|
@then("block boundaries should be found")
|
|
def step_check_block_boundaries_found(context):
|
|
"""Check block boundaries are found."""
|
|
blocks = context.structure["template_blocks"]
|
|
for block in blocks:
|
|
assert "start_line" in block
|
|
assert "end_line" in block
|
|
assert block["start_line"] < block["end_line"]
|
|
|
|
|
|
@given("I have template blocks with varying indentations")
|
|
def step_template_blocks_varying_indentations(context):
|
|
"""Create template blocks with varying indentations."""
|
|
context.yaml_lines = [
|
|
"section1:",
|
|
" {% for item in items %}",
|
|
" subsection:",
|
|
" value: {{ item.value }}",
|
|
" {% endfor %}",
|
|
"section2:",
|
|
" {% if condition %}",
|
|
" deep_section:",
|
|
" nested: value",
|
|
" {% endif %}",
|
|
]
|
|
|
|
|
|
@when("I find block end boundaries")
|
|
def step_find_block_end_boundaries(context):
|
|
"""Find block end boundaries."""
|
|
context.block_ends = []
|
|
for i, line in enumerate(context.yaml_lines):
|
|
if (
|
|
"{%" in line
|
|
and any(kw in line for kw in ["for", "if"])
|
|
and "end" not in line
|
|
):
|
|
indent = len(line) - len(line.lstrip())
|
|
end_line = context.yaml_engine._find_block_end(
|
|
context.yaml_lines, i, indent
|
|
)
|
|
context.block_ends.append((i, end_line, indent))
|
|
|
|
|
|
@then("end markers should respect indentation levels")
|
|
def step_check_end_markers_respect_indentation(context):
|
|
"""Check end markers respect indentation levels."""
|
|
for start, end, indent in context.block_ends:
|
|
assert end > start
|
|
# Verify the end line has proper end marker
|
|
end_line = context.yaml_lines[end] if end < len(context.yaml_lines) else ""
|
|
if end_line.strip():
|
|
# Should contain end marker or be at proper indentation
|
|
assert indent >= 0
|
|
|
|
|
|
@then("nested blocks should be handled correctly")
|
|
def step_check_nested_blocks_handled(context):
|
|
"""Check nested blocks are handled correctly."""
|
|
# Should find at least 2 blocks at different indentation levels
|
|
indents = [indent for _, _, indent in context.block_ends]
|
|
assert len(set(indents)) >= 1 # Different indentation levels
|
|
|
|
|
|
@given("I have complex templates that may fail extraction")
|
|
def step_complex_templates_fail_extraction(context):
|
|
"""Create complex templates that may fail extraction."""
|
|
context.complex_yaml = """
|
|
complex:
|
|
{% macro test_macro(param) %}
|
|
{{ param }}: value
|
|
{% endmacro %}
|
|
{% for item in items %}
|
|
{{ item }}: {{ test_macro(item) }}
|
|
{% endfor %}
|
|
nested:
|
|
{% if complex_condition %}
|
|
{% for sub in sub_items %}
|
|
{{ sub.name }}: {{ sub.value }}
|
|
{% endfor %}
|
|
{% endif %}
|
|
"""
|
|
|
|
|
|
@when("I attempt complex template extraction")
|
|
def step_attempt_complex_extraction(context):
|
|
"""Attempt complex template extraction."""
|
|
context.extracted = context.yaml_engine._prepare_for_deferred_rendering(
|
|
context.complex_yaml
|
|
)
|
|
|
|
|
|
@then("fallback to simple extraction should occur")
|
|
def step_check_fallback_simple_extraction(context):
|
|
"""Check fallback to simple extraction occurs."""
|
|
# Should result in simple template format
|
|
assert isinstance(context.extracted, dict)
|
|
assert "_raw_template" in context.extracted or "_is_template" in context.extracted
|
|
|
|
|
|
@then("templates should be marked for deferred rendering")
|
|
def step_check_templates_marked_deferred(context):
|
|
"""Check templates are marked for deferred rendering."""
|
|
assert "_is_template" in context.extracted
|
|
assert context.extracted["_is_template"] is True
|
|
|
|
|
|
@given("I have stored template structures with various types")
|
|
def step_stored_template_structures_various_types(context):
|
|
"""Create stored template structures with various types."""
|
|
context.stored_structures = [
|
|
# Template with block content
|
|
{
|
|
"section": {
|
|
"_template_content": "{% for item in items %}\n{{ item.name }}: {{ item.value }}\n{% endfor %}",
|
|
"_template_type": "block",
|
|
}
|
|
},
|
|
# Template with inline content
|
|
{
|
|
"config": {
|
|
"_template_value": "{{ config_value }}",
|
|
"_template_type": "inline",
|
|
}
|
|
},
|
|
# Regular nested structure
|
|
{
|
|
"nested": {
|
|
"key1": "value1",
|
|
"key2": None,
|
|
"subsection": {"inner": "inner_value"},
|
|
},
|
|
"list_data": ["item1", "item2", {"nested_item": "value"}],
|
|
},
|
|
]
|
|
|
|
|
|
@when("I reconstruct YAML from the structures")
|
|
def step_reconstruct_yaml_structures(context):
|
|
"""Reconstruct YAML from structures."""
|
|
context.reconstructed = []
|
|
for structure in context.stored_structures:
|
|
reconstructed = context.yaml_engine._reconstruct_from_structure(structure)
|
|
context.reconstructed.append(reconstructed)
|
|
|
|
|
|
@then("template blocks should be restored")
|
|
def step_check_template_blocks_restored(context):
|
|
"""Check template blocks are restored."""
|
|
# First structure should have template block restored
|
|
first_reconstructed = context.reconstructed[0]
|
|
assert "{% for item in items %}" in first_reconstructed
|
|
|
|
|
|
@then("inline templates should be placed correctly")
|
|
def step_check_inline_templates_placed(context):
|
|
"""Check inline templates are placed correctly."""
|
|
# Second structure should have inline template
|
|
second_reconstructed = context.reconstructed[1]
|
|
assert "{{ config_value }}" in second_reconstructed
|
|
|
|
|
|
@then("nested structures should be reconstructed")
|
|
def step_check_nested_structures_reconstructed(context):
|
|
"""Check nested structures are reconstructed."""
|
|
# Third structure should have nested content
|
|
third_reconstructed = context.reconstructed[2]
|
|
assert "nested:" in third_reconstructed
|
|
assert "key1:" in third_reconstructed
|
|
assert "list_data:" in third_reconstructed
|
|
|
|
|
|
@given("I have templates in raw and structured formats")
|
|
def step_templates_raw_structured_formats(context):
|
|
"""Create templates in different formats."""
|
|
context.raw_template = {
|
|
"_raw_template": "config:\n name: {{ name }}\n value: {{ value }}",
|
|
"_is_template": True,
|
|
}
|
|
|
|
context.structured_template = {
|
|
"config": {
|
|
"_template_content": "name: {{ name }}\nvalue: {{ value }}",
|
|
"_template_type": "block",
|
|
}
|
|
}
|
|
|
|
|
|
@when("I render templates from both formats")
|
|
def step_render_templates_both_formats(context):
|
|
"""Render templates from both formats."""
|
|
render_context = {"name": "test_name", "value": "test_value"}
|
|
|
|
context.raw_rendered = context.yaml_engine.render_template(
|
|
context.raw_template, render_context
|
|
)
|
|
|
|
context.structured_rendered = context.yaml_engine.render_template(
|
|
context.structured_template, render_context
|
|
)
|
|
|
|
|
|
@then("both formats should be supported")
|
|
def step_check_both_formats_supported(context):
|
|
"""Check both formats are supported."""
|
|
assert isinstance(context.raw_rendered, dict)
|
|
assert isinstance(context.structured_rendered, dict)
|
|
|
|
|
|
@then("template variables should be substituted correctly")
|
|
def step_check_template_variables_substituted_correctly(context):
|
|
"""Check template variables are substituted correctly."""
|
|
# Raw template should have config section with substituted values
|
|
if "config" in context.raw_rendered:
|
|
config = context.raw_rendered["config"]
|
|
assert config["name"] == "test_name"
|
|
assert config["value"] == "test_value"
|
|
|
|
# Structured template should also have proper values
|
|
assert isinstance(context.structured_rendered, dict)
|