diff --git a/server/pruef-rollen.mjs b/server/pruef-rollen.mjs index 3cedf226..3e91c624 100644 --- a/server/pruef-rollen.mjs +++ b/server/pruef-rollen.mjs @@ -85,6 +85,10 @@ setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 540_000).unref?.( let fehler = 0; let geprueft = 0; +/* Summe der Seiten, die alle Rollen ZUSAMMEN durchgehen -- je Rolle + verschieden, seit die beiden aus dem anderen Haus ihre eigenen + haben. Siehe unten beim Abschluss. */ +let erwartetGesamt = 0; /* DER DRITTE AUSGANG (23.09.2026). Diese Pruefung kannte nur zwei: in Ordnung und nicht in Ordnung. Eine Kachel, die aus dem Haus hinausfuehrt, ist weder das eine noch das andere -- sie ist von hier @@ -196,20 +200,89 @@ const ROLLEN = [ { rolle: "creator", code: "CODE-TILI-0001", name: "Creator (Tili)" }, { rolle: "scout", code: "CODE-NEU-000001", name: "Scout (OHNE Creator)" }, { rolle: "manager", code: "CODE-NEUM-00001", name: "Manager (OHNE Creator)" }, - /* DER SIEBTE DURCHGANG: ein Modi (09.09.2026). + /* ==== DIE BEIDEN AUS DEM ANDEREN HAUS (berichtigt 06.10.2026) ==== - `rolle` ist hier die KACHEL, auf die geklickt wird -- nicht die - Rolle, die dabei herauskommt. Fuer einen Modi gibt es keine Kachel; - er tippt auf irgendeine vorhandene, und der Code entscheidet. Dass - ausgerechnet "creator" dasteht, ist Absicht und beweist nebenbei, - dass die Kachel wirklich gleichgueltig ist. */ - { rolle: "creator", code: "CODE-MODI-0001", name: "Modi (Marina)" }, - /* Auch sie meldet sich ueber eine fremde Kachel an -- auf der - gewohnten Wand gibt es fuer sie keine, und der verborgene Zugang - erkennt sie am Code. Genau so kommt sie auch im Betrieb herein. */ - { rolle: "creator", code: "CODE-HAND-0001", name: "Rechte Hand (Rieke)" }, + HIER STAND `rolle: "creator"` MIT EINER AUSFUEHRLICHEN BEGRUENDUNG: + „Fuer einen Modi gibt es keine Kachel; er tippt auf irgendeine + vorhandene, und der Code entscheidet." Das war am 09.09.2026 + richtig und ist es seit der Haustrennung nicht mehr. + + Ein Modi kommt auf der AGENTURADRESSE gar nicht mehr herein -- + die Anmeldung antwortet mit 401, und zwar zu Recht + (`sitzungPasstZurAdresse`). Der Lauf hing danach fuenfzehn + Sekunden in `waitForURL` und BRACH AB. Damit war nicht nur der + Modi ungeprueft, sondern auch die rechte Hand dahinter -- und der + ganze Lauf meldete rot, seit Wochen. Eine Pruefung, die immer rot + ist, liest niemand mehr. + + DER FEHLER SAH AUS WIE EINE ZEITUEBERSCHREITUNG, also wie + Langsamkeit. Gemessen (eigener Nachbau, echter Browser) war es + etwas anderes: Die Seite blieb auf `/workspace/` stehen, und in + der Konsole stand ein 401. Nicht langsam -- abgewiesen. + + Beide gehen jetzt ueber ihre eigene Adresse, und `rolle` ist + wieder das, wonach es aussieht: die Kachel, die dort steht. */ + { rolle: "modi", code: "CODE-MODI-0001", name: "Modi (Marina)", haus: "crew" }, + { rolle: "hand", code: "CODE-HAND-0001", name: "Rechte Hand (Rieke)", haus: "crew" }, ]; +/* ===================================================================== + DIE CREW-ADRESSE BRAUCHT HTTPS -- und das ist keine Umstaendlichkeit + ===================================================================== + + Erster Versuch war `http://crew.dogfather-universe.com:PORT` mit + blosser Namensaufloesung. Das ergab eine Seite GANZ OHNE + GESTALTUNG: nackte Browserdarstellung, der Anmeldeknopf 76 x 21 + Pixel am linken Rand, und jeder Klick darauf lief in eine + Zeitueberschreitung. + + DER GRUND steht in pruef-handy-teamdogi: `dogfather-universe.com` + ist in der HSTS-Liste der Browser eingetragen. Chromium stuft jede + http-Adresse dieser Domain selbst auf https hoch -- die + Stilvorlagen kamen deshalb nie an. Nachgemessen hat das erst der + Blick auf das Bildschirmfoto; die Zahlen davor (sichtbar, nicht + gesperrt, bewegt sich nicht) sahen alle in Ordnung aus. + + LIVE IST ALLES HEIL -- gegengeprueft auf der echten Adresse: Die + Wand verlangt gate.css und crew-haus.css, beide kommen mit 200. + Es war also ein Fehler meines Aufbaus, kein Befund am Haus. + + Also derselbe Weg wie dort: ein kleiner HTTPS-Vorbau mit eigenem + Zertifikat, der unveraendert an den echten Server weiterreicht -- + besonders die Host-Kopfzeile, denn an ihr entscheidet das Haus, + wer was sieht. */ +const CREW = "crew.dogfather-universe.com"; +const { createServer: httpsServer } = await import("node:https"); +const { request: httpAnfrage } = await import("node:http"); +const { readFileSync: liesDatei } = await import("node:fs"); +const { zertifikatBauen } = await import("./helfer-openssl.mjs"); +const { mkdtempSync: machOrdner } = await import("node:fs"); +const { tmpdir: zwischenlager } = await import("node:os"); +const { join: pfadBauen } = await import("node:path"); + +const certOrdner = machOrdner(pfadBauen(zwischenlager(), "ws-rollen-cert-")); +const schluesselDatei = pfadBauen(certOrdner, "pruef.key"); +const zertDatei = pfadBauen(certOrdner, "pruef.crt"); +zertifikatBauen(schluesselDatei, zertDatei, CREW); + +const CREW_PORT = PORT + 1; +const vorbau = httpsServer({ + key: liesDatei(schluesselDatei), cert: liesDatei(zertDatei), +}, (von, nach) => { + const weiter = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: von.url, method: von.method, + headers: { ...von.headers, host: `${CREW}:${CREW_PORT}` }, + }, (antwort) => { + nach.writeHead(antwort.statusCode || 502, antwort.headers); + antwort.pipe(nach); + }); + weiter.on("error", () => { try { nach.writeHead(502); nach.end(); } catch { /* egal */ } }); + von.pipe(weiter); +}); +await new Promise((r) => vorbau.listen(CREW_PORT, "127.0.0.1", r)); + +const CREW_BASIS = `https://${CREW}:${CREW_PORT}`; + const { chromium } = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); @@ -250,7 +323,15 @@ const istEgal = (t) => EGAL.some((r) => r.test(t)); const berichte = []; for (const r of ROLLEN) { - const browser = await chromium.launch(); + /* `--ignore-certificate-errors` als ARGUMENT und nicht nur + `ignoreHTTPSErrors` am Kontext: Die Domain steht in der + HSTS-Liste, und dort laesst Chromium sonst kein eigenes + Zertifikat durch (steht so in pruef-handy-teamdogi). */ + const browser = await chromium.launch({ + args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, + "--ignore-certificate-errors"], + ignoreHTTPSErrors: true, + }); const kontext = await browser.newContext({ viewport: { width: 1440, height: 1000 } }); const seite = await kontext.newPage(); @@ -263,6 +344,9 @@ for (const r of ROLLEN) { console.error(" Das ist kein Befund am Code -- meist fehlender Speicher."); }); + /* JEDE ROLLE AUF IHRER EIGENEN ADRESSE. Steht hier oben, weil der + Antwort-Zuhoerer darunter sie schon braucht. */ + const wurzel = r.haus === "crew" ? CREW_BASIS : BASIS; const konsole = []; const schlechteAntworten = []; seite.on("console", (m) => { if (m.type() === "error" && !istEgal(m.text())) konsole.push(m.text()); }); @@ -273,21 +357,55 @@ for (const r of ROLLEN) { /* 401 auf dem Anmeldeweg gehoert dazu -- die Seite fragt, bekommt "nicht angemeldet" und leitet um. Alles andere nicht. */ if (a.status() === 401 && /\/api\/ich|\/api\/sitzung/.test(u)) return; - if (a.status() >= 400) schlechteAntworten.push(`${a.status()} ${u.replace(BASIS, "")}`); + if (a.status() >= 400) schlechteAntworten.push(`${a.status()} ${u.replace(wurzel, "")}`); }); - await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" }); - await seite.click(`.rolle[data-rolle="${r.rolle}"]`); + await seite.goto(wurzel + "/workspace/", { waitUntil: "domcontentloaded" }); + await seite.click(`.rolle[data-rolle="${r.rolle}"]`, { timeout: 10000 }); await seite.fill("#code", r.code); + /* Die Altersfrage erscheint nur fuer die Community -- deshalb erst + fragen, ob sie ueberhaupt sichtbar ist. Dieselbe Reihenfolge wie + in pruef-handy-teamdogi. */ + const alter = await seite.$("#alter-ok"); + if (alter && await alter.isVisible().catch(() => false)) await alter.check(); await seite.click("button[type=submit]"); - await seite.waitForURL("**/start.html", { timeout: 15000 }); + await seite.waitForURL("**/start.html", { timeout: 20000 }); console.log(`\n===== ${r.name} =====`); - for (const pfad of SEITEN) { + /* WELCHE SEITEN DIESE ROLLE BEKOMMT -- abgeleitet, nicht + abgeschrieben. + + `SEITEN` oben ist die Liste des AGENTURHAUSES. Fuer einen Modi + waeren das lauter Seiten, die es auf seiner Adresse nicht gibt; + der Rundgang liefe sechzehnmal gegen dieselbe Startseite und + bewiese nichts. + + Was eine Rolle wirklich hat, steht auf ihrer Startseite -- genau + das benutzt der Kachel-Durchgang weiter unten schon seit dem + 10.09.2026, mit derselben Begruendung („eine feste Liste daneben + koennte selbst veralten"). */ + let seitenListe = SEITEN; + if (r.haus === "crew") { + await seite.goto(wurzel + "/workspace/start.html", { waitUntil: "networkidle" }); + await seite.waitForSelector(".gruppe a[href]", { timeout: 15000 }).catch(() => {}); + const eigene = await seite.evaluate(() => + [...document.querySelectorAll(".gruppe a[href]")] + .map((a) => a.getAttribute("href")) + .filter((h) => h && !h.startsWith("#") && !/^https?:/i.test(h))); + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Faende sich keine Kachel, + bliebe die Liste leer und der Rundgang waere still + uebersprungen -- gruen, ohne eine Seite gesehen zu haben. */ + ok(eigene.length > 0, + `${r.name.padEnd(18)} hat ${eigene.length} eigene Seiten zum Durchgehen`); + if (eigene.length) seitenListe = [...new Set(["start.html", ...eigene])]; + } + erwartetGesamt += seitenListe.length; + + for (const pfad of seitenListe) { konsole.length = 0; schlechteAntworten.length = 0; - await seite.goto(BASIS + "/workspace/" + pfad, { waitUntil: "networkidle" }); + await seite.goto(wurzel + "/workspace/" + pfad, { waitUntil: "networkidle" }); await seite.waitForTimeout(700); const zustand = await seite.evaluate(() => { @@ -353,7 +471,7 @@ for (const r of ROLLEN) { hier durchgeklickt. Eine feste Liste daneben ginge am Punkt vorbei -- sie koennte selbst veralten. ===================================================================== */ - await seite.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" }); + await seite.goto(wurzel + "/workspace/start.html", { waitUntil: "networkidle" }); await seite.waitForSelector(".gruppe a[href]", { timeout: 15000 }).catch(() => {}); const kacheln = await seite.evaluate(() => [...document.querySelectorAll(".gruppe a[href]")] @@ -405,9 +523,9 @@ for (const r of ROLLEN) { } const ziel = href.split("#")[0]; const seitenname = ziel.split("?")[0]; - await seite.goto(BASIS + "/workspace/" + ziel, { waitUntil: "networkidle" }); + await seite.goto(wurzel + "/workspace/" + ziel, { waitUntil: "networkidle" }); await seite.waitForTimeout(400); - const wo = seite.url().replace(BASIS + "/workspace/", ""); + const wo = seite.url().replace(wurzel + "/workspace/", ""); ok(wo.split("?")[0] === seitenname, `${r.name.padEnd(18)} Kachel ${ziel.padEnd(26)} ` + (wo.split("?")[0] === seitenname ? "kommt an" : `LANDET AUF ${wo}`)); @@ -421,9 +539,12 @@ for (const r of ROLLEN) { /* ---------- Abschluss --------------------------------------------------- */ console.log(""); -const ERWARTET = ROLLEN.length * SEITEN.length; -ok(geprueft >= ERWARTET, - `jede Rolle war auf jeder Seite (${geprueft} von ${ERWARTET} Durchgängen)`); +/* FRUEHER: `ROLLEN.length * SEITEN.length`. Das stimmte, solange alle + acht Rollen dieselbe Seitenliste hatten. Seit die beiden aus dem + anderen Haus ihre EIGENEN Seiten durchgehen, wird aufsummiert -- + eine feste Multiplikation waere hier eine Rechnung von gestern. */ +ok(geprueft >= erwartetGesamt, + `jede Rolle war auf jeder ihrer Seiten (${geprueft} von ${erwartetGesamt} Durchgängen)`); if (berichte.length) { console.log("\n--- Was zu tun ist ---");