Files
mission-control-v2/backend/tests/test_neuerungen.py
T
HitonabiandClaude Opus 5.5 45f11bc3dc Welle 2 · „Was ist neu?“ vor dem Knopf: Release Notes, Debian-Änderungslisten und Lucys Kurzfassung
Vor einem App-Update sammelt der Orchestrator die Release Notes aller Versionen zwischen installiert und neu (GitHub),
beim Proxmox-Host die Debian-Änderungslisten der wichtigsten Pakete (neue Ausführer-Aktion changelog, nur lesend, nur
für anstehende Pakete). Lucy fasst sie über das Modell fast der KI-Box auf Deutsch zusammen: Handarbeit ganz oben,
höchstens fünf Stichpunkte, Brüche zuerst. Die Update-Tabelle hat „Was ist neu?“ je Gerät, und die Rückfrage vor dem
Update zeigt die Kurzfassung gleich mit. Gemerkt je Version; der Ausführer braucht ausfuehrer-einrichten.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:47:04 +02:00

158 lines
7.8 KiB
Python

"""„Was ist neu?“ vor dem Knopf (Ausbauplan Welle 2, Punkt 9, 25.09.2026): Release Notes zwischen installiert und neu,
Debian-Änderungslisten des Proxmox-Hosts über den Ausführer und Lucys Kurzfassung. Die Änderungsliste ist ein Auszug
von pvesh get /nodes/pve/apt/changelog --name pve-manager vom 25.09."""
import importlib.util
import json
from pathlib import Path
import pytest
from kern import einstellungen as einstellungen_mod
from services.homelab import inventar, kanal, neuerungen
AUSFUEHRER = Path(__file__).resolve().parents[2] / "deploy" / "homelab" / "ausfuehrer.py"
RELEASES = [
{"tag_name": "v1.27.0-rc1", "prerelease": True, "body": "Vorab"},
{"tag_name": "v1.26.1", "name": "Gitea 1.26.1", "published_at": "2026-09-20T10:00:00Z", "html_url": "u1",
"body": "* Security: fix XSS in markdown renderer"},
{"tag_name": "v1.26.0", "name": "Gitea 1.26.0", "published_at": "2026-09-01T10:00:00Z", "html_url": "u0",
"body": "BREAKING: `[server] LFS_JWT_SECRET` moved to `[lfs] JWT_SECRET`"},
{"tag_name": "v1.25.4", "body": "alt"},
{"tag_name": "v1.26.2", "draft": True, "body": "Entwurf"},
]
CHANGELOG = """pve-manager (9.2.20) trixie; urgency=medium
* Revert "partially fix #1989: ui: qemu: disk: add qcow2 cache size config"
-- Proxmox Support Team <support@proxmox.com> Fri, 11 Sep 2026 14:10:20 +0200
pve-manager (9.2.19) trixie; urgency=medium
* pvesh: fix startup failure caused by API endpoints with empty parameter schemas
-- Proxmox Support Team <support@proxmox.com> Tue, 01 Sep 2026 10:00:00 +0200
pve-manager (9.2.18) trixie; urgency=medium
* älter
"""
def test_releases_zwischen_installiert_und_neu():
versionen = neuerungen.zwischen(RELEASES, "1.25.4", "1.26.1")
assert [v["version"] for v in versionen] == ["v1.26.1", "v1.26.0"] # ohne Vorab und Entwurf
assert versionen[1]["text"].startswith("BREAKING") and versionen[0]["url"] == "u1"
assert neuerungen.zwischen(RELEASES, "Image vom 2026-08-27", "") == [] # Datum statt Version
assert neuerungen.zwischen("kaputt", "1.0", "2.0") == []
def test_wichtige_pakete_zuerst():
updates = [{"paket": n} for n in ("libc6", "pve-manager", "zlib1g", "proxmox-kernel-7.0", "qemu-server", "curl")]
assert neuerungen.wichtige_pakete(updates)[:3] == ["proxmox-kernel-7.0", "pve-manager", "qemu-server"]
def test_lucys_antwort_auswerten():
a = neuerungen.auswerten("AKTION: JA — In app.ini [lfs] JWT_SECRET setzen\n- ⚠️ LFS-Schlüssel umgezogen\n"
"- Sicherheitskorrektur im Markdown\n\n* noch was")
assert a == {"aktion": True, "aktion_text": "In app.ini [lfs] JWT_SECRET setzen",
"punkte": ["⚠️ LFS-Schlüssel umgezogen", "Sicherheitskorrektur im Markdown", "noch was"]}
assert neuerungen.auswerten("\nAKTION: NEIN\n- nur Fehlerkorrekturen")["aktion"] is False
@pytest.fixture
def daten(tmp_path, monkeypatch):
monkeypatch.setenv("MC_DATEN_DIR", str(tmp_path))
monkeypatch.setenv("MC_PARTNER_URL", "http://box:9001")
einstellungen_mod.einstellungen.cache_clear()
yield tmp_path
einstellungen_mod.einstellungen.cache_clear()
class _Antwort:
def __init__(self, daten: dict):
self._daten = daten
def raise_for_status(self):
pass
def json(self):
return self._daten
def test_kurzfassung_von_der_box(daten, monkeypatch):
gesendet: list[dict] = []
monkeypatch.setattr(neuerungen.httpx, "post", lambda url, timeout, json: gesendet.append({"url": url, **json}) or _Antwort(
{"choices": [{"message": {"content": "AKTION: NEIN\n- Fehler behoben"}, "finish_reason": "stop"}]}))
z = neuerungen.zusammenfassen("Gitea", "1.25.4", "1.26.1", "## v1.26.1\n* fix")
assert z == {"aktion": False, "aktion_text": "", "punkte": ["Fehler behoben"]}
assert gesendet[0]["url"] == "http://box:9001/v1/chat/completions" and gesendet[0]["model"] == "fast"
assert "Gitea (1.25.4 → 1.26.1)" in gesendet[0]["messages"][0]["content"]
monkeypatch.delenv("MC_PARTNER_URL")
einstellungen_mod.einstellungen.cache_clear()
assert neuerungen.zusammenfassen("Gitea", "1", "2", "x") is None # ohne Box keine Kurzfassung
def test_fuer_eine_app_und_gemerkt(daten, monkeypatch):
ziel = {"id": "ct-104", "bausteine": [{"id": "app", "zustand": "neu", "installiert": "1.25.4", "verfuegbar": "1.26.1"}]}
monkeypatch.setattr(inventar, "ziele", lambda: {"ziele": [ziel]})
monkeypatch.setattr(inventar, "gast", lambda zid: {"vmid": 104, "name": "gitea", "app": {"kennung": "gitea"}})
abrufe: list[str] = []
monkeypatch.setattr(neuerungen, "github_json", lambda pfad: abrufe.append(pfad) or RELEASES)
monkeypatch.setattr(neuerungen, "zusammenfassen", lambda was, von, nach, quellen: {
"aktion": True, "aktion_text": "JWT_SECRET umziehen", "punkte": [quellen.count("## ")]})
e = neuerungen.fuer("ct-104", "app")
assert e["verfuegbar"] and [v["version"] for v in e["versionen"]] == ["v1.26.1", "v1.26.0"]
assert e["zusammenfassung"]["punkte"] == [2] and e["quelle_url"] == "https://github.com/go-gitea/gitea/releases"
assert abrufe == ["repos/go-gitea/gitea/releases?per_page=40"]
assert neuerungen.fuer("ct-104", "app") == e and len(abrufe) == 1 # gemerkt
assert neuerungen.fuer("ct-104", "os")["verfuegbar"] is False
def test_fuer_den_proxmox_host(daten, monkeypatch):
ziel = {"id": "pve", "bausteine": [{"id": "pakete", "zustand": "neu", "installiert": "9.2.19", "kurz": "3 Pakete"}]}
monkeypatch.setattr(inventar, "ziele", lambda: {"ziele": [ziel]})
monkeypatch.setattr(kanal, "bericht", lambda: {"bericht": {"host": {"updates": [
{"paket": "pve-manager"}, {"paket": "curl"}, {"paket": "libc6"}]}}})
angefragt: list[list[str]] = []
monkeypatch.setattr(neuerungen, "_changelogs", lambda pakete: angefragt.append(pakete) or {"pve-manager": "* Revert"})
monkeypatch.setattr(neuerungen, "zusammenfassen", lambda was, von, nach, quellen: {"aktion": False, "aktion_text": "",
"punkte": [was]})
e = neuerungen.fuer("pve", "pakete")
assert angefragt == [["pve-manager", "curl", "libc6"]]
assert e["pakete"] == [{"name": "pve-manager", "text": "* Revert"}] and e["weitere"] == 2
assert e["zusammenfassung"]["punkte"] == ["Proxmox VE (3 Pakete)"]
# --- Ausführer: changelog -------------------------------------------------------------------------------------------
@pytest.fixture(scope="module")
def a():
spec = importlib.util.spec_from_file_location("ausfuehrer_changelog", AUSFUEHRER)
modul = importlib.util.module_from_spec(spec)
spec.loader.exec_module(modul)
return modul
def test_changelog_bis_zur_installierten_version(a):
neu = a.changelog_neu(CHANGELOG, "9.2.19")
assert neu.startswith("pve-manager (9.2.20)") and "9.2.19" not in neu
assert a.changelog_neu(CHANGELOG, None) == CHANGELOG.strip()
assert a.changelog_neu(CHANGELOG, "1.0") == CHANGELOG.strip() # unbekannt: alles
def test_changelog_nur_fuer_anstehende_pakete_und_nur_lesend(a, monkeypatch):
def pvesh(pfad, *parameter):
if pfad.endswith("/apt/update"):
return [{"Package": "pve-manager", "OldVersion": "9.2.19"}]
return CHANGELOG
monkeypatch.setattr(a, "NODE", "pve")
monkeypatch.setattr(a, "_pvesh", pvesh)
monkeypatch.setattr(a, "NUR_LESEN", True)
antwort = a.ausfuehren({"aktion": "changelog", "parameter": {"pakete": ["pve-manager", "curl"]}})
assert antwort["code"] == 0
assert list(json.loads(antwort["text"])) == ["pve-manager"] # curl steht nicht an
kaputt = a.ausfuehren({"aktion": "changelog", "parameter": {"pakete": ["pve; rm -rf /"]}})
assert kaputt["code"] == 2 and "ungültige Paketliste" in kaputt["text"]