Eine Nacht voller Auftraege von Filipe. Der Reihe nach: 1. VERTRAULICH MELDEN -- der groesste neue Teil. "es soll auch eine kategorie also eine hauptkachel geben wo die community leute sich anonym melden koennen wenn sie probleme haben ... rechte hand und dogfather sollen zugriff auf diese anonyme nachrichten haben. also anonym fuer die anderen." WICHTIG, UND ES STEHT SO AUF DER SEITE: "anonym" heisst hier VERTRAULICH. DogFather und die rechte Hand sehen den Namen -- so bestellt und auch richtig, ohne Namen kann man niemandem helfen. Anonym ist es gegenueber allen anderen: kein Modi, kein anderes Mitglied. Wer glaubt, er schreibe unerkannt, schreibt anders und fuehlt sich hinterher getaeuscht -- deshalb steht der Satz im Formular, bevor jemand tippt. Der strikte Wechsel (melden -> warten -> antworten -> warten) ist im SERVER erzwungen, nicht nur im Knopf. Ein ausgegrauter Knopf ist keine Regel. Geschlossen wird nur von der Leitung; abgeschlossene Faelle werden nach 90 Tagen samt Nachrichten geloescht -- hier stehen die empfindlichsten Texte des Hauses. Modis sind ausdruecklich AUSSEN VOR: Sehr oft geht es in diesen Meldungen um eine Moderationsentscheidung. 2. JEDE KATEGORIE LAESST SICH ZUKLAPPEN. "man soll auf dieser seite jede kategorie auf und zu klappen koennen mit einem button ... ueberall wo so eine liste entstehen kann." Das Muster gab es auf der Startseite schon; es steht jetzt als `abschnittKlappbar` in kopf.js und wird von den Brettern und dem Zeitstrahl benutzt. Die Koepfe sind echte <button> (fuer die Tastatur), der Zustand haelt in localStorage, und er haengt je Brett UND Art -- sonst waere "Regel zugeklappt" ueberall gleichzeitig zu. 3. DIE SPRUNGLEISTE SIEHT AUS WIE ETWAS ZUM ANFASSEN. Vorher unterstrichene Woerter, die aussahen wie eine Fusszeile. Jetzt Marken mit Rahmen in einem eigenen Block. 4. DIE STUNDEN SIND LILA. UMGERECHNET, NICHT NEU GEWAEHLT: Jeder der fuenf Verlaufsstopps wurde nach OKLCH zerlegt, der Farbton auf 303 Grad gedreht, zurueckgerechnet. Helligkeit und Buntheit blieben gleich -- eine frei gegriffene Palette waere heller oder bunter geworden, und die Uhr damit unruhiger. 5. DIE KARTEN "UNSERE SEITEN": babyblau und lila, mit schwebenden Logos. Beide Logos wurden zu MASKEN gerechnet (tools/logo-maske.mjs): Die PNG tragen nur Alpha, die Farbe kommt aus dem CSS. Dadurch passt dasselbe Logo in jede Kachel, egal welchen Ton sie hat -- und VanVans 378-KB- Logo wurde dabei zu 109 KB. Vier Stueck je Karte, verschieden gross, zwei Bahnen, 26 bis 41 Sekunden Umlauf. Der erste Versuch war mit 5-9 % Deckkraft praktisch unsichtbar; jetzt 10-16 %. 6. "ENTWICKLUNG" HEISST JETZT "EURE AUFGABEN". Filipe hat recht: Die Kachel fuehrt seit dem 11.09. auf den Katalog -- eine Aufgabenliste. Der Name blieb stehen, weil beim Umhaengen des Ziels niemand ihn angefasst hat. Der Name "Entwicklung" ist damit frei fuer das, was er darunter versteht (Auswertung je Person mit Verlauf und Text) -- das ist noch NICHT gebaut. DREI PRUEFUNGEN, DIE ZUFAELLIG GRUEN WAREN, sind nebenbei aufgefallen: - pruef-kachelraster wartete feste 500 ms auf eine gestaffelte Einblendung und mass vier Reihen, wo drei sind. Jetzt reducedMotion. - pruef-start-ansicht scrollte 600 px und nahm an, danach liege keine Kachel mehr unter dem Zeiger. - pruef-wege-nach-draussen zaehlte das Kachelraster ueber ALLE Gruppen statt je Gruppe. GEMESSEN: pruef-hilfe (neu) 55/0, pruef-wege-nach-draussen 64/0, pruef-jeder-hat-eine-seite 65/0, pruef-community-sicht 10/0, pruef-bereiche-lesend, pruef-steckbrief, pruef-start-ansicht, pruef-kachelraster alle gruen. Co-Authored-By: Claude Opus 5 <[email protected]>
403 lines
18 KiB
JavaScript
403 lines
18 KiB
JavaScript
/* =====================================================================
|
|
DER VERTRAULICHE MELDEWEG -- 19.09.2026
|
|
|
|
Filipe: "es soll auch eine kategorie also eine hauptkachel geben wo
|
|
die community leute sich anonym melden koennen wenn sie probleme
|
|
haben ... rechte hand und dogfather sollen zugriff auf diese anonyme
|
|
nachrichten haben. also anonym fuer die anderen."
|
|
|
|
HIER LIEGEN DIE EMPFINDLICHSTEN TEXTE DES GANZEN HAUSES. Jemand
|
|
beschreibt ein Problem, oft mit anderen Menschen darin. Diese
|
|
Pruefung fragt deshalb vor allem eines: Kann irgendjemand etwas
|
|
sehen, das nicht fuer ihn ist?
|
|
|
|
DREI SORTEN FEHLER WERDEN GESUCHT:
|
|
|
|
1. EIN FALL BEIM FALSCHEN. Ein zweites Mitglied darf einen fremden
|
|
Fall weder in der Liste noch ueber die Fallnummer sehen. Auch ein
|
|
Modi nicht -- sehr oft geht es genau um eine
|
|
Moderationsentscheidung.
|
|
|
|
2. DER WECHSEL LAESST SICH UMGEHEN. Die Oberflaeche blendet den
|
|
Knopf aus; wer die Route direkt aufruft, kaeme daran vorbei. Die
|
|
Regel muss im Server stehen, und genau das wird hier geprueft --
|
|
nicht, ob ein Knopf verschwindet.
|
|
|
|
3. EIN GESCHLOSSENER FALL NIMMT NOCH NACHRICHTEN AN. Dann ist
|
|
"zugemacht" eine Meinung und keine Tatsache.
|
|
|
|
Aufrufen mit: node server/pruef-hilfe.mjs
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } 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(4188, "pruef-hilfe");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-hilfe-"));
|
|
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(150_000, "pruef-hilfe");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", 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";
|
|
|
|
function roh(pfad, host, kopf = {}, koerper = null, methode = 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 {}; } };
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
|
|
const LEUTE = [
|
|
["Filipe", "admin", "CODE-DOGI-0001"],
|
|
["Rieke", "hand", "CODE-HAND-0001"],
|
|
["Kessi", "modi", "CODE-MODI-0001"],
|
|
["Mara", "gast", "CODE-GAST-0001"],
|
|
["Tim", "gast", "CODE-GAST-0002"],
|
|
];
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
for (const [name, rolle, code] of LEUTE) {
|
|
const salz = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salz, 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, salz, 32768, new Date().toISOString());
|
|
}
|
|
d.close();
|
|
}
|
|
|
|
async function anmelden(rolle, code) {
|
|
let a = await roh("/workspace/api/anmelden", CREW, {}, { rolle, code });
|
|
if (a.code === 400 && a.text.includes("alter_offen")) {
|
|
a = await roh("/workspace/api/anmelden", CREW, {}, { rolle, code, alter_ok: true });
|
|
}
|
|
return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
|
|
const k = {};
|
|
k.dogi = await anmelden("admin", "CODE-DOGI-0001");
|
|
k.hand = await anmelden("hand", "CODE-HAND-0001");
|
|
k.modi = await anmelden("modi", "CODE-MODI-0001");
|
|
k.mara = await anmelden("gast", "CODE-GAST-0001");
|
|
k.tim = await anmelden("gast", "CODE-GAST-0002");
|
|
|
|
/* =======================================================================
|
|
1. WER DIE SEITE UEBERHAUPT OEFFNEN DARF
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Die Tuer ===");
|
|
{
|
|
for (const [wer, keks, soll] of [
|
|
["Mara (Mitglied)", k.mara, 200],
|
|
["DogFather", k.dogi, 200],
|
|
["die rechte Hand", k.hand, 200],
|
|
]) {
|
|
const a = await roh("/workspace/hilfe.html", CREW, { cookie: keks });
|
|
ok(a.code === soll, `${wer}: ${a.code}`);
|
|
}
|
|
/* MODIS AUSDRUECKLICH NICHT -- sehr oft geht es um sie. */
|
|
const m = await roh("/workspace/hilfe.html", CREW, { cookie: k.modi });
|
|
ok(m.code === 302 || m.code === 403, `ein Modi kommt NICHT hinein (${m.code})`);
|
|
const o = await roh("/workspace/hilfe.html", CREW);
|
|
ok(o.code === 302 || o.code === 401 || o.code === 403, `ohne Anmeldung nicht (${o.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. MELDEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Eine Meldung aufmachen ===");
|
|
let fallId = 0;
|
|
{
|
|
const zuKurz = await roh("/workspace/api/hilfe", CREW, { cookie: k.mara },
|
|
{ betreff: "Hm", text: "kurz" });
|
|
ok(zuKurz.code === 400, `zu kurz wird abgelehnt (${zuKurz.code})`);
|
|
|
|
const a = await roh("/workspace/api/hilfe", CREW, { cookie: k.mara }, {
|
|
betreff: "Jemand geht mich im Chat immer wieder an",
|
|
text: "Seit einer Woche schreibt mir dieselbe Person nach jedem Stream. "
|
|
+ "Ich habe schon geblockt, dann kommt ein neuer Zugang.",
|
|
});
|
|
ok(a.code === 201, `Mara kann melden (${a.code})`);
|
|
fallId = json(a).id;
|
|
ok(Number.isInteger(fallId) && fallId > 0, `der Fall hat eine Nummer (${fallId})`);
|
|
|
|
/* DIE LEITUNG MELDET SICH NICHT SELBST. */
|
|
const d = await roh("/workspace/api/hilfe", CREW, { cookie: k.dogi },
|
|
{ betreff: "Test von DogFather", text: "Das sollte nicht gehen, denke ich." });
|
|
ok(d.code === 400, `DogFather kann keinen Fall aufmachen (${d.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. WER SIEHT WAS -- der wichtigste Abschnitt
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Vertraulich heisst vertraulich ===");
|
|
{
|
|
const mara = json(await roh("/workspace/api/hilfe", CREW, { cookie: k.mara }));
|
|
ok(mara.faelle?.length === 1, `Mara sieht ihren Fall (${mara.faelle?.length})`);
|
|
ok(mara.leitung === false, "und ist nicht Leitung");
|
|
/* IHR EIGENER NAME KOMMT NICHT MIT -- sie weiss, wer sie ist. Das
|
|
Feld gibt es fuer sie gar nicht, also kann es auch nicht
|
|
versehentlich angezeigt werden. */
|
|
ok(!("melder" in (mara.faelle?.[0] || {})),
|
|
"in ihrer Antwort steht kein Melder-Feld");
|
|
|
|
/* EIN ZWEITES MITGLIED SIEHT NICHTS. Das ist der Kern. */
|
|
const tim = json(await roh("/workspace/api/hilfe", CREW, { cookie: k.tim }));
|
|
ok(tim.faelle?.length === 0, `Tim sieht Maras Fall NICHT (${tim.faelle?.length})`);
|
|
const timDirekt = await roh(`/workspace/api/hilfe/${fallId}`, CREW, { cookie: k.tim });
|
|
/* 404 UND NICHT 403: Ein "darfst du nicht" verriete, dass es den
|
|
Fall gibt. */
|
|
ok(timDirekt.code === 404, `und auch nicht ueber die Nummer (${timDirekt.code})`);
|
|
|
|
/* EIN MODI AUCH NICHT. */
|
|
const modi = await roh(`/workspace/api/hilfe/${fallId}`, CREW, { cookie: k.modi });
|
|
ok(modi.code === 404 || modi.code === 302 || modi.code === 403,
|
|
`ein Modi kommt nicht an den Fall (${modi.code})`);
|
|
|
|
/* DIE LEITUNG SIEHT IHN -- MIT NAMEN. Genau so bestellt. */
|
|
const dogi = json(await roh("/workspace/api/hilfe", CREW, { cookie: k.dogi }));
|
|
ok(dogi.faelle?.length === 1, `DogFather sieht den Fall (${dogi.faelle?.length})`);
|
|
ok(dogi.faelle?.[0]?.melder === "Mara",
|
|
`und den Namen dazu (${dogi.faelle?.[0]?.melder})`);
|
|
const hand = json(await roh("/workspace/api/hilfe", CREW, { cookie: k.hand }));
|
|
ok(hand.faelle?.[0]?.melder === "Mara", "die rechte Hand ebenso");
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. DER WECHSEL -- und zwar im Server, nicht im Knopf
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Immer abwechselnd ===");
|
|
{
|
|
/* Mara ist gerade NICHT dran -- sie hat gerade geschrieben. */
|
|
const nochmal = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.mara }, { text: "Und noch etwas dazu." });
|
|
ok(nochmal.code === 409,
|
|
`Mara kann nicht zweimal hintereinander schreiben (${nochmal.code})`);
|
|
|
|
const d = json(await roh(`/workspace/api/hilfe/${fallId}`, CREW, { cookie: k.mara }));
|
|
ok(d.ich_bin_dran === false, "und der Server sagt ihr das auch");
|
|
|
|
/* Die Leitung antwortet. */
|
|
const antwort = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.dogi }, { text: "Danke, dass du dich meldest. Wie heisst der Zugang?" });
|
|
ok(antwort.code === 200, `DogFather antwortet (${antwort.code})`);
|
|
|
|
/* Jetzt ist die Leitung NICHT mehr dran. */
|
|
const nochmal2 = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.hand }, { text: "Ergaenzung." });
|
|
ok(nochmal2.code === 409,
|
|
`auch die Leitung kann nicht zweimal (${nochmal2.code})`);
|
|
|
|
/* Und Mara schon. */
|
|
const maraJetzt = json(await roh(`/workspace/api/hilfe/${fallId}`, CREW, { cookie: k.mara }));
|
|
ok(maraJetzt.ich_bin_dran === true, "Mara ist jetzt dran");
|
|
const maraAntwort = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.mara }, { text: "Der Zugang heisst xyz123." });
|
|
ok(maraAntwort.code === 200, `und kann antworten (${maraAntwort.code})`);
|
|
|
|
/* GEGENPROBE: Tim kann in einen fremden Fall nichts schreiben. */
|
|
const timSchreibt = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.tim }, { text: "Ich mische mich mal ein." });
|
|
ok(timSchreibt.code === 404, `Tim kann nicht hineinschreiben (${timSchreibt.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. ZUMACHEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Zumachen kann nur die Leitung ===");
|
|
{
|
|
const mara = await roh(`/workspace/api/hilfe/${fallId}/schliessen`, CREW,
|
|
{ cookie: k.mara }, { text: "Passt schon." });
|
|
ok(mara.code === 403, `Mara kann ihren Fall NICHT schliessen (${mara.code})`);
|
|
|
|
const zu = await roh(`/workspace/api/hilfe/${fallId}/schliessen`, CREW,
|
|
{ cookie: k.hand }, { text: "Zugang gesperrt, melde dich wenn wieder etwas ist." });
|
|
ok(zu.code === 200, `die rechte Hand schliesst ihn (${zu.code})`);
|
|
|
|
/* EIN GESCHLOSSENER FALL IST GESCHLOSSEN -- auch fuer die Leitung. */
|
|
const danach = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.dogi }, { text: "Noch ein Nachtrag." });
|
|
ok(danach.code === 409, `danach nimmt er nichts mehr an (${danach.code})`);
|
|
const maraDanach = await roh(`/workspace/api/hilfe/${fallId}/antwort`, CREW,
|
|
{ cookie: k.mara }, { text: "Doch noch etwas." });
|
|
ok(maraDanach.code === 409, `auch von Mara nicht (${maraDanach.code})`);
|
|
|
|
const d = json(await roh(`/workspace/api/hilfe/${fallId}`, CREW, { cookie: k.mara }));
|
|
ok(d.fall?.stand === "geschlossen", `der Stand steht auf geschlossen (${d.fall?.stand})`);
|
|
ok(d.nachrichten?.length === 4,
|
|
`vier Nachrichten im Verlauf, das Schlusswort mitgezaehlt (${d.nachrichten?.length})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. DIE GRENZE FUER OFFENE FAELLE
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Drei offene sind genug ===");
|
|
{
|
|
let letzte = 0;
|
|
for (let i = 1; i <= 4; i++) {
|
|
const a = await roh("/workspace/api/hilfe", CREW, { cookie: k.tim }, {
|
|
betreff: `Sache Nummer ${i}`,
|
|
text: `Das ist die ${i}. Meldung und sie ist lang genug fuer die Pruefung.`,
|
|
});
|
|
letzte = a.code;
|
|
if (i <= 3) ok(a.code === 201, `Meldung ${i} geht durch (${a.code})`);
|
|
}
|
|
ok(letzte === 429, `die vierte wird abgelehnt (${letzte})`);
|
|
|
|
/* UND NACH DEM SCHLIESSEN GEHT WIEDER EINE. Ohne diese Zeile waere
|
|
die Grenze eine Sackgasse statt einer Bremse. */
|
|
const tim = json(await roh("/workspace/api/hilfe", CREW, { cookie: k.tim }));
|
|
const ersterOffener = tim.faelle?.[tim.faelle.length - 1]?.id;
|
|
await roh(`/workspace/api/hilfe/${ersterOffener}/schliessen`, CREW, { cookie: k.dogi }, {});
|
|
const wieder = await roh("/workspace/api/hilfe", CREW, { cookie: k.tim }, {
|
|
betreff: "Jetzt wieder eine",
|
|
text: "Nachdem einer geschlossen wurde, sollte das hier wieder gehen.",
|
|
});
|
|
ok(wieder.code === 201, `nach dem Schliessen geht wieder eine (${wieder.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. AUFRAEUMEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Was niemand mehr braucht, verschwindet ===");
|
|
{
|
|
const { hilfeAufraeumen } = await import("./workspace-hilfe.js");
|
|
const { db } = await import("./workspace.js");
|
|
|
|
/* Einen geschlossenen Fall kuenstlich altern lassen. KEIN echtes
|
|
Warten -- eine Pruefung, die 90 Tage braucht, ist keine. */
|
|
const alt = new Date(Date.now() - 100 * 86400000).toISOString();
|
|
db().prepare("UPDATE hilfe_faelle SET geschlossen_am = ? WHERE id = ?").run(alt, fallId);
|
|
|
|
const vorher = db().prepare("SELECT COUNT(*) AS n FROM hilfe_faelle").get().n;
|
|
const weg = hilfeAufraeumen(90);
|
|
const nachher = db().prepare("SELECT COUNT(*) AS n FROM hilfe_faelle").get().n;
|
|
ok(weg === 1 && nachher === vorher - 1,
|
|
`ein alter, geschlossener Fall wird geloescht (${weg}, ${vorher} -> ${nachher})`);
|
|
|
|
/* UND DIE NACHRICHTEN MIT IHM. Ein Fall ohne Text waere geloescht,
|
|
die Texte laegen aber weiter in der Datenbank -- genau das, was
|
|
man beim Aufraeumen vermeiden will. */
|
|
const reste = db().prepare(
|
|
"SELECT COUNT(*) AS n FROM hilfe_nachrichten WHERE fall_id = ?").get(fallId).n;
|
|
ok(reste === 0, `und seine ${4 - reste} Nachrichten ebenfalls (${reste} uebrig)`);
|
|
|
|
/* GEGENPROBE: Ein OFFENER Fall wird nicht angefasst, egal wie alt. */
|
|
const offen = db().prepare("SELECT COUNT(*) AS n FROM hilfe_faelle WHERE stand != 'geschlossen'").get().n;
|
|
const weg2 = hilfeAufraeumen(0);
|
|
const offenDanach = db().prepare("SELECT COUNT(*) AS n FROM hilfe_faelle WHERE stand != 'geschlossen'").get().n;
|
|
ok(offen === offenDanach && offen > 0,
|
|
`offene Faelle bleiben, auch bei Aufbewahrung 0 (${offen} vorher, ${offenDanach} danach, ${weg2} geschlossene weg)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. IM BROWSER
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 8. Im Browser ===");
|
|
{
|
|
let pw = null;
|
|
try {
|
|
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
} catch { /* dritter Ausgang */ }
|
|
|
|
if (!pw) {
|
|
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
|
|
} else {
|
|
const browser = await pw.chromium.launch();
|
|
try {
|
|
for (const [name, keks, breite] of [
|
|
["Mara", k.mara, 1400], ["Mara am Handy", k.mara, 412], ["DogFather", k.dogi, 1400],
|
|
]) {
|
|
const ctx = await browser.newContext({
|
|
viewport: { width: breite, height: 950 },
|
|
isMobile: breite < 700, hasTouch: breite < 700,
|
|
reducedMotion: "reduce",
|
|
});
|
|
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
|
const [n, ...r] = z.split("=");
|
|
return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await ctx.newPage();
|
|
const kaputt = [];
|
|
seite.on("pageerror", (e) => kaputt.push(String(e).slice(0, 140)));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/hilfe.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(900);
|
|
|
|
const z = await seite.evaluate(() => ({
|
|
titel: document.getElementById("seiten-titel")?.textContent?.trim() || "",
|
|
hinweis: document.getElementById("vertrauen-text")?.textContent?.trim() || "",
|
|
neuSichtbar: !document.getElementById("neu-block")?.hidden,
|
|
zeilen: document.querySelectorAll(".fall-zeile").length,
|
|
/* Nichts darf ueber den rechten Rand laufen. */
|
|
ueber: Math.max(0, document.documentElement.scrollWidth - window.innerWidth),
|
|
}));
|
|
|
|
ok(z.titel.length > 0, `${name}: die Seite hat einen Titel (${z.titel})`);
|
|
/* DER HINWEIS MUSS DA SEIN, IMMER. Er ist der Unterschied
|
|
zwischen "vertraulich" und einem falschen Versprechen. */
|
|
ok(/sonst niemand|nur du und die rechte Hand/i.test(z.hinweis),
|
|
" der Hinweis, wer mitliest, steht da");
|
|
ok(z.ueber === 0, ` nichts laeuft ueber den Rand (${z.ueber} px)`);
|
|
ok(kaputt.length === 0,
|
|
` kein Skriptfehler${kaputt.length ? ": " + kaputt.join(" | ") : ""}`);
|
|
|
|
if (name.startsWith("Mara")) {
|
|
ok(z.neuSichtbar, " sie kann etwas Neues melden");
|
|
} else {
|
|
ok(!z.neuSichtbar, " DogFather bekommt KEIN Melde-Formular");
|
|
ok(z.zeilen > 0, ` und sieht die Meldungen (${z.zeilen})`);
|
|
}
|
|
await ctx.close();
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest */ }
|
|
process.exit(fehler ? 1 : 0);
|