MC2 wird Updater, Waechter und Modell-Radar (Konzept „MC2 als Box-Wart“, 23.09.2026). - Neuer Waechter (services/waechter.py) loest sentry.py ab: Dienste, Timer-Laeufe, Hermes-Jobs samt Werkzeugfehlern, Kern-HTTP-Proben, Platte. Abgestuerzte Dienste startet er selbst neu (max. 2/h), rote Hinweise gehen an Telegram und Lucy. Laeuft im mc2-steward; waehrend eines Updates haelt er still. - Neue Schnittstellen (routers/boxwart.py): /api/start, /api/hinweise (+ Aktionen), /api/modelle/nutzung, /api/zeitplan. - Modell-Nutzung aus dem llama-swap-Journal (wer fragt wie oft, 24 h je Stunde). - Entfernt: Ideen, Wissen, Chronik, Skills, Verbinden, Konsolen-Proxy, /api/events; Lucys Werkzeug idee_notieren; box_status nennt jetzt die offenen Hinweise. - Behoben: projekte-sync ueberspringt leere Gitea-Repos (lief seit 07.09. stuendlich rot); Motor-Version kam aus dem verwaisten /opt/llamacpp statt /opt/llamacpp-vulkan. - Erste Backend-Tests (8) fuer Waechter und Modell-Nutzung. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
3.6 KiB
Python
80 lines
3.6 KiB
Python
"""Tests für die reinen Auswertungen des Wächters — mit echten Zeilen von der Box (23.09.2026)."""
|
|
|
|
from services import waechter
|
|
|
|
PROJEKTE_SYNC_LOG = [
|
|
"2026-09-23 21:02:44 === Sync-Lauf (14 Repos gemeldet) ===",
|
|
"2026-09-23 21:02:45 = NerdQuiz-next: aktuell (a1b2c3d)",
|
|
"2026-09-23 21:02:46 ! TTT2-Toolbox_ClaudeEdition: Pull fehlgeschlagen — fatal: no such ref was fetched",
|
|
"2026-09-23 21:02:46 === Ende (Fehler: 1) ===",
|
|
]
|
|
|
|
ERRORS_LOG = [
|
|
'2026-09-23 07:02:41,584 WARNING [cron_195e479e8e30_20260923_070017] agent.tool_executor: '
|
|
'Tool write_file returned error (0.00s): {"error": "Refusing to overwrite /tmp/news-text.md: '
|
|
'/tmp/news-text.md exists"}',
|
|
'2026-09-23 07:02:58,516 WARNING [cron_195e479e8e30_20260923_070017] agent.tool_executor: '
|
|
'Tool write_file returned error (0.00s): {"error": "Refusing to overwrite /tmp/news-text.md"}',
|
|
"2026-09-23 07:03:39,596 WARNING gateway.mirror: Mirror: no session found for telegram:1 thread=None",
|
|
'2026-09-22 07:01:10,000 WARNING [cron_195e479e8e30_20260922_070017] agent.tool_executor: '
|
|
'Tool web_extract returned error (0.10s): {"error": "timeout"}',
|
|
]
|
|
|
|
|
|
def test_fehlerzeile_nimmt_die_letzte_fehlerzeile_ohne_zeitstempel():
|
|
zeile = waechter.waehle_fehlerzeile(PROJEKTE_SYNC_LOG)
|
|
assert zeile.startswith("! TTT2-Toolbox_ClaudeEdition: Pull fehlgeschlagen")
|
|
assert "2026-09-23" not in zeile
|
|
|
|
|
|
def test_fehlerzeile_leer_wenn_nichts_nach_fehler_aussieht():
|
|
assert waechter.waehle_fehlerzeile(["alles gut", "=== Ende (Fehler: 0) ==="]) == ""
|
|
|
|
|
|
def test_werkzeugfehler_zaehlt_nur_den_lauf_des_tages():
|
|
anzahl, beispiel = waechter.werkzeugfehler_im_lauf(
|
|
ERRORS_LOG, "195e479e8e30", "2026-09-23T07:00:17.675474+02:00")
|
|
assert anzahl == 2
|
|
assert beispiel.startswith("write_file: Refusing to overwrite /tmp/news-text.md")
|
|
|
|
|
|
def test_werkzeugfehler_null_fuer_anderen_job_oder_kaputtes_datum():
|
|
assert waechter.werkzeugfehler_im_lauf(ERRORS_LOG, "ca7d4ed207c2", "2026-09-23T08:00:00+02:00") == (0, "")
|
|
assert waechter.werkzeugfehler_im_lauf(ERRORS_LOG, "195e479e8e30", "kein-datum") == (0, "")
|
|
|
|
|
|
def test_befunde_werden_erst_nach_fail_after_takten_zum_hinweis(monkeypatch, tmp_path):
|
|
"""Flankenlogik: ein kurzer Aussetzer (1 Takt) erzeugt keinen Hinweis, ein dauerhafter schon.
|
|
Rote Hinweise gehen an Telegram, Erledigtes verschwindet und landet im Verlauf."""
|
|
monkeypatch.setattr(waechter, "STORE_PATH", tmp_path / "waechter.json")
|
|
monkeypatch.setattr(waechter, "_stand", {"hinweise": {}, "kandidaten": {}, "verlauf": [], "auto": {}, "stand": 0.0})
|
|
monkeypatch.setattr(waechter, "_update_laeuft", lambda: False)
|
|
meldungen: list[str] = []
|
|
monkeypatch.setattr(waechter, "_telegram", lambda betreff, text: meldungen.append(betreff))
|
|
defekt = [True]
|
|
|
|
def pruefung():
|
|
if not defekt[0]:
|
|
return []
|
|
return [waechter.Befund(id="kern:engine", stufe="rot", titel="Der Motor antwortet nicht",
|
|
text="…", quelle="engine")]
|
|
|
|
monkeypatch.setattr(waechter, "PRUEFUNGEN", (pruefung,))
|
|
monkeypatch.setattr(waechter, "FAIL_AFTER", 2)
|
|
|
|
waechter.takt()
|
|
assert waechter._stand["hinweise"] == {} # erster Takt: nur Kandidat
|
|
waechter.takt()
|
|
assert "kern:engine" in waechter._stand["hinweise"]
|
|
assert meldungen == ["[Box-Problem]"]
|
|
|
|
defekt[0] = False
|
|
waechter.takt()
|
|
assert waechter._stand["hinweise"] == {}
|
|
assert meldungen == ["[Box-Problem]", "[Box wieder ok]"]
|
|
arten = [v["art"] for v in waechter._stand["verlauf"]]
|
|
assert arten == ["neu", "erledigt"]
|
|
|
|
stand = waechter.lese_stand()
|
|
assert stand["aktiv"] is True and stand["hinweise"] == []
|