From 01f043524114579908a1863371976d06d2192f81 Mon Sep 17 00:00:00 2001 From: Patrick Ogenstad Date: Wed, 4 Feb 2026 17:09:35 +0100 Subject: [PATCH] Raise SDK specific Jinja errors on syntax error --- infrahub_sdk/template/__init__.py | 11 ++++-- tests/unit/sdk/test_template.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/infrahub_sdk/template/__init__.py b/infrahub_sdk/template/__init__.py index 910ec216..ff866ecd 100644 --- a/infrahub_sdk/template/__init__.py +++ b/infrahub_sdk/template/__init__.py @@ -82,7 +82,10 @@ def get_variables(self) -> list[str]: if self.is_file_based and env.loader: template_source = env.loader.get_source(env, self._template)[0] - template = env.parse(template_source) + try: + template = env.parse(template_source) + except jinja2.TemplateSyntaxError as exc: + self._raise_template_syntax_error(error=exc) return sorted(meta.find_undeclared_variables(template)) @@ -96,7 +99,11 @@ def validate(self, restricted: bool = True) -> None: if self.is_file_based and env.loader: template_source = env.loader.get_source(env, self._template)[0] - template = env.parse(template_source) + try: + template = env.parse(template_source) + except jinja2.TemplateSyntaxError as exc: + self._raise_template_syntax_error(error=exc) + for node in template.find_all(nodes.Filter): if node.name not in allowed_list: raise JinjaTemplateOperationViolationError(f"The '{node.name}' filter isn't allowed to be used") diff --git a/tests/unit/sdk/test_template.py b/tests/unit/sdk/test_template.py index 2554dc46..016ed45b 100644 --- a/tests/unit/sdk/test_template.py +++ b/tests/unit/sdk/test_template.py @@ -42,6 +42,14 @@ class JinjaTestCaseFailing: error: JinjaTemplateError +@dataclass +class JinjaSyntaxErrorTestCase: + name: str + template: str + expected_message: str + expected_lineno: int + + SUCCESSFUL_STRING_TEST_CASES = [ JinjaTestCase( name="hello-world", @@ -253,6 +261,56 @@ async def test_manage_unhandled_error() -> None: assert exc.value.message == "division by zero" +SYNTAX_ERROR_TEST_CASES = [ + JinjaSyntaxErrorTestCase( + name="empty-expression", + template="{{ }} testing", + expected_message="Expected an expression, got 'end of print statement'", + expected_lineno=1, + ), + JinjaSyntaxErrorTestCase( + name="missing-closing-end-if", + template="Hello {% if name is undefined %}stranger{% else %}{{name}}{% endif", + expected_message="unexpected end of template, expected 'end of statement block'.", + expected_lineno=1, + ), + JinjaSyntaxErrorTestCase( + name="fail-on-line-2", + template="Hello \n{{ name }", + expected_message="unexpected '}'", + expected_lineno=2, + ), +] + + +@pytest.mark.parametrize( + "test_case", + [pytest.param(tc, id=tc.name) for tc in SYNTAX_ERROR_TEST_CASES], +) +def test_get_variables_syntax_error(test_case: JinjaSyntaxErrorTestCase) -> None: + """Test that get_variables() raises JinjaTemplateSyntaxError for invalid templates.""" + jinja = Jinja2Template(template=test_case.template) + with pytest.raises(JinjaTemplateSyntaxError) as exc: + jinja.get_variables() + + assert exc.value.message == test_case.expected_message + assert exc.value.lineno == test_case.expected_lineno + + +@pytest.mark.parametrize( + "test_case", + [pytest.param(tc, id=tc.name) for tc in SYNTAX_ERROR_TEST_CASES], +) +def test_validate_syntax_error(test_case: JinjaSyntaxErrorTestCase) -> None: + """Test that validate() raises JinjaTemplateSyntaxError for invalid templates.""" + jinja = Jinja2Template(template=test_case.template) + with pytest.raises(JinjaTemplateSyntaxError) as exc: + jinja.validate() + + assert exc.value.message == test_case.expected_message + assert exc.value.lineno == test_case.expected_lineno + + async def test_validate_filter() -> None: jinja = Jinja2Template(template="{{ network | get_all_host }}") jinja.validate(restricted=False)