Files
mission-control-v2/frontend/src/views/Einstellungen.test.tsx
T
HitonabiandClaude Opus 5.5 1104200a30 oberflaeche v4: Uebersicht als Startseite, KI-Box und Homelab mit Monitoring, Homelab-Updates je Geraet mit Alle aktualisieren, Protokoll, Einstellungsseite, neues Tab-Symbol
- 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>
2026-09-24 22:18:56 +02:00

123 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
})
})