import { describe, expect, it } from "vitest"; import { darfKorrigiertWerden, loeschVorschau } from "@/lib/history"; import type { AuditChange, HistoryEventType } from "@/lib/supabase/types"; const HEUTE = "2026-08-13"; function eintrag(teil: Partial<{ id: string; event_type: HistoryEventType; event_date: string; created_at: string; changes: AuditChange[] | null; pending_id: string | null }> = {}) { return { id: "h1", event_type: "Stammdatenänderung" as HistoryEventType, event_date: "2026-08-10", created_at: "2026-08-10T09:00:00.000Z", changes: [{ feld: "Adresse", vorher: "Feldweg 66", nachher: "Bahnhofstrasse 5" }] as AuditChange[] | null, ...teil, }; } describe("darfKorrigiertWerden", () => { it("lässt eine wirksame Stammdatenänderung mit Werten zu", () => { expect(darfKorrigiertWerden(eintrag(), HEUTE)).toEqual({ erlaubt: true }); }); it("lässt eine Vertragsänderung ebenso zu", () => { expect(darfKorrigiertWerden(eintrag({ event_type: "Vertragsänderung" }), HEUTE).erlaubt).toBe(true); }); it("schützt den Eintritt", () => { const u = darfKorrigiertWerden(eintrag({ event_type: "Eintritt", changes: null }), HEUTE); expect(u.erlaubt).toBe(false); expect(u.erlaubt === false && u.grund).toMatch(/Anfang der Zeitleiste/); }); it.each(["Versetzung", "Beförderung", "Karenz", "Rückkehr", "Austritt", "Wiedereintritt", "Reorganisation"])( "verweist bei %s auf den fachlichen Vorgang", (typ) => { const u = darfKorrigiertWerden(eintrag({ event_type: typ }), HEUTE); expect(u.erlaubt).toBe(false); expect(u.erlaubt === false && u.grund).toMatch(/passenden Vorgang|Planstellen oder den Status/); } ); it("lässt Zukünftiges zu, sobald der geplante Vorgang bekannt ist", () => { // Das ist der Sinn von pending_id: ohne Bezug liesse sich die geplante // Änderung nur über Person und Datum finden, und das ist nicht eindeutig. expect(darfKorrigiertWerden(eintrag({ event_date: "2026-09-01", pending_id: "p1" }), HEUTE).erlaubt).toBe(true); }); it("weist Zukünftiges ohne Bezug zum Vorgang ab", () => { const u = darfKorrigiertWerden(eintrag({ event_date: "2026-09-01", pending_id: null }), HEUTE); expect(u.erlaubt).toBe(false); expect(u.erlaubt === false && u.grund).toMatch(/kein Vorgang hinterlegt/); }); it("zählt den heutigen Tag als wirksam", () => { expect(darfKorrigiertWerden(eintrag({ event_date: HEUTE }), HEUTE).erlaubt).toBe(true); }); it("weist Einträge ohne Feldwerte ab", () => { for (const ohne of [null, []]) { const u = darfKorrigiertWerden(eintrag({ changes: ohne }), HEUTE); expect(u.erlaubt).toBe(false); expect(u.erlaubt === false && u.grund).toMatch(/keine Feldwerte/); } }); }); describe("loeschVorschau", () => { const alt = eintrag({ id: "alt", event_date: "2026-08-01", created_at: "2026-08-01T09:00:00.000Z", changes: [ { feld: "Adresse", vorher: "Feldweg 66", nachher: "Zwischenweg 1" }, { feld: "Telefon", vorher: "0664 000", nachher: "0664 111" }, ], }); const neu = eintrag({ id: "neu", event_date: "2026-08-10", created_at: "2026-08-10T09:00:00.000Z", changes: [{ feld: "Adresse", vorher: "Zwischenweg 1", nachher: "Endstrasse 9" }], }); it("lässt ein Feld stehen, das später erneut geändert wurde", () => { // Das ist die Regel, um die es geht: die letztgültige Änderung schlägt. const v = loeschVorschau(alt, [alt, neu]); expect(v.find((x) => x.feld === "Adresse")!.bleibt).toBe(true); }); it("setzt ein Feld zurück, das seither niemand angefasst hat", () => { const v = loeschVorschau(alt, [alt, neu]); const tel = v.find((x) => x.feld === "Telefon")!; expect(tel.bleibt).toBe(false); expect(tel.auf).toBe("0664 000"); }); it("setzt beim neuesten Eintrag alles zurück", () => { const v = loeschVorschau(neu, [alt, neu]); expect(v.every((x) => !x.bleibt)).toBe(true); expect(v[0].auf).toBe("Zwischenweg 1"); }); it("unterscheidet zwei Einträge am selben Tag über die Erfassungszeit", () => { const frueh = eintrag({ id: "a", event_date: HEUTE, created_at: `${HEUTE}T08:00:00.000Z` }); const spaet = eintrag({ id: "b", event_date: HEUTE, created_at: `${HEUTE}T16:00:00.000Z` }); expect(loeschVorschau(frueh, [frueh, spaet])[0].bleibt).toBe(true); expect(loeschVorschau(spaet, [frueh, spaet])[0].bleibt).toBe(false); }); it("nennt die Richtung so, wie sie im Dialog steht", () => { // von = was jetzt drinsteht, auf = worauf zurückgesetzt wird. const [v] = loeschVorschau(neu, [neu]); expect(v).toMatchObject({ feld: "Adresse", von: "Endstrasse 9", auf: "Zwischenweg 1" }); }); });