import { describe, expect, it } from "vitest"; import { belegAuswerten, datum, jsonHerausloesen, zahl } from "@/lib/ki/beleg-lesen"; /** * Das Auswerten einer Modellantwort auf ein Belegfoto. * * Der Maßstab ist überall derselbe: **im Zweifel leer.** Ein nicht erkannter * Betrag ist ein leeres Feld, das jemand ausfüllt. Ein geratener Betrag ist * eine falsche Buchung, die niemand bemerkt. * * Deshalb prüfen die meisten Tests hier nicht, ob etwas erkannt wird, sondern * ob Unsinn zuverlässig **nicht** erkannt wird. */ describe("jsonHerausloesen", () => { it("nimmt blankes JSON", () => { expect(jsonHerausloesen('{"betrag": 12.5}')).toBe('{"betrag": 12.5}'); }); it("schält JSON aus einem Codeblock", () => { const antwort = 'Hier die Angaben:\n```json\n{"betrag": 12.5}\n```\nBitte prüfen.'; expect(jsonHerausloesen(antwort)).toBe('{"betrag": 12.5}'); }); it("schält JSON aus einem Codeblock ohne Sprachangabe", () => { expect(jsonHerausloesen('```\n{"a": 1}\n```')).toBe('{"a": 1}'); }); it("findet JSON hinter einem Vorspann", () => { expect(jsonHerausloesen('Gerne! {"betrag": 3}')).toBe('{"betrag": 3}'); }); it("gibt null zurück, wenn nichts da ist", () => { expect(jsonHerausloesen("Ich kann das Bild nicht lesen.")).toBeNull(); expect(jsonHerausloesen("")).toBeNull(); }); }); describe("zahl", () => { it("nimmt eine echte Zahl", () => { expect(zahl(12.5)).toBe(12.5); expect(zahl(0)).toBe(0); }); it("liest englische Schreibweise", () => { expect(zahl("12.50")).toBe(12.5); }); it("liest deutsche Schreibweise", () => { expect(zahl("12,50")).toBe(12.5); }); /** * Der Fall, der ohne Sorgfalt danebengeht: „1.234,56" ist * tausendzweihundert und nicht eins Komma zwei. Entschieden wird über das * letzte Trennzeichen. */ it("hält Tausenderpunkt und Dezimalkomma auseinander", () => { expect(zahl("1.234,56")).toBe(1234.56); expect(zahl("1,234.56")).toBe(1234.56); }); it("wirft Währungszeichen weg", () => { expect(zahl("12,50 €")).toBe(12.5); expect(zahl("EUR 12.50")).toBe(12.5); expect(zahl("€12,50")).toBe(12.5); }); it("gibt bei Unsinn null zurück", () => { expect(zahl("keine Ahnung")).toBeNull(); expect(zahl(null)).toBeNull(); expect(zahl(undefined)).toBeNull(); expect(zahl({})).toBeNull(); expect(zahl(Number.NaN)).toBeNull(); expect(zahl("")).toBeNull(); }); }); describe("datum", () => { it("liest ISO", () => { expect(datum("2026-05-24")).toEqual(new Date(Date.UTC(2026, 4, 24))); }); it("liest die deutsche Schreibweise", () => { expect(datum("24.05.2026")).toEqual(new Date(Date.UTC(2026, 4, 24))); expect(datum("4.5.2026")).toEqual(new Date(Date.UTC(2026, 4, 4))); }); it("ergänzt ein zweistelliges Jahr", () => { expect(datum("24.05.26")).toEqual(new Date(Date.UTC(2026, 4, 24))); }); /** Der 31. Februar wird von Date stillschweigend zum 3. März. */ it("weist einen Tag ab, den es nicht gibt", () => { expect(datum("2026-02-31")).toBeNull(); expect(datum("31.02.2026")).toBeNull(); }); it("weist ein unmögliches Jahr ab", () => { // Ein Beleg von 1899 oder aus dem Jahr 2500 ist ein Lesefehler. expect(datum("1899-01-01")).toBeNull(); expect(datum("2500-01-01")).toBeNull(); }); it("gibt bei Unsinn null zurück", () => { expect(datum("gestern")).toBeNull(); expect(datum(null)).toBeNull(); expect(datum(42)).toBeNull(); }); }); describe("belegAuswerten", () => { it("liest eine vollständige Antwort", () => { const ergebnis = belegAuswerten( JSON.stringify({ betrag: 45.9, waehrung: "eur", belegdatum: "2026-05-24", haendler: "Tankstelle Musterhausen", steuersatz: 19, kategorie: "Kraftstoff", }), ); expect(ergebnis.betrag).toBe(45.9); expect(ergebnis.waehrung).toBe("EUR"); expect(ergebnis.haendler).toBe("Tankstelle Musterhausen"); expect(ergebnis.steuersatz).toBe(19); expect(ergebnis.fehlend).toEqual([]); expect(ergebnis.erkannt).toHaveLength(6); }); it("meldet, was gefehlt hat", () => { const ergebnis = belegAuswerten(JSON.stringify({ betrag: 12.5, haendler: null })); expect(ergebnis.betrag).toBe(12.5); expect(ergebnis.erkannt).toEqual(["betrag"]); expect(ergebnis.fehlend).toContain("haendler"); expect(ergebnis.fehlend).toContain("belegdatum"); }); it("liefert bei unlesbarer Antwort ein leeres Formular statt eines Fehlers", () => { const ergebnis = belegAuswerten("Ich kann auf dem Bild nichts erkennen."); expect(ergebnis.betrag).toBeNull(); expect(ergebnis.erkannt).toEqual([]); expect(ergebnis.fehlend).toHaveLength(6); }); it("verträgt kaputtes JSON", () => { expect(belegAuswerten('{"betrag": ').erkannt).toEqual([]); expect(belegAuswerten("{}").erkannt).toEqual([]); expect(belegAuswerten("[1,2,3]").erkannt).toEqual([]); }); /** Eine Quittung über minus zwölf Euro gibt es nicht — das ist ein Lesefehler. */ it("verwirft einen negativen Betrag", () => { expect(belegAuswerten(JSON.stringify({ betrag: -12.5 })).betrag).toBeNull(); }); it("verwirft einen unmöglichen Steuersatz", () => { expect(belegAuswerten(JSON.stringify({ steuersatz: 190 })).steuersatz).toBeNull(); expect(belegAuswerten(JSON.stringify({ steuersatz: -7 })).steuersatz).toBeNull(); }); it('nimmt die Zeichenkette "null" nicht als Text', () => { expect(belegAuswerten(JSON.stringify({ haendler: "null" })).haendler).toBeNull(); }); it("kürzt einen überlangen Händlernamen", () => { const ergebnis = belegAuswerten(JSON.stringify({ haendler: "A".repeat(500) })); expect(ergebnis.haendler).toHaveLength(200); }); });