Files
dogfather-universe/server/pruef-personen-formular.mjs
T
DogFatherGitandClaude Opus 5 d8720debc1 Personen & Automationen gehoeren DogFather allein -- und das Formular laeuft nicht mehr ueber
ZWEI MELDUNGEN.

1. "DAS SIEHT JA MAL RICHTIG SCHEISSE AUS"
   Die Rollenauswahl im Formular "Neue Person" lief unten aus dem
   Formular heraus und legte sich ueber die Personenliste darunter.

   Die Ursache war keine schlampige Gestaltung, sondern eine Regel, die
   fuer etwas anderes gemacht ist: `.neu__raster > *` (aufgaben.css) gibt
   JEDEM direkten Kind ein Raster mit fester Zeilenhoehe von 44 px, damit
   Beschriftung und Eingabefeld in allen Spalten auf einer Linie sitzen.
   Fuer ein einzeiliges Feld ist das genau richtig. Die Rollenauswahl ist
   aber kein Feld, sondern ein Block aus vier hohen Karten -- die passten
   nicht in 44 px, und was nicht hineinpasst, steht eben daneben.

   Das Formular ist jetzt aus dem Spaltenraster geloest und liest sich
   von oben nach unten: NAME (schmal, ein Name braucht keine 1200 px),
   ROLLE (volle Breite, vier gleich breite Karten), Knoepfe. Das ist
   nicht nur reparierter Ueberlauf, sondern auch die Reihenfolge, in der
   man die Sache tatsaechlich entscheidet.

   Dazu zwei Kleinigkeiten, die den Unterschied machen: Das Namensfeld
   war ohne Rasterzeile 60 px hoch geworden (`height: 100%` einer Zeile,
   die es nicht mehr gibt) -- hoeher als die Knoepfe daneben, was nach
   Versehen aussieht, weil es eins war. Und die gewaehlte Rolle traegt
   jetzt ein HAEKCHEN, nicht nur einen etwas helleren Rahmen: Genau die
   Art Unterschied, die im Kontrastmodus verschwindet und fuer
   farbunsichere Augen nie existiert hat.

2. "DIE MANAGER SOLLEN DIESE KATEGORIEN GARNICHT SEHEN"
   "Personen & Zugaenge" und "Automationen" gehoeren ab sofort allein
   DogFather.

   Bei den Personen war es ohnehin ein Widerspruch im eigenen Haus: In
   der Rollenauswahl steht seit jeher "Manager -- dieselben Rechte wie
   DogFather, AUSSER der Personenverwaltung". Die Kachel stand trotzdem
   bei ihm, und die Schranke liess ihn hinein: Codes erneuern, sperren,
   Zuteilungen aendern. Eine Beschreibung, die etwas anderes sagt als die
   Software, ist schlimmer als beides einzeln -- man weiss danach nicht
   mehr, welcher von beiden man glauben soll.

   Umgesetzt auf DREI Ebenen, weil Wegnehmen was man SIEHT kein
   Rechteentzug ist:
     1. die Kachel erscheint nicht mehr
     2. die Seite leitet zur Startseite zurueck (GESCHUETZT)
     3. die Schnittstellen antworten mit 404 -- Personenverwaltung,
        Systemzustand, KI-Schalter

   FOLGE, die bewusst in Kauf genommen wird: Die Betreuungs-Zuteilung
   liegt auf der Personenseite. Ab jetzt kann also NUR DogFather
   festlegen, wer welchen Creator betreut.

GEPRUEFT: server/pruef-personen-formular.mjs, 20 Pruefungen. Das Aussehen
wird gemessen, nicht angesehen: Endet die Rollenauswahl innerhalb des
Formulars? Ragt eine Karte seitlich hinaus? Liegt etwas ueber der Liste?
Sind alle vier Karten gleich breit (272-272 px)? Traegt die gewaehlte ein
Haekchen? Dazu zwei Gegenproben: DogFather kommt weiterhin an beide
Bereiche (sonst waere jede Sperr-Zeile auch bei einem fuer ALLE kaputten
Bereich gruen), und die Managerin arbeitet ansonsten unveraendert weiter.
Bestehende Laeufe gruen: Rollen 97, Handy 50, Sicherung 41, Personenliste
33, Team 30, Formulare 19, Code 17, Grosscheck 15.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-03 00:00:41 +02:00

