Files
dogfather-universe/pruef-sicherung-waechter.mjs
T
DogFatherGitandClaude Opus 5 46563b02fc Waechter merkt jetzt, wenn die naechtliche Sicherung ausfaellt
Beim Nachweis des ersten automatischen Laufs gefunden: Der Cron-Eintrag
endet auf >/dev/null 2>&1 -- jede Fehlermeldung wird verworfen. Das
Sicherungsskript fuehrt zwar ein eigenes Protokoll, aber alles, was VOR
der ersten Protokollzeile schiefgeht (Skript geloescht, sqlite3 weg,
Platte voll, Cron gestoppt), passiert spurlos. Niemand haette es
gemerkt -- ausser in dem Moment, in dem man die Sicherung braucht.

Der Waechter prueft ab sofort die Datei selbst, nicht das Protokoll:
Ein Protokoll kann "erfolgreich" melden, waehrend die Datei fehlt.

In lib/ ausgelagert, weil waechter.mjs beim Import sofort seinen ganzen
Durchlauf startet -- testbar war die Funktion dort nicht.

Beim Testentwurf einen eigenen Fehler gefunden: readdirSync wirft
sowohl bei fehlendem Ordner (ENOENT) als auch bei fehlenden Rechten
(EACCES). Die erste Fassung behandelte beides als "kein Urteil" und
haette damit einen geloeschten Sicherungsordner verschwiegen. Jetzt
getrennt: ENOENT meldet, EACCES schweigt.

Die 26-Stunden-Grenze ist bewusst nicht enger: Kurz vor dem naechsten
Lauf ist die juengste Sicherung regulaer 24 Stunden alt. Genau dort
entstehen die Fehlalarme, nach denen man die Meldungen abschaltet --
und dann geht der eine echte mit unter. Als eigener Testfall abgesichert.

14 von 14 Pruefungen bestanden.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-27 03:02:55 +02:00

146 lines
5.9 KiB
JavaScript

