"""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 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")