Skip to content

Commit

Permalink
Merge pull request #282 from Cray-HPE/CRAYSAT-1917-add-jinja-rendered…
Browse files Browse the repository at this point in the history
…-unit-tests

CRAYSAT-1917: Add unit tests for `jinja_rendered` decorator
  • Loading branch information
haasken-hpe authored Nov 6, 2024
2 parents 71ef427 + 8ff2874 commit 2fa4583
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 53 deletions.
180 changes: 132 additions & 48 deletions tests/cli/bootprep/input/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,79 +26,163 @@
"""

import unittest
from jinja2 import Template
from unittest.mock import MagicMock

from sat.cli.bootprep.input.base import BaseInputItem
from jinja2.sandbox import SandboxedEnvironment

from sat.cli.bootprep.input.base import BaseInputItem, jinja_rendered
from sat.cli.bootprep.input.instance import InputInstance
from sat.constants import MISSING_VALUE


def create_input_item_cls(cls_report_attrs):
def create_input_item_cls():

class SomeTestItem(BaseInputItem):
"""A mock concrete BaseInputItem class"""
description = 'some input item'
report_attrs = cls_report_attrs

def __init__(self, data):
# Since the test input item class doesn't need access
# to the input instance, we can just ignore it here.
# As a consequence, other methods on this class won't
# work as expected.
self.data = data

def __getattr__(self, item):
if item in cls_report_attrs:
try:
return self.data[item]
except KeyError as err:
raise AttributeError(str(err))
description = 'some test item'
report_attrs = ['name', 'type', 'complex_data']

def get_create_item_data(self):
return

def create(self, payload):
return

def render_jinja_template(self, template_str):
"""Render a Jinja template with provided data."""
template = Template(template_str)
return template.render(**self.data)
@property
@jinja_rendered
def name(self):
return self.data.get('name')

@property
@jinja_rendered
def type(self):
return self.data.get('type')

@property
@jinja_rendered
def complex_data(self):
return self.data.get('complex_data')

return SomeTestItem


class TestBaseInputItem(unittest.TestCase):

def setUp(self):
self.mock_instance = MagicMock(spec=InputInstance)
self.jinja_env = SandboxedEnvironment()
self.jinja_env.globals = {
'default': {
'note': 'foo',
'suffix': '-bar',
'system_name': 'ryan',
'site-domain': 'example.com'
}
}

def test_report_rows_have_correct_attrs(self):
"""Test that report rows are generated for input items"""
row_headings = ['name', 'type']
row_vals = ['foo', 'configuration']
item_cls = create_input_item_cls(row_headings)
self.assertEqual(item_cls(dict(zip(row_headings, row_vals))).report_row(),
row_vals)
data = {'name': 'foo', 'type': 'configuration', 'complex_data': 'columbo'}
item_cls = create_input_item_cls()
self.assertEqual(['foo', 'configuration', 'columbo'],
item_cls(data, self.mock_instance, 0, self.jinja_env).report_row())

def test_report_rows_excluded(self):
"""Test that report rows are not included if not present in report_attrs"""
item_cls = create_input_item_cls(['name'])
self.assertEqual(item_cls({'name': 'foo', 'something_else': 'another_thing'}).report_row(), ['foo'])
data = {'name': 'foo', 'something_else': 'another_thing'}
item_cls = create_input_item_cls()
item_cls.report_attrs = ['name']
self.assertEqual(item_cls(data, self.mock_instance, 0, self.jinja_env).report_row(), ['foo'])

def test_missing_report_attrs_are_missing(self):
"""Test that missing report attrs are reported as MISSING"""
row_headings = ['name', 'type', 'one_more_thing']
row_vals = ['foo', 'configuration', 'columbo']
item_cls = create_input_item_cls(row_headings) # Cut off last report attr
data = dict(zip(row_headings[:-1], row_vals[:-1]))

report_row = item_cls(data).report_row()
self.assertEqual(len(report_row), len(row_headings))
self.assertEqual(report_row[-1], MISSING_VALUE)

def test_jinja_template_rendering(self):
"""Test Jinja template rendering with data from the input item."""
item_cls = create_input_item_cls(['name', 'type'])
# This data is missing a value for the 'complex_data' key
data = {'name': 'foo', 'type': 'configuration'}
item = item_cls(data)

template_str = "name: {{name}} and type: {{type}}"
rendered_output = item.render_jinja_template(template_str)
expected_output = "name: foo and type: configuration"
self.assertEqual(rendered_output, expected_output)
item_cls = create_input_item_cls()
item_cls.report_attrs = item_cls.report_attrs + ['nonexistent_property']
report_row = item_cls(data, self.mock_instance, 0, self.jinja_env).report_row()
self.assertEqual(['foo', 'configuration', None, MISSING_VALUE], report_row)

def test_jinja_rendered_string_value(self):
"""Test that jinja_rendered works on a string value"""
data = {'name': 'foo{{default.suffix}}', 'type': 'configuration-{{default.system_name}}'}
item_cls = create_input_item_cls()
item = item_cls(data, self.mock_instance, 0, self.jinja_env)
self.assertEqual('foo-bar', item.name)
self.assertEqual('configuration-ryan', item.type)

def test_jinja_rendered_complex_value(self):
"""Test that jinja_rendered works on a more complex value"""
data = {
'name': 'foo',
'type': 'configuration',
'complex_data': {
'kitchen': {
'furniture': ['{{furniture.brand}} table', '{{furniture.brand}} chair'],
'appliances': ['oven', 'fridge'],
'color': '{{paint.primary}}'
},
'living_room': {
'furniture': ['couch', 'chair', 'rug'],
'appliances': ['lamp', 'tv'],
'color': '{{paint.secondary}}'
}
}
}
self.jinja_env.globals = {
'furniture': {
'brand': 'IKEA'
},
'paint': {
'primary': 'blue',
'secondary': 'red'
}
}
item_cls = create_input_item_cls()
item = item_cls(data, self.mock_instance, 0, self.jinja_env)
self.assertEqual(
{
'kitchen': {
'furniture': ['IKEA table', 'IKEA chair'],
'appliances': ['oven', 'fridge'],
'color': 'blue'
},
'living_room': {
'furniture': ['couch', 'chair', 'rug'],
'appliances': ['lamp', 'tv'],
'color': 'red'
}
},
item.complex_data
)

def test_jinja_template_rendering_undefined_variable(self):
"""Test jinja_rendered property that uses an undefined variable."""
item_cls = create_input_item_cls()
data = {'name': '{{undefined_variable}}', 'type': '{{nested.undefined_variable}}'}
item = item_cls(data, self.mock_instance, 0, self.jinja_env)

self.assertEqual('', item.name)
err_regex = (r"Failed to render Jinja2 template {{nested\.undefined_variable}} "
r"for value type: 'nested' is undefined")
with self.assertRaisesRegex(item_cls.template_render_err, err_regex):
item.type

def test_jinja_template_rendering_undefined_variable_nested(self):
"""Test jinja_rendered property that uses an undefined variable in a nested field."""
item_cls = create_input_item_cls()
data = {
'name': 'foo',
'type': 'configuration',
'complex_data': {
'kitchen': {
'color': '{{colors.primary}}'
}
}
}
item = item_cls(data, self.mock_instance, 0, self.jinja_env)

err_regex = (r"Failed to render Jinja2 template {{colors.primary}} "
r"for value complex_data: 'colors' is undefined")
with self.assertRaisesRegex(item_cls.template_render_err, err_regex):
item.complex_data
20 changes: 15 additions & 5 deletions tests/cli/bootprep/input/test_session_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ def setUp(self):
self.input_instance = Mock()
self.index = 0
self.jinja_env = SandboxedEnvironment()
self.jinja_env.globals = {
'default': {
'system_name': 'drax',
'site_domain': 'example.com',
}
}
self.bos_client = Mock()
self.mock_base_boot_set_data = {
'rootfs_provider': 'cpss3'
Expand Down Expand Up @@ -93,16 +99,20 @@ def get_input_and_expected_bos_data(self, name='my-session-template',
for boot_set_name, boot_set_arch in arch_by_bootset.items():
input_bootsets[boot_set_name] = {
'node_roles_groups': ['Compute'],
'rootfs_provider_passthrough': "dvs:api-gw-service-nmn.local:300:hsn0,nmn0:0"
'rootfs_provider': 'sbps',
'rootfs_provider_passthrough': ('sbps:v1:iqn.2023-06.csm.iscsi:_sbps-hsn._tcp.'
'{{default.system_name}}.{{default.site_domain}}:300')
}
bos_payload_bootsets[boot_set_name] = {
bos_payload_bootsets[boot_set_name] = self.mock_base_boot_set_data.copy()
bos_payload_bootsets[boot_set_name].update({
'node_roles_groups': ['Compute'],
'rootfs_provider_passthrough': "dvs:api-gw-service-nmn.local:300:hsn0,nmn0:0",
'rootfs_provider': 'sbps',
'rootfs_provider_passthrough': ('sbps:v1:iqn.2023-06.csm.iscsi:_sbps-hsn._tcp.'
'drax.example.com:300'),
'path': self.mock_path,
'etag': self.mock_etag,
'type': self.mock_image_type
}
bos_payload_bootsets[boot_set_name].update(self.mock_base_boot_set_data)
})
if boot_set_arch:
input_bootsets[boot_set_name]['arch'] = boot_set_arch
bos_payload_bootsets[boot_set_name]['arch'] = boot_set_arch
Expand Down

0 comments on commit 2fa4583

Please sign in to comment.