Der Rundumcheck geht weiter. Diese zwoelf standen im Nachtlauf als rot
und waren es nicht: Jede hat den Umbau vom 24.09. (die Haustrennung),
den vom 25.09. (die zugetragenen Punkte) oder eine Umbenennung
mitbekommen -- nur ihre Erwartung nicht.
DAS IST KEIN TROST. Ein Fehlalarm zeigt auf eine echte Stelle und nennt
eine plausible Zahl; man sucht danach an richtigem Code. Und zwoelf rote
Zeilen, die immer rot sind, nehmen dem ganzen Satz die Stimme.
=== DIESELBE WURZEL, SECHSMAL: DAS FALSCHE HAUS ===
Seit dem 24.09. antwortet der Server auf der Agenturadresse nicht mehr
ueber Team-Dogi-Leute -- und genau das taten diese Pruefungen:
pruef-nachwuchs legte einen Modi ueber die Agenturadresse an
(400). Einundzwanzig Meldungen hingen an diesem
einen Aufruf. 82 Zeilen auf crew. umgestellt,
aus „Mara, Managerin" wurde die linke Hand --
im Teamhaus ist sie das, was gemeint war.
230 -> 262 Pruefungen, 0 Fehler.
pruef-schritt dasselbe beim Aufgabenbrett (404/400).
pruef-treff-werkzeuge fragte die Einladungsliste eines Modis auf der
Agenturadresse ab -- dort ist er nicht.
pruef-rechte-umstellen benutzte `wissen.html` als zweite Probeseite.
Die ist laut Rechtetafel fuer alle offen und auf
crew. trotzdem zu: Dort kommt man nur auf Seiten,
fuer die es eine Kachel gibt. Jetzt material.html.
pruef-personen-formular verglich eine Erwartung FUER die Agentur mit
einer Messung OHNE Adresse (127.0.0.1) -- vier
gegen acht Rollen. Beides war richtig, nur nicht
dasselbe.
pruef-treff suchte „Support" zwischen den Brettern. Er steht
in „Fuer dich", wo er hingehoert -- eine kaputte
Seite zu melden ist kein Aushang. Die Liste stand
ZWEIMAL in der Datei; nachgezogen wurde eine.
Jetzt eine, von beiden benutzt.
=== ZWEIMAL: EINE FESTE ZAHL, DIE DIE SEITE UEBERHOLT HAT ===
pruef-community-sicht erlaubte „3 bis 8 Seiten". Es sind zwoelf, und
jede gehoert dorthin. Statt einer Spanne steht
jetzt die LISTE da: Kommt eine dazu, wird die
Zeile rot und nennt sie beim Namen. Eine Spanne
haette elf statt zwoelf stillschweigend
durchgelassen -- in beide Richtungen.
pruef-nachwuchs schaltete EINE Person ab und erwartete, dass die
Ampel danach schweigt. Der Kommentar darueber
sagt selbst: „Die Schwelle wird nicht
abgeschrieben, sondern aus dem Verhalten
abgeleitet" -- eine feste Anzahl abzuschalten ist
aber eine Abschrift in anderer Waehrung. Jetzt
wird abgeschaltet, BIS es kippt.
=== UND VIERMAL EIN WERKZEUG, DAS STUMPF WAR ===
pruef-scout-zuteilung suchte `.person__zeile` -- eine Klasse, die es
nicht mehr gibt. `querySelectorAll` liefert dafuer
ein leeres Feld, `.some()` darauf ist immer
false: Die Pruefung war nicht rot, weil etwas
fehlte, sondern weil sie nichts ansehen konnte.
Jetzt `.person`, und die ANZAHL der gefundenen
Karten steht in einer eigenen Bedingung.
pruef-loeschen suchte `::before` in einem Fenster von 600
Zeichen ab dem Selektor. Das misst die Laenge des
KOMMENTARS: Am 25.09. kam eine Begruendung von
zwanzig Zeilen dazu, und die Regel rutschte
hinaus. Jetzt wird nach dem Selektor gesucht.
pruef-portnummern hielt jedes `listen(` fuer einen Serverstart --
auch das blosse Nachsehen, ob eine Nummer frei
ist. Damit mahnte sie ausgerechnet die Datei an,
die das Vergeben der Nummern prueft. Jetzt zaehlt
der Import von `index.js`; dazu vier Gegenproben,
damit die engere Fassung nicht zum blinden Fleck
wird.
pruef-spicy stellte eine Person auf „manager" und dann
weiter. An Managern aendert seit dem 22.09. nur
DogFather etwas -- die Pruefung hatte sich selbst
die Tuer zugezogen. Jetzt kommt „manager" zuletzt,
und dass danach nichts mehr geht, ist eine eigene
Zeile. Aus dem Stolperstein wird eine Aussage.
=== EINER WAR FAST EIN BEFUND ===
pruef-alle-sehen-es meldete „highlight: DogFather 0, Community 0".
Das Brett hat als einziges keinen Katalog (dort
stehen echte Hoehepunkte, keine Vorlagen), und an
einem leeren Brett ist „sehen beide dasselbe?"
nicht zu messen: null gleich null waere auch dann
wahr, wenn die Community gar nichts duerfte.
Die Pruefung legt dort jetzt selbst einen Eintrag
an -- und musste dabei zweimal lernen: Der Eintrag
gehoert VOR den Katalog (danach ist die
Schreibbremse ausgeloest, 429), und er muss
FREIGEGEBEN werden, sonst sieht die Community ihn
zu Recht nicht. Beides steht jetzt als Aussage in
der Pruefung.
GEMESSEN: alle zwoelf gruen -- 43, 10, 31, 262, 43, 15, 56, 69, 37, 85,
73 und 80 Pruefungen, zusammen 804, kein Fehler.
Co-Authored-By: Claude Opus 5 <[email protected]>
1790 lines
90 KiB
JavaScript
1790 lines
90 KiB
JavaScript
/* =====================================================================
|
||
Entwicklung & Nachwuchs — die Riegel (11.09.2026)
|
||
|
||
Der Bereich hat vier Regeln, und jede einzelne wäre ohne Prüfung
|
||
eine Zusage statt einer Eigenschaft:
|
||
|
||
1. Block 5 gehört der Person. DogFather sieht ihn nicht — lesend
|
||
UND schreibend. Mit Gegenprobe: die Person selbst sieht ihn.
|
||
2. Der Stand des anderen kommt erst nach dem eigenen Klick. Mit
|
||
Gegenprobe: danach kommt er auch wirklich.
|
||
3. „Da hakt es" ohne Anlass wird abgelehnt. Mit Gegenprobe: mit
|
||
Anlass geht es durch.
|
||
4. Talente gehört DogFather und der rechten Hand. Ein Modi bekommt
|
||
404 — und zwar auf Seite UND Schnittstelle.
|
||
|
||
Dazu der Katalog selbst (Schlüssel, Stufen, keine Dopplungen) und
|
||
das Verblassen nach 90 Tagen — von beiden Seiten der Grenze, sonst
|
||
beweist es nichts.
|
||
|
||
Aufrufen mit: node server/pruef-nachwuchs.mjs
|
||
===================================================================== */
|
||
|
||
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { request as httpAnfrage } from "node:http";
|
||
import { eigenerPort } from "./helfer-port.mjs";
|
||
import { tagLokal } from "./helfer-tag.mjs";
|
||
|
||
const PORT = await eigenerPort(import.meta, "pruef-nachwuchs");
|
||
|
||
const ordner = mkdtempSync(join(tmpdir(), "ws-nachwuchs-"));
|
||
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
||
process.env.PORT = String(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 }); };
|
||
import { notbremse } from "./helfer-notbremse.mjs";
|
||
await import("./index.js");
|
||
notbremse(240_000, "pruef-nachwuchs");
|
||
await new Promise((r) => setTimeout(r, 700));
|
||
|
||
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
||
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
||
|
||
let fehler = 0, geprueft = 0;
|
||
const melde = (t) => console.log(t);
|
||
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
||
/* DER DRITTE AUSGANG: nicht in Ordnung ist etwas anderes als "konnte
|
||
nicht nachsehen". Ohne ihn wuerde ein fehlendes Playwright entweder
|
||
als Fehler gemeldet (falscher Alarm) oder stillschweigend
|
||
uebersprungen (schlimmer -- die Zahl saenke, und niemand wuesste
|
||
warum). */
|
||
let offen = 0;
|
||
const unklar = (t) => { offen++; console.log(" -- " + t); };
|
||
|
||
const CREW = "crew.dogfather-universe.com";
|
||
/* ==== DIESE PRUEFUNG WOHNT BEI TEAM DOGI (25.09.2026) ==============
|
||
|
||
Nachwuchs, Entwicklung, Talente, die 68 Punkte -- alles davon
|
||
gehoert ins Teamhaus. Gefragt wurde trotzdem ueber die
|
||
AGENTURadresse, und seit der Trennung vom 24.09. antwortet der
|
||
Server dort richtigerweise mit 400: Einen Modi legt man dort
|
||
nicht an. Einundzwanzig Meldungen hingen an diesem einen
|
||
Aufruf -- alle Folge, keine Ursache.
|
||
|
||
Die Agenturadresse bleibt als Name stehen; gebraucht wird sie
|
||
nur noch dort, wo ausdruecklich das andere Haus gemeint ist. */
|
||
const AGENTUR = "workspace.dogfather-universe.com";
|
||
|
||
const { DatabaseSync } = await import("node:sqlite");
|
||
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
||
const kat = await import("./workspace-entwicklungs-punkte.js");
|
||
const tal = await import("./workspace-talent-punkte.js");
|
||
const rechte = await import("./rechte.js");
|
||
|
||
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);
|
||
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, createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
|
||
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
||
}
|
||
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
||
const idHand = anlegen("Rieke", "hand", "CODE-HAND-0001");
|
||
const idModi = anlegen("Frida", "modi", "CODE-MODI-0001");
|
||
anlegen("Nina", "modi", "CODE-MODI-0002");
|
||
/* MARA IST DIE LINKE HAND UND KEIN MANAGER (25.09.2026).
|
||
Diese Pruefung misst das Teamhaus; ein Manager taucht dort seit
|
||
der Haustrennung nicht mehr auf und konnte deshalb auch nichts
|
||
mehr beweisen. Als linke Hand steht sie da, wo sie gebraucht
|
||
wird: in der Rangfolge unter der rechten Hand. */
|
||
anlegen("Mara", "linke", "CODE-LINK-0001");
|
||
|
||
/* Ein Kandidat im Talente-Brett -- ohne ihn waere jede Zeile ueber den
|
||
Trichter ein gruener Haken ueber der Null. */
|
||
d.prepare("INSERT INTO eintraege (bereich, art, titel, text, datum, erstellt, erstellt_von, einsatz)"
|
||
+ " VALUES ('talente','ruhig','Kessi aus dem Chat','Faellt seit Wochen auf.',?,?,?,?)")
|
||
.run(jetzt.slice(0, 10), jetzt, idDogi, "Im Stream am Dienstag");
|
||
const idTalent = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
||
d.close();
|
||
|
||
function roh(pfad, host, kopf = {}, koerper = null, methode = null) {
|
||
if (methode === "DELETE") koerper = null;
|
||
return new Promise((fertig, schief) => {
|
||
const a = httpAnfrage({
|
||
host: "127.0.0.1", port: PORT, path: pfad,
|
||
method: methode || (koerper ? "POST" : "GET"),
|
||
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf },
|
||
}, (antwort) => {
|
||
let text = "";
|
||
antwort.on("data", (s) => { text += s; });
|
||
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
||
});
|
||
a.on("error", schief);
|
||
if (koerper) a.write(JSON.stringify(koerper));
|
||
a.end();
|
||
});
|
||
}
|
||
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
|
||
async function anmelden(rolle, code, host) {
|
||
const r = await roh("/workspace/api/anmelden", host, {}, { rolle, code });
|
||
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
||
}
|
||
const kDogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
|
||
const kHand = await anmelden("hand", "CODE-HAND-0001", CREW);
|
||
const kModi = await anmelden("modi", "CODE-MODI-0001", CREW);
|
||
ok(!!kDogi && !!kHand && !!kModi, "DogFather, rechte Hand und ein Modi sind angemeldet");
|
||
|
||
/* =======================================================================
|
||
1. DER KATALOG SELBST
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 1. Die zwei Kataloge ===");
|
||
{
|
||
const alle = kat.ENTWICKLUNG_ALLE;
|
||
ok(alle.length > 20, `${alle.length} Beobachtungen im Katalog`);
|
||
ok(new Set(alle.map((p) => p.schluessel)).size === alle.length,
|
||
"kein Schlüssel doppelt");
|
||
ok(alle.every((p) => p.schluessel.startsWith("e-")),
|
||
"jeder Schlüssel beginnt mit e- (keine Kollision mit der Checkliste)");
|
||
ok(alle.every((p) => p.titel && p.text && p.stufe),
|
||
"jede Beobachtung hat Titel, Erklärung und Erwartungsstufe");
|
||
const stufen = new Set(alle.map((p) => p.stufe));
|
||
ok(kat.ENTWICKLUNG_ERWARTUNG.every((e) => stufen.has(e.schluessel)),
|
||
`alle drei Erwartungsstufen kommen vor (${[...stufen].join(", ")})`);
|
||
|
||
/* DER RIEGEL IST EINE EIGENSCHAFT DES KATALOGS, nicht nur des
|
||
Servers: Genau ein Block ist `nurSelbst`, und die abgeleiteten
|
||
Listen passen dazu. Verrutscht das hier, greift der Server
|
||
weiterhin -- aber die Oberflaeche zeigte etwas anderes an. */
|
||
const selbstBloecke = kat.ENTWICKLUNG_BLOECKE.filter((b) => b.nurSelbst);
|
||
ok(selbstBloecke.length === 1, `genau ein Block gehört der Person selbst (${selbstBloecke.length})`);
|
||
ok(kat.ENTWICKLUNG_FREMD.length + kat.ENTWICKLUNG_SELBST.length === alle.length,
|
||
`${kat.ENTWICKLUNG_FREMD.length} fremd + ${kat.ENTWICKLUNG_SELBST.length} selbst = ${alle.length}`);
|
||
ok(kat.ENTWICKLUNG_SELBST.length > 0 && kat.ENTWICKLUNG_FREMD.length > 0,
|
||
"beide Listen haben Inhalt — sonst prüft die Zeile darüber nichts");
|
||
|
||
const merk = tal.TALENT_ALLE;
|
||
ok(merk.length > 15, `${merk.length} Merkmale im Talente-Katalog`);
|
||
ok(tal.TALENT_WARNZEICHEN.length > 0, `${tal.TALENT_WARNZEICHEN.length} Warnzeichen, getrennt geführt`);
|
||
ok(tal.TALENT_NACH_SCHLUESSEL.size === merk.length + tal.TALENT_WARNZEICHEN.length,
|
||
"kein Schlüssel doppelt zwischen Merkmalen und Warnzeichen");
|
||
ok([...merk, ...tal.TALENT_WARNZEICHEN].every((m) => m.schluessel.startsWith("t-")),
|
||
"jeder Schlüssel beginnt mit t-");
|
||
|
||
const raenge = tal.TALENT_STUFEN.map((s) => s.rang);
|
||
ok(JSON.stringify(raenge) === JSON.stringify([...raenge].sort((a, b) => a - b)),
|
||
`die fünf Stufen stehen in der richtigen Reihenfolge (${tal.TALENT_STUFEN.map((s) => s.name).join(" → ")})`);
|
||
ok(tal.TALENT_STUFEN.slice(0, -1).every((s) => s.knopf && s.schritt),
|
||
"jede Stufe außer der letzten hat GENAU EINEN nächsten Schritt mit Knopf");
|
||
ok(tal.talentNaechste("imteam") === null, "und hinter der letzten kommt nichts mehr");
|
||
ok(tal.talentStufe("gibt-es-nicht") === null,
|
||
"eine erfundene Stufe ergibt null statt der ersten — sie soll auffallen");
|
||
}
|
||
|
||
/* =======================================================================
|
||
2. BLOCK 5 GEHÖRT DER PERSON
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 2. Block 5 ===");
|
||
{
|
||
const selbst = kat.ENTWICKLUNG_SELBST[0].schluessel;
|
||
|
||
const karte = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
|
||
const bloecke = json(karte).bloecke || [];
|
||
const alleSchluessel = bloecke.flatMap((b) => (b.punkte || []).map((p) => p.schluessel));
|
||
ok(karte.code === 200 && alleSchluessel.length > 0,
|
||
`DogFather bekommt die Karte des Modis (${alleSchluessel.length} Punkte)`);
|
||
const durch = kat.ENTWICKLUNG_SELBST.filter((p) => alleSchluessel.includes(p.schluessel));
|
||
ok(durch.length === 0,
|
||
`und KEINEN Punkt aus Block 5${durch.length ? " -- DURCH: " + durch.map((p) => p.schluessel).join(", ") : ""}`);
|
||
ok(!/Wie geht es dir/.test(karte.text), "auch der Blockname steht nicht in der Antwort");
|
||
|
||
/* Und schreiben geht auch nicht -- mit derselben Antwort wie ein
|
||
erfundener Schluessel, damit man an der Antwort nichts ablesen kann. */
|
||
const schreib = await roh(`/workspace/api/entwicklung/person/${idModi}/${selbst}`, CREW,
|
||
{ cookie: kDogi }, { stufe: "laeuft" }, "PUT");
|
||
const erfunden = await roh(`/workspace/api/entwicklung/person/${idModi}/e-gibt-es-nicht`, CREW,
|
||
{ cookie: kDogi }, { stufe: "laeuft" }, "PUT");
|
||
ok(schreib.code === 404, `DogFather kann dort auch nichts setzen (${schreib.code})`);
|
||
ok(schreib.code === erfunden.code,
|
||
`dieselbe Antwort wie bei einem erfundenen Punkt (${schreib.code}/${erfunden.code})`);
|
||
|
||
/* GEGENPROBE: Die Person selbst sieht ihn sehr wohl. Ohne diese
|
||
Zeile waeren die drei darueber auch dann gruen, wenn Block 5 fuer
|
||
NIEMANDEN existierte. */
|
||
const meins = await roh("/workspace/api/entwicklung/mich", CREW, { cookie: kModi });
|
||
const meine = (json(meins).punkte || []).map((p) => p.schluessel);
|
||
ok(meins.code === 200 && meine.length === kat.ENTWICKLUNG_SELBST.length,
|
||
`der Modi selbst sieht alle ${meine.length} Fragen`);
|
||
|
||
const setzen = await roh(`/workspace/api/entwicklung/mich/${selbst}`, CREW,
|
||
{ cookie: kModi }, { stufe: "hakt" }, "PUT");
|
||
ok(setzen.code === 200, `und kann sie beantworten (${setzen.code})`);
|
||
|
||
/* Und danach steht sie immer noch nicht in DogFathers Karte. */
|
||
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
|
||
ok(!nachher.text.includes(selbst),
|
||
"auch nach der Antwort steht sie nicht in DogFathers Karte");
|
||
|
||
/* Und in die andere Richtung: ueber jemand ANDEREN kann man dort
|
||
nichts eintragen -- es gibt gar keine Nummer in der Adresse. */
|
||
const fremdPunkt = await roh(`/workspace/api/entwicklung/mich/${kat.ENTWICKLUNG_FREMD[0].schluessel}`,
|
||
CREW, { cookie: kModi }, { stufe: "laeuft" }, "PUT");
|
||
ok(fremdPunkt.code === 404,
|
||
`über „mich" lässt sich kein fremder Punkt setzen (${fremdPunkt.code})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
3. DIE AMPEL VERRÄT KEINEN NAMEN
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 3. Die Ampel ===");
|
||
{
|
||
/* BEIDE SEITEN DER GRENZE, sonst beweist es nichts.
|
||
|
||
Die Ampel schweigt, solange das Team so klein ist, dass „jemandem
|
||
geht es gerade zu viel" dasselbe waere wie ein Name. Eine Pruefung,
|
||
die nur eine Seite ansieht, waere auch dann gruen, wenn die Ampel
|
||
IMMER schwiege -- oder immer redete. Beides waere ein Fehler, und
|
||
keiner davon faellt von allein auf.
|
||
|
||
Die Schwelle wird nicht abgeschrieben, sondern aus dem Verhalten
|
||
abgeleitet: Erst mit vollem Team messen, dann eine Person
|
||
abschalten und noch einmal. Was dazwischen kippt, IST die Grenze. */
|
||
const voll = json(await roh("/workspace/api/entwicklung/ampel", CREW, { cookie: kDogi }));
|
||
ok(voll.team >= 3 && voll.sagbar === true,
|
||
`bei ${voll.team} Personen sagt die Ampel etwas (${voll.lage})`);
|
||
ok(typeof voll.geantwortet === "number" && voll.geantwortet >= 1,
|
||
`und zaehlt, wie viele geantwortet haben (${voll.geantwortet} von ${voll.team})`);
|
||
/* Ein Modi hat in Abschnitt 2 „hakt" gesetzt -- die Ampel muss das
|
||
sehen, ohne zu sagen, wer es war. */
|
||
ok(voll.lage === "jemand",
|
||
`sie meldet die Lage aus Abschnitt 2 (${voll.lage})`);
|
||
ok(!/Frida|Nina|Rieke|Filipe/.test(JSON.stringify(voll)),
|
||
"und in der ganzen Antwort steht kein einziger Name");
|
||
|
||
/* ==== DIE SCHWELLE WIRD GESUCHT, NICHT ANGENOMMEN (25.09.2026) ==
|
||
|
||
Hier wurde EINE Person abgeschaltet und erwartet, dass die Ampel
|
||
danach schweigt. Das stimmte, solange das Team genau so gross
|
||
war, wie es an dem Tag war -- und ging kaputt, als eine Rolle
|
||
dazukam (aus dem Manager im anderen Haus wurde die linke Hand in
|
||
diesem). Die Pruefung meldete „bei 3 Personen schweigt sie" und
|
||
meinte: sie tut es nicht.
|
||
|
||
Der Kommentar darueber sagt es selbst: „Die Schwelle wird nicht
|
||
abgeschrieben, sondern aus dem Verhalten abgeleitet." Eine feste
|
||
Anzahl abzuschalten IST aber eine Abschrift -- nur in einer
|
||
anderen Waehrung. Jetzt wird abgeschaltet, BIS es kippt, und
|
||
hoechstens so oft, wie es Leute gibt.
|
||
|
||
WAS DAMIT GEMESSEN WIRD, bleibt dasselbe und wird sogar
|
||
schaerfer: dass es ueberhaupt kippt (eine Ampel, die immer redet,
|
||
waere ein Bruch des Versprechens an die Leute), dass sie dabei
|
||
einen Grund nennt, und dass in keiner der beiden Antworten ein
|
||
Name steht. */
|
||
const namen = ["Nina", "Mara", "Frida", "Rieke"];
|
||
let klein = voll, abgeschaltet = 0;
|
||
for (const wen of namen) {
|
||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd.prepare("UPDATE personen SET aktiv = 0 WHERE name = ?").run(wen);
|
||
dd.close();
|
||
abgeschaltet++;
|
||
klein = json(await roh("/workspace/api/entwicklung/ampel", CREW, { cookie: kDogi }));
|
||
if (klein.sagbar === false) break;
|
||
}
|
||
ok(klein.sagbar === false && klein.team < voll.team,
|
||
`nach ${abgeschaltet} Abschaltung(en) schweigt sie `
|
||
+ `(${voll.team} -> ${klein.team} Personen)`);
|
||
ok(/Name/.test(klein.grund || ""),
|
||
"und sagt, warum – ein stilles Nichts sieht aus wie ein Fehler");
|
||
ok(!/Frida|Nina|Rieke|Filipe/.test(JSON.stringify(klein)),
|
||
"auch im schweigenden Fall steht kein Name da");
|
||
|
||
const dd2 = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd2.prepare("UPDATE personen SET aktiv = 1 WHERE name = 'Nina'").run();
|
||
dd2.close();
|
||
|
||
const modi = await roh("/workspace/api/entwicklung/ampel", CREW, { cookie: kModi });
|
||
ok(modi.code === 404, `ein Modi bekommt sie gar nicht (${modi.code})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
4. DER STAND DES ANDEREN KOMMT ERST NACH DEM EIGENEN
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 4. Zwei Augenpaare ===");
|
||
{
|
||
const punkt = kat.ENTWICKLUNG_FREMD[0].schluessel;
|
||
|
||
const gesetzt = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
|
||
{ cookie: kHand }, { stufe: "laeuft", anlass: "Gestern im Chat gesehen." }, "PUT");
|
||
ok(gesetzt.code === 200, `die rechte Hand setzt einen Punkt (${gesetzt.code})`);
|
||
|
||
const vorher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
|
||
const p1 = (json(vorher).bloecke || []).flatMap((b) => b.punkte || [])
|
||
.find((p) => p.schluessel === punkt);
|
||
ok(p1 && p1.andere === null,
|
||
"DogFather sieht ihren Stand NICHT, bevor er selbst gesetzt hat");
|
||
ok(p1 && p1.andere_offen === 1,
|
||
`aber er sieht, DASS es einen gibt (${p1?.andere_offen}) – sonst wäre „noch niemand" dasselbe Bild`);
|
||
ok(!vorher.text.includes("Gestern im Chat gesehen"),
|
||
"und ihr Anlasstext wird gar nicht erst ausgeliefert");
|
||
|
||
const eigen = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
|
||
{ cookie: kDogi }, { stufe: "waechst" }, "PUT");
|
||
ok(eigen.code === 200, `DogFather setzt selbst (${eigen.code})`);
|
||
|
||
/* GEGENPROBE: jetzt kommt er. */
|
||
const nachher = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
|
||
const p2 = (json(nachher).bloecke || []).flatMap((b) => b.punkte || [])
|
||
.find((p) => p.schluessel === punkt);
|
||
ok(p2 && p2.andere?.length === 1 && p2.andere[0].stufe === "laeuft",
|
||
`danach sieht er ihn (${p2?.andere?.[0]?.stufe})`);
|
||
|
||
/* UND DIE ABWEICHUNG IST DAS ERGEBNIS. */
|
||
const ab = json(nachher).abweichungen || [];
|
||
ok(ab.length === 1 && ab[0].schluessel === punkt,
|
||
`die Abweichung steht oben in der Karte (${ab.length})`);
|
||
ok(ab[0].meins === "waechst" && ab[0].andere === "laeuft",
|
||
`mit beiden Ständen nebeneinander (${ab[0]?.meins} / ${ab[0]?.andere})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
5. „DA HAKT ES" BRAUCHT EINEN ANLASS
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 5. Kein Klick ohne Anlass ===");
|
||
{
|
||
const punkt = kat.ENTWICKLUNG_FREMD[1].schluessel;
|
||
const ohne = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
|
||
{ cookie: kDogi }, { stufe: "hakt", anlass: "" }, "PUT");
|
||
ok(ohne.code === 400, `„Da hakt es" ohne Anlass wird abgelehnt (${ohne.code})`);
|
||
ok(/gemerkt|Satz/.test(json(ohne).fehler || ""),
|
||
"und die Absage sagt, was fehlt, statt nur nein");
|
||
|
||
const mit = await roh(`/workspace/api/entwicklung/person/${idModi}/${punkt}`, CREW,
|
||
{ cookie: kDogi }, { stufe: "hakt", anlass: "Am Samstag zweimal nicht reagiert." }, "PUT");
|
||
ok(mit.code === 200, `mit Anlass geht es durch (${mit.code})`);
|
||
|
||
/* GEGENPROBE: „Läuft" braucht keinen — sonst wären es 28 Pflichtfelder
|
||
je Person, und „so einfach wie möglich" war die halbe Aufgabe. */
|
||
const laeuft = await roh(`/workspace/api/entwicklung/person/${idModi}/${kat.ENTWICKLUNG_FREMD[2].schluessel}`,
|
||
CREW, { cookie: kDogi }, { stufe: "laeuft" }, "PUT");
|
||
ok(laeuft.code === 200, `„Läuft" geht ohne Anlass (${laeuft.code})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
6. NACH 90 TAGEN VERBLASST EINE EINSCHÄTZUNG
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 6. Verblassen ===");
|
||
{
|
||
const modul = await import("./workspace-entwicklung.js");
|
||
const alt = kat.ENTWICKLUNG_FREMD[3].schluessel;
|
||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd.prepare(`INSERT INTO entwicklung_stand (person_id, schluessel, von_id, stufe, anlass, am)
|
||
VALUES (?,?,?,'laeuft',NULL,?)`)
|
||
.run(idModi, alt, idDogi,
|
||
new Date(Date.now() - (modul.VERBLASST_NACH_TAGEN + 10) * 86400000).toISOString());
|
||
dd.close();
|
||
|
||
const r = await roh(`/workspace/api/entwicklung/person/${idModi}`, CREW, { cookie: kDogi });
|
||
const punkte = (json(r).bloecke || []).flatMap((b) => b.punkte || []);
|
||
const alterPunkt = punkte.find((p) => p.schluessel === alt);
|
||
ok(alterPunkt?.meins?.verblasst === true,
|
||
`ein Stand von vor ${modul.VERBLASST_NACH_TAGEN + 10} Tagen gilt als verblasst`);
|
||
|
||
/* GEGENPROBE, und ohne sie beweist die Zeile darüber nichts: Einer
|
||
von heute gilt NICHT als verblasst. */
|
||
const frisch = punkte.find((p) => p.schluessel === kat.ENTWICKLUNG_FREMD[0].schluessel);
|
||
ok(frisch?.meins?.verblasst === false,
|
||
"einer von heute nicht");
|
||
|
||
const lage = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kDogi }));
|
||
const zeile = (lage.personen || []).find((p) => p.id === idModi);
|
||
ok(zeile?.veraltet === 1, `und die Übersicht zählt ihn (${zeile?.veraltet})`);
|
||
ok(zeile?.abweichungen === 1, `samt der Abweichung (${zeile?.abweichungen})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
7. TALENTE GEHÖRT DOGFATHER UND DER RECHTEN HAND
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 7. Talente ===");
|
||
{
|
||
const lage = await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi });
|
||
const r = json(lage);
|
||
ok(lage.code === 200 && (r.kandidaten || []).length === 1,
|
||
`DogFather sieht den Trichter (${(r.kandidaten || []).length} Kandidat)`);
|
||
ok((r.trichter || []).length === tal.TALENT_STUFEN.length,
|
||
`alle ${r.trichter?.length} Stufen kommen mit`);
|
||
const k = r.kandidaten[0];
|
||
ok(k.stufe === "aufgefallen",
|
||
`ein neuer Kandidat startet auf „${k.stufe_name}" (${k.stufe})`);
|
||
ok(!!k.schritt && !!k.knopf, `und hat genau einen nächsten Schritt: „${k.knopf}"`);
|
||
|
||
/* DER RIEGEL. Auf Seite UND Schnittstelle -- eine Kachel, die es
|
||
nicht gibt, ist keine Sicherung. */
|
||
const modiApi = await roh("/workspace/api/talente/lage", CREW, { cookie: kModi });
|
||
ok(modiApi.code === 404, `ein Modi bekommt die Schnittstelle nicht (${modiApi.code})`);
|
||
const modiSeite = await roh("/workspace/talente.html", CREW, { cookie: kModi });
|
||
ok(modiSeite.code !== 200, `und die Seite auch nicht (${modiSeite.code})`);
|
||
const wer = rechte.SEITEN["/workspace/talente.html"] || [];
|
||
ok(wer.length === 2 && wer.includes("admin") && wer.includes("hand"),
|
||
`die Tafel gibt sie genau an ${wer.join(" und ")}`);
|
||
|
||
/* GEGENPROBE: die rechte Hand kommt hinein. */
|
||
const handApi = await roh("/workspace/api/talente/lage", CREW, { cookie: kHand });
|
||
ok(handApi.code === 200, `die rechte Hand schon (${handApi.code})`);
|
||
|
||
/* Merkmale: ein Klick setzt, der zweite nimmt zurück. */
|
||
const m = tal.TALENT_ALLE[0].schluessel;
|
||
const w = tal.TALENT_WARNZEICHEN[0].schluessel;
|
||
const eins = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW,
|
||
{ cookie: kDogi }, {}, "PUT");
|
||
ok(eins.code === 200 && json(eins).gesetzt === true, "ein Merkmal wird gesetzt");
|
||
const zwei = await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW,
|
||
{ cookie: kDogi }, {}, "PUT");
|
||
ok(zwei.code === 200 && json(zwei).gesetzt === false, "und der zweite Klick nimmt es zurück");
|
||
await roh(`/workspace/api/talente/${idTalent}/merkmal/${m}`, CREW, { cookie: kDogi }, {}, "PUT");
|
||
await roh(`/workspace/api/talente/${idTalent}/merkmal/${w}`, CREW, { cookie: kDogi }, {}, "PUT");
|
||
|
||
const nach = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi })).kandidaten[0];
|
||
ok(nach.merkmale.length === 1 && nach.warnzeichen.length === 1,
|
||
`Merkmale und Warnzeichen stehen getrennt (${nach.merkmale.length} / ${nach.warnzeichen.length})`);
|
||
ok(!nach.merkmale.includes(w),
|
||
"ein Warnzeichen landet nicht zwischen den Merkmalen");
|
||
|
||
/* Die Stufe wandert -- und die Standzeit beginnt von vorn. */
|
||
const weiter = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
|
||
{ cookie: kDogi }, { stufe: "beobachtet" }, "PUT");
|
||
ok(weiter.code === 200, `ein Schritt weiter im Trichter (${weiter.code})`);
|
||
const nach2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi })).kandidaten[0];
|
||
ok(nach2.stufe === "beobachtet" && nach2.tage === 0,
|
||
`Stufe „${nach2.stufe_name}", Standzeit beginnt bei ${nach2.tage}`);
|
||
ok(nach2.liegt === false, "und liegt damit auch nicht");
|
||
|
||
const erfunden = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
|
||
{ cookie: kDogi }, { stufe: "chef" }, "PUT");
|
||
ok(erfunden.code === 400, `eine erfundene Stufe wird abgelehnt (${erfunden.code})`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
8. LIEGENGEBLIEBEN IST EINE AUSKUNFT ÜBER UNS
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 8. Standzeit ===");
|
||
{
|
||
const stufe = tal.talentStufe("beobachtet");
|
||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd.prepare("UPDATE talent_stufe SET seit = ? WHERE eintrag_id = ?")
|
||
.run(new Date(Date.now() - (stufe.frist + 11) * 86400000).toISOString(), idTalent);
|
||
dd.close();
|
||
|
||
const r = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const k = r.kandidaten[0];
|
||
ok(k.liegt === true && k.tage === stufe.frist + 11,
|
||
`nach ${k.tage} Tagen auf einer Stufe mit Frist ${stufe.frist} gilt die Karte als liegengeblieben`);
|
||
const t = (r.trichter || []).find((x) => x.schluessel === "beobachtet");
|
||
ok(t?.liegt === 1, `und der Trichter zählt sie (${t?.liegt})`);
|
||
|
||
/* GEGENPROBE von der anderen Seite der Grenze -- sonst wäre „liegt"
|
||
auch dann grün, wenn es immer liegt. */
|
||
const dd2 = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd2.prepare("UPDATE talent_stufe SET seit = ? WHERE eintrag_id = ?")
|
||
.run(new Date(Date.now() - (stufe.frist - 1) * 86400000).toISOString(), idTalent);
|
||
dd2.close();
|
||
const r2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
ok(r2.kandidaten[0].liegt === false,
|
||
`einen Tag vor der Frist noch nicht (${r2.kandidaten[0].tage} Tage)`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
9. DER VORLAGENTEXT
|
||
|
||
Er ist der Unterschied zwischen "Ansprechen" als Aufgabe und
|
||
"Ansprechen" als erledigter Sache. Geprueft wird deshalb nicht, DASS
|
||
ein Text kommt, sondern dass er aus den angeklickten Merkmalen
|
||
gebaut ist -- sonst waere es eine Floskel mit eingesetztem Namen.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 9. Der Vorlagentext ===");
|
||
{
|
||
const zwei = [tal.TALENT_ALLE[0], tal.TALENT_ALLE[1]];
|
||
const text = tal.ansprechtext("Kessi aus dem Chat", zwei.map((m) => m.schluessel));
|
||
|
||
ok(text.startsWith("Hey Kessi,"),
|
||
"die Anrede nimmt nur den ersten Namensteil, nicht den ganzen Eintragstitel");
|
||
ok(zwei.every((m) => text.includes(m.du)),
|
||
"beide angeklickten Merkmale stehen woertlich im Text");
|
||
ok(/kein Druck|nein/.test(text),
|
||
"und der Ausstieg steht drin -- ein Angebot ohne Nein ist keines");
|
||
|
||
/* GEGENPROBE: ohne ein einziges Merkmal darf KEINES drinstehen --
|
||
sonst baut die Funktion sich etwas zusammen, das niemand beobachtet
|
||
hat. Das waere die schlimmste Sorte Vorlage. */
|
||
const leer = tal.ansprechtext("Kessi", []);
|
||
ok(!tal.TALENT_ALLE.some((m) => leer.includes(m.du)),
|
||
"ohne angeklickte Merkmale steht auch keines im Text");
|
||
ok(leer.length > 100, `aber ein vollstaendiger Text bleibt es (${leer.length} Zeichen)`);
|
||
ok(leer !== text, "und er ist ein anderer als der mit Merkmalen");
|
||
|
||
/* HAUSREGEL: In Text, den ein Mensch liest, stehen echte Umlaute.
|
||
"Haettest du Lust" war genau der Fehler, den diese Zeile findet. */
|
||
const beide = text + " " + leer;
|
||
ok(!/\b(fuer|koenn|haett|muess|ueber|waer|moecht)/i.test(beide),
|
||
"keine ASCII-Ersatzumlaute in einem Text, den ein Mensch liest");
|
||
ok(/\b(fuer)/i.test("das ist fuer dich"), "und der Sucher findet so einen auch wirklich");
|
||
|
||
/* UND ER KOMMT UEBER DIE LEITUNG AN, nicht nur aus dem Modul. */
|
||
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const k = (lage.kandidaten || [])[0];
|
||
ok(typeof k?.ansprechtext === "string" && k.ansprechtext.length > 100,
|
||
"die Karte bringt ihren Text mit");
|
||
const gesetzt = (k?.merkmale || []).map((s) => tal.TALENT_NACH_SCHLUESSEL.get(s)?.du).filter(Boolean);
|
||
ok(gesetzt.length > 0 && gesetzt.every((d) => k.ansprechtext.includes(d)),
|
||
`und zwar aus ihren ${gesetzt.length} gesetzten Merkmalen`);
|
||
}
|
||
|
||
/* =======================================================================
|
||
10. DIE EIGENE KARTE
|
||
|
||
Sie ist die Gegenprobe zur ganzen Kategorie: Ueber Menschen wird hier
|
||
aufgeschrieben, und wer beschrieben wird, darf es lesen. Aber erst,
|
||
wenn ALLE gesetzt haben -- sonst waere die erste Einschaetzung eine
|
||
Vorgabe fuer die zweite -- und ohne Namen und ohne Anlasstext.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 10. Die eigene Karte ===");
|
||
{
|
||
const meine = json(await roh("/workspace/api/entwicklung/meine-karte", CREW, { cookie: kModi }));
|
||
ok(meine.hat_karte === true, "der Modi hat eine eigene Karte");
|
||
ok(meine.beurteiler === 2,
|
||
`zwei Augenpaare urteilen ueber ihn (${meine.beurteiler}) -- er selbst zaehlt nicht mit`);
|
||
|
||
const punkte = (meine.bloecke || []).flatMap((b) => b.punkte || []);
|
||
ok(punkte.length > 0, `${punkte.length} Punkte in seiner Karte`);
|
||
|
||
/* Aus Abschnitt 4: DIESEN Punkt haben beide gesetzt (laeuft/waechst). */
|
||
const beide = punkte.find((p) => p.schluessel === kat.ENTWICKLUNG_FREMD[0].schluessel);
|
||
ok(beide && beide.offen === false, "ein Punkt, den beide gesetzt haben, ist offen");
|
||
ok(beide?.antworten?.length === 2,
|
||
`mit beiden Antworten nebeneinander (${(beide?.antworten || []).map((a) => a.name).join(" / ")})`);
|
||
ok(beide?.einig === false, "und dem ehrlichen Hinweis, dass sie sich uneinig sind");
|
||
|
||
/* GEGENPROBE 1 -- und ohne sie beweist die Zeile darueber nichts:
|
||
Einen Punkt, den erst EINER gesetzt hat, bekommt er NICHT. */
|
||
const einer = punkte.find((p) => p.schluessel === kat.ENTWICKLUNG_FREMD[1].schluessel);
|
||
ok(einer && einer.offen === true,
|
||
"einen Punkt, den erst einer gesetzt hat, sieht er noch nicht");
|
||
ok(einer && einer.antworten === undefined,
|
||
"und die Antwort des einen reist dabei auch nicht heimlich mit");
|
||
|
||
/* GEGENPROBE 2: sobald die zweite dazukommt, oeffnet er sich. */
|
||
const nach = await roh(`/workspace/api/entwicklung/person/${idModi}/${kat.ENTWICKLUNG_FREMD[1].schluessel}`,
|
||
CREW, { cookie: kHand }, { stufe: "hakt", anlass: "Zweimal zu spaet zur Schicht." }, "PUT");
|
||
ok(nach.code === 200, `die rechte Hand setzt denselben Punkt (${nach.code})`);
|
||
const meine2 = await roh("/workspace/api/entwicklung/meine-karte", CREW, { cookie: kModi });
|
||
const jetztOffen = (json(meine2).bloecke || []).flatMap((b) => b.punkte || [])
|
||
.find((p) => p.schluessel === kat.ENTWICKLUNG_FREMD[1].schluessel);
|
||
ok(jetztOffen?.offen === false && jetztOffen?.antworten?.length >= 1,
|
||
"danach steht er in seiner Karte");
|
||
|
||
/* KEINE NAMEN UND KEIN ANLASS. Beides steht in der Karte, die
|
||
DogFather sieht -- und beides waere hier ein Vertrauensbruch:
|
||
"wer hat das ueber mich gesagt" macht aus einer Entwicklung eine
|
||
Abrechnung. */
|
||
const roh2 = meine2.text;
|
||
ok(!/Filipe|Rieke/.test(roh2), "kein Name eines Beurteilers in der ganzen Antwort");
|
||
ok(!/zu spaet zur Schicht|Am Samstag/.test(roh2), "und kein einziger Anlasstext");
|
||
ok(/hakt|laeuft|waechst/.test(roh2),
|
||
"die Antworten selbst kommen aber an -- sonst waere die Zeile darueber trivial");
|
||
|
||
/* Block 5 gehoert der Person -- er steht in IHRER Selbstauskunft,
|
||
nicht in der Fremdkarte. Waere er hier, stuenden Antworten
|
||
anderer auf Fragen, die nur sie sich stellt. */
|
||
const selbst = kat.ENTWICKLUNG_SELBST.map((p) => p.schluessel);
|
||
ok(!punkte.some((p) => selbst.includes(p.schluessel)),
|
||
"Block 5 taucht in der eigenen Karte nicht als Fremdurteil auf");
|
||
|
||
/* Wer gar nicht beurteilt wird, bekommt auch keine leere Karte
|
||
vorgesetzt -- eine Karte ohne Inhalt liest sich wie ein Versaeumnis. */
|
||
/* UND DIE RECHTE HAND HAT AUCH EINE. Ueber sie urteilt nur DogFather
|
||
-- sie selbst zaehlt nicht mit, sonst waere ihre Karte erst dann
|
||
vollstaendig, wenn sie sich selbst eingeschaetzt hat. */
|
||
const ihre = json(await roh("/workspace/api/entwicklung/meine-karte", CREW, { cookie: kHand }));
|
||
ok(ihre.hat_karte === true, `auch die rechte Hand hat eine eigene Karte (${JSON.stringify(ihre).slice(0, 90)})`);
|
||
ok(ihre.beurteiler === 1, `ueber sie urteilt genau einer (${ihre.beurteiler})`);
|
||
|
||
const chef = json(await roh("/workspace/api/entwicklung/meine-karte", CREW, { cookie: kDogi }));
|
||
ok(chef.hat_karte === false, "DogFather selbst hat keine -- ueber ihn urteilt hier niemand");
|
||
}
|
||
|
||
/* =======================================================================
|
||
11. AUS DER PROBE WIRD EIN ZUGANG
|
||
|
||
Der letzte Schritt des Trichters ist kein Schritt, sondern eine
|
||
Entscheidung. Bis heute stand dort ein Satz ("Zugang in Personen &
|
||
Zugaenge anlegen"), und wer ihn vergass, hatte eine Karte auf "Im
|
||
Team" und keinen Menschen darin.
|
||
|
||
GEPRUEFT WIRD VOR ALLEM EINES: dass es dafuer keinen ZWEITEN Weg
|
||
gibt. Ein Zugang entsteht im ganzen Haus an genau einer Stelle.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 11. Probe → Zugang ===");
|
||
{
|
||
const letzte = tal.TALENT_STUFEN.at(-1);
|
||
ok(!/Personen/.test(letzte.schritt || ""),
|
||
"die letzte Stufe schickt niemanden mehr von Hand in die Personenverwaltung");
|
||
|
||
/* EIN EINZIGER WEG, und es ist der vorhandene. */
|
||
const js = readFileSync(new URL("../workspace/assets/js/talente.js", import.meta.url), "utf8");
|
||
/* OHNE DIE KOMMENTARE gezaehlt: Der Weg wird daneben auch erklaert,
|
||
und eine Erklaerung ist kein zweiter Weg. */
|
||
const jsCode = js.replace(/\/\*[\s\S]*?\*\//g, "");
|
||
const wege = jsCode.split("/workspace/api/verwaltung/personen").length - 1;
|
||
ok(wege === 1, `talente.js kennt genau einen Weg zum Anlegen (${wege})`);
|
||
ok(js.split("/workspace/api/verwaltung/personen").length - 1 > wege,
|
||
"und der Kommentar daneben nennt ihn beim Namen -- sonst zaehlt die Zeile darueber nichts weg");
|
||
const eigen = readFileSync(new URL("../server/workspace-entwicklung.js", import.meta.url), "utf8");
|
||
ok(!/personAnlegen|INSERT INTO personen/.test(eigen),
|
||
"und der Talente-Server legt selbst keinen einzigen Zugang an");
|
||
|
||
/* DIE RECHTE HAND LEGT ZUGAENGE AN -- SEIT DEM 22.09.2026.
|
||
|
||
Hier stand bis zum 23.09. das Gegenteil: "vergibt aber keine
|
||
Zugaenge", geprueft mit 404. Das war bis zum 22.09. richtig. Dann
|
||
hat Filipe es ausdruecklich umgedreht -- "damit sie das auch
|
||
machen kann wenn er live ist" -- und die Route in
|
||
workspace-personen.js oeffnet seither genau zwei Wege: anlegen
|
||
und einen Code neu erzeugen.
|
||
|
||
DIE PRUEFUNG WAR ALSO SEIT EINEM TAG ROT, ohne dass es jemandem
|
||
aufgefallen waere -- sie lief in der Zwischenzeit nicht. Das ist
|
||
der Normalfall und nicht die Ausnahme: Man startet die
|
||
Pruefungen, an denen man gerade baut.
|
||
|
||
UMGEDREHT WIRD SIE, NICHT GELOESCHT. Eine Erlaubnis ohne Grenze
|
||
ist keine Entscheidung, sondern ein Loch. Geprueft wird deshalb
|
||
ab jetzt beides: dass die rechte Hand es DARF -- und dass die
|
||
Grenzen daneben stehen bleiben. */
|
||
const hand = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kHand },
|
||
{ name: "Heimlich", rolle: "modi" });
|
||
ok(hand.code === 201, `die rechte Hand darf einen Zugang anlegen (${hand.code})`);
|
||
|
||
/* ABER NICHT ALLES. Sperren gehoert nicht dazu -- das ist die
|
||
Grenze, die "anlegen" von "verwalten" trennt. Ohne diese Zeile
|
||
waere die Aenderung oben eine Erlaubnis ohne Rand. */
|
||
const handSperrt = await roh(`/workspace/api/verwaltung/personen/${json(hand).id}/aktiv`,
|
||
CREW, { cookie: kHand }, { aktiv: 0 }, "PUT");
|
||
ok(handSperrt.code === 404 || handSperrt.code === 403,
|
||
`aber sperren darf sie niemanden (${handSperrt.code})`);
|
||
|
||
/* UND DAS PROTOKOLL BLEIBT BEI DOGFATHER. Dort stehen die
|
||
Einstellungsaenderungen des ganzen Hauses. */
|
||
/* ==== INS PROTOKOLL SIEHT SIE SEHR WOHL (25.09.2026) ===========
|
||
|
||
Hier stand „und ins Protokoll sieht sie nicht", geprueft mit
|
||
404/403. Das war richtig, bis Filipe es umgedreht hat: „sie soll
|
||
die gleichen rechte haben wie dogfather, das einzige was sie
|
||
nicht kann ist die dogfather rolle oder leute anfassen." Fuer das
|
||
Protokoll laesst dieser Satz keinen Rest -- und
|
||
workspace-personen.js oeffnet den Weg seither ausdruecklich
|
||
(`protokollWeg`, nur GET, ohne Nummer im Pfad).
|
||
|
||
ZUM ZWEITEN MAL DIESELBE STELLE. Am 23.09. wurde hier schon
|
||
einmal aus „darf nicht" ein „darf" -- damals beim Anlegen. Eine
|
||
Pruefung, die eine Erlaubnis festhaelt, veraltet genau dann, wenn
|
||
jemand die Erlaubnis erweitert; das ist kein Fehler der Pruefung,
|
||
sondern ihr Zweck. Gemeldet werden muss es trotzdem, sonst steht
|
||
sie rot herum und niemand liest sie mehr.
|
||
|
||
GEPRUEFT WIRD JETZT BEIDES: dass sie hineinsieht -- und dass es
|
||
wirklich das Protokoll ist und nicht eine leere Antwort, die
|
||
zufaellig 200 heisst. */
|
||
const handProtokoll = await roh("/workspace/api/verwaltung/protokoll", CREW, { cookie: kHand });
|
||
ok(handProtokoll.code === 200,
|
||
`und ins Protokoll sieht sie sehr wohl (${handProtokoll.code})`);
|
||
const handZeilen = json(handProtokoll).eintraege;
|
||
ok(Array.isArray(handZeilen),
|
||
` und bekommt wirklich Eintraege (${Array.isArray(handZeilen)
|
||
? handZeilen.length + " Stueck" : typeof handZeilen})`);
|
||
|
||
/* EIN MODI DARF WEITERHIN GAR NICHTS DAVON. Das ist die eigentliche
|
||
Aussage dieses Blocks -- und sie ist unveraendert. */
|
||
const einModi = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kModi },
|
||
{ name: "Heimlich", rolle: "modi" });
|
||
ok(einModi.code === 404, `ein Modi kommt am Anlegen nicht vorbei (${einModi.code})`);
|
||
|
||
/* GEGENPROBE: DogFather kann es -- sonst waere "404" oben auch dann
|
||
gruen, wenn der Weg gar nicht existiert. */
|
||
const neu = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi },
|
||
{ name: "Kessi", rolle: "modi" });
|
||
ok(neu.code === 201, `DogFather legt den Zugang an (${neu.code})`);
|
||
const person = json(neu);
|
||
ok(person.rolle === "modi", `und zwar als Modi (${person.rolle})`);
|
||
ok(typeof person.code === "string" && person.code.length > 8,
|
||
"der Zugangscode kommt genau einmal zurueck");
|
||
|
||
/* UND DANACH IST ER WEG. Nirgends abrufbar -- auch nicht fuer den,
|
||
der ihn angelegt hat. */
|
||
const liste = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi });
|
||
ok(liste.code === 200 && !liste.text.includes(person.code),
|
||
"danach steht er in keiner Liste mehr");
|
||
|
||
/* Erst jetzt wandert die Karte -- in derselben Reihenfolge wie in der
|
||
Oberflaeche: erst der Mensch, dann der Haken. */
|
||
const stufe = await roh(`/workspace/api/talente/${idTalent}/stufe`, CREW,
|
||
{ cookie: kDogi }, { stufe: "imteam" }, "PUT");
|
||
ok(stufe.code === 200, `und die Karte geht auf "Im Team" (${stufe.code})`);
|
||
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const k = (lage.kandidaten || []).find((x) => x.id === idTalent);
|
||
ok(k?.stufe === "imteam" && k?.knopf === null,
|
||
"dahinter kommt kein weiterer Knopf -- der Trichter ist zu Ende");
|
||
|
||
/* Der neue Mensch ist wirklich da, mit allem, was daran haengt. */
|
||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
const z = dd.prepare("SELECT rolle, aktiv FROM personen WHERE name = 'Kessi'").get();
|
||
dd.close();
|
||
ok(z?.rolle === "modi" && z?.aktiv === 1, "und er steht als aktiver Modi in der Datenbank");
|
||
}
|
||
|
||
/* =======================================================================
|
||
12. DIE REIHENFOLGE — NACH RANG, NICHT NACH NAMEN
|
||
|
||
Filipe, 11.09.2026 mit Bildschirmfoto der Liste "Wen gehst du
|
||
durch?" (Diene, Dogi, VanVan — alphabetisch, die rechte Hand
|
||
ganz rechts): "rechte hand soll immer als erstes sein. ueberall.
|
||
... ausser bei personen und zugaengen soll sie ueber jedem aber
|
||
unter dogfather sein."
|
||
|
||
WARUM DIE NAMEN IM PRUEFSTAND SO GEWAEHLT SIND: Rieke (rechte Hand)
|
||
steht alphabetisch HINTER Frida, Mara und Nina. Waere die Sortierung
|
||
auch nur an einer Stelle wieder die alte, stuende sie dort hinten —
|
||
und die Pruefung wuerde es sehen. Mit einem Namen wie "Anna" waere
|
||
jede Zeile hier gruen, ohne irgendetwas zu messen.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 12. Die Reihenfolge ===");
|
||
{
|
||
/* Die Quelle selbst: EINE Liste, und der SQL-Ausdruck daraus
|
||
abgeleitet. Zwei gepflegte Listen waeren die Stelle, an der es
|
||
beim naechsten Mal auseinanderlaeuft. */
|
||
const w = await import("./workspace.js");
|
||
const reihe = w.ROLLEN_REIHE;
|
||
ok(reihe.indexOf("hand") === reihe.indexOf("admin") + 1,
|
||
`die rechte Hand steht direkt hinter DogFather (${reihe.join(" > ")})`);
|
||
ok(reihe.every((r, i) => w.ROLLEN_SORTIERUNG.includes(`WHEN '${r}' THEN ${i}`)),
|
||
"der SQL-Ausdruck kennt jede Rolle mit genau ihrer Nummer");
|
||
ok(w.ROLLEN_SORTIERUNG.split("WHEN").length - 1 === reihe.length,
|
||
`und keine darueber hinaus (${w.ROLLEN_SORTIERUNG.split("WHEN").length - 1} von ${reihe.length})`);
|
||
/* Die Aufrufer stellen "rolle" per .replace() auf "p.rolle" um --
|
||
das trifft nur die erste Fundstelle. Kaeme das Wort zweimal vor,
|
||
waere die zweite Haelfte des Ausdrucks still falsch. */
|
||
ok(w.ROLLEN_SORTIERUNG.split("rolle").length - 1 === 1,
|
||
"das Wort „rolle“ kommt darin genau einmal vor");
|
||
ok(!w.ROLLEN_REIHE.includes("gast"),
|
||
"die Community steht nicht in der Rangfolge — sie faellt ans Ende");
|
||
|
||
/* ---- Die Liste aus dem Bildschirmfoto ---- */
|
||
const lage = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kDogi }));
|
||
const namen = (lage.personen || []).map((p) => p.name);
|
||
ok(namen.length >= 3, `${namen.length} Menschen in „Wen gehst du durch?“ (${namen.join(", ")})`);
|
||
ok(namen[0] === "Rieke", `die rechte Hand steht ganz links (${namen[0]})`);
|
||
/* GEGENPROBE, und ohne sie beweist die Zeile darueber nichts:
|
||
Alphabetisch stuende sie HINTEN. Dass beides verschieden ist, ist
|
||
der eigentliche Beweis, dass hier nach Rang sortiert wird. */
|
||
const alphabetisch = [...namen].sort((a, b) => a.localeCompare(b, "de"));
|
||
ok(alphabetisch[0] !== "Rieke",
|
||
`alphabetisch stuende dort ${alphabetisch[0]} — die Reihenfolge ist also keine Namensfolge`);
|
||
|
||
/* ---- Personen & Zugaenge: unter DogFather, ueber allen anderen ---- */
|
||
const liste = json(await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi }));
|
||
const alle = (liste.personen || []).map((p) => ({ name: p.name, rolle: p.rolle }));
|
||
ok(alle.length >= 4, `${alle.length} Menschen in „Personen & Zugaenge“`);
|
||
const platz = (rolle) => alle.findIndex((p) => p.rolle === rolle);
|
||
const vonHand = platz("hand");
|
||
ok(vonHand >= 0, "die rechte Hand steht in der Liste");
|
||
ok(platz("admin") >= 0 && platz("admin") < vonHand,
|
||
`DogFather steht ueber ihr (Platz ${platz("admin") + 1} vor ${vonHand + 1})`);
|
||
const darunter = alle.filter((p, i) => i > vonHand).map((p) => p.rolle);
|
||
const darueber = alle.filter((p, i) => i < vonHand).map((p) => p.rolle);
|
||
ok(darueber.every((r) => r === "admin" || r === "spicy"),
|
||
`ueber ihr steht nur die Leitung (${darueber.join(", ") || "niemand"})`);
|
||
/* ==== IM TEAMHAUS STEHEN TEAMROLLEN (25.09.2026) ================
|
||
|
||
Hier stand `darunter.includes("manager")`. Seit der Haustrennung
|
||
vom 24.09. liefert die Personenliste nur noch das Haus, aus dem
|
||
gefragt wird -- und diese Pruefung fragt aus dem Teamhaus. Ein
|
||
Manager kommt dort nicht mehr vor, und das ist die Absicht:
|
||
„Eine Aenderung an einem der beiden Haeuser darf im anderen
|
||
nichts bewirken."
|
||
|
||
Gemessen wird deshalb, was hier wirklich unter der rechten Hand
|
||
steht: die linke Hand und die Modis. Die Aussage bleibt dieselbe
|
||
-- nach Rang sortiert, die Leitung oben -- nur eben mit den
|
||
Leuten, die es in diesem Haus gibt. */
|
||
ok(darunter.includes("linke") && darunter.includes("modi"),
|
||
`unter ihr stehen die linke Hand und die Modis (${[...new Set(darunter)].join(", ")})`);
|
||
ok(!darunter.includes("manager") && !darueber.includes("manager"),
|
||
"und KEIN Manager -- der gehoert ins andere Haus (Gegenprobe zur Trennung)");
|
||
|
||
/* ---- Und dieselbe Regel im Haus nebenan ---- */
|
||
const crew = json(await roh("/workspace/api/entwicklung/lage", CREW, { cookie: kHand }));
|
||
const crewNamen = (crew.personen || []).map((p) => p.name);
|
||
ok(crewNamen[0] === "Rieke",
|
||
`auch auf der Team-Adresse steht sie vorn (${crewNamen.join(", ")})`);
|
||
}
|
||
|
||
|
||
/* =======================================================================
|
||
13. DIE PROBEZEIT HAT EINEN INHALT (17.09.2026)
|
||
|
||
Filipe: „wie talente bewertet werden, aufgaben bekommen, analysiert
|
||
werden von vanvan und mir."
|
||
|
||
Gemessen war vorher: Die Stufe „Probe" hatte eine Frist von 30 Tagen
|
||
und sonst nichts. Worauf die Entscheidung am Tag 30 sich gruenden
|
||
soll, stand nirgends -- und dann entscheidet das Bauchgefuehl
|
||
dessen, der zuletzt etwas gesehen hat.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 13. Die Probezeit ===");
|
||
{
|
||
const probeplan = await import("./workspace-probeplan.js");
|
||
|
||
/* ---- Der Katalog selbst ---- */
|
||
ok(probeplan.PROBE_ALLE.length >= 10,
|
||
`der Plan hat ${probeplan.PROBE_ALLE.length} Schritte in ${probeplan.PROBE_PHASEN.length} Phasen`);
|
||
{
|
||
const s = probeplan.PROBE_ALLE.map((x) => x.schluessel);
|
||
ok(new Set(s).size === s.length, "die Schluessel sind eindeutig");
|
||
}
|
||
{
|
||
/* JEDER SCHRITT HAT EINEN ZUSTAENDIGEN. Ohne ihn wandern in jeder
|
||
Probezeit dieselben drei an niemanden. */
|
||
const ohne = probeplan.PROBE_ALLE.filter((x) => !probeplan.PROBE_WER[x.wer]);
|
||
ok(ohne.length === 0,
|
||
ohne.length ? `ohne Zustaendigen: ${ohne.map((x) => x.schluessel).join(", ")}`
|
||
: "jeder Schritt sagt, WER ihn tut");
|
||
const wer = new Set(probeplan.PROBE_ALLE.map((x) => x.wer));
|
||
ok(wer.size === 3,
|
||
`und es sind wirklich drei verschiedene (${[...wer].join(", ")}) -- `
|
||
+ "eine Probe, die nur der Kandidat abarbeitet, prueft nichts");
|
||
}
|
||
{
|
||
/* JEDER SCHRITT SAGT, WOFUER ER DA IST. Ein Haken, dessen Zweck man
|
||
nicht kennt, wird gesetzt, weil er dasteht. */
|
||
const duenn = probeplan.PROBE_ALLE.filter((x) => (x.wofuer || "").length < 40);
|
||
ok(duenn.length === 0,
|
||
duenn.length ? `ohne Begruendung: ${duenn.map((x) => x.schluessel).join(", ")}`
|
||
: "und jeder sagt, welche Frage er beantwortet");
|
||
}
|
||
|
||
/* ---- Entscheidbar heisst NICHT „alles abgehakt" ---- */
|
||
{
|
||
const leer = probeplan.probeStand([]);
|
||
const voll = probeplan.probeStand(probeplan.PROBE_ALLE.map((x) => x.schluessel));
|
||
ok(leer.entscheidbar === false && leer.fehlt_noetig.length === 3,
|
||
`ohne einen Haken kann man nicht entscheiden (es fehlen ${leer.fehlt_noetig.length})`);
|
||
ok(voll.entscheidbar === true, "mit allen Haken schon");
|
||
/* DER FALL DAZWISCHEN, und der ist der eigentliche Punkt: Neun von
|
||
zwoelf Haken reichen NICHT, wenn die drei noetigen fehlen. Eine
|
||
Schwelle nach Anzahl waere Fleiss beim Abhaken, keine
|
||
Beurteilung. */
|
||
const ohneNoetige = probeplan.PROBE_ALLE
|
||
.map((x) => x.schluessel)
|
||
.filter((s) => !["pr-allein-schicht", "pr-team-gefragt", "pr-er-will-noch"].includes(s));
|
||
const fast = probeplan.probeStand(ohneNoetige);
|
||
ok(fast.getan === probeplan.PROBE_ALLE.length - 3 && fast.entscheidbar === false,
|
||
`${fast.getan} von ${fast.gesamt} Haken reichen nicht -- es kommt auf die drei an, `
|
||
+ "nicht auf die Anzahl");
|
||
/* Und die Gegenprobe dazu: NUR die drei genuegen. */
|
||
const nurDrei = probeplan.probeStand(
|
||
["pr-allein-schicht", "pr-team-gefragt", "pr-er-will-noch"]);
|
||
ok(nurDrei.entscheidbar === true,
|
||
`drei von ${nurDrei.gesamt} genuegen, wenn es die richtigen sind`);
|
||
}
|
||
|
||
/* ---- Am lebenden Kandidaten ----
|
||
|
||
EIN EIGENER, FRISCHER. Der aus den Abschnitten davor ist laengst
|
||
durch den ganzen Trichter gelaufen und steht auf "imteam" -- an ihm
|
||
waere "vor der Probe steht keine Liste" nicht falsch, sondern
|
||
unpruefbar. Beim ersten Anlauf war genau das der Grund fuer vier
|
||
rote Zeilen: meine Annahme, nicht der Code. */
|
||
const neuerTalent = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
|
||
{ art: "ruhig", titel: "Jonas aus dem Chat", text: "Faellt seit kurzem auf." });
|
||
ok(neuerTalent.code === 201 || neuerTalent.code === 200,
|
||
`ein frischer Kandidat fuer diesen Abschnitt (${neuerTalent.code})`);
|
||
const lage = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const kandidat = (lage.kandidaten || []).find((k) => k.titel === "Jonas aus dem Chat");
|
||
ok(!!kandidat, `er steht im Trichter (${kandidat?.stufe})`);
|
||
|
||
/* SOLANGE ER NICHT IN DER PROBE IST, gibt es keine Liste. Eine leere
|
||
Zwoelferliste an einem Beobachteten waere Laerm -- und Laerm liest
|
||
man auch dann weg, wenn er gilt. */
|
||
ok(!kandidat.probe,
|
||
`vor der Probe steht dort keine Liste (${kandidat.probe ? "doch eine" : "keine"})`);
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
|
||
CREW, { cookie: kDogi }, {}, "PUT");
|
||
ok(r.code === 409,
|
||
`und abhaken geht nicht, solange die Probe nicht laeuft (${r.code})`);
|
||
}
|
||
|
||
/* ---- Jetzt in die Probe ---- */
|
||
/* Ortszeit, nicht UTC: Zwischen 00:00 und 02:00 waere „morgen“ aus
|
||
UTC gerechnet heute -- und der Server lehnt ein Datum in der
|
||
Vergangenheit ab. Die Pruefung waere nachts rot, ohne dass
|
||
irgendetwas kaputt ist. */
|
||
const morgen = tagLokal(1);
|
||
{
|
||
/* Ueber den vorhandenen Weg, nicht an ihm vorbei. */
|
||
let stufe = kandidat.stufe;
|
||
let schutz = 0;
|
||
while (stufe !== "probe" && schutz++ < 6) {
|
||
const naechste = tal.talentNaechste(stufe)?.schluessel;
|
||
if (!naechste) break;
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/stufe`, CREW,
|
||
{ cookie: kDogi },
|
||
naechste === "probe"
|
||
? { stufe: naechste, buddy_id: idModi, erste_schicht: morgen }
|
||
: { stufe: naechste },
|
||
"PUT");
|
||
if (r.code !== 200) break;
|
||
stufe = naechste;
|
||
}
|
||
ok(stufe === "probe", `der Kandidat steht jetzt in der Probe (${stufe})`);
|
||
}
|
||
|
||
{
|
||
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.kandidaten.find((x) => x.id === kandidat.id);
|
||
ok(!!k.probe, "jetzt steht die Liste an der Karte");
|
||
ok(k.probe.gesamt === probeplan.PROBE_ALLE.length && k.probe.getan === 0,
|
||
`${k.probe.getan} von ${k.probe.gesamt} -- und noch nichts abgehakt`);
|
||
ok(k.probe.entscheidbar === false,
|
||
"und noch nicht entscheidbar -- am Tag 1 waere alles andere gelogen");
|
||
const mitWer = k.probe.schritte.filter((s) => s.wer_name);
|
||
ok(mitWer.length === k.probe.gesamt,
|
||
`jeder Schritt bringt seinen Zustaendigen mit (${mitWer.length})`);
|
||
}
|
||
|
||
/* ---- Abhaken, und WER es war ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
|
||
CREW, { cookie: kDogi }, {}, "PUT");
|
||
ok(r.code === 200 && json(r).fertig === true, `abhaken geht (${r.code})`);
|
||
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.kandidaten.find((x) => x.id === kandidat.id);
|
||
const s = k.probe.schritte.find((x) => x.schluessel === "pr-erste-schicht");
|
||
ok(s.fertig === true && s.von === "Filipe",
|
||
`und es steht dabei, wer es war (${s.von})`);
|
||
ok(k.probe.getan === 1, `der Stand zieht mit (${k.probe.getan} von ${k.probe.gesamt})`);
|
||
}
|
||
{
|
||
/* EIN KNOPF, ZWEI RICHTUNGEN -- wie bei den Merkmalen. */
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
|
||
CREW, { cookie: kDogi }, {}, "PUT");
|
||
ok(r.code === 200 && json(r).fertig === false, "ein zweiter Klick nimmt zurueck");
|
||
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.kandidaten.find((x) => x.id === kandidat.id);
|
||
ok(k.probe.getan === 0, "und der Stand geht mit zurueck");
|
||
}
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/voellig-erfunden`,
|
||
CREW, { cookie: kDogi }, {}, "PUT");
|
||
ok(r.code === 404, `ein erfundener Schritt wird abgewiesen (${r.code})`);
|
||
}
|
||
|
||
/* ---- Und die Frage, wegen der das alles da ist ---- */
|
||
{
|
||
for (const s of ["pr-allein-schicht", "pr-team-gefragt", "pr-er-will-noch"]) {
|
||
await roh(`/workspace/api/talente/${kandidat.id}/probe/${s}`, CREW,
|
||
{ cookie: kDogi }, {}, "PUT");
|
||
}
|
||
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.kandidaten.find((x) => x.id === kandidat.id);
|
||
ok(k.probe.entscheidbar === true,
|
||
`mit den drei noetigen Schritten ist es entscheidbar (${k.probe.getan} von ${k.probe.gesamt})`);
|
||
ok(Array.isArray(k.probe.fragen) && k.probe.fragen.length === 3,
|
||
`und drei Fragen stehen bereit (${k.probe.fragen?.length})`);
|
||
}
|
||
|
||
/* ---- Wer was gesehen hat ---- */
|
||
{
|
||
const m = tal.TALENT_MERKMALE[tal.TALENT_GRUPPEN[0].schluessel][0].schluessel;
|
||
await roh(`/workspace/api/talente/${kandidat.id}/merkmal/${m}`, CREW,
|
||
{ cookie: kDogi }, {}, "PUT");
|
||
const k = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.kandidaten.find((x) => x.id === kandidat.id);
|
||
ok(k.gesehen_von?.[m] === "Filipe",
|
||
`an einem Merkmal steht, wer es gesehen hat (${k.gesehen_von?.[m]})`);
|
||
/* GEGENPROBE: Ein nicht gesetztes Merkmal hat auch keinen Namen --
|
||
sonst hiesse "steht dabei" nur, dass ueberall etwas steht. */
|
||
const ander = tal.TALENT_MERKMALE[tal.TALENT_GRUPPEN[0].schluessel][1]?.schluessel;
|
||
ok(!ander || !k.gesehen_von?.[ander],
|
||
"und an einem nicht gesetzten steht keiner");
|
||
}
|
||
|
||
/* ---- Die Community kommt an nichts davon heran ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${kandidat.id}/probe/pr-erste-schicht`,
|
||
CREW, { cookie: kModi }, {}, "PUT");
|
||
ok(r.code === 404, `ein Modi kann nichts abhaken -- und erfaehrt nicht, dass es das gibt (${r.code})`);
|
||
}
|
||
}
|
||
|
||
/* =======================================================================
|
||
14. DER AUSGANG AUS DEM TRICHTER (17.09.2026)
|
||
|
||
Filipe: „alles moegliche, wie gesagt ich will hoch profissionel
|
||
arbeiten."
|
||
|
||
Gemessen vor dem Bau: talentNaechste kettet aufgefallen -> beobachtet
|
||
-> angesprochen -> probe -> imteam, und danach kommt nichts. Ein
|
||
Kandidat, der nicht passt, blieb auf seiner Stufe stehen -- mit
|
||
wachsender Standzeit und rotem "liegt"-Zeichen -- obwohl der Text der
|
||
Stufe woertlich verspricht: "uebernehmen ... oder sauber beenden".
|
||
|
||
Geprueft wird hier DREIERLEI, und der dritte Punkt ist der, wegen dem
|
||
es die Spalten und nicht eine neue Stufe geworden ist:
|
||
1. Der Satz ist Pflicht -- und die Grenze beisst wirklich.
|
||
2. Beendete verschwinden aus der Arbeitsliste, nicht aus dem Haus.
|
||
3. Die STUFE bleibt stehen: Man sieht, WO es geendet hat.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 14. Der Ausgang ===");
|
||
{
|
||
/* Jonas steht aus Abschnitt 13 in der Probe -- genau der interessante
|
||
Fall: Wer in der Probe aussteigt, hat eine andere Geschichte als
|
||
wer nach dem ersten Hinsehen aussteigt. */
|
||
const vorher = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const jonas = (vorher.kandidaten || []).find((k) => k.titel === "Jonas aus dem Chat");
|
||
ok(!!jonas && jonas.stufe === "probe",
|
||
`Jonas steht in der Arbeitsliste, Stufe ${jonas?.stufe}`);
|
||
const stufeVorher = (vorher.trichter || []).find((x) => x.schluessel === "probe")?.anzahl;
|
||
|
||
/* ---- Der Satz ist Pflicht ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
|
||
{ cookie: kDogi }, { grund: "" }, "PUT");
|
||
ok(r.code === 400, `ohne Satz geht es nicht (${r.code})`);
|
||
ok((json(r).fehler || "").length > 20,
|
||
"und es steht dabei, warum der Satz verlangt wird");
|
||
}
|
||
{
|
||
/* DIE GRENZE BEISST WIRKLICH. "Passt nicht" hat neun Zeichen -- die
|
||
haeufigste Nicht-Begruendung ueberhaupt, und genau die soll
|
||
auffallen. Ohne diese Zeile wuesste man nur, dass ein LEERES Feld
|
||
abgewiesen wird, und das ist eine andere, viel schwaechere
|
||
Aussage. */
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
|
||
{ cookie: kDogi }, { grund: "passt nix" }, "PUT");
|
||
ok(r.code === 400, `neun Zeichen sind noch kein Satz (${r.code})`);
|
||
}
|
||
|
||
/* ---- Beenden ---- */
|
||
const GRUND = "Hat sich nach der zweiten Schicht nicht mehr gemeldet.";
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
|
||
{ cookie: kDogi }, { grund: GRUND }, "PUT");
|
||
ok(r.code === 200 && json(r).beendet === true, `mit Satz geht es (${r.code})`);
|
||
}
|
||
{
|
||
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
ok(!(l.kandidaten || []).some((k) => k.id === jonas.id),
|
||
"er steht nicht mehr in der Arbeitsliste");
|
||
const b = (l.beendete || []).find((k) => k.id === jonas.id);
|
||
ok(!!b, "aber er ist da -- unter den Beendeten");
|
||
ok(b?.beendet?.grund === GRUND, `der Satz steht dabei (${b?.beendet?.grund})`);
|
||
ok(b?.beendet?.von === "Filipe", `und wer es war (${b?.beendet?.von})`);
|
||
/* DER EIGENTLICHE PUNKT: Die Stufe blieb stehen. Waere daraus ein
|
||
eigener Stufenwert "beendet" geworden, stuende hier "beendet" --
|
||
und die Frage "wie weit war er gekommen?" waere fuer immer
|
||
unbeantwortbar. */
|
||
ok(b?.stufe === "probe",
|
||
`und WO es geendet hat: ${b?.stufe_name} -- nicht bloss "beendet"`);
|
||
ok(b?.liegt === false,
|
||
"er liegt nicht mehr -- eine beendete Karte wird nicht rot");
|
||
const nachher = (l.trichter || []).find((x) => x.schluessel === "probe")?.anzahl;
|
||
ok(nachher === stufeVorher - 1,
|
||
`der Trichter zaehlt ihn nicht mehr mit (${stufeVorher} -> ${nachher})`);
|
||
}
|
||
|
||
/* ---- Wieder aufnehmen ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, CREW,
|
||
{ cookie: kDogi }, null, "PUT");
|
||
ok(r.code === 200, `wieder aufnehmen geht (${r.code})`);
|
||
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const k = (l.kandidaten || []).find((x) => x.id === jonas.id);
|
||
ok(!!k, "er steht wieder in der Arbeitsliste");
|
||
ok(k?.stufe === "aufgefallen",
|
||
`und faengt vorne an (${k?.stufe}) -- ein Jahr altes Urteil gilt nicht weiter`);
|
||
/* DER SATZ VON DAMALS BLEIBT. Er ist beim zweiten Anlauf das
|
||
Wertvollste, was man hat -- und genau er wuerde beim Zuruecksetzen
|
||
am leichtesten verschwinden. */
|
||
ok((k?.notiz || "").includes(GRUND),
|
||
"der Grund von damals steht in der Notiz");
|
||
ok(!k?.beendet, "und die Karte ist nicht mehr als beendet gezeichnet");
|
||
/* Die Probe faengt neu an -- alte Haken wuerden eine Probezeit
|
||
beschreiben, die vier Wochen zurueckliegt. */
|
||
ok(!(l.beendete || []).some((x) => x.id === jonas.id),
|
||
"aus der Sammlung der Beendeten ist er heraus");
|
||
}
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, CREW,
|
||
{ cookie: kDogi }, null, "PUT");
|
||
ok(r.code === 409, `wer laeuft, kann nicht wieder aufgenommen werden (${r.code})`);
|
||
}
|
||
|
||
/* ---- Wer im Team ist, wird nicht hier beendet ----
|
||
|
||
Gefunden am Bildschirmfoto: Auf einer Karte auf "Im Team" stand
|
||
"Nichts mehr zu tun -- der Zugang steht" und direkt darunter
|
||
"Passt doch nicht?". Ein Beenden dort haette die KARTE stillgelegt
|
||
und den ZUGANG offengelassen -- und das ist schlimmer als gar kein
|
||
Ausgang, weil man glaubt, gehandelt zu haben. */
|
||
{
|
||
const l = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
const drin = (l.kandidaten || []).find((k) => k.stufe === "imteam");
|
||
ok(!!drin, `es gibt einen im Team zum Gegenprobieren (${drin?.titel})`);
|
||
const r = await roh(`/workspace/api/talente/${drin.id}/beenden`, CREW,
|
||
{ cookie: kDogi }, { grund: GRUND }, "PUT");
|
||
ok(r.code === 409, `wer im Team ist, wird nicht hier beendet (${r.code})`);
|
||
ok((json(r).fehler || "").includes("Zug"),
|
||
`und es steht dabei, wo stattdessen (${(json(r).fehler || "").slice(0, 40)}...)`);
|
||
const l2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
ok((l2.kandidaten || []).some((k) => k.id === drin.id),
|
||
"er steht unveraendert in der Liste -- nichts halb passiert");
|
||
}
|
||
|
||
/* ---- Und die Gegenproben ---- */
|
||
{
|
||
const r = await roh("/workspace/api/talente/999999/beenden", CREW,
|
||
{ cookie: kDogi }, { grund: GRUND }, "PUT");
|
||
ok(r.code === 404, `eine erfundene Nummer wird abgewiesen (${r.code})`);
|
||
}
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/beenden`, CREW,
|
||
{ cookie: kModi }, { grund: GRUND }, "PUT");
|
||
ok(r.code === 404,
|
||
`ein Modi kann niemanden beenden -- und erfaehrt nicht, dass es das gibt (${r.code})`);
|
||
}
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${jonas.id}/wieder`, CREW,
|
||
{ cookie: kModi }, null, "PUT");
|
||
ok(r.code === 404, `dasselbe beim Wiederaufnehmen (${r.code})`);
|
||
}
|
||
}
|
||
|
||
/* =======================================================================
|
||
15. DIE TALENTE-SEITE IM ECHTEN BROWSER (17.09.2026)
|
||
|
||
Der erste Browserblick auf diese Seite ueberhaupt. Er ist faellig:
|
||
|
||
Heute Mittag hat ein Bildschirmfoto zwei Dinge gefunden, die keine
|
||
Zahl gefunden haette -- ein Wasserzeichen, das durch vertraulichen
|
||
Text hindurchschien (color-mix mit DURCHSICHTIG statt mit Weiss),
|
||
und eine Klassenkollision, bei der .t-schritt fuer zwei verschiedene
|
||
Dinge stand. Beides war gruen in jeder Prueflinie und falsch auf dem
|
||
Schirm.
|
||
|
||
GEKLICKT WIRD WIRKLICH, nicht ueber die Schnittstelle. Der ganze Weg
|
||
-- aufklappen, tippen, absenden -- ist der, der kaputtgehen kann; was
|
||
darunter liegt, ist in Abschnitt 14 schon bewiesen.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 15. Im echten Browser ===");
|
||
{
|
||
let chromium;
|
||
try {
|
||
({ chromium } = await import(
|
||
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"));
|
||
} catch { chromium = null; }
|
||
|
||
if (!chromium) {
|
||
unklar("kein Playwright gefunden -- die Oberflaeche wurde nicht angesehen");
|
||
} else {
|
||
const browser = await chromium.launch();
|
||
try {
|
||
/* 390 x 844 -- ein Handy. Filipe arbeitet am Handy, und die
|
||
Kopfleiste ist genau dort im September zerbrochen. */
|
||
const sicht = await browser.newContext({ viewport: { width: 390, height: 844 } });
|
||
await sicht.addCookies(kDogi.split("; ").map((c) => {
|
||
const [name, ...rest] = c.split("=");
|
||
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
||
}));
|
||
const seite = await sicht.newPage();
|
||
const konsole = [];
|
||
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/talente.html`,
|
||
{ waitUntil: "domcontentloaded" });
|
||
await seite.waitForTimeout(1200);
|
||
|
||
const karten = await seite.locator(".t-karte").count();
|
||
ok(karten > 0, `die Seite zeigt ${karten} Karten`);
|
||
/* ABGELEITET, NICHT ABGESCHRIEBEN: An jeder Karte IM TRICHTER
|
||
steht ein Ausgang -- an denen im Team ausdruecklich nicht.
|
||
Feste Zahlen waeren hier die Rechnung von gestern: Kommt ein
|
||
Kandidat dazu, muesste man sie nachziehen. */
|
||
const imTeam = await seite.locator('.t-karte[data-stufe="imteam"]').count();
|
||
const ausgaenge = await seite.locator(".t-ausgang__auf").count();
|
||
ok(ausgaenge === karten - imTeam,
|
||
`an jeder Karte im Trichter ein Ausgang (${ausgaenge} von ${karten} `
|
||
+ `- ${imTeam} im Team)`);
|
||
ok(imTeam > 0 && await seite.locator(".t-ausgang__anderswo").count() === imTeam,
|
||
`und an denen im Team steht stattdessen der Weg zum Zugang (${imTeam})`);
|
||
ok(await seite.locator('.t-karte[data-stufe="imteam"] .t-ausgang__auf').count() === 0,
|
||
"dort gibt es das Feld nicht einmal zum Aufklappen");
|
||
|
||
/* ---- Der Weg: aufklappen, zu kurz, dann richtig ----
|
||
Gezielt eine Karte AUS DEM TRICHTER, nicht einfach die erste:
|
||
Welche oben steht, entscheidet die Sortierung, und die haengt
|
||
an Standzeiten. */
|
||
const erste = seite.locator('.t-karte:not([data-stufe="imteam"])').first();
|
||
const titel = (await erste.locator(".t-karte__titel").textContent())?.trim();
|
||
ok(await erste.locator(".t-ausgang__form").isHidden(),
|
||
"das Feld ist zugeklappt -- der Ausgang draengt sich nicht auf");
|
||
await erste.locator(".t-ausgang__auf").click();
|
||
await seite.waitForTimeout(200);
|
||
ok(await erste.locator(".t-ausgang__form").isVisible(),
|
||
"nach einem Klick steht es da");
|
||
|
||
{
|
||
/* ZU KURZ -- und zwar im Browser, nicht nur am Server. Was der
|
||
Browser prueft, ist Hoeflichkeit; faellt sie aus, sieht man
|
||
statt eines Satzes eine rohe Fehlermeldung. */
|
||
await erste.locator(".t-ausgang__feld").fill("passt nix");
|
||
await erste.locator(".t-ausgang__knopf").click();
|
||
await seite.waitForTimeout(400);
|
||
const meldung = (await seite.locator("#fehler").textContent())?.trim() || "";
|
||
ok(meldung.length > 10, `ein zu kurzer Satz wird abgewiesen ("${meldung}")`);
|
||
ok(await seite.locator(".t-karte").count() === karten,
|
||
"und die Karte bleibt stehen");
|
||
}
|
||
|
||
const GRUND_B = "Kam dreimal nicht zur Schicht, ohne Bescheid zu sagen.";
|
||
{
|
||
await erste.locator(".t-ausgang__feld").fill(GRUND_B);
|
||
await erste.locator(".t-ausgang__knopf").click();
|
||
await seite.waitForTimeout(900);
|
||
const uebrig = await seite.locator(".t-karte").count();
|
||
ok(uebrig === karten - 1,
|
||
`mit Satz verschwindet sie aus der Arbeitsliste (${karten} -> ${uebrig})`);
|
||
ok(await seite.locator(".t-beendet__auf").count() === 1,
|
||
"und unten steht die Sammlung der Beendeten");
|
||
const wort = (await seite.locator(".t-beendet__auf").textContent())?.trim() || "";
|
||
ok(/[0-9]/.test(wort), `zugeklappt mit Zahl ("${wort}")`);
|
||
}
|
||
|
||
{
|
||
await seite.locator(".t-beendet__auf").click();
|
||
await seite.waitForTimeout(300);
|
||
const karte = seite.locator(".t-beendet__karte").first();
|
||
ok(await karte.isVisible(), "aufgeklappt steht die Karte da");
|
||
ok((await karte.locator(".t-beendet__titel").textContent())?.trim() === titel,
|
||
"mit dem richtigen Namen");
|
||
ok((await karte.locator(".t-beendet__grund").textContent())?.includes(GRUND_B),
|
||
"und dem Satz von damals");
|
||
const stufe = (await karte.locator(".t-beendet__stufe").textContent())?.trim() || "";
|
||
ok(stufe.length > 3, `und WO es geendet hat (${stufe})`);
|
||
/* GEGENPROBE ZUR SICHTBARKEIT: An einer beendeten Karte steht
|
||
KEIN zweiter Ausgang -- sonst koennte man beenden, was schon
|
||
beendet ist, und der Grund wuerde ueberschrieben. */
|
||
ok(await karte.locator(".t-ausgang__auf").count() === 0,
|
||
"an einer beendeten Karte steht kein zweiter Ausgang");
|
||
ok(await karte.locator(".t-beendet__wieder").count() === 1,
|
||
"sondern der Weg zurueck");
|
||
}
|
||
|
||
{
|
||
/* DIE KOPFZEILE BEI 390 px. margin-inline-start: auto schiebt
|
||
das Datum nach rechts -- genau die Bauart, die im September
|
||
in der Kopfleiste zwei Knoepfe uebereinandergelegt hat. Eine
|
||
Messung statt einer Zahl im CSS. */
|
||
const k = seite.locator(".t-beendet__karte").first();
|
||
const a = await k.locator(".t-beendet__titel").boundingBox();
|
||
const b = await k.locator(".t-beendet__zeit").boundingBox();
|
||
const ueber = a && b
|
||
&& a.x < b.x + b.width && b.x < a.x + a.width
|
||
&& a.y < b.y + b.height && b.y < a.y + a.height;
|
||
ok(!!a && !!b && !ueber,
|
||
`Name und Datum liegen nicht uebereinander (${Math.round(a?.x ?? -1)}+`
|
||
+ `${Math.round(a?.width ?? 0)} | ${Math.round(b?.x ?? -1)})`);
|
||
/* Und der Weg zurueck muss mit einem Finger zu treffen sein. */
|
||
const w = await k.locator(".t-beendet__wieder").boundingBox();
|
||
ok(!!w && w.height >= 28 && w.width >= 100,
|
||
`der Weg zurueck ist treffbar (${Math.round(w?.width ?? 0)} x ${Math.round(w?.height ?? 0)})`);
|
||
}
|
||
|
||
{
|
||
await seite.locator(".t-beendet__wieder").first().click();
|
||
await seite.waitForTimeout(900);
|
||
const zurueck = await seite.locator(".t-karte").count();
|
||
ok(zurueck === karten, `wieder aufnehmen bringt sie zurueck (${zurueck})`);
|
||
ok(await seite.locator(".t-beendet__auf").count() === 0,
|
||
"und die Sammlung verschwindet, wenn nichts mehr drin ist");
|
||
}
|
||
|
||
ok(konsole.length === 0,
|
||
konsole.length ? `Konsolenfehler: ${konsole[0]}` : "kein einziger Konsolenfehler");
|
||
|
||
/* Das Bildschirmfoto -- der Teil, den keine Zahl ersetzt. */
|
||
await seite.locator('.t-karte:not([data-stufe="imteam"]) .t-ausgang__auf')
|
||
.first().click().catch(() => {});
|
||
await seite.waitForTimeout(300);
|
||
await seite.screenshot({ path: "server/pruef-nachwuchs-ausgang.png", fullPage: true });
|
||
melde(" -- Bildschirmfoto: server/pruef-nachwuchs-ausgang.png");
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* =======================================================================
|
||
16. AUS DEM TALENT WIRD EIN MENSCH MIT AUFGABEN (17.09.2026)
|
||
|
||
Filipe: „wie talente bewertet werden, aufgaben bekommen, analysiert
|
||
werden von vanvan und mir, alles moegliche."
|
||
|
||
Gemessen vor dem Bau: Der letzte Schritt des Trichters legt einen
|
||
Zugang an -- und die Verbindung wurde NIRGENDS gespeichert. Die
|
||
Karte wusste hinterher nicht, wer aus ihr geworden ist. Damit ging
|
||
weder der Sprung zur Person noch die Frage, aus welchem Kandidaten
|
||
welches Teammitglied wurde -- und erst recht nicht, ihm aus der
|
||
Karte heraus seine ersten Aufgaben zu geben.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 16. Vom Talent zur Person mit Aufgaben ===");
|
||
{
|
||
/* Ein eigener, frischer Kandidat. Der aus Abschnitt 13/14 ist durch
|
||
den Ausgang und wieder hinein gelaufen -- an ihm waere „hat noch
|
||
keine Person" nicht falsch, sondern unpruefbar. */
|
||
const neuer = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
|
||
{ art: "ruhig", titel: "Marek aus dem Stream", text: "Faellt auf." });
|
||
ok(neuer.code === 201 || neuer.code === 200, `ein frischer Kandidat (${neuer.code})`);
|
||
|
||
const finde = async (titel) => (json(await roh("/workspace/api/talente/lage", CREW,
|
||
{ cookie: kDogi })).kandidaten || []).find((k) => k.titel === titel);
|
||
|
||
let k = await finde("Marek aus dem Stream");
|
||
ok(!!k && k.person === null,
|
||
`am Anfang haengt kein Mensch an der Karte (${JSON.stringify(k?.person)})`);
|
||
|
||
/* ---- Die Nummer wird geprueft, nicht geglaubt ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "beobachtet", person_id: 999999 }, "PUT");
|
||
ok(r.code === 400, `eine erfundene Zugangsnummer wird abgewiesen (${r.code})`);
|
||
/* GEGENPROBE zur Wirkung: Die Stufe darf sich dabei NICHT geaendert
|
||
haben -- eine abgewiesene Anfrage, die trotzdem die Haelfte tut,
|
||
waere schlimmer als eine, die durchgeht. */
|
||
k = await finde("Marek aus dem Stream");
|
||
ok(k.stufe === "aufgefallen", `und die Stufe bleibt stehen (${k.stufe})`);
|
||
}
|
||
|
||
/* ---- Der Weg bis ins Team ---- */
|
||
{
|
||
const morgen = tagLokal(1);
|
||
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "beobachtet" }, "PUT");
|
||
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "angesprochen" }, "PUT");
|
||
const hand = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }))
|
||
.buddys?.[0];
|
||
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
/* Die Felder heissen `buddy_id` und `erste_schicht` -- beim
|
||
ersten Anlauf stand hier `buddy`/`datum`, und die Route wies
|
||
den Uebergang ab. Der Fehler lag in dieser Datei, nicht im
|
||
Code; nachgesehen in uebergangPruefen(). */
|
||
{ stufe: "probe", buddy_id: hand?.id, erste_schicht: morgen }, "PUT");
|
||
k = await finde("Marek aus dem Stream");
|
||
ok(k.stufe === "probe", `er steht in der Probe (${k.stufe})`);
|
||
}
|
||
|
||
/* ---- Der Zugang entsteht und wird VERKNUEPFT ---- */
|
||
let personId = null;
|
||
{
|
||
const p = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kDogi },
|
||
{ name: "Marek", rolle: "modi" });
|
||
ok(p.code === 200 || p.code === 201, `der Zugang entsteht (${p.code})`);
|
||
personId = json(p).id;
|
||
ok(!!personId, `und hat eine Nummer (${personId})`);
|
||
|
||
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "imteam", person_id: personId }, "PUT");
|
||
ok(r.code === 200, `die Karte geht ins Team (${r.code})`);
|
||
|
||
k = await finde("Marek aus dem Stream");
|
||
ok(k.person?.id === personId,
|
||
`und weiss jetzt, wer daraus wurde (${k.person?.name})`);
|
||
ok(k.person?.aktiv === true, "der Zugang laeuft");
|
||
}
|
||
|
||
/* ---- Die Verknuepfung ueberlebt einen Stufenwechsel ----
|
||
|
||
`person_id` wird nur gesetzt, nie geloescht. Ohne diese Regel
|
||
verloere die Karte ihren Menschen, sobald jemand sie zurueckstellt
|
||
-- und niemand wuesste mehr, wer das war. */
|
||
{
|
||
/* ZURUECK AUF "angesprochen", NICHT AUF "probe".
|
||
|
||
Beim ersten Anlauf stand hier `probe` -- und dieser Uebergang
|
||
verlangt Buddy und Datum (uebergangPruefen mit pflicht: true).
|
||
Ohne sie antwortete die Route mit 400, die Stufe blieb stehen,
|
||
und die Zeile darunter bestaetigte, dass sich nichts geaendert
|
||
hat: eine Pruefung, die immer gruen ist.
|
||
|
||
Aufgefallen ist das erst an der GEGENPROBE -- mit ausgebautem
|
||
COALESCE blieb sie gruen, obwohl sie genau das absichern soll.
|
||
Eine Gegenprobe ist deshalb keine Kuer: Sie prueft die Pruefung. */
|
||
const r = await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "angesprochen" }, "PUT");
|
||
ok(r.code === 200, `ein Schritt zurueck geht durch (${r.code})`);
|
||
const zurueck = await finde("Marek aus dem Stream");
|
||
ok(zurueck.stufe === "angesprochen",
|
||
`und er steht wirklich dort (${zurueck.stufe})`);
|
||
ok(zurueck.person?.id === personId,
|
||
"der Schritt zurueck nimmt ihr den Menschen nicht weg");
|
||
await roh(`/workspace/api/talente/${k.id}/stufe`, CREW, { cookie: kDogi },
|
||
{ stufe: "imteam" }, "PUT");
|
||
}
|
||
|
||
/* ---- Womit faengt er an? ---- */
|
||
{
|
||
const lage2 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
ok(Array.isArray(lage2.start_kategorien) && lage2.start_kategorien.length > 0,
|
||
`es gibt ${lage2.start_kategorien?.length} Bereiche zum Anfangen`);
|
||
/* JEDER BEREICH HAT AUCH WIRKLICH AUFGABEN. Ein Knopf, der „0
|
||
Aufgaben" anlegt, ist schlimmer als keiner. */
|
||
ok((lage2.start_kategorien || []).every((g) => g.anzahl > 0),
|
||
"und in jedem steht mindestens eine");
|
||
ok((lage2.start_kategorien || []).every((g) => g.name && g.wert),
|
||
"jeder hat Namen und Schluessel");
|
||
|
||
const g = lage2.start_kategorien[0];
|
||
const vorher = lage2.schon_vergeben?.[personId] || 0;
|
||
ok(vorher === 0, `er hat noch nichts (${vorher})`);
|
||
|
||
const r = await roh("/workspace/api/vorlagen/uebernehmen", CREW, { cookie: kDogi },
|
||
{ art: "katalog_alle", gruppe: g.wert, stufe: "neu", person_id: personId });
|
||
ok(r.code === 200, `die Aufgaben lassen sich geben (${r.code})`);
|
||
ok(json(r).angelegt === g.anzahl,
|
||
`und es sind genau so viele, wie der Knopf versprochen hat `
|
||
+ `(${json(r).angelegt} von ${g.anzahl})`);
|
||
|
||
const lage3 = json(await roh("/workspace/api/talente/lage", CREW, { cookie: kDogi }));
|
||
ok((lage3.schon_vergeben?.[personId] || 0) === g.anzahl,
|
||
`die Karte zeigt jetzt ${lage3.schon_vergeben?.[personId]} offene`);
|
||
}
|
||
|
||
/* ---- Und die Community kommt an nichts davon ---- */
|
||
{
|
||
const l = await roh("/workspace/api/talente/lage", CREW, { cookie: kModi });
|
||
ok(l.code === 404, `ein Modi sieht den Trichter nicht (${l.code})`);
|
||
}
|
||
}
|
||
|
||
/* =======================================================================
|
||
17. DER ANSCHLUSS IM BROWSER (17.09.2026)
|
||
|
||
Abschnitt 16 weist nach, dass die Verknuepfung in der Datenbank steht
|
||
und die Aufgaben sich geben lassen. Hier geht es darum, ob man es
|
||
SIEHT und ob der Weg mit einem Klick geht -- der ganze Zweck war ja,
|
||
die vier Schritte auf einer anderen Seite loszuwerden.
|
||
|
||
EIGENER BROWSER, NICHT DER AUS ABSCHNITT 15. Der lief, bevor es
|
||
„Marek" ueberhaupt gab. Einen Browserstand von zwei Abschnitten
|
||
vorher weiterzubenutzen waere keine Messung, sondern eine Annahme.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 17. Der Anschluss im Browser ===");
|
||
{
|
||
let chromium;
|
||
try {
|
||
({ chromium } = await import(
|
||
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"));
|
||
} catch { chromium = null; }
|
||
|
||
if (!chromium) {
|
||
unklar("kein Playwright gefunden -- der Anschluss wurde nicht angesehen");
|
||
} else {
|
||
const browser = await chromium.launch();
|
||
try {
|
||
const sicht = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
||
await sicht.addCookies(kDogi.split("; ").map((c) => {
|
||
const [name, ...rest] = c.split("=");
|
||
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
||
}));
|
||
const seite = await sicht.newPage();
|
||
const konsole = [];
|
||
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/talente.html`,
|
||
{ waitUntil: "domcontentloaded" });
|
||
await seite.waitForTimeout(1600);
|
||
|
||
const leute = await seite.evaluate(() =>
|
||
[...document.querySelectorAll(".t-person")].map((p2) => ({
|
||
name: p2.querySelector(".t-person__name")?.textContent || "",
|
||
ziel: p2.querySelector(".t-person__name")?.getAttribute("href") || "",
|
||
sichtbar: p2.offsetHeight > 0,
|
||
schon: p2.querySelector(".t-person__schon")?.textContent || "",
|
||
})));
|
||
ok(leute.length > 0, `an ${leute.length} Karte(n) steht, wer daraus wurde`);
|
||
ok(leute.length > 0 && leute.every((x) => x.sichtbar), "und man sieht es auch");
|
||
ok(leute.some((x) => x.ziel === "personen.html"),
|
||
`mit dem Weg zu den Zugaengen (${leute[0]?.ziel})`);
|
||
ok(leute.some((x) => /[0-9]/.test(x.schon)),
|
||
`und mit der Zahl dessen, was schon offen ist ("${leute.map((x) => x.schon).join("|")}")`);
|
||
|
||
/* GEGENPROBE: NICHT an jeder Karte. Wer nicht im Team ist, hat
|
||
auch keinen Menschen -- sonst hiesse „daraus wurde" nur, dass
|
||
ueberall etwas steht. */
|
||
const karten = await seite.locator(".t-karte").count();
|
||
ok(karten > leute.length, `aber nicht an allen (${leute.length} von ${karten})`);
|
||
|
||
/* ---- Der Kasten mit den ersten Aufgaben ---- */
|
||
const kaesten = await seite.locator(".t-start").count();
|
||
ok(kaesten > 0, `der Kasten fuer die ersten Aufgaben steht da (${kaesten})`);
|
||
|
||
await seite.locator(".t-start__auf").first().click();
|
||
await seite.waitForTimeout(500);
|
||
const knoepfe = await seite.evaluate(() =>
|
||
[...document.querySelectorAll(".t-start__kat")].map((b) => ({
|
||
name: b.querySelector(".t-start__kat-name")?.textContent || "",
|
||
zahl: b.querySelector(".t-start__kat-zahl")?.textContent || "",
|
||
hoehe: b.offsetHeight,
|
||
})));
|
||
ok(knoepfe.length > 5, `aufgeklappt stehen ${knoepfe.length} Bereiche zur Wahl`);
|
||
ok(knoepfe.length > 0 && knoepfe.every((b) => b.name && /[0-9]/.test(b.zahl)),
|
||
`jeder nennt Namen und Anzahl ("${knoepfe[0]?.name}", "${knoepfe[0]?.zahl}")`);
|
||
/* Mit einem Finger treffbar -- die Seite wird am Handy benutzt. */
|
||
ok(knoepfe.length > 0 && knoepfe.every((b) => b.hoehe >= 44),
|
||
`und treffbar (kleinster ${Math.min(...knoepfe.map((b) => b.hoehe))} px)`);
|
||
|
||
/* WAS HIER ZU PRUEFEN IST -- und was nicht (17.09.2026).
|
||
|
||
Der erste Anlauf verlangte, dass das Wort NIRGENDS auf der
|
||
Seite steht. Das war zu scharf und hat prompt einen Treffer
|
||
gemeldet: „Die Modis, die mit ihm gearbeitet haben" -- ein Satz
|
||
aus dem Probeplan, der ueber /api/talente/lage kommt und damit
|
||
nur bei jemandem ankommt, der ohnehin alles sieht (nurLeitung).
|
||
Er gehoert genau dorthin.
|
||
|
||
Die Regel, um die es wirklich geht, lautet anders: Das Wort darf
|
||
nicht in einer DATEI stehen, die der Server jedem ausliefert,
|
||
der die Adresse kennt. Darueber wacht pruef-modi-wortleck, und
|
||
zwar ueber alle Dateien statt ueber eine Seite.
|
||
|
||
HIER wird deshalb das geprueft, was HIER neu ist: Die Bausteine
|
||
fuer den Anschluss duerfen die Rolle nicht nennen -- weder an
|
||
einem Knopf noch in einem Schild. Sie werden im Browser gebaut,
|
||
also faellt es nur hier auf. */
|
||
const neueTexte = await seite.evaluate(() =>
|
||
[...document.querySelectorAll(".t-start, .t-person")]
|
||
.map((e) => e.textContent || "").join(" "));
|
||
ok(!/\bmodis?\b/i.test(neueTexte),
|
||
`die neuen Bausteine nennen die Rolle nicht (${neueTexte.length} Zeichen geprueft)`);
|
||
/* GEGENPROBE zur Voraussetzung: Es wurde ueberhaupt etwas
|
||
angesehen. Ohne Text waere die Zeile darueber auch dann gruen,
|
||
wenn die Kaesten gar nicht da sind. */
|
||
/* GEMESSEN WIRD AUFGEKLAPPT. Zugeklappt steht dort ein
|
||
Schaltersatz von 64 Zeichen -- daran ist nichts zu sehen.
|
||
Die Zahl haengt an den Kategorien, die der Server liefert,
|
||
nicht an einer geratenen Untergrenze. */
|
||
ok(neueTexte.length > 200 && knoepfe.length > 5,
|
||
`und es stand wirklich etwas da (${neueTexte.length} Zeichen, `
|
||
+ `${knoepfe.length} Bereiche)`);
|
||
|
||
/* ---- EIN KLICK LEGT SIE AN ---- */
|
||
{
|
||
const vorher = Number((leute[0]?.schon.match(/\d+/) || [0])[0]);
|
||
const name = knoepfe[1]?.name;
|
||
const menge = Number((knoepfe[1]?.zahl.match(/\d+/) || [0])[0]);
|
||
await seite.locator(".t-start__kat").nth(1).click();
|
||
await seite.waitForTimeout(1400);
|
||
const meldung = await seite.evaluate(() =>
|
||
document.querySelector(".t-start__fertig")?.textContent || "");
|
||
ok(/bereit/.test(meldung), `ein Klick meldet, was entstanden ist ("${meldung.trim()}")`);
|
||
ok(/Aufgaben ansehen/.test(meldung), "und nennt den Weg dorthin");
|
||
const nachher = await seite.evaluate(() =>
|
||
document.querySelector(".t-person__schon")?.textContent || "");
|
||
const n2 = Number((nachher.match(/\d+/) || [0])[0]);
|
||
ok(n2 === vorher + menge,
|
||
`die Zahl an der Karte zieht mit (${vorher} + ${menge} = ${n2}), `
|
||
+ `Bereich „${name}"`);
|
||
}
|
||
|
||
/* KEIN ROLLENNAME IM AUSGELIEFERTEN ZUSTAND. Die Seite bekommt
|
||
jeder, der die Adresse kennt -- am 16.09. stand das Wort schon
|
||
einmal im Quelltext. */
|
||
|
||
|
||
ok(konsole.length === 0,
|
||
konsole.length ? `Konsolenfehler: ${konsole[0]}` : "kein Konsolenfehler");
|
||
|
||
await seite.screenshot({ path: "server/pruef-nachwuchs-anschluss.png", fullPage: true });
|
||
melde(" -- Bildschirmfoto: server/pruef-nachwuchs-anschluss.png");
|
||
|
||
/* ---- BEIDE RICHTUNGEN (17.09.2026) -------------------------------
|
||
|
||
Die Stufe „Im Team" sagt zweimal „ab hier zaehlt die
|
||
Entwicklungskarte" -- und bis heute gab es keinen Weg dorthin.
|
||
Dasselbe Muster wie der fehlende Ausgang aus dem Trichter: ein
|
||
Text, der einen Weg verspricht, den es nicht gibt.
|
||
|
||
Geprueft wird nicht, ob ein Link DASTEHT, sondern ob man am
|
||
Ziel ankommt -- und ob der Rueckweg ebenfalls steht. Ein Link,
|
||
der auf einer Liste endet, auf der man die Person wieder suchen
|
||
muss, hat nichts erspart. */
|
||
melde("");
|
||
melde("=== 18. Hin und zurueck ===");
|
||
{
|
||
const weiter = await seite.evaluate(() =>
|
||
document.querySelector(".t-person__weiter")?.getAttribute("href") || "");
|
||
ok(/^entwicklung\.html\?zeigen=person-\d+$/.test(weiter),
|
||
`von der Talentkarte fuehrt ein Weg zur Entwicklungskarte (${weiter})`);
|
||
|
||
await seite.goto(`http://127.0.0.1:${PORT}/workspace/${weiter}`,
|
||
{ waitUntil: "domcontentloaded" });
|
||
await seite.waitForTimeout(2000);
|
||
|
||
const dort = await seite.evaluate(() => ({
|
||
/* Die Karte ist WIRKLICH offen -- nicht nur die Kachel
|
||
markiert. Wer dem Link folgt, will die Karte sehen. */
|
||
karteOffen: !document.getElementById("karte")?.hidden,
|
||
name: document.querySelector(".e-karte__kopf")?.textContent?.trim().slice(0, 40) || "",
|
||
markiert: document.querySelectorAll('.e-person[data-treffer="ja"]').length,
|
||
herkunft: document.querySelector(".e-herkunft")?.textContent?.trim() || "",
|
||
zurueck: document.querySelector(".e-herkunft__weg")?.getAttribute("href") || "",
|
||
}));
|
||
ok(dort.karteOffen, "die Karte ist beim Ankommen offen");
|
||
ok(/Marek/.test(dort.name), `und es ist die richtige (${dort.name})`);
|
||
ok(dort.markiert === 1,
|
||
`die Kachel dazu ist hervorgehoben (${dort.markiert})`);
|
||
ok(/Trichter/.test(dort.herkunft),
|
||
`und die Karte sagt, woher er kommt ("${dort.herkunft}")`);
|
||
ok(dort.zurueck === "talente.html", `mit dem Weg zurueck (${dort.zurueck})`);
|
||
|
||
/* GEGENPROBE: Wer NICHT aus dem Trichter kam, bekommt diese
|
||
Zeile nicht -- sonst hiesse „kam ueber den Trichter" nur,
|
||
dass ueberall etwas steht. Kessi ist vor der Verknuepfung ins
|
||
Team gekommen und hat deshalb keine. */
|
||
const ohne = await seite.evaluate(async () => {
|
||
const kacheln = [...document.querySelectorAll(".e-person")];
|
||
const andere = kacheln.find((k) => !/Marek/.test(k.textContent || ""));
|
||
if (!andere) return { da: false };
|
||
andere.click();
|
||
await new Promise((r) => setTimeout(r, 1200));
|
||
return {
|
||
da: true,
|
||
name: document.querySelector(".e-karte__kopf")?.textContent?.trim().slice(0, 30) || "",
|
||
herkunft: document.querySelectorAll(".e-herkunft").length,
|
||
};
|
||
});
|
||
ok(ohne.da, `es gibt eine zweite Karte zum Gegenpruefen (${ohne.name})`);
|
||
ok(ohne.da && ohne.herkunft === 0,
|
||
`und die traegt keine Herkunft (${ohne.herkunft})`);
|
||
}
|
||
} finally {
|
||
await browser.close();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* =======================================================================
|
||
19. WER SICH KUEMMERT (17.09.2026)
|
||
|
||
Filipe wollte die Arbeit mit der rechten Hand teilen. Gemessen: Es
|
||
gab dafuer nichts -- beide sehen jeden Kandidaten, niemand ist
|
||
benannt. Das ist nicht „geteilt", das ist „jeder dachte, der andere
|
||
macht es". Bei einem Kandidaten heisst das: Er bleibt auf seiner
|
||
Stufe liegen, die Standzeit waechst, und die Karte sagt zwar DASS
|
||
etwas liegt, aber nicht, wer es aufheben sollte.
|
||
|
||
DER WICHTIGSTE FALL IST DER DRITTE: Eine fremde Uebernahme wird
|
||
ABGEWIESEN, nicht stillschweigend ueberschrieben. Sonst nimmt einer
|
||
dem anderen den Kandidaten aus der Hand, ohne dass es jemand merkt
|
||
-- und beide glauben, es sei erledigt.
|
||
======================================================================= */
|
||
melde("");
|
||
melde("=== 19. Wer sich kuemmert ===");
|
||
{
|
||
const neuer = await roh("/workspace/api/bereich/talente", CREW, { cookie: kDogi },
|
||
{ art: "ruhig", titel: "Sami aus dem Chat", text: "Faellt auf." });
|
||
ok(neuer.code === 201 || neuer.code === 200, `ein Kandidat dafuer (${neuer.code})`);
|
||
|
||
const finde = async (keks, haus) => (json(await roh("/workspace/api/talente/lage",
|
||
haus, { cookie: keks })).kandidaten || []).find((k) => k.titel === "Sami aus dem Chat");
|
||
|
||
let k = await finde(kDogi, CREW);
|
||
ok(k?.zustaendig === null,
|
||
`am Anfang kuemmert sich niemand (${JSON.stringify(k?.zustaendig)})`);
|
||
|
||
/* ---- Uebernehmen ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
|
||
{ cookie: kDogi }, null, "PUT");
|
||
ok(r.code === 200 && json(r).zustaendig === true, `uebernehmen geht (${r.code})`);
|
||
k = await finde(kDogi, CREW);
|
||
ok(k.zustaendig?.name === "Filipe", `und es steht dabei, wer (${k.zustaendig?.name})`);
|
||
ok(k.zustaendig?.tage === 0, `seit wann (${k.zustaendig?.tage} Tage)`);
|
||
}
|
||
|
||
/* ---- DER FALL, AUF DEN ES ANKOMMT ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
|
||
{ cookie: kHand }, null, "PUT");
|
||
ok(r.code === 409,
|
||
`die rechte Hand kann ihn nicht aus der Hand nehmen (${r.code})`);
|
||
ok((json(r).fehler || "").includes("Filipe"),
|
||
`und erfaehrt, wer sich kuemmert ("${json(r).fehler}")`);
|
||
/* GEGENPROBE ZUR WIRKUNG: nichts halb passiert. */
|
||
k = await finde(kDogi, CREW);
|
||
ok(k.zustaendig?.name === "Filipe", "der Zustaendige bleibt unveraendert");
|
||
}
|
||
|
||
/* ---- Abgeben, dann kann der andere ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
|
||
{ cookie: kDogi }, null, "PUT");
|
||
ok(r.code === 200 && json(r).zustaendig === false, `derselbe Knopf gibt ab (${r.code})`);
|
||
k = await finde(kDogi, CREW);
|
||
ok(k.zustaendig === null, "danach kuemmert sich wieder niemand");
|
||
|
||
const r2 = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
|
||
{ cookie: kHand }, null, "PUT");
|
||
ok(r2.code === 200, `jetzt kann die rechte Hand uebernehmen (${r2.code})`);
|
||
const kh = await finde(kHand, CREW);
|
||
ok(kh?.zustaendig?.name === "Rieke", `und steht dort (${kh?.zustaendig?.name})`);
|
||
}
|
||
|
||
/* ---- Die Community kommt nicht heran ---- */
|
||
{
|
||
const r = await roh(`/workspace/api/talente/${k.id}/kuemmern`, CREW,
|
||
{ cookie: kModi }, null, "PUT");
|
||
ok(r.code === 404, `ein Modi kann sich nicht zustaendig erklaeren (${r.code})`);
|
||
}
|
||
{
|
||
const r = await roh("/workspace/api/talente/999999/kuemmern", CREW,
|
||
{ cookie: kDogi }, null, "PUT");
|
||
ok(r.code === 404, `eine erfundene Nummer wird abgewiesen (${r.code})`);
|
||
}
|
||
}
|
||
|
||
/* ------------------------------------------------------------------- */
|
||
melde("");
|
||
melde(`${geprueft} Pruefungen, ${fehler} Fehler`
|
||
+ (offen ? `, ${offen} nicht nachsehbar` : ""));
|
||
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
||
process.exit(fehler ? 1 : 0);
|