90efcc5b00
Die App blieb nach einem Neustart von Home Assistant auf dem alten Stand stehen, mit einer 502-Fehlerzeile, die erst ein Beenden der App wegbekam. Zwei Ursachen: 502/503/504 galten als harter Ladefehler statt als "Server faehrt gerade hoch", und nach dem Wiederverbinden wurde genau einmal nachgeladen - schlug das fehl, kam nie wieder ein Versuch. Beides behoben, vier Regressionstests dazu. OTA: das Buendel ist von HEAD ueber die Platte bis zu den ausgelieferten Bytes nachweislich deckungsgleich. Die Fassung haengt jetzt trotzdem als ?v= an der Adresse (der statische Pfad kommt ohne Cache-Control, aber mit ETag), und ein Pruefsummenfehler wird erklaert statt durchgereicht. Statistik: der Gesamtwert steht jetzt als grosse Zahl in der Kopfzeile, darunter bleiben vier Spalten - fuenf passen auf dem Telefon nachweislich nicht in eine Zeile. Fahrzeugfoto ohne Beschnitt und ohne Bodenschatten, damit auch ohne den Schalter dafuer. Setup filtert zusaetzlich nach dem Signalnamen aus dem Katalog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
/**
|
|
* 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 (
|
|
<div>
|
|
<span data-testid="bereit">{bereit ? "ja" : "nein"}</span>
|
|
<span data-testid="fehler">{ladefehler ?? "-"}</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** 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(
|
|
<DatenAnbieter api={api}>
|
|
<Anzeige />
|
|
</DatenAnbieter>,
|
|
)
|
|
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(
|
|
<DatenAnbieter api={api}>
|
|
<Anzeige />
|
|
</DatenAnbieter>,
|
|
)
|
|
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)
|
|
})
|
|
})
|