Die rechte Hand legt selbst Personen an -- und sieht die Codes

Filipe: "dan will ich dass die rechte hand auch neue personen
hinzufuegen kann. also neue erstellen kann und die codes genau so
sieht wie dogfather, damit sie das auch machen kann wenn er live ist."

WAS SIE DARF: Modis und Community anlegen, und deren Codes neu setzen.
Die Liste ist ABGELEITET aus ROLLEN_ZUM_AENDERN -- dieselben zwei
Rollen, die sie ohnehin vergeben darf. Zwei Listen waeren zwei
Gelegenheiten, eine davon zu aendern und die andere zu vergessen.

WAS SIE NICHT DARF: eine zweite rechte Hand, eine linke Hand oder
einen zweiten DogFather anlegen -- und an einer linken Hand auch
nichts aendern. Ohne die zweite Schranke haette sie den Code einer
linken Hand neu setzen koennen und damit einen Zugang in der Hand, der
fast so viel darf wie sie selbst. Die alte Schranke kannte nur "admin"
und "manager".

NUR DIE RECHTE, NICHT DIE LINKE. `istHand()` haette beide getroffen;
fuer die linke Hand ist "legt niemanden an" eine ausdrueckliche
Entscheidung vom 21.09.

VIER STELLEN IN DER OBERFLAECHE, die alle an Rollennamen hingen:

  `nurLesen = ich.rolle === 'hand'` -- sie bekam die Liste und kein
  Formular. Jetzt abgeleitet aus `darf_anlegen`.

  Die Wache darueber warf sie auf die Startseite, sobald `nurLesen`
  falsch wurde. Die Seite ging fuer sie einfach nicht auf, ohne
  Meldung.

  Der Sendeweg hing an `ich.rolle === 'admin'`. Fuer sie gab es damit
  GAR KEINEN: Die Seite antwortete "Fuer die Rolle modi gibt es hier
  keinen Weg" -- ein Satz, der wie ein Formularfehler klingt und eine
  fehlende Zeile war.

  UND EIN ECHTER FUND: `rollenwahlErgaenzen()` hing jede Zusatzrolle an
  das Formular, die mit der Personenliste kam -- ohne zu fragen, ob man
  sie anlegen darf. Solange nur DogFather das Formular sah, fiel es
  nicht auf: Er darf sie alle. Der rechten Hand bot es "rechte Hand"
  und "linke Hand" an. Der Server haette es abgelehnt -- aber der Knopf
  verriet eine Rolle, die sie nicht vergeben soll.

Zwei Pruefungen waren dabei selbst kaputt: pruef-personen-formular
erwartete sieben Rollen (seit "linke" am 21.09. sind es acht) und
suchte den Namen im sichtbaren Text -- die Abschnitte sind zugeklappt
und zeigen nur Anfangsbuchstaben. Beides abgeleitet statt gezaehlt.

