server-up/tests/test_verplichte_velden.py
Ramon 58d9be1f48
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 2m1s
v0.7.90-beta - ARR-stack opgesplitst, /data-mount, netwerkopties per container
- Elk *arr-onderdeel is nu ook een losse app in de store (46 nieuwe sjablonen);
  los en gebundeld komen uit één bron (tools/arr_sjablonen.py) met een test die
  bewaakt dat ze niet uiteen lopen
- Alles deelt één /data-map, zodat hardlinks werken; losse /tv-, /movies- en
  /downloads-mounts zijn eruit, UMASK=002 erbij
- Nieuwe eerste stap "Onderdelen" in het invulmenu, met kopjes per soort; de
  dubbele groepsschakelaar en de lege kaarten in Instellingen zijn weg
- Eigen IP-adres per container in plaats van per stack; containers zonder adres
  houden hun poortmapping. Ook Pangolin publiceert nu per container
- Huntarr vervangen door NeutArr (project offline na lekken, image bestaat niet
  meer), Maintainerr naar zijn nieuwe organisatie
- 31 apps toegevoegd uit het *arr-ecosysteem, alle images tegen hun registry
  gecontroleerd
- data_dir hernoemd naar appdata_dir in de hele catalogus, naast de nieuwe
  data_root; qBittorrent van poort 8080 naar 8097

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
2026-07-31 22:21:04 +02:00

160 lines
6.5 KiB
Python

"""Verplichte velden mogen niet leeg blijven, en geheimen horen gemaskeerd.
Het formulier zette een sterretje achter een verplicht veld en hield verder
niets tegen: je kon een app installeren met een leeg databasewachtwoord of een
lege JWT-sleutel. De container start dan niet, of draait met een leeg geheim.
"""
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "server-up"))
from conftest import login
def _maak_bron(env, naam="testapp", items=None, groepen=None):
"""Zet een app-sjabloon neer waar _find_stack_src het verwacht."""
from core import git
wortel = git.cache_dir("server-up") / "apps" / naam
(wortel / "files").mkdir(parents=True, exist_ok=True)
variabelen = groepen or [{"title": "Algemeen", "items": items or []}]
(wortel / "template.json").write_text(json.dumps({
"kind": "compose",
"metadata": {"name": naam, "description": "test"},
"variables": variabelen,
}), encoding="utf-8")
(wortel / "files" / "compose.yaml").write_text(
"services:\n"
" << service_name >>:\n"
" image: nginx\n"
" environment:\n"
" - DB_PASSWORD=<< db_password >>\n", encoding="utf-8")
return wortel
VELDEN = [
{"name": "service_name", "type": "str", "title": "Naam",
"default": "testapp", "required": True},
{"name": "db_password", "type": "str", "title": "Databasewachtwoord",
"default": "", "required": True},
]
def test_leeg_verplicht_veld_wordt_geweigerd(client, env):
csrf = login(client)
_maak_bron(env, items=VELDEN)
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t1", "values": {"db_password": ""}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400, r.get_json()
d = r.get_json()
assert "Databasewachtwoord" in d["msg"]
assert "Databasewachtwoord" in d["missing"]
def test_ontbrekend_veld_wordt_geweigerd(client, env):
"""Niet meegestuurd is net zo fout als leeg meegestuurd."""
csrf = login(client)
_maak_bron(env, items=VELDEN)
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t2", "values": {}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "Databasewachtwoord" in r.get_json()["missing"]
def test_alleen_spaties_telt_als_leeg(client, env):
csrf = login(client)
_maak_bron(env, items=VELDEN)
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t3", "values": {"db_password": " "}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
def test_ingevuld_veld_gaat_door(client, env):
csrf = login(client)
_maak_bron(env, items=VELDEN)
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t4", "values": {"db_password": "geheim123"}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 200, r.get_json()
assert r.get_json().get("job_id")
def test_veld_met_een_default_hoeft_niet_meegestuurd(client, env):
"""service_name heeft een standaardwaarde; die telt als ingevuld."""
csrf = login(client)
_maak_bron(env, items=VELDEN)
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t5", "values": {"db_password": "x"}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 200, r.get_json()
def test_veld_in_uitgeschakelde_groep_telt_niet_mee(client, env):
"""Staat de groepsschakelaar uit, dan komt het veld niet in compose terecht
en mag het dus leeg blijven."""
csrf = login(client)
_maak_bron(env, groepen=[
{"title": "Algemeen", "items": [VELDEN[0]]},
{"title": "Database", "toggle": "gebruik_db", "toggle_default": False,
"items": [{"name": "db_password", "type": "str", "title": "Wachtwoord",
"default": "", "required": True}]},
])
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t6",
"values": {"gebruik_db": False, "db_password": ""}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 200, r.get_json()
def test_veld_in_ingeschakelde_groep_telt_wel_mee(client, env):
csrf = login(client)
_maak_bron(env, groepen=[
{"title": "Algemeen", "items": [VELDEN[0]]},
{"title": "Database", "toggle": "gebruik_db", "toggle_default": True,
"items": [{"name": "db_password", "type": "str", "title": "Wachtwoord",
"default": "", "required": True}]},
])
r = client.post("/api/store/install",
json={"stack": "testapp", "repo_id": "server-up",
"instance": "t7",
"values": {"gebruik_db": True, "db_password": ""}},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "Wachtwoord" in r.get_json()["missing"]
# ── Geheimen herkennen ───────────────────────────────────────────────────────
@pytest.mark.parametrize("naam", [
"db_password", "admin_password", "jwt_secret", "secret_key", "api_key",
"app_key", "encryption_key", "admin_token", "hass_token",
])
def test_geheime_velden_worden_herkend(env, naam):
"""Zonder deze markering toonde de interface het wachtwoord als gewone
tekst én verscheen de dobbelsteenknop niet, terwijl de beschrijving van
zeventien apps ernaar verwijst."""
from core import boilerplates
wortel = _maak_bron(env, items=[
{"name": naam, "type": "str", "title": "X", "default": "", "required": True}])
veld = boilerplates.fields(wortel)[0]
assert veld["secret"] is True
@pytest.mark.parametrize("naam", ["service_name", "port", "appdata_dir", "timezone"])
def test_gewone_velden_zijn_geen_geheim(env, naam):
from core import boilerplates
wortel = _maak_bron(env, items=[
{"name": naam, "type": "str", "title": "X", "default": "x"}])
assert boilerplates.fields(wortel)[0]["secret"] is False