DER BEFUND
Im naechtlichen Lauf standen dreizehn Pruefungen mit derselben Zeile
rot:
[uncaughtException] Error: spawnSync openssl ENOENT
Kein Programmfehler. Die Pruefungen brauchen einen https-Vorbau (der
Sitzungskeks des Hauses ist `secure` und kommt ueber http nicht an),
und dafuer erzeugen sie ein Wegwerf-Zertifikat mit openssl.
GEMESSEN, NICHT VERMUTET
openssl liegt hier: C:\Program Files\Git\mingw64\bin\openssl.exe
C:\Program Files\Git\usr\bin\openssl.exe
im System-PATH steht: C:\Program Files\Git\cmd (nur git.exe)
im Benutzer-PATH: nichts mit Git
Wer aus Git Bash startet, erbt die mingw-Pfade und merkt nie etwas.
Die Aufgabenplanung startet `node.exe` DIREKT, ohne Shell -- und
bekommt sie nicht. Bei mir gruen, nachts rot, und der Grund steht
nicht im Code.
Das Schlimmste daran ist nicht der Ausfall: Dreizehn dauerhaft rote
Zeilen in einer Notiz, die Filipe morgens liest, gewoehnen einem das
Hinsehen ab -- und decken dabei die echten Befunde zu.
DIE REPARATUR
`server/helfer-openssl.mjs` sucht openssl erst im PATH (ohne `where`
oder `which` -- beides sind selbst Programme und koennen genauso
fehlen), danach an den Orten, an denen es auf einem Windows-Rechner
mit Git wirklich liegt.
Dazu `zertifikatBauen(schluessel, zertifikat, host)`. Die acht
Aufrufformen im Haus waren identisch bis auf die Variablennamen
(schl/zert, schluesselDatei/zertDatei, zKey/zCrt, sk/zt, key/crt) --
eine Stelle traegt alle neunundzwanzig. Zwei Tage zuvor war eine
davon um ein `-addext` aermer als die anderen; gemerkt hat es
niemand, weil der Browser den fehlenden Alternativnamen erst bei
einer Weiterleitung anmahnt. Eine Stelle kann nicht von sich selbst
abweichen.
KEIN EINTRAG IN DEN SYSTEM-PATH. `mingw64\bin` enthaelt rund hundert
Programme mit Unix-Namen (`find`, `sort`, `link`), die gleichnamige
Windows-Befehle verdecken. Das fuer eine Pruefung zu aendern haette
an ganz anderen Stellen Fehler verursacht, die niemand hierher
zurueckverfolgt.
GEGENPROBE IN BEIDE RICHTUNGEN, mit dem PATH des Nachtlaufs:
vorher (direkter Aufruf): ABSTURZ: spawnSync openssl ENOENT
nachher (ueber den Helfer): Zertifikat gebaut, 1236 Bytes
Und eine echte Pruefung unter denselben Bedingungen:
PATH ohne mingw64 -> pruef-chat-neu-stelle
63 Pruefungen, 0 Fehler, 0 Abstuerze
Genau diese Datei stand im Nachtlauf mit ENOENT rot.
EINE WACHE DAGEGEN
pruef-struktur prueft ab jetzt, dass niemand openssl wieder direkt
ruft -- der naechste merkt es dort und nicht erst in einem
Nachtlauf, den niemand liest. Drei Gegenproben.
Beim ersten Anlauf schlug sie auf ihre EIGENEN Probetexte an: Sie
liest alle Serverdateien, und dazu gehoert sie selbst. Die Texte
werden jetzt zusammengesetzt. Derselbe Selbsttreffer ist mir heute
schon zweimal passiert.
DER DRITTE AUSGANG BLEIBT, WO ER HINGEHOERT
`helfer-kachel-echtfarbe.mjs` faengt den Fehler weiterhin ab und
meldet `moeglich: false` mit Grund, statt abzubrechen. Eine
Sicherung abzuschaffen, weil ihr Anlass gerade behoben ist, ist der
Anfang des naechsten stillen Fehlschlags.
NEBENBEI: pruef-community-sicht
Sie war rot mit "ZU VIEL: reaktion.html" -- mein eigener Rueckstand
vom 28.09. Die Reaction steht als Kachel im Community-Bereich; die
Liste war nicht nachgezogen. Jetzt 10 / 0.
Die Liste bleibt bewusst von Hand gepflegt: Sie ist eine ABSICHT,
keine Ableitung. Aus rechte.js gelesen verglichen sich zwei Kopien
derselben Quelle -- immer gruen, nie ein Beweis.
GEPRUEFT
pruef-struktur ALLES IN ORDNUNG (mit der neuen Wache)
pruef-community-sicht 10 / 0
pruef-chat-neu-stelle 63 / 0 (mit dem PATH des Nachtlaufs)
pruef-notizen, -teamlage-karten, -werdegang: EXIT 0, keine Abstuerze
mess-quer EXIT 0
29 Dateien: node --check auf allen, kein direkter Aufruf mehr
708 lines
35 KiB
JavaScript
708 lines
35 KiB
JavaScript
/* =====================================================================
|
|
pruef-zuteilung.mjs — AUFGABEN AN MENSCHEN
|
|
=====================================================================
|
|
|
|
Der Auftrag vom 21.09.2026, Abschnitte 2 bis 5, durchgespielt wie
|
|
ein Arbeitstag: verteilen, annehmen, ablehnen, übernehmen,
|
|
fertigmachen, bewerten.
|
|
|
|
WAS HIER GEMESSEN WIRD — und warum gerade das:
|
|
|
|
(1) DASS JEMAND SIEHT, WAS ER BEKOMMEN HAT. Bei "mehrere" und "pool"
|
|
bleibt `verantwortlich_id` leer, und die alten Sichtregeln
|
|
fragen genau danach. Ohne die Erweiterung stünde eine Aufgabe im
|
|
Resümee der Leitung und wäre für den, der sie machen soll,
|
|
unsichtbar. Das ist der unangenehmste denkbare Fehler in einem
|
|
Aufgabensystem, und man sieht ihn nirgends — deshalb steht er
|
|
hier an erster Stelle.
|
|
|
|
(2) DASS DER POOL WIRKLICH SCHLIESST. "Die erste Modi, die gerade
|
|
Zeit hat, übernimmt" ist nur dann etwas wert, wenn die anderen
|
|
es merken. Geprüft wird nicht nur, dass einer sie hat, sondern
|
|
dass die anderen sie NICHT mehr haben.
|
|
|
|
(3) DASS ZWEI NICHT GLEICHZEITIG ÜBERNEHMEN KÖNNEN. Die Prüfung
|
|
fragt vor dem Schreiben, ob schon jemand zugesagt hat.
|
|
|
|
(4) DIE GRENZEN. Ein Modi darf nicht bewerten, niemand darf eine
|
|
Aufgabe annehmen, die er nicht bekommen hat, und bewertet wird
|
|
erst, wenn jemand fertig ist.
|
|
|
|
(5) GEGENPROBEN. Zu jeder Sperre eine Stelle, an der es erlaubt ist
|
|
— sonst wäre nur bewiesen, dass gar nichts geht.
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync, appendFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import http from "node:http";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-zuteil-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
import { zertifikatBauen } from "./helfer-openssl.mjs";
|
|
const { eigenerPort } = await import("./helfer-port.mjs");
|
|
const PORT = await eigenerPort(import.meta, "die Zuteilungspruefung");
|
|
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
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(300_000, "pruef-zuteilung");
|
|
await new Promise((r) => setTimeout(r, 1200));
|
|
|
|
const PROTOKOLL = join(process.cwd(), "pruef-zuteilung-lauf.txt");
|
|
try { rmSync(PROTOKOLL, { force: true }); } catch { /* gab es noch nicht */ }
|
|
const melde = (z) => { console.log(z); try { appendFileSync(PROTOKOLL, z + "\n"); } catch { /* Zugabe */ } };
|
|
let fehler = 0;
|
|
const ok = (b, t) => { melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const HOST = "crew.dogfather-universe.com";
|
|
/* http.request statt fetch: fetch verwirft den Host-Kopf still, und
|
|
ohne den landet alles im falschen Haus (gemessen am 20.09.2026). */
|
|
function rufe(pfad, wie = {}) {
|
|
return new Promise((fertig) => {
|
|
const daten = wie.body === undefined ? null : JSON.stringify(wie.body);
|
|
const a = http.request({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: wie.method || "GET",
|
|
headers: {
|
|
Host: HOST,
|
|
...(daten ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) } : {}),
|
|
...(wie.keks ? { Cookie: wie.keks } : {}),
|
|
Origin: `https://${HOST}`, Referer: `https://${HOST}/workspace/`,
|
|
},
|
|
}, (a2) => {
|
|
let t = "";
|
|
a2.on("data", (c) => (t += c));
|
|
a2.on("end", () => fertig({
|
|
code: a2.statusCode, kopf: a2.headers, text: t,
|
|
json: (() => { try { return JSON.parse(t); } catch { return null; } })(),
|
|
}));
|
|
});
|
|
a.on("error", (e) => fertig({ code: 0, text: e.message, json: null }));
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
/* ---- Leute anlegen -------------------------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
await rufe("/workspace/api/anmelden", { method: "POST", body: { rolle: "admin", code: "x" } });
|
|
for (let i = 0; i < 60; i++) {
|
|
try { const t = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
try { t.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { t.close(); }
|
|
} catch { await new Promise((r) => setTimeout(r, 250)); }
|
|
}
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
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,aktiv,erstellt) VALUES (?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idChef = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
const idLinks = anlegen("Lino", "linke", "CODE-LINK-0001");
|
|
const idAnna = anlegen("Anna", "modi", "CODE-MOD1-0001");
|
|
const idBea = anlegen("Bea", "modi", "CODE-MOD2-0001");
|
|
const idCem = anlegen("Cem", "modi", "CODE-MOD3-0001");
|
|
d.close();
|
|
|
|
async function alsWer(rolle, code) {
|
|
const a = await rufe("/workspace/api/anmelden", { method: "POST", body: { rolle, code } });
|
|
return (a.kopf?.["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
const chef = await alsWer("admin", "CODE-DOGI-0001");
|
|
const links = await alsWer("linke", "CODE-LINK-0001");
|
|
const anna = await alsWer("modi", "CODE-MOD1-0001");
|
|
const bea = await alsWer("modi", "CODE-MOD2-0001");
|
|
const cem = await alsWer("modi", "CODE-MOD3-0001");
|
|
|
|
/** Die Aufgabe mit dieser Nummer aus der Liste, die jemand sieht. */
|
|
async function aufgabeBei(keks, id) {
|
|
const a = await rufe("/workspace/api/aufgaben", { keks });
|
|
return (a.json?.aufgaben || []).find((x) => x.id === id) || null;
|
|
}
|
|
|
|
melde("\n===== AUFGABEN AN MENSCHEN =====");
|
|
|
|
/* =====================================================================
|
|
1. AN MEHRERE VERTEILEN -- jede macht ihren Teil
|
|
===================================================================== */
|
|
melde("\n=== An mehrere verteilen ===");
|
|
let idMehrere = null;
|
|
{
|
|
const a = await rufe("/workspace/api/aufgaben", { method: "POST", keks: chef,
|
|
body: { titel: "Clips schneiden", verteilart: "mehrere", zuteilung: [idAnna, idBea] } });
|
|
ok(a.code === 201, `DogFather legt sie an (HTTP ${a.code})`);
|
|
idMehrere = a.json?.id;
|
|
ok(a.json?.zuteilung?.art === "mehrere",
|
|
`sie ist als "mehrere" verteilt (${a.json?.zuteilung?.art})`);
|
|
ok((a.json?.zuteilung?.gesetzt || []).length === 2,
|
|
`an zwei Menschen (${(a.json?.zuteilung?.gesetzt || []).length})`);
|
|
|
|
/* DER KERN: Sieht Anna sie ueberhaupt? Bei "mehrere" ist
|
|
verantwortlich_id leer -- die alten Regeln faenden sie nicht. */
|
|
const beiAnna = await aufgabeBei(anna, idMehrere);
|
|
ok(!!beiAnna, "Anna sieht die Aufgabe, obwohl niemand 'verantwortlich' ist");
|
|
ok(beiAnna?.meine_zuteilung?.zustand === "offen",
|
|
`ihr eigener Stand ist "offen" (${beiAnna?.meine_zuteilung?.zustand})`);
|
|
ok((beiAnna?.zuteilung || []).length === 2,
|
|
`sie sieht beide Namen (${(beiAnna?.zuteilung || []).map((z) => z.person_name).join(", ")})`);
|
|
|
|
/* GEGENPROBE, UND SIE IST DER KERN VON ABSCHNITT 1:
|
|
*"Eine Modi darf nicht automatisch die vollstaendige
|
|
Aufgabenuebersicht aller anderen Modis sehen."*
|
|
|
|
Cem hat sie nicht bekommen -- und er sieht sie auch nicht. Das
|
|
stimmt genau deshalb, weil DogFather sie angelegt hat: Die alte
|
|
Regel gibt einem Modi die Aufgaben des Modi-TEAMS (Entscheidung
|
|
vom 09.09.2026, damit Schichttausch geht), und dazu gehoert eine
|
|
Aufgabe von DogFather an zwei bestimmte Leute nicht.
|
|
|
|
Beim ERSTEN Lauf stand hier `meine_zuteilung === null` -- und das
|
|
schlug fehl, weil Cem die Zeile gar nicht bekommt und `null?.x`
|
|
`undefined` ergibt, nicht `null`. Die Zusicherung war zu grob:
|
|
Sie konnte "sieht sie nicht" und "sieht sie ohne Stand" nicht
|
|
unterscheiden, also pruefte sie beides nicht richtig. Jetzt
|
|
stehen beide Faelle einzeln da. */
|
|
const beiCem = await aufgabeBei(cem, idMehrere);
|
|
ok(beiCem === null,
|
|
"Cem sieht sie gar nicht -- sie ist nicht fuer ihn und nicht von seinem Team");
|
|
|
|
/* UND DASS DAS NICHT AN EINER LEEREN LISTE LIEGT: Die Aufgabe, die
|
|
er GLEICH aus dem Pool bekommt, sieht er sehr wohl. Ohne diese
|
|
Gegenprobe waere oben auch gruen, wenn Cem gar nichts sieht. */
|
|
const seineListe = await rufe("/workspace/api/aufgaben", { keks: cem });
|
|
ok(seineListe.code === 200,
|
|
`und seine Liste laedt trotzdem (HTTP ${seineListe.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
2. ANNEHMEN UND ABLEHNEN
|
|
===================================================================== */
|
|
melde("\n=== Annehmen und ablehnen ===");
|
|
{
|
|
const ja = await rufe(`/workspace/api/aufgaben/${idMehrere}/annehmen`, { method: "POST", keks: anna, body: {} });
|
|
ok(ja.code === 200, `Anna nimmt an (HTTP ${ja.code})`);
|
|
|
|
const ohneGrund = await rufe(`/workspace/api/aufgaben/${idMehrere}/ablehnen`,
|
|
{ method: "POST", keks: bea, body: { grund: "" } });
|
|
ok(ohneGrund.code === 400 && ohneGrund.json?.fehler === "grund_fehlt",
|
|
`ohne Begruendung geht Ablehnen nicht (HTTP ${ohneGrund.code}, ${ohneGrund.json?.fehler})`);
|
|
|
|
const nein = await rufe(`/workspace/api/aufgaben/${idMehrere}/ablehnen`,
|
|
{ method: "POST", keks: bea, body: { grund: "Bin diese Woche im Urlaub." } });
|
|
ok(nein.code === 200, `mit Begruendung schon (HTTP ${nein.code})`);
|
|
|
|
/* UND DIE LEITUNG SIEHT BEIDES -- getrennt nach Person. Genau das
|
|
verlangt Abschnitt 4: keine Sammelaufgabe, sondern je Mensch. */
|
|
const beiChef = await aufgabeBei(chef, idMehrere);
|
|
const stand = Object.fromEntries((beiChef?.zuteilung || []).map((z) => [z.person_name, z.zustand]));
|
|
ok(stand.Anna === "angenommen" && stand.Bea === "abgelehnt",
|
|
`DogFather sieht es je Person (Anna: ${stand.Anna}, Bea: ${stand.Bea})`);
|
|
const beaGrund = (beiChef?.zuteilung || []).find((z) => z.person_name === "Bea")?.grund;
|
|
ok(beaGrund === "Bin diese Woche im Urlaub.",
|
|
`mit Beas Begruendung im Klartext ("${beaGrund}")`);
|
|
|
|
/* GRENZE: Cem hat sie nicht bekommen und kann sie nicht annehmen. */
|
|
const fremd = await rufe(`/workspace/api/aufgaben/${idMehrere}/annehmen`, { method: "POST", keks: cem, body: {} });
|
|
ok(fremd.code === 403 && fremd.json?.fehler === "nicht_zugeteilt",
|
|
`Cem kann sie nicht annehmen (HTTP ${fremd.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
3. DER POOL -- die erste, die Zeit hat
|
|
===================================================================== */
|
|
melde("\n=== Der Pool ===");
|
|
let idPool = null;
|
|
{
|
|
const a = await rufe("/workspace/api/aufgaben", { method: "POST", keks: links,
|
|
body: { titel: "Kommentare durchgehen", verteilart: "pool", zuteilung: [idAnna, idBea, idCem] } });
|
|
ok(a.code === 201, `die LINKE Hand darf auch verteilen (HTTP ${a.code})`);
|
|
idPool = a.json?.id;
|
|
ok(a.json?.zuteilung?.art === "pool", `als Pool (${a.json?.zuteilung?.art})`);
|
|
|
|
const vorher = await aufgabeBei(bea, idPool);
|
|
ok(vorher?.uebernommen_von === null,
|
|
"vorher hat sie niemand");
|
|
|
|
/* ===================================================================
|
|
HIER STAND BIS ZUM 22.09.2026 „Bea uebernimmt sie" -- und genau
|
|
das geht seitdem nicht mehr.
|
|
|
|
Filipe: „die modis und linke hand sollen da nichts uebernehmen
|
|
koennen von aufgaben, ueberhaupt ueberall sollen die keine
|
|
aufgaben selber uebernehmen die ihnen nicht zugetragen sind. ich
|
|
will dass die sich fuer aufgaben bewerben koennen aber die rechte
|
|
hand oder dogfather muessen annehmen oder ablehnen koennen."
|
|
|
|
Der Pool bleibt, sein Sinn bleibt („einer macht es, die anderen
|
|
sehen es"). Nur der Weg dahin geht jetzt ueber eine Bewerbung.
|
|
Die Pruefung darunter misst denselben EFFEKT wie vorher -- die
|
|
anderen sehen, dass sie weg ist -- nur ueber den neuen Weg.
|
|
=================================================================== */
|
|
const nimmAlt = await rufe(`/workspace/api/aufgaben/${idPool}/uebernehmen`, { method: "POST", keks: bea, body: {} });
|
|
ok(nimmAlt.code === 403 && nimmAlt.json?.fehler === "erst_bewerben",
|
|
`Bea kann sie sich nicht mehr selbst nehmen (HTTP ${nimmAlt.code}, ${nimmAlt.json?.fehler})`);
|
|
|
|
const bewerb = await rufe(`/workspace/api/aufgaben/${idPool}/bewerben`, { method: "POST", keks: bea,
|
|
body: { text: "Ich mache das." } });
|
|
ok(bewerb.code === 200, `sie bewirbt sich (HTTP ${bewerb.code})`);
|
|
|
|
/* DIE LINKE HAND HAT SIE VERTEILT -- entscheiden darf sie trotzdem
|
|
nicht. Das ist der Unterschied zwischen den zwei Fragen. */
|
|
const linksVersuch = await rufe(`/workspace/api/aufgaben/${idPool}/bewerbung/annehmen`,
|
|
{ method: "POST", keks: links, body: { person_id: idBea } });
|
|
ok(linksVersuch.code === 403,
|
|
`die linke Hand entscheidet nicht darueber (HTTP ${linksVersuch.code})`);
|
|
|
|
const ja = await rufe(`/workspace/api/aufgaben/${idPool}/bewerbung/annehmen`,
|
|
{ method: "POST", keks: chef, body: { person_id: idBea, kommentar: "Passt." } });
|
|
ok(ja.code === 200, `DogFather nimmt ihre Bewerbung an (HTTP ${ja.code})`);
|
|
|
|
/* DER EIGENTLICHE PUNKT, UNVERAENDERT: Die anderen muessen es SEHEN. */
|
|
const beiCem = await aufgabeBei(cem, idPool);
|
|
ok(beiCem?.uebernommen_von === "Bea",
|
|
`Cem sieht, dass Bea sie hat (${beiCem?.uebernommen_von})`);
|
|
ok(beiCem?.meine_zuteilung?.zustand === "abgelehnt",
|
|
`und dass sie fuer ihn weg ist (${beiCem?.meine_zuteilung?.zustand})`);
|
|
ok(beiCem?.meine_zuteilung?.grund === "Von jemand anderem übernommen",
|
|
`mit dem Grund im Klartext ("${beiCem?.meine_zuteilung?.grund}")`);
|
|
|
|
/* ZWEI KOENNEN SIE NICHT GLEICHZEITIG BEKOMMEN. Cem bewirbt sich
|
|
jetzt noch -- und laeuft in dieselbe Wand wie frueher beim
|
|
Uebernehmen. */
|
|
const zweite = await rufe(`/workspace/api/aufgaben/${idPool}/bewerben`, { method: "POST", keks: cem, body: {} });
|
|
ok(zweite.code === 409, `eine zweite Bewerbung wird abgelehnt (HTTP ${zweite.code}, ${zweite.json?.fehler})`);
|
|
ok(zweite.json?.wer === "Bea", `und sagt, wer sie hat (${zweite.json?.wer})`);
|
|
|
|
/* GEGENPROBE: Bei einer "mehrere"-Aufgabe gibt es kein Uebernehmen. */
|
|
const keinPool = await rufe(`/workspace/api/aufgaben/${idMehrere}/uebernehmen`, { method: "POST", keks: anna, body: {} });
|
|
ok(keinPool.code === 409 && keinPool.json?.fehler === "kein_pool",
|
|
`bei "mehrere" gibt es kein Uebernehmen (HTTP ${keinPool.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
4. FERTIG MACHEN UND BEWERTEN
|
|
===================================================================== */
|
|
melde("\n=== Fertig machen und bewerten ===");
|
|
{
|
|
const zuFrueh = await rufe(`/workspace/api/aufgaben/${idPool}/bewerten`, { method: "POST", keks: chef,
|
|
body: { person_id: idBea, bewertung: "gut" } });
|
|
ok(zuFrueh.code === 409 && zuFrueh.json?.fehler === "noch_nicht_erledigt",
|
|
`vor dem Fertigwerden wird nicht bewertet (HTTP ${zuFrueh.code})`);
|
|
|
|
const arbeit = await rufe(`/workspace/api/aufgaben/${idPool}/mein-stand`, { method: "POST", keks: bea,
|
|
body: { zustand: "arbeit" } });
|
|
ok(arbeit.code === 200, `Bea setzt "in Bearbeitung" (HTTP ${arbeit.code})`);
|
|
const fertig = await rufe(`/workspace/api/aufgaben/${idPool}/mein-stand`, { method: "POST", keks: bea,
|
|
body: { zustand: "erledigt" } });
|
|
ok(fertig.code === 200, `und danach "erledigt" (HTTP ${fertig.code})`);
|
|
|
|
/* DIE DREI RUECKMELDUNGEN AUS ABSCHNITT 5. */
|
|
const leer = await rufe(`/workspace/api/aufgaben/${idPool}/bewerten`, { method: "POST", keks: chef,
|
|
body: { person_id: idBea, bewertung: "verbessern", text: "" } });
|
|
ok(leer.code === 400 && leer.json?.fehler === "text_fehlt",
|
|
`"Verbesserungsmoeglichkeiten" ohne Text geht nicht (HTTP ${leer.code})`);
|
|
|
|
const bew = await rufe(`/workspace/api/aufgaben/${idPool}/bewerten`, { method: "POST", keks: chef,
|
|
body: { person_id: idBea, bewertung: "verbessern", text: "Beim naechsten Mal bitte die Zeitstempel mitschreiben." } });
|
|
ok(bew.code === 200, `mit Text schon (HTTP ${bew.code})`);
|
|
|
|
/* UND BEA SIEHT SIE. Abschnitt 5: "Die Modi soll nachvollziehen
|
|
koennen, welches Feedback zu ihrer Aufgabe gegeben wurde." */
|
|
const beiBea = await aufgabeBei(bea, idPool);
|
|
ok(beiBea?.meine_zuteilung?.bewertung === "verbessern",
|
|
`Bea sieht die Rueckmeldung (${beiBea?.meine_zuteilung?.bewertung})`);
|
|
ok(String(beiBea?.meine_zuteilung?.bewertung_text || "").includes("Zeitstempel"),
|
|
"samt dem Text dazu");
|
|
ok(beiBea?.meine_zuteilung?.bewertung_text_kurz === "Verbesserungsmöglichkeiten",
|
|
`und im Klartext ("${beiBea?.meine_zuteilung?.bewertung_text_kurz}")`);
|
|
|
|
/* GRENZE: Ein Modi bewertet niemanden. */
|
|
const modiBewertet = await rufe(`/workspace/api/aufgaben/${idPool}/bewerten`, { method: "POST", keks: anna,
|
|
body: { person_id: idBea, bewertung: "gut" } });
|
|
ok(modiBewertet.code === 403, `ein Modi darf nicht bewerten (HTTP ${modiBewertet.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
5. DAS RESUEMEE -- je Mensch und fuer alle
|
|
===================================================================== */
|
|
melde("\n=== Das Resuemee ===");
|
|
{
|
|
const meins = await rufe("/workspace/api/aufgaben/resuemee", { keks: bea });
|
|
ok(meins.code === 200, `Bea sieht ihr eigenes Resuemee (HTTP ${meins.code})`);
|
|
ok(meins.json?.erledigt === 1 && meins.json?.abgelehnt === 1,
|
|
`mit ihren Zahlen (erledigt ${meins.json?.erledigt}, abgelehnt ${meins.json?.abgelehnt})`);
|
|
ok(meins.json?.zu_verbessern === 1,
|
|
`und der Rueckmeldung (${meins.json?.zu_verbessern})`);
|
|
|
|
/* GRENZE: Ein Modi sieht nicht das Resuemee eines anderen. */
|
|
const fremd = await rufe(`/workspace/api/aufgaben/resuemee?person=${idAnna}`, { keks: bea });
|
|
ok(fremd.code === 403, `aber nicht das von Anna (HTTP ${fremd.code})`);
|
|
|
|
/* GEGENPROBE: Die Leitung schon -- das ist der Sinn der Sache. */
|
|
const durchChef = await rufe(`/workspace/api/aufgaben/resuemee?person=${idAnna}`, { keks: chef });
|
|
ok(durchChef.code === 200 && durchChef.json?.name === "Anna",
|
|
`DogFather schon (HTTP ${durchChef.code}, ${durchChef.json?.name})`);
|
|
|
|
const team = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: links });
|
|
ok(team.code === 200, `die linke Hand sieht das Team-Resuemee (HTTP ${team.code})`);
|
|
const namen = (team.json?.leute || []).map((p) => p.name);
|
|
ok(namen.includes("Anna") && namen.includes("Bea") && namen.includes("Cem"),
|
|
`mit allen Modis (${namen.join(", ")})`);
|
|
const annaZeile = (team.json?.leute || []).find((p) => p.name === "Anna");
|
|
ok(annaZeile?.angenommen === 1,
|
|
`und je Person die eigenen Zahlen (Anna angenommen: ${annaZeile?.angenommen})`);
|
|
|
|
const modiTeam = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: anna });
|
|
ok(modiTeam.code === 403, `ein Modi bekommt es nicht (HTTP ${modiTeam.code})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
5b. WAS UNTEN IM BRETT STEHT, STEHT AUCH OBEN IM BAND (22.09.2026)
|
|
=====================================================================
|
|
Filipe: "die aufgaben die man unten sieht soll man auch oben sehen."
|
|
|
|
Gemessen war es so: aufgaben_zuteilung leer, im Brett zwei Aufgaben,
|
|
oben bei jeder Person "nichts zugeteilt". Die Aufgaben hingen am
|
|
aelteren Feld verantwortlich_id -- genau dem, nach dem das Brett
|
|
gruppiert.
|
|
|
|
Deshalb wird hier eine Aufgabe GENAU SO angelegt, wie die echten
|
|
alten liegen: mit verantwortlich_id und OHNE Zeile in
|
|
aufgaben_zuteilung. Eine Aufgabe ueber die Schnittstelle anzulegen
|
|
wuerde den Fall nicht treffen -- die legt heute beides an. */
|
|
melde("\n=== Die alten Aufgaben zaehlen oben mit ===");
|
|
{
|
|
const vorher = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: chef });
|
|
const cemVor = (vorher.json?.leute || []).find((x) => x.name === "Cem") || {};
|
|
|
|
/* Die Verbindung von oben ist laengst zu (Zeile 115). Eine eigene,
|
|
kurz geoeffnete -- und gleich wieder geschlossen, damit sie dem
|
|
laufenden Server nicht im Weg steht. */
|
|
{
|
|
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const setz = dd.prepare(`INSERT INTO aufgaben
|
|
(titel, status, verantwortlich_id, erstellt_von, erstellt)
|
|
VALUES (?,?,?,?,?)`);
|
|
const jetzt = new Date().toISOString();
|
|
setz.run("Alte Aufgabe ohne Zuteilung", "arbeit", idCem, idChef, jetzt);
|
|
setz.run("Alte Aufgabe, noch offen", "offen", idCem, idChef, jetzt);
|
|
/* Eine abgebrochene darf NICHT mitzaehlen -- sie steht auch im
|
|
Brett in keiner der beiden Spalten. */
|
|
setz.run("Alte Aufgabe, abgebrochen", "abgebrochen", idCem, idChef, jetzt);
|
|
dd.close();
|
|
}
|
|
|
|
const nachher = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: chef });
|
|
const cem = (nachher.json?.leute || []).find((x) => x.name === "Cem") || {};
|
|
|
|
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Ein Vergleich von zwei Nullen
|
|
waere gruen, ohne irgendetwas bewiesen zu haben. */
|
|
ok((cemVor.dran || 0) + 2 === (cem.dran || 0),
|
|
`Cems "dran" steigt um genau 2 (${cemVor.dran || 0} -> ${cem.dran || 0})`);
|
|
ok(cem.offen >= 1, `eine offene alte Aufgabe zaehlt (offen: ${cem.offen})`);
|
|
ok(cem.arbeit >= 1, `eine laufende alte Aufgabe zaehlt (arbeit: ${cem.arbeit})`);
|
|
ok((cemVor.gesamt || 0) + 2 === (cem.gesamt || 0),
|
|
`die abgebrochene zaehlt NICHT mit (gesamt ${cemVor.gesamt || 0} -> ${cem.gesamt || 0})`);
|
|
|
|
/* GEGENPROBE AM BRETT: Genau so viele, wie oben stehen, stehen auch
|
|
unten -- das ist Filipes Satz, als Messung. */
|
|
const brett = await rufe("/workspace/api/aufgaben", { keks: chef });
|
|
const untenCem = (brett.json?.aufgaben || [])
|
|
.filter((a) => a.verantwortlich_id === idCem
|
|
&& (a.status === "offen" || a.status === "arbeit" || a.status === "review"));
|
|
ok(untenCem.length >= 2 && untenCem.length === (cem.dran || 0),
|
|
`unten im Brett stehen genauso viele wie oben (${untenCem.length} = ${cem.dran})`);
|
|
|
|
/* Und die Person darf nicht aus der Liste fallen, nur weil sie
|
|
ueber den alten Weg etwas hat. */
|
|
ok(!!cem.id, "Cem steht in der Liste");
|
|
}
|
|
|
|
/* =====================================================================
|
|
6. EINZELN -- der haeufigste Fall, und die alte Spalte stimmt noch
|
|
===================================================================== */
|
|
melde("\n=== Eine einzelne Person ===");
|
|
{
|
|
const a = await rufe("/workspace/api/aufgaben", { method: "POST", keks: chef,
|
|
body: { titel: "Thumbnail bauen", zuteilung: [idCem] } });
|
|
ok(a.code === 201 && a.json?.zuteilung?.art === "einzeln",
|
|
`eine Person heisst "einzeln" (${a.json?.zuteilung?.art})`);
|
|
const beiCem = await aufgabeBei(cem, a.json.id);
|
|
ok(beiCem?.verantwortlich_name === "Cem",
|
|
`und die alte Spalte stimmt weiterhin (${beiCem?.verantwortlich_name})`);
|
|
|
|
/* GEGENPROBE ZUR ABLEITUNG: Wer "pool" mit einer Person schickt,
|
|
bekommt "einzeln" -- ein Pool aus einem Menschen ist keiner. */
|
|
const b = await rufe("/workspace/api/aufgaben", { method: "POST", keks: chef,
|
|
body: { titel: "Allein im Pool", verteilart: "pool", zuteilung: [idCem] } });
|
|
ok(b.json?.zuteilung?.art === "einzeln",
|
|
`"pool" mit einer Person wird "einzeln" (${b.json?.zuteilung?.art})`);
|
|
}
|
|
|
|
/* =====================================================================
|
|
7. EIN MODI VERTEILT NICHTS
|
|
===================================================================== */
|
|
melde("\n=== Grenzen beim Verteilen ===");
|
|
{
|
|
/* EIN MODI LEGT GAR NICHTS MEHR AN (22.09.2026).
|
|
------------------------------------------------------------------
|
|
Hier stand bis heute: "Anna darf sich selbst etwas notieren" --
|
|
ihre Zuteilung an Bea wurde stillschweigend auf sie selbst
|
|
umgebogen. Filipe hat das umgedreht: "sie sich nicht selber
|
|
aufgaben geben."
|
|
|
|
403 ist hier die ehrlichere Antwort als ein stilles Umbiegen: Wer
|
|
etwas versucht, das nicht vorgesehen ist, soll es erfahren -- und
|
|
nicht ein Ergebnis bekommen, das er nicht gemeint hat. */
|
|
const a = await rufe("/workspace/api/aufgaben", { method: "POST", keks: anna,
|
|
body: { titel: "Bea soll das machen", zuteilung: [idBea] } });
|
|
ok(a.code === 403, `ein Modi legt gar keine Aufgabe an (HTTP ${a.code})`);
|
|
ok(a.json?.fehler === "nur_leitung_legt_an",
|
|
`und bekommt einen Grund, der einen Satz hat (${a.json?.fehler})`);
|
|
}
|
|
|
|
|
|
/* =====================================================================
|
|
8. AM ECHTEN BILDSCHIRM
|
|
=====================================================================
|
|
|
|
Bis hierher ist bewiesen, dass der Server richtig entscheidet. Das
|
|
sagt nichts darüber, ob ein Mensch es bedienen kann. Geprüft wird
|
|
der Weg, den er wirklich geht: Seite öffnen, Band lesen, Knopf
|
|
drücken.
|
|
|
|
WARUM DAS NICHT DIE SCHNITTSTELLE NOCH EINMAL PRÜFT: Ein Knopf, den
|
|
es nicht gibt, ist genauso wirkungslos wie eine Route, die 403 sagt
|
|
— nur fällt er niemandem auf.
|
|
===================================================================== */
|
|
melde("\n=== Am echten Bildschirm ===");
|
|
{
|
|
/* WARUM HIER EIN https-VORBAU STEHT -- gemessen, nicht vermutet.
|
|
|
|
`crew.dogfather-universe.com` steht in Chromiums eingebauter
|
|
HSTS-Liste. Der Browser schaltet von sich aus auf https um, noch
|
|
bevor eine Anfrage hinausgeht; der Testserver spricht http, und
|
|
das Ergebnis ist `chrome-error://chromewebdata/` -- die Seite
|
|
laedt gar nicht erst. Genau so ist dieser Abschnitt beim ersten
|
|
Lauf gescheitert.
|
|
|
|
Abschalten laesst sich das nicht (die Liste ist einkompiliert).
|
|
Also spricht die Pruefung selbst https: ein eigenes Zertifikat
|
|
auf diesen Namen, ein Vorbau, der alles unveraendert
|
|
weiterreicht -- BESONDERS die Host-Kopfzeile, denn an ihr
|
|
entscheidet das Haus, wer was sieht.
|
|
|
|
Derselbe Weg wie in pruef-handy-teamdogi. Eine Nachbildung waere
|
|
die zweite Fassung, die anders altert. */
|
|
const { execFileSync } = await import("node:child_process");
|
|
const { createServer: httpsServer } = await import("node:https");
|
|
const { request: httpAnfrage } = await import("node:http");
|
|
const { readFileSync } = await import("node:fs");
|
|
|
|
const schluessel = join(ordner, "pruef.key");
|
|
const zertifikat = join(ordner, "pruef.crt");
|
|
zertifikatBauen(schluessel, zertifikat, HOST);
|
|
|
|
const HTTPS_PORT = PORT + 1;
|
|
const vorbau = httpsServer({
|
|
key: readFileSync(schluessel), cert: readFileSync(zertifikat),
|
|
}, (von, nach) => {
|
|
const weiter = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: von.url, method: von.method,
|
|
headers: { ...von.headers, host: `${HOST}:${HTTPS_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(HTTPS_PORT, "127.0.0.1", r));
|
|
const SEITE = `https://${HOST}:${HTTPS_PORT}`;
|
|
|
|
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await pw.chromium.launch({
|
|
args: [`--host-resolver-rules=MAP ${HOST} 127.0.0.1`,
|
|
"--ignore-certificate-errors"],
|
|
});
|
|
const seitenFehler = [];
|
|
async function alsSeite(rolle, code) {
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 950 } });
|
|
const s2 = await ctx.newPage();
|
|
s2.on("pageerror", (e) => seitenFehler.push(`${rolle}: ${String(e).slice(0, 110)}`));
|
|
await s2.goto(`${SEITE}/workspace/`, { waitUntil: "networkidle" });
|
|
await s2.click(`.rolle[data-rolle="${rolle}"]`);
|
|
await s2.fill("#code", code);
|
|
await s2.click("button[type=submit]");
|
|
await s2.waitForURL("**/start.html", { timeout: 25000 });
|
|
await s2.goto(`${SEITE}/workspace/aufgaben.html`, { waitUntil: "networkidle" });
|
|
await s2.waitForTimeout(1500);
|
|
return { ctx, seite: s2 };
|
|
}
|
|
|
|
/* ---- Der Modi: sein eigenes Resümee und seine Knöpfe ---- */
|
|
const pAnna = await alsSeite("modi", "CODE-MOD1-0001");
|
|
try {
|
|
const band = await pAnna.seite.evaluate(() => {
|
|
const b = document.querySelector(".z-band");
|
|
if (!b) return null;
|
|
return {
|
|
titel: b.querySelector(".z-band__titel")?.textContent,
|
|
satz: b.querySelector(".z-band__satz")?.textContent,
|
|
zahlen: [...b.querySelectorAll(".zk")].map((z) =>
|
|
z.querySelector(".zk__zahl").textContent + " " + z.querySelector(".zk__wort").textContent),
|
|
istTeam: b.classList.contains("z-band--team"),
|
|
};
|
|
});
|
|
ok(!!band, "ein Modi sieht ein Band ueber dem Brett");
|
|
ok(band && band.titel === "Deine Aufgaben",
|
|
`und es ist SEIN Resuemee (${band?.titel})`);
|
|
ok(band && band.istTeam === false,
|
|
"nicht die Uebersicht ueber alle");
|
|
ok(band && band.zahlen.length >= 3,
|
|
`mit Zahlen (${(band?.zahlen || []).join(" · ")})`);
|
|
|
|
/* DER KERN DER OBERFLAECHE: Der Knopf, mit dem sie annimmt. */
|
|
const knopf = await pAnna.seite.evaluate(() => {
|
|
const k = [...document.querySelectorAll(".z-karte .knopf")]
|
|
.map((b) => b.textContent.trim());
|
|
return k;
|
|
});
|
|
ok(knopf.length > 0, `an ihren Karten stehen Knoepfe (${knopf.join(", ")})`);
|
|
ok(knopf.includes("Fertig") || knopf.includes("Ich fange an") || knopf.includes("Annehmen"),
|
|
"und darunter einer, der sie weiterbringt");
|
|
|
|
/* GEGENPROBE: Es darf KEIN Bewerten-Knopf dastehen. */
|
|
const bewerten = await pAnna.seite.evaluate(() =>
|
|
document.querySelectorAll(".z-bewerten").length);
|
|
ok(bewerten === 0, `ein Modi bekommt keine Bewerten-Knoepfe (${bewerten})`);
|
|
} finally {
|
|
await pAnna.ctx.close();
|
|
}
|
|
|
|
/* ---- DogFather: die Uebersicht ueber alle ---- */
|
|
const pChef = await alsSeite("admin", "CODE-DOGI-0001");
|
|
try {
|
|
const band = await pChef.seite.evaluate(() => {
|
|
const b = document.querySelector(".z-band");
|
|
if (!b) return null;
|
|
return {
|
|
titel: b.querySelector(".z-band__titel")?.textContent,
|
|
istTeam: b.classList.contains("z-band--team"),
|
|
leute: [...b.querySelectorAll(".z-person__name")].map((n) => n.textContent),
|
|
marken: [...b.querySelectorAll(".z-marke")].map((m) => m.textContent),
|
|
};
|
|
});
|
|
ok(!!band && band.istTeam === true,
|
|
`DogFather sieht die Uebersicht ueber alle (${band?.titel})`);
|
|
ok((band?.leute || []).includes("Anna") && (band?.leute || []).includes("Bea"),
|
|
`mit allen Namen (${(band?.leute || []).join(", ")})`);
|
|
ok((band?.marken || []).some((m) => /erledigt|offen|in Arbeit/.test(m)),
|
|
`und Marken, die etwas sagen (${(band?.marken || []).slice(0, 4).join(" · ")})`);
|
|
|
|
/* EINE PERSON ANTIPPEN FILTERT DAS BRETT. */
|
|
const vorher = await pChef.seite.evaluate(() =>
|
|
[...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length);
|
|
await pChef.seite.click('.z-person[data-person]');
|
|
await pChef.seite.waitForTimeout(400);
|
|
const nachher = await pChef.seite.evaluate(() =>
|
|
[...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length);
|
|
ok(vorher > 0, `vorher sind ${vorher} Karten zu sehen`);
|
|
ok(nachher < vorher, `nach dem Antippen weniger (${nachher})`);
|
|
|
|
/* UND NOCH EINMAL ANTIPPEN ZEIGT WIEDER ALLE -- sonst waere der
|
|
Filter eine Falle: Man kommt hinein und nicht mehr heraus. */
|
|
await pChef.seite.click('.z-person[data-person]');
|
|
await pChef.seite.waitForTimeout(400);
|
|
const zurueck = await pChef.seite.evaluate(() =>
|
|
[...document.querySelectorAll(".karte[data-id]")].filter((k) => !k.hidden).length);
|
|
ok(zurueck === vorher, `und noch einmal zeigt wieder alle (${zurueck})`);
|
|
|
|
/* ==== DIE MEHRFACHAUSWAHL -- SIE IST UMGEZOGEN (berichtigt 23.09.2026)
|
|
|
|
Hier stand `await seite.click("#neu-oeffnen")` auf der
|
|
Aufgabenseite. Dieser Knopf ist fuer DogFather seit dem
|
|
22.09.2026 VERBORGEN, und zwar mit Absicht:
|
|
|
|
Filipe: "dieser button kann da jetzt doch endlich
|
|
verschwinden, auf dieser seite sollen ja keine aufgaben mehr
|
|
verteilt werden."
|
|
|
|
Verteilt wird seither auf der Entwicklungsseite. Die Pruefung
|
|
ist deshalb seit einem Tag mit einer Zeitueberschreitung
|
|
ABGESTUERZT -- 30 Sekunden Warten auf einen Knopf, den es dort
|
|
nicht mehr geben soll -- und hat dabei auch alles danach nicht
|
|
mehr geprueft.
|
|
|
|
DAS IST DIE DRITTE SORTE FEHLER aus den Hausregeln: kein
|
|
uebersprungener Test und kein gruener, der das Falsche prueft,
|
|
sondern einer, der seine Voraussetzung verloren hat. Er sah aus
|
|
wie ein Befund am Programm und war einer an der Pruefung.
|
|
|
|
Sie folgt der Faehigkeit jetzt dorthin, wo sie lebt. Gemessen
|
|
wird dasselbe wie vorher: Stehen Leute zur Auswahl, und
|
|
erscheint die Frage "wie soll das laufen" erst, wenn es
|
|
wirklich mehrere sind?
|
|
|
|
UND DASS DER KNOPF DORT WEG IST, WIRD MITGEPRUEFT -- sonst
|
|
koennte er stillschweigend zurueckkommen, und Filipes Ansage
|
|
waere wieder rueckgaengig, ohne dass es jemand merkt. */
|
|
const knopfWeg = await pChef.seite.evaluate(() => {
|
|
const k = document.getElementById("neu-oeffnen");
|
|
return !k || k.hidden;
|
|
});
|
|
ok(knopfWeg,
|
|
"auf der Aufgabenseite legt DogFather nichts mehr an — so gewollt seit dem 22.09.");
|
|
|
|
await pChef.seite.goto(`${SEITE}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
|
|
await pChef.seite.waitForTimeout(1500);
|
|
/* ERST DAS FORMULAR AUFMACHEN. Es startet zugeklappt -- die Liste
|
|
der Leute wird beim Aufklappen gebaut (fuerLeuteBauen laeuft nur,
|
|
wenn das Formular offen ist). Ohne diesen Klick maesse die
|
|
Pruefung null Kaestchen und meldete einen Fehler, den es nicht
|
|
gibt. Genau so ist der erste Anlauf gelaufen. */
|
|
await pChef.seite.click("#verteilen-knopf");
|
|
await pChef.seite.waitForTimeout(900);
|
|
const mehr = await pChef.seite.evaluate(() => {
|
|
const leute = [...document.querySelectorAll('#e-fuer-leute input[type="checkbox"]')];
|
|
const art = document.getElementById("e-art");
|
|
if (!leute.length || !art) return { da: false, zahl: leute.length };
|
|
const artVorher = art.hidden;
|
|
leute.slice(0, 2).forEach((x) => {
|
|
x.checked = true;
|
|
x.dispatchEvent(new Event("change", { bubbles: true }));
|
|
});
|
|
return { da: true, zahl: leute.length, artVorher, artNachher: art.hidden };
|
|
});
|
|
ok(mehr.da, `DogFather kann mehrere dazunehmen (${mehr.zahl} zur Wahl)`);
|
|
ok(mehr.zahl >= 2, `es stehen Leute zur Auswahl (${mehr.zahl})`);
|
|
ok(mehr.artVorher === true,
|
|
"die Frage 'wie soll das laufen' steht erst nicht da");
|
|
ok(mehr.artNachher === false,
|
|
"und erscheint, sobald zwei gewaehlt sind");
|
|
} finally {
|
|
await pChef.ctx.close();
|
|
}
|
|
|
|
ok(seitenFehler.length === 0, seitenFehler.length === 0
|
|
? "kein Skript ist unterwegs gestolpert"
|
|
: `Skriptfehler: ${seitenFehler.join(" | ")}`);
|
|
await browser.close();
|
|
await new Promise((r) => vorbau.close(r));
|
|
}
|
|
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Zugabe */ }
|
|
melde(`\n ${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
|
|
process.exit(fehler ? 1 : 0);
|