/* ===================================================================== pruef-willkommen.mjs — DIE ALTE ADRESSE DARF NIEMANDEN VERLIEREN ===================================================================== WAS HIER FRUEHER STAND, UND WARUM ES WEG IST (06.10.2026) Diese Datei pruefte bis heute eine Seite, die es nicht mehr gibt: `willkommen.html` erklaerte JEDE Kachel einer Rolle, alle gleich ausfuehrlich. Genau das beschreibt der Bauplan „Willkommen & Anleitung" in Kapitel 01 als das Problem — wer neu ist, bekam sechsunddreissig gleichwertige Absaetze und wusste danach nicht, womit er anfangen soll. An ihre Stelle ist `anleitung.html` getreten: dieselben Kacheln, aber nach Muss, Regelmaessig und Bei Bedarf sortiert, je Rolle. DIE ALTE PRUEFUNG WAR DAMIT EINE, DIE EINE ZURUECKGENOMMENE REGEL VERTEIDIGT — und sie hat es auch gemeldet: Beim ersten Lauf nach dem Umbau wurde sie rot („der Name steht oben: leer", „0 Kacheln werden erklaert") und lief danach in ihre Notbremse. Das war kein Befund am Haus, sondern einer an ihr selbst. Eine Pruefung muss mit der Aenderung mitgehen, sonst erzeugt sie Laerm, der die echten Befunde zudeckt. MITGEGANGEN IST SIE SO: Die inhaltlichen Fragen („bekommt jede Rolle ihre eigenen Texte?", „wird nur erklaert, was man oeffnen darf?") beantwortet jetzt `pruef-anleitung.mjs` — an derselben Rechtetafel und mit denselben Gegenproben. Hier bleibt die eine Frage, die dort NICHT hingehoert und die sonst niemand stellt: Verliert die alte Adresse jemanden? Es gibt Lesezeichen, Verweise in alten Nachrichten und ausgedruckte Zettel. Der Bauplan verlangt in Kapitel 03 ausdruecklich: „Adresse: bleibt willkommen.html. Alte Verweise funktionieren weiter." UND DIE ZWEITE FRAGE: Ist das Alte wirklich WEG — oder liegt nur die Seite still und der Unterbau laeuft weiter? Ein Server, der eine Schnittstelle anbietet, die niemand mehr aufruft, ist kein harmloser Rest: Er ist die zweite Wahrheit, die beim naechsten Rechteumbau vergessen wird. Deshalb wird hier nachgesehen, dass es sie nicht mehr gibt — mit Gegenprobe, damit die Zeile nicht nur deshalb gruen ist, weil sie gar nichts findet. AUFRUF: node server/pruef-willkommen.mjs ===================================================================== */ import { mkdtempSync, rmSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import http from "node:http"; const ordner = mkdtempSync(join(tmpdir(), "ws-willk-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); import { notbremse } from "./helfer-notbremse.mjs"; /* Kein HTTPS-Vorbau mehr: Hier wird keine Seite im Browser geladen, sondern nur geantwortet. */ const { eigenerPort } = await import("./helfer-port.mjs"); const PORT = await eigenerPort(import.meta, "die Willkommenspruefung"); process.env.PORT = `${PORT}`; process.env.SITE_ACCESS_SECRET = "lokaler-test"; process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); notbremse(300_000, "pruef-willkommen"); await new Promise((r) => setTimeout(r, 1200)); let fehler = 0, geprueft = 0; const melde = (t) => console.log(t); /* Die ANZAHL gehoert ans Ende, nicht nur das Ergebnis: Ein Rueckgang ist genauso ein Befund wie ein Fehlschlag -- er heisst, dass still etwas nicht mehr geprueft wird. */ const ok = (b, t) => { geprueft++; melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; const HOST = "crew.dogfather-universe.com"; function rufe(pfad, wie = {}) { return new Promise((fertig) => { const daten = wie.body === undefined ? null : JSON.stringify(wie.body); const a = http.request({ host: "127.0.0.1", port: PORT, path: pfad, method: wie.method || "GET", headers: { Host: HOST, ...(daten ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) } : {}), ...(wie.keks ? { Cookie: wie.keks } : {}), Origin: `https://${HOST}`, Referer: `https://${HOST}/workspace/`, }, }, (a2) => { let t = ""; a2.on("data", (c) => (t += c)); a2.on("end", () => fertig({ code: a2.statusCode, kopf: a2.headers, text: t, json: (() => { try { return JSON.parse(t); } catch { return null; } })(), })); }); a.on("error", (e) => fertig({ code: 0, text: e.message, json: null })); if (daten) a.write(daten); a.end(); }); } const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes } = await import("node:crypto"); await rufe("/workspace/api/anmelden", { method: "POST", body: { rolle: "admin", code: "x" } }); for (let i = 0; i < 60; i++) { try { const t = new DatabaseSync(process.env.WORKSPACE_DB); try { t.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { t.close(); } } catch { await new Promise((r) => setTimeout(r, 250)); } } const d = new DatabaseSync(process.env.WORKSPACE_DB); const jetzt = new Date().toISOString(); function anlegen(name, rolle, code) { const salt = randomBytes(16).toString("hex"); const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); d.prepare("INSERT INTO personen (name,rolle,code_hash,code_salt,code_n,aktiv,erstellt) VALUES (?,?,?,?,?,1,?)") .run(name, rolle, hash, salt, 32768, jetzt); return d.prepare("SELECT last_insert_rowid() AS id").get().id; } anlegen("Filipe", "admin", "CODE-DOGI-0001"); anlegen("Funny", "hand", "CODE-HAND-0001"); anlegen("Lino", "linke", "CODE-LINK-0001"); anlegen("Kessi", "modi", "CODE-MOD1-0001"); /* Ein Gast muss sein Alter bestaetigt haben, sonst kommt er nicht durch. */ const idGast = anlegen("Robin", "gast", "CODE-GAST-0001"); d.prepare("UPDATE personen SET alter_bestaetigt_am = ? WHERE id = ?").run(jetzt, idGast); d.close(); async function alsWer(rolle, code) { const a = await rufe("/workspace/api/anmelden", { method: "POST", body: { rolle, code } }); return (a.kopf?.["set-cookie"] || []).map((z) => z.split(";")[0]).join("; "); } /* --------------------------------------------------------------------- 1. DIE ALTE ADRESSE FUEHRT WEITER --------------------------------------------------------------------- */ melde("\n=== Die alte Adresse ==="); const keksModi = await alsWer("modi", "CODE-MOD1-0001"); const alt = await rufe("/workspace/willkommen.html", { keks: keksModi }); ok(alt.code === 200, `willkommen.html antwortet (${alt.code}) — und zwar mit einer Seite, ` + "nicht mit einer Umleitung"); /* DREI WEGE, UND ALLE DREI WERDEN GEBRAUCHT. Der Kopfbefehl wirkt auch ohne JavaScript, `location.replace` wirkt sofort und OHNE Eintrag im Verlauf (sonst landete man beim Zurueckgehen wieder hier), und der sichtbare Verweis faengt den Fall ab, dass beides nicht greift. */ ok(/http-equiv="refresh"[^>]*anleitung\.html/.test(alt.text), "…mit dem Kopfbefehl, der auch ohne JavaScript weiterleitet"); ok(/location\.replace\(/.test(alt.text), "…mit location.replace — ohne Eintrag im Verlauf"); ok(/]+href="anleitung\.html"/.test(alt.text), "…und mit einem sichtbaren Verweis als letzter Rettung"); /* Wer mit einem Zusatz in der Adresse kommt, soll dort landen, wohin er wollte. Gemessen wird der Code, nicht die Absicht. */ ok(/location\.search/.test(alt.text), "…und der Zusatz in der Adresse wird mitgenommen"); /* Die Community kommt auf dieselbe Seite. Sie ist die Gruppe, bei der die alte Kachel am laengsten im Gedaechtnis steht. */ const keksGast = await alsWer("gast", "CODE-GAST-0001"); const altGast = await rufe("/workspace/willkommen.html", { keks: keksGast }); ok(altGast.code === 200 && /anleitung\.html/.test(altGast.text), `auch fuer die Community (${altGast.code})`); /* GEGENPROBE ZUR SCHRANKE: Ohne Anmeldung gibt es sie nicht. Sonst waeren die Zeilen oben auch gruen, wenn die Seite fuer jeden offen stuende. */ const altOhne = await rufe("/workspace/willkommen.html"); ok(altOhne.code === 302 || altOhne.code === 401, `ohne Anmeldung gibt es sie nicht (${altOhne.code})`); /* --------------------------------------------------------------------- 2. DAS ALTE IST WIRKLICH WEG --------------------------------------------------------------------- */ melde("\n=== Der abgeloeste Unterbau ==="); const api = await rufe("/workspace/api/willkommen", { keks: keksModi }); ok(api.code === 404, `die alte Schnittstelle gibt es nicht mehr (${api.code})`); /* GEGENPROBE: Eine Schnittstelle, die es GIBT, antwortet anders. Ohne diese Zeile waere die obige auch dann gruen, wenn der ganze Server nicht mehr liefe. */ const lebt = await rufe("/workspace/api/anleitung", { keks: keksModi }); ok(lebt.code === 200, `Gegenprobe: die neue Schnittstelle antwortet sehr wohl (${lebt.code})`); const fehltDatei = (pfad) => { try { readFileSync(new URL(pfad, import.meta.url)); return false; } catch { return true; } }; ok(fehltDatei("./workspace-willkommen.js"), "server/workspace-willkommen.js ist fort"); ok(fehltDatei("../workspace/assets/js/willkommen.js"), "assets/js/willkommen.js ist fort"); ok(fehltDatei("../workspace/assets/css/willkommen.css"), "assets/css/willkommen.css ist fort"); /* GEGENPROBE: Dieselbe Frage an eine Datei, die es gibt. Ohne sie waeren die drei Zeilen darueber auch dann gruen, wenn `fehltDatei` immer „ja" sagte — zum Beispiel, weil der Pfad falsch gebaut wird. */ ok(!fehltDatei("./workspace-anleitung.js"), "Gegenprobe: eine vorhandene Datei wird als vorhanden erkannt"); /* Und niemand haengt den Router wieder ein. Kommentare werden vorher entfernt — sonst wuerde dieser Hinweis hier selbst zum Treffer. */ const indexRoh = readFileSync(new URL("./index.js", import.meta.url), "utf8"); const indexCode = indexRoh .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/(^|[^:])\/\/[^\n]*/g, "$1 "); ok(!/willkommenRouter/.test(indexCode), "index.js haengt den alten Router nicht mehr ein"); ok(/anleitungRouter/.test(indexCode), "Gegenprobe: der neue Router steht sehr wohl darin"); /* ---------- Abschluss ------------------------------------------------ */ try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } melde(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`); process.exit(fehler ? 1 : 0);