"""Lucy liest Protokolle, wenn sie einen Hinweis untersucht (Ausbauplan Welle 3, Punkt 14, 25.09.2026): die Aktion journal des Ausführers (nur lesend) und GET /api/homelab/ziele/{id}/journal; dazu der Knopf „nur Sicherheitsupdates“ (POST /api/homelab/ziele/{id}/sicherheitsupdates), den ihre Werkzeuge und die Rückfragen nutzen.""" import importlib.util from pathlib import Path import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from services.homelab import inventar, journal, kanal, updates AUSFUEHRER = Path(__file__).resolve().parents[2] / "deploy" / "homelab" / "ausfuehrer.py" @pytest.fixture(scope="module") def a(): spec = importlib.util.spec_from_file_location("ausfuehrer_journal", AUSFUEHRER) modul = importlib.util.module_from_spec(spec) spec.loader.exec_module(modul) return modul def test_ausfuehrer_liest_nur(a, monkeypatch): befehle = [] monkeypatch.setattr(a, "_gast_pruefen", lambda vmid: ("lxc", vmid)) monkeypatch.setattr(a, "_im_gast", lambda vmid, befehl, zeitlimit=30: befehle.append((vmid, befehl)) or (0, "x" * 20000)) monkeypatch.setattr(a, "NUR_LESEN", True) # geht auch im Nur-Lesen-Modus antwort = a.ausfuehren({"aktion": "journal", "parameter": {"vmid": 104, "zeilen": 50}}) assert antwort["code"] == 0 and len(antwort["text"]) == 12000 vmid, befehl = befehle[0] assert vmid == 104 and "journalctl -p warning -n 50 --no-pager -o short-iso --since -24h" in befehl assert "tail -n 50 /var/log/messages" in befehl for falsch in ({"vmid": 104, "zeilen": "80; reboot"}, {"vmid": 104, "zeilen": 5000}): abgelehnt = a.ausfuehren({"aktion": "journal", "parameter": falsch}) assert abgelehnt["code"] == 2 and "zeilen" in abgelehnt["text"] monkeypatch.setattr(a, "_gast_pruefen", lambda vmid: ("qemu", vmid)) assert "nur von Containern" in a.ausfuehren({"aktion": "journal", "parameter": {"vmid": 106}})["text"] @pytest.fixture def gaeste(monkeypatch): liste = {"ct-104": {"vmid": 104, "art": "lxc", "erlaubt": True}, "vm-106": {"vmid": 106, "art": "qemu", "erlaubt": True}, "ct-120": {"vmid": 120, "art": "lxc", "erlaubt": False}} monkeypatch.setattr(inventar, "gast", lambda zid: liste.get(zid)) auftraege = [] ergebnis = {"status": "fertig", "text": "2026-09-25T03:12:00 gitea[311]: database is locked\n"} monkeypatch.setattr(kanal, "anlegen", lambda aktion, parameter=None: auftraege.append((aktion, parameter)) or "a1") monkeypatch.setattr(kanal, "warten", lambda aid, zeitlimit_s, takt_s=2.0: ergebnis) return auftraege, ergebnis def test_journal_ueber_den_ausfuehrer(gaeste): auftraege, ergebnis = gaeste assert journal.lesen("ct-104", 5000) == {"ok": True, "text": "2026-09-25T03:12:00 gitea[311]: database is locked"} assert auftraege == [("journal", {"vmid": 104, "zeilen": 300})] assert "nur von Containern" in journal.lesen("vm-106")["detail"] assert "nicht freigegeben" in journal.lesen("ct-120")["detail"] assert "nicht im Bericht" in journal.lesen("ct-999")["detail"] ergebnis.update(status="fehler", text="Abgelehnt: Gast 104 hat kein erlaubtes Etikett") assert journal.lesen("ct-104")["detail"].startswith("Abgelehnt") def test_schnittstellen(gaeste, monkeypatch): from routers import homelab gestartet = [] monkeypatch.setattr(updates, "starten", lambda zid, baustein, sammellauf=None: gestartet.append((zid, baustein)) or {"ok": True, "lauf": "l9"}) app = FastAPI() app.include_router(homelab.router) c = TestClient(app) assert c.get("/api/homelab/ziele/ct-104/journal").json()["ok"] assert c.get("/api/homelab/ziele/vm-106/journal").status_code == 409 assert c.post("/api/homelab/ziele/ct-104/sicherheitsupdates").json() == {"ok": True, "lauf": "l9"} assert gestartet == [("ct-104", "sicherheit")]