172 lines
6.5 KiB
Python
172 lines
6.5 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
|
|
|
|
|
|
def test_webmetadata_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"]["web"] = {"scheme": "HTTPS", "port": "8443"}
|
|
pad.write_text(json.dumps(data), encoding="utf-8")
|
|
|
|
assert bp.metadata(src)["web"] == {"scheme": "https", "port": 8443}
|
|
|
|
|
|
@pytest.mark.parametrize("web", [
|
|
{"scheme": "ftp", "port": 21},
|
|
{"scheme": "https", "port": 70000},
|
|
{"scheme": "https", "port": "niet-een-poort"},
|
|
])
|
|
def test_ongeldige_webmetadata_wordt_genegeerd(tmp_path, web):
|
|
src = _maak_boilerplate(tmp_path, "services: {}\n")
|
|
pad = src / "template.json"
|
|
data = json.loads(pad.read_text(encoding="utf-8"))
|
|
data["metadata"]["web"] = web
|
|
pad.write_text(json.dumps(data), encoding="utf-8")
|
|
|
|
assert bp.metadata(src)["web"] == {}
|