"""Wartezeit nach einer Änderung am Update-Skript (24.09.2026): ct/.sh kommt ungepinnt von GitHub und läuft als root im Gast — frisch Geändertes erst nach MC_HOMELAB_KARENZ_H Stunden.""" import json from datetime import datetime from pathlib import Path import pytest from kern import einstellungen as einstellungen_mod from kern.zeit import LOCAL_TZ from services.homelab import inventar, kanal, karenz, updates BERICHT = json.loads((Path(__file__).parent / "fixtures" / "pve-bericht.json").read_text(encoding="utf-8")) # 23.09.2026 14:05 in Berlin = 12:05 UTC, so wie GitHub es schreibt. GEAENDERT = "2026-09-23T12:05:11Z" GEAENDERT_TS = datetime(2026, 9, 23, 14, 5, 11, tzinfo=LOCAL_TZ).timestamp() def _github(datum: str = GEAENDERT, pfade: list | None = None): def antwort(pfad, timeout=8): if pfade is not None: pfade.append(pfad) return [{"sha": "abc", "commit": {"author": {"date": "2026-09-01T08:00:00Z"}, "committer": {"date": datum}}}] return antwort def test_frische_aenderung_sperrt_mit_berliner_zeiten(monkeypatch): pfade: list[str] = [] monkeypatch.setattr(karenz, "github_json", _github(pfade=pfade)) monkeypatch.delenv("MC_HOMELAB_KARENZ_H", raising=False) stand = karenz.pruefen("gitea", jetzt=GEAENDERT_TS + 10 * 3600) assert stand == {"gesperrt": "Das Update-Skript wurde am 23.09. geändert; zur Sicherheit erst ab 25.09. 14:05.", "geprueft": True} # Das Datum, an dem die Änderung auf main landete (committer), nicht das des Autors. assert pfade == ["repos/community-scripts/ProxmoxVE/commits?path=ct/gitea.sh&sha=main&per_page=1"] assert karenz.pruefen("gitea", jetzt=GEAENDERT_TS + 48 * 3600) == {"gesperrt": None, "geprueft": True} def test_wartezeit_einstellbar(monkeypatch): monkeypatch.setattr(karenz, "github_json", _github()) monkeypatch.setenv("MC_HOMELAB_KARENZ_H", "0") assert karenz.pruefen("gitea", jetzt=GEAENDERT_TS + 60)["gesperrt"] is None monkeypatch.setenv("MC_HOMELAB_KARENZ_H", "72") assert "erst ab 26.09. 14:05" in karenz.pruefen("gitea", jetzt=GEAENDERT_TS + 50 * 3600)["gesperrt"] monkeypatch.setenv("MC_HOMELAB_KARENZ_H", "viel") assert karenz.karenz_s() == 48 * 3600 def test_ohne_antwort_von_github_nicht_blockieren(monkeypatch): def kaputt(pfad, timeout=8): raise RuntimeError("403 rate limit") monkeypatch.setattr(karenz, "github_json", kaputt) assert karenz.pruefen("gitea") == {"gesperrt": None, "geprueft": False} monkeypatch.setattr(karenz, "github_json", lambda pfad, timeout=8: []) # Skript gibt es nicht assert karenz.pruefen("gitea")["geprueft"] is False assert karenz.pruefen("../../etc")["geprueft"] is False # fremde Kennung: keine Abfrage # --- Übersicht und Knopf ------------------------------------------------------------------------- @pytest.fixture def gitea_neu(tmp_path, monkeypatch): """Gitea 1.27.2 installiert, 1.27.3 verfügbar (der Stand vor dem ersten echten Lauf am 24.09.).""" monkeypatch.setenv("MC_DATEN_DIR", str(tmp_path)) einstellungen_mod.einstellungen.cache_clear() monkeypatch.setattr(inventar, "neueste_version", lambda repo: { "tag": "v1.27.3", "tag_roh": "v1.27.3", "version": "1.27.3" if repo == "go-gitea/gitea" else "0.0.1", "datum": "2026-09-20"}) monkeypatch.setattr(inventar, "erreichbar", lambda url: True) monkeypatch.setattr(inventar.arcane, "app_version", lambda url: None) monkeypatch.delenv("MC_HOMELAB_KARENZ_H", raising=False) kanal.bericht_speichern(BERICHT) yield einstellungen_mod.einstellungen.cache_clear() def _gitea_app() -> dict: ziele = {z["id"]: z for z in inventar.ziele()["ziele"]} return {b["id"]: b for b in ziele["ct-104"]["bausteine"]}["app"] def test_frisch_geaendertes_skript_neu_aber_ohne_knopf(gitea_neu, monkeypatch): jetzt = datetime.now(LOCAL_TZ).timestamp() monkeypatch.setattr(karenz, "skript_geaendert", lambda kennung: jetzt - 3600) app = _gitea_app() assert (app["zustand"], app["kurz"], app["aktion"]) == ("neu", "1.27.2 → 1.27.3", None) assert app["grund"].startswith("Das Update-Skript wurde am ") and "zur Sicherheit erst ab" in app["grund"] # Der Knopf fehlt nicht nur: auch die Schnittstelle lehnt mit demselben Satz ab, und kein Lauf beginnt. antwort = updates.starten("ct-104", "app") assert antwort == {"ok": False, "detail": app["grund"]} assert updates.laufende_ziele() == set() and kanal.liste() == [] def test_altes_skript_knopf_wie_bisher(gitea_neu, monkeypatch): monkeypatch.setattr(karenz, "skript_geaendert", lambda kennung: 0.0) app = _gitea_app() assert app["aktion"]["frage"] == ("Gitea jetzt aktualisieren? Vorher wird ein Snapshot angelegt; ist die Prüfung " "danach rot, geht es von selbst zurück.") def test_ungeprueft_sagt_es_die_rueckfrage(gitea_neu, monkeypatch): def kaputt(kennung): raise RuntimeError("GitHub antwortet nicht") monkeypatch.setattr(karenz, "skript_geaendert", kaputt) app = _gitea_app() assert app["aktion"] is not None and app["aktion"]["frage"].endswith(karenz.NICHT_GEPRUEFT) assert app["aktion"]["frage"] == ("Gitea jetzt aktualisieren? Vorher wird ein Snapshot angelegt; ist die Prüfung " "danach rot, geht es von selbst zurück. Ob das Skript kürzlich geändert wurde, " "ließ sich nicht prüfen.")