Gemessen: 165 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN
doppelt, vier davon dreifach. Niemand hatte das gewollt; jede neue
Pruefung wurde von einer vorhandenen abgeschrieben, und die Nummer kam
mit. Meine eigene Notiz sagte "sieben" -- auch eine Bestandsliste
altert.
Der Waechter faengt den Schaden ab, aber er kann nur melden, was schon
passiert ist: Zwei Pruefungen mit derselben Nummer koennen nie
gleichzeitig laufen, und ein liegengebliebener Prozess der einen laesst
die andere abbrechen mit einer Meldung, die wie ein Befund aussieht.
Genau das ist mir am 20.09. zweimal passiert.
Jetzt leitet jede Datei ihre Nummer aus ihrer STELLE IM ALPHABET ab
(eigenerPort in helfer-port.mjs), zwei je Datei. Nicht ueber eine
Pruefsumme: Bei 165 Namen in 4900 Nummern waeren nach dem
Geburtstagsproblem rund DREI Zusammenstoesse zu erwarten -- ein Hash
tauscht eine sichtbare Doppelung gegen eine unsichtbare. Die Stelle im
Alphabet ist eindeutig von der Bauart her.
--- ZWEI FEHLER AUF DEM WEG, BEIDE LEHRREICH -------------------------
1. DER ERSTE VERSUCH WAR GRUEN UND KAPUTT. Ersetzt wurde mit einem
Muster: "([^"]*4231[^"]*)". Das hielt
{ host: "127.0.0.1", port: 4231, path: "/404.html" }
fuer eine Zeichenkette -- ein Muster kann eine oeffnende nicht von
einer schliessenden Anfuehrung unterscheiden. Heraus kam
{ host: "127.0.0.1`, port: ${PORT}, path: `/404.html" }
also GUELTIGER Code ohne port-Feld. `node --check` sagte gruen fuer
alle 164 Dateien. Aufgefallen ist es erst, weil ich vier Vertreter
gegen eine vorher gemessene Grundlinie laufen liess: pruef-schranke
39/0 vorher, 38/1 nachher.
Alles zurueckgenommen und mit einem Zerleger neu gemacht, der weiss,
ob eine Stelle Code, Zeichenkette, Vorlage, Kommentar oder
regulaerer Ausdruck ist. In pruef-ics stand die Nummer in einem
regulaeren Ausdruck -- der wird jetzt gebaut statt hingeschrieben.
2. EIN MODUL, DAS BEIM IMPORTIEREN ARBEITET, IST EINE FALLE. Der
zweite Durchgang importierte den ersten, um seine Mechanik zu
benutzen -- und fuehrte dessen Hauptlauf gleich mit aus. Die
zweiten Nummern wurden dadurch als erste behandelt, zwei Aufrufe
bekamen dieselbe Nummer, und in pruef-content stand `const PORT`
zweimal.
--- WAS DAS DAUERHAFT HAELT -----------------------------------------
pruef-portnummern.mjs (neu, 8 Pruefungen) fragt nicht "welche Nummern
sind doppelt", sondern "wer traegt ueberhaupt noch eine von Hand ein"
und "wer startet einen Server, ohne seine Nummer abzuleiten". Die
zweite Frage hat sofort etwas gefunden, das in KEINER Doppelungsliste
stand: pruef-push-weg belegte 4341 und 4342, rief den Waechter aber
gar nicht auf -- dieselbe Nummer wie pruef-agentur. Eine Liste zeigt
nur, was auf ihr steht.
Mit Gegenprobe: Eine unbekannte Datei bekommt keine geratene Nummer,
sondern einen Abbruch, und eine dritte Nummer je Datei gibt es nicht.
--- NACHGEMESSEN ----------------------------------------------------
Sechzehn Pruefungen gegen ihre vorher gemessene Grundlinie, je eine
Vertreterin jeder umgestellten Bauweise (eine Nummer, zwei Nummern,
Nummer in einer Zeichenkette, in einer Vorlage, in einem regulaeren
Ausdruck, dynamische Einfuhr, Nachtlauf mit zwei Laeufen):
crew-adresse 132/0 · schranke 39/0 · content 45/0 · entwicklung 46/0
anruf 127/0 · ics 37/0 · push-weg 20/0 · arten 28/0 · video 67/0
kanalzeile 14/0 · agentur 62/0 · spicy 83/0 · creator-anlegen 50/0
push-ziel 10/0 · portnummern 8/0
Alle exakt wie vorher. Zwei waren schon vorher rot und sind es
unveraendert geblieben (crew-wand-bild 41/4, teilen 14/3) -- per
`git stash` belegt, nicht angenommen.
Co-Authored-By: Claude Opus 5 <[email protected]>
216 lines
9.9 KiB
JavaScript
216 lines
9.9 KiB
JavaScript
/* =====================================================================
|
|
WAS DIE OBERFLÄCHE ANBIETET, MUSS DER SERVER AUCH ANNEHMEN
|
|
(17.09.2026)
|
|
|
|
Filipe: "und wieso kann ich immer noch keine community rolle
|
|
erstellen?"
|
|
|
|
Nachgemessen: Die Auskunft `/api/verwaltung/personen` schickte
|
|
"gast/Community" als waehlbare Rolle mit, die Oberflaeche baute den
|
|
Knopf daraus -- und das Anlegen antwortete "Unbekannte Rolle."
|
|
|
|
---------------------------------------------------------------------
|
|
DIE URSACHE: EINE LISTE, ZWEI FRAGEN
|
|
|
|
Die Pruefung las `ROLLEN`, und `ROLLEN` ist `ROLLEN_REIHE` -- die
|
|
SORTIERREIHENFOLGE. Dort fehlt "gast" mit Absicht; der Kommentar in
|
|
workspace.js sagt es woertlich ("steht mit Absicht NICHT in der
|
|
Liste, sie faellt ans Ende").
|
|
|
|
`ROLLEN_REIHE` beantwortet "in welcher Reihenfolge", nicht "welche
|
|
gibt es". Genau davor wird in diesem Haus an zehn Stellen gewarnt.
|
|
|
|
NEBENBEFUND, und er war die groessere Luecke: Die alte Pruefung
|
|
liess "admin" durch -- `ROLLEN_REIHE` enthaelt sie. Filipes Regel vom
|
|
11.09. ("dogfather soll man nicht auswaehlen koennen, das ist die
|
|
einzige die man nicht auswaehlen kann") stand nur in `ANLEGBAR`, und
|
|
die wurde hier nicht gefragt.
|
|
|
|
---------------------------------------------------------------------
|
|
DIE PRUEFUNG LEITET AB STATT ABZUSCHREIBEN
|
|
|
|
Sie holt sich die angebotenen Rollen aus derselben Auskunft, aus der
|
|
die Oberflaeche ihre Knoepfe baut, und legt JEDE davon wirklich an.
|
|
Eine eigene Liste hier waere eine DRITTE -- und damit dieselbe Falle
|
|
noch einmal.
|
|
|
|
Aufrufen mit: node server/pruef-rollen-anlegen.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-rollen-anlegen");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-ra-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(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 }); };
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-rollen-anlegen");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
const BASIS = `http://127.0.0.1:${PORT}`;
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
const sch = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", sch, jetzt);
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync("CODE-DOGI-0001", 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, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run("Filipe", "admin", hash, salt, 32768,
|
|
createHmac("sha256", sch).update("CODE-DOGI-0001").digest("hex"), jetzt);
|
|
d.close();
|
|
|
|
const an = await fetch(BASIS + "/workspace/api/anmelden", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ rolle: "admin", code: "CODE-DOGI-0001" }),
|
|
});
|
|
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
|
|
const kopf = { "Content-Type": "application/json", cookie: keks, origin: BASIS };
|
|
|
|
async function anlegen(name, rolle) {
|
|
const a = await fetch(BASIS + "/workspace/api/verwaltung/personen", {
|
|
method: "POST", headers: kopf, body: JSON.stringify({ name, rolle }),
|
|
});
|
|
return { status: a.status, daten: await a.json().catch(() => null) };
|
|
}
|
|
|
|
/* =======================================================================
|
|
1. WAS DIE OBERFLAECHE ANBIETET
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Das Angebot ===");
|
|
const v = await fetch(BASIS + "/workspace/api/verwaltung/personen", { headers: { cookie: keks } });
|
|
const auskunft = await v.json().catch(() => null);
|
|
const angeboten = [
|
|
...(auskunft?.rollen || []).map((r) => (typeof r === "string" ? r : r.wert)),
|
|
...(auskunft?.zusatzrollen || []).map((r) => r.wert),
|
|
].filter(Boolean);
|
|
{
|
|
/* DREI, NICHT MEHR -- und das ist richtig so: Die Grundrollen
|
|
(Creator, Scout, Manager, Spicy) stehen als Knoepfe in der
|
|
HTML-Seite, nur die ZUSATZROLLEN kommen aus der Auskunft. Beim
|
|
ersten Lauf habe ich fuenf erwartet und die Pruefung rot gemacht,
|
|
nicht den Code. */
|
|
ok(v.status === 200 && angeboten.length >= 3,
|
|
`die Auskunft bietet ${angeboten.length} Zusatzrollen an (${angeboten.join(", ")})`);
|
|
ok(angeboten.includes("gast"), "darunter die Community-Rolle");
|
|
|
|
/* UND DIE VOLLE MENGE kommt aus derselben Quelle wie die Route:
|
|
`darfAnlegen`. Damit deckt Abschnitt 2 wirklich alles ab, was
|
|
jemand anlegen koennen soll -- nicht nur die Zusatzrollen. */
|
|
const { darfAnlegen } = await import("./workspace.js");
|
|
const alleErlaubt = darfAnlegen({ rolle: "admin", haus: "workspace" });
|
|
ok(alleErlaubt.length >= 6,
|
|
`darfAnlegen nennt ${alleErlaubt.length} Rollen (${alleErlaubt.join(", ")})`);
|
|
for (const r of alleErlaubt) if (!angeboten.includes(r)) angeboten.push(r);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. UND DER SERVER NIMMT JEDE DAVON AN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Die Annahme ===");
|
|
{
|
|
/* DAS IST DER EIGENTLICHE PUNKT. Ein Knopf, den der Server ablehnt,
|
|
ist schlimmer als kein Knopf: Man tippt einen Namen, drueckt, und
|
|
bekommt eine Absage fuer etwas, das angeboten wurde. */
|
|
const abgelehnt = [];
|
|
let n = 0;
|
|
for (const rolle of angeboten) {
|
|
const a = await anlegen(`Probe ${++n}`, rolle);
|
|
if (a.status !== 201) abgelehnt.push(`${rolle} (${a.status} ${a.daten?.fehler || ""})`);
|
|
}
|
|
ok(abgelehnt.length === 0,
|
|
abgelehnt.length ? `abgelehnt: ${abgelehnt.join(" · ")}`
|
|
: `alle ${angeboten.length} angebotenen Rollen lassen sich anlegen`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. UND WAS NICHT ANGEBOTEN WIRD, GEHT AUCH NICHT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Die Gegenprobe ===");
|
|
{
|
|
/* OHNE DIESEN ABSCHNITT hiesse "alles geht" nur, dass gar nicht
|
|
geprueft wird -- man koennte die Pruefung bestehen, indem man sie
|
|
ganz entfernt. */
|
|
const x = await anlegen("Erfunden", "oberchef");
|
|
ok(x.status === 400, `eine erfundene Rolle wird abgelehnt (${x.status})`);
|
|
|
|
/* UND DER NEBENBEFUND VON HEUTE: "admin" stand in ROLLEN_REIHE und
|
|
rutschte damit durch die alte Pruefung. Filipes Regel vom 11.09.
|
|
("dogfather soll man nicht auswaehlen koennen") stand nur in
|
|
ANLEGBAR -- und die wurde nicht gefragt. */
|
|
const a = await anlegen("Zweiter DogFather", "admin");
|
|
ok(a.status === 400,
|
|
`ein zweiter DogFather-Zugang laesst sich NICHT anlegen (${a.status})`);
|
|
ok(!angeboten.includes("admin"), "und die Oberflaeche bietet ihn auch nicht an");
|
|
|
|
/* Ein leerer Wert ist keine Rolle. */
|
|
const l = await anlegen("Ohne Rolle", "");
|
|
ok(l.status === 400, `eine leere Rolle ebenfalls nicht (${l.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. DIE BEIDEN LISTEN STEHEN NICHT MEHR NEBENEINANDER
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Eine Quelle, nicht zwei ===");
|
|
{
|
|
const { readFileSync } = await import("node:fs");
|
|
const quelle = readFileSync(new URL("./workspace-personen.js", import.meta.url), "utf8");
|
|
/* KOMMENTARE RAUS, bevor gesucht wird. Beim ersten Lauf ist die
|
|
Pruefung an ihrem eigenen Erfolg gescheitert: Der Kommentar, der
|
|
den behobenen Fehler BESCHREIBT, enthaelt den alten Ausdruck --
|
|
und die Suche fand ihn. Eine Pruefung, die Beschreibungen fuer
|
|
Code haelt, misst die Dokumentation. */
|
|
const ohneKommentar = quelle
|
|
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
.replace(/\/\/[^\r\n]*/g, " ");
|
|
const route = ohneKommentar.slice(
|
|
ohneKommentar.indexOf('personenRouter.post("/workspace/api/verwaltung/personen"'));
|
|
const stelle = route.slice(0, 3000);
|
|
ok(/darfAnlegen\(req\.person\)\.includes\(rolle\)/.test(stelle),
|
|
"die Route fragt darfAnlegen");
|
|
/* UND NICHT MEHR DIE SORTIERREIHENFOLGE. Ein Kommentar daneben
|
|
wuerde es nicht verhindern -- das steht so in den Hausregeln. */
|
|
ok(!/!ROLLEN\.includes\(rolle\)/.test(stelle),
|
|
"und nicht mehr die Sortierreihenfolge ROLLEN_REIHE");
|
|
|
|
/* ROLLEN_REIHE enthaelt "gast" weiterhin NICHT -- das ist richtig
|
|
so, sie ist eine Sortierung. Steht sie eines Tages doch drin, war
|
|
jemand an der falschen Stelle. */
|
|
const { ROLLEN_REIHE } = await import("./workspace.js");
|
|
ok(!ROLLEN_REIHE.includes("gast"),
|
|
`ROLLEN_REIHE bleibt eine Sortierung ohne "gast" (${ROLLEN_REIHE.join(", ")})`);
|
|
}
|
|
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|