/* ===================================================================== Prueft Migration 0018: Ein Projekt, das abgebrochen wurde, BEVOR es das Archiv-Kennzeichen gab, muss danach aus der laufenden Liste verschwinden -- ohne dass dabei etwas erfunden oder zerstoert wird. Der Test stellt den Altzustand echt nach: Er laesst alle Migrationen bis 0017 laufen, baut den Altfall, und laesst DANN 0018 laufen. Ein Test, der 0018 einfach zweimal auf eine leere Datenbank wirft, wuerde nichts beweisen. ===================================================================== */ import Database from "better-sqlite3"; import { readFileSync, readdirSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; const HIER = dirname(fileURLToPath(import.meta.url)); const MIG = join(HIER, "..", "cloudflare-worker", "migrations"); let ok = 0, fehl = 0; const pruefe = (name, gut, info) => { if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); } else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); } }; const db = new Database(":memory:"); db.pragma("foreign_keys = ON"); /* ---- Alle Migrationen BIS 0017 (der Stand vor der Reparatur) ---- */ const alle = readdirSync(MIG).filter((f) => f.endsWith(".sql")).sort(); const bis17 = alle.filter((f) => f < "0018"); for (const f of bis17) db.exec(readFileSync(join(MIG, f), "utf8")); console.log("1. DER ALTZUSTAND"); /* Ein Kunde, weil wd_projekte einen Fremdschluessel darauf hat. */ db.prepare( `INSERT INTO wd_kunden (id, name, email, erstellt_am) VALUES ('k-alt', 'Testkunde', 'alt@example.com', '2026-08-01T10:00:00.000Z')` ).run(); /* Der Altfall: Status abgebrochen, aber archiviert = 0 und alle Abbruch-Felder leer -- genau wie P-2608-0001. */ db.prepare( `INSERT INTO wd_projekte (id, nummer, kunde_id, titel, paket, status, archiviert, wartet_auf, erstellt_am, aktualisiert_am) VALUES ('p-alt', 'P-2608-0001', 'k-alt', 'Altfall', 'start', 'abgebrochen', 0, 'kunde', '2026-08-01T10:00:00.000Z', '2026-08-01T10:00:00.000Z')` ).run(); /* Ein laufendes Projekt als Gegenprobe -- es darf NICHT angefasst werden. */ db.prepare( `INSERT INTO wd_projekte (id, nummer, kunde_id, titel, paket, status, archiviert, wartet_auf, erstellt_am, aktualisiert_am) VALUES ('p-lauf', 'P-2608-0002', 'k-alt', 'Laeuft', 'start', 'design', 0, 'kunde', '2026-08-02T10:00:00.000Z', '2026-08-02T10:00:00.000Z')` ).run(); /* Und ein bereits sauber abgebrochenes -- seine echten Abbruchdaten duerfen durch die Migration nicht ueberschrieben werden. */ db.prepare( `INSERT INTO wd_projekte (id, nummer, kunde_id, titel, paket, status, archiviert, wartet_auf, abbruch_am, abbruch_grund, abbruch_wer, abbruch_erstattung_cent, erstellt_am, aktualisiert_am) VALUES ('p-neu', 'P-2608-0003', 'k-alt', 'Sauber abgebrochen', 'start', 'abgebrochen', 1, 'niemand', '2026-08-20T09:00:00.000Z', 'kunde_zahlt_nicht', 'dogfather', 14900, '2026-08-03T10:00:00.000Z', '2026-08-20T09:00:00.000Z')` ).run(); const vorher = db.prepare(`SELECT * FROM wd_projekte WHERE id = 'p-alt'`).get(); pruefe("der Altfall steht in der laufenden Liste", vorher.archiviert === 0, "archiviert = " + vorher.archiviert); pruefe("und hat keine Abbruchdaten", !vorher.abbruch_am, String(vorher.abbruch_am)); console.log("\n2. NACH DER MIGRATION"); db.exec(readFileSync(join(MIG, "0018_webdesign_altabbrueche_archivieren.sql"), "utf8")); const alt = db.prepare(`SELECT * FROM wd_projekte WHERE id = 'p-alt'`).get(); pruefe("der Altfall ist archiviert", alt.archiviert === 1, "archiviert = " + alt.archiviert); pruefe("die Uhr steht formal still", alt.wartet_auf === "niemand", alt.wartet_auf); pruefe("der Status bleibt abgebrochen", alt.status === "abgebrochen", alt.status); /* Der Kern der Ehrlichkeitsregel aus dem Migrationskopf. */ pruefe("es wurde KEIN Abbruchdatum erfunden", alt.abbruch_am === null, String(alt.abbruch_am)); pruefe("es wurde KEIN Grund erfunden", alt.abbruch_grund === null, String(alt.abbruch_grund)); pruefe("es wurde KEINE Erstattung erfunden", alt.abbruch_erstattung_cent === null, String(alt.abbruch_erstattung_cent)); console.log("\n3. WAS NICHT ANGEFASST WERDEN DARF"); const lauf = db.prepare(`SELECT * FROM wd_projekte WHERE id = 'p-lauf'`).get(); pruefe("das laufende Projekt bleibt in der Liste", lauf.archiviert === 0, "archiviert = " + lauf.archiviert); pruefe("und sein Wartezustand bleibt unveraendert", lauf.wartet_auf === "kunde", lauf.wartet_auf); pruefe("und sein Aenderungsdatum wurde nicht angefasst", lauf.aktualisiert_am === "2026-08-02T10:00:00.000Z", lauf.aktualisiert_am); const neu = db.prepare(`SELECT * FROM wd_projekte WHERE id = 'p-neu'`).get(); pruefe("der echte Abbruch behaelt sein Datum", neu.abbruch_am === "2026-08-20T09:00:00.000Z", neu.abbruch_am); pruefe("und seinen Grund", neu.abbruch_grund === "kunde_zahlt_nicht", neu.abbruch_grund); pruefe("und seine Erstattung", neu.abbruch_erstattung_cent === 14900, String(neu.abbruch_erstattung_cent)); pruefe("und sein Aenderungsdatum wurde nicht angefasst", neu.aktualisiert_am === "2026-08-20T09:00:00.000Z", neu.aktualisiert_am); console.log("\n4. NOCHMAL LAUFEN LASSEN AENDERT NICHTS"); /* Eine Migration darf nie davon abhaengen, dass sie genau einmal laeuft -- beim Wiederherstellen einer Sicherung passiert genau das. */ const standVorher = db.prepare( `SELECT id, archiviert, wartet_auf, aktualisiert_am FROM wd_projekte ORDER BY id` ).all(); db.exec(readFileSync(join(MIG, "0018_webdesign_altabbrueche_archivieren.sql"), "utf8")); const standNachher = db.prepare( `SELECT id, archiviert, wartet_auf, aktualisiert_am FROM wd_projekte ORDER BY id` ).all(); pruefe("zweiter Durchlauf aendert nichts mehr", JSON.stringify(standVorher) === JSON.stringify(standNachher)); console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`); db.close(); process.exit(fehl ? 1 : 0);