Make templates more faithful to the original file in terms of indentation, newlines, no deserialisation of things like < or >. More test coverage
This commit is contained in:
parent
3d53d4fb30
commit
49fee7afe4
12 changed files with 579 additions and 52 deletions
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from jinja2 import Environment
|
||||
|
||||
from jinjaturtle.core import (
|
||||
parse_config,
|
||||
|
|
@ -13,6 +15,7 @@ from jinjaturtle.core import (
|
|||
generate_jinja2_template,
|
||||
)
|
||||
from jinjaturtle.handlers.json import JsonHandler
|
||||
from jinjaturtle.loop_analyzer import LoopCandidate
|
||||
|
||||
SAMPLES_DIR = Path(__file__).parent / "samples"
|
||||
|
||||
|
|
@ -35,23 +38,136 @@ def test_json_roundtrip():
|
|||
assert defaults["foobar_nested_a"] == 1
|
||||
# Booleans are now preserved as booleans (not stringified)
|
||||
assert defaults["foobar_nested_b"] is True
|
||||
# List should be a list (not flattened to scalars)
|
||||
assert defaults["foobar_list"] == [10, 20]
|
||||
# JSON stays scalar/index-based so source formatting can be preserved.
|
||||
assert loop_candidates == []
|
||||
assert defaults["foobar_list_0"] == 10
|
||||
assert defaults["foobar_list_1"] == 20
|
||||
|
||||
# Template generation with loops
|
||||
template = generate_jinja2_template("json", parsed, "foobar", None, loop_candidates)
|
||||
|
||||
# Template should use | tojson for type preservation
|
||||
assert "{{ foobar_foo | tojson }}" in template
|
||||
assert "{{ foobar_nested_a | tojson }}" in template
|
||||
assert "{{ foobar_nested_b | tojson }}" in template
|
||||
# Template should use Ansible | to_json for type preservation without
|
||||
# HTML-safe escaping.
|
||||
assert "{{ foobar_foo | to_json(ensure_ascii=False) }}" in template
|
||||
assert "{{ foobar_nested_a | to_json(ensure_ascii=False) }}" in template
|
||||
assert "{{ foobar_nested_b | to_json(ensure_ascii=False) }}" in template
|
||||
assert "{{ foobar_list_0 | to_json(ensure_ascii=False) }}" in template
|
||||
assert "{{ foobar_list_1 | to_json(ensure_ascii=False) }}" in template
|
||||
assert "{% for" not in template
|
||||
|
||||
# List should use loop (not scalar indices)
|
||||
assert "{% for" in template
|
||||
assert "foobar_list" in template
|
||||
# Should NOT have scalar indices
|
||||
assert "foobar_list_0" not in template
|
||||
assert "foobar_list_1" not in template
|
||||
|
||||
def test_json_template_preserves_original_indentation_and_final_newline_state(
|
||||
tmp_path: Path,
|
||||
):
|
||||
json_text = (
|
||||
"{\n"
|
||||
' "default-runtime": "runsc",\n'
|
||||
' "runtimes": {\n'
|
||||
' "runsc": {\n'
|
||||
' "path": "/usr/bin/runsc"\n'
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}"
|
||||
)
|
||||
path = tmp_path / "daemon.json"
|
||||
path.write_text(json_text, encoding="utf-8")
|
||||
|
||||
fmt, parsed = parse_config(path)
|
||||
template = generate_jinja2_template(fmt, parsed, "docker", json_text, [])
|
||||
|
||||
assert ' "default-runtime"' in template
|
||||
assert ' "runsc"' in template
|
||||
assert ' "path"' in template
|
||||
assert not template.endswith("\n")
|
||||
|
||||
|
||||
def test_json_template_uses_non_html_safe_json_filter_for_angle_brackets(
|
||||
tmp_path: Path,
|
||||
):
|
||||
json_text = '{"version_requirement": ">= 8.0.0 < 9.0.0"}\n'
|
||||
path = tmp_path / "metadata.json"
|
||||
path.write_text(json_text, encoding="utf-8")
|
||||
|
||||
fmt, parsed = parse_config(path)
|
||||
template = generate_jinja2_template(fmt, parsed, "puppet", json_text, [])
|
||||
|
||||
assert "to_json(ensure_ascii=False)" in template
|
||||
assert "tojson" not in template
|
||||
|
||||
|
||||
def test_json_inline_object_array_roundtrips_without_expanding(tmp_path: Path):
|
||||
json_text = (
|
||||
"{\n"
|
||||
' "description": "Gets the last boot time of a Linux or Windows system",\n'
|
||||
' "implementations": [\n'
|
||||
' {"name": "last_boot_time_nix.sh", "requirements": ["shell"]},\n'
|
||||
' {"name": "last_boot_time_win.ps1", "requirements": ["powershell"]}\n'
|
||||
" ]\n"
|
||||
"}\n"
|
||||
)
|
||||
path = tmp_path / "metadata.json"
|
||||
path.write_text(json_text, encoding="utf-8")
|
||||
|
||||
fmt, parsed = parse_config(path)
|
||||
loop_candidates = analyze_loops(fmt, parsed)
|
||||
flat_items = flatten_config(fmt, parsed, loop_candidates)
|
||||
defaults = yaml.safe_load(
|
||||
generate_ansible_yaml("fact", flat_items, loop_candidates)
|
||||
)
|
||||
template = generate_jinja2_template(fmt, parsed, "fact", json_text, loop_candidates)
|
||||
|
||||
env = Environment(keep_trailing_newline=True)
|
||||
env.filters["to_json"] = lambda value, **kwargs: json.dumps(value, **kwargs)
|
||||
rendered = env.from_string(template).render(**defaults)
|
||||
|
||||
assert loop_candidates == []
|
||||
assert '{"name": {{ fact_implementations_0_name' in template
|
||||
assert rendered == json_text
|
||||
|
||||
|
||||
def test_json_direct_loop_generation_renders_valid_json_without_joined_objects():
|
||||
items = [
|
||||
{"name": "last_boot_time_nix.sh", "requirements": ["shell"]},
|
||||
{"name": "last_boot_time_win.ps1", "requirements": ["powershell"]},
|
||||
]
|
||||
parsed = {"implementations": items}
|
||||
candidate = LoopCandidate(
|
||||
path=("implementations",),
|
||||
loop_var="implementation",
|
||||
items=items,
|
||||
item_schema="simple_dict",
|
||||
)
|
||||
handler = JsonHandler()
|
||||
template = handler.generate_jinja2_template_with_loops(
|
||||
parsed, "fact", None, [candidate]
|
||||
)
|
||||
|
||||
env = Environment(keep_trailing_newline=True)
|
||||
env.filters["to_json"] = lambda value, **kwargs: json.dumps(value, **kwargs)
|
||||
rendered = env.from_string(template).render(fact_implementations=items)
|
||||
|
||||
assert "}, {" not in rendered
|
||||
assert json.loads(rendered) == parsed
|
||||
|
||||
|
||||
def test_json_scalar_span_collector_empty_containers_and_invalid_json():
|
||||
handler = JsonHandler()
|
||||
|
||||
assert handler._collect_json_scalar_spans('{"empty": [], "obj": {}}') == []
|
||||
assert handler._collect_json_scalar_spans('{"unterminated": [1,}') is None
|
||||
|
||||
|
||||
def test_json_template_falls_back_when_source_scanner_cannot_collect_spans(monkeypatch):
|
||||
handler = JsonHandler()
|
||||
monkeypatch.setattr(handler, "_collect_json_scalar_spans", lambda _text: None)
|
||||
|
||||
template = handler.generate_jinja2_template(
|
||||
{"answer": 42}, "role", original_text='{"answer": 42}'
|
||||
)
|
||||
|
||||
assert (
|
||||
template
|
||||
== '{\n "answer": {{ role_answer | to_json(ensure_ascii=False) }}\n}\n'
|
||||
)
|
||||
|
||||
|
||||
def test_generate_jinja2_template_json_type_error():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue