import { kennzahlText, lage, lampeFuer, listenAlter, offeneUpdates, rueckwegText } from "@/lib/homelab" import type { BausteinStand, Hinweis, HomelabStand, ZielStand } from "@/lib/typen" const baustein = (b: Partial & Pick): BausteinStand => ({ name: b.id, installiert: null, verfuegbar: null, kurz: "", grund: null, aktion: null, ...b, }) const ziel = (z: Partial & Pick): 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() }) })