/* =====================================================================
Merkt der Waechter, wenn die Sicherung ausfaellt?
Die Prüfung selbst muss geprüft werden -- sonst hat man am Ende zwei
Dinge, auf die man sich verlässt und von denen keines funktioniert.
Getestet wird mit echten Ordnern in einem temporären Verzeichnis,
nicht mit nachgebauten Attrappen: Die Funktion soll genau das tun,
was sie auf dem Server tut.
===================================================================== */
import { mkdtempSync, mkdirSync, writeFileSync, utimesSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { sicherungZustand, MAX_STUNDEN, MIN_BYTE } from "./server-internal/lib/sicherung-pruefen.js";
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 wurzel = mkdtempSync(join(tmpdir(), "sicherungstest-"));
/** Legt eine Sicherung an: Alter in Stunden, Groesse in Byte. */
function baue(name, altStunden, groesse) {
const basis = join(wurzel, name);
const tag = join(basis, "taeglich", "2026-08-27");
mkdirSync(tag, { recursive: true });
const datei = join(tag, "dogfather-internal.db");
writeFileSync(datei, Buffer.alloc(groesse));
const zeit = new Date(Date.now() - altStunden * 3600000);
utimesSync(datei, zeit, zeit);
return basis;
}
console.log("\n=== Der gute Fall ===");
{
const r = sicherungZustand(baue("frisch", 2, 800 * 1024));
pruefe("frische Sicherung gilt als heil", r && r.heil === true, r?.grund);
}
console.log("\n=== Die Ausfaelle, die gemeldet werden muessen ===");
{
const r = sicherungZustand(join(wurzel, "gibtesnicht"));
pruefe("gar kein Sicherungsordner", r && r.heil === false, r?.grund);
}
{
/* Ordner da, aber keine Laeufe darin. */
const basis = join(wurzel, "leer");
mkdirSync(join(basis, "taeglich"), { recursive: true });
const r = sicherungZustand(basis);
pruefe("Ordner vorhanden, aber kein Lauf", r && r.heil === false, r?.grund);
}
{
/* Der Unterordner "taeglich" fehlt ganz -- beim ersten Entwurf lieferte
das faelschlich "kein Urteil" statt eines Befunds. */
const basis = join(wurzel, "ohnetaeglich");
mkdirSync(basis, { recursive: true });
const r = sicherungZustand(basis);
pruefe("Unterordner taeglich fehlt", r && r.heil === false, r?.grund);
}
{
/* Lauf angelegt, aber die Datenbank fehlt darin. */
const basis = join(wurzel, "ohnedb");
mkdirSync(join(basis, "taeglich", "2026-08-27"), { recursive: true });
const r = sicherungZustand(basis);
pruefe("Lauf ohne Datenbankdatei", r && r.heil === false, r?.grund);
}
{
const r = sicherungZustand(baue("alt", MAX_STUNDEN + 3, 800 * 1024));
pruefe("Sicherung zu alt", r && r.heil === false, r?.grund);
}
{
const r = sicherungZustand(baue("winzig", 2, 1024));
pruefe("Sicherung verdaechtig klein", r && r.heil === false, r?.grund);
}
{
const r = sicherungZustand(baue("leerdatei", 2, 0));
pruefe("Sicherung mit 0 Byte", r && r.heil === false, r?.grund);
}
console.log("\n=== Die Grenze darf nicht zu eng sitzen ===");
{
/* Genau 24 Stunden: Das ist der Normalfall kurz vor dem naechsten Lauf
und darf auf keinen Fall Alarm ausloesen. Genau hier entstehen die
Fehlalarme, die dazu fuehren, dass man die Meldungen abschaltet. */
const r = sicherungZustand(baue("grenzfall", 24, 800 * 1024));
pruefe("24 Stunden alt gilt noch als heil", r && r.heil === true, r?.grund);
}
{
/* Und knapp darueber muss es kippen. */
const r = sicherungZustand(baue("knappdrueber", MAX_STUNDEN + 0.5, 800 * 1024));
pruefe("knapp ueber der Grenze kippt es", r && r.heil === false, r?.grund);
}
{
/* Knapp ueber der Mindestgroesse: darf nicht anschlagen. */
const r = sicherungZustand(baue("knappgross", 2, MIN_BYTE + 1024));
pruefe("knapp ueber der Mindestgroesse ist heil", r && r.heil === true, r?.grund);
}
console.log("\n=== Der juengste Lauf zaehlt, nicht irgendeiner ===");
{
/* Ein alter Lauf daneben darf das Urteil nicht verfaelschen. */
const basis = join(wurzel, "mehrere");
for (const [tag, alt] of [["2026-08-20", 170], ["2026-08-27", 2]]) {
const o = join(basis, "taeglich", tag);
mkdirSync(o, { recursive: true });
const d = join(o, "dogfather-internal.db");
writeFileSync(d, Buffer.alloc(800 * 1024));
const z = new Date(Date.now() - alt * 3600000);
utimesSync(d, z, z);
}
const r = sicherungZustand(basis);
pruefe("alter Lauf daneben stoert nicht", r && r.heil === true, r?.grund);
}
{
/* Umgekehrt: Ist der juengste Lauf alt, hilft ein Ordner mit anderem
Namen daneben nicht -- geprueft wird der juengste, nicht der beste. */
const basis = join(wurzel, "nurAlt");
const o = join(basis, "taeglich", "2026-08-01");
mkdirSync(o, { recursive: true });
const d = join(o, "dogfather-internal.db");
writeFileSync(d, Buffer.alloc(800 * 1024));
const z = new Date(Date.now() - 600 * 3600000);
utimesSync(d, z, z);
const r = sicherungZustand(basis);
pruefe("nur ein alter Lauf wird gemeldet", r && r.heil === false, r?.grund);
}
console.log("\n=== Fremde Ordner werden ignoriert ===");
{
/* Im Zielverzeichnis koennen andere Dinge liegen (das Protokoll, ein
Ordner "woechentlich"). Nur YYYY-MM-DD darf mitzaehlen. */
const basis = join(wurzel, "fremdes");
mkdirSync(join(basis, "taeglich", "zwischenablage"), { recursive: true });
mkdirSync(join(basis, "taeglich", "2026-08-27"), { recursive: true });
const d = join(basis, "taeglich", "2026-08-27", "dogfather-internal.db");
writeFileSync(d, Buffer.alloc(800 * 1024));
const r = sicherungZustand(basis);
pruefe("Ordner ohne Datumsnamen zaehlen nicht mit", r && r.heil === true, r?.grund);
}
rmSync(wurzel, { recursive: true, force: true });
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
process.exitCode = fehl ? 1 : 0;