server-up/tests/test_ui_logica.py
Ramon ea9b1eb668
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 2m10s
v0.7.80-beta - Volgende-knop, tijdzones, netwerken per groepje, Pangolin
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
2026-07-28 22:25:38 +02:00

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