Files
cleveragents-core/tests/unit/templates/test_renderer.py

398 lines
14 KiB
Python

"""
Unit tests for renderer module.
"""
from unittest.mock import Mock, patch
import pytest
from cleveragents.core.exceptions import TemplateError
from cleveragents.templates.renderer import (
TemplateEngine,
TemplateRenderer,
_resolve_path,
)
@pytest.fixture
def simple_renderer():
"""Fixture for TemplateRenderer with SIMPLE engine."""
return TemplateRenderer(TemplateEngine.SIMPLE)
@pytest.fixture
def jinja2_renderer():
"""Fixture for TemplateRenderer with JINJA2 engine."""
return TemplateRenderer(TemplateEngine.JINJA2)
class TestResolvePath:
"""Test cases for _resolve_path function."""
def test_resolve_path_simple(self):
"""Test resolving simple path."""
ctx = {"name": "test"}
result = _resolve_path("name", ctx)
assert result == "test"
def test_resolve_path_dotted(self):
"""Test resolving dotted path."""
ctx = {"user": {"name": "John"}}
result = _resolve_path("user.name", ctx)
assert result == "John"
def test_resolve_path_nested(self):
"""Test resolving nested path."""
ctx = {"app": {"config": {"database": {"host": "localhost"}}}}
result = _resolve_path("app.config.database.host", ctx)
assert result == "localhost"
def test_resolve_path_missing_key(self):
"""Test resolving path with missing key returns empty string."""
ctx = {"name": "test"}
result = _resolve_path("missing", ctx)
assert result == ""
def test_resolve_path_none_value(self):
"""Test resolving path with None value returns empty string."""
ctx = {"value": None}
result = _resolve_path("value", ctx)
assert result == ""
def test_resolve_path_object_attribute(self):
"""Test resolving path with object attribute."""
class MockObject:
def __init__(self):
self.name = "test_name"
ctx = {"obj": MockObject()}
result = _resolve_path("obj.name", ctx)
assert result == "test_name"
def test_resolve_path_object_missing_attribute(self):
"""Test resolving path with missing object attribute."""
class MockObject:
pass
ctx = {"obj": MockObject()}
result = _resolve_path("obj.missing", ctx)
assert result == ""
class TestTemplateEngine:
"""Test cases for TemplateEngine enum."""
def test_template_engine_values(self):
"""Test TemplateEngine enum values."""
assert TemplateEngine.SIMPLE.value == "simple"
assert TemplateEngine.JINJA2.value == "jinja2"
assert TemplateEngine.MUSTACHE.value == "mustache"
class TestTemplateRenderer:
"""Test cases for TemplateRenderer class."""
def test_init_simple(self):
"""Test TemplateRenderer initialization with simple engine."""
renderer = TemplateRenderer(TemplateEngine.SIMPLE)
assert renderer.engine_type == TemplateEngine.SIMPLE
assert renderer.templates == {}
assert renderer.engine is not None
def test_init_jinja2(self):
"""Test TemplateRenderer initialization with Jinja2 engine."""
renderer = TemplateRenderer(TemplateEngine.JINJA2)
assert renderer.engine_type == TemplateEngine.JINJA2
assert renderer.templates == {}
assert renderer.engine is not None
def test_init_jinja2_not_installed(self):
"""Test TemplateRenderer initialization when Jinja2 not installed."""
with patch('builtins.__import__', side_effect=ImportError("No module named 'jinja2'")):
with pytest.raises(TemplateError, match="Jinja2 is not installed"):
TemplateRenderer(TemplateEngine.JINJA2)
def test_init_mustache(self):
"""Test TemplateRenderer initialization with Mustache engine."""
try:
renderer = TemplateRenderer(TemplateEngine.MUSTACHE)
assert renderer.engine_type == TemplateEngine.MUSTACHE
except TemplateError:
# Pystache may not be installed, which is expected
pytest.skip("Pystache not installed")
def test_init_mustache_not_installed(self):
"""Test TemplateRenderer initialization when Pystache not installed."""
with patch('builtins.__import__', side_effect=ImportError("No module named 'pystache'")):
with pytest.raises(TemplateError, match="Pystache is not installed"):
TemplateRenderer(TemplateEngine.MUSTACHE)
def test_init_default_engine(self):
"""Test TemplateRenderer initialization with default engine."""
renderer = TemplateRenderer()
assert renderer.engine_type == TemplateEngine.SIMPLE
def test_register_template_simple(self, simple_renderer):
"""Test registering a template with simple engine."""
simple_renderer.register_template("test", "Hello {{ name }}")
assert "test" in simple_renderer.templates
def test_register_template_jinja2(self, jinja2_renderer):
"""Test registering a template with Jinja2 engine."""
jinja2_renderer.register_template("test", "Hello {{ name }}")
assert "test" in jinja2_renderer.templates
def test_register_template_empty_name(self, simple_renderer):
"""Test registering template with empty name raises error."""
with pytest.raises(TemplateError, match="name cannot be empty"):
simple_renderer.register_template("", "template")
def test_register_template_jinja2_error(self, jinja2_renderer):
"""Test registering invalid Jinja2 template raises error."""
# Invalid Jinja2 syntax
with pytest.raises(TemplateError, match="Failed to register"):
jinja2_renderer.register_template("test", "{% if %}")
def test_register_template_jinja2_no_from_string(self, jinja2_renderer):
"""Test registering template when engine has no from_string method."""
jinja2_renderer.engine = Mock()
delattr(jinja2_renderer.engine, 'from_string')
with pytest.raises(TemplateError, match="no from_string method"):
jinja2_renderer.register_template("test", "template")
def test_render_simple(self, simple_renderer):
"""Test rendering template with simple engine."""
simple_renderer.register_template("test", "Hello {{ name }}")
result = simple_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_jinja2(self, jinja2_renderer):
"""Test rendering template with Jinja2 engine."""
jinja2_renderer.register_template("test", "Hello {{ name }}")
result = jinja2_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_template_not_found(self, simple_renderer):
"""Test rendering non-existent template raises error."""
with pytest.raises(TemplateError, match="not found"):
simple_renderer.render("nonexistent", {})
def test_render_simple_missing_variable(self, simple_renderer):
"""Test rendering with missing variable in simple engine."""
simple_renderer.register_template("test", "Hello {name}")
with pytest.raises(TemplateError, match="Missing template variable"):
simple_renderer.render("test", {})
def test_render_simple_with_format_placeholders(self, simple_renderer):
"""Test rendering with {} format placeholders."""
simple_renderer.register_template("test", "Hello {name}")
result = simple_renderer.render("test", {"name": "World"})
assert result == "Hello World"
def test_render_simple_with_jinja_like_placeholders(self, simple_renderer):
"""Test rendering with {{ }} placeholders in simple engine."""
simple_renderer.register_template("test", "Value: {{ value }}")
result = simple_renderer.render("test", {"value": 42})
assert result == "Value: 42"
def test_render_simple_with_expression(self, simple_renderer):
"""Test rendering with expression in simple engine."""
simple_renderer.register_template("test", "Result: {{ len(items) }}")
result = simple_renderer.render("test", {"items": [1, 2, 3], "len": len})
assert result == "Result: 3"
def test_render_jinja2_no_render_method(self, jinja2_renderer):
"""Test rendering when Jinja2 template has no render method."""
jinja2_renderer.register_template("test", "template")
jinja2_renderer.templates["test"] = Mock(spec=[])
with pytest.raises(TemplateError, match="no render method"):
jinja2_renderer.render("test", {})
def test_render_mustache(self):
"""Test rendering template with Mustache engine."""
try:
renderer = TemplateRenderer(TemplateEngine.MUSTACHE)
renderer.register_template("test", "Hello {{name}}")
result = renderer.render("test", {"name": "World"})
assert "World" in result
except TemplateError:
pytest.skip("Pystache not installed")
def test_render_mustache_no_render_method(self):
"""Test rendering when Mustache engine has no render method."""
try:
renderer = TemplateRenderer(TemplateEngine.MUSTACHE)
renderer.register_template("test", "template")
renderer.engine = Mock(spec=[]) # Mock without render method
with pytest.raises(TemplateError, match="no render method"):
renderer.render("test", {})
except TemplateError:
pytest.skip("Pystache not installed")
def test_render_string_simple(self, simple_renderer):
"""Test rendering string with simple engine."""
result = simple_renderer.render_string("Hello {{ name }}", {"name": "World"})
assert result == "Hello World"
def test_render_string_jinja2(self, jinja2_renderer):
"""Test rendering string with Jinja2 engine."""
result = jinja2_renderer.render_string("Hello {{ name }}", {"name": "World"})
assert result == "Hello World"
def test_render_string_simple_missing_variable(self, simple_renderer):
"""Test rendering string with missing variable in simple engine."""
with pytest.raises(TemplateError, match="Missing template variable"):
simple_renderer.render_string("Hello {name}", {})
def test_render_string_with_source_description(self, simple_renderer):
"""Test rendering string with source description."""
with pytest.raises(TemplateError, match="for config.yaml"):
simple_renderer.render_string("Hello {name}", {}, "config.yaml")
def test_render_string_jinja2_no_from_string(self, jinja2_renderer):
"""Test rendering string when Jinja2 engine has no from_string method."""
jinja2_renderer.engine = Mock(spec=[])
with pytest.raises(TemplateError, match="no from_string method"):
jinja2_renderer.render_string("template", {})
def test_render_string_mustache(self):
"""Test rendering string with Mustache engine."""
try:
renderer = TemplateRenderer(TemplateEngine.MUSTACHE)
result = renderer.render_string("Hello {{name}}", {"name": "World"})
assert "World" in result
except TemplateError:
pytest.skip("Pystache not installed")
def test_render_string_mustache_no_render_method(self):
"""Test rendering string when Mustache engine has no render method."""
try:
renderer = TemplateRenderer(TemplateEngine.MUSTACHE)
renderer.engine = Mock(spec=[]) # Mock without render method
with pytest.raises(TemplateError, match="no render method"):
renderer.render_string("template", {})
except TemplateError:
pytest.skip("Pystache not installed")
def test_get_template_exists(self, simple_renderer):
"""Test getting an existing template."""
simple_renderer.register_template("test", "template")
template = simple_renderer.get_template("test")
assert template is not None
def test_get_template_not_found(self, simple_renderer):
"""Test getting non-existent template raises error."""
with pytest.raises(TemplateError, match="not found"):
simple_renderer.get_template("nonexistent")
def test_list_templates_empty(self, simple_renderer):
"""Test listing templates when none registered."""
result = simple_renderer.list_templates()
assert result == []
def test_list_templates_multiple(self, simple_renderer):
"""Test listing multiple templates."""
simple_renderer.register_template("test1", "template1")
simple_renderer.register_template("test2", "template2")
simple_renderer.register_template("test3", "template3")
result = simple_renderer.list_templates()
assert len(result) == 3
assert "test1" in result
assert "test2" in result
assert "test3" in result
def test_render_simple_with_none_in_expression(self, simple_renderer):
"""Test rendering expression that evaluates to None."""
simple_renderer.register_template("test", "Value: {{ none_value }}")
result = simple_renderer.render("test", {"none_value": None})
assert result == "Value: "
def test_render_string_simple_with_format_and_jinja(self, simple_renderer):
"""Test rendering with both format and jinja-like placeholders."""
result = simple_renderer.render_string(
"Name: {{ name }}, Age: {age}",
{"name": "John", "age": 30}
)
assert "John" in result
assert "30" in result
def test_render_simple_with_complex_expression(self, simple_renderer):
"""Test rendering with complex expression."""
simple_renderer.register_template("test", "Sum: {{ sum([1, 2, 3]) }}")
result = simple_renderer.render("test", {"sum": sum})
assert result == "Sum: 6"
def test_render_jinja2_with_complex_template(self, jinja2_renderer):
"""Test rendering complex Jinja2 template."""
jinja2_renderer.register_template(
"test",
"{% for item in items %}{{ item }}{% if not loop.last %}, {% endif %}{% endfor %}"
)
result = jinja2_renderer.render("test", {"items": ["a", "b", "c"]})
assert result == "a, b, c"
def test_render_error_handling(self, simple_renderer):
"""Test rendering error is properly wrapped."""
simple_renderer.register_template("test", "{{ bad_expression() }}")
with pytest.raises(TemplateError, match="Failed to render template"):
simple_renderer.render("test", {})
if __name__ == "__main__":
pytest.main([__file__, "-v"])