- Startseite "Alles auf einen Blick": Gesamtlage, je Bereich Messwerte mit Verlauf, wer welche Updates hat (Kurzform, Klick fuehrt in den Bereich), Hinweise, als Naechstes und zuletzt eingespielt - Menue: Uebersicht, KI-Box (Cockpit, Updates, Modelle, Monitoring), Homelab (Cockpit, Updates, Monitoring, Protokoll), Einstellungen; Bereichsfarben Indigo und Violett; alte Adressen /updates und /modelle leiten weiter - Homelab-Updates je Geraet: App-Version laeuft -> neu (mit Grund, wenn sie wartet), Pakete, Rueckweg, Knoepfe; Alle aktualisieren mit Plan in der Rueckfrage und Fortschritt; Cockpit zeigt die Gesundheit der Geraete - Einstellungen als Seite: KI-Box, Homelab (Arcane-Schluessel, Docker echt/Probelauf, Ausfuehrer), Meldungen, Software-Stand - Diagramme: Verlauf mit Fadenkreuz, Legende und Tabellenansicht, Kennzahlen mit Linie; Farben geprueft - Tab-Symbol: favicon.ico/.svg, apple-touch-icon, Manifest-Icons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
5.5 KiB
TypeScript
123 lines
5.5 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"
|
||
import { api, post } from "@/lib/api"
|
||
import type { EinstellungenStand, InstanzMitStand } from "@/lib/typen"
|
||
import { EinstellungenSeite } from "./Einstellungen"
|
||
|
||
vi.mock("@/lib/api", async (original) => ({
|
||
...(await original<typeof import("@/lib/api")>()),
|
||
api: vi.fn(),
|
||
post: vi.fn(),
|
||
}))
|
||
|
||
const BOX: EinstellungenStand = {
|
||
schaltbar: true,
|
||
update_fenster: {
|
||
tag: 7, uhrzeit: "04:30", tag_name: "Sonntag", neustart_fenster: "Sonntag 04:00–06:59", timer_aktiv: true,
|
||
naechster_lauf: "2026-09-27T04:36:00+02:00", wirksam: true,
|
||
},
|
||
radar: { an: true, zustand: "an", naechster_lauf: "2026-09-25T00:30:00+02:00", wirksam: true },
|
||
}
|
||
|
||
function instanz(rolle: "box" | "homelab", commit: string): InstanzMitStand {
|
||
return {
|
||
rolle, instanz: rolle === "box" ? "Box-Wart" : "Homelab",
|
||
partner: { eingerichtet: true, name: rolle === "box" ? "Homelab" : "Box-Wart" },
|
||
stand: {
|
||
quelle: "deploy", commit, betreff: "doku: Phase 4", commit_datum: "2026-09-24T20:46:03+02:00",
|
||
deploy_zeit: "2026-09-24T21:10:00+02:00",
|
||
},
|
||
}
|
||
}
|
||
|
||
function antworten(partnerCommit = "40d2840") {
|
||
const daten: Record<string, unknown> = {
|
||
"/api/maintenance/geheimnisse": { hf_token_gesetzt: false, hf_token_aus_env: false, schreibbar: true },
|
||
"/api/einstellungen": BOX,
|
||
"/api/instanz": instanz("box", "40d2840"),
|
||
"/api/partner/instanz": instanz("homelab", partnerCommit),
|
||
"/api/partner": { eingerichtet: true, name: "Homelab", erreichbar: true },
|
||
"/api/homelab/einstellungen": {
|
||
arcane: { url: "http://192.168.178.28:3552", schluessel: "fehlt", echt: false, echt_quelle: "standard" },
|
||
karenz_h: 48,
|
||
ausfuehrer: { verbunden: true, zuletzt: 1_790_000_000, nur_lesen: false, sicherung_speicher: "local" },
|
||
},
|
||
}
|
||
vi.mocked(api).mockImplementation((async (pfad: string) => {
|
||
if (pfad in daten) return daten[pfad]
|
||
throw new Error(`unerwartete Abfrage ${pfad}`)
|
||
}) as typeof api)
|
||
}
|
||
|
||
function zeigen() {
|
||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||
render(
|
||
<QueryClientProvider client={qc}>
|
||
<EinstellungenSeite />
|
||
</QueryClientProvider>,
|
||
)
|
||
}
|
||
|
||
describe("Einstellungen", () => {
|
||
it("schaltet das Radar erst nach der Rückfrage aus", async () => {
|
||
antworten()
|
||
vi.mocked(post).mockResolvedValue({ ok: true, text: "Das Radar ist aus und schläft." })
|
||
zeigen()
|
||
fireEvent.click(await screen.findByRole("button", { name: "Ausschalten" }))
|
||
const dialog = await screen.findByRole("dialog", { name: "Modell-Radar ausschalten?" })
|
||
expect(dialog).toHaveTextContent("Auch ein Deploy schaltet es nicht wieder ein.")
|
||
expect(post).not.toHaveBeenCalled()
|
||
|
||
fireEvent.click(within(dialog).getByRole("button", { name: "Ausschalten" }))
|
||
await waitFor(() => expect(post).toHaveBeenCalledWith("/api/einstellungen/radar", { an: false }))
|
||
})
|
||
|
||
it("nennt vor dem Speichern des Zeitfensters das neue Neustart-Fenster", async () => {
|
||
antworten()
|
||
vi.mocked(post).mockResolvedValue({ ok: true, text: "Gespeichert." })
|
||
zeigen()
|
||
const bereich = await screen.findByRole("region", { name: "Update-Zeitfenster" })
|
||
const speichern = await within(bereich).findByRole("button", { name: "Speichern" })
|
||
expect(speichern).toBeDisabled() // nichts geändert, der Timer passt
|
||
fireEvent.change(within(bereich).getByRole("combobox", { name: "Wochentag" }), { target: { value: "6" } })
|
||
fireEvent.change(within(bereich).getByRole("combobox", { name: "Uhrzeit" }), { target: { value: "23:30" } })
|
||
fireEvent.click(speichern)
|
||
|
||
const dialog = await screen.findByRole("dialog", { name: "Update-Zeitfenster ändern?" })
|
||
expect(dialog).toHaveTextContent("jeden Samstag um 23:30 Uhr")
|
||
expect(dialog).toHaveTextContent("Samstag 23:00 – Sonntag 01:59")
|
||
expect(post).not.toHaveBeenCalled()
|
||
fireEvent.click(within(dialog).getByRole("button", { name: "Speichern" }))
|
||
await waitFor(() =>
|
||
expect(post).toHaveBeenCalledWith("/api/einstellungen/update-fenster", { tag: 6, uhrzeit: "23:30" }))
|
||
})
|
||
|
||
it("zeigt beim Telegram-Test des Homelab-Teils den Grund aus dem Melde-Log", async () => {
|
||
antworten()
|
||
vi.mocked(post).mockResolvedValue({
|
||
ok: false, gesendet: false, weg: null, grund: "Zweitweg: Telegram lehnt ab, HTTP-Fehler",
|
||
detail: "Die Testmeldung ging nicht raus.", instanz: "Homelab", zeit: "2026-09-24T21:14:00+02:00",
|
||
})
|
||
zeigen()
|
||
fireEvent.click(await screen.findByRole("button", { name: "Testmeldung von Homelab senden" }))
|
||
await waitFor(() => expect(post).toHaveBeenCalledWith("/api/homelab/einstellungen/telegram-test"))
|
||
expect(await screen.findByText("Nicht gesendet: Zweitweg: Telegram lehnt ab, HTTP-Fehler")).toBeInTheDocument()
|
||
})
|
||
|
||
it("zeigt beide Software-Stände und warnt, wenn sie abweichen", async () => {
|
||
antworten("37133fd")
|
||
zeigen()
|
||
expect(await screen.findByText(/laufen mit verschiedenen Ständen/)).toBeInTheDocument()
|
||
expect(screen.getByText("40d2840")).toBeInTheDocument()
|
||
expect(screen.getByText("37133fd")).toBeInTheDocument()
|
||
expect(screen.getAllByText(/Live seit 24\.09\.2026, 21:10/)).toHaveLength(2)
|
||
})
|
||
|
||
it("schweigt bei gleichem Stand, auch wenn ein Hash länger ist", async () => {
|
||
antworten("40d2840a")
|
||
zeigen()
|
||
expect(await screen.findByText("40d2840a")).toBeInTheDocument()
|
||
expect(screen.queryByText(/laufen mit verschiedenen Ständen/)).not.toBeInTheDocument()
|
||
})
|
||
})
|