/**
* Was passiert, wenn Home Assistant neu startet.
*
* Gemeldet vom Eigentümer (2026-08-31): während eines Neustarts zeigte die App
* „502 Error“, und die Meldung blieb stehen, nachdem Home Assistant längst
* wieder lief — erst ein Beenden der App half. Zwei Ursachen, beide hier
* abgesichert:
*
* 1. 502 galt als harter Ladefehler statt als „Server kommt gerade hoch“.
* 2. Nach dem Wiederverbinden wurde genau einmal nachgeladen. Schlug das
* fehl (die Websocket-Verbindung steht regelmäßig ein paar Sekunden vor
* der REST-Schnittstelle), kam nie wieder ein „verbunden“ — und damit nie
* wieder ein Versuch.
*/
import { render, screen, waitFor } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"
import { ApiFehler, type DataMetricApi } from "../api"
import { DatenAnbieter, useDaten } from "./DatenKontext"
import { beispielApi } from "../tests/beispieldaten"
function Anzeige() {
const { bereit, ladefehler } = useDaten()
return (
{bereit ? "ja" : "nein"}
{ladefehler ?? "-"}
)
}
/** Eine API, deren `profilLesen` sich auf Kommando wie ein gerade startender
Home Assistant verhält: die nächsten n Aufrufe mit 502 abweisen, danach
wieder normal antworten.
Die Fehlschläge werden bewusst gezählt und nicht an der Aufrufnummer
festgemacht: der Horcher meldet beim Abonnieren sofort "verbunden", die
Zählung liefe sonst schon beim Aufbau los. */
function apiMitNeustart() {
const echt = beispielApi()
let verbindungHorcher: ((z: string) => void) | null = null
let offeneFehlschlaege = 0
const profilLesen = vi.fn(async () => {
if (offeneFehlschlaege > 0) {
offeneFehlschlaege -= 1
throw new ApiFehler("Bad Gateway", 502)
}
return echt.profilLesen()
})
const api = {
...echt,
profilLesen,
live: {
...echt.live,
aufVerbindung: (h: (z: string) => void) => {
verbindungHorcher = h
h("verbunden")
return () => {}
},
},
} as unknown as DataMetricApi
return {
api,
profilLesen,
/** Home Assistant startet neu: die nächsten n Ladeversuche laufen ins
502, dann meldet sich die Websocket-Verbindung zurück. */
neustartMelden: (fehlschlaege: number) => {
offeneFehlschlaege = fehlschlaege
verbindungHorcher?.("verbunden")
},
}
}
describe("Neustart von Home Assistant", () => {
it("meldet 502 nicht als Ladefehler, solange Daten da sind", async () => {
const { api, neustartMelden } = apiMitNeustart()
render(
,
)
await waitFor(() => expect(screen.getByTestId("bereit").textContent).toBe("ja"))
neustartMelden(1)
// Der fehlgeschlagene Versuch darf keine Fehlerzeile hinterlassen.
await waitFor(() => expect(screen.getByTestId("fehler").textContent).toBe("-"))
expect(screen.getByTestId("bereit").textContent).toBe("ja")
})
it("versucht es nach einem fehlgeschlagenen Nachladen erneut", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
try {
const { api, profilLesen, neustartMelden } = apiMitNeustart()
render(
,
)
await waitFor(() => expect(screen.getByTestId("bereit").textContent).toBe("ja"))
const vorher = profilLesen.mock.calls.length
neustartMelden(2)
await waitFor(() => expect(profilLesen.mock.calls.length).toBe(vorher + 1))
// Ohne Wiederholung bliebe es bei diesem einen Versuch — und die App
// auf dem Stand von vor dem Neustart, bis jemand sie beendet.
await vi.advanceTimersByTimeAsync(3000)
await waitFor(() => expect(profilLesen.mock.calls.length).toBe(vorher + 2))
await vi.advanceTimersByTimeAsync(5000)
await waitFor(() => expect(profilLesen.mock.calls.length).toBe(vorher + 3))
expect(screen.getByTestId("fehler").textContent).toBe("-")
} finally {
vi.useRealTimers()
}
})
})
describe("ApiFehler.istVoruebergehend", () => {
it("gilt für kein Netz und für einen startenden Server", () => {
expect(new ApiFehler("offline", null).istVoruebergehend).toBe(true)
expect(new ApiFehler("bad gateway", 502).istVoruebergehend).toBe(true)
expect(new ApiFehler("unavailable", 503).istVoruebergehend).toBe(true)
expect(new ApiFehler("timeout", 504).istVoruebergehend).toBe(true)
})
it("gilt nicht für einen echten Serverfehler", () => {
// 500 bessert sich durch Warten nicht — das muss sichtbar bleiben.
expect(new ApiFehler("kaputt", 500).istVoruebergehend).toBe(false)
expect(new ApiFehler("nicht da", 404).istVoruebergehend).toBe(false)
})
})