Files
mission-control-v2/frontend/src/lib/homelab.test.ts
T
HitonabiandClaude Opus 5.5 ad8dbbc1b3 Welle 1 · Live-Zahlen je Dienst auf den Kacheln, AdGuard-Zugang in den Einstellungen
Jede Gerätekachel (Homelab-Cockpit und Übersicht) zeigt eine Live-Zahl, wo es eine gibt: Proxmox-Host „8 von 8
Gästen an“, PBS „letzte Sicherung vor 13 h“, NPMplus „10 Zertifikate gültig“ (aus der Zertifikats-Wache), Arcane
„11 von 12 Containern an“ (Arcane-API mit dem vorhandenen Schlüssel) und AdGuard „12 % geblockt“. Für AdGuard gibt es
unter Einstellungen → Homelab einen Zugang, der vor dem Speichern gegen AdGuard geprüft wird und nur im Homelab-Teil
liegt (0600, das Passwort steht in keiner Antwort). Arcane und AdGuard werden höchstens alle zwei Minuten gefragt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 16:52:41 +02:00

113 lines
5.4 KiB
TypeScript

import { kennzahlText, lage, lampeFuer, listenAlter, offeneUpdates, rueckwegText } from "@/lib/homelab"
import type { BausteinStand, Hinweis, HomelabStand, ZielStand } from "@/lib/typen"
const baustein = (b: Partial<BausteinStand> & Pick<BausteinStand, "id" | "zustand">): BausteinStand => ({
name: b.id, installiert: null, verfuegbar: null, kurz: "", grund: null, aktion: null, ...b,
})
const ziel = (z: Partial<ZielStand> & Pick<ZielStand, "id" | "name" | "bausteine">): ZielStand => ({
art: "container", instanz: "homelab", erreichbar: true, geprueft: null, rueckweg: "Snapshot vor jedem Update",
zaehlung: {}, offen: 0, ...z,
})
const knopf = { methode: "POST" as const, pfad: "/api/homelab/ziele/x/update", frage: "Wirklich?", label: "Jetzt updaten" }
const netbird = ziel({
id: "ct-102", name: "NetBird",
bausteine: [
baustein({ id: "os", zustand: "unbekannt", grund: "Die Paketlisten sind vom 22.08.2026; was fehlt, weiß erst eine neue Suche." }),
baustein({ id: "app", zustand: "neu", installiert: "0.77.1", verfuegbar: "0.79.0", aktion: knopf }),
],
})
const arcane = ziel({
id: "vm-106", name: "Arcane (Docker)", art: "vm",
bausteine: [baustein({ id: "arcane", zustand: "neu", installiert: "2.12.0", verfuegbar: "2.13.1", grund: "eigene Oberfläche" })],
})
const npm = ziel({
id: "ct-101", name: "NPMplus",
bausteine: [baustein({ id: "app", zustand: "aktuell", installiert: "Image vom 2026-07-24" })],
})
const stand = (ziele: ZielStand[], verbunden = true): HomelabStand => ({
ziele, bericht: { empfangen: 1_790_000_000, veraltet: false }, ausfuehrer: { verbunden, zuletzt: null },
})
const hinweis = (stufe: Hinweis["stufe"]): Hinweis => ({
id: "h", stufe, titel: "t", text: "x", quelle: "q", seit: 0, zuletzt: 0, aktionen: [],
})
describe("offeneUpdates", () => {
it("setzt, was per Knopf geht, vor das, was die App selbst erledigt", () => {
const liste = offeneUpdates([arcane, netbird])
expect(liste.map((u) => u.ziel.id)).toEqual(["ct-102", "vm-106"])
})
})
describe("lampeFuer", () => {
it("zeigt die neue Version einer App als Info", () => {
expect(lampeFuer(netbird, false)).toMatchObject({ label: "NetBird", zustand: "info", wert: "→ 0.79.0" })
})
it("kürzt Namen und Image-Datum für die Lampe", () => {
expect(lampeFuer(npm, false)).toMatchObject({ zustand: "ok", wert: "2026-07-24" })
expect(lampeFuer(arcane, false).label).toBe("Arcane")
})
it("warnt bei voller Platte vor allem anderen außer Stille", () => {
expect(lampeFuer({ ...netbird, platte: { belegt: 98, gesamt: 100 } }, true)).toMatchObject({ zustand: "fehler", wert: "Platte voll" })
expect(lampeFuer({ ...npm, platte: { belegt: 85, gesamt: 100 } }, false)).toMatchObject({ zustand: "warn", wert: "Platte 85 %" })
expect(lampeFuer({ ...npm, platte: { belegt: 50, gesamt: 100 } }, false).zustand).toBe("ok")
})
it("meldet ein stummes Gerät rot und ein laufendes Update als Info", () => {
expect(lampeFuer({ ...npm, erreichbar: false }, false).zustand).toBe("fehler")
expect(lampeFuer(npm, true)).toMatchObject({ zustand: "info", wert: "Update läuft" })
})
})
describe("lage", () => {
it("zählt nur Updates mit Knopf", () => {
expect(lage(stand([netbird, arcane]), [], 0, null)).toMatchObject({ art: "info", mitte: "1 Update", ziel: "updates" })
})
it("stellt Störung vor Hinweis vor Update", () => {
expect(lage(stand([{ ...npm, erreichbar: false }, netbird]), [hinweis("gelb")], 0, null)).toMatchObject({
art: "rot", mitte: "1 Gerät stumm", unten: "NPMplus",
})
expect(lage(stand([netbird]), [hinweis("gelb")], 0, null)).toMatchObject({ art: "gelb", mitte: "1 Hinweis" })
expect(lage(stand([netbird]), [hinweis("rot"), hinweis("gelb")], 0, null)).toMatchObject({ art: "rot", mitte: "2 Hinweise" })
expect(lage(stand([netbird], false), [], 0, null)).toMatchObject({ art: "gelb", mitte: "Ausführer stumm" })
expect(lage(stand([netbird]), [], 1, null)).toMatchObject({ art: "info", mitte: "Update läuft" })
})
it("sagt „alles aktuell“ nur ohne offene Updates", () => {
expect(lage(stand([npm]), [], 0, "HEUTE 20:14")).toMatchObject({ art: "ok", mitte: "Alles aktuell", unten: "Stand HEUTE 20:14" })
})
})
describe("rueckwegText", () => {
it("fasst den Rückweg in wenigen Worten", () => {
expect(rueckwegText(netbird)).toBe("Snapshot vorher, bei Rot zurück")
expect(rueckwegText({ ...netbird, rueckweg: "Sicherung vor jedem Update" })).toBe("Sicherung vorher, bei Rot zurück")
expect(rueckwegText({ ...netbird, rueckweg_art: "sicherung", rueckweg: null })).toBe("Sicherung vorher, bei Rot zurück")
expect(rueckwegText({ ...netbird, art: "proxmox-host", rueckweg: "Kein Snapshot möglich" })).toBe("Kein Rückweg, Neustart getrennt")
})
})
describe("listenAlter", () => {
it("holt das Datum der Paketlisten aus dem Grund", () => {
expect(listenAlter(netbird.bausteine[0].grund)).toBe("22.08.2026")
expect(listenAlter(null)).toBeNull()
})
})
describe("kennzahlText (seit 25.09.2026)", () => {
it("setzt „vor …“ hinter Zahlen mit Zeitpunkt", () => {
const jetzt = Math.floor(Date.now() / 1000)
expect(kennzahlText({ text: "12 % geblockt" })).toBe("12 % geblockt")
expect(kennzahlText({ text: "letzte Sicherung", zeit: jetzt - 13 * 3600 })).toBe("letzte Sicherung vor 13 h")
expect(kennzahlText(null)).toBeNull()
expect(kennzahlText(undefined)).toBeNull()
})
})