Spicy Media legt Manager UND Scouts an -- eine Liste statt vier

Filipe, mit Bildschirmfoto der Zugaenge-Seite: "die spicy rolle soll
auch manager und scouts hinzufuegen koennen."

DER MANAGER-KNOPF FEHLTE NICHT AUS RECHTEGRUENDEN. Serverseitig war die
Tuer /workspace/api/manager-anlegen fuer Spicy Media die ganze Zeit
offen. Es gab nur nichts zum Draufdruecken -- wegen ZWEIER Listen in
derselben Funktion, drei Zeilen auseinander (personen.js):

  const darf = ... spicy ? ['manager', 'creator'] : ['creator'];
  ...
  if ((r.wert === 'admin' || r.wert === 'manager') && ich.rolle !== 'admin') continue;

Die erste erlaubt den Manager, die zweite nimmt ihn wieder weg. Uebrig
blieb ein einziger Knopf: Creator. Nichts war kaputt, nichts wurde rot,
es fehlte einfach -- die Sorte Fehler, die nur jemandem auffaellt, der
davorsitzt.

FUER SCOUTS GAB ES UEBERHAUPT KEINE TUER. Nur DogFather konnte welche
anlegen. Und die Wegwahl in der Oberflaeche war eine Kette mit
Auffangbecken (`rolle === 'manager' ? ... : creator-anlegen`): Ein Scout
waere im else gelandet, und creator-anlegen legt IMMER einen Creator an.
Der Knopf haette Erfolg gemeldet und das Falsche getan.

DIE ANTWORT STEHT JETZT AN EINER STELLE. `darfAnlegen` in workspace.js
sagt, wer wen anlegen darf. Daraus lesen:

  - die beiden Team-Tueren (Manager, Scout)
  - die allgemeine Verwaltungs-Tuer von DogFather
  - die Oberflaeche, ueber `darf_anlegen` in /workspace/api/ich

Die Oberflaeche hat damit gar keine eigene Liste mehr und kann deshalb
auch nicht mehr abweichen -- weder zu streng noch zu grosszuegig.

ZWEI TUEREN, NICHT EINE MIT EINEM ROLLENFELD. Der Absatz an der
Manager-Tuer raet davon ab, und der Rat gilt: Eine Tuer, die NICHTS
anderes kann, als eine bestimmte Rolle anzulegen, ist sicherer als eine,
die vorher nachfragt. `teamTuer(rolle)` baut beide aus demselben Text --
die Rolle wird beim Einhaengen festgelegt und kommt nie aus dem Aufruf.
Geprueft: ein mitgeschicktes "rolle: admin" bleibt wirkungslos.

GEPRUEFT (pruef-creator-anlegen, 33 -> 49 Pruefungen)
  - Spicy Media legt Manager an       -> 201, Rolle stimmt
  - Spicy Media legt Scout an         -> 201, Rolle stimmt
  - "rolle: admin" mitgeschickt       -> wirkungslos, es wird ein Scout
  - ein Manager durch die Scout-Tuer  -> 404
  - ein Scout durch die Scout-Tuer    -> 404
  - Personenliste lesen               -> 200 (die eine gewollte Ausnahme)
  - darueber anlegen                  -> 404, und es entsteht niemand
  - /api/ich nennt Spicy: manager, scout, creator -- und keinen DogFather
  - ein Manager bekommt genau eine Rolle genannt, ein Scout keine
  - die Knoepfe auf der Seite stimmen mit alldem ueberein
  - DogFather sieht unveraendert alle -- gemessen, nicht geglaubt

Der erste Anlauf der Pruefung behauptete, Spicy Media komme gar nicht an
/workspace/api/verwaltung. Falsch, und sie wurde zu Recht rot: nurAdmin
laesst genau einen Fall durch, das LESEN der Personenliste. Diese
Trennung ist jetzt festgenagelt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-10 21:56:45 +02:00
co-authored by Claude Opus 5
parent 5f270c96c5
commit bcc1c70f4a
28 changed files with 577 additions and 293 deletions
+143
View File
@@ -97,6 +97,9 @@ let idDogi, idMax, idMia, idScoutMax, idScoutMia, idCreator;
idScoutMax = anlegen(d, "SamVonMax", "scout", "CODE-SAM-00001");
idScoutMia = anlegen(d, "TimVonMia", "scout", "CODE-TIM-00001");
idCreator = anlegen(d, "Luna", "creator", "CODE-LUNA-0001");
/* Seit dem 10.09.2026 gehoert Spicy Media dazu: Sie darf Manager UND
Scouts anlegen, und genau das steht im Abschnitt 6. */
anlegen(d, "Agentur", "spicy", "CODE-SPIC-0001");
d.prepare("INSERT INTO scout_zuteilung (scout_id, manager_id, seit) VALUES (?,?,?)")
.run(idScoutMax, idMax, jetzt);
d.prepare("INSERT INTO scout_zuteilung (scout_id, manager_id, seit) VALUES (?,?,?)")
@@ -118,6 +121,7 @@ const keksMax = await anmelden("manager", "CODE-MAX-00001");
const keksMia = await anmelden("manager", "CODE-MIA-00001");
const keksScout = await anmelden("scout", "CODE-SAM-00001");
const keksLuna = await anmelden("creator", "CODE-LUNA-0001");
const keksSpicy = await anmelden("spicy", "CODE-SPIC-0001");
const ruf = async (art, weg, keks, koerper) => {
const a = await fetch(BASIS + weg, { method: art,
@@ -301,6 +305,145 @@ console.log("\n=== Der Knopf auf dem Dashboard ===");
await browser.close();
}
/* =======================================================================
6. SPICY MEDIA LEGT MANAGER UND SCOUTS AN (10.09.2026)
Filipe, mit Bildschirmfoto: "die spicy rolle soll auch manager und
scouts hinzufuegen koennen."
Der Fehler davor war kein fehlendes Recht, sondern ZWEI Listen, die
einander widersprachen: Die Oberflaeche erlaubte Spicy Media in einer
Zeile den Manager und nahm ihn drei Zeilen spaeter wieder weg. Fuer
Scouts gab es ueberhaupt keine Tuer.
Deshalb wird hier BEIDES geprueft — dass es geht, und dass die
Oberflaeche dieselbe Antwort bekommt wie der Server. Eine der beiden
Pruefungen allein haette den Fehler nicht gefunden: Serverseitig war
der Manager ja die ganze Zeit erlaubt.
======================================================================= */
console.log("\n=== Spicy Media legt Manager und Scouts an ===");
{
const { status, daten } = await ruf("POST", "/workspace/api/manager-anlegen",
keksSpicy, { name: "NeuerManager" });
ok(status === 201, `ein Manager entsteht (HTTP ${status})`);
ok(daten?.rolle === "manager", `und ist wirklich ein Manager (${daten?.rolle})`);
}
{
const { status, daten } = await ruf("POST", "/workspace/api/scout-anlegen",
keksSpicy, { name: "NeuerScout" });
ok(status === 201, `ein Scout entsteht (HTTP ${status})`);
/* DIE ROLLE IST HIER DER GANZE PUNKT. Vor dem Umbau waere die
Oberflaeche mit "scout" beim Creator-Weg gelandet, und der legt
IMMER einen Creator an -- der Knopf haette Erfolg gemeldet und das
Falsche getan. */
ok(daten?.rolle === "scout", `und ist wirklich ein Scout (${daten?.rolle})`);
}
{
/* Die Rolle kommt NICHT aus dem Aufruf: Wer sie mitschickt, aendert
nichts. Die Tuer kennt genau eine. */
const { status, daten } = await ruf("POST", "/workspace/api/scout-anlegen",
keksSpicy, { name: "MoechtegernChef", rolle: "admin" });
ok(status === 201 && daten?.rolle === "scout",
`ein mitgeschicktes "rolle: admin" bleibt wirkungslos (${daten?.rolle})`);
}
{
const { status } = await ruf("POST", "/workspace/api/scout-anlegen",
keksMax, { name: "ScoutVonMax" });
ok(status === 404, `ein Manager kommt durch diese Tuer nicht (HTTP ${status})`);
}
{
const { status } = await ruf("POST", "/workspace/api/scout-anlegen",
keksScout, { name: "ScoutVomScout" });
ok(status === 404, `ein Scout auch nicht (HTTP ${status})`);
}
/* DIE AUSNAHME, DIE ES WIRKLICH GIBT — und ihre Grenze.
Erster Anlauf dieser Pruefung behauptete, Spicy Media komme gar nicht
an /workspace/api/verwaltung. Das war falsch, und die Pruefung wurde
zu Recht rot: `nurAdmin` laesst genau EINEN Fall durch, das LESEN der
Personenliste ("spicy soll genau das sehen koennen"). Ueberblick ist
nicht Verwaltung — und genau diese Trennung gehoert festgenagelt,
sonst weitet sie sich beim naechsten Umbau unbemerkt aus. */
{
const { status } = await ruf("GET", "/workspace/api/verwaltung/personen", keksSpicy);
ok(status === 200, `die Personenliste darf Spicy Media LESEN (HTTP ${status})`);
}
{
const { status } = await ruf("POST", "/workspace/api/verwaltung/personen",
keksSpicy, { name: "AufDemKurzenWeg", rolle: "admin" });
ok(status === 404, `aber nicht darueber anlegen (HTTP ${status})`);
const z = new DatabaseSync(DB).prepare(
"SELECT rolle FROM personen WHERE name = ?").get("AufDemKurzenWeg");
ok(!z, "und es entsteht dabei auch niemand");
}
/* ---- Und die Oberflaeche fragt dieselbe Stelle ---------------------- */
{
const { daten } = await ruf("GET", "/workspace/api/ich", keksSpicy);
const darf = daten?.darf_anlegen || [];
ok(darf.includes("manager") && darf.includes("scout") && darf.includes("creator"),
`/api/ich nennt Spicy Media: ${darf.join(", ")}`);
/* DAS WICHTIGERE: was NICHT drinsteht. Ein Knopf zu viel waere hier
schlimmer als einer zu wenig -- er verspraeche ein Recht, das es
nicht gibt. */
ok(!darf.includes("admin"), "und keinen DogFather");
}
{
const { daten } = await ruf("GET", "/workspace/api/ich", keksMax);
const darf = daten?.darf_anlegen || [];
ok(darf.length === 1 && darf[0] === "creator",
`ein Manager bekommt genau eine Rolle genannt: ${darf.join(", ") || "keine"}`);
}
{
const { daten } = await ruf("GET", "/workspace/api/ich", keksScout);
const darf = daten?.darf_anlegen || [];
ok(darf.length === 0, `ein Scout bekommt keine genannt (${darf.length})`);
}
/* ---- Die Knoepfe auf der Zugaenge-Seite ----------------------------- */
console.log("\n=== Die Rollenknoepfe auf der Zugaenge-Seite ===");
{
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
const knoepfeFuer = async (rolle, code) => {
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
const seite = await ctx.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await seite.click(`.rolle[data-rolle="${rolle}"]`);
await seite.fill("#code", code);
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 25000 });
await seite.goto(BASIS + "/workspace/personen.html", { waitUntil: "networkidle" });
await seite.waitForTimeout(1600);
const liste = await seite.$$eval(".rollenwahl__knopf",
(ks) => ks.map((k) => k.dataset.rolle)).catch(() => []);
await ctx.close();
return liste;
};
const spicy = await knoepfeFuer("spicy", "CODE-SPIC-0001");
ok(spicy.includes("manager"), `Spicy Media sieht den Manager-Knopf (${spicy.join(", ")})`);
ok(spicy.includes("scout"), "und den Scout-Knopf");
ok(spicy.includes("creator"), "und den Creator-Knopf");
ok(!spicy.includes("admin"), "aber keinen DogFather-Knopf");
/* GEGENPROBE: Die Zaehlung erkennt einen fehlenden Knopf wirklich.
Beim Manager MUSS genau einer stehen -- stuenden dort drei, waere
die Messung oben wertlos. */
const managerKnoepfe = await knoepfeFuer("manager", "CODE-MAX-00001");
ok(managerKnoepfe.length === 1 && managerKnoepfe[0] === "creator",
`ein Manager sieht genau einen: ${managerKnoepfe.join(", ")}`);
/* UND BEI DOGFATHER AENDERT SICH NICHTS. Er bekommt jetzt eine
laengere Liste vom Server (auch die verborgenen Rollen), die
Knopfliste der Seite kennt aber nur die fuenf sichtbaren -- der
Schnitt der beiden ist derselbe wie vorher. Das ist eine Behauptung
ueber MEINE Aenderung, also gehoert sie gemessen und nicht
geglaubt. */
const dogiKnoepfe = await knoepfeFuer("admin", "CODE-DOGI-0001");
ok(dogiKnoepfe.length >= 5 && ["spicy", "admin", "manager", "scout", "creator"]
.every((r) => dogiKnoepfe.includes(r)),
`DogFather sieht weiterhin alle fuenf: ${dogiKnoepfe.join(", ")}`);
await browser.close();
}
kind.kill();
await new Promise((r) => setTimeout(r, 400));
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Zugabe */ }