test_template_loader.py•8.04 kB
"""Tests for the template loader functionality."""
import datetime
import pytest
from pathlib import Path
from basic_memory.api.template_loader import TemplateLoader
@pytest.fixture
def temp_template_dir(tmpdir):
"""Create a temporary directory for test templates."""
template_dir = tmpdir.mkdir("templates").mkdir("prompts")
return template_dir
@pytest.fixture
def custom_template_loader(temp_template_dir):
"""Return a TemplateLoader instance with a custom template directory."""
return TemplateLoader(str(temp_template_dir))
@pytest.fixture
def simple_template(temp_template_dir):
"""Create a simple test template."""
template_path = temp_template_dir / "simple.hbs"
template_path.write_text("Hello, {{name}}!", encoding="utf-8")
return "simple.hbs"
@pytest.mark.asyncio
async def test_render_simple_template(custom_template_loader, simple_template):
"""Test rendering a simple template."""
context = {"name": "World"}
result = await custom_template_loader.render(simple_template, context)
assert result == "Hello, World!"
@pytest.mark.asyncio
async def test_template_cache(custom_template_loader, simple_template):
"""Test that templates are cached."""
context = {"name": "World"}
# First render, should load template
await custom_template_loader.render(simple_template, context)
# Check that template is in cache
assert simple_template in custom_template_loader.template_cache
# Modify the template file - shouldn't affect the cached version
template_path = Path(custom_template_loader.template_dir) / simple_template
template_path.write_text("Goodbye, {{name}}!", encoding="utf-8")
# Second render, should use cached template
result = await custom_template_loader.render(simple_template, context)
assert result == "Hello, World!"
# Clear cache and render again - should use updated template
custom_template_loader.clear_cache()
assert simple_template not in custom_template_loader.template_cache
result = await custom_template_loader.render(simple_template, context)
assert result == "Goodbye, World!"
@pytest.mark.asyncio
async def test_date_helper(custom_template_loader, temp_template_dir):
# Test date helper
date_path = temp_template_dir / "date.hbs"
date_path.write_text("{{date timestamp}}", encoding="utf-8")
date_result = await custom_template_loader.render(
"date.hbs", {"timestamp": datetime.datetime(2023, 1, 1, 12, 30)}
)
assert "2023-01-01" in date_result
@pytest.mark.asyncio
async def test_default_helper(custom_template_loader, temp_template_dir):
# Test default helper
default_path = temp_template_dir / "default.hbs"
default_path.write_text("{{default null 'default-value'}}", encoding="utf-8")
default_result = await custom_template_loader.render("default.hbs", {"null": None})
assert default_result == "default-value"
@pytest.mark.asyncio
async def test_capitalize_helper(custom_template_loader, temp_template_dir):
# Test capitalize helper
capitalize_path = temp_template_dir / "capitalize.hbs"
capitalize_path.write_text("{{capitalize 'test'}}", encoding="utf-8")
capitalize_result = await custom_template_loader.render("capitalize.hbs", {})
assert capitalize_result == "Test"
@pytest.mark.asyncio
async def test_size_helper(custom_template_loader, temp_template_dir):
# Test size helper
size_path = temp_template_dir / "size.hbs"
size_path.write_text("{{size collection}}", encoding="utf-8")
size_result = await custom_template_loader.render("size.hbs", {"collection": [1, 2, 3]})
assert size_result == "3"
@pytest.mark.asyncio
async def test_json_helper(custom_template_loader, temp_template_dir):
# Test json helper
json_path = temp_template_dir / "json.hbs"
json_path.write_text("{{json data}}", encoding="utf-8")
json_result = await custom_template_loader.render("json.hbs", {"data": {"key": "value"}})
assert json_result == '{"key": "value"}'
@pytest.mark.asyncio
async def test_less_than_helper(custom_template_loader, temp_template_dir):
# Test lt (less than) helper
lt_path = temp_template_dir / "lt.hbs"
lt_path.write_text("{{#if_cond (lt 2 3)}}true{{else}}false{{/if_cond}}", encoding="utf-8")
lt_result = await custom_template_loader.render("lt.hbs", {})
assert lt_result == "true"
@pytest.mark.asyncio
async def test_file_not_found(custom_template_loader):
"""Test that FileNotFoundError is raised when a template doesn't exist."""
with pytest.raises(FileNotFoundError):
await custom_template_loader.render("non_existent_template.hbs", {})
@pytest.mark.asyncio
async def test_extension_handling(custom_template_loader, temp_template_dir):
"""Test that template extensions are handled correctly."""
# Create template with .hbs extension
template_path = temp_template_dir / "test_extension.hbs"
template_path.write_text("Template with extension: {{value}}", encoding="utf-8")
# Test accessing with full extension
result = await custom_template_loader.render("test_extension.hbs", {"value": "works"})
assert result == "Template with extension: works"
# Test accessing without extension
result = await custom_template_loader.render("test_extension", {"value": "also works"})
assert result == "Template with extension: also works"
# Test accessing with wrong extension gets converted
template_path = temp_template_dir / "liquid_template.hbs"
template_path.write_text("Liquid template: {{value}}", encoding="utf-8")
result = await custom_template_loader.render("liquid_template.liquid", {"value": "converted"})
assert result == "Liquid template: converted"
@pytest.mark.asyncio
async def test_dedent_helper(custom_template_loader, temp_template_dir):
"""Test the dedent helper for text blocks."""
dedent_path = temp_template_dir / "dedent.hbs"
# Create a template with indented text blocks
template_content = """Before
{{#dedent}}
This is indented text
with nested indentation
that should be dedented
while preserving relative indentation
{{/dedent}}
After"""
dedent_path.write_text(template_content, encoding="utf-8")
# Render the template
result = await custom_template_loader.render("dedent.hbs", {})
# Print the actual output for debugging
print(f"Dedent helper result: {repr(result)}")
# Check that the indentation is properly removed
assert "This is indented text" in result
assert "with nested indentation" in result
assert "that should be dedented" in result
assert "while preserving relative indentation" in result
assert "Before" in result
assert "After" in result
# Check that relative indentation is preserved
assert result.find("with nested indentation") > result.find("This is indented text")
@pytest.mark.asyncio
async def test_nested_dedent_helper(custom_template_loader, temp_template_dir):
"""Test the dedent helper with nested content."""
dedent_path = temp_template_dir / "nested_dedent.hbs"
# Create a template with nested indented blocks
template_content = """
{{#each items}}
{{#dedent}}
--- Item {{this}}
Details for item {{this}}
- Indented detail 1
- Indented detail 2
{{/dedent}}
{{/each}}"""
dedent_path.write_text(template_content, encoding="utf-8")
# Render the template
result = await custom_template_loader.render("nested_dedent.hbs", {"items": [1, 2]})
# Print the actual output for debugging
print(f"Actual result: {repr(result)}")
# Use a more flexible assertion that checks individual components
# instead of exact string matching
assert "--- Item 1" in result
assert "Details for item 1" in result
assert "- Indented detail 1" in result
assert "--- Item 2" in result
assert "Details for item 2" in result
assert "- Indented detail 2" in result