All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 2m1s
- 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
160 lines
6.5 KiB
Python
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
|