Filipe, 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. wie in dem sinne soll sie ganz links
sein. ausser bei personen und zugaenge soll sie ueber jedem aber unter
dogfather sein. ich will dass auch ueberall immer nach der hirarchie
gearbeitet wird."
EINE REIHENFOLGE, NICHT ZWEI REGELN
ROLLEN_REIHE in workspace.js ist jetzt:
Spicy Media > DogFather > Rechte Hand > Manager > Scout > Creator > Modi
Er hat zwei Faelle beschrieben, aber es braucht nur eine Reihenfolge: In
den Team-Listen kommt DogFather gar nicht vor (er beurteilt, er wird
nicht beurteilt) - dort steht sie dadurch automatisch ganz vorn. In
"Personen & Zugaenge" steht er drin, also steht sie dort hinter ihm.
Zwei Sonderfaelle waeren zwei Stellen, an denen es auseinanderlaeuft.
Dass Spicy Media davor bleibt, ist seine ausdrueckliche Entscheidung auf
Nachfrage. 'gast' steht bewusst nicht in der Liste und faellt ans Ende.
ROLLEN_SORTIERUNG (der SQL-Ausdruck) wird jetzt aus ROLLEN_REIHE
ABGELEITET statt danebengeschrieben. Bis heute stand die Reihenfolge
zweimal da; beim Hochziehen der rechten Hand haetten beide geaendert
werden muessen. Eine Liste, die niemand pflegt, kann nicht veralten -
derselbe Grundsatz wie beim Spaltenverlust vom 06.09.
WAS DABEI AUFFIEL, OHNE DASS JEMAND DANACH GESUCHT HAT
Der Server sortierte laengst richtig. DREI Auswahllisten im Browser
haben seine Reihenfolge wieder verworfen und nach einer eigenen Liste
mit fuenf Agentur-Rollen neu gezeichnet - Team Dogi kommt darin nicht
vor und DARF es nicht (bereiche.js laedt jeder herunter).
- Chat-Auswahl und Sicht-Umschalter: Team Dogi landete in einem
Nachzuegler-Block ganz unten, hinter jedem Creator.
- Teilnehmerwahl im Kalender: dort gab es nicht einmal einen
Nachzuegler-Block. Die rechte Hand und die Modis standen GAR NICHT
zur Auswahl. DogFather konnte sein eigenes Team zu keinem Termin
einladen, und auf dem Bildschirm sah das vollkommen normal aus.
Das ist derselbe Fehler zum vierten Mal (Chat 10.09., Personenliste
10.09., Sicht-Umschalter 11.09., Kalender 11.09.). Deshalb keine vierte
Einzelreparatur, sondern eine Stelle: window.Bereiche.gruppieren()
gruppiert in genau der Reihenfolge, in der der Server die Menschen
schickt - ohne einen einzigen Rang zu kennen. Fehlt der Helfer, wird
eine Gruppe mit allen gezeichnet: nicht schoen, aber sichtbar, und
niemand verschwindet. Der Kalender-Weg schickt die Ueberschrift jetzt
mit, wie der Chat es laengst tut.
PRUEFUNGEN
pruef-nachwuchs 109 -> 123 (Abschnitt 12: die Reihenfolge, mit der
Gegenprobe, dass sie NICHT alphabetisch ist -- Rieke
steht alphabetisch hinten, mit "Anna" waere jede
Zeile gruen ohne etwas zu messen)
pruef-dabei-optik misst die Wahl jetzt im Browser: ist Team Dogi
ueberhaupt da, und steht es vorn
pruef-rollen 315 (vorher 312), 423 s gemessen. Die Notbremse lag
bei 480 s und hat angeschlagen - kein Haenger,
sondern zu wenig Luft, seit die rechte Hand vier
Kacheln mehr hat. Jetzt 900 s, mit der Messung
daneben und dem Hinweis, beim naechsten Mal nicht
die Zahl zu erhoehen, sondern nachzusehen.
Co-Authored-By: Claude Opus 5 <[email protected]>
728 lines
38 KiB
JavaScript
728 lines
38 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 { portMussFreiSein } from "./helfer-port.mjs";
|
||
|
||
const PORT = await portMussFreiSein(4404, "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++; };
|
||
|
||
const CREW = "crew.dogfather-universe.com";
|
||
const HAUS = "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");
|
||
anlegen("Mara", "manager", "CODE-MANA-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", HAUS);
|
||
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}`, HAUS, { 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}`, HAUS,
|
||
{ cookie: kDogi }, { stufe: "laeuft" }, "PUT");
|
||
const erfunden = await roh(`/workspace/api/entwicklung/person/${idModi}/e-gibt-es-nicht`, HAUS,
|
||
{ 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}`, HAUS, { 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", HAUS, { 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");
|
||
|
||
/* Jetzt eine Person abschalten -- das Team faellt unter die Schwelle. */
|
||
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
||
dd.prepare("UPDATE personen SET aktiv = 0 WHERE name = 'Nina'").run();
|
||
dd.close();
|
||
|
||
const klein = json(await roh("/workspace/api/entwicklung/ampel", HAUS, { cookie: kDogi }));
|
||
ok(klein.team === voll.team - 1 && klein.sagbar === false,
|
||
`bei ${klein.team} Personen schweigt sie`);
|
||
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}`, HAUS, { 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}`, HAUS,
|
||
{ 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}`, HAUS, { 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}`, HAUS,
|
||
{ 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}`, HAUS,
|
||
{ 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}`,
|
||
HAUS, { 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}`, HAUS, { 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", HAUS, { 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", HAUS, { 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}`, HAUS,
|
||
{ 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}`, HAUS,
|
||
{ 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}`, HAUS, { cookie: kDogi }, {}, "PUT");
|
||
await roh(`/workspace/api/talente/${idTalent}/merkmal/${w}`, HAUS, { cookie: kDogi }, {}, "PUT");
|
||
|
||
const nach = json(await roh("/workspace/api/talente/lage", HAUS, { 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`, HAUS,
|
||
{ 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", HAUS, { 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`, HAUS,
|
||
{ 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", HAUS, { 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", HAUS, { 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", HAUS, { 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", HAUS, { 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 SIEHT DEN TRICHTER, VERGIBT ABER KEINE ZUGAENGE. */
|
||
const hand = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kHand },
|
||
{ name: "Heimlich", rolle: "modi" });
|
||
ok(hand.code === 404, `die rechte Hand kommt am Anlegen nicht vorbei (${hand.code})`);
|
||
const einModi = await roh("/workspace/api/verwaltung/personen", CREW, { cookie: kModi },
|
||
{ name: "Heimlich", rolle: "modi" });
|
||
ok(einModi.code === 404, `ein Modi erst recht nicht (${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", HAUS, { 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", HAUS, { 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`, HAUS,
|
||
{ 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", HAUS, { 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", HAUS, { 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", HAUS, { 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"})`);
|
||
ok(darunter.includes("manager") && darunter.includes("modi"),
|
||
`unter ihr stehen Manager und Modis (${[...new Set(darunter)].join(", ")})`);
|
||
|
||
/* ---- 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(", ")})`);
|
||
}
|
||
|
||
/* ------------------------------------------------------------------- */
|
||
melde("");
|
||
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
||
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
||
process.exit(fehler ? 1 : 0);
|