import { describe, expect, it } from "vitest"; import { beanstandetePunkte, beantwortet, dauerInMinuten, ergebnisVorschlag, geraetStatusNachPruefung, messwertInOrdnung, offenePflichtpunkte, punktInOrdnung, type Antwort, type Punkt, } from "@/lib/wartung/bewertung"; /** * Bewertung einer Prüfung. * * Der Kern ist eine einzige fachliche Regel: ein durchgefallener Pflichtpunkt * bedeutet "nicht bestanden", ein durchgefallener Kann-Punkt "bestanden mit * Mängeln". Daran hängt, ob ein Gerät im Einsatz sein darf — deshalb steht das * hier so ausführlich unter Test. */ const SICHTPRUEFUNG: Punkt = { id: "p1", text: "Trageeinrichtung ohne Beschädigung", typ: "JA_NEIN", pflicht: true, }; const WARNDRUCK: Punkt = { id: "p2", text: "Ansprechdruck der Warneinrichtung", typ: "MESSWERT", pflicht: true, minWert: 50, maxWert: 60, }; const BEUTEL: Punkt = { id: "p3", text: "Leinenbeutel unbeschädigt", typ: "JA_NEIN", pflicht: false, }; const BEFUND: Punkt = { id: "p4", text: "Auffälligkeiten", typ: "TEXT", pflicht: false, }; const FOTO: Punkt = { id: "p5", text: "Foto nach der Reinigung", typ: "FOTO", pflicht: false }; function antwort(punktId: string, teil: Partial = {}): Antwort { return { punktId, ...teil }; } describe("messwertInOrdnung", () => { it("prüft gegen beide Grenzen", () => { expect(messwertInOrdnung(55, 50, 60)).toBe(true); expect(messwertInOrdnung(50, 50, 60)).toBe(true); expect(messwertInOrdnung(60, 50, 60)).toBe(true); expect(messwertInOrdnung(49.9, 50, 60)).toBe(false); expect(messwertInOrdnung(61, 50, 60)).toBe(false); }); it("behandelt eine fehlende Grenze als offen", () => { expect(messwertInOrdnung(300, 270, null)).toBe(true); expect(messwertInOrdnung(0.2, null, 0.5)).toBe(true); expect(messwertInOrdnung(0.8, null, 0.5)).toBe(false); }); it("wertet einen fehlenden Wert nicht als in Ordnung", () => { expect(messwertInOrdnung(null, 50, 60)).toBe(false); expect(messwertInOrdnung(undefined)).toBe(false); expect(messwertInOrdnung(Number.NaN, 0, 10)).toBe(false); }); }); describe("beantwortet", () => { it("erkennt jede Antwortart", () => { expect(beantwortet(SICHTPRUEFUNG, antwort("p1", { jaNein: false }))).toBe(true); expect(beantwortet(WARNDRUCK, antwort("p2", { zahlwert: 0 }))).toBe(true); expect(beantwortet(BEFUND, antwort("p4", { text: "ohne Befund" }))).toBe(true); expect(beantwortet(FOTO, antwort("p5", { fotoDateiId: "abc" }))).toBe(true); }); it("erkennt fehlende Antworten", () => { expect(beantwortet(SICHTPRUEFUNG, undefined)).toBe(false); expect(beantwortet(SICHTPRUEFUNG, antwort("p1"))).toBe(false); expect(beantwortet(WARNDRUCK, antwort("p2", { zahlwert: null }))).toBe(false); expect(beantwortet(BEFUND, antwort("p4", { text: " " }))).toBe(false); }); it("wertet die Null als Antwort — sie ist ein Messwert wie jeder andere", () => { expect(beantwortet(WARNDRUCK, antwort("p2", { zahlwert: 0 }))).toBe(true); }); }); describe("punktInOrdnung", () => { it("folgt bei Ja/Nein der Antwort", () => { expect(punktInOrdnung(SICHTPRUEFUNG, antwort("p1", { jaNein: true }))).toBe(true); expect(punktInOrdnung(SICHTPRUEFUNG, antwort("p1", { jaNein: false }))).toBe(false); }); it("rechnet bei Messwerten den Bereich aus", () => { expect(punktInOrdnung(WARNDRUCK, antwort("p2", { zahlwert: 55 }))).toBe(true); expect(punktInOrdnung(WARNDRUCK, antwort("p2", { zahlwert: 45 }))).toBe(false); }); it("lässt die ausdrückliche Angabe des Prüfers gewinnen", () => { // Wert in der Toleranz, der Prüfer beanstandet trotzdem. expect(punktInOrdnung(WARNDRUCK, antwort("p2", { zahlwert: 55, inOrdnung: false }))).toBe( false, ); // Und umgekehrt. expect(punktInOrdnung(SICHTPRUEFUNG, antwort("p1", { jaNein: false, inOrdnung: true }))).toBe( true, ); }); }); describe("offenePflichtpunkte", () => { it("nennt nur unbeantwortete Pflichtpunkte", () => { const offen = offenePflichtpunkte( [SICHTPRUEFUNG, WARNDRUCK, BEUTEL], [antwort("p1", { jaNein: true })], ); expect(offen.map((punkt) => punkt.id)).toEqual(["p2"]); }); it("ist leer, wenn alle Pflichtpunkte beantwortet sind", () => { const offen = offenePflichtpunkte( [SICHTPRUEFUNG, WARNDRUCK, BEUTEL], [antwort("p1", { jaNein: true }), antwort("p2", { zahlwert: 55 })], ); expect(offen).toHaveLength(0); }); }); describe("ergebnisVorschlag", () => { const punkte = [SICHTPRUEFUNG, WARNDRUCK, BEUTEL, BEFUND]; it("bestanden, wenn nichts beanstandet ist", () => { const antworten = [ antwort("p1", { jaNein: true }), antwort("p2", { zahlwert: 55 }), antwort("p3", { jaNein: true }), ]; expect(ergebnisVorschlag(punkte, antworten)).toBe("BESTANDEN"); }); it("mit Mängeln, wenn nur ein Kann-Punkt durchfällt", () => { const antworten = [ antwort("p1", { jaNein: true }), antwort("p2", { zahlwert: 55 }), antwort("p3", { jaNein: false }), ]; expect(ergebnisVorschlag(punkte, antworten)).toBe("BESTANDEN_MIT_MAENGELN"); expect(beanstandetePunkte(punkte, antworten).map((punkt) => punkt.id)).toEqual(["p3"]); }); it("nicht bestanden, sobald ein Pflichtpunkt durchfällt", () => { const antworten = [ antwort("p1", { jaNein: true }), // Messwert außerhalb der Toleranz — Pflichtpunkt. antwort("p2", { zahlwert: 42 }), antwort("p3", { jaNein: false }), ]; expect(ergebnisVorschlag(punkte, antworten)).toBe("NICHT_BESTANDEN"); }); it("wertet unbeantwortete Punkte nicht als Beanstandung", () => { // Sonst wäre jede halb ausgefüllte Prüfung sofort "nicht bestanden". expect(ergebnisVorschlag(punkte, [antwort("p1", { jaNein: true })])).toBe("BESTANDEN"); }); }); describe("geraetStatusNachPruefung", () => { it("sperrt nur bei nicht bestanden", () => { expect(geraetStatusNachPruefung("NICHT_BESTANDEN")).toBe("GESPERRT"); expect(geraetStatusNachPruefung("BESTANDEN_MIT_MAENGELN")).toBe("EINSATZBEREIT"); expect(geraetStatusNachPruefung("BESTANDEN")).toBe("EINSATZBEREIT"); }); }); describe("dauerInMinuten", () => { it("rundet auf volle Minuten", () => { const beginn = new Date("2026-06-15T09:00:00.000Z"); const ende = new Date("2026-06-15T09:24:40.000Z"); expect(dauerInMinuten(beginn, ende)).toBe(25); }); it("gibt ohne Beginn nichts zurück", () => { expect(dauerInMinuten(null, new Date())).toBeNull(); }); it("liefert keine negative Dauer, wenn die Uhr gesprungen ist", () => { const beginn = new Date("2026-06-15T10:00:00.000Z"); const ende = new Date("2026-06-15T09:00:00.000Z"); expect(dauerInMinuten(beginn, ende)).toBeNull(); }); });