Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 2s
Rollen per gebruiker: - Drie rollen: beheerder (alles), operator (stacks, containers en backups) en alleen lezen. Afgedwongen in de before_request-guard, niet alleen in de UI. - Eigen wachtwoord wijzigen mag iedereen; de laatste beheerder kan niet gedegradeerd of verwijderd worden. - Proxy-gebruikers zonder eigen account krijgen AUTH.proxy_role; bestaat er wel een lokaal account met die naam, dan wint dat account. - Accounts van voor deze versie hebben geen rol en gelden als beheerder. Rechtenescalatie verholpen: - set_password verving het hele gebruikersrecord, waardoor het role-veld wegviel. Een ontbrekende rol geldt als beheerder (nodig voor bestaande accounts), dus elke operator of viewer kon zichzelf tot beheerder promoveren door zijn eigen wachtwoord te wijzigen. Gevonden bij het naspelen van de rollenflow tegen een draaiende instantie. Geplande backups draaiden nooit: - De scheduler stempelde ook taken af die zichzelf hadden overgeslagen. De backup-taak bewaakt zelf het ingestelde uur, dus die werd afgestempeld op het moment van de eerste tick; 24 uur later viel het volgende moment op precies dat verkeerde tijdstip. Een taak stempelt nu alleen af bij True. De backup-taak draait op een kort interval en beslist zelf, zodat een gewijzigde planning meteen werkt. BACKUP_SCHEDULE_DAY toegevoegd voor wekelijks. Verder: - Zoekveld en tagfilters in de app store; repo's zonder treffers vallen weg. - Opstartcontrole op de meegeleverde front-end-bestanden: mislukt het downloaden tijdens de image-build, dan laadde de UI zonder opmaak terwijl de server prima leek te draaien. Nu een duidelijke melding in het log. - 227 tests (15 nieuwe voor rollen, 15 voor de scheduler). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
"""Vertalingen: nl en en moeten dezelfde sleutels hebben, en de UI mag geen
|
|
sleutels gebruiken die nergens gedefinieerd zijn.
|
|
|
|
Zonder deze test loopt de vertaling stilletjes scheef zodra iemand een scherm
|
|
toevoegt — precies wat er bij de auth-, netwerk- en update-schermen gebeurde.
|
|
"""
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
WORTEL = Path(__file__).resolve().parent.parent / "server-up"
|
|
VERTALINGEN = WORTEL / "translations"
|
|
TEMPLATE = WORTEL / "templates" / "index.html"
|
|
|
|
|
|
def _laad(code: str) -> dict:
|
|
data = json.loads((VERTALINGEN / f"{code}.json").read_text(encoding="utf-8"))
|
|
return {k: v for k, v in data.items() if not k.startswith("_")}
|
|
|
|
|
|
def test_nl_en_en_hebben_dezelfde_sleutels():
|
|
nl, en = _laad("nl"), _laad("en")
|
|
alleen_nl = sorted(set(nl) - set(en))
|
|
alleen_en = sorted(set(en) - set(nl))
|
|
assert not alleen_nl, f"Ontbreekt in en.json: {alleen_nl}"
|
|
assert not alleen_en, f"Ontbreekt in nl.json: {alleen_en}"
|
|
|
|
|
|
@pytest.mark.parametrize("code", ["nl", "en"])
|
|
def test_geen_lege_vertalingen(code):
|
|
leeg = [k for k, v in _laad(code).items() if not str(v).strip()]
|
|
assert not leeg, f"Lege waarden in {code}.json: {leeg}"
|
|
|
|
|
|
@pytest.mark.parametrize("code", ["nl", "en"])
|
|
def test_meta_is_compleet(code):
|
|
data = json.loads((VERTALINGEN / f"{code}.json").read_text(encoding="utf-8"))
|
|
meta = data.get("_meta", {})
|
|
assert meta.get("code") == code
|
|
assert meta.get("name")
|
|
|
|
|
|
def test_plaatshouders_komen_overeen():
|
|
"""`{user}` in het Nederlands moet ook in het Engels staan, anders valt er
|
|
bij het vertalen een variabele weg."""
|
|
nl, en = _laad("nl"), _laad("en")
|
|
for sleutel, tekst in nl.items():
|
|
vars_nl = set(re.findall(r"\{(\w+)\}", str(tekst)))
|
|
vars_en = set(re.findall(r"\{(\w+)\}", str(en.get(sleutel, ""))))
|
|
assert vars_nl == vars_en, f"{sleutel}: nl={vars_nl} en={vars_en}"
|
|
|
|
|
|
def test_ui_gebruikt_alleen_bestaande_sleutels():
|
|
regels = TEMPLATE.read_text(encoding="utf-8").splitlines()
|
|
# Commentaarregels overslaan: die noemen t('key') als voorbeeld.
|
|
html = "\n".join(r for r in regels if not r.lstrip().startswith(("//", "*", "/*")))
|
|
# Alleen letterlijke sleutels: `t('foo')` en `t('foo', {...})`. Dynamisch
|
|
# samengestelde sleutels zoals t('role_'+r) vallen hierbuiten en worden
|
|
# afzonderlijk gecontroleerd in test_dynamische_sleutels_bestaan.
|
|
gebruikt = set(re.findall(r"\bt\('([a-z0-9_]+)'\s*[,)]", html))
|
|
bekend = set(_laad("nl"))
|
|
onbekend = sorted(gebruikt - bekend)
|
|
assert not onbekend, f"t() verwijst naar niet-bestaande sleutels: {onbekend}"
|
|
|
|
|
|
def test_dynamische_sleutels_bestaan():
|
|
"""Sleutels die de UI opbouwt uit een voorvoegsel plus een waarde."""
|
|
nl = _laad("nl")
|
|
for rol in ("admin", "operator", "viewer"):
|
|
assert f"role_{rol}" in nl
|
|
for plan in ("off", "daily", "weekly"):
|
|
assert f"backup_schedule_{plan}" in nl
|
|
|
|
|
|
def test_nieuwe_schermen_zijn_vertaald():
|
|
"""Steekproef op de schermen die in v0.5.x zijn toegevoegd."""
|
|
nl = _laad("nl")
|
|
for sleutel in ("login", "setup_title", "security", "auth_mode",
|
|
"trusted_proxies", "networks", "own_ip_hint", "channel",
|
|
"update_now", "rollback", "port_taken", "loading"):
|
|
assert sleutel in nl, f"vertaalsleutel ontbreekt: {sleutel}"
|
|
|
|
|
|
def test_i18n_module_leest_beide_talen(env):
|
|
from core import i18n
|
|
i18n.load()
|
|
codes = {t["code"] for t in i18n.available()}
|
|
assert {"nl", "en"} <= codes
|
|
# strings() valt terug op het Engels voor ontbrekende sleutels.
|
|
assert i18n.strings("nl")["login"]
|
|
assert i18n.strings("en")["login"]
|