Files
mission-control-v2/backend/tests/test_wochenbericht.py
T
HitonabiandClaude Opus 5.5 6904bcbfb0 Hermes-Update: neue Commits im flachen Klon richtig zählen (281 statt 42 044); Wochenbericht duzt
- maintenance._hermes_bereich: --ancestry-path, wenn HEAD Vorfahr von origin/<branch> ist (seit dem Paketmanager
  ist der Checkout ein flacher Klon); sonst der einfache Bereich wie bisher
- Wochenbericht: Lucy duzt, nennt nur als Aufgabe, was ausdrücklich auf den Commander wartet; ungetestete
  Radar-Kandidaten testet das Radar nachts selbst

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:24:13 +02:00

151 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Wochenbericht in Lucys Stimme (Ausbauplan Welle 3, Punkt 16, 25.09.2026): feste Sätze aus den Zahlen beider Teile,
ein kurzer Absatz vom Modell obenauf, offene App-Updates als Rückfragen mit Antworttasten."""
import json
from datetime import datetime
import pytest
from kern import einstellungen as einstellungen_mod
from kern.zeit import LOCAL_TZ
from services import rueckfragen, wochenbericht
JETZT = datetime(2026, 9, 27, 7, 30, tzinfo=LOCAL_TZ).timestamp() # Sonntag
TAG = 86400
FAKTEN = {
"homelab_laeufe": [
{"status": "fertig", "ergebnis": "eingespielt", "name": "Gitea", "baustein": "app", "ende": JETZT - TAG},
{"status": "fertig", "ergebnis": "eingespielt", "name": "Proxmox-Host", "baustein": "sicherheit",
"ende": JETZT - 4 * 3600},
{"status": "fertig", "ergebnis": "zurueckgerollt", "name": "NetBird", "baustein": "os", "ende": JETZT - 2 * TAG},
{"status": "fertig", "ergebnis": "eingespielt", "name": "Alt", "baustein": "app", "ende": JETZT - 9 * TAG},
{"status": "fertig", "ergebnis": "eingespielt", "name": "Probe", "baustein": "probe", "ende": JETZT - TAG},
{"status": "laeuft", "name": "Vaultwarden", "baustein": "os", "ende": None},
],
"box_laeufe": [
{"start": datetime(2026, 9, 27, 4, 30, tzinfo=LOCAL_TZ).isoformat(),
"zeilen": [{"baustein": "Motor", "ergebnis": "eingespielt"}, {"baustein": "Hermes", "ergebnis": "festgehalten"},
{"baustein": "llama-swap", "ergebnis": "aktuell"}]},
{"start": "2026-09-13T04:30:00+02:00", "zeilen": [{"baustein": "Hermes", "ergebnis": "fehler"}]},
],
"ziele_box": [{"id": "ki-box", "name": "KI-Box", "bausteine": [
{"id": "os", "name": "Betriebssystem", "zustand": "neu", "kurz": "5 Pakete", "aktion": {"pfad": "/api/x"}}]}],
"ziele_homelab": [
{"id": "ct-104", "name": "Gitea", "bausteine": [
{"id": "app", "name": "Gitea", "zustand": "neu", "verfuegbar": "1.27.4",
"aktion": {"pfad": "/api/homelab/ziele/ct-104/update"}},
{"id": "os", "name": "Pakete", "zustand": "neu", "sicherheit": 2, "aktion": {"pfad": "/api/homelab/ziele/ct-104/os-update"}}]},
{"id": "ct-110", "name": "Home Assistant", "bausteine": [
{"id": "app", "name": "Home Assistant", "zustand": "neu", "verfuegbar": "2026.10", "aktion": None,
"grund": "eigene Oberfläche"}]},
],
"hinweise_box": [],
"hinweise_homelab": [{"stufe": "gelb", "titel": "Platte von AdGuard Home zu 84 % voll"}],
"speicher": {"pools": [{"speicher": "local-lvm", "anteil": 0.52, "vorhersage": {"tage": 40.0}}],
"systemplatte": {"anteil": 0.31, "vorhersage": {"tage": None}},
"nas": [{"problem": "NAS antwortet nicht."}],
"sicherungen": {"stufe": "gelb", "fehlend": [{"name": "Arcane"}],
"geraete": [{"name": "Gitea", "probe": {"ok": True}},
{"name": "NetBird", "probe": {"ok": False}}]}},
"box_platte": {"percent": 61.0, "vorhersage": {"tage": 10.0}},
"zertifikate": {"zertifikate": [{"name": "mc.example", "stufe": "gelb", "problem": "wird nicht erneuert, noch "
"2 Tage gültig"},
{"name": "pve", "stufe": "ok"}]},
"docker": {"verfuegbar": True, "stacks": [{"container": [
{"name": "immich", "luecken": {"kritisch": 3}}, {"name": "paperless", "luecken": {"kritisch": 0}},
{"name": "redis", "luecken": None}]}]},
"radar": [{"name": "Qwen4-30B", "status": "bestanden"}, {"name": "X", "status": "wartet"},
{"name": "Y", "status": "neu"}],
"rueckfragen": {"zuletzt": [{"zeit": JETZT - TAG, "antwort": {"ja": True}}, {"zeit": JETZT - TAG, "antwort": None},
{"zeit": JETZT - 10 * TAG, "antwort": {"ja": True}}]},
"lucy": [{"zeit": "2026-09-26T10:00:00", "werkzeug": "dienst_neustarten", "stufe": "selbst", "ergebnis": "ok"},
{"zeit": "2026-09-26T11:00:00", "werkzeug": "update_starten", "stufe": "auf_bitte", "ergebnis": "ok"},
{"zeit": "2026-09-26T12:00:00", "werkzeug": "update_starten", "stufe": "fragen", "ergebnis": "Rückfrage 3"},
{"zeit": "2026-09-26T13:00:00", "werkzeug": "x", "stufe": "selbst", "ergebnis": "Nicht ausgeführt: nein"},
{"zeit": "2026-09-10T10:00:00", "werkzeug": "dienst_neustarten", "stufe": "selbst", "ergebnis": "ok"}],
}
@pytest.fixture
def daten(tmp_path, monkeypatch):
monkeypatch.setenv("MC_DATEN_DIR", str(tmp_path))
einstellungen_mod.einstellungen.cache_clear()
yield tmp_path
einstellungen_mod.einstellungen.cache_clear()
def test_feste_saetze():
assert wochenbericht.zeilen(FAKTEN, JETZT) == [
"Eingespielt: Gitea (App), Proxmox-Host (Sicherheitsupdates) und KI-Box Motor.",
"Nicht glatt gelaufen: NetBird (Pakete) zurückgerollt und KI-Box Hermes festgehalten.",
"Offen: Gitea 1.27.4 und Home Assistant 2026.10; Paket-Updates auf 2 Geräten, davon 2 Sicherheitsupdates.",
"Hinweise: gelb: Platte von AdGuard Home zu 84 % voll.",
("Speicher: Pool local-lvm 52 % (voll in etwa 6 Wochen); Host-Systemplatte 31 %; NAS: NAS antwortet nicht; "
"KI-Box 61 % (voll in etwa 10 Tagen)."),
"Sicherungen: Stufe gelb; zu alt oder fehlend: Arcane; Probe-Wiederherstellung gescheitert: NetBird.",
"Zertifikate: mc.example: wird nicht erneuert, noch 2 Tage gültig.",
"Lücken-Scan: immich (3 kritisch).",
("Modell-Radar: bestanden und wartet auf deine Entscheidung: Qwen4-30B; 2 Kandidaten sind noch ungetestet "
"(das Radar testet nachts selbst)."),
"Lucy: 1× selbst gehandelt; 1× auf deine Bitte; 2 Rückfragen beantwortet oder abgelaufen, davon 1× ja.",
]
def test_leere_woche():
zeilen = wochenbericht.zeilen({}, JETZT)
assert zeilen == ["Eingespielt: nichts.", "Offen: nichts.", "Hinweise: keine.", "Speicher: keine Angaben.",
"Sicherungen: keine Angaben.", "Zertifikate: keine Angaben."]
def test_bericht_mit_rueckfragen(daten, monkeypatch):
gesendet = []
monkeypatch.setattr(wochenbericht, "fakten", lambda jetzt=None: FAKTEN)
monkeypatch.setattr(wochenbericht, "prosa", lambda zeilen: "Commander, eine ruhige Woche.")
monkeypatch.setattr(wochenbericht.announce, "add", lambda text, **k: gesendet.append(("briefkasten", k)))
monkeypatch.setattr(wochenbericht.announce, "notify_telegram",
lambda betreff, text, dringend=False, knoepfe=None: gesendet.append((betreff, text, knoepfe)))
assert wochenbericht.senden(JETZT) == {"ok": True, "text": "Der Wochenbericht ist unterwegs."}
briefkasten, telegram = gesendet
assert briefkasten == ("briefkasten", {"subject": "[Wochenbericht]", "source": "wochenbericht",
"priority": "silent"})
betreff, text, knoepfe = telegram
assert betreff == "[Wochenbericht]" and knoepfe == ["ja 1", "ja 2"]
assert text.startswith("Commander, eine ruhige Woche.\n\nDie Woche in Stichpunkten:\n• Eingespielt: Gitea (App)")
assert text.endswith("Auf Zuruf:\n• Gitea 1.27.4 einspielen — „ja 1“\n"
"• alles auf einmal („Alle aktualisieren“) — „ja 2“")
offen = rueckfragen.liste(jetzt=JETZT)["offen"]
assert [(f["pfad"], f["von"]) for f in offen] == [("/api/partner/homelab/alle", "wochenbericht"),
("/api/partner/homelab/ziele/ct-104/update", "wochenbericht")]
assert json.loads((daten / "wochenbericht.json").read_text(encoding="utf-8"))["zeit"] == JETZT
assert wochenbericht.stand(JETZT)["zuletzt"]["text"] == text
def test_ohne_modell_ein_fester_satz(daten, monkeypatch):
monkeypatch.setattr(wochenbericht, "fakten", lambda jetzt=None: {})
monkeypatch.setattr(wochenbericht, "prosa", lambda zeilen: None)
bericht = wochenbericht.bauen(JETZT)
assert bericht["text"].startswith("Commander, hier ist dein Wochenbericht.\n\nDie Woche in Stichpunkten:")
assert bericht["knoepfe"] == [] and "Auf Zuruf" not in bericht["text"]
def test_naechster_termin():
samstag = datetime(2026, 9, 26, 12, 0, tzinfo=LOCAL_TZ).timestamp()
sonntag_acht = datetime(2026, 9, 27, 8, 0, tzinfo=LOCAL_TZ).timestamp()
assert wochenbericht.naechster(samstag) == JETZT
assert wochenbericht.naechster(sonntag_acht) == datetime(2026, 10, 4, 7, 30, tzinfo=LOCAL_TZ).timestamp()
def test_prosa_ohne_denkpause(monkeypatch):
class Antwort:
status_code = 200
@staticmethod
def json():
return {"choices": [{"message": {"content": "<think>hm</think>Commander, alles ruhig."}}]}
gesendet = {}
monkeypatch.setattr(wochenbericht.httpx, "post", lambda url, timeout, json: gesendet.update(json) or Antwort())
assert wochenbericht.prosa(["Eingespielt: nichts."]) == "Commander, alles ruhig."
assert gesendet["model"] == "fast" and gesendet["chat_template_kwargs"] == {"enable_thinking": False}
assert "Eingespielt: nichts." in gesendet["messages"][0]["content"]