server-up/tests/test_selfupdate.py
Ramon 8ff5f4b1d3
Some checks failed
Deploy server-up (dev) / deploy (push) Failing after 4s
v0.5.10-beta - update-systeem met kanalen en een-klik bijwerken
- Update-kanalen stable/beta in plaats van het vinkje "pre-releases meenemen".
  Stable ziet alleen releases, beta ook pre-releases; een release telt hoger
  dan zijn eigen beta (0.5.10-beta1 < 0.5.10). UPDATE_INCLUDE_PRERELEASE
  migreert automatisch naar het beta-kanaal.
- Nieuw core/selfupdate.py: image uit de registry ophalen, SU_TAG wegschrijven
  en de eigen container laten hercreeren door een korte helper-container die op
  het nieuwe image draait (bevat de docker- en compose-CLI al). Een container
  kan zichzelf niet hercreeren, vandaar de helper.
- Deploy-map wordt uitgelezen uit de compose-labels van de eigen container.
  Ontbreken die, draait de container niet vanaf een registry-image of is de
  docker-socket er niet, dan meldt de UI waarom bijwerken niet kan.
- Vorige tag wordt onthouden; terugrolknop in de instellingen.
- Update-check een uur gecached, met geforceerde check via de knop; eerder deed
  elke paginalading een netwerkverzoek. Laatst-gecontroleerd zichtbaar.
- Registry-inloggegevens instelbaar; het token komt net als de git-tokens nooit
  terug via de API en leeg laten betekent ongewijzigd.
- docker-compose.yml gebruikt ${SU_IMAGE:-server-up}:${SU_TAG:-latest}; zonder
  SU_IMAGE blijft lokaal bouwen werken zoals voorheen.
- deploy-prod.yml bouwt en pusht het image in dezelfde job wanneer SU_IMAGE
  ingesteld is; build.yml is nu alleen handmatig. Bewust geen aparte
  build-workflow op dezelfde tag: bij een runner met een job tegelijk zou de
  deploy wachten op een build die zelf nog in de wachtrij staat.
- docs/updates.md: werking, complete Forgejo-instelling (registry, tokens,
  variables, secrets, runners), release-procedure voor stable en beta,
  terugrollen en probleemoplossing.
- Tests uitgebreid naar 148 (kanaallogica, cache, image-afleiding incl.
  registry-poortnummers, tag-validatie, .env-schrijven, tokenlek).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-26 15:13:18 +02:00

156 lines
5.7 KiB
Python

"""Zelf-update: image-afleiding, veiligheidscontroles en de tag-administratie."""
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "server-up"))
from conftest import login
@pytest.fixture
def su(env):
from core import selfupdate
return selfupdate
def _fake_inspect(su, monkeypatch, **overrides):
info = {"name": "server-up", "image": "git.example.com/bes-r/server-up:0.5.00",
"image_id": "sha256:abc", "project": "server-up",
"working_dir": "/opt/server-up",
"config_files": "/opt/server-up/docker-compose.yml",
"service": "server-up"}
info.update(overrides)
monkeypatch.setattr(su, "inspect_self", lambda: info)
return info
@pytest.mark.parametrize("image,verwacht", [
("git.example.com/bes-r/server-up:0.5.00", "git.example.com/bes-r/server-up"),
("git.example.com/bes-r/server-up", "git.example.com/bes-r/server-up"),
("server-up:latest", "server-up"),
# Poortnummer in de registryhost mag niet als tag gelezen worden.
("git.example.com:3000/bes-r/server-up", "git.example.com:3000/bes-r/server-up"),
("git.example.com:3000/bes-r/server-up:1.2.3", "git.example.com:3000/bes-r/server-up"),
])
def test_image_repo_afleiding(su, monkeypatch, image, verwacht):
_fake_inspect(su, monkeypatch, image=image)
assert su.image_repo() == verwacht
def test_expliciet_ingesteld_image_wint(su, env, monkeypatch):
_fake_inspect(su, monkeypatch)
env["core"].patch({"UPDATE_IMAGE": "registry.intern/team/server-up"})
assert su.image_repo() == "registry.intern/team/server-up"
def test_status_ok_bij_registry_image(su, monkeypatch):
_fake_inspect(su, monkeypatch)
st = su.status()
assert st["can_apply"] is True
assert st["image"] == "git.example.com/bes-r/server-up"
def test_status_weigert_lokaal_gebouwd_image(su, monkeypatch):
_fake_inspect(su, monkeypatch, image="server-up:latest")
st = su.status()
assert st["can_apply"] is False
assert "registry" in st["reason"]
def test_status_weigert_zonder_compose_labels(su, monkeypatch):
_fake_inspect(su, monkeypatch, working_dir="")
st = su.status()
assert st["can_apply"] is False
assert "compose" in st["reason"]
def test_status_zonder_container(su, monkeypatch):
monkeypatch.setattr(su, "inspect_self", lambda: {})
st = su.status()
assert st["can_apply"] is False
assert "niet te vinden" in st["reason"]
@pytest.mark.parametrize("tag", [
"../../etc", "0.5.0; rm -rf /", "-x", "", "a" * 200, "tag met spatie",
])
def test_pull_weigert_ongeldige_tag(su, monkeypatch, tag):
_fake_inspect(su, monkeypatch)
ok, msg = su.pull(tag)
assert ok is False
assert "Ongeldige tag" in msg
def test_write_tag_maakt_env_aan(su, tmp_path):
su._write_tag(str(tmp_path), "0.5.10")
assert (tmp_path / ".env").read_text().strip() == "SU_TAG=0.5.10"
def test_write_tag_vervangt_bestaande_regel(su, tmp_path):
(tmp_path / ".env").write_text("BIND=0.0.0.0\nSU_TAG=0.5.00\nPORT=5000\n")
su._write_tag(str(tmp_path), "0.5.10")
regels = (tmp_path / ".env").read_text().splitlines()
assert regels == ["BIND=0.0.0.0", "SU_TAG=0.5.10", "PORT=5000"]
assert sum(1 for r in regels if r.startswith("SU_TAG=")) == 1
def test_write_tag_behoudt_andere_instellingen(su, tmp_path):
(tmp_path / ".env").write_text("SU_IMAGE=git.example.com/bes-r/server-up\n")
su._write_tag(str(tmp_path), "0.5.10")
inhoud = (tmp_path / ".env").read_text()
assert "SU_IMAGE=git.example.com/bes-r/server-up" in inhoud
assert "SU_TAG=0.5.10" in inhoud
def test_rollback_zonder_vorige_versie(su):
ok, msg = su.rollback()
assert ok is False
assert "Geen vorige versie" in msg
def test_apply_endpoint_weigert_zonder_registry(client, monkeypatch):
from core import selfupdate
csrf = login(client)
monkeypatch.setattr(selfupdate, "inspect_self",
lambda: {"image": "server-up:latest", "working_dir": "/opt/x",
"service": "server-up"})
r = client.post("/api/update/apply", json={"tag": "0.5.10"},
headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "registry" in r.get_json()["msg"]
def test_rollback_endpoint_weigert_zonder_vorige_tag(client, monkeypatch):
from core import selfupdate
csrf = login(client)
monkeypatch.setattr(selfupdate, "inspect_self",
lambda: {"image": "git.example.com/bes-r/server-up:0.5.00",
"working_dir": "/opt/x", "service": "server-up"})
r = client.post("/api/update/rollback", headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "Geen vorige versie" in r.get_json()["msg"]
def test_registry_token_lekt_niet_via_settings(client, env):
csrf = login(client)
client.put("/api/settings",
json={"UPDATE_REGISTRY_USER": "bes-r",
"UPDATE_REGISTRY_TOKEN": "geheim-package-token"},
headers={"X-CSRF-Token": csrf})
body = client.get("/api/settings").get_data(as_text=True)
assert "geheim-package-token" not in body
assert "has_update_registry_token" in body
# Wel echt opgeslagen:
assert env["core"].load()["UPDATE_REGISTRY_TOKEN"] == "geheim-package-token"
def test_leeg_registry_token_wist_bestaande_niet(client, env):
csrf = login(client)
client.put("/api/settings", json={"UPDATE_REGISTRY_TOKEN": "blijf-staan"},
headers={"X-CSRF-Token": csrf})
client.put("/api/settings", json={"UPDATE_REGISTRY_TOKEN": ""},
headers={"X-CSRF-Token": csrf})
assert env["core"].load()["UPDATE_REGISTRY_TOKEN"] == "blijf-staan"