"""De cache van de image-controle. De volledige ronde kost elf à twaalf minuten omdat registries afknijpen en een afgeknepen verzoek niet te onderscheiden is van een ontbrekend image. Die ronde draaide bij élke push, ook als er geen enkel sjabloon was gewijzigd. Wat hier vastligt is niet de snelheid maar de correctheid van het overslaan: een gewijzigde tag mag nooit ongezien blijven, en een ontbrekend image mag nooit uit beeld verdwijnen doordat het ooit goed was. """ import importlib import json import sys import time from pathlib import Path import pytest WORTEL = Path(__file__).resolve().parent.parent @pytest.fixture def ci(tmp_path, monkeypatch): monkeypatch.setenv("SU_IMAGE_CACHE", str(tmp_path / "images.json")) sys.path.insert(0, str(WORTEL / "tools")) for naam in [n for n in sys.modules if n == "controleer_images"]: del sys.modules[naam] mod = importlib.import_module("controleer_images") return mod def test_een_verse_cache_controleert_alles(ci): alles = {"nginx:alpine": ["a"], "redis:7": ["b"]} assert sorted(ci.te_controleren(alles, {}, time.time())) == sorted(alles) def test_recent_goed_bevonden_wordt_overgeslagen(ci): nu = time.time() alles = {"nginx:alpine": ["a"], "redis:7": ["b"]} cache = {"nginx:alpine": nu, "redis:7": nu} assert ci.te_controleren(alles, cache, nu) == [] def test_een_andere_tag_wordt_altijd_gecontroleerd(ci): """De sleutel is de naam mét tag. Wijzigt een sjabloon zijn tag, dan is dat een nieuwe sleutel — anders zou je juist de wijziging ongezien laten.""" nu = time.time() cache = {"nginx:alpine": nu} assert ci.te_controleren({"nginx:1.27": ["a"]}, cache, nu) == ["nginx:1.27"] def test_de_cache_verloopt(ci): nu = time.time() oud = nu - ci.HOUDBAAR - ci.SPREIDING - 10 assert ci.te_controleren({"nginx:alpine": ["a"]}, {"nginx:alpine": oud}, nu) \ == ["nginx:alpine"] def test_niet_alles_verloopt_op_dezelfde_dag(ci): """Zonder spreiding heb je na een week alsnog één ronde van twaalf minuten.""" ts = 1_700_000_000.0 momenten = {ci.houdbaar_tot(f"voorbeeld/app-{i}:latest", ts) for i in range(200)} spanne = max(momenten) - min(momenten) assert spanne > 2 * 24 * 3600, f"vervaldata liggen te dicht op elkaar: {spanne}s" def test_een_ontbrekend_image_blijft_gemeld(ci, monkeypatch, tmp_path): """Een mislukking mag nooit in de cache belanden: dan zou hij één keer gemeld worden en daarna een week onzichtbaar zijn.""" monkeypatch.setattr(ci, "images", lambda: {"bestaat/niet:1": ["proefapp"], "goed/wel:1": ["b"]}) monkeypatch.setattr(ci, "bestaat", lambda i: not i.startswith("bestaat/niet")) assert ci.main() == 1 # eerste ronde meldt hem cache = json.loads((tmp_path / "images.json").read_text()) assert "goed/wel:1" in cache assert "bestaat/niet:1" not in cache, "de mislukking is onthouden" assert ci.main() == 1, "tweede ronde zwijgt over het ontbrekende image" def test_verdwenen_images_blijven_niet_in_de_cache_hangen(ci, monkeypatch, tmp_path): (tmp_path / "images.json").write_text(json.dumps({ "oud/weg:1": time.time(), "blijft/staan:1": time.time()})) monkeypatch.setattr(ci, "images", lambda: {"blijft/staan:1": ["a"]}) monkeypatch.setattr(ci, "bestaat", lambda i: True) ci.main() cache = json.loads((tmp_path / "images.json").read_text()) assert list(cache) == ["blijft/staan:1"], cache def test_alles_negeert_de_cache(ci, monkeypatch, tmp_path): (tmp_path / "images.json").write_text(json.dumps({"nginx:alpine": time.time()})) gecontroleerd = [] monkeypatch.setattr(ci, "images", lambda: {"nginx:alpine": ["a"]}) monkeypatch.setattr(ci, "bestaat", lambda i: (gecontroleerd.append(i), True)[1]) monkeypatch.setattr(sys, "argv", ["x", "--alles"]) ci.main() assert gecontroleerd == ["nginx:alpine"] def test_zonder_cachepad_werkt_het_gewoon(tmp_path, monkeypatch): """Lokaal draaien zonder volume mag niet stukgaan.""" monkeypatch.delenv("SU_IMAGE_CACHE", raising=False) sys.path.insert(0, str(WORTEL / "tools")) for naam in [n for n in sys.modules if n == "controleer_images"]: del sys.modules[naam] mod = importlib.import_module("controleer_images") assert mod.CACHE_PAD is None assert mod.lees_cache() == {} mod.schrijf_cache({"a": 1}) # mag geen fout geven