Filipe, 22.09.2026: "ersetze die zentrale durch, Die IrrenAnstalt.
genau so geschrieben bitte. und alles was treff heißt oder wo treff
steht soll durch Rudel ersetzt werden bitte."
Die Schreibweise "IrrenAnstalt" mit grossem A in der Mitte ist so
gewollt. Das steht als Hinweis daneben, damit sie beim naechsten Mal
niemand "korrigiert".
WAS UMBENANNT WURDE: alles, was jemand LIEST -- Kachelnamen,
Ueberschriften, Markenzeilen, Saetze, Meldungen, Aufgabenvorlagen.
116 Zeilen in 42 Dateien.
WAS BLEIBT: Adressen (treff-regeln.html), Bezeichner (TREFF_ROLLEN),
Datenbankwerte (bereich = 'treff'), Abfrageteile (b=treff). Eine
Adresse umzubenennen bricht jedes Lesezeichen, und ein Datenbankwert
umzuschreiben waere eine Umstellung ohne Gegenwert. Dieselbe
Entscheidung wie heute frueh bei Dogi-Media, wo material.html auch
material.html geblieben ist.
DREI GRAMMATIKFEHLER IM EIGENEN ENTWURF, alle vor dem Ausliefern
gefunden -- ein blindes Ersetzen reicht hier nicht:
1. "der Treff" ist maennlich, "das Rudel" saechlich. Ohne Tabelle
waere ueberall "Der Rudel" gestanden. (Und "Treffer" waere zu
"Rudeler" geworden, "Treffen" zu "Rudelen" -- deshalb greift die
Regel nur bei Wortgrenze und nie vor einem Kleinbuchstaben.)
2. BINDESTRICH-ZUSAMMENSETZUNGEN. In "der Treff-Chat" gehoert der
Artikel zu "Chat", nicht zu "Treff". Der erste Durchlauf machte
daraus "das Rudel-Chat", "das Rudel-Kacheln" und "ein Rudel-Raum".
Jetzt greift die Artikelregel nur, wenn das Wort allein steht.
3. GESCHUETZTE LEERZEICHEN. In den Markenzeilen steht
`Der Treff`, damit die zwei Woerter nicht umbrechen. Das
Muster hat daran vorbeigegriffen: "Der Rudel", auf fuenf
Seiten. Jetzt wird das Trennzeichen mitgefasst und unveraendert
wieder eingesetzt.
Gefunden wurden alle drei, weil jede einzelne der 116 Zeilen vor dem
Schreiben als ALT/NEU ausgegeben und gelesen wurde -- und danach
gezielt nach falschen Artikeln gesucht ("den Rudel", "der Rudel",
"einen Rudel"). Uebrig blieben sechs Treffer, und die sind alle
richtig: "einen Rudel-Raum", "der Rudel-Chat", "den Rudel-Regeln" --
Zusammensetzungen, bei denen der Artikel zum letzten Wort gehoert.
UND ZWEI PRUEFUNGEN, die die Umbenennung nicht mitbekommen haetten:
`/Treff/.test(...)` und `/Treff/i.test(...)`. Ein regulaerer Ausdruck
ist keine Zeichenkette -- sie haetten ab sofort nach einem Wort
gesucht, das die Seite nicht mehr sagt, und waeren still rot geworden,
ohne dass etwas kaputt ist.
Geprueft, alle gruen: pruef-treff, pruef-treffchat,
pruef-deutsche-texte, pruef-uebernahme, pruef-crew-adresse,
pruef-willkommen, pruef-wege-nach-draussen, pruef-vorlagen,
pruef-start-ansicht, pruef-struktur.
Co-Authored-By: Claude Opus 5 <[email protected]>
880 lines
45 KiB
JavaScript
880 lines
45 KiB
JavaScript
/* =====================================================================
|
|
DIE EIGENE ADRESSE DER MODI-APP
|
|
|
|
Geprueft wird nicht "die neue Adresse geht", sondern die vier Fragen,
|
|
an denen dieser Umbau scheitern kann:
|
|
|
|
1. KOMMT EIN MODI AUF crew. HEREIN -- und sonst niemand?
|
|
2. IST DER WORKSPACE UNVERAENDERT? Derselbe Dienst bedient beide
|
|
Adressen. Wer nicht auf den Hostnamen prueft, sperrt mit einer
|
|
einzigen Zeile alle Creator, Scouts und Manager aus, sofort nach
|
|
dem Neustart und ohne Vorwarnung.
|
|
3. BLEIBEN DIE PRUEFADRESSEN UNBERUEHRT? Waere die Regel "alles
|
|
ausser crew", dann wuerden ab sofort sieben andere Pruefdateien
|
|
messen, dass ein Modi nirgends hereinkommt -- gruen, weil sie
|
|
nichts mehr finden. Ein Lauf, der nichts mehr prueft, ist
|
|
schlimmer als ein roter.
|
|
4. HEISST DIE APP AUF DEM HANDY DES MODIS ANDERS? Sonst war der
|
|
ganze Umzug umsonst.
|
|
|
|
MIT GEGENPROBE. Ganz unten bekommt der Modi in der Datenbank eine
|
|
andere Rolle -- danach MUSS ihn dieselbe Sitzung auf crew. nicht mehr
|
|
hereinlassen. Ohne diesen Teil waere nicht bewiesen, dass die
|
|
Pruefung ueberhaupt anschlagen kann.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request } from "node:http";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-crew-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
/* DER PORT MUSS MEINER SEIN (nachgeruestet 19.09.2026).
|
|
Ohne diese Zeile startet der eigene Server bei belegtem Port
|
|
STILL nicht -- und alles Folgende misst gegen einen fremden
|
|
Stand. Das ist der dritte Ausgang: belegt ist etwas anderes
|
|
als kaputt. */
|
|
const PORT = await eigenerPort(import.meta, "pruef-crew-adresse");
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
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";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-crew-adresse");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
let fehler = 0;
|
|
let geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
let offen = 0;
|
|
const unklar = (t) => { offen++; console.log(" -- " + t); };
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Ein Aufruf mit frei waehlbarem Host-Kopf.
|
|
|
|
Bewusst node:http und nicht fetch: `Host` gilt in der Fetch-Norm als
|
|
verbotener Kopf, und ob undici das durchlaesst, ist eine Annahme, auf
|
|
die diese ganze Pruefung nicht gebaut sein soll. Hier wird der Kopf
|
|
nachweislich gesetzt.
|
|
------------------------------------------------------------------ */
|
|
function hole(pfad, { host, methode = "GET", rumpf = null, keks = "" } = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const kopf = { Host: host };
|
|
if (rumpf) { kopf["Content-Type"] = "application/json"; kopf["Content-Length"] = Buffer.byteLength(rumpf); }
|
|
if (keks) kopf.Cookie = keks;
|
|
const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, headers: kopf },
|
|
(antwort) => {
|
|
let text = "";
|
|
antwort.setEncoding("utf8");
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({
|
|
status: antwort.statusCode,
|
|
kopf: antwort.headers,
|
|
text,
|
|
keks: (antwort.headers["set-cookie"] || [])
|
|
.map((z) => z.split(";")[0]).join("; "),
|
|
}));
|
|
});
|
|
a.on("error", schief);
|
|
a.setTimeout(15_000, () => { a.destroy(new Error("Zeitueberschreitung")); });
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const anmelden = (host, rolle, code) =>
|
|
hole("/workspace/api/anmelden", { host, methode: "POST", rumpf: JSON.stringify({ rolle, code }) });
|
|
|
|
/* ---------- Personen anlegen ---------------------------------------- */
|
|
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 schluessel = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", schluessel, jetzt);
|
|
const kennung = (code) => createHmac("sha256", schluessel).update(code).digest("hex");
|
|
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, 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(name, rolle, hash, salt, 32768, kennung(code), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
|
|
anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
anlegen("Schulle", "manager", "CODE-MAN-0001");
|
|
anlegen("Rieke", "hand", "CODE-HAND-0001");
|
|
const idModi = anlegen("Marina", "modi", "CODE-MODI-0001");
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const WS = "workspace.dogfather-universe.com";
|
|
const HAUPT = "dogfather-universe.com";
|
|
const LOKAL = `127.0.0.1:${PORT}`;
|
|
|
|
/* =====================================================================
|
|
TEIL A -- DIE REGELN, OHNE SERVER
|
|
|
|
Sie stehen als eigene Funktionen in crew-adresse.js, damit genau das
|
|
hier moeglich ist: die Entscheidung pruefen, nicht ihre Auswirkung.
|
|
===================================================================== */
|
|
console.log("\n--- Die Regeln selbst ---");
|
|
const { istCrewAdresse, istOhneModiAdresse, istWorkspacePfad, sitzungPasstZurAdresse }
|
|
= await import("./crew-adresse.js");
|
|
|
|
ok(istCrewAdresse(CREW) === true, "crew.dogfather-universe.com ist die Modi-Adresse");
|
|
ok(istCrewAdresse("CREW.Dogfather-Universe.COM") === true, "Gross/klein macht keinen Unterschied");
|
|
ok(istCrewAdresse(CREW + ":443") === true, "ein Port angehaengt macht keinen Unterschied");
|
|
ok(istCrewAdresse(WS) === false, "workspace. ist NICHT die Modi-Adresse");
|
|
ok(istCrewAdresse(HAUPT) === false, "die Hauptseite ist NICHT die Modi-Adresse");
|
|
ok(istCrewAdresse("crew.dogfather-universe.com.boese.de") === false,
|
|
"eine fremde Domain mit dem Namen darin zaehlt nicht");
|
|
ok(istCrewAdresse("") === false && istCrewAdresse(null) === false,
|
|
"leer und fehlend sind nicht die Modi-Adresse");
|
|
|
|
ok(istOhneModiAdresse(WS) === true, "auf workspace. haben Modis nichts verloren");
|
|
ok(istOhneModiAdresse(HAUPT) === true, "auf der Hauptseite ebenfalls nicht");
|
|
ok(istOhneModiAdresse("www." + HAUPT) === true, "auf www. ebenfalls nicht");
|
|
ok(istOhneModiAdresse(CREW) === false, "crew. gehoert NICHT dazu (Falle 1)");
|
|
ok(istOhneModiAdresse(LOKAL) === false, "127.0.0.1 gehoert NICHT dazu (Falle 2)");
|
|
ok(istOhneModiAdresse("localhost:4100") === false, "localhost gehoert NICHT dazu (Falle 2)");
|
|
|
|
/* Die beiden Mengen duerfen sich nirgends beruehren. Ein Muster statt
|
|
einer Liste haette hier sofort angeschlagen. */
|
|
const alleAdressen = [CREW, WS, HAUPT, "www." + HAUPT, LOKAL, "localhost:4100", "git." + HAUPT];
|
|
ok(alleAdressen.every((h) => !(istCrewAdresse(h) && istOhneModiAdresse(h))),
|
|
"keine Adresse liegt in beiden Mengen");
|
|
|
|
ok(istWorkspacePfad("/workspace") === true, "/workspace gehoert dazu");
|
|
ok(istWorkspacePfad("/workspace/start.html") === true, "/workspace/start.html gehoert dazu");
|
|
ok(istWorkspacePfad("/workspaceXYZ") === false, "/workspaceXYZ gehoert NICHT dazu (Falle 3)");
|
|
ok(istWorkspacePfad("/workspace-alt") === false, "/workspace-alt gehoert NICHT dazu (Falle 3)");
|
|
ok(istWorkspacePfad("/assets/img/x.png") === false, "/assets liegt ausserhalb");
|
|
|
|
/* SEIT DEM 10.09.2026 DREI ROLLEN AUF crew. -- Filipe: "3 rollen.
|
|
dogfather. rechte hand und modis."
|
|
|
|
DogFather steht damit auf BEIDEN Adressen. Das ist keine Luecke in
|
|
der Trennung, sondern ihr Sinn: Er ist die einzige Person, die in
|
|
beiden Welten arbeitet. Die rechte Hand und die Modis dagegen gibt
|
|
es nur hier -- und auf workspace. gar nicht. */
|
|
const ROLLEN = ["admin", "spicy", "manager", "scout", "creator", "hand", "modi"];
|
|
const TEAM = new Set(["hand", "modi"]);
|
|
let passtWahr = 0, passtFalsch = 0;
|
|
for (const r of ROLLEN) {
|
|
const aufCrew = sitzungPasstZurAdresse(r, CREW);
|
|
const aufWs = sitzungPasstZurAdresse(r, WS);
|
|
const aufLokal = sitzungPasstZurAdresse(r, LOKAL);
|
|
const darfCrew = TEAM.has(r) || r === "admin";
|
|
ok(aufCrew === darfCrew, `crew.: ${r} ${darfCrew ? "darf" : "darf nicht"}`);
|
|
ok(aufWs === !TEAM.has(r), `workspace.: ${r} ${TEAM.has(r) ? "darf nicht" : "darf"}`);
|
|
ok(aufLokal === true, `127.0.0.1: ${r} unveraendert erlaubt`);
|
|
for (const w of [aufCrew, aufWs, aufLokal]) { if (w) passtWahr++; else passtFalsch++; }
|
|
}
|
|
/* Die Gegenprobe zur Regel selbst: Eine Funktion, die immer `true`
|
|
liefert, haette alle 18 Zeilen darueber ebenfalls teilweise bestanden.
|
|
Beide Antworten muessen vorkommen. */
|
|
ok(passtWahr > 0 && passtFalsch > 0,
|
|
`die Regel unterscheidet wirklich (${passtWahr}x ja, ${passtFalsch}x nein)`);
|
|
|
|
/* =====================================================================
|
|
TEIL B -- AM LAUFENDEN SERVER
|
|
===================================================================== */
|
|
console.log("\n--- Anmeldung je Adresse ---");
|
|
|
|
const modiAufCrew = await anmelden(CREW, "modi", "CODE-MODI-0001");
|
|
ok(modiAufCrew.status === 200, "crew.: der Modi kommt herein");
|
|
let zielModi = {};
|
|
try { zielModi = JSON.parse(modiAufCrew.text); } catch { /* bleibt leer */ }
|
|
ok(zielModi.weiter === "/workspace/start.html", "crew.: er landet auf seiner Startseite");
|
|
ok(modiAufCrew.keks.includes("dfw_sitzung"), "crew.: er bekommt eine Sitzung");
|
|
|
|
/* Der Code des Modis wird bewusst mit der Kachel 'creator' geschickt --
|
|
genau so kommt er auch aus der Zugangswand, denn eine Modi-Kachel
|
|
gibt es nicht. */
|
|
const modiOhneKachel = await anmelden(CREW, "creator", "CODE-MODI-0001");
|
|
ok(modiOhneKachel.status === 200, "crew.: die gewaehlte Kachel ist egal (stiller Zugang)");
|
|
|
|
const managerAufCrew = await anmelden(CREW, "manager", "CODE-MAN-0001");
|
|
ok(managerAufCrew.status === 401, "crew.: ein Manager mit gueltigem Code kommt NICHT herein");
|
|
ok(/ungueltig/.test(managerAufCrew.text),
|
|
"crew.: er bekommt dieselbe Antwort wie bei einem Tippfehler");
|
|
ok(!managerAufCrew.keks.includes("dfw_sitzung"), "crew.: und keine Sitzung");
|
|
|
|
const adminAufCrew = await anmelden(CREW, "admin", "CODE-DOGI-0001");
|
|
ok(adminAufCrew.status === 200 && adminAufCrew.keks.includes("dfw_sitzung"),
|
|
"crew.: DogFather kommt herein -- er arbeitet in beiden Welten");
|
|
|
|
const handAufCrew = await anmelden(CREW, "hand", "CODE-HAND-0001");
|
|
ok(handAufCrew.status === 200 && handAufCrew.keks.includes("dfw_sitzung"),
|
|
"crew.: die rechte Hand kommt herein");
|
|
|
|
/* Und sie kommt NICHT auf die andere Adresse -- genau wie ein Modi,
|
|
und mit genau derselben Antwort wie ein erfundener Code. */
|
|
const handAufWs = await anmelden(WS, "creator", "CODE-HAND-0001");
|
|
const erfunden2 = await anmelden(WS, "creator", "AUCH-DIESEN-NICHT-000");
|
|
ok(handAufWs.status === 401 && handAufWs.text === erfunden2.text,
|
|
`workspace.: die rechte Hand wird abgewiesen wie ein erfundener Code (${handAufWs.status})`);
|
|
ok(!handAufWs.keks.includes("dfw_sitzung"),
|
|
"workspace.: und bekommt dort keine Sitzung");
|
|
|
|
/* Eine Kachel, die es auf crew. nicht gibt, ist dort so gut wie
|
|
erfunden -- auch mit einem gueltigen Code dahinter. */
|
|
const spicyAufCrew = await anmelden(CREW, "spicy", "CODE-DOGI-0001");
|
|
ok(spicyAufCrew.status === 401, "crew.: eine fremde Kachel gibt es dort nicht");
|
|
|
|
/* =====================================================================
|
|
DIE ALTE WAND SCHWEIGT (Entscheidung Filipe, 10.09.2026)
|
|
|
|
Vorher zeigte sie einem Modi den Weg zur neuen Adresse. Seine
|
|
Entscheidung: "gar nichts -- Code stimmt nicht."
|
|
|
|
GEPRUEFT WIRD NICHT DER STATUSCODE, SONDERN DIE UNUNTERSCHEIDBARKEIT.
|
|
Eine 401 waere leicht zu erfuellen und truege trotzdem eine Spur, wenn
|
|
der Rumpf, die Koepfe oder die Dauer anders aussaehen als bei einem
|
|
erfundenen Code. Deshalb wird beides nebeneinandergelegt und
|
|
verglichen -- Zeichen fuer Zeichen. */
|
|
const erfunden = await anmelden(WS, "creator", "DIESEN-CODE-GIBT-ES-NICHT");
|
|
const modiAufWs = await anmelden(WS, "creator", "CODE-MODI-0001");
|
|
ok(modiAufWs.status === erfunden.status && modiAufWs.status === 401,
|
|
`workspace.: der Modi-Code wird abgewiesen wie ein erfundener (${modiAufWs.status})`);
|
|
ok(modiAufWs.text === erfunden.text,
|
|
`workspace.: und die Antwort ist Zeichen fuer Zeichen dieselbe (${modiAufWs.text.slice(0, 40)})`);
|
|
ok(!modiAufWs.keks.includes("dfw_sitzung"), "workspace.: keine Sitzung");
|
|
ok(!/crew\./.test(modiAufWs.text), "workspace.: die neue Adresse wird nicht genannt");
|
|
|
|
/* DER WICHTIGSTE PUNKT DIESER DATEI. Derselbe Dienst bedient beide
|
|
Adressen; ein Denkfehler hier sperrt das ganze Team aus. */
|
|
const managerAufWs = await anmelden(WS, "manager", "CODE-MAN-0001");
|
|
ok(managerAufWs.status === 200 && managerAufWs.keks.includes("dfw_sitzung"),
|
|
"workspace.: der Manager kommt herein wie immer -- der Workspace ist unberuehrt");
|
|
|
|
const managerLokal = await anmelden(LOKAL, "manager", "CODE-MAN-0001");
|
|
const modiLokal = await anmelden(LOKAL, "creator", "CODE-MODI-0001");
|
|
ok(managerLokal.status === 200 && managerLokal.keks.includes("dfw_sitzung"),
|
|
"127.0.0.1: der Manager kommt herein (Pruefadressen unberuehrt)");
|
|
ok(modiLokal.status === 200 && modiLokal.keks.includes("dfw_sitzung"),
|
|
"127.0.0.1: der Modi ebenfalls -- die sieben anderen Modi-Pruefungen messen weiter");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Eine Sitzung gehoert zu genau einer Adresse.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Eine Sitzung, eine Adresse ---");
|
|
const keksModi = modiAufCrew.keks;
|
|
const keksManager = managerAufWs.keks;
|
|
|
|
const modiDaheim = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(modiDaheim.status === 200, "crew.: der angemeldete Modi wird erkannt");
|
|
ok(/Marina/.test(modiDaheim.text), "crew.: und zwar als er selbst");
|
|
|
|
const modiFremd = await hole("/workspace/api/ich", { host: WS, keks: keksModi });
|
|
ok(modiFremd.status === 401, "workspace.: dieselbe Sitzung zaehlt dort nicht");
|
|
|
|
const managerDaheim = await hole("/workspace/api/ich", { host: WS, keks: keksManager });
|
|
ok(managerDaheim.status === 200, "workspace.: der Manager wird erkannt");
|
|
|
|
const managerFremd = await hole("/workspace/api/ich", { host: CREW, keks: keksManager });
|
|
ok(managerFremd.status === 401, "crew.: seine Sitzung zaehlt dort nicht");
|
|
|
|
/* Auch die Seitenschranke, nicht nur die Schnittstelle: Eine Seite, die
|
|
ausgeliefert wird, waere ein Loch, selbst wenn die Daten dahinter
|
|
leer blieben. */
|
|
const seiteFremd = await hole("/workspace/start.html", { host: CREW, keks: keksManager });
|
|
ok(seiteFremd.status === 302, "crew.: die Startseite schickt den Manager zur Zugangswand");
|
|
const seiteDaheim = await hole("/workspace/start.html", { host: CREW, keks: keksModi });
|
|
ok(seiteDaheim.status === 200, "crew.: dem Modi liefert sie die Seite");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die Weiche: was es auf crew. ueberhaupt gibt.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Die Weiche ---");
|
|
const wurzel = await hole("/", { host: CREW });
|
|
ok(wurzel.status === 302 && wurzel.kopf.location === "/workspace/",
|
|
"crew.: die Hauptseite gibt es dort nicht, es geht zur Zugangswand");
|
|
const wurzelWs = await hole("/", { host: HAUPT });
|
|
ok(wurzelWs.status === 200, "die Hauptseite selbst ist unveraendert erreichbar");
|
|
|
|
const fastWorkspace = await hole("/workspaceXYZ", { host: CREW });
|
|
ok(fastWorkspace.status === 302, "crew.: /workspaceXYZ gehoert nicht dazu (Falle 3)");
|
|
|
|
const robots = await hole("/workspace/", { host: CREW });
|
|
ok(String(robots.kopf["x-robots-tag"] || "").includes("noindex"),
|
|
"crew.: nicht indizieren -- die Adresse soll in keiner Suchmaschine stehen");
|
|
const robotsWs = await hole("/workspace/", { host: WS });
|
|
ok(!robotsWs.kopf["x-robots-tag"],
|
|
"workspace.: dort steht der Kopf NICHT (Gegenprobe zur Weiche)");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die App heisst anders.
|
|
------------------------------------------------------------------ */
|
|
console.log("\n--- Zwei Apps, zwei Namen ---");
|
|
const manifestCrew = await hole("/workspace/app.webmanifest", { host: CREW });
|
|
const manifestWs = await hole("/workspace/app.webmanifest", { host: WS });
|
|
let mc = {}, mw = {};
|
|
try { mc = JSON.parse(manifestCrew.text); } catch { /* bleibt leer */ }
|
|
try { mw = JSON.parse(manifestWs.text); } catch { /* bleibt leer */ }
|
|
ok(manifestCrew.status === 200 && manifestWs.status === 200,
|
|
"beide Adressen liefern ein Manifest");
|
|
/* DER NAME GILT SEIT DEM 11.09.2026 FUER BEIDE (Entscheidung Filipe).
|
|
|
|
Er hiess "Team Dogi". Seit der Treff hier wohnt, wuerde ein Mitglied
|
|
der Community diesen Namen auf seinem Startbildschirm haben -- den
|
|
Namen eines Bereichs, zu dem es nicht gehoert. Das Manifest gilt je
|
|
ADRESSE und kann sich nicht nach der Rolle richten; also einer, der
|
|
fuer beide stimmt.
|
|
|
|
GEPRUEFT WIRD NICHT NUR DER NAME, SONDERN DER UNTERSCHIED. Ein
|
|
fester Name allein waere beim naechsten Umbenennen wieder rot, ohne
|
|
dass etwas kaputt ist. Dass die beiden Adressen sich UNTERSCHEIDEN,
|
|
ist die Eigenschaft, um die es geht -- sonst waere es eine App. */
|
|
ok(mc.name === "DogFather Universe",
|
|
`crew.: die App heisst "DogFather Universe" (gelesen: ${mc.name || "?"})`);
|
|
ok(mc.name && mw.name && mc.name !== mw.name,
|
|
`und anders als auf der Agenturadresse ("${mw.name}")`);
|
|
ok(mw.name === "Creator Workspace — Spicy & Dogi",
|
|
`workspace.: dort heisst sie unveraendert (gelesen: ${mw.name || "?"})`);
|
|
ok(Array.isArray(mc.icons) && mc.icons.length > 0
|
|
&& mc.icons.every((s) => /crew-/.test(s.src)),
|
|
`crew.: alle ${(mc.icons || []).length} Symbole sind die eigenen`);
|
|
ok(Array.isArray(mw.icons) && mw.icons.length > 0
|
|
&& mw.icons.every((s) => /workspace-/.test(s.src)),
|
|
"workspace.: dort weiterhin die alten");
|
|
ok(mc.start_url === "/workspace/start.html" && mc.scope === "/workspace/",
|
|
"crew.: Bereich und Startseite stimmen");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
UND DAS SYMBOL AUF DEM IPHONE (22.09.2026)
|
|
---------------------------------------------------------------------
|
|
Die Zeilen darueber pruefen das MANIFEST -- daraus nimmt Android sein
|
|
Symbol. iOS Safari nimmt es von dort NICHT, sondern aus
|
|
`<link rel="apple-touch-icon">`, und die Zeile steht in jeder HTML
|
|
auf workspace-180.png.
|
|
|
|
Bis zum 22.09.2026 war das Manifest deshalb richtig und der
|
|
Startbildschirm eines Modis trotzdem falsch -- aber nur auf dem
|
|
iPhone. Genau die Sorte Fehler, die beim Ansehen nie auffaellt: Man
|
|
braeuchte beide Geraete nebeneinander.
|
|
|
|
Gemessen wird der AUSGELIEFERTE INHALT, nicht der Pfad. Eine
|
|
Umleitung, die auf eine Datei zeigt, die es nicht gibt, waere sonst
|
|
gruen. */
|
|
console.log("\n--- Das Symbol auf dem Startbildschirm ---");
|
|
{
|
|
const pfad = "/assets/img/app-symbole/workspace-180.png";
|
|
const aufCrew = await hole(pfad, { host: CREW });
|
|
const aufWs = await hole(pfad, { host: WS });
|
|
const echtCrew = await hole("/assets/img/app-symbole/crew-180.png", { host: CREW });
|
|
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Waeren beide Antworten leer,
|
|
waeren sie auch gleich -- und der Vergleich unten trivial gruen. */
|
|
ok(aufCrew.status === 200 && aufWs.status === 200 && echtCrew.status === 200
|
|
&& aufCrew.text.length > 1000,
|
|
`alle drei Symbole werden ausgeliefert (${aufCrew.text.length} Byte)`);
|
|
ok(aufCrew.text === echtCrew.text,
|
|
"crew.: hinter apple-touch-icon steckt wirklich das Crew-Zeichen");
|
|
/* GEGENPROBE. Ohne sie wuesste niemand, ob die Umleitung ueberhaupt
|
|
etwas tut -- waeren beide Dateien identisch, waere die Zeile
|
|
darueber auch ohne jede Umleitung gruen. */
|
|
ok(aufCrew.text !== aufWs.text,
|
|
"workspace.: dort unveraendert das Agenturzeichen — die Umleitung greift also");
|
|
|
|
/* UND SIE GREIFT NUR, WO ES EINE CREW-FASSUNG GIBT. workspace-1024.png
|
|
ist das TikTok-Symbol und hat keine; wuerde es mit umgebogen,
|
|
liefe die Anfrage auf eine Datei, die es nicht gibt. */
|
|
const tiktok = await hole("/assets/img/app-symbole/workspace-1024.png", { host: CREW });
|
|
ok(tiktok.status === 200 && tiktok.text.length > 1000,
|
|
`und Groessen ohne Crew-Fassung bleiben unangetastet (1024: ${tiktok.status})`);
|
|
}
|
|
|
|
/* Die Symboldateien muessen es auch wirklich geben -- ein Manifest, das
|
|
auf nichts zeigt, faellt sonst erst auf dem Startbildschirm auf. */
|
|
let symboleDa = 0;
|
|
for (const s of mc.icons || []) {
|
|
const pfad = String(s.src).split("?")[0];
|
|
const a = await hole(pfad, { host: CREW });
|
|
if (a.status === 200) symboleDa++;
|
|
}
|
|
ok((mc.icons || []).length > 0 && symboleDa === (mc.icons || []).length,
|
|
`crew.: alle ${symboleDa} Symbole werden auch ausgeliefert`);
|
|
|
|
/* =====================================================================
|
|
DIE ZUGANGSWAND
|
|
|
|
Filipe am 10.09.2026: "alles soll auf dogfather und die modis
|
|
perfektionniert werden auf der neuen modi seite." Bis dahin stand auf
|
|
crew. die gewohnte Wand -- Spicy-Media-Logo, "Creator Workspace",
|
|
fuenf Rollenkacheln. Fuer einen Modi war davon nichts richtig.
|
|
|
|
Zwei Dinge werden hier gemessen, und das zweite ist das wichtigere:
|
|
dass die ALTE Wand unangetastet blieb.
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Zwei Waende ---");
|
|
|
|
const wandCrew = await hole("/workspace/", { host: CREW });
|
|
const wandWs = await hole("/workspace/", { host: WS });
|
|
|
|
ok(wandCrew.status === 200, "crew.: die Zugangswand kommt (keine Weiterleitungsschleife)");
|
|
ok(wandWs.status === 200, "workspace.: die Zugangswand kommt");
|
|
|
|
ok(/Team Dogi|Team Dogi/.test(wandCrew.text), "crew.: dort steht Team Dogi");
|
|
ok(/DogFather/.test(wandCrew.text), "crew.: und DogFather daneben");
|
|
ok(!/Spicy/i.test(wandCrew.text), "crew.: von Spicy Media steht dort nichts mehr");
|
|
ok(!/Creator/i.test(wandCrew.text), "crew.: und vom Creator Workspace auch nicht");
|
|
/* DER SATZ AUF DER WAND MUSS FUER BEIDE STIMMEN (11.09.2026).
|
|
|
|
Hier stand `/die Modis/` -- und der Satz lautete "Privater Bereich
|
|
fuer DogFather und die Modis". Seit die Community durch dieselbe Wand
|
|
kommt, liest sie ihn auch, und fuer sie ist er schlicht falsch.
|
|
|
|
UND DIE ALTE PRUEFUNG WAERE TROTZDEM GRUEN GEBLIEBEN: Sie durchsuchte
|
|
den QUELLTEXT, und dort steht "die Modis" weiterhin in einem
|
|
Kommentar. Sie haette also bestaetigt, dass ein Satz dasteht, den es
|
|
auf dem Bildschirm gar nicht mehr gibt. Gemessen wird deshalb die
|
|
sichtbare Unterzeile, nicht die Datei. */
|
|
{
|
|
const unter = /<p class="unterzeile">([^<]*)<\/p>/.exec(wandCrew.text)?.[1] || "";
|
|
/* Sucht seit dem 22.09.2026 nach "Rudel": Der Treff heisst so.
|
|
Ein regulaerer Ausdruck ist keine Zeichenkette -- die
|
|
Umbenennung ist an ihm vorbeigelaufen, und er haette ab sofort
|
|
nach einem Wort gesucht, das die Seite nicht mehr sagt. */
|
|
ok(/Team\s*Dogi/.test(unter) && /Rudel/.test(unter),
|
|
`crew.: die Unterzeile nennt Team Dogi und das Rudel ("${unter.trim()}")`);
|
|
ok(!/Arbeitsbereich/.test(unter),
|
|
"und behauptet nicht mehr, das hier sei nur zum Arbeiten");
|
|
}
|
|
|
|
/* DIE KACHELREIHE HAT SEIT DEM 21.09.2026 FUENF EINTRAEGE.
|
|
|
|
Die Geschichte dieser Zeile ist lehrreich und steht deshalb hier:
|
|
Erst gab es keine Kachelreihe (eine Sorte Zugang, eine Reihe waere
|
|
eine Huerde gewesen). Dann drei ("3 rollen. dogfather. rechte hand
|
|
und modis"). Dann vier, weil der Treff hier eingezogen ist. Jetzt
|
|
fuenf -- die linke Hand ist dazugekommen.
|
|
|
|
GEPRUEFT WIRD NICHT DIE ZAHL, SONDERN WELCHE. Eine Zahl war schon
|
|
zweimal rot, ohne dass etwas kaputt war. Die NAMEN dagegen sagen
|
|
etwas: Steht dort eines Tages "manager", ist das ein Befund und kein
|
|
Umbau.
|
|
|
|
NACHGETRAGEN AM 22.09.2026, und der Rueckstand gehoert hierher:
|
|
Die Karte "Linke Hand" kam am 21.09. auf die Wand (Commit 21793b6),
|
|
diese Erwartung blieb bei vier. Die Pruefung war seitdem rot -- an
|
|
einer Stelle, an der NICHTS kaputt war. Eine rote Pruefung, die rot
|
|
bleibt, gewoehnt einem das Hinsehen ab; die Zeile darunter ("so
|
|
viele, wie es Rollen auf der Wand gibt") war die ganze Zeit gruen
|
|
und hat den Fehler damit sogar verdeckt.
|
|
|
|
Warum die Liste trotzdem stehen bleibt und nicht abgeleitet wird:
|
|
Die REIHENFOLGE ist eine Entscheidung (DogFather, rechte Hand, linke
|
|
Hand, Modis, Gaeste) und steht sonst nirgends. Abzuleiten hiesse nur,
|
|
die Liste woanders hinzuschreiben. Die Zahl daneben ist die
|
|
abgeleitete Haelfte -- zwei Fragen, zwei Zeilen. */
|
|
const rollenAufWand = [...wandCrew.text.matchAll(/data-rolle="([a-z]+)"/g)].map((m) => m[1]);
|
|
const ERWARTETE_REIHE = ["admin", "hand", "linke", "modi", "gast"];
|
|
ok(JSON.stringify(rollenAufWand) === JSON.stringify(ERWARTETE_REIHE),
|
|
`crew.: die ${ERWARTETE_REIHE.length} Kacheln in dieser Reihenfolge `
|
|
+ `(${rollenAufWand.join(", ")})`);
|
|
const kachelnCrew = (wandCrew.text.match(/class="rolle"/g) || []).length;
|
|
const kachelnWs = (wandWs.text.match(/class="rolle"/g) || []).length;
|
|
ok(kachelnCrew === rollenAufWand.length,
|
|
`crew.: ${kachelnCrew} Rollenkacheln -- so viele, wie es Rollen auf der Wand gibt`);
|
|
for (const [rolle, name] of [["admin", "DogFather"], ["hand", "Rechte Hand"], ["modi", "Modi"]]) {
|
|
ok(wandCrew.text.includes(`data-rolle="${rolle}"`) && wandCrew.text.includes(`>${name}<`),
|
|
`crew.: die Kachel "${name}" steht da`);
|
|
}
|
|
/* WELCHES ZEICHEN AUF WELCHER KACHEL
|
|
---------------------------------------------------------------------
|
|
HIER STAND BIS ZUM 22.09.2026 DAS GEGENTEIL, und beide Fassungen
|
|
geben eine Anweisung von Filipe wieder:
|
|
|
|
10.09.: "bei rechte hand soll der husky sein und bei modis soll
|
|
die pfote sein" -> DogFather und rechte Hand gleich.
|
|
22.09.: "bei dogi soll kein husky sein sondern eine krone. die
|
|
auch extrem auffaellt damit man den unterschied extrem
|
|
erkennt." -> DogFather eigenes Zeichen.
|
|
|
|
Dazwischen ist die LINKE Hand dazugekommen. Damit trugen DREI von
|
|
fuenf Kacheln denselben Husky -- und drei gleiche Zeichen
|
|
untereinander sind kein Zeichen mehr, sondern Tapete.
|
|
|
|
WAS JETZT GEPRUEFT WIRD, ist deshalb nicht mehr eine einzelne
|
|
Zuordnung, sondern die Eigenschaft, um die es geht: Jede Kachel
|
|
traegt ein Zeichen, und das von DogFather kommt kein zweites Mal
|
|
vor. Das haelt auch, wenn die Zeichen neu gezeichnet werden oder
|
|
eine sechste Rolle dazukommt -- eine abgeschriebene Paarliste
|
|
haette beim naechsten Umbau wieder danebengelegen.
|
|
|
|
Die zwei Haende teilen sich weiterhin eines, und das ist Absicht:
|
|
Sie haben denselben Untertitel ("vertritt und koordiniert") und
|
|
lesen sich als Paar. Geprueft wird deshalb nicht "alle
|
|
verschieden", sondern "DogFather ist einmalig". */
|
|
{
|
|
const zeichenVon = (rolle) => {
|
|
const stelle = wandCrew.text.indexOf(`data-rolle="${rolle}"`);
|
|
if (stelle < 0) return null;
|
|
return (/href="(#r-[a-z-]+)"/.exec(wandCrew.text.slice(stelle, stelle + 400)) || [])[1] || null;
|
|
};
|
|
const zeichen = Object.fromEntries(ERWARTETE_REIHE.map((r) => [r, zeichenVon(r)]));
|
|
const ohne = ERWARTETE_REIHE.filter((r) => !zeichen[r]);
|
|
ok(ohne.length === 0 && ERWARTETE_REIHE.length > 0,
|
|
ohne.length ? `crew.: ohne Zeichen: ${ohne.join(", ")}`
|
|
: `crew.: jede der ${ERWARTETE_REIHE.length} Kacheln traegt ein Zeichen`);
|
|
|
|
const zAdmin = zeichen.admin;
|
|
const wieOft = Object.values(zeichen).filter((z) => z === zAdmin).length;
|
|
ok(!!zAdmin && wieOft === 1,
|
|
`crew.: DogFathers Zeichen (${zAdmin}) kommt genau einmal vor (${wieOft})`);
|
|
ok(zAdmin === "#r-krone", `crew.: und es ist die Krone (${zAdmin})`);
|
|
|
|
/* Das Zeichen muss es auch GEBEN. Ein `use` auf eine Kennung, die
|
|
nirgends steht, zeichnet nichts -- und zwar lautlos: keine
|
|
Fehlermeldung, kein leeres Kaestchen, einfach nichts. Genau so
|
|
saehe eine vertippte Kennung aus. */
|
|
ok(wandCrew.text.includes('id="r-krone"'),
|
|
"crew.: und die Krone ist auf der Seite auch definiert");
|
|
|
|
/* GEGENPROBE: Die Messung muss einen Verstoss auch sehen koennen.
|
|
Zwei erfundene Verteilungen, eine richtig, eine mit doppeltem
|
|
DogFather-Zeichen. */
|
|
const zaehl = (v) => Object.values(v).filter((z) => z === v.admin).length;
|
|
ok(zaehl({ admin: "#r-krone", hand: "#r-husky", modi: "#r-pfote" }) === 1
|
|
&& zaehl({ admin: "#r-husky", hand: "#r-husky", modi: "#r-pfote" }) === 2,
|
|
"crew.: Gegenprobe -- ein doppelt vergebenes Zeichen wuerde auffallen");
|
|
|
|
const zModi = zeichen.modi;
|
|
ok(!!zModi && zModi !== zAdmin, `crew.: der Modi ein eigenes (${zModi})`);
|
|
ok(zeichen.hand === zeichen.linke && !!zeichen.hand,
|
|
`crew.: die zwei Haende lesen sich als Paar (${zeichen.hand} / ${zeichen.linke})`);
|
|
}
|
|
|
|
/* Die Namen kommen aus den Kacheln, nicht aus einer Liste in gate.js --
|
|
sonst stuenden sie in einer Datei, die jeder bekommt. */
|
|
ok(!/Rechte Hand/.test(wandWs.text), "workspace.: dort steht die dritte Rolle nicht");
|
|
ok(kachelnWs === 5, `workspace.: dort weiterhin fuenf (gezaehlt: ${kachelnWs})`);
|
|
ok(/SpicyMedia/.test(wandWs.text), "workspace.: die alte Wand ist unveraendert");
|
|
|
|
/* Auch der ausgeschriebene Weg. Ihn zu vergessen waere nicht
|
|
aufgefallen -- im Alltag tippt ihn niemand. */
|
|
const wandCrewLang = await hole("/workspace/index.html", { host: CREW });
|
|
ok(wandCrewLang.status === 200 && /Team Dogi|Team Dogi/.test(wandCrewLang.text),
|
|
"crew.: auch /workspace/index.html fuehrt zur neuen Wand");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
DIE ZWEITE WAND GIBT ES NUR AUF crew.
|
|
|
|
Sie liegt als Datei im selben Ordner wie alles andere. Ohne Sperre
|
|
koennte sie jeder unter ihrem Dateinamen aufrufen -- auch auf
|
|
workspace. -- und saehe dort eine Anmeldeseite, die es dort nicht
|
|
geben soll.
|
|
------------------------------------------------------------------ */
|
|
/* ZWEI NUMMERN, EINE AUSSAGE. Auf workspace. antwortet die Sperre mit
|
|
404 "gibt es nicht". Auf der Hauptseite und www. kommt schon der
|
|
Umzugs-Zwischenschritt vom 06.09.2026 davor und beantwortet den
|
|
GANZEN Pfad /workspace mit 410 "gab es, ist absichtlich weg".
|
|
|
|
Erwartet werden deshalb beide -- was zaehlt, ist: kein Inhalt und
|
|
keine 200. Auf eine der zwei Nummern zu bestehen hiesse, eine
|
|
fremde, aeltere Entscheidung nachtraeglich zu einem Fehler zu
|
|
erklaeren. */
|
|
for (const [name, host] of [["workspace.", WS], ["die Hauptseite", HAUPT], ["www.", "www." + HAUPT]]) {
|
|
const a = await hole("/workspace/crew-index.html", { host });
|
|
ok(a.status === 404 || a.status === 410,
|
|
`${name}: die zweite Wand gibt es dort nicht (${a.status})`);
|
|
ok(!/Team Dogi/.test(a.text), `${name}: und ihr Inhalt kommt auch nicht durch`);
|
|
}
|
|
/* Doppelte Schraegstriche haben diese Schranke im Haus schon einmal
|
|
umgangen (04.09.2026, //workspace/start.html kam mit 200). */
|
|
const schraeg = await hole("/workspace//crew-index.html", { host: WS });
|
|
ok(schraeg.status === 404 || !/Team Dogi/.test(schraeg.text),
|
|
`workspace.: auch mit doppeltem Schraegstrich nicht (${schraeg.status})`);
|
|
|
|
/* GEGENPROBE: Auf crew. und auf den Pruefadressen bleibt sie erreichbar
|
|
-- sonst haette ich sie fuer alle gesperrt, auch fuer die Pruefung,
|
|
die sie im Browser ansieht. Die waere dann gruen und wertlos. */
|
|
const dortSchon = await hole("/workspace/crew-index.html", { host: CREW });
|
|
ok(dortSchon.status === 200, "crew.: dort gibt es sie natuerlich");
|
|
const lokalSchon = await hole("/workspace/crew-index.html", { host: LOKAL });
|
|
ok(lokalSchon.status === 200, "127.0.0.1: und auf den Pruefadressen auch");
|
|
|
|
/* ---------------------------------------------------------------------
|
|
JEDER EINTRAG DER TAFEL, NICHT NUR DIE WAND (21.09.2026)
|
|
|
|
Der Block darueber prueft crew-index.html namentlich. Am 21.09. kam
|
|
ein zweiter Eintrag dazu -- crew-haus.css, das ganze Farbhaus von
|
|
Team Dogi. Es stand bis dahin NICHT unter der Sperre: 27 806 Byte,
|
|
HTTP 200, von der Agenturadresse, ohne jede Anmeldung. Im Kopf der
|
|
Datei stand seit dem 10.09. das Gegenteil.
|
|
|
|
WAERE DIESER BLOCK EINE ZWEITE NAMENTLICHE ABSCHRIFT, stuende beim
|
|
dritten Eintrag dasselbe Loch wieder offen. Er geht deshalb die
|
|
Tafel durch -- dieselbe Liste, die der Server durchsetzt. Ein neuer
|
|
Eintrag wird damit automatisch in beide Richtungen gemessen, ohne
|
|
dass jemand hier etwas nachtraegt.
|
|
|
|
UND IN BEIDE RICHTUNGEN heisst: nicht nur "draussen gesperrt",
|
|
sondern auch "drinnen erreichbar". Eine Sperre, die zu viel sperrt,
|
|
nimmt der zweiten Adresse ihr Aussehen -- und das faellt erst auf,
|
|
wenn jemand hinsieht.
|
|
------------------------------------------------------------------ */
|
|
const { GEHOERT_ZU_ADRESSE } = await import("./crew-adresse.js");
|
|
ok(GEHOERT_ZU_ADRESSE.length >= 2,
|
|
`die Tafel hat ${GEHOERT_ZU_ADRESSE.length} Eintraege`);
|
|
|
|
for (const eintrag of GEHOERT_ZU_ADRESSE) {
|
|
const weg = eintrag.datei;
|
|
const draussen = await hole(weg, { host: WS });
|
|
ok(draussen.status === 404 || draussen.status === 410,
|
|
`workspace.: ${weg} gibt es dort nicht (${draussen.status})`);
|
|
|
|
/* DIE ZAHL ALLEIN REICHT NICHT. Ein 404 mit Inhalt waere kein
|
|
Schutz; gemessen wird deshalb auch, dass nichts Nennenswertes
|
|
mitkommt. */
|
|
ok((draussen.text || "").length < 200,
|
|
`workspace.: und es kommt auch kein Inhalt mit (${(draussen.text || "").length} Zeichen)`);
|
|
|
|
const drinnen = await hole(weg, { host: CREW });
|
|
ok(drinnen.status === 200,
|
|
`crew.: ${weg} kommt dort an (${drinnen.status})`);
|
|
ok((drinnen.text || "").length > 200,
|
|
`crew.: und zwar mit Inhalt (${(drinnen.text || "").length} Zeichen)`);
|
|
}
|
|
|
|
/* DIE UMBIEGUNG DARF DIE SPERRE NICHT MITNEHMEN.
|
|
|
|
`haus.css` wird auf crew. intern auf crew-haus.css umgebogen. Die
|
|
Tafel wird ganz oben in crewWeiche() gefragt, die Umbiegung passiert
|
|
weiter unten -- deshalb geht das gut. Stuende die Tafel eines Tages
|
|
NACH der Umbiegung, waere auf crew. jede Seite ohne ihr Farbhaus, und
|
|
niemand saehe eine Fehlermeldung: Eine Seite ohne Stilvorlage laedt,
|
|
sie sieht nur falsch aus. */
|
|
const umgebogen = await hole("/workspace/assets/css/haus.css", { host: CREW });
|
|
ok(umgebogen.status === 200 && (umgebogen.text || "").length > 200,
|
|
`crew.: haus.css wird weiterhin umgebogen (${umgebogen.status}, `
|
|
+ `${(umgebogen.text || "").length} Zeichen)`);
|
|
const leer = await hole("/workspace/assets/css/haus.css", { host: WS });
|
|
ok(leer.status === 200,
|
|
`workspace.: haus.css kommt dort weiterhin an (${leer.status})`);
|
|
ok((leer.text || "").length < (umgebogen.text || "").length,
|
|
`und ist dort die kleinere, leere Fassung `
|
|
+ `(${(leer.text || "").length} gegen ${(umgebogen.text || "").length} Zeichen)`);
|
|
|
|
/* ---------------------------------------------------------------------
|
|
WAS gate.js AUS DEM HTML BRAUCHT -- auf BEIDEN Seiten.
|
|
|
|
Es gibt jetzt zwei Wanddateien und ein gemeinsames Skript. Wer nur
|
|
eine anfasst, macht die andere kaputt, und zwar lautlos: Ein
|
|
fehlendes Element ist im Browser ein `null`, der Fehler steht in der
|
|
Konsole und sonst nirgends. Deshalb wird beides gegen dieselbe Liste
|
|
gehalten.
|
|
------------------------------------------------------------------ */
|
|
const GEBRAUCHT = ["formular", "code", "auge", "fehler", "hinweis", "knopf", "fuer"];
|
|
for (const [name, wand] of [["crew.", wandCrew], ["workspace.", wandWs]]) {
|
|
const fehlend = GEBRAUCHT.filter((id) => !wand.text.includes(`id="${id}"`));
|
|
ok(fehlend.length === 0,
|
|
`${name} die Wand hat alle ${GEBRAUCHT.length} Elemente, die gate.js sucht`
|
|
+ (fehlend.length ? ` -- es fehlen: ${fehlend.join(", ")}` : ""));
|
|
ok(wand.text.includes("knopf__text") && wand.text.includes("buehne__bild"),
|
|
`${name} auch die beiden Klassen, die gate.js anspricht`);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------
|
|
Die Buehne.
|
|
------------------------------------------------------------------ */
|
|
ok(/crew-gate-\d+\.webp/.test(wandCrew.text), "crew.: die eigene Buehne ist eingebunden");
|
|
ok(!/buehne\/gate-\d+\.webp/.test(wandCrew.text), "crew.: die alte Buehne nicht mehr");
|
|
ok(/buehne\/gate-\d+\.webp/.test(wandWs.text), "workspace.: dort weiterhin die alte");
|
|
ok(/crew-32\.png/.test(wandCrew.text) && /crew-180\.png/.test(wandCrew.text),
|
|
"crew.: eigenes Zeichen im Reiter und auf dem Startbildschirm");
|
|
|
|
/* Ein Verweis beweist nicht, dass die Datei da ist. */
|
|
const buehnen = [...new Set(wandCrew.text.match(/assets\/img\/buehne\/crew-gate-[a-z0-9-]+\.webp/g) || [])];
|
|
let buehnenDa = 0;
|
|
for (const b of buehnen) {
|
|
const a = await hole("/workspace/" + b, { host: CREW });
|
|
if (a.status === 200) buehnenDa++;
|
|
}
|
|
ok(buehnen.length >= 8 && buehnenDa === buehnen.length,
|
|
`crew.: alle ${buehnenDa} von ${buehnen.length} Buehnenbildern werden ausgeliefert`);
|
|
|
|
/* =====================================================================
|
|
DIE BUEHNEN INNEN (10.09.2026)
|
|
|
|
Filipe: "auch die hintergrund bilder vom crew espace sollen anderes
|
|
aussehen, der community angepasst wie die zugangscode seite".
|
|
|
|
DIE SZENENNAMEN WERDEN AUS start.css GELESEN, nicht hier
|
|
abgeschrieben. Eine Liste an zwei Stellen ist eine Liste, die
|
|
auseinanderlaeuft -- und dann faellt eine neue Szene ohne eigenes
|
|
Motiv niemandem auf: Auf der Modi-Seite stuende ploetzlich wieder
|
|
ein fremdes Logo im Hintergrund, und zwar genau auf der Seite, an
|
|
die niemand gedacht hat.
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Die Buehnen innen ---");
|
|
{
|
|
const { readFileSync } = await import("node:fs");
|
|
const { dirname, join } = await import("node:path");
|
|
const { fileURLToPath } = await import("node:url");
|
|
const css = readFileSync(
|
|
join(dirname(fileURLToPath(import.meta.url)), "..", "workspace", "assets", "css", "start.css"),
|
|
"utf8");
|
|
const namen = [...new Set((css.match(/buehne-[a-z]+-(?:uhd|breit|schmal)\.webp/g) || []))];
|
|
|
|
/* Die Zahl gehoert in die Bedingung: Faende das Muster nichts --
|
|
umbenannte Dateien, anderer Ordner --, waere alles darunter gruen,
|
|
ohne dass ein einziges Bild angesehen wurde. */
|
|
ok(namen.length >= 5, `${namen.length} Buehnenbilder in start.css gefunden`);
|
|
|
|
let eigene = 0, alte = 0;
|
|
const fehlend = [];
|
|
for (const name of namen) {
|
|
const aufCrew = await hole(`/workspace/assets/img/${name}`, { host: CREW });
|
|
const aufWs = await hole(`/workspace/assets/img/${name}`, { host: WS });
|
|
if (aufCrew.status === 200) eigene++; else fehlend.push(name);
|
|
if (aufWs.status === 200) alte++;
|
|
}
|
|
ok(eigene === namen.length,
|
|
`crew.: fuer alle ${namen.length} gibt es ein eigenes Motiv`
|
|
+ (fehlend.length ? ` -- es fehlt: ${fehlend.slice(0, 3).join(", ")}` : ""));
|
|
ok(alte === namen.length, `workspace.: dort liegen unveraendert die alten (${alte})`);
|
|
|
|
/* DAS ZEICHEN IN DER KOPFLEISTE. Es kommt aus dem CSS als
|
|
Hintergrundbild -- auf der Modi-Adresse stand dort die Chili von
|
|
Spicy Media, auf jeder Seite. */
|
|
const zeichenCrew = await hole("/workspace/assets/img/marke-spicy.webp", { host: CREW });
|
|
const zeichenWs = await hole("/workspace/assets/img/marke-spicy.webp", { host: WS });
|
|
const husky = await hole("/workspace/assets/img/marke-husky.webp", { host: WS });
|
|
ok(zeichenCrew.status === 200 && husky.status === 200
|
|
&& zeichenCrew.text === husky.text,
|
|
"crew.: oben links kommt der Husky, nicht die Chili");
|
|
ok(zeichenWs.status === 200 && zeichenWs.text !== husky.text,
|
|
"workspace.: dort bleibt es die Chili (Gegenprobe)");
|
|
|
|
/* UND SIE MUESSEN VERSCHIEDEN SEIN. Beide Adressen antworten mit 200
|
|
-- das sagt noch nicht, dass es zwei verschiedene Bilder sind. Ohne
|
|
diese Zeile waere die Pruefung auch dann gruen, wenn die Weiche gar
|
|
nicht greift. */
|
|
const probe = namen.find((n) => n.endsWith("-breit.webp")) || namen[0];
|
|
const a1 = await hole(`/workspace/assets/img/${probe}`, { host: CREW });
|
|
const a2 = await hole(`/workspace/assets/img/${probe}`, { host: WS });
|
|
ok(a1.text.length > 0 && a2.text.length > 0 && a1.text !== a2.text,
|
|
`und es sind zwei verschiedene Bilder (${probe}: `
|
|
+ `${Math.round(a1.text.length / 1024)} KB gegen ${Math.round(a2.text.length / 1024)} KB)`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
DAS HAUS (10.09.2026)
|
|
|
|
Filipe: "ich will doch dass der eingang hier getrennt ist von der
|
|
workspace seite … aber es soll trotzdem so bleiben dass ich die
|
|
daten hier und da sehe."
|
|
|
|
Getrennt wird das AUSSEHEN, nicht der Bestand. Jede Seite laedt als
|
|
letzte Stilvorlage `haus.css`; auf der Agenturadresse ist die leer,
|
|
auf crew. biegt die Weiche sie auf `crew-haus.css` um.
|
|
|
|
GEPRUEFT WIRD NICHT NUR "beide antworten mit 200". Zwei gueltige
|
|
Antworten sagen nichts darueber, ob es zwei VERSCHIEDENE Dateien
|
|
sind -- genau daran waere die Pruefung der Buehnen weiter oben schon
|
|
einmal fast vorbeigelaufen. Deshalb wird der Inhalt verglichen, und
|
|
zusaetzlich der Griff `--haus-name` gelesen: Er unterscheidet "die
|
|
leere Fassung ist angekommen" von "gar nichts ist angekommen".
|
|
===================================================================== */
|
|
console.log("");
|
|
console.log("--- Welches Haus kommt an? ---");
|
|
{
|
|
const hausCrew = await hole("/workspace/assets/css/haus.css", { host: CREW });
|
|
const hausWs = await hole("/workspace/assets/css/haus.css", { host: WS });
|
|
ok(hausCrew.status === 200 && hausWs.status === 200,
|
|
`beide Adressen liefern ein Haus (${hausCrew.status}/${hausWs.status})`);
|
|
ok(hausCrew.text !== hausWs.text,
|
|
`und es sind zwei verschiedene (${hausCrew.text.length} gegen ${hausWs.text.length} Zeichen)`);
|
|
ok(/--haus-name:\s*"crew"/.test(hausCrew.text),
|
|
"auf crew. steht das Haus von Team Dogi darin");
|
|
ok(/--haus-name:\s*"workspace"/.test(hausWs.text),
|
|
"auf workspace. das der Agentur (Gegenprobe)");
|
|
|
|
/* DIE FASSUNGSNUMMER MUSS MITREISEN. Jede Seite ruft die Datei mit
|
|
`?v=…` auf. Wer beim Umbiegen nur den Pfad setzt, wirft sie weg --
|
|
und der Browser behaelt die Datei dann laenger, als er soll.
|
|
Aufgefallen waere das erst beim naechsten Farbwechsel, Wochen
|
|
spaeter, bei einem einzelnen Menschen. */
|
|
const mitNummer = await hole("/workspace/assets/css/haus.css?v=202609102225", { host: CREW });
|
|
ok(mitNummer.status === 200 && /--haus-name:\s*"crew"/.test(mitNummer.text),
|
|
`mit Fassungsnummer kommt dasselbe an (${mitNummer.status})`);
|
|
|
|
/* HIER STAND BIS ZUM 21.09.2026 DAS GEGENTEIL -- und die Aenderung
|
|
ist eine Entscheidung von Filipe, keine stille Korrektur.
|
|
|
|
Der alte Satz lautete: "Die Datei des anderen Hauses ist auf
|
|
beiden Adressen direkt abrufbar -- sie ist kein Geheimnis, sie ist
|
|
nur nicht die Vorgabe." Dazu die Sorge, eine Sperre koenne die
|
|
Pruefung darueber blind machen.
|
|
|
|
WAS SICH GEAENDERT HAT, IST NICHT DIE MEINUNG, SONDERN DIE
|
|
MESSUNG. Nachgemessen am 21.09.: 27 806 Byte, HTTP 200, von der
|
|
Agenturadresse, OHNE jede Anmeldung -- also nicht nur fuer
|
|
Kollegen, sondern fuer jeden im Netz. Darin das ganze Farbhaus von
|
|
Team Dogi samt seiner Kommentare, und in denen zweimal die Adresse
|
|
der zweiten Wand im Klartext. Die Adresse ist das eigentlich
|
|
Verborgene: Die Rollennamen stehen auf jener Wand ohnehin offen,
|
|
WO sie liegt dagegen nicht.
|
|
|
|
Im Kopf der Datei selbst stand seit dem 10.09. das Gegenteil
|
|
("wird NUR auf crew... ausgeliefert"). Zwei Stellen im Haus haben
|
|
sich also widersprochen, und die durchgesetzte war die offene.
|
|
|
|
DIE ALTE SORGE IST BEANTWORTET, nicht uebergangen: Der Block
|
|
weiter oben misst jeden Eintrag der Tafel in BEIDE Richtungen und
|
|
prueft zusaetzlich, dass die Umbiegung von `haus.css` auf crew.
|
|
weiterhin greift. Genau das war der Fall, vor dem der alte
|
|
Kommentar gewarnt hat -- er ist jetzt gemessen statt vermieden. */
|
|
const direkt = await hole("/workspace/assets/css/crew-haus.css", { host: WS });
|
|
ok(direkt.status === 404 || direkt.status === 410,
|
|
`crew-haus.css ist von der Agenturadresse nicht mehr abrufbar (${direkt.status})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
DIE GEGENPROBE
|
|
|
|
Bis hierhin koennte alles auch dann gruen sein, wenn die Regel gar
|
|
nicht auf die Rolle sieht. Deshalb wird sie jetzt ausgehebelt: Der
|
|
Modi bekommt in der Datenbank die Rolle 'manager'. Seine Sitzung
|
|
bleibt dieselbe -- aber auf crew. darf sie ab jetzt NICHT mehr
|
|
gelten.
|
|
===================================================================== */
|
|
console.log("\n--- Gegenprobe: die Regel ausgehebelt ---");
|
|
d.prepare("UPDATE personen SET rolle = 'manager' WHERE id = ?").run(idModi);
|
|
|
|
const nachher = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(nachher.status === 401,
|
|
"aus dem Modi wurde ein Manager -- seine Sitzung gilt auf crew. nicht mehr");
|
|
const nachherWs = await hole("/workspace/api/ich", { host: WS, keks: keksModi });
|
|
ok(nachherWs.status === 200,
|
|
"und dieselbe Sitzung gilt jetzt auf workspace. -- die Regel sieht wirklich auf die Rolle");
|
|
|
|
d.prepare("UPDATE personen SET rolle = 'modi' WHERE id = ?").run(idModi);
|
|
const zurueck = await hole("/workspace/api/ich", { host: CREW, keks: keksModi });
|
|
ok(zurueck.status === 200, "zurueckgedreht gilt sie wieder");
|
|
|
|
/* ---------- Ende ------------------------------------------------------ */
|
|
d.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt die Datei manchmal */ }
|
|
|
|
console.log(`\n${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}`
|
|
+ ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
|
|
process.exit(fehler === 0 ? 0 : 1);
|