206 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
Prüfung: das Formular "Neue Person" und die Rechte auf den beiden
Verwaltungsbereichen (02.09.2026).
Zwei Meldungen:
"das sieht ja mal richtig scheisse aus. mach das besser bitte."
-- Die Rollenauswahl lief unten aus dem Formular heraus und legte
sich über die Personenliste. Ursache war keine schlampige
Gestaltung, sondern eine Raster-Regel für EINZEILIGE Felder
(feste Zeilenhöhe 44 px), die auch auf den Block aus vier hohen
Karten angewandt wurde.
"die manager sollen diese kategorien garnicht sehen"
-- "Personen & Zugänge" und "Automationen" gehören allein DogFather.
Der zweite Punkt wird auf DREI Ebenen geprüft, nicht nur an der
Kachel: Wegnehmen, was man sieht, ist kein Rechteentzug. Es zählt,
was der Server antwortet.
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-pform-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = "4198";
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
process.env.SITE_ACCESS_CODE_VANVAN = "VANVAN-TEST-1";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4198";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
let fehler = 0;
let geprueft = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
anlegen("Dogfather", "admin", "CODE-DOGI-0001");
anlegen("Cigdem", "manager", "CODE-CIGDEM-01");
anlegen("Tili", "creator", "CODE-TILI-0001");
d.close();
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
async function anmelden(rolle, code, breite = 1440) {
const kontext = await browser.newContext({ viewport: { width: breite, height: 1000 } });
const seite = await kontext.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
await seite.click(`.rolle[data-rolle="${rolle}"]`);
await seite.fill("#code", code);
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
return seite;
}
/* ===================================================================
TEIL 1 — Das Formular läuft nicht mehr über
=================================================================== */
console.log("\n— Das Formular „Neue Person“ —");
const dogi = await anmelden("admin", "CODE-DOGI-0001");
await dogi.goto(BASIS + "/workspace/personen.html", { waitUntil: "networkidle" });
await dogi.waitForTimeout(700);
await dogi.click("#neu-oeffnen").catch(() => {});
await dogi.waitForTimeout(500);
const mass = await dogi.evaluate(() => {
const form = document.getElementById("neu");
const wahl = document.getElementById("f-rolle");
const karten = [...document.querySelectorAll(".rollenwahl__knopf")];
const f = form.getBoundingClientRect();
const w = wahl.getBoundingClientRect();
/* Das erste Element NACH dem Formular -- über das die Karten vorher
hinweggelaufen sind. */
const danach = document.querySelector("#liste");
const dn = danach ? danach.getBoundingClientRect() : null;
return {
formUnten: Math.round(f.bottom),
wahlUnten: Math.round(w.bottom),
wahlOben: Math.round(w.top),
listeOben: dn ? Math.round(dn.top) : null,
karten: karten.length,
kartenBreiten: karten.map((k) => Math.round(k.getBoundingClientRect().width)),
/* Liegt eine Karte weiter rechts als das Formular breit ist? */
ueberRand: karten.some((k) => k.getBoundingClientRect().right > f.right + 1),
};
});
ok(mass.karten === 4, `alle vier Rollen stehen zur Wahl (${mass.karten})`);
ok(mass.wahlUnten <= mass.formUnten,
`die Rollenauswahl endet INNERHALB des Formulars (${mass.wahlUnten} ≤ ${mass.formUnten})`);
ok(!mass.ueberRand, "keine Karte ragt seitlich aus dem Formular");
ok(mass.listeOben === null || mass.wahlUnten <= mass.listeOben,
`die Karten liegen nicht über der Personenliste (${mass.wahlUnten} ≤ ${mass.listeOben})`);
/* Alle Karten gleich breit -- ungleiche Kacheln sind das, was "sieht
scheisse aus" in Zahlen bedeutet. */
const breiteste = Math.max(...mass.kartenBreiten);
const schmalste = Math.min(...mass.kartenBreiten);
ok(breiteste - schmalste <= 2,
`alle Karten sind gleich breit (${schmalste}–${breiteste} px)`);
/* Die gewählte Rolle muss man SEHEN, nicht erraten. */
const vorher = await dogi.evaluate(() =>
getComputedStyle(document.querySelector('.rollenwahl__knopf[data-an="ja"]'), "::after").content);
ok(vorher.includes("✓"), `die gewählte Rolle trägt ein Häkchen (${vorher})`);
await dogi.screenshot({ path: "pruef-personen-formular.png", fullPage: false });
/* Auf dem Handy darf nichts herausragen. */
const handy = await anmelden("admin", "CODE-DOGI-0001", 390);
await handy.goto(BASIS + "/workspace/personen.html", { waitUntil: "networkidle" });
await handy.waitForTimeout(700);
await handy.click("#neu-oeffnen").catch(() => {});
await handy.waitForTimeout(500);
const engUeber = await handy.evaluate(() =>
document.documentElement.scrollWidth - document.documentElement.clientWidth);
ok(engUeber <= 0, `auf dem Handy ragt nichts heraus (${engUeber}px)`);
await handy.screenshot({ path: "pruef-personen-formular-handy.png", fullPage: false });
/* ===================================================================
TEIL 2 — Die beiden Bereiche gehören DogFather allein
=================================================================== */
console.log("\n— Personen & Automationen: nur DogFather —");
const cigdem = await anmelden("manager", "CODE-CIGDEM-01");
await cigdem.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
await cigdem.waitForTimeout(800);
const kacheln = await cigdem.evaluate(() =>
[...document.querySelectorAll(".kachel")].map((k) => k.innerText.split("\n")[0].trim()));
ok(!kacheln.some((t) => /Personen/.test(t)),
"1. die Kachel „Personen & Zugänge“ fehlt bei der Managerin");
ok(!kacheln.some((t) => /Automationen/.test(t)),
"1. die Kachel „Automationen“ fehlt ebenfalls");
ok(kacheln.length > 8,
` – aber der Rest steht noch da (${kacheln.length} Kacheln), es ist nicht alles weg`);
/* 2. Die SEITE, falls jemand die Adresse kennt. */
for (const pfad of ["personen.html", "automation.html"]) {
const a = await cigdem.goto(BASIS + "/workspace/" + pfad, { waitUntil: "networkidle" });
const gelandet = cigdem.url();
ok(gelandet.endsWith("/start.html"),
`2. ${pfad} direkt aufgerufen führt zurück zur Startseite (${gelandet.split("/").pop()})`);
}
/* 3. Die SCHNITTSTELLE, falls jemand ganz an der Seite vorbeigeht.
Das ist die einzige Ebene, die wirklich zählt -- die beiden darüber
sind Bequemlichkeit. */
const api = (seite, pfad, optionen) => seite.evaluate(async ([p, o]) => {
const a = await fetch(p, { credentials: "same-origin", ...(o || {}) });
return a.status;
}, [pfad, optionen]);
await cigdem.goto(BASIS + "/workspace/start.html", { waitUntil: "networkidle" });
ok(await api(cigdem, "/workspace/api/verwaltung/personen") === 404,
"3. die Personenverwaltung antwortet ihr mit 404");
ok(await api(cigdem, "/workspace/api/zustand") === 404,
"3. der Systemzustand ebenfalls");
ok(await api(cigdem, "/workspace/api/ki/schalter", { method: "PUT" }) !== 200,
"3. und den KI-Schalter darf sie nicht umlegen");
/* GEGENPROBE: Bei DogFather geht das alles weiterhin. Ohne sie wäre
jede Zeile oben auch dann grün, wenn die Bereiche für ALLE kaputt
wären -- und das würde erst auffallen, wenn er sie braucht. */
ok(await api(dogi, "/workspace/api/verwaltung/personen") === 200,
"GEGENPROBE: DogFather kommt weiterhin an die Personenverwaltung");
ok(await api(dogi, "/workspace/api/zustand") === 200,
"GEGENPROBE: und an den Systemzustand");
/* Und die Managerin behält, was sie behalten soll. */
ok(await api(cigdem, "/workspace/api/aufgaben") === 200,
"die Managerin arbeitet ansonsten unverändert weiter (Aufgaben)");
ok(await api(cigdem, "/workspace/api/calls") === 200, " und die Gespräche auch");
const ERWARTET = 17;
console.log("");
ok(geprueft >= ERWARTET, `es sind ${geprueft} Prüfungen gelaufen (erwartet mindestens ${ERWARTET})`);
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(`\n${fehler ? "FEHLER: " + fehler : "Alles in Ordnung"} — ${geprueft} Prüfungen`);
process.exit(fehler ? 1 : 0);