Add unit tests for the template engine

This commit is contained in:
s3lph 2019-11-25 04:35:50 +01:00
parent 839d8e25cb
commit 70e980c5c0
5 changed files with 98 additions and 37 deletions

View file

@ -1,5 +1,5 @@
from spaceapi_server.template import env_init from spaceapi_server.template import _env_init
def template_function(fn): def template_function(fn):
@ -8,7 +8,7 @@ def template_function(fn):
:param fn: The function to register. :param fn: The function to register.
""" """
# Make sure the Jinja2 environment is initialized # Make sure the Jinja2 environment is initialized
env = env_init() env = _env_init()
# Add the function to the environment's globals # Add the function to the environment's globals
env.globals[fn.__name__] = fn env.globals[fn.__name__] = fn
return fn return fn
@ -20,7 +20,7 @@ def template_filter(fn):
:param fn: The function to register. :param fn: The function to register.
""" """
# Make sure the Jinja2 environment is initialized # Make sure the Jinja2 environment is initialized
env = env_init() env = _env_init()
# Add the function to the environment's filters # Add the function to the environment's filters
env.filters[fn.__name__] = fn env.filters[fn.__name__] = fn
return fn return fn
@ -32,7 +32,7 @@ def template_test(fn):
:param fn: The function to register. :param fn: The function to register.
""" """
# Make sure the Jinja2 environment is initialized # Make sure the Jinja2 environment is initialized
env = env_init() env = _env_init()
# Add the function to the environment's tests # Add the function to the environment's tests
env.tests[fn.__name__] = fn env.tests[fn.__name__] = fn
return fn return fn

View file

@ -13,35 +13,11 @@ from spaceapi_server import template, config
__TEMPLATE = None __TEMPLATE = None
def render_traverse(obj):
"""
Walk through a complex, JSON-serializable data structure, and pass
string objects through the Jinja2 templating engine.
:param obj: The object to traverse.
"""
if isinstance(obj, list):
# list -> recurse into each item
for i in range(len(obj)):
obj[i] = render_traverse(obj[i])
return obj
elif isinstance(obj, dict):
# dict -> recurse into the value of each (key, value)
for k, v in obj.items():
obj[k] = render_traverse(obj[k])
return obj
elif isinstance(obj, str):
# str -> template
return template.render(obj)
else:
# anything else -> return as-is
return obj
@bottle.route('/') @bottle.route('/')
def serve(): def serve():
global __TEMPLATE global __TEMPLATE
# Render the response template # Render the response template
rendered = render_traverse(__TEMPLATE) rendered = template.render_traverse(__TEMPLATE)
# Set the response Content-Type # Set the response Content-Type
bottle.response.content_type = 'application/json; charset=utf-8' bottle.response.content_type = 'application/json; charset=utf-8'
# CORS "whitelist" # CORS "whitelist"

View file

@ -4,19 +4,19 @@ import jinja2
# The Jinja2 environment # The Jinja2 environment
_ENV = None __ENV = None
def env_init(force: bool = False): def _env_init(force: bool = False):
""" """
Initialize the Jinja2 environment. Initialize the Jinja2 environment.
:param force: If true, force reload the environment. :param force: If true, force reload the environment.
""" """
global _ENV global __ENV
if _ENV is None or force: if __ENV is None or force:
# Use json.dumps as finalizer in order to preserve complex data structures # Use json.dumps as finalizer in order to preserve complex data structures
_ENV = jinja2.Environment(finalize=json.dumps) __ENV = jinja2.Environment(finalize=json.dumps)
return _ENV return __ENV
def render(template: str): def render(template: str):
@ -24,9 +24,33 @@ def render(template: str):
Render the given string as a Jinja2 template. Render the given string as a Jinja2 template.
:param template: The template string to render. :param template: The template string to render.
""" """
# Make sure the Jinaj2 environment is initialized # Make sure the Jinja2 environment is initialized
env = env_init() env = _env_init()
# Create a Jinja2 template from the input string # Create a Jinja2 template from the input string
t = env.from_string(template) t = env.from_string(template)
# Render the template and turn the JSON dump back into complex data structures # Render the template and turn the JSON dump back into complex data structures
return json.loads(t.render()) return json.loads(t.render())
def render_traverse(obj):
"""
Walk through a complex, JSON-serializable data structure, and pass
string objects through the Jinja2 templating engine.
:param obj: The object to traverse.
"""
if isinstance(obj, list):
# list -> recurse into each item
for i in range(len(obj)):
obj[i] = render_traverse(obj[i])
return obj
elif isinstance(obj, dict):
# dict -> recurse into the value of each (key, value)
for k, v in obj.items():
obj[k] = render_traverse(obj[k])
return obj
elif isinstance(obj, str):
# str -> template
return render(obj)
else:
# anything else -> return as-is
return obj

View file

@ -0,0 +1,61 @@
import unittest
from spaceapi_server.template import _env_init, render, render_traverse
from spaceapi_server.plugins import template_function, template_filter, template_test
class TemplateTest(unittest.TestCase):
@staticmethod
@template_function
def template_test_function(value):
return f'test_{value}'
@staticmethod
@template_filter
def template_test_filter(value, other):
return f'test_{other}_{value}'
@staticmethod
@template_test
def template_test_test(value):
return value == 'baz'
def test_template(self):
env = _env_init()
self.assertEqual('"not a template"', env.from_string('not a template').render())
self.assertEqual('"a template"', env.from_string('{{ "a template" }}').render())
self.assertEqual('42', env.from_string('{{ 42 }}').render())
self.assertEqual('["foo"]', env.from_string('{{ [ "foo" ] }}').render())
self.assertEqual('{"foo": ["bar"]}', env.from_string('{{ { "foo": [ "bar" ] } }}').render())
def test_render(self):
self.assertEqual('not a template', render('not a template'))
self.assertEqual('a template', render('{{ "a template" }}'))
self.assertEqual(42, render('{{ 42 }}'))
self.assertEqual(['foo'], render('{{ [ "foo" ] }}'))
self.assertEqual({'foo': ['bar']}, render('{{ { "foo": [ "bar" ] } }}'))
def test_render_traverse(self):
template = {
'foo': 42,
'bar': [1, 2, 3],
'notemplate': 'foo',
'builtin': '{{ [ 1337, 42 ] | first }}',
'test_functions': {
'test_function': '{{ template_test_function("foo") }}',
'test_filter': '{{ "bar" | template_test_filter("other") }}',
'test_test_true': '{{ "baz" is template_test_test }}',
'test_test_false': '{{ "foo" is template_test_test }}'
}
}
rendered = render_traverse(template)
self.assertEqual(42, rendered['foo'])
self.assertEqual([1, 2, 3], rendered['bar'])
self.assertEqual('foo', rendered['notemplate'])
self.assertEqual(1337, rendered['builtin'])
self.assertEqual('test_foo', rendered['test_functions']['test_function'])
self.assertEqual('test_other_bar', rendered['test_functions']['test_filter'])
self.assertTrue(rendered['test_functions']['test_test_true'])
self.assertFalse(rendered['test_functions']['test_test_false'])