Phase 9: Tests fuer die portierte Rechenlogik

41 weitere Tests fuer Statistik, Service-Prognose und Formatierung. Die
Faelle sind entlang der Regeln gewaehlt, die beim Portieren wichtig waren:
Wochenbeginn Montag, Nachtspanne ueber Mitternacht, mengengewichteter
Durchschnittspreis statt Mittel der Einzelpreise, Plausibilitaetsfenster beim
Langzeitverbrauch, Neustart der Oelprognose ab dem letzten Wechsel samt
Zeitlimit-Deckelung, BOM und Semikolon in der CSV-Ausgabe.

Ein Test war zunaechst falsch: er verglich das UTC-Datum gegen eine
Ortszeit-Erwartung. Der Code rechnet bewusst lokal - sonst begaenne der
Monat in unserer Zeitzone einen Tag zu frueh.

companion-app/README.md auf den erreichten Stand gebracht.

Gesamt: 90 Unit- und Rendertests plus 9 Pruefungen gegen die laufende
Home-Assistant-Instanz.
This commit is contained in:
Paul Nothaft
2026-08-11 11:21:56 +02:00
parent 571d12310f
commit a68817065b
4 changed files with 451 additions and 60 deletions
+94
View File
@@ -0,0 +1,94 @@
import { describe, expect, it } from "vitest"
import { letzterOelwechsel, oelwechselPrognose } from "./service"
import type { Fahrzeug } from "./profilAdapter"
function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug {
return {
fin: "",
details: "",
erstzulassung: "",
hu: "",
odo,
odoBekannt: true,
tankPct: 0,
reichweite: null,
batteriespannung: null,
gesichert: null,
sicherheitscheck: [],
oelwechselFaelligTs: null,
oelwechselFaelligKm: null,
inspektionFaelligTs: null,
inspektionFaelligKm: null,
smartdeal: { aktiv: false, laeuftAb: null },
oel: { modus, km: 15000, monate: 12, herstellerKm: 30000, herstellerMonate: 24 },
versicherung: {},
steuer: { faellig: "" },
reifen: { aktiv: "Sommer", anzugsmoment: null, wechsel: {}, sommer: {}, winter: {} },
service: {},
technik: {},
ausstattung: [],
}
}
const BUCH = [
{ datum: "2026-02-10", km: 40000, art: "Ölwechsel" },
{ datum: "2025-03-05", km: 25000, art: "Inspektion" },
]
describe("letzterOelwechsel", () => {
it("findet den jüngsten Ölwechsel und ignoriert andere Arten", () => {
expect(letzterOelwechsel(BUCH)?.datum).toBe("2026-02-10")
})
it("erkennt auch die Schreibweise ohne Umlaut", () => {
expect(letzterOelwechsel([{ datum: "2026-01-01", km: 1, art: "Oelwechsel" }])).not.toBeNull()
})
it("liefert null, wenn nie einer eingetragen wurde", () => {
expect(letzterOelwechsel([{ datum: "2026-01-01", km: 1, art: "Inspektion" }])).toBeNull()
})
})
describe("oelwechselPrognose", () => {
const jetzt = new Date("2026-08-11T12:00:00")
it("rechnet ab dem letzten Ölwechsel, nicht ab dem ersten Buchungseintrag", () => {
// Seit 10.02. sind 182 Tage vergangen, 6000 km gefahren -> rund 33 km/Tag.
// Ziel ist 40000 + 30000 = 70000, offen sind also 24000 km.
const p = oelwechselPrognose(fahrzeug(46000), BUCH, jetzt)
expect(p).not.toBeNull()
expect(p!.restKm).toBe(24000)
expect(p!.kmProTag).toBeGreaterThan(30)
expect(p!.kmProTag).toBeLessThan(36)
})
it("deckelt auf das Zeitlimit, wenn wenig gefahren wurde", () => {
// Nur 100 km seit dem Wechsel: die Kilometerprognose läge Jahre entfernt,
// das Zeitlimit von 24 Monaten greift vorher.
const p = oelwechselPrognose(fahrzeug(40100), BUCH, jetzt)
expect(p!.durchZeitlimit).toBe(true)
expect(p!.datum).toBe("2028-02-10")
})
it("meldet Fälligkeit sofort, wenn das Intervall überschritten ist", () => {
const p = oelwechselPrognose(fahrzeug(75000), BUCH, jetzt)
expect(p!.restKm).toBe(0)
expect(p!.datum).toBe("2026-08-11")
})
it("nutzt bei eigenem Intervall den kleineren der beiden Werte", () => {
// modus "eigen": min(15000, 30000) = 15000 -> Ziel 55000, offen 9000.
const p = oelwechselPrognose(fahrzeug(46000, "eigen"), BUCH, jetzt)
expect(p!.restKm).toBe(9000)
})
it("liefert nichts ohne bekannten Kilometerstand", () => {
const ohne = { ...fahrzeug(0), odoBekannt: false }
expect(oelwechselPrognose(ohne, BUCH, jetzt)).toBeNull()
})
it("liefert nichts, wenn nie ein Ölwechsel eingetragen wurde", () => {
expect(oelwechselPrognose(fahrzeug(46000), [], jetzt)).toBeNull()
})
})