All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 2m10s
De knop Volgende deed niets. Alpine haalt een boolean-attribuut alleen weg bij null, undefined of false; het getal 0 zet het juist AAN. Mijn :disabled="stepMissing().length" gaf bij een ingevuld formulier 0 en zette de knop dus permanent uit. De logica klopte, de binding niet — met losse logica niet te vinden, dus er draait nu een test die de echte modal met Alpine in een DOM rendert en erop klikt. Tijdzones: - Nieuwe instelling TIMEZONE, standaard die van de server (TZ, /etc/timezone, /etc/localtime). In alle 77 sjablonen stond Europe/Amsterdam hard ingevuld. - Apps zonder tijdzoneveld krijgen TZ er automatisch bij; die draaiden in UTC. Poorten: - _bezette_poorten() telt nu ook mee waar de host zelf op luistert, gelezen uit /proc/net/tcp in een hulpcontainer met --network host. Een nginx buiten Docker zag Server Up eerder niet. - GET /api/ports/check geeft vrij/bezet plus een alternatief, en meldt of de host echt gecontroleerd kon worden. Netwerken: - APP_NETWORKS: meerdere gedeelde bridge-netwerken in plaats van één waar élke app aan hing. Per app kies je welke; aanmaken kan vanuit het invulmenu, met eigen naam of een voorstel (su-<appnaam>). - add_shared_network() accepteert een lijst; één naam blijft werken. - GET /api/networks/<n>/ip-check controleert subnet, bereik, Docker-toewijzing en de ARP-tabel van de host (vangt een fysiek apparaat met DHCP-adres) en stelt een vrij adres voor. Pangolin (nieuw, core/pangolin.py): - Publieke URL per app via een tunnel, zonder poorten open te zetten. - De API veranderde in 1.9 (resource hing onder een site, staat nu los); beide routes worden geprobeerd en bij een fout krijg je beide meldingen. - API-sleutel is write-only. Getest tegen een nagebouwde API, niet tegen een echte server. Nieuw: docs/pangolin.md; docs/netwerken.md uitgebreid. 2369 tests groen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
49 lines
2 KiB
Python
49 lines
2 KiB
Python
"""De logica van het invulmenu, uitgevoerd zoals de browser het doet.
|
|
|
|
De stapindeling, de geavanceerd-schakelaar en het blokkeren van 'Volgende'
|
|
zitten in JavaScript. Een Python-test kan daar niets over zeggen, dus draaien we
|
|
de echte component uit index.html in Node. Zonder Node slaat dit zichzelf over.
|
|
"""
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
WORTEL = Path(__file__).resolve().parent.parent
|
|
SCRIPT = WORTEL / "tests" / "js" / "menu.test.js"
|
|
|
|
|
|
@pytest.mark.skipif(not shutil.which("node"), reason="node ontbreekt")
|
|
def test_invulmenu_logica():
|
|
r = subprocess.run(["node", str(SCRIPT)], capture_output=True, text=True,
|
|
timeout=60, cwd=str(WORTEL))
|
|
assert r.returncode == 0, r.stdout + r.stderr
|
|
|
|
|
|
@pytest.mark.skipif(not shutil.which("node"), reason="node ontbreekt")
|
|
def test_installatiemodal_in_een_echte_dom():
|
|
"""Rendert de modal met Alpine en klikt op Volgende.
|
|
|
|
Losse logica zegt niets over de binding: `:disabled="lijst.length"` gaf bij
|
|
een lege lijst 0, en Alpine haalt een boolean-attribuut alleen weg bij
|
|
null, undefined of false — dus de knop stond juist uit.
|
|
"""
|
|
r = subprocess.run(["node", str(WORTEL / "tests" / "js" / "modal.dom.test.js")],
|
|
capture_output=True, text=True, timeout=90, cwd=str(WORTEL))
|
|
if r.returncode == 2:
|
|
pytest.skip(r.stdout.strip() or "jsdom ontbreekt")
|
|
assert r.returncode == 0, r.stdout + r.stderr
|
|
|
|
|
|
@pytest.mark.skipif(not shutil.which("node"), reason="node ontbreekt")
|
|
def test_javascript_van_de_pagina_parseert():
|
|
"""Een typefout in de component maakt de hele interface blanco; dat wil je
|
|
niet pas in de browser ontdekken."""
|
|
import re
|
|
html = (WORTEL / "server-up" / "templates" / "index.html").read_text(encoding="utf-8")
|
|
blokken = re.findall(r"<script[^>]*>(.*?)</script>", html, re.S)
|
|
grootste = max(blokken, key=len)
|
|
r = subprocess.run(["node", "--check", "-"], input=grootste,
|
|
capture_output=True, text=True, timeout=30)
|
|
assert r.returncode == 0, r.stderr
|