diff --git a/server/pruef-meldungen.mjs b/server/pruef-meldungen.mjs new file mode 100644 index 00000000..1ce4a932 --- /dev/null +++ b/server/pruef-meldungen.mjs @@ -0,0 +1,219 @@ +/* ===================================================================== + pruef-meldungen.mjs — Kein Benutzer liest je eine Maschinenkennung. + + Filipe, 19.09.2026: „keine kryptischen oder technischen + Fehlermeldungen, sondern klare Aussagen darüber, was passiert ist + und wie man das Problem lösen kann." + + --------------------------------------------------------------------- + WARUM DIESE PRÜFUNG DIE EIGENTLICHE ARBEIT IST + + Die Übersetzungstabelle in `workspace/assets/js/meldung.js` zu + schreiben war eine Stunde Arbeit. Sie in einem halben Jahr noch + vollständig zu haben, ist die schwierigere Aufgabe — und zwar aus + einem Grund, den dieses Haus teuer gelernt hat: + + „Eine abgeschriebene Liste ist dieselbe Falle wie eine feste Zahl." + (11.09.2026, als die Umstellung drei Spalten verlor, obwohl ein + Kommentar direkt daneben davor warnte.) + + Wer morgen `fehler: "zu_frueh"` erfindet, wird nicht daran denken, + hier einen Satz nachzutragen. Er merkt es auch nicht: Die Oberfläche + zeigt dann klaglos den Ersatzsatz. Es sieht aus, als funktioniere es. + + Deshalb rechnet diese Prüfung die Liste der Kennungen AUS DEM SERVER + aus, statt sie zu kennen. Eine neue Kennung ohne Satz macht sie rot. + Man KANN es nicht mehr vergessen. + + --------------------------------------------------------------------- + DREI DINGE WERDEN GEPRÜFT + + 1. VOLLSTÄNDIG — jede Kennung, die der Server schicken kann, hat + einen Satz. + 2. ANGESCHLOSSEN — keine Seite zeigt `.fehler` noch roh an, und + jede Seite, deren Skript `sagWas` benutzt, lädt auch + `meldung.js`. (Der zweite Teil ist der wichtigere: Ohne die + Datei wäre `sagWas` nicht definiert, und aus einer Fehlermeldung + würde ein Absturz — schlimmer als vorher.) + 3. LESBAR — kein Satz enthält eine Kennung, eine nackte Zahl oder + ein englisches Wort. + + Dazu drei GEGENPROBEN. Eine Prüfung, die immer bestätigt, bestätigt + nichts (01.09.2026): Jede der drei muss beweisen, dass sie auch + „nicht in Ordnung" sagen kann. + + AUFRUF: node server/pruef-meldungen.mjs + Läuft in unter einer Sekunde, ohne Server, ohne Browser. + ===================================================================== */ + +import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const HIER = dirname(fileURLToPath(import.meta.url)); +const WURZEL = join(HIER, ".."); +const JS = join(WURZEL, "workspace", "assets", "js"); +const SEITEN = join(WURZEL, "workspace"); + +let ok = 0, fehl = 0; +const pruef = (name, bedingung, zusatz = "") => { + if (bedingung) { ok++; console.log(` OK ${name}`); } + else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); } +}; + +/* --------------------------------------------------------------------- + Die Tabelle aus meldung.js lesen — ohne einen Browser zu bauen. + + `meldung.js` hängt sich an `window`. Statt eine halbe Browserwelt + nachzustellen, wird der Block zwischen `var SAETZE = {` und der + schließenden Klammer ausgeschnitten und als JavaScript ausgewertet. + Das ist ehrlicher als ein zweiter Auszug der Namen: Es ist dieselbe + Quelle, kein abgeschriebener Zwilling. + ------------------------------------------------------------------- */ +function saetzeLesen() { + const quelle = readFileSync(join(JS, "meldung.js"), "utf8"); + const start = quelle.indexOf("var SAETZE = {"); + if (start < 0) return null; + const auf = quelle.indexOf("{", start); + let tiefe = 0, ende = -1; + for (let i = auf; i < quelle.length; i++) { + if (quelle[i] === "{") tiefe++; + else if (quelle[i] === "}") { tiefe--; if (tiefe === 0) { ende = i; break; } } + } + if (ende < 0) return null; + // eslint-disable-next-line no-new-func + return Function(`"use strict"; return (${quelle.slice(auf, ende + 1)});`)(); +} + +/* --------------------------------------------------------------------- + Alle Kennungen, die der Server schicken kann. + + ABGELEITET, NICHT ABGESCHRIEBEN. Gesucht wird `fehler: "…"` in allen + Server-Dateien; als Kennung gilt, was nur aus Kleinbuchstaben, + Ziffern und Unterstrichen besteht. Alles andere ist ein fertiger + Satz und braucht keine Übersetzung. + ------------------------------------------------------------------- */ +function kennungenAusServer() { + const gefunden = new Map(); + for (const datei of readdirSync(HIER)) { + if (!datei.endsWith(".js")) continue; + if (datei.startsWith("pruef-") || datei.startsWith("test-")) continue; + const text = readFileSync(join(HIER, datei), "utf8"); + for (const m of text.matchAll(/fehler:\s*"([a-z0-9_]+)"/g)) { + if (!gefunden.has(m[1])) gefunden.set(m[1], datei); + } + } + return gefunden; +} + +console.log("\n=== Meldungen: sagt das Haus ganze Sätze? ===\n"); + +const SAETZE = saetzeLesen(); +/* DER DRITTE AUSGANG. Lässt sich die Tabelle nicht lesen, ist das + weder „in Ordnung" noch „ein Satz fehlt" — es heißt, die Prüfung + kann ihre Frage nicht beantworten. Dann bricht sie ab, statt grün + zu melden. Genau dieser Fall hat am 03.09. wochenlang eine + Pflichtprüfung wertlos gemacht. */ +if (!SAETZE) { + console.log(" KONNTE NICHT NACHSEHEN — in meldung.js ist kein Block `var SAETZE = {` zu finden."); + console.log(" Grund: entweder wurde die Datei umgebaut oder sie fehlt."); + console.log(" Ohne sie lässt sich nicht sagen, ob Sätze fehlen. Abbruch.\n"); + process.exit(2); +} + +const kennungen = kennungenAusServer(); +console.log(` (${kennungen.size} Kennungen im Server, ${Object.keys(SAETZE).length} Sätze in der Tabelle)\n`); + +/* ---- 1. Vollständig ------------------------------------------------- */ +const ohneSatz = [...kennungen.keys()].filter((k) => !SAETZE[k]); +pruef("Jede Server-Kennung hat einen Satz", + kennungen.size > 0 && ohneSatz.length === 0, + ohneSatz.length ? `ohne Satz: ${ohneSatz.map((k) => `${k} (${kennungen.get(k)})`).join(", ")}` : ""); + +/* Die Zahl gehört in die BEDINGUNG, nicht nur in den Meldetext. + `every()` auf einem leeren Feld liefert `true` — eine Prüfung, die + nichts gefunden hat, wäre sonst grün (01.09.2026, Shop). */ +pruef("Es wurden überhaupt Kennungen gefunden", kennungen.size >= 30, + `nur ${kennungen.size} — findet der Suchausdruck noch, was er soll?`); + +/* ---- 2. Angeschlossen ----------------------------------------------- */ +const skripte = readdirSync(JS).filter((f) => f.endsWith(".js")); + +/* 2a: Keine Rohanzeige mehr. Gesucht wird `.fehler` in derselben Zeile + wie eine Ausgabe — aber nur dort, wo KEIN `sagWas` und kein + Vergleich (`===`) steht. Ein Vergleich übersetzt selbst. */ +const roh = []; +for (const f of skripte) { + if (f === "meldung.js") continue; + const zeilen = readFileSync(join(JS, f), "utf8").split("\n"); + zeilen.forEach((z, i) => { + if (!/\.fehler/.test(z)) return; + if (!/(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z)) return; + if (/sagWas/.test(z) || /\.fehler\s*===/.test(z) || /\[\s*\w+\.fehler\s*\]/.test(z)) return; + roh.push(`${f}:${i + 1}`); + }); +} +pruef("Keine Seite zeigt eine Kennung roh an", roh.length === 0, + roh.length ? roh.join(", ") : ""); + +/* 2b: Wer sagWas benutzt, muss meldung.js laden. DAS IST DIE + WICHTIGERE HÄLFTE: Fehlt die Datei, ist `sagWas` nicht definiert, + und aus einer Fehlermeldung wird ein Absturz. */ +const benutzt = new Set(skripte.filter( + (f) => f !== "meldung.js" && /\bsagWas\(/.test(readFileSync(join(JS, f), "utf8")))); +const luecken = []; +for (const seite of readdirSync(SEITEN).filter((f) => f.endsWith(".html"))) { + const html = readFileSync(join(SEITEN, seite), "utf8"); + const geladen = [...html.matchAll(/src="assets\/js\/([a-z0-9-]+)\.js/g)].map((m) => m[1] + ".js"); + const braucht = geladen.filter((g) => benutzt.has(g)); + if (braucht.length && !geladen.includes("meldung.js")) { + luecken.push(`${seite} (braucht es wegen ${braucht.join(", ")})`); + } +} +pruef("Jede Seite, die sagWas braucht, lädt meldung.js", + benutzt.size > 0 && luecken.length === 0, + luecken.length ? luecken.join("; ") : (benutzt.size ? "" : "keine Datei benutzt sagWas — stimmt das?")); + +/* ---- 3. Lesbar ------------------------------------------------------ */ +const schlecht = []; +for (const [k, satz] of Object.entries(SAETZE)) { + if (/[a-z]+_[a-z]+/.test(satz)) schlecht.push(`${k}: enthält eine Kennung`); + else if (/\b(?:400|401|403|404|409|429|500|503)\b/.test(satz)) schlecht.push(`${k}: enthält eine Fehlernummer`); + else if (/\b(error|failed|invalid|denied|request|server error)\b/i.test(satz)) schlecht.push(`${k}: englisches Wort`); + else if (satz.length < 15) schlecht.push(`${k}: zu kurz ("${satz}")`); +} +pruef("Jeder Satz ist deutsch, ohne Kennung und ohne Zahl", + Object.keys(SAETZE).length > 0 && schlecht.length === 0, + schlecht.length ? schlecht.join("; ") : ""); + +/* ===================================================================== + DIE GEGENPROBEN + + Zu jeder Prüfung, die „alles in Ordnung" sagen kann, gehört der + Beweis, dass sie auch „nicht in Ordnung" sagen kann. Sonst weiß + niemand, ob sie überhaupt hinsieht. + ===================================================================== */ +console.log("\n --- Gegenproben (jede MUSS anschlagen) ---"); + +const g1 = (() => { const t = { ...SAETZE }; delete t.nicht_verfuegbar; + return [...kennungen.keys()].filter((k) => !t[k]).length > 0; })(); +pruef("Gegenprobe: fehlt ein Satz, schlägt Prüfung 1 an", g1); + +const g2 = (() => { + const z = " $('x').textContent = d.fehler || 'Ging nicht.';"; + return /\.fehler/.test(z) + && /(textContent\s*=|melde[n]?\(|alert\(|innerHTML\s*=)/.test(z) + && !/sagWas/.test(z) && !/\.fehler\s*===/.test(z); +})(); +pruef("Gegenprobe: eine Rohanzeige würde Prüfung 2a finden", g2); + +const g3 = (() => { + const s = "Fehler 503: request denied"; + return /[a-z]+_[a-z]+/.test(s) + || /\b(?:400|401|403|404|409|429|500|503)\b/.test(s) + || /\b(error|failed|invalid|denied|request)\b/i.test(s); +})(); +pruef("Gegenprobe: ein unlesbarer Satz würde Prüfung 3 finden", g3); + +console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`); +process.exit(fehl ? 1 : 0); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index df57f60a..242ec87b 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -75,6 +75,8 @@
+ +