Filipe: "und wieso kann ich immer noch keine community rolle
erstellen?"
Nachgemessen statt geraten: Die Auskunft schickte "gast/Community" als
waehlbare Rolle mit, die Oberflaeche baute den Knopf daraus -- und das
Anlegen antwortete 400 "Unbekannte Rolle." Genau so reproduziert.
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 -- und
hier stand es vier Zeilen ueber der Stelle, an der es passiert ist.
Gefragt wird jetzt `darfAnlegen` -- dieselbe Auskunft, aus der die
Oberflaeche ihre Knoepfe baut. Damit koennen die beiden nicht mehr
auseinanderlaufen.
ZWEI NEBENBEFUNDE, beide groesser als der gemeldete Fehler:
1. 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. Es liess sich
also ein zweiter DogFather-Zugang anlegen.
2. DIESELBE ZEILE STAND EIN ZWEITES MAL, beim Rollenwechsel. Auch eine
bestehende Person liess sich nicht zu "Community" machen. Dort stand
die richtige Pruefung (`darfAnlegen`) schon direkt darunter -- die
falsche davor kam ihr nur zuvor. Und sie antwortete gespraechiger
(403 "Diese Rolle vergibst du nicht." gegen 400 "Unbekannte
Rolle."); wer durchprobiert, haette daran ablesen koennen, welche
Rollen es gibt. Jetzt beide Faelle wortgleich.
Gefunden hat das nicht das Lesen, sondern die neue Pruefung: Ihre
Suche nach der alten Zeile reichte in die Nachbarroute hinein.
NEU: pruef-rollen-anlegen (11 Pruefungen)
Sie schreibt keine Liste ab, sondern holt sich die Rollen aus
`darfAnlegen` -- derselben Quelle wie Route und Oberflaeche -- und legt
JEDE davon wirklich an. Eine eigene Liste waere eine dritte gewesen und
damit dieselbe Falle noch einmal.
Dazu die Gegenproben: eine erfundene Rolle, eine leere, und ein zweiter
DogFather -- alle drei abgelehnt.
pruef-rollen 315, pruef-personen-formular 36, pruef-personen-liste,
pruef-rechtetafel 19: gruen.
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 { portMussFreiSein } from "./helfer-port.mjs";
|
|
|
|
const PORT = await portMussFreiSein(4497, "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);
|