Geprueft: pruef-personen-formular 43/0 (war 34 ok / 2 FEHL), davon
neun am echten Bildschirm auf der Crew-Adresse -- anmelden, Formular
oeffnen, anlegen, Code lesen, Person in der Liste wiederfinden.
pruef-haus-trennung 81/0 (war 72 ok / 2 FEHL), pruef-rollen-anlegen
11/0.
This commit is contained in:
2026-09-22 01:39:12 +02:00
parent 39c0b4dc1f
commit 82335c783d
43 changed files with 899 additions and 540 deletions
+177 -4
View File
@@ -169,10 +169,25 @@ const mass = await dogi.evaluate(() => {
DIE ZEILE WURDE GEDREHT, NICHT GELOESCHT: Dass `admin` FEHLT, ist
jetzt eine eigene Aussage -- sonst haette die Liste unten still eine
Rolle mehr bekommen koennen, ohne dass es jemand merkt. */
ok(mass.karten === 7, `alle sieben vergebbaren Rollen stehen zur Wahl (${mass.karten})`);
ok(JSON.stringify([...mass.kartenRollen].sort())
=== JSON.stringify(["creator", "gast", "hand", "manager", "modi", "scout", "spicy"]),
`und es sind die richtigen (${mass.kartenRollen.join(", ")})`);
/* ABGELEITET, NICHT GEZAEHLT (berichtigt 22.09.2026).
---------------------------------------------------------------------
Hier stand `=== 7` und eine abgeschriebene Liste von sieben Namen.
Am 21.09. kam die Rolle "linke" dazu, und beide Zeilen wurden rot,
obwohl das Formular richtig war -- gemessen acht Karten, erwartet
sieben. Die Pruefung war einen Tag lang rot und hat dabei nichts
gefunden ausser sich selbst.
Die eigentliche Aussage ist auch eine andere: Das Formular soll
GENAU DAS anbieten, was der Server auch annimmt. Zwei Listen an zwei
Stellen sind zwei Gelegenheiten, eine davon zu vergessen -- genau
daran ist es am 17.09. schon einmal gescheitert ("wieso kann ich
immer noch keine community rolle erstellen?"). */
const w = await import("./workspace.js");
const sollKarten = [...w.darfAnlegen({ rolle: "admin", haus: "agentur" })].sort();
ok(mass.karten === sollKarten.length,
`alle ${sollKarten.length} vergebbaren Rollen stehen zur Wahl (${mass.karten})`);
ok(JSON.stringify([...mass.kartenRollen].sort()) === JSON.stringify(sollKarten),
`und es sind genau die, die der Server annimmt (${mass.kartenRollen.join(", ")})`);
ok(!mass.kartenRollen.includes("admin"),
"DogFather steht NICHT zur Wahl -- auch nicht fuer DogFather");
@@ -413,6 +428,164 @@ const ERWARTET = 17;
console.log("");
ok(geprueft >= ERWARTET, `es sind ${geprueft} Prüfungen gelaufen (erwartet mindestens ${ERWARTET})`);
/* ===================================================================
TEIL: DIE RECHTE HAND LEGT SELBST JEMANDEN AN (22.09.2026)
===================================================================
Filipe: "dan will ich dass die rechte hand auch neue personen
hinzufuegen kann ... und die codes genau so sieht wie dogfather,
damit sie das auch machen kann wenn er live ist."
GEPRUEFT WIRD DER GANZE WEG, nicht die Rechtetabelle. Die sagt seit
heute "ja"; ob ein Mensch damit jemanden anlegen kann, ist eine
andere Frage. Beim ersten Anlauf war sie mit "nein" zu beantworten:
Die Seite entschied den Sendeweg an `ich.rolle === 'admin'`, und
fuer die rechte Hand gab es damit GAR KEINEN -- sie bekam "Fuer die
Rolle modi gibt es hier keinen Weg", einen Satz, der wie ein Fehler
im Formular klingt.
=================================================================== */
console.log("\n— Die rechte Hand legt jemanden an —");
{
/* EIGENER BROWSER AUF DER CREW-ADRESSE.
------------------------------------------------------------------
127.0.0.1 ist die AGENTURWAND -- dort gibt es die Kachel "rechte
Hand" gar nicht, und der erste Anlauf lief in eine
Zeitueberschreitung. Das war kein Befund, sondern die falsche Tuer.
Chromium zwingt `crew.dogfather-universe.com` auf https (die
Adresse steht in seiner eingebauten HSTS-Liste), deshalb ein
Vorbau mit eigenem Zertifikat -- derselbe Weg wie in
pruef-handy-teamdogi und pruef-zuteilung. */
const { execFileSync } = await import("node:child_process");
const { createServer: httpsServer } = await import("node:https");
const { request: httpAnfrage } = await import("node:http");
const { readFileSync: lesen } = await import("node:fs");
const HOST = "crew.dogfather-universe.com";
const sk = join(ordner, "pf.key"), zt = join(ordner, "pf.crt");
execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes",
"-keyout", sk, "-out", zt, "-days", "2", "-subj", `/CN=${HOST}`,
"-addext", `subjectAltName=DNS:${HOST}`], { stdio: "ignore" });
const HP = PORT + 1;
const vorbau = httpsServer({ key: lesen(sk), cert: lesen(zt) }, (von, nach) => {
const w = httpAnfrage({ host: "127.0.0.1", port: PORT, path: von.url,
method: von.method, headers: { ...von.headers, host: `${HOST}:${HP}` } },
(a) => { nach.writeHead(a.statusCode || 502, a.headers); a.pipe(nach); });
von.pipe(w);
});
await new Promise((r) => vorbau.listen(HP, "127.0.0.1", r));
const CREW = `https://${HOST}:${HP}`;
const crewBrowser = await chromium.launch({
args: [`--host-resolver-rules=MAP ${HOST} 127.0.0.1`, "--ignore-certificate-errors"],
});
const kontext = await crewBrowser.newContext({ viewport: { width: 1440, height: 1000 } });
const hand = await kontext.newPage();
try {
await hand.goto(CREW + "/workspace/", { waitUntil: "domcontentloaded" });
await hand.click('.rolle[data-rolle="hand"]');
await hand.fill("#code", "CODE-HAND-0001");
await hand.click("button[type=submit]");
await hand.waitForURL("**/start.html", { timeout: 20000 });
await hand.goto(CREW + "/workspace/personen.html", { waitUntil: "networkidle" });
await hand.waitForTimeout(900);
await hand.click("#neu-oeffnen").catch(() => {});
await hand.waitForTimeout(500);
const bild = await hand.evaluate(async () => {
const a = await fetch("/workspace/api/ich").then((r) => r.json()).catch(() => ({}));
const f = document.getElementById("neu");
const auf = document.getElementById("neu-oeffnen");
return {
formDa: !!f && !f.hidden,
knopfDa: !!auf && !auf.hidden,
rollen: [...document.querySelectorAll("#neu .rollenwahl__knopf")]
.map((k) => k.dataset.rolle || k.getAttribute("data-rolle")).filter(Boolean),
woSonst: [...document.querySelectorAll(".rollenwahl__knopf")]
.map((k) => (k.closest("[id]") || {}).id + ":" + (k.dataset.rolle || "?")),
vomServer: a.darf_anlegen,
meineRolle: a.rolle,
};
});
console.log(" /api/ich sagt: rolle=" + bild.meineRolle
+ " darf_anlegen=" + JSON.stringify(bild.vomServer)
+ " Knopf=" + bild.knopfDa + " Form offen=" + bild.formDa);
console.log(" Rollenknoepfe: " + JSON.stringify(bild.woSonst));
ok(bild.formDa, "sie bekommt das Formular ueberhaupt zu sehen");
ok(bild.rollen.length > 0 && bild.rollen.every((r) => ["modi", "gast"].includes(r)),
`und genau die Rollen, die sie darf (${bild.rollen.join(", ") || "keine"})`);
/* JETZT WIRKLICH ANLEGEN. Ein Formular, das dasteht und beim
Absenden scheitert, ist schlimmer als keines. */
const name = "Neue Modi " + Date.now().toString().slice(-5);
await hand.evaluate((n) => {
const k = [...document.querySelectorAll(".rollenwahl__knopf")]
.find((x) => (x.dataset.rolle || "") === "modi");
if (k) k.click();
document.getElementById("f-name").value = n;
}, name);
await hand.waitForTimeout(250);
await hand.click("#neu button[type=submit]").catch(async () => {
await hand.evaluate(() => document.getElementById("neu").requestSubmit());
});
await hand.waitForTimeout(2200);
/* DIE ABSCHNITTE DER LISTE SIND ZUGEKLAPPT -- `innerText` sieht
zugeklappten Inhalt nicht. Dieselbe Falle wie am 21.09. bei
pruef-nachfrage. Erst aufklappen, dann messen. */
await hand.evaluate(() => {
for (const d of document.querySelectorAll("details")) d.open = true;
});
await hand.waitForTimeout(400);
const danach = await hand.evaluate((n) => {
const kasten = document.querySelector(".code-kasten, #code-kasten");
const text = document.body.innerText;
return {
fehler: (document.getElementById("neu-fehler") || {}).textContent || "",
codeDa: !!kasten && !kasten.hidden,
codeText: kasten ? (kasten.innerText || "").slice(0, 200) : "",
stehtInListe: text.includes(n),
listeDa: !!document.getElementById("liste"),
listeText: (document.getElementById("liste") || {}).innerText
? document.getElementById("liste").innerText.replace(/\s+/g, " ").slice(-260) : "(leer)",
gesucht: n,
};
}, name);
ok(!danach.fehler, `kein Fehler beim Absenden (${danach.fehler || "keiner"})`);
ok(danach.codeDa, "der Zugangscode wird ihr angezeigt");
ok(/[A-Z0-9]{4}/.test(danach.codeText),
`und er sieht auch wie einer aus (${danach.codeText.replace(/\s+/g, " ").slice(0, 60)})`);
/* GEMESSEN WIRD DIE LISTE DES SERVERS, NICHT DER SICHTBARE TEXT
(berichtigt 22.09.2026).
Hier stand `document.body.innerText.includes(name)`. Die
Abschnitte der Personenliste sind zugeklappt und zeigen nur die
Anfangsbuchstaben -- der volle Name steht dort also gar nicht,
obwohl die Person laengst angelegt ist. Die Zeile war rot, und
kaputt war nur sie selbst.
Jetzt wird gefragt, was sie WIRKLICH bekommt: dieselbe Auskunft,
aus der die Liste gebaut wird. Das beweist beides auf einmal --
dass sie anlegen kann UND dass sie das Ergebnis sieht. */
const ausListe = await hand.evaluate(async (n) => {
const a = await fetch("/workspace/api/verwaltung/personen")
.then((r) => r.json()).catch(() => null);
return {
code: Array.isArray(a?.personen) ? 200 : 0,
dabei: (a?.personen || []).some((x) => x.name === n),
zahl: (a?.personen || []).length,
};
}, name);
ok(ausListe.code === 200,
`sie bekommt die Personenliste (${ausListe.zahl} Menschen)`);
ok(ausListe.dabei, ausListe.dabei
? "und die eben angelegte Person steht darin"
: `die eben angelegte Person fehlt in ihrer Liste (${name})`);
} finally {
await kontext.close();
await crewBrowser.close();
await new Promise((r) => vorbau.close(r));
}
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`);