Gemessen: 165 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN
doppelt, vier davon dreifach. Niemand hatte das gewollt; jede neue
Pruefung wurde von einer vorhandenen abgeschrieben, und die Nummer kam
mit. Meine eigene Notiz sagte "sieben" -- auch eine Bestandsliste
altert.
Der Waechter faengt den Schaden ab, aber er kann nur melden, was schon
passiert ist: Zwei Pruefungen mit derselben Nummer koennen nie
gleichzeitig laufen, und ein liegengebliebener Prozess der einen laesst
die andere abbrechen mit einer Meldung, die wie ein Befund aussieht.
Genau das ist mir am 20.09. zweimal passiert.
Jetzt leitet jede Datei ihre Nummer aus ihrer STELLE IM ALPHABET ab
(eigenerPort in helfer-port.mjs), zwei je Datei. Nicht ueber eine
Pruefsumme: Bei 165 Namen in 4900 Nummern waeren nach dem
Geburtstagsproblem rund DREI Zusammenstoesse zu erwarten -- ein Hash
tauscht eine sichtbare Doppelung gegen eine unsichtbare. Die Stelle im
Alphabet ist eindeutig von der Bauart her.
--- ZWEI FEHLER AUF DEM WEG, BEIDE LEHRREICH -------------------------
1. DER ERSTE VERSUCH WAR GRUEN UND KAPUTT. Ersetzt wurde mit einem
Muster: "([^"]*4231[^"]*)". Das hielt
{ host: "127.0.0.1", port: 4231, path: "/404.html" }
fuer eine Zeichenkette -- ein Muster kann eine oeffnende nicht von
einer schliessenden Anfuehrung unterscheiden. Heraus kam
{ host: "127.0.0.1`, port: ${PORT}, path: `/404.html" }
also GUELTIGER Code ohne port-Feld. `node --check` sagte gruen fuer
alle 164 Dateien. Aufgefallen ist es erst, weil ich vier Vertreter
gegen eine vorher gemessene Grundlinie laufen liess: pruef-schranke
39/0 vorher, 38/1 nachher.
Alles zurueckgenommen und mit einem Zerleger neu gemacht, der weiss,
ob eine Stelle Code, Zeichenkette, Vorlage, Kommentar oder
regulaerer Ausdruck ist. In pruef-ics stand die Nummer in einem
regulaeren Ausdruck -- der wird jetzt gebaut statt hingeschrieben.
2. EIN MODUL, DAS BEIM IMPORTIEREN ARBEITET, IST EINE FALLE. Der
zweite Durchgang importierte den ersten, um seine Mechanik zu
benutzen -- und fuehrte dessen Hauptlauf gleich mit aus. Die
zweiten Nummern wurden dadurch als erste behandelt, zwei Aufrufe
bekamen dieselbe Nummer, und in pruef-content stand `const PORT`
zweimal.
--- WAS DAS DAUERHAFT HAELT -----------------------------------------
pruef-portnummern.mjs (neu, 8 Pruefungen) fragt nicht "welche Nummern
sind doppelt", sondern "wer traegt ueberhaupt noch eine von Hand ein"
und "wer startet einen Server, ohne seine Nummer abzuleiten". Die
zweite Frage hat sofort etwas gefunden, das in KEINER Doppelungsliste
stand: pruef-push-weg belegte 4341 und 4342, rief den Waechter aber
gar nicht auf -- dieselbe Nummer wie pruef-agentur. Eine Liste zeigt
nur, was auf ihr steht.
Mit Gegenprobe: Eine unbekannte Datei bekommt keine geratene Nummer,
sondern einen Abbruch, und eine dritte Nummer je Datei gibt es nicht.
--- NACHGEMESSEN ----------------------------------------------------
Sechzehn Pruefungen gegen ihre vorher gemessene Grundlinie, je eine
Vertreterin jeder umgestellten Bauweise (eine Nummer, zwei Nummern,
Nummer in einer Zeichenkette, in einer Vorlage, in einem regulaeren
Ausdruck, dynamische Einfuhr, Nachtlauf mit zwei Laeufen):
crew-adresse 132/0 · schranke 39/0 · content 45/0 · entwicklung 46/0
anruf 127/0 · ics 37/0 · push-weg 20/0 · arten 28/0 · video 67/0
kanalzeile 14/0 · agentur 62/0 · spicy 83/0 · creator-anlegen 50/0
push-ziel 10/0 · portnummern 8/0
Alle exakt wie vorher. Zwei waren schon vorher rot und sind es
unveraendert geblieben (crew-wand-bild 41/4, teilen 14/3) -- per
`git stash` belegt, nicht angenommen.
Co-Authored-By: Claude Opus 5 <[email protected]>
361 lines
17 KiB
JavaScript
361 lines
17 KiB
JavaScript
/* =====================================================================
|
|
Aus einer Idee wird Arbeit (15.09.2026)
|
|
|
|
Stufe 9: „Idee → Aufgabe". Das ganze Haus steht auf dem Satz *„Was
|
|
hier nicht steht, passiert nicht."* Ein Ideenbrett, aus dem keine
|
|
Aufgabe werden kann, ist der eine Ort, an dem er nicht gilt.
|
|
|
|
---------------------------------------------------------------------
|
|
DREI FRAGEN, UND ALLE DREI HABEN EINE GEGENPROBE
|
|
|
|
1. Wird aus einer Idee eine Aufgabe? ... und steht wirklich alles
|
|
Richtige darin?
|
|
2. Genau EINMAL? ... und was passiert beim
|
|
zweiten Versuch?
|
|
3. Aus JEDEM Brett? ... nein — und genau das ist
|
|
der Punkt: Ein Beitrag
|
|
aus dem Treff gehört
|
|
einem Menschen aus der
|
|
Community.
|
|
|
|
Dazu die Frage, die das Loeschkonzept aufwirft: Was passiert mit der
|
|
Aufgabe, wenn die Idee geloescht wird? (Sie bleibt — mit `SET NULL`.)
|
|
|
|
Aufrufen mit: node server/pruef-uebernahme.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 { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-uebernahme");
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-ueb-"));
|
|
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(180_000, "pruef-uebernahme");
|
|
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 modul = await import("./workspace-uebernahme.js");
|
|
const { BEREICHE } = await import("./workspace-bereiche.js");
|
|
/* Welche Bretter dem Treff gehoeren, sagt workspace.js -- nicht
|
|
ein Merkmal, das ich mir an BEREICHE vorgestellt habe. Der erste
|
|
Anlauf fragte nach `BEREICHE[b].treff` und fand null Bretter: eine
|
|
Gegenprobe, die nichts gegenprueft. */
|
|
const { TREFF_BRETTER } = await import("./workspace.js");
|
|
|
|
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
|
|
let idDogi, idLuna, idIdee, idTreff, idIdeeHoch;
|
|
{
|
|
const d = oeffne();
|
|
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);
|
|
const 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;
|
|
};
|
|
idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
idLuna = anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
anlegen("Kessi", "gast", "CODE-GAST-0001");
|
|
|
|
const rein = d.prepare(`INSERT INTO eintraege
|
|
(bereich, art, titel, text, datum, dringlichkeit, status, creator_id, erstellt, erstellt_von)
|
|
VALUES (?,?,?,?,?,?,'offen',?,?,?)`);
|
|
rein.run("ideen", "content", "Reihe über Technik", "Drei Teile, je fünf Minuten.",
|
|
jetzt.slice(0, 10), "mittel", idLuna, jetzt, idDogi);
|
|
idIdee = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
rein.run("ideen", "content", "Dringende Idee", "Muss diese Woche.",
|
|
jetzt.slice(0, 10), "hoch", null, jetzt, idDogi);
|
|
idIdeeHoch = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, erstellt, erstellt_von)
|
|
VALUES ('treff','hallo','Beitrag aus dem Treff','Hallo zusammen',?,?,?)`)
|
|
.run(jetzt.slice(0, 10), jetzt, idDogi);
|
|
idTreff = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
d.close();
|
|
}
|
|
|
|
function ruf(pfad, host, methode, kopf = {}, koerper = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode,
|
|
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 ruf("/workspace/api/anmelden", host, "POST", {},
|
|
rolle === "gast" ? { rolle, code, alter_ok: true } : { rolle, code });
|
|
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
|
}
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS);
|
|
const kGast = await anmelden("gast", "CODE-GAST-0001", CREW);
|
|
ok(!!kDogi && !!kGast, "DogFather und ein Community-Mitglied sind angemeldet");
|
|
|
|
/* =======================================================================
|
|
1. DIE LISTE DER BRETTER IST NICHT ERFUNDEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Aus welchen Brettern ===");
|
|
{
|
|
const echte = Object.keys(BEREICHE);
|
|
ok(echte.length > 5, `${echte.length} Bretter gibt es im Haus`);
|
|
|
|
const erfunden = modul.AUS_BRETTERN.filter((b) => !echte.includes(b));
|
|
ok(erfunden.length === 0,
|
|
erfunden.length ? `GIBT ES NICHT: ${erfunden.join(", ")}`
|
|
: `alle ${modul.AUS_BRETTERN.length} genannten Bretter gibt es wirklich`);
|
|
|
|
/* DIE TREFF-BRETTER STEHEN NICHT DRIN, und das ist der Punkt. */
|
|
const treffBretter = TREFF_BRETTER.filter((b) => echte.includes(b));
|
|
ok(treffBretter.length > 0, `${treffBretter.length} Bretter gehören dem Treff`);
|
|
const drin = treffBretter.filter((b) => modul.AUS_BRETTERN.includes(b));
|
|
ok(drin.length === 0,
|
|
drin.length ? `TREFF-BRETT DABEI: ${drin.join(", ")}`
|
|
: "keines davon ist dabei — was jemand aus der Community schreibt, gehört ihm");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. AUS EINER IDEE WIRD EINE AUFGABE
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Die Übernahme ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idIdee}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 201, `aus der Idee wird eine Aufgabe (${a.code})`);
|
|
const neu = json(a);
|
|
ok(Number.isInteger(neu.aufgabe_id), `mit einer Nummer (${neu.aufgabe_id})`);
|
|
|
|
const d = oeffne();
|
|
const auf = d.prepare("SELECT * FROM aufgaben WHERE id = ?").get(neu.aufgabe_id);
|
|
const idee = d.prepare("SELECT * FROM eintraege WHERE id = ?").get(idIdee);
|
|
d.close();
|
|
|
|
ok(auf.titel === "Reihe über Technik", `der Titel wandert mit (${auf.titel})`);
|
|
ok(/fünf Minuten/.test(auf.beschreibung || ""), "der Text auch");
|
|
ok(auf.status === "offen", `sie beginnt offen (${auf.status})`);
|
|
ok(auf.aus_eintrag_id === idIdee, `und weiß, woher sie kam (${auf.aus_eintrag_id})`);
|
|
ok(auf.creator_id === idLuna,
|
|
`der Creator-Bezug bleibt erhalten (${auf.creator_id === idLuna ? "Luna" : auf.creator_id})`);
|
|
ok(auf.verantwortlich_id === idDogi,
|
|
"zuständig ist, wer übernommen hat — nicht, wer die Idee aufgeschrieben hat");
|
|
/* WAS ABSICHTLICH NICHT MITWANDERT. */
|
|
ok(auf.frist === null, "eine Frist wird NICHT erfunden");
|
|
|
|
/* DIE IDEE BLEIBT STEHEN. Sie wird nicht verschoben und nicht
|
|
geloescht -- wer nachsieht, was aus einem Gedanken geworden ist,
|
|
soll den Gedanken noch finden. */
|
|
ok(!!idee && idee.bereich === "ideen",
|
|
`und die Idee steht unverändert im Brett (${idee?.bereich})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. GENAU EINMAL
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Genau einmal ===");
|
|
{
|
|
const zweite = await ruf(`/workspace/api/eintrag/${idIdee}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(zweite.code === 409, `ein zweiter Versuch wird abgelehnt (${zweite.code})`);
|
|
ok(json(zweite).fehler === "schon_uebernommen", "mit einem Grund, den die Seite versteht");
|
|
ok(Number.isInteger(json(zweite).aufgabe_id),
|
|
`und dem Verweis auf die vorhandene Aufgabe (${json(zweite).aufgabe_id})`);
|
|
|
|
const d = oeffne();
|
|
const wieviele = d.prepare(
|
|
"SELECT COUNT(*) AS n FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee).n;
|
|
d.close();
|
|
ok(wieviele === 1, `es gibt genau eine Aufgabe dazu (${wieviele})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. NICHT AUS JEDEM BRETT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Nicht aus dem Treff ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idTreff}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 400, `aus einem Treff-Beitrag wird keine Aufgabe (${a.code})`);
|
|
ok(/keine Aufgabe/.test(json(a).fehler || ""), `und die Absage sagt warum (${json(a).fehler})`);
|
|
|
|
const d = oeffne();
|
|
const n = d.prepare("SELECT COUNT(*) AS n FROM aufgaben WHERE aus_eintrag_id = ?").get(idTreff).n;
|
|
d.close();
|
|
ok(n === 0, "es ist auch wirklich keine entstanden");
|
|
|
|
const erfunden = await ruf("/workspace/api/eintrag/999999/zur-aufgabe", HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(erfunden.code === 404, `eine erfundene Nummer ergibt 404 (${erfunden.code})`);
|
|
|
|
const ohne = await ruf(`/workspace/api/eintrag/${idIdeeHoch}/zur-aufgabe`, HAUS, "POST", {}, {});
|
|
ok(ohne.code === 401, `ohne Anmeldung gar nichts (${ohne.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DIE DRINGLICHKEIT WIRD ZUR PRIORITÄT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Dringlichkeit ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idIdeeHoch}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 201, `die dringende Idee wird übernommen (${a.code})`);
|
|
ok(json(a).prioritaet === "hoch", `und behält ihre Dringlichkeit (${json(a).prioritaet})`);
|
|
|
|
/* GEGENPROBE -- ohne sie beweist die Zeile darueber nichts: Die
|
|
andere Idee stand auf "mittel" und muss dort geblieben sein. */
|
|
const d = oeffne();
|
|
const andere = d.prepare(
|
|
"SELECT prioritaet FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee);
|
|
d.close();
|
|
ok(andere?.prioritaet === "mittel",
|
|
`die andere steht weiterhin auf mittel (${andere?.prioritaet}) — es wird nicht alles „hoch“`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. WAS DAS BRETT DAZU SAGT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Die Liste am Brett ===");
|
|
{
|
|
const a = json(await ruf("/workspace/api/bereich/ideen/uebernahmen", HAUS, "GET", { cookie: kDogi }));
|
|
ok(a.moeglich === true, "das Ideenbrett kennt Übernahmen");
|
|
ok(Object.keys(a.uebernahmen || {}).length === 2,
|
|
`zwei Einträge sind übernommen (${Object.keys(a.uebernahmen || {}).join(", ")})`);
|
|
ok(a.uebernahmen[idIdee]?.status === "offen",
|
|
`und der Stand der Aufgabe steht dabei (${a.uebernahmen[idIdee]?.status})`);
|
|
|
|
/* GEGENPROBE: Ein Treff-Brett sagt „nicht moeglich" -- daran
|
|
entscheidet die Oberflaeche, ob der Knopf ueberhaupt hingehoert. */
|
|
const treff = json(await ruf("/workspace/api/bereich/treff/uebernahmen", HAUS, "GET", { cookie: kDogi }));
|
|
ok(treff.moeglich === false, "ein Treff-Brett kennt keine");
|
|
ok(Object.keys(treff.uebernahmen || {}).length === 0, "und liefert auch keine");
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. WIRD DIE IDEE GELÖSCHT, BLEIBT DIE ARBEIT
|
|
|
|
Die Frage, die das Loeschkonzept aufwirft. `ON DELETE SET NULL` und
|
|
nicht CASCADE: Die Aufgabe ist inzwischen etwas Eigenes.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Wenn die Idee verschwindet ===");
|
|
{
|
|
const d = oeffne();
|
|
const vorher = d.prepare("SELECT id FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee);
|
|
d.prepare("PRAGMA foreign_keys = ON").run();
|
|
d.prepare("DELETE FROM eintraege WHERE id = ?").run(idIdee);
|
|
const nachher = d.prepare("SELECT id, aus_eintrag_id, titel FROM aufgaben WHERE id = ?")
|
|
.get(vorher.id);
|
|
d.close();
|
|
|
|
ok(!!nachher, "die Aufgabe gibt es noch, obwohl die Idee weg ist");
|
|
ok(nachher.aus_eintrag_id === null,
|
|
`und der Verweis steht auf null statt ins Leere (${nachher.aus_eintrag_id})`);
|
|
ok(nachher.titel === "Reihe über Technik", "mit unverändertem Titel");
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. DER KNOPF STEHT IM BROWSER — UND WIRKT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 8. Im Browser ===");
|
|
{
|
|
const { chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await chromium.launch();
|
|
const kontext = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); });
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
/* Eine frische Idee, damit es etwas zu uebernehmen gibt. */
|
|
const d = oeffne();
|
|
d.prepare(`INSERT INTO eintraege
|
|
(bereich, art, titel, text, datum, dringlichkeit, status, erstellt, erstellt_von)
|
|
VALUES ('ideen','content','BILDSCHIRM-IDEE','Zum Anklicken.',?,'mittel','offen',?,?)`)
|
|
.run(jetzt.slice(0, 10), jetzt, idDogi);
|
|
d.close();
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/`, { waitUntil: "domcontentloaded" });
|
|
await seite.click('.rolle[data-rolle="admin"]');
|
|
await seite.fill("#code", "CODE-DOGI-0001");
|
|
await seite.click("button[type=submit]");
|
|
await seite.waitForURL("**/start.html", { timeout: 20000 });
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=ideen`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(900);
|
|
|
|
const knoepfe = await seite.evaluate(() =>
|
|
[...document.querySelectorAll("button")].filter((b) => /zur Aufgabe/i.test(b.textContent)).length);
|
|
ok(knoepfe >= 1, `${knoepfe} Knopf/Knöpfe „zur Aufgabe“ auf dem Ideenbrett`);
|
|
|
|
const verweise = await seite.evaluate(() =>
|
|
document.querySelectorAll(".eintrag__aufgabe").length);
|
|
ok(verweise >= 1,
|
|
`${verweise} Einträge zeigen stattdessen den Verweis auf ihre Aufgabe`);
|
|
|
|
/* DER KNOPF MUSS WIRKEN -- gemessen an der Datenbank, nicht am Text. */
|
|
const vorher = (() => { const dd = oeffne();
|
|
const n = dd.prepare("SELECT COUNT(*) AS n FROM aufgaben").get().n; dd.close(); return n; })();
|
|
await seite.evaluate(() => {
|
|
const b = [...document.querySelectorAll("button")].find((x) => /zur Aufgabe/i.test(x.textContent));
|
|
b?.click();
|
|
});
|
|
await seite.waitForTimeout(1200);
|
|
const nachher = (() => { const dd = oeffne();
|
|
const n = dd.prepare("SELECT COUNT(*) AS n FROM aufgaben").get().n; dd.close(); return n; })();
|
|
ok(nachher === vorher + 1, `nach dem Klick gibt es eine Aufgabe mehr (${vorher} → ${nachher})`);
|
|
|
|
ok(konsole.length === 0,
|
|
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 90) : "keine Fehler im Browser");
|
|
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 { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|