server-up/tests/test_boilerplates.py
Ramon 1575db42c2
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 5m5s
v0.8.14-beta - Raspberry Pi compatibiliteit en UniFi Mongo herstellen
2026-08-04 16:49:24 +02:00

147 lines
5.6 KiB
Python

"""Boilerplate-rendering: de sandbox, en de YAML-opruiming die conditionele
blokken achterlaten."""
import json
import stat
import pytest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "server-up"))
from core import boilerplates as bp
def _maak_boilerplate(tmp_path, compose: str, variables=None) -> Path:
d = tmp_path / "stack"
(d / "files").mkdir(parents=True)
(d / "template.json").write_text(json.dumps({
"kind": "compose",
"metadata": {"name": "Test", "description": "", "tags": []},
"variables": variables if variables is not None else [{
"title": "Algemeen",
"items": [
{"name": "service_name", "type": "str", "default": "test"},
{"name": "port", "type": "int", "default": 8080},
{"name": "admin", "type": "bool", "default": False},
],
}],
}), encoding="utf-8")
(d / "files" / "compose.yaml").write_text(compose, encoding="utf-8")
return d
def test_render_vult_variabelen_in(tmp_path):
src = _maak_boilerplate(tmp_path, "services:\n << service_name >>:\n image: nginx\n"
" ports:\n - \"<< port >>:80\"\n")
dest = tmp_path / "out"
bp.render_to_dir(src, dest, {"service_name": "web", "port": 9090})
tekst = (dest / "docker-compose.yml").read_text()
assert "web:" in tekst
assert '"9090:80"' in tekst
def test_conditioneel_blok_wordt_overgeslagen(tmp_path):
src = _maak_boilerplate(
tmp_path,
"services:\n app:\n image: nginx\n"
" environment:\n"
"<% if admin %>\n - ADMIN=1\n<% endif %>\n")
dest = tmp_path / "out"
bp.render_to_dir(src, dest, {"admin": False})
tekst = (dest / "docker-compose.yml").read_text()
assert "ADMIN" not in tekst
# `environment:` zonder inhoud is ongeldige YAML en moet weg zijn.
assert "environment:" not in tekst
def test_lege_mapping_aan_einde_verdwijnt():
assert bp._drop_empty_mappings("services:\n app:\n image: x\n volumes:\n") \
== "services:\n app:\n image: x\n"
def test_gevulde_mapping_blijft_staan():
tekst = "services:\n app:\n image: x\n"
assert bp._drop_empty_mappings(tekst) == tekst
def test_sandbox_geeft_geen_python_internals_prijs(tmp_path):
"""Templates komen uit externe git-repo's en worden al gerenderd in
/api/store/preview. De sandbox levert Undefined (leeg) voor afgeschermde
attributen, zodat er nooit een echt Python-object in het resultaat komt."""
src = _maak_boilerplate(tmp_path, "x: << service_name.__class__ >>\n")
bp.render_to_dir(src, tmp_path / "out", {"service_name": "web"})
tekst = (tmp_path / "out" / "docker-compose.yml").read_text()
# De waarde blijft leeg; `x:` zelf blijft staan omdat alleen bekende
# compose-sleutels worden opgeruimd (zie _OPRUIMBARE_SLEUTELS).
assert tekst.strip() == "x:"
assert "class" not in tekst
assert "object" not in tekst
def test_sandbox_laat_aanroep_van_internals_falen(tmp_path):
"""De klassieke SSTI-keten faalt hard in plaats van de subclass-lijst —
en daarmee een weg naar os.system — op te leveren."""
src = _maak_boilerplate(
tmp_path, "x: << ''.__class__.__mro__[1].__subclasses__() >>\n")
with pytest.raises(bp.BoilerplateError):
bp.render_to_dir(src, tmp_path / "out", {})
def test_normale_filters_blijven_werken(tmp_path):
"""De sandbox mag de bestaande templates niet breken — vaultwarden gebruikt
bijvoorbeeld `<< signups_allowed | lower >>`."""
src = _maak_boilerplate(
tmp_path, "a: << service_name | upper >>\nb: << admin | lower >>\n")
bp.render_to_dir(src, tmp_path / "out", {"service_name": "web", "admin": True})
tekst = (tmp_path / "out" / "docker-compose.yml").read_text()
assert "a: WEB" in tekst
assert "b: true" in tekst
def test_is_boilerplate_herkent_layout(tmp_path):
src = _maak_boilerplate(tmp_path, "services: {}\n")
assert bp.is_boilerplate(src)
assert not bp.is_boilerplate(tmp_path)
def test_architectuurmetadata_wordt_genormaliseerd(tmp_path):
src = _maak_boilerplate(tmp_path, "services: {}\n")
pad = src / "template.json"
data = json.loads(pad.read_text(encoding="utf-8"))
data["metadata"]["architectures"] = ["x86_64", "aarch64", "arm64", "onbekend"]
data["metadata"]["architecture_note"] = "Gebruik een 64-bits OS."
pad.write_text(json.dumps(data), encoding="utf-8")
meta = bp.metadata(src)
assert meta["architectures"] == ["amd64", "arm64"]
assert meta["architecture_note"] == "Gebruik een 64-bits OS."
def test_explicitiete_architectuur_mismatch_wordt_geweigerd():
meta = {"architectures": ["amd64", "arm64"],
"architecture_note": "Gebruik een 64-bits OS."}
assert bp.compatibility(meta, "aarch64")["supported"] is True
fout = bp.compatibility(meta, "armv7l")
assert fout["known"] is True
assert fout["supported"] is False
assert "ARM 32-bit" in fout["reason"]
assert "64-bits OS" in fout["reason"]
def test_onbekende_architectuurmetadata_blokkeert_niet():
assert bp.compatibility({}, "armv7l")["supported"] is True
assert bp.compatibility({"architectures": ["vreemd"]}, "armv7l")["supported"] is True
def test_render_behoudt_uitvoerbaar_recht_van_tekstscript(tmp_path):
src = _maak_boilerplate(tmp_path, "services: {}\n")
script = src / "files" / "init.sh"
script.write_text("#!/bin/sh\necho << service_name >>\n", encoding="utf-8")
script.chmod(0o755)
dest = tmp_path / "out"
bp.render_to_dir(src, dest, {"service_name": "test"})
assert (dest / "init.sh").stat().st_mode & stat.S_IXUSR