server-up/tests/test_install_script.py
Ramon 0bc944de5f v0.7.30-beta - proefdraai werkt zonder curl
De teststap draait in python:3.12-slim, waar curl ontbreekt. Het
installatiescript brak daar af op de vereistencontrole, waardoor
test_dry_run_wijzigt_niets en test_dry_run_toont_de_hele_gang_van_zaken
faalden op de runner (runs 74 en 75).

Ontbrekende curl/tar is nu een waarschuwing bij --dry-run in plaats van
een harde fout, net als bij Docker: een proefdraai hoort juist te tonen
wat er zou gebeuren op een machine waar nog niets staat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 20:37:34 +02:00

145 lines
5.3 KiB
Python

"""Het installatiescript: syntaxis, opties en de dingen die stuk mogen gaan.
Een installer die je met `curl | sh` draait moet in elk geval syntactisch
kloppen onder de shells die distributies als /bin/sh gebruiken, en niet
stilzwijgend het verkeerde doen bij een typefout in de opties.
"""
import re
import shutil
import subprocess
from pathlib import Path
import pytest
WORTEL = Path(__file__).resolve().parent.parent
SCRIPT = WORTEL / "install.sh"
def _draai(*args, **kw):
return subprocess.run(["sh", str(SCRIPT), *args], capture_output=True,
text=True, timeout=60, **kw)
def test_script_bestaat_en_is_uitvoerbaar():
assert SCRIPT.is_file()
assert SCRIPT.stat().st_mode & 0o111, "niet uitvoerbaar"
@pytest.mark.parametrize("shell", ["sh", "dash", "bash"])
def test_syntaxis_klopt_onder(shell):
"""Debian en Ubuntu gebruiken dash als /bin/sh; bashismen breken daar."""
if not shutil.which(shell):
pytest.skip(f"{shell} niet aanwezig")
r = subprocess.run([shell, "-n", str(SCRIPT)], capture_output=True, text=True)
assert r.returncode == 0, r.stderr
def test_geen_bashismen():
tekst = SCRIPT.read_text(encoding="utf-8")
for patroon, uitleg in [
(r"\[\[", "[[ ]] is bash-only"),
(r"^\s*declare ", "declare is bash-only"),
(r"\$\{[A-Za-z_]+\[", "arrays zijn bash-only"),
(r"==", "== in test is bash-only"),
]:
treffers = [l for l in tekst.splitlines()
if re.search(patroon, l) and not l.strip().startswith("#")]
assert not treffers, f"{uitleg}: {treffers[:2]}"
def test_help_werkt_en_noemt_alle_opties():
r = _draai("--help")
assert r.returncode == 0
for optie in ("--dir", "--port", "--bind", "--branch", "--token",
"--update", "--uninstall", "--yes", "--dry-run"):
assert optie in r.stdout, f"{optie} niet gedocumenteerd in --help"
def test_onbekende_optie_stopt():
r = _draai("--bestaatniet")
assert r.returncode != 0
assert "Onbekende optie" in r.stderr
@pytest.mark.parametrize("poort", ["abc", "", "80a", "-1"])
def test_ongeldige_poort_stopt(poort):
r = _draai("--port", poort)
assert r.returncode != 0
# Een lege waarde valt al af bij het uitlezen van de optie, de rest bij de
# controle daarna; beide meldingen gaan over de poort.
assert "poort" in r.stderr.lower() or "port" in r.stderr.lower()
def test_update_zonder_installatie_zegt_dat(tmp_path):
r = _draai("--update", "--dir", str(tmp_path / "bestaatniet"))
assert r.returncode != 0
assert "Geen installatie gevonden" in r.stderr
def test_dry_run_wijzigt_niets(tmp_path):
doel = tmp_path / "server-up"
r = _draai("--dry-run", "--dir", str(doel), "--yes")
assert not doel.exists(), "proefdraai heeft toch iets aangemaakt"
assert "[droog]" in r.stdout
def test_dry_run_toont_de_hele_gang_van_zaken(tmp_path):
"""Ook op een machine zonder Docker moet je de voorvertoning kunnen zien."""
r = _draai("--dry-run", "--dir", str(tmp_path / "su"), "--yes")
for kop in ("Systeem controleren", "Broncode ophalen", "Instellingen",
"Bouwen en starten"):
assert kop in r.stdout, f"stap ontbreekt: {kop}"
def _pad_zonder(tmp_path, *weglaten):
"""Een PATH met alles erin behalve de genoemde commando's."""
nep = tmp_path / "bin"
nep.mkdir()
import os
for map_ in os.environ.get("PATH", "").split(os.pathsep):
p = Path(map_)
if not p.is_dir():
continue
for f in p.iterdir():
if f.name in weglaten or (nep / f.name).exists():
continue
try:
(nep / f.name).symlink_to(f)
except OSError:
pass
return str(nep)
def test_proefdraai_werkt_zonder_curl_en_tar(tmp_path):
"""De CI-runner draait in python:3.12-slim, waar curl ontbreekt. Een
proefdraai hoort dan nog steeds de hele voorvertoning te tonen: dat is juist
het moment waarop je wil zien wat er zou gebeuren."""
r = subprocess.run(["sh", str(SCRIPT), "--dry-run", "--dir",
str(tmp_path / "su"), "--yes"],
capture_output=True, text=True, timeout=60,
env={"PATH": _pad_zonder(tmp_path, "curl", "tar"),
"HOME": str(tmp_path)})
assert r.returncode == 0, r.stderr
assert "[droog]" in r.stdout
assert "Bouwen en starten" in r.stdout
assert not (tmp_path / "su").exists()
def test_verwijst_naar_het_juiste_git_domein():
tekst = SCRIPT.read_text(encoding="utf-8")
assert 'GIT_HOST="git.ramonbesselink.nl"' in tekst
def test_compose_wordt_met_expliciet_bestand_aangeroepen():
"""`--project-directory` alleen laat compose het bestand nog in de huidige
map zoeken; met `curl | sh` is dat je thuismap."""
for regel in SCRIPT.read_text(encoding="utf-8").splitlines():
if "docker compose" in regel and ("up -d" in regel or "down " in regel):
assert "-f " in regel, f"geen expliciet compose-bestand: {regel.strip()}"
def test_readme_en_docs_noemen_dezelfde_installatieregel():
regel = ("curl -fsSL https://git.ramonbesselink.nl/bes-r/server-up/"
"raw/branch/main/install.sh")
assert regel in (WORTEL / "README.md").read_text(encoding="utf-8")
assert regel in (WORTEL / "docs" / "installeren.md").read_text(encoding="utf-8")