Ruby-Cogs/autoroom/pcx_template_test.py
Valerie 7fc83b2022
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
Add Autoroom cog
2025-06-13 19:15:32 -04:00

930 lines
23 KiB
Python

"""Tests for template engine using Jinja2."""
import pytest
from pcx_template import (
Template,
TemplateTimeoutError,
)
@pytest.mark.asyncio
async def test_simple_template():
tpl = Template()
template_str = "Hello, {{ name }}!"
data = {"name": "World"}
result = await tpl.render(template_str, data)
assert result == "Hello, World!"
@pytest.mark.asyncio
async def test_template_with_no_data():
tpl = Template()
template_str = "Hello, World!"
result = await tpl.render(template_str)
assert result == "Hello, World!"
@pytest.mark.asyncio
async def test_template_with_missing_variable():
tpl = Template()
template_str = "Hello, {{ name }}!"
result = await tpl.render(template_str)
assert result == "Hello, !"
@pytest.mark.asyncio
async def test_template_timeout():
tpl = Template()
template_str = """{% for i in range(100000) %}{% for j in range(100000) %}{% for k in range(100000) %}{{ i*i }}{% endfor %}{% endfor %}{% endfor %}"""
data = {}
with pytest.raises(TemplateTimeoutError) as excinfo:
await tpl.render(template_str, data)
assert "Template rendering exceeded" in str(excinfo.value)
@pytest.mark.asyncio
async def test_template_no_timeout():
tpl = Template()
template_str = """{% for i in range(100) %}{{ i }}\n{% endfor %}"""
data = {}
result = await tpl.render(template_str, data, timeout=2)
expected_result = "\n".join(str(i) for i in range(100)) + "\n"
assert result == expected_result
@pytest.mark.asyncio
async def test_template_with_custom_timeout():
tpl = Template()
template_str = """
{% for i in range(100000) %}
{% for j in range(100000) %}
{{ i }}
{% endfor %}
{% endfor %}
"""
data = {}
with pytest.raises(TemplateTimeoutError) as excinfo:
await tpl.render(template_str, data, timeout=0.1)
assert "Template rendering exceeded 0.1 seconds" in str(excinfo.value)
@pytest.mark.asyncio
async def test_template_with_complex_data():
tpl = Template()
template_str = (
"User: {{ user.name }}, Age: {{ user.age }}, Active: {{ user.active }}"
)
data = {"user": {"name": "John", "age": 30, "active": True}}
result = await tpl.render(template_str, data)
assert result == "User: John, Age: 30, Active: True"
@pytest.mark.asyncio
async def test_template_with_escape_sequences():
tpl = Template()
template_str = "Hello, {{ user.name }}!\nYour score: {{ user.score }}\n\nThank you!"
data = {"user": {"name": "Alice", "score": 42}}
result = await tpl.render(template_str, data)
expected_result = "Hello, Alice!\nYour score: 42\n\nThank you!"
assert result == expected_result
@pytest.mark.asyncio
async def test_deterministic_random() -> None:
tpl = Template()
template_str = "Random choice: {{ ['a', 'b', 'c', 'd', 'e', 'f', 'g']|random }}"
data = {"random_seed": "test_seed"}
result1 = await tpl.render(template_str, data)
result2 = await tpl.render(template_str, data)
assert result1 == result2 # Should be the same due to deterministic randomness
@pytest.mark.asyncio
async def test_deterministic_random_with_different_seeds():
tpl = Template()
template_str = "Random choice: {{ ['a', 'b', 'c', 'd', 'e', 'f', 'g']|random }}"
data1 = {"random_seed": "seed1"}
data2 = {"random_seed": "seed2"}
result1 = await tpl.render(template_str, data1)
result2 = await tpl.render(template_str, data2)
assert result1 != result2 # Should be different due to different seeds
@pytest.mark.asyncio
async def test_template_with_large_data():
tpl = Template()
template_str = "Sum: {{ data|sum }}"
data = {"data": list(range(10000))}
result = await tpl.render(template_str, data)
assert result == f"Sum: {sum(range(10000))}"
@pytest.mark.asyncio
async def test_template_with_nested_data():
tpl = Template()
template_str = "User: {{ user.name }}, Address: {{ user.address.city }}, {{ user.address.zip }}"
data = {"user": {"name": "John", "address": {"city": "New York", "zip": "10001"}}}
result = await tpl.render(template_str, data)
assert result == "User: John, Address: New York, 10001"
# Old template system tests: Interpolation
@pytest.mark.asyncio
async def test_no_interpolation():
tpl = Template()
template_str = "PhasecoreX says {hello}!"
data = {}
result = await tpl.render(template_str, data)
expected = template_str
assert expected == result
@pytest.mark.asyncio
async def test_basic_interpolation():
tpl = Template()
template_str = "Hello, {{subject}}!"
data = {"subject": "world"}
result = await tpl.render(template_str, data)
expected = "Hello, world!"
assert expected == result
@pytest.mark.asyncio
async def test_basic_integer_interpolation():
tpl = Template()
template_str = '"{{mph}} miles an hour!"'
data = {"mph": 88}
result = await tpl.render(template_str, data)
expected = '"88 miles an hour!"'
assert expected == result
@pytest.mark.asyncio
async def test_basic_float_interpolation():
tpl = Template()
template_str = '"{{power}} jiggawatts!"'
data = {"power": 1.210}
result = await tpl.render(template_str, data)
expected = '"1.21 jiggawatts!"'
assert expected == result
@pytest.mark.asyncio
async def test_basic_context_miss_interpolation():
tpl = Template()
template_str = "I ({{cannot}}) be seen!"
data = {}
result = await tpl.render(template_str, data)
expected = "I () be seen!"
assert expected == result
# Dotted Names
@pytest.mark.asyncio
async def test_dotted_names_arbitrary_depth():
tpl = Template()
template_str = '"{{a.b.c.d.e.name}}" == "Phil"'
data = {"a": {"b": {"c": {"d": {"e": {"name": "Phil"}}}}}}
result = await tpl.render(template_str, data)
expected = '"Phil" == "Phil"'
assert expected == result
@pytest.mark.asyncio
async def test_dotted_names_broken_chains():
tpl = Template()
template_str = '"{{a.b.c}}" == ""'
data = {"a": {}}
result = await tpl.render(template_str, data)
expected = '"" == ""'
assert expected == result
@pytest.mark.asyncio
async def test_dotted_names_broken_chain_resolution():
tpl = Template()
template_str = '"{{a.b.c.name}}" == ""'
data = {"a": {"b": {}}, "c": {"name": "Jim"}}
result = await tpl.render(template_str, data)
expected = '"" == ""'
assert expected == result
# Whitespace Sensitivity
@pytest.mark.asyncio
async def test_interpolation_surrounding_whitespace():
tpl = Template()
template_str = "| {{string}} |"
data = {"string": "---"}
result = await tpl.render(template_str, data)
expected = "| --- |"
assert expected == result
@pytest.mark.asyncio
async def test_interpolation_standalone():
tpl = Template()
template_str = " {{string}}\n"
data = {"string": "---"}
result = await tpl.render(template_str, data)
expected = " ---\n"
assert expected == result
# Whitespace Insensitivity
@pytest.mark.asyncio
async def test_interpolation_with_padding():
tpl = Template()
template_str = "|{{ string }}|"
data = {"string": "---"}
result = await tpl.render(template_str, data)
expected = "|---|"
assert expected == result
# Old template system tests: IfStatement
@pytest.mark.asyncio
async def test_if_truthy():
tpl = Template()
template_str = '"{% if boolean %}This should be rendered.{% endif %}"'
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = '"This should be rendered."'
assert expected == result
@pytest.mark.asyncio
async def test_if_falsey():
tpl = Template()
template_str = '"{% if boolean %}This should not be rendered.{% endif %}"'
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = '""'
assert expected == result
@pytest.mark.asyncio
async def test_empty_lists():
tpl = Template()
template_str = '"{% if list %}Yay lists!{% endif %}"'
data = {"list": []}
result = await tpl.render(template_str, data)
expected = '""'
assert expected == result
@pytest.mark.asyncio
async def test_if_doubled():
tpl = Template()
template_str = """{% if bool %}
* first
{% endif %}
* {{two}}
{% if bool %}
* third
{% endif %}
"""
data = {"bool": True, "two": "second"}
result = await tpl.render(template_str, data)
expected = """* first
* second
* third
"""
assert expected == result
@pytest.mark.asyncio
async def test_if_nested_truthy():
tpl = Template()
template_str = "| A {% if bool %}B {% if bool %}C{% endif %} D{% endif %} E |"
data = {"bool": True}
result = await tpl.render(template_str, data)
expected = "| A B C D E |"
assert expected == result
@pytest.mark.asyncio
async def test_if_nested_falsey():
tpl = Template()
template_str = "| A {% if bool %}B {% if bool %}C{% endif %} D{% endif %} E |"
data = {"bool": False}
result = await tpl.render(template_str, data)
expected = "| A E |"
assert expected == result
@pytest.mark.asyncio
async def test_if_context_misses():
tpl = Template()
template_str = "[{% if missing %}Found key 'missing'!{% endif %}]"
data = {}
result = await tpl.render(template_str, data)
expected = "[]"
assert expected == result
# Dotted Names
@pytest.mark.asyncio
async def test_if_dotted_names_truthy():
tpl = Template()
template_str = '"{% if a.b.c %}Here{% endif %}" == "Here"'
data = {"a": {"b": {"c": True}}}
result = await tpl.render(template_str, data)
expected = '"Here" == "Here"'
assert expected == result
@pytest.mark.asyncio
async def test_if_dotted_names_falsey():
tpl = Template()
template_str = '"{% if a.b.c %}Here{% endif %}" == ""'
data = {"a": {"b": {"c": False}}}
result = await tpl.render(template_str, data)
expected = '"" == ""'
assert expected == result
@pytest.mark.asyncio
async def test_if_dotted_names_broken_chains():
tpl = Template()
template_str = '"{% if a.b.c %}Here{% endif %}" == ""'
data = {"a": {}}
result = await tpl.render(template_str, data)
expected = '"" == ""'
assert expected == result
# Whitespace Sensitivity
@pytest.mark.asyncio
async def test_if_surrounding_whitespace():
tpl = Template()
template_str = " | {% if boolean %}\t|\t{% endif %} | \n"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = " | \t|\t | \n"
assert expected == result
@pytest.mark.asyncio
async def test_if_internal_whitespace():
tpl = Template()
template_str = " | {% if boolean %} {# Important Whitespace #}\n {% endif %} | \n"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = " | \n | \n"
assert expected == result
@pytest.mark.asyncio
async def test_if_indented_inline_sections():
tpl = Template()
template_str = " {% if boolean %}YES{% endif %}\n {% if boolean %}GOOD{% endif %}\n"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = " YES\n GOOD\n"
assert expected == result
@pytest.mark.asyncio
async def test_if_standalone_lines():
tpl = Template()
template_str = """| This Is
{% if boolean %}
|
{% endif %}
| A Line
"""
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = """| This Is
|
| A Line
"""
assert expected == result
@pytest.mark.asyncio
async def test_if_indented_standalone_lines():
tpl = Template()
template_str = """| This Is
{% if boolean %}
|
{% endif %}
| A Line
"""
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = """| This Is
|
| A Line
"""
assert expected == result
@pytest.mark.asyncio
async def test_if_standalone_line_endings():
tpl = Template()
template_str = "|\r\n{% if boolean %}\r\n{% endif %}\r\n|"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = "|\r\n|"
assert expected == result
@pytest.mark.asyncio
async def test_if_standalone_without_previous_line():
tpl = Template()
template_str = " {% if boolean %}\n#{% endif %}\n/"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = "#\n/"
assert expected == result
@pytest.mark.asyncio
async def test_if_standalone_without_newline():
tpl = Template()
template_str = "#{% if boolean %}\n/\n {% endif %}"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = "#\n/\n"
assert expected == result
# Whitespace Insensitivity
@pytest.mark.asyncio
async def test_if_padding():
tpl = Template()
template_str = "|{% if boolean%}={% endif %}|"
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = "|=|"
assert expected == result
# Old template system tests: IfNotStatement
@pytest.mark.asyncio
async def test_falsey():
tpl = Template()
template_str = '"{% if not boolean %}This should be rendered.{% endif %}"'
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = '"This should be rendered."'
assert expected == result
@pytest.mark.asyncio
async def test_truthy():
tpl = Template()
template_str = '"{% if not boolean %}This should not be rendered.{% endif %}"'
data = {"boolean": True}
result = await tpl.render(template_str, data)
expected = '""'
assert expected == result
@pytest.mark.asyncio
async def test_empty_list():
tpl = Template()
template_str = '"{% if not list %}Yay lists!{% endif %}"'
data = {"list": []}
result = await tpl.render(template_str, data)
expected = '"Yay lists!"'
assert expected == result
@pytest.mark.asyncio
async def test_doubled():
tpl = Template()
template_str = """{% if not bool %}
* first
{% endif %}
* {{two}}
{% if not bool %}
* third
{% endif %}
"""
data = {"bool": False, "two": "second"}
result = await tpl.render(template_str, data)
expected = """* first
* second
* third
"""
assert expected == result
@pytest.mark.asyncio
async def test_nested_falsey():
tpl = Template()
template_str = (
"| A {% if not bool %}B {% if not bool %}C{% endif %} D{% endif %} E |"
)
data = {"bool": False}
result = await tpl.render(template_str, data)
expected = "| A B C D E |"
assert expected == result
@pytest.mark.asyncio
async def test_nested_truthy():
tpl = Template()
template_str = (
"| A {% if not bool %}B {% if not bool %}C{% endif %} D{% endif %} E |"
)
data = {"bool": True}
result = await tpl.render(template_str, data)
expected = "| A E |"
assert expected == result
@pytest.mark.asyncio
async def test_context_misses():
tpl = Template()
template_str = "[{% if not missing %}Cannot find key 'missing'!{% endif %}]"
data = {}
result = await tpl.render(template_str, data)
expected = "[Cannot find key 'missing'!]"
assert expected == result
# Dotted Names
@pytest.mark.asyncio
async def test_dotted_names_truthy():
tpl = Template()
template_str = '"{% if not a.b.c %}Here{% endif %}" == ""'
data = {"a": {"b": {"c": True}}}
result = await tpl.render(template_str, data)
expected = '"" == ""'
assert expected == result
@pytest.mark.asyncio
async def test_dotted_names_falsey():
tpl = Template()
template_str = '"{% if not a.b.c %}Not Here{% endif %}" == "Not Here"'
data = {"a": {"b": {"c": False}}}
result = await tpl.render(template_str, data)
expected = '"Not Here" == "Not Here"'
assert expected == result
@pytest.mark.asyncio
async def test_ifnot_dotted_names_broken_chains():
tpl = Template()
template_str = '"{% if not a.b.c %}Not Here{% endif %}" == "Not Here"'
data = {"a": {}}
result = await tpl.render(template_str, data)
expected = '"Not Here" == "Not Here"'
assert expected == result
# Whitespace Sensitivity
@pytest.mark.asyncio
async def test_ifnot_surrounding_whitespace():
tpl = Template()
template_str = " | {% if not boolean %}\t|\t{% endif %} | \n"
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = " | \t|\t | \n"
assert expected == result
@pytest.mark.asyncio
async def test_internal_whitespace():
tpl = Template()
template_str = (
" | {% if not boolean %} {# Important Whitespace #}\n {% endif %} | \n"
)
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = " | \n | \n"
assert expected == result
@pytest.mark.asyncio
async def test_indented_inline_sections():
tpl = Template()
template_str = (
" {% if not boolean %}YES{% endif %}\n {% if not boolean %}GOOD{% endif %}\n"
)
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = " YES\n GOOD\n"
assert expected == result
@pytest.mark.asyncio
async def test_standalone_lines():
tpl = Template()
template_str = """| This Is
{% if not boolean %}
|
{% endif %}
| A Line
"""
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = """| This Is
|
| A Line
"""
assert expected == result
@pytest.mark.asyncio
async def test_indented_standalone_lines():
tpl = Template()
template_str = """| This Is
{% if not boolean %}
|
{% endif %}
| A Line
"""
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = """| This Is
|
| A Line
"""
assert expected == result
@pytest.mark.asyncio
async def test_ifnot_standalone_line_endings():
tpl = Template()
template_str = "|\r\n{% if not boolean %}\r\n{% endif %}\r\n|"
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = "|\r\n|"
assert expected == result
@pytest.mark.asyncio
async def test_ifnot_standalone_without_previous_line():
tpl = Template()
template_str = " {% if not boolean %}\n#{% endif %}\n/"
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = "#\n/"
assert expected == result
@pytest.mark.asyncio
async def test_ifnot_standalone_without_newline():
tpl = Template()
template_str = "#{% if not boolean %}\n/\n {% endif %}"
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = "#\n/\n"
assert expected == result
# Whitespace Insensitivity
@pytest.mark.asyncio
async def test_padding():
tpl = Template()
template_str = "|{% if not boolean%}={% endif %}|"
data = {"boolean": False}
result = await tpl.render(template_str, data)
expected = "|=|"
assert expected == result
# Old template system tests: ElseStatement
@pytest.mark.asyncio
async def test_else():
tpl = Template()
template_str = "{% if 1 > 2 %}Bad...{% else %}Good!{% endif %}"
data = {}
result = await tpl.render(template_str, data)
expected = "Good!"
assert expected == result
# Old template system tests: ElifStatement
@pytest.mark.asyncio
async def test_elif():
tpl = Template()
template_str = "{% if 1 > 2 %}Bad if...{% elif 2 == 3 %}Bad elif 1...{% elif 1 == 1 %}Good!{% elif 1 == 2 %}Bad elif 2...{% else %}Bad else...{% endif %}"
data = {}
result = await tpl.render(template_str, data)
expected = "Good!"
assert expected == result
# Old template system tests: Comment
@pytest.mark.asyncio
async def test_inline():
tpl = Template()
template_str = "12345{# Comment Block! #}67890"
data = {}
result = await tpl.render(template_str, data)
expected = "1234567890"
assert expected == result
@pytest.mark.asyncio
async def test_multiline():
tpl = Template()
template_str = """12345{#
This is a
multi-line comment...
#}67890
"""
data = {}
result = await tpl.render(template_str, data)
expected = """1234567890
"""
assert expected == result
@pytest.mark.asyncio
async def test_standalone():
tpl = Template()
template_str = """Begin.
{# Comment Block! #}
End.
"""
data = {}
result = await tpl.render(template_str, data)
expected = """Begin.
End.
"""
assert expected == result
@pytest.mark.asyncio
async def test_indented_standalone():
tpl = Template()
template_str = """Begin.
{# Indented Comment Block! #}
End.
"""
data = {}
result = await tpl.render(template_str, data)
expected = """Begin.
End.
"""
assert expected == result
@pytest.mark.asyncio
async def test_standalone_line_endings():
tpl = Template()
template_str = "|\r\n{# Standalone Comment #}\r\n|"
data = {}
result = await tpl.render(template_str, data)
expected = "|\r\n|"
assert expected == result
@pytest.mark.asyncio
async def test_standalone_without_previous_line():
tpl = Template()
template_str = " {# I'm Still Standalone #}\n!"
data = {}
result = await tpl.render(template_str, data)
expected = "!"
assert expected == result
@pytest.mark.asyncio
async def test_standalone_without_newline():
tpl = Template()
template_str = "!\n {# I'm Still Standalone #}"
data = {}
result = await tpl.render(template_str, data)
expected = "!\n"
assert expected == result
@pytest.mark.asyncio
async def test_multiline_standalone():
tpl = Template()
template_str = """Begin.
{#
Something's going on here...
#}
End.
"""
data = {}
result = await tpl.render(template_str, data)
expected = """Begin.
End.
"""
assert expected == result
@pytest.mark.asyncio
async def test_indented_multiline_standalone():
tpl = Template()
template_str = """Begin.
{#
Something's going on here...
#}
End.
"""
data = {}
result = await tpl.render(template_str, data)
expected = """Begin.
End.
"""
assert expected == result
@pytest.mark.asyncio
async def test_indented_inline():
tpl = Template()
template_str = " 12 {# 34 #}\n"
data = {}
result = await tpl.render(template_str, data)
expected = " 12 \n"
assert expected == result
@pytest.mark.asyncio
async def test_surrounding_whitespace():
tpl = Template()
template_str = "12345 {# Comment Block! #} 67890"
data = {}
result = await tpl.render(template_str, data)
expected = "12345 67890"
assert expected == result
# Old template system tests: Filter
@pytest.mark.asyncio
async def test_invalid():
tpl = Template()
template_str = "{{words | invalidFilter}}"
data = {"words": "This won't be changed"}
result = await tpl.render(template_str, data)
expected = "This won't be changed"
assert expected == result
@pytest.mark.asyncio
async def test_lower():
tpl = Template()
template_str = "{{word | lower}}"
data = {"word": "QUIET"}
result = await tpl.render(template_str, data)
expected = "quiet"
assert expected == result
@pytest.mark.asyncio
async def test_upper():
tpl = Template()
template_str = "{{word | upper}}"
data = {"word": "loud"}
result = await tpl.render(template_str, data)
expected = "LOUD"
assert expected == result
@pytest.mark.asyncio
async def test_multiple():
tpl = Template()
template_str = "{{word | upper | lower | upper | lower | upper | lower | upper | lower | upper}}"
data = {"word": "loud"}
result = await tpl.render(template_str, data)
expected = "LOUD"
assert expected == result
if __name__ == "__main__":
pytest.main(["-v", __file__])