/* ===================================================================== „AN ALLE" BEI „AN WEN" (24.09.2026) Filipe: „bei an ween fehlt noch die option alle neben den namen allen." DREI FRAGEN, und die dritte ist die, an der es schiefgehen kann: 1. Trifft „alle" wirklich JEDEN -- oder nur den ersten? 2. Bleibt der Duplikat-Schutz erhalten? Er fragt, was EINE Person schon hat; bei „alle" muss er je Person neu gestellt werden. Sonst bekaeme nur die erste ihre Pruefung und alle anderen die Vorlage doppelt -- genau so entstanden am 22.09. aus zwei Klicks 112 Aufgaben. 3. Kommt jemand darueber an Leute heran, die ihn nichts angehen? „alle" darf nicht mehr treffen als die Namen daneben. ===================================================================== */ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-aa-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); import { notbremse } from "./helfer-notbremse.mjs"; const { eigenerPort } = await import("./helfer-port.mjs"); const PORT = await eigenerPort(import.meta, "die An-alle-Pruefung"); 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 }); }; await import("./index.js"); notbremse(240_000, "pruef-an-alle"); await new Promise((r) => setTimeout(r, 900)); let fehler = 0, geprueft = 0; const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; const melde = (t) => console.log(t); 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 ks = randomBytes(32).toString("hex"); d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") .run("code_kennung_schluessel", ks, jetzt); function anlegen(name, rolle, code, aktiv = 1) { 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 (?,?,?,?,?,?,?,?)") .run(name, rolle, hash, salt, 32768, createHmac("sha256", ks).update(code).digest("hex"), aktiv, jetzt); return d.prepare("SELECT last_insert_rowid() AS id").get().id; } anlegen("Filipe", "admin", "CODE-DOGI-0001"); const modis = [ anlegen("Miss", "modi", "CODE-MODI-0001"), anlegen("Ghost", "modi", "CODE-MODI-0002"), anlegen("Marina", "modi", "CODE-MODI-0003"), ]; /* EIN GESPERRTER MODI. Er darf NICHT mitbekommen -- ein Zugang, der nicht hereinkommt, braucht keine Aufgabenliste. */ const gesperrt = anlegen("Diene", "modi", "CODE-MODI-0004", 0); /* Und ein Creator: Er steht gar nicht in dieser Auswahl und darf auch ueber „alle" nichts bekommen. */ const creator = anlegen("Luna", "creator", "CODE-CREA-0001"); d.close(); const CREW = "crew.dogfather-universe.com"; const { request } = await import("node:http"); function roh(pfad, keks, koerper, methode) { return new Promise((fertig, schief) => { const daten = koerper ? JSON.stringify(koerper) : null; const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: methode || (daten ? "POST" : "GET"), headers: { Host: CREW, Origin: `https://${CREW}`, ...(keks ? { Cookie: keks } : {}), ...(daten ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) } : {}), }, }, (antwort) => { let text = ""; antwort.on("data", (t) => { text += t; }); antwort.on("end", () => fertig({ code: antwort.statusCode, text })); }); a.on("error", schief); if (daten) a.write(daten); a.end(); }); } const json = (r) => { try { return JSON.parse(r.text); } catch { return {}; } }; async function anmelden(rolle, code) { const r = await new Promise((fertig, schief) => { const daten = JSON.stringify({ rolle, code, alter_ok: true }); const a = request({ host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden", method: "POST", headers: { Host: CREW, Origin: `https://${CREW}`, "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) }, }, (antwort) => { let text = ""; antwort.on("data", (t) => { text += t; }); antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers })); }); a.on("error", schief); a.write(daten); a.end(); }); if (r.code !== 200) return null; return [].concat(r.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; "); } const kDogi = await anmelden("admin", "CODE-DOGI-0001"); const kModi = await anmelden("modi", "CODE-MODI-0001"); ok(!!kDogi && !!kModi, "DogFather und ein Modi sind angemeldet"); /* Wie viele Aufgaben hat wer? Direkt aus der Datenbank -- die Antwort der Route zu glauben hiesse, sie mit sich selbst zu pruefen. */ function zaehle(personId) { const db2 = new DatabaseSync(process.env.WORKSPACE_DB, { readOnly: true }); const { n } = db2.prepare( "SELECT COUNT(*) AS n FROM aufgaben WHERE verantwortlich_id = ? AND vorlage IS NOT NULL") .get(personId); db2.close(); return n; } /* Welche Kategorie gibt es ueberhaupt? Aus dem Katalog, den die Seite auch bekommt -- eine erfundene Gruppe waere eine Pruefung, die nichts trifft. */ const katalog = json(await roh("/workspace/api/vorlagen", kDogi)); const ersteGruppe = (katalog.katalog?.gruppen || [])[0]?.schluessel || (katalog.katalog?.aufgaben || [])[0]?.gruppe || ""; ok(!!ersteGruppe, `es gibt eine Kategorie zum Verteilen (${ersteGruppe})`); melde("\n=== 1. alle trifft jeden ==="); { const vorher = modis.map(zaehle); ok(vorher.every((x) => x === 0), `vorher hat niemand etwas (${vorher.join(", ")})`); const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); ok(a.code === 200, `die Anfrage geht durch (${a.code})`); const r = json(a); ok((r.angelegt || 0) > 0, `es wurde etwas angelegt (${r.angelegt})`); const nachher = modis.map(zaehle); ok(nachher.every((x) => x > 0), `JEDER der drei Modis hat jetzt Aufgaben (${nachher.join(", ")})`); ok(new Set(nachher).size === 1, "und alle gleich viele -- sonst hat die Schleife jemanden ausgelassen"); ok((r.angelegt || 0) === nachher[0] * modis.length, `die Zahl in der Antwort passt zur Datenbank (${r.angelegt} = ${nachher[0]} x ${modis.length})`); } melde("\n=== 2. Der Duplikat-Schutz gilt je Person ==="); { const vorher = modis.map(zaehle); const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); ok(a.code === 200, `ein zweiter Klick geht durch (${a.code})`); const r = json(a); ok((r.angelegt || 0) === 0, `und legt NICHTS mehr an (${r.angelegt}) -- genau das war der Fehler vom 22.09.`); /* `uebersprungen` ist eine ZAHL, kein Feld -- nachgesehen in workspace-vorlagen.js (`uebersprungen: uebersprungen.length`). Mein erster Versuch rief `.length` darauf auf und bekam `undefined`; ein Messfehler, der wie eine fehlende Antwort aussah. */ ok(Number(r.uebersprungen) > 0, `stattdessen sagt die Antwort, wie viele schon lagen (${r.uebersprungen})`); ok(Number(r.uebersprungen) === 36, `und zwar alle 36 -- 12 Vorlagen mal 3 Modis (${r.uebersprungen})`); const nachher = modis.map(zaehle); ok(nachher.join() === vorher.join(), `niemand hat etwas doppelt (${nachher.join(", ")})`); } melde("\n=== 3. alle trifft nicht mehr als die Namen ==="); { ok(zaehle(gesperrt) === 0, `ein GESPERRTER Modi bekommt nichts (${zaehle(gesperrt)})`); ok(zaehle(creator) === 0, `ein Creator erst recht nicht (${zaehle(creator)})`); /* Und wer gar nicht verteilen darf, kommt auch ueber „alle" nicht weiter -- sonst waere der neue Wert ein Weg an der Rechtefrage vorbei. */ const alsModi = await roh("/workspace/api/vorlagen/uebernehmen", kModi, { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); ok(alsModi.code >= 400, `ein Modi kann nicht an alle verteilen (${alsModi.code})`); } melde("\n=== 4. Gegenproben ==="); { /* EINE EINZELNE PERSON GEHT WEITERHIN -- sonst hiesse „alle funktioniert" nur, dass der alte Weg kaputt ist. */ const einzeln = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, { art: "katalog", nr: 0, gruppe: ersteGruppe, person_id: modis[0] }); ok(einzeln.code === 200 || einzeln.code === 400, `der Weg an EINE Person antwortet noch (${einzeln.code})`); /* Ein erfundener Wert ist kein "alle". */ const quatsch = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle-zusammen" }); ok(quatsch.code >= 400, `alle-zusammen ist nicht alle (${quatsch.code})`); /* Und ohne Person weiterhin die alte Absage. */ const ohne = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, { art: "katalog_alle", gruppe: ersteGruppe, stufe: "" }); ok(ohne.code === 400, `ohne Person immer noch 400 (${ohne.code})`); } try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } console.log(""); console.log(`${geprueft} Pruefungen, ${fehler} Fehler`); console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); process.exit(fehler ? 1 : 0);