Files
dogfather-universe/server-internal/test-altabbruch.mjs
T
DogFatherGitandClaude Opus 5 01b61905aa Altfaelle aufraeumen: abgebrochene Projekte verschwinden aus der Liste
P-2608-0001 wurde abgebrochen, bevor es das Archiv-Kennzeichen gab. Es
hat deshalb zwar den Status "abgebrochen", aber archiviert = 0 -- und
stand bis heute zwischen den offenen Auftraegen. Also genau das, was der
Wunsch "wenn ich abbreche dan sollen die auch da weg" abstellen sollte.

Ueber die Oberflaeche war das nicht zu reparieren: Die Verwaltung zeigt
bei Status "abgebrochen" den Info-Block statt des Abbrechen-Knopfes, ein
zweiter Klick war also gar nicht moeglich.

Migration statt Handarbeit in der Datenbank: Ein einzelner UPDATE per
Hand repariert einen Fall und hinterlaesst keine Spur. Die Migration
erwischt jeden Altfall, laeuft ueberall gleich (Server, Testdatenbank,
ein spaeterer Neuaufbau) und steht nachvollziehbar in der Geschichte.

Bewusst NICHT gefuellt: abbruch_am, abbruch_grund, abbruch_wer,
abbruch_erstattung_cent. Diese Felder gab es beim damaligen Abbruch noch
nicht. Sie nachtraeglich mit plausiblen Werten zu fuellen waere eine
Behauptung ueber einen Vorgang, bei dem niemand mehr weiss, wie er
wirklich ablief -- und ausgerechnet im Streitfall waere das die
gefaehrlichste Stelle fuer eine Erfindung. Ein leeres Feld sagt ehrlich
"unbekannt", und die Verwaltung zeigt diese Angaben ohnehin nur an, wenn
sie gefuellt sind.

Der Test stellt den Altzustand echt nach: alle Migrationen bis 0017,
dann der Altfall, dann erst 0018. Geprueft wird auch, was NICHT passieren
darf -- ein laufendes Projekt bleibt unberuehrt, ein sauber abgebrochenes
behaelt Datum, Grund und Erstattung, und ein zweiter Durchlauf aendert
nichts mehr (beim Wiederherstellen einer Sicherung passiert genau das).

Geprueft: 167 Pruefungen gruen (Altabbruch 16, Automatik 55, Angebote 56,
Kette 40).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-24 23:07:37 +02:00

121 lines
6.0 KiB
JavaScript

/* =====================================================================
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', '[email protected]', '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);