Files
temp/tests/unit/templates/test_inline_jinja_handler.py

648 lines
19 KiB
Python

"""
Unit tests for inline_jinja_handler module.
"""
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
import yaml
from cleveragents.templates.inline_jinja_handler import InlineJinjaHandler
class TestInlineJinjaHandler:
"""Test cases for InlineJinjaHandler class."""
def test_init(self):
"""Test InlineJinjaHandler initialization."""
handler = InlineJinjaHandler()
assert handler.env is not None
assert handler.env.block_start_string == "{%"
assert handler.env.block_end_string == "%}"
assert handler.env.variable_start_string == "{{"
assert handler.env.variable_end_string == "}}"
def test_process_yaml_file_without_templates(self):
"""Test processing YAML file without templates."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
value: 123
items:
- one
- two
"""
with tempfile.NamedTemporaryFile(mode="w", encoding='utf-8', prefix='cleveragent_', suffix=".yaml", delete=False) as f:
f.write(yaml_content)
temp_path = Path(f.name)
try:
result = handler.process_yaml_file(temp_path)
assert result["name"] == "test"
assert result["value"] == 123
assert len(result["items"]) == 2
finally:
temp_path.unlink()
def test_process_yaml_string_without_templates(self):
"""Test processing YAML string without templates."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
value: 123
"""
result = handler.process_yaml_string(yaml_content)
assert result["name"] == "test"
assert result["value"] == 123
def test_process_yaml_string_with_inline_template_defer(self):
"""Test processing YAML with inline template (deferred)."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
greeting: {{ message }}
"""
result = handler.process_yaml_string(yaml_content, defer_rendering=True)
assert "__templates__" in result
# Check that template placeholder was created
greeting_value = result["greeting"]
assert greeting_value.startswith("__template_") and greeting_value.endswith("__")
def test_process_yaml_string_with_inline_template_render(self):
"""Test processing YAML with inline template (immediate rendering)."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
greeting: Hello {{ name }}!
"""
context = {"name": "World"}
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context=context)
assert result["name"] == "test"
assert result["greeting"] == "Hello World!"
def test_process_yaml_string_with_for_loop_defer(self):
"""Test processing YAML with for loop (deferred)."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
items:
{% for i in range(3) %}
item_{{ i }}: value_{{ i }}
{% endfor %}
"""
result = handler.process_yaml_string(yaml_content, defer_rendering=True)
assert "__templates__" in result
assert "items" in result
def test_process_yaml_string_with_for_loop_render(self):
"""Test processing YAML with for loop (immediate rendering)."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
items:
{% for i in range(3) %}
- item{{ i }}
{% endfor %}
"""
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context={})
assert result["name"] == "test"
assert len(result["items"]) == 3
def test_process_yaml_string_with_if_block_render(self):
"""Test processing YAML with if block (immediate rendering)."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
{% if enabled %}
feature: active
{% endif %}
"""
context = {"enabled": True}
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context=context)
assert result["name"] == "test"
assert result["feature"] == "active"
def test_process_yaml_string_with_if_block_false_render(self):
"""Test processing YAML with if block when condition is false."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
{% if enabled %}
feature: active
{% endif %}
"""
context = {"enabled": False}
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context=context)
assert result["name"] == "test"
assert "feature" not in result
def test_extract_templates_with_inline_variable(self):
"""Test template extraction with inline variable."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
message: {{ greeting }}
value: 123
"""
result = handler._extract_templates(yaml_content)
assert "__templates__" in result
assert result["name"] == "test"
assert result["value"] == 123
# Check template was extracted
templates = result["__templates__"]
assert any(t["type"] == "inline" for t in templates.values())
def test_extract_templates_with_block_template(self):
"""Test template extraction with block template."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
items:
{% for i in range(3) %}
item{{ i }}: value{{ i }}
{% endfor %}
"""
result = handler._extract_templates(yaml_content)
assert "__templates__" in result
templates = result["__templates__"]
assert any(t["type"] == "block" for t in templates.values())
def test_extract_templates_with_nested_structure(self):
"""Test template extraction with nested structure."""
handler = InlineJinjaHandler()
yaml_content = """
root:
level1:
name: {{ name }}
value: {{ value }}
"""
result = handler._extract_templates(yaml_content)
assert result is not None
if "__templates__" in result:
assert len(result["__templates__"]) > 0
def test_render_templates_with_context(self):
"""Test rendering templates with context."""
handler = InlineJinjaHandler()
yaml_content = """
name: {{ app_name }}
version: {{ version }}
"""
context = {"app_name": "MyApp", "version": "1.0"}
result = handler._render_templates(yaml_content, context)
assert result["name"] == "MyApp"
assert result["version"] == 1.0 # YAML parses "1.0" as float
def test_render_templates_with_builtin_functions(self):
"""Test rendering templates with built-in functions."""
handler = InlineJinjaHandler()
yaml_content = """
count: {{ len(items) }}
first: {{ items[0] }}
"""
context = {"items": ["a", "b", "c"]}
result = handler._render_templates(yaml_content, context)
assert result["count"] == 3
assert result["first"] == "a"
def test_apply_templates_without_templates(self):
"""Test applying templates to config without templates."""
handler = InlineJinjaHandler()
config = {"name": "test", "value": 123}
context = {"var": "value"}
result = handler.apply_templates(config, context)
assert result == config
def test_apply_templates_with_inline_template(self):
"""Test applying templates with inline template."""
handler = InlineJinjaHandler()
# First extract templates
yaml_content = """
name: test
message: {{ greeting }}
"""
config = handler._extract_templates(yaml_content)
# Then apply templates
context = {"greeting": "Hello!"}
result = handler.apply_templates(config, context)
assert result["name"] == "test"
assert result["message"] == "Hello!"
def test_apply_templates_with_block_template(self):
"""Test applying templates with block template."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
items:
{% for i in range(2) %}
item{{ i }}: value{{ i }}
{% endfor %}
"""
config = handler._extract_templates(yaml_content)
context = {}
result = handler.apply_templates(config, context)
assert result["name"] == "test"
# Block templates should be rendered
assert "items" in result or "item0" in result
def test_apply_templates_recursive_dict(self):
"""Test recursive template application in nested dict."""
handler = InlineJinjaHandler()
templates = {
"__template_abc123__": {
"type": "inline",
"key": "value",
"content": "{{ var }}",
"indent": 0,
}
}
data = {
"outer": {
"inner": {"value": "__template_abc123__"},
}
}
context = {"var": "rendered_value"}
result = handler._apply_templates_recursive(data, templates, context)
assert result["outer"]["inner"]["value"] == "rendered_value"
def test_apply_templates_recursive_list(self):
"""Test recursive template application in list."""
handler = InlineJinjaHandler()
templates = {
"__template_abc123__": {
"type": "inline",
"key": "item",
"content": "{{ var }}",
"indent": 0,
}
}
data = ["__template_abc123__", "regular_value"]
context = {"var": "rendered"}
result = handler._apply_templates_recursive(data, templates, context)
# Should process the list
assert isinstance(result, list)
def test_render_template_string(self):
"""Test rendering a single template string."""
handler = InlineJinjaHandler()
template_str = "Hello {{ name }}!"
context = {"name": "World"}
result = handler._render_template_string(template_str, context)
assert result == "Hello World!"
def test_render_template_string_with_utilities(self):
"""Test rendering template string with utility functions."""
handler = InlineJinjaHandler()
template_str = "Count: {{ len(items) }}"
context = {"items": [1, 2, 3]}
result = handler._render_template_string(template_str, context)
assert result == "Count: 3"
def test_parse_rendered_value_string(self):
"""Test parsing rendered value as string."""
handler = InlineJinjaHandler()
result = handler._parse_rendered_value("simple string")
assert result == "simple string"
def test_parse_rendered_value_number(self):
"""Test parsing rendered value as number."""
handler = InlineJinjaHandler()
result = handler._parse_rendered_value("123")
assert result == 123
def test_parse_rendered_value_list(self):
"""Test parsing rendered value as list."""
handler = InlineJinjaHandler()
result = handler._parse_rendered_value("[1, 2, 3]")
assert result == [1, 2, 3]
def test_parse_rendered_value_dict(self):
"""Test parsing rendered value as dict."""
handler = InlineJinjaHandler()
result = handler._parse_rendered_value("{key: value}")
assert isinstance(result, dict)
assert result["key"] == "value"
def test_parse_rendered_value_boolean(self):
"""Test parsing rendered value as boolean."""
handler = InlineJinjaHandler()
result_true = handler._parse_rendered_value("true")
result_false = handler._parse_rendered_value("false")
assert result_true is True
assert result_false is False
def test_extract_templates_with_multiple_inline_templates(self):
"""Test extracting multiple inline templates."""
handler = InlineJinjaHandler()
yaml_content = """
name: {{ name }}
age: {{ age }}
city: {{ city }}
"""
result = handler._extract_templates(yaml_content)
assert "__templates__" in result
assert len(result["__templates__"]) == 3
def test_extract_templates_with_macro(self):
"""Test extracting templates with macro block."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
template:
{% macro greeting(name) %}
Hello {{ name }}
{% endmacro %}
"""
result = handler._extract_templates(yaml_content)
assert result is not None
def test_extract_templates_with_endblock(self):
"""Test extracting templates with block/endblock."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
content:
{% block content %}
This is content
{% endblock %}
"""
result = handler._extract_templates(yaml_content)
assert result is not None
def test_extract_templates_invalid_yaml(self):
"""Test handling of invalid YAML after extraction."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
invalid: [unclosed bracket
"""
with pytest.raises(yaml.YAMLError):
handler._extract_templates(yaml_content)
def test_process_yaml_file_defer_and_apply(self):
"""Test full workflow: defer and then apply templates."""
handler = InlineJinjaHandler()
yaml_content = """
app_name: {{ name }}
version: {{ version }}
"""
# First pass: defer rendering
config = handler.process_yaml_string(yaml_content, defer_rendering=True)
# Second pass: apply templates
context = {"name": "MyApp", "version": "2.0"}
result = handler.apply_templates(config, context)
assert result["app_name"] == "MyApp"
assert result["version"] == 2.0 # YAML parses "2.0" as float
def test_extract_templates_preserves_indent_info(self):
"""Test that template extraction preserves indent information."""
handler = InlineJinjaHandler()
yaml_content = """
root:
nested:
value: {{ var }}
"""
result = handler._extract_templates(yaml_content)
if "__templates__" in result:
for template_info in result["__templates__"].values():
assert "indent" in template_info
def test_apply_templates_with_failed_yaml_parse(self):
"""Test template application when rendered content fails to parse as YAML."""
handler = InlineJinjaHandler()
templates = {
"__template_abc123__": {
"type": "block",
"key": "items",
"content": "{% for i in range(1) %}\ninvalid: [bracket\n{% endfor %}",
"indent": 0,
}
}
data = {"items": "__template_abc123__"}
context = {}
# Should handle the exception gracefully
result = handler._apply_templates_recursive(data, templates, context)
# Result should still be a dict
assert isinstance(result, dict)
def test_render_templates_with_complex_expressions(self):
"""Test rendering templates with complex Jinja expressions."""
handler = InlineJinjaHandler()
yaml_content = """
result: {{ (value * 2) + 10 }}
"""
context = {"value": 5}
result = handler._render_templates(yaml_content, context)
assert result["result"] == 20
def test_extract_templates_with_nested_for_loops(self):
"""Test extracting nested for loops."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
matrix:
{% for i in range(2) %}
row{{ i }}:
{% for j in range(2) %}
col{{ j }}: {{ i }}_{{ j }}
{% endfor %}
{% endfor %}
"""
result = handler._extract_templates(yaml_content)
assert result is not None
assert "name" in result
def test_process_yaml_string_no_context_for_immediate_render(self):
"""Test immediate rendering without providing context."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
value: {{ unknown_var | default('default_value') }}
"""
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context=None)
# Should use empty context
assert result["name"] == "test"
def test_apply_templates_removes_templates_key(self):
"""Test that apply_templates removes __templates__ key from result."""
handler = InlineJinjaHandler()
config = {
"name": "test",
"__templates__": {"template_id": {"type": "inline", "content": "value"}},
}
context = {}
result = handler.apply_templates(config, context)
assert "__templates__" not in result
assert "name" in result
def test_process_yaml_string_non_dict_result(self):
"""Test processing YAML that parses to non-dict (e.g., list) with templates."""
handler = InlineJinjaHandler()
# YAML content that produces a list with templates
yaml_content = """
- name: item1
value: {{ value1 }}
- name: item2
value: {{ value2 }}
"""
result = handler.process_yaml_string(yaml_content, defer_rendering=True)
# When parsed result is not a dict, it should be wrapped
assert isinstance(result, dict)
assert "__templates__" in result or "_content" in result
def test_parse_rendered_value_with_yaml_error(self):
"""Test _parse_rendered_value when YAML parsing fails."""
handler = InlineJinjaHandler()
# Create a string that looks like YAML but will cause an error
# Using mock to force an exception
with patch('yaml.safe_load', side_effect=Exception("YAML error")):
result = handler._parse_rendered_value("some value")
# Should return the original string when YAML parsing fails
assert result == "some value"
def test_parse_rendered_value_plain_string(self):
"""Test _parse_rendered_value with a plain string that can't be parsed as YAML."""
handler = InlineJinjaHandler()
# This string will fail YAML parsing and should be returned as-is
result = handler._parse_rendered_value("just a plain string")
assert isinstance(result, str)
def test_process_yaml_string_with_multiline_template(self):
"""Test processing YAML with multiline Jinja2 template."""
handler = InlineJinjaHandler()
yaml_content = """
config:
{% if condition %}
enabled: true
{% else %}
enabled: false
{% endif %}
"""
context = {"condition": True}
result = handler.process_yaml_string(yaml_content, defer_rendering=False, context=context)
assert "config" in result
assert result["config"]["enabled"] is True
def test_process_yaml_string_empty_content(self):
"""Test processing empty or nearly empty YAML."""
handler = InlineJinjaHandler()
yaml_content = """
# Just a comment
"""
# Comment-only YAML results in None, which raises ValueError
# because inline_jinja_handler expects dict
with pytest.raises(ValueError, match="Expected YAML to parse as dict, got NoneType"):
handler.process_yaml_string(yaml_content, defer_rendering=True)
def test_extract_templates_with_yaml_comment(self):
"""Test extracting templates with YAML comments."""
handler = InlineJinjaHandler()
yaml_content = """
name: test
# This is a YAML comment with {{ variable }}
value: {{ actual_template }}
description: test string
"""
result = handler._extract_templates(yaml_content)
assert result is not None
assert "name" in result