forked from cleveragents/cleveragents-core
398 lines
14 KiB
Python
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"])
|
|
|