Files
audi-app/companion-app/src/daten/DatenKontext.test.tsx
T
tobias 90efcc5b00 Neustart-Festhaenger, OTA-Pruefsumme, Statistik-Kopfzahl (2026.8.31.4)
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>
2026-08-31 15:35:56 +02:00

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)
})
})