From e5915f3cc282b8f782ebe2ed9edc23034df4d910 Mon Sep 17 00:00:00 2001 From: JUSN Date: Mon, 21 Sep 2026 14:42:09 +0200 Subject: [PATCH] Gesundheits-Check: ein Befehl prueft alles, was schon mal still kaputt war MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Nutzer fragte zu Recht, warum staendig neue Probleme auftauchen. Antwort: Sie tauchen nicht auf, wir schauen erst jetzt hin — und es gab bisher keine einzige automatische Pruefung. Das Warteliste-Formular war SECHS WOCHEN kaputt, ohne dass etwas Alarm geschlagen haette. tools/pruefung.js prueft in ~30 Sekunden zehn Bereiche, jeder davon war schon einmal ein echter Fehler: 1 abgeschnittene Zeilen '[...]' (Werkzeug kuerzte auf 200 Zeichen) 2 Warteliste-Formular vollstaendig (war 186 statt 248 Zeichen -> tot) 3 strukturierte Daten gueltig (Google meldete 'Fehlerhafter Aufbau') 4 verlinkte Dateien existieren (manifest.json lieferte HTML) 5 Preis-IDs Worker == Seiten (Kauf scheitert sonst stumm) 6 alle Sitemap-Adressen liefern 200 7 Kauf erzeugt echte cs_live_-Seite + fremde Origins abgewiesen 8 Webhook-Secret gesetzt (sonst keine Wiederherstellungs-Mail) 9 KI antwortet (Schluessel war zweimal ungueltig) 10 Turnstile eingebunden (sonst Missbrauch auf eigene Kosten) Gegengeprueft: Mit absichtlich eingebauten Fehlern (abgeschnittene Zeile, gekuerzte Formular-Adresse, kaputtes JSON-LD) schlaegt sie bei allen dreien an und liefert Rueckgabewert 1. Aufruf: npm run pruefung Co-Authored-By: Claude Opus 5 --- package.json | 3 +- tools/pruefung.js | 186 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tools/pruefung.js diff --git a/package.json b/package.json index b9e3148..67a0f3e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", - "test": "jest" + "test": "jest", + "pruefung": "node tools/pruefung.js" }, "jest": { "preset": "jest-expo" diff --git a/tools/pruefung.js b/tools/pruefung.js new file mode 100644 index 0000000..88ac968 --- /dev/null +++ b/tools/pruefung.js @@ -0,0 +1,186 @@ +/** + * Gesundheits-Check für BehördenKlar. + * + * node tools/pruefung.js + * + * Prüft in EINEM Durchlauf alles, was in der Vergangenheit schon einmal + * still kaputt war — ohne dass es jemand gemerkt hat: + * + * - Warteliste-Formular (war 6 Wochen kaputt: Adresse abgeschnitten) + * - abgeschnittene Zeilen "[...]" (ein Werkzeug kürzte auf 200 Zeichen) + * - ungültige strukturierte Daten (Google meldete "Fehlerhafter Aufbau") + * - tote Verweise auf Dateien, die es nicht gibt (manifest.json lieferte HTML) + * - Bezahlung, KI-Analyse und Webhook — die drei Dinge, an denen Geld hängt + * + * Rückgabewert 0 = alles in Ordnung, 1 = mindestens ein Problem. + * Nach jedem Deploy ausführen. Dauert etwa 30 Sekunden. + */ +const { execFileSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const WEB = path.join(__dirname, '..', 'webseite'); +const BASIS = 'https://behoerdenklar.de'; +const WORKER = 'https://behoerdenklar-proxy.behoerdenbrief.workers.dev'; +const BREVO_LAENGE = 248; // vollständige Formular-Adresse + +let fehler = 0; +let warnungen = 0; + +function ok(text) { console.log(' \x1b[32m✓\x1b[0m ' + text); } +function schlecht(text) { console.log(' \x1b[31m✗ ' + text + '\x1b[0m'); fehler++; } +function warnung(text) { console.log(' \x1b[33m!\x1b[0m ' + text); warnungen++; } +function titel(text) { console.log('\n\x1b[1m' + text + '\x1b[0m'); } + +/** Holt eine Adresse. curl statt fetch, damit es überall gleich läuft. */ +function hole(url, optionen = []) { + try { + return execFileSync('curl', ['-s', '--max-time', '30', ...optionen, url], + { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 }); + } catch { return ''; } +} +function status(url) { + try { + return execFileSync('curl', ['-s', '-o', '/dev/null', '-w', '%{http_code}', '--max-time', '30', url], + { encoding: 'utf8' }).trim(); + } catch { return '000'; } +} +function htmlDateien() { + const raus = []; + (function lauf(ordner) { + for (const eintrag of fs.readdirSync(ordner, { withFileTypes: true })) { + const p = path.join(ordner, eintrag.name); + if (eintrag.isDirectory()) lauf(p); + else if (eintrag.name.endsWith('.html')) raus.push(p); + } + })(WEB); + return raus; +} + +// ───────────────────────────────────────────────────────────── +titel('1. Abgeschnittene Texte'); +{ + const betroffen = htmlDateien().filter((p) => fs.readFileSync(p, 'utf8').includes('[...]')); + if (betroffen.length) betroffen.forEach((p) => schlecht('abgeschnitten: ' + path.relative(WEB, p))); + else ok('keine Datei enthält "[...]"'); +} + +titel('2. Warteliste-Formular'); +{ + let geprueft = 0; + for (const p of htmlDateien()) { + const s = fs.readFileSync(p, 'utf8'); + const t = s.match(/action="(https:\/\/86550585[^"]*)"/); + if (!t) continue; + geprueft++; + if (t[1].length !== BREVO_LAENGE) { + schlecht(`${path.relative(WEB, p)}: Adresse ${t[1].length} statt ${BREVO_LAENGE} Zeichen — Anmeldungen gehen ins Leere!`); + } + } + if (geprueft && !fehler) ok(`${geprueft} Formulare, alle Adressen vollständig`); + if (!geprueft) warnung('kein Warteliste-Formular gefunden'); +} + +titel('3. Strukturierte Daten (Google)'); +{ + let anzahl = 0, kaputt = 0; + for (const p of htmlDateien()) { + const s = fs.readFileSync(p, 'utf8'); + for (const m of s.matchAll(/