Files
netbox-plugin-services/netbox_services/tests/test_templating.py
T
sven b1c9c7a40b modified: netbox_services/engine/__init__.py
modified:   netbox_services/engine/allocators.py
modified:   netbox_services/engine/context.py
new file:   netbox_services/engine/mappers.py
modified:   netbox_services/engine/resolver.py
new file:   netbox_services/engine/templating.py
modified:   netbox_services/forms.py
modified:   netbox_services/models.py
modified:   netbox_services/tests/test_allocators.py
new file:   netbox_services/tests/test_mappers.py
modified:   netbox_services/tests/test_resolver.py
new file:   netbox_services/tests/test_templating.py
Simplify layout to template and service instances
2026-08-17 15:12:42 +02:00

131 lines
4.4 KiB
Python

"""Tests for Jinja2 attribute templating with .. / ... parent access."""
import pytest
from netbox_services.engine.templating import (
preprocess_dots,
render_value,
render_attrs,
)
class TestPreprocessDots:
def test_double_dot(self):
assert preprocess_dots("{{ ..name }}") == "{{ p1.name }}"
def test_triple_dot(self):
assert preprocess_dots("{{ ...site.slug }}") == "{{ p2.site.slug }}"
def test_quadruple_dot(self):
assert preprocess_dots("{{ ....region.slug }}") == "{{ p3.region.slug }}"
def test_no_dots(self):
assert preprocess_dots("1000base-t") == "1000base-t"
def test_ip_address_preserved(self):
assert preprocess_dots("10.0.0.1/24") == "10.0.0.1/24"
def test_service_ref_preserved(self):
assert preprocess_dots("{{ service.tenant.slug }}") == "{{ service.tenant.slug }}"
def test_mixed(self):
assert (
preprocess_dots("rtr-{{ service.tenant.slug }}-{{ ..name }}-{{ ...site.slug }}")
== "rtr-{{ service.tenant.slug }}-{{ p1.name }}-{{ p2.site.slug }}"
)
def test_plain_string_no_braces(self):
assert preprocess_dots("active") == "active"
def test_empty_string(self):
assert preprocess_dots("") == ""
class TestRenderValue:
def test_literal_int(self):
assert render_value(42, None, [], 0) == 42
def test_literal_bool(self):
assert render_value(True, None, [], 0) is True
def test_plain_string(self):
assert render_value("active", None, [], 0) == "active"
def test_ip_address_string(self):
assert render_value("10.0.0.1/24", None, [], 0) == "10.0.0.1/24"
def test_jinja2_with_parent(self):
parent = {"name": "rtr-01"}
result = render_value("{{ p1.name }}", None, [parent], 0)
assert result == "rtr-01"
def test_jinja2_with_grandparent(self):
parent = {"name": "dev", "site": None}
grandparent = {"site": {"slug": "hq"}}
result = render_value("{{ p2.site.slug }}", None, [parent, grandparent], 0)
assert result == "hq"
def test_jinja2_with_service(self):
service = {"tenant": {"slug": "acme"}}
result = render_value("{{ service.tenant.slug }}", service, [], 0)
assert result == "acme"
def test_jinja2_with_index(self):
result = render_value("rtr-{{ index + 1 }}", None, [], 0)
assert result == "rtr-1"
def test_jinja2_dotted_parent_access(self):
parent = {"name": "edge-router", "site": {"slug": "hq"}}
result = render_value("{{ ..site.slug }}", None, [parent], 0)
assert result == "hq"
def test_jinja2_dotted_parent_name(self):
parent = {"name": "edge-router"}
result = render_value("iface-{{ ..name }}", None, [parent], 0)
assert result == "iface-edge-router"
def test_django_model_traversal(self):
from dcim.models import Device, DeviceRole, Site
site = Site(name="HQ", slug="hq")
role = DeviceRole(name="Router", slug="router")
parent = Device(name="rtr-01", site=site, role=role)
result = render_value("{{ p1.site.slug }}-{{ p1.role.slug }}", None, [parent], 0)
assert result == "hq-router"
class TestRenderAttrs:
def test_plain_attrs(self):
attrs = {"name": "eth0", "type": "1000base-t", "mtu": 1500}
result = render_attrs(attrs, None, [], 0)
assert result == {"name": "eth0", "type": "1000base-t", "mtu": 1500}
def test_templated_attrs(self):
parent = {"name": "rtr-01"}
attrs = {"name": "iface-{{ ..name }}", "type": "1000base-t"}
result = render_attrs(attrs, None, [parent], 0)
assert result == {"name": "iface-rtr-01", "type": "1000base-t"}
def test_empty_attrs(self):
assert render_attrs({}, None, [], 0) == {}
def test_none_attrs(self):
assert render_attrs(None, None, [], 0) == {}
def test_index_in_attrs(self):
attrs = {"name": "rtr-{{ index + 1 }}"}
assert render_attrs(attrs, None, [], 0) == {"name": "rtr-1"}
assert render_attrs(attrs, None, [], 2) == {"name": "rtr-3"}
class TestSandboxSecurity:
def test_blocks_dunder_class(self):
parent = {"name": "test"}
result = render_value("{{ p1.__class__ }}", None, [parent], 0)
assert "dict" not in str(result)
def test_blocks_method_call(self):
parent = {"name": "test"}
with pytest.raises(Exception):
render_value("{{ p1.pop() }}", None, [parent], 0)