From 66bdd38d543c461df3b097cae9cacdf2ccef9320 Mon Sep 17 00:00:00 2001 From: Clemens Creutzburg Date: Sun, 12 Jul 2026 00:51:01 +0200 Subject: [PATCH] M1 Abschluss --- docs/m1-reconstructed-golden-master.md | 69 +++++++- docs/saas-umstrukturierungsplan.md | 23 ++- exportKaffeeliste.php | 2 +- scripts/http-smoke.php | 220 +++++++++++++++++++++++++ umfrage.php | 6 +- 5 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 scripts/http-smoke.php diff --git a/docs/m1-reconstructed-golden-master.md b/docs/m1-reconstructed-golden-master.md index 64779b5..a94bc15 100644 --- a/docs/m1-reconstructed-golden-master.md +++ b/docs/m1-reconstructed-golden-master.md @@ -1,6 +1,6 @@ # M1 Rekonstruierter Golden Master -Stand: 2026-07-11 +Stand: 2026-07-12 Da kein alter MS-SQL-Datenbankstand verfuegbar ist, wird M1 als rekonstruierter Golden Master aufgebaut. Die Referenz entsteht aus Legacy-Code, Screenshots und @@ -75,8 +75,10 @@ Fuer jeden Testfall sollen erwartete Werte dokumentiert werden: damit normale Dev-Daten nicht zwangsweise ueberschrieben werden. 3. Ein erstes Vergleichsskript `scripts/check-golden-master.php` erstellen. Erledigt. -4. Die wichtigsten Seiten per HTTP abrufen und auf erwartete Texte/Werte pruefen. -5. Danach M2 starten: technisches Fundament fuer SaaS-Struktur. +4. Die wichtigsten Seiten per HTTP abrufen und auf erwartete Texte/Werte + pruefen. Erledigt mit `scripts/http-smoke.php`. +5. Danach M2 starten: technisches Fundament fuer SaaS-Struktur. M1 ist als + Sicherheitsnetz nutzbar; offene Dependency-Themen sind unten dokumentiert. ## Skripte @@ -85,6 +87,8 @@ Fuer jeden Testfall sollen erwartete Werte dokumentiert werden: - `scripts/seed-golden-master.php`: legt Golden-Master-Testdaten idempotent an. - `scripts/check-golden-master.php`: prueft Salden, Jahreswerte, Rollen, Listenfilter, CSV-Lookup und Hinweise. +- `scripts/http-smoke.php`: ruft sichere GET-Seiten per HTTP ab und prueft auf + HTTP 200, erwartete Texte und PHP-Fehlermarker. ## Ausfuehrung @@ -93,6 +97,7 @@ Mit normal installierter PHP-CLI: ```bash php scripts/seed-golden-master.php php scripts/check-golden-master.php +php scripts/http-smoke.php ``` In der aktuellen lokalen Umgebung mit entpacktem PHP: @@ -107,6 +112,11 @@ LD_LIBRARY_PATH="$PWD/.local/php/usr/lib/x86_64-linux-gnu" \ "$PWD/.local/php/usr/bin/php8.3" \ -c "$PWD/.local/php-dev.ini" \ scripts/check-golden-master.php + +LD_LIBRARY_PATH="$PWD/.local/php/usr/lib/x86_64-linux-gnu" \ + "$PWD/.local/php/usr/bin/php8.3" \ + -c "$PWD/.local/php-dev.ini" \ + scripts/http-smoke.php ``` Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`, @@ -127,6 +137,28 @@ Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`, | Aktiver Hinweis | `[GM] Aktiver Hinweis fuer Golden-Master-Test` | | Abgelaufener Hinweis | `[GM] Abgelaufener Hinweis fuer Golden-Master-Test` | +## HTTP-Smoke-Abdeckung + +Der HTTP-Smoke-Test prueft nur GET-Seiten ohne bekannte Schreib- oder +Versand-Nebenwirkungen. + +| Seite | Erwartung | +| --- | --- | +| `index.php` | Dashboard laedt fuer `Test Admin` | +| `stricheintragen.php` | Stricherfassung laedt | +| `einzahlung.php` | Einzahlungserfassung laedt | +| `kaffeeliste.php` | Gesamtuebersicht laedt | +| `mitarbeiterverwalten.php` | Mitgliederverwaltung inkl. `PayPal-Name` laedt | +| `letzteneintraege.php` | letzte Einzahlungen und Striche laden | +| `csvupload.php` | CSV-Upload-Formular laedt | +| `hinweise.php` | Hinweisverwaltung laedt | +| `faq.php` | FAQ laedt | +| `namenanpassen.php` | Namensanpassung laedt | +| `teilnehmerauswertung.php?user_id=1` | Teilnehmerauswertung fuer Dev-Admin laedt | +| `mailausgebe.php` | Mailadress-Ausgabe laedt | +| `umfrage.php` | geschlossene Umfrage laedt | +| `umfrageergebnisse.php` | Umfrageauswertung laedt | + ## Akzeptanzkriterien - Testdaten sind reproduzierbar. @@ -134,13 +166,42 @@ Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`, - Salden und Jahreswerte sind automatisch pruefbar. - CSV-Importfaelle sind fachlich abgedeckt. - Die wichtigsten UI-Seiten liefern HTTP 200 ohne PHP-Warnings. +- Seiten mit Schreib- oder Versand-Nebenwirkungen werden nicht automatisch per + GET ausgefuehrt, sondern als Risiko dokumentiert. + +## Gefundene Fehler und Bewertung + +Im M1-Abschlusslauf wurden folgende Punkte geprueft: + +- `number_format(null)` in `kaffeeliste.php`: behoben; leere Summen werden als + `0` behandelt. +- `paypalname` in der Mitgliederverwaltung: eingepflegt und im Golden Master + abgedeckt. +- `umfrage.php`: `mb_strtolower()` ist in der lokalen PHP-Umgebung nicht + verfuegbar; Fallback auf `strtolower()` ergaenzt. +- `exportKaffeeliste.php`: Pfad auf `TCPDF/tcpdf.php` korrigiert. Der Export + bleibt trotzdem als bekannter Dependency-Fehler offen, weil die TCPDF-Kopie im + Repo unvollstaendig ist und `TCPDF/include/tcpdf_font_data.php` fehlt. +- `mailversenden.php`: nicht automatisch per HTTP ausgefuehrt, weil ein GET bei + vollstaendiger PHPMailer-Installation E-Mails versenden kann. Zusaetzlich ist + die PHPMailer-Kopie im Repo unvollstaendig. +- `jahresauswertung.php`: nicht automatisch per HTTP ausgefuehrt, weil ein GET + Jahresbuchungen schreiben und E-Mails versenden kann. Zusaetzlich ist die + PHPMailer-Kopie im Repo unvollstaendig. ## Aktueller Pruefstatus -Stand 2026-07-11: +Stand 2026-07-12: - `scripts/seed-golden-master.php` erfolgreich gegen die MySQL-Dev-Datenbank ausgefuehrt. - `scripts/check-golden-master.php` erfolgreich ausgefuehrt. - Zweiter Seed+Check-Lauf erfolgreich, Idempotenz bestaetigt. - Ergebnis: 104 Assertions bestanden. +- `scripts/http-smoke.php` erfolgreich gegen `http://127.0.0.1:8080` + ausgefuehrt. +- Ergebnis: 14 sichere Seiten bestanden ohne `Deprecated`, `Warning`, + `Fatal error`, `Parse error`, `Notice` oder `Uncaught Error`. +- Bekannter offener Punkt: PDF-Export wegen unvollstaendiger TCPDF-Abhaengigkeit. +- Bewusst nicht automatisch ausgefuehrt: `mailversenden.php` und + `jahresauswertung.php`, da GET dort Nebenwirkungen haben kann. diff --git a/docs/saas-umstrukturierungsplan.md b/docs/saas-umstrukturierungsplan.md index 588e50f..6242f2d 100644 --- a/docs/saas-umstrukturierungsplan.md +++ b/docs/saas-umstrukturierungsplan.md @@ -281,7 +281,7 @@ Uebersicht: | Meilenstein | Schwerpunkt | Hauptergebnis | | --- | --- | --- | | M0 | Baseline | Bestand, Sicherheit und Designreferenz sind dokumentiert | -| M1 | Golden Master | Legacy-Ergebnisse sind als Vergleichsbasis eingefroren | +| M1 | Golden Master | Legacy-Ergebnisse sind als Vergleichsbasis eingefroren; HTTP-Smoke prueft sichere Seiten | | M2 | Technisches Fundament | Public-/App-Struktur, Bootstrap, Session und CSRF stehen | | M3 | SaaS-Basis | Tenants, User, Registrierung, Login und Rollen funktionieren | | M4 | Datenmigration | Legacy-Daten sind tenant-sicher im Zielmodell abgebildet | @@ -335,23 +335,33 @@ Bestand liefern. Schritte: -- Referenzdatensatz aus Legacy erzeugen. +- Referenzdatensatz aus Legacy erzeugen. Erledigt als rekonstruierter Golden + Master in der MySQL-Dev-Datenbank. - Pro Mitglied berechnen: Gesamteinzahlungen, Gesamtausgaben, Gesamtstriche, - aktueller Stand, Jahreswerte. -- PDF-/Export-Summen als Referenz sichern. -- CSV-Importfaelle sammeln: Treffer, Dubletten, unbekannte Namen. + aktueller Stand, Jahreswerte. Erledigt mit `scripts/check-golden-master.php`. +- CSV-Importfaelle sammeln: Treffer, Dubletten, unbekannte Namen. Erledigt im + Golden-Master-Check. - Testfaelle fuer Guthaben, Schulden, Nullsaldo und inaktive Teilnehmer - anlegen. + anlegen. Erledigt. +- Sichere GET-Seiten per HTTP-Smoke pruefen. Erledigt mit + `scripts/http-smoke.php`. +- PDF-/Export-Summen als Referenz sichern. Offen, weil die TCPDF-Kopie im Repo + unvollstaendig ist. Ergebnis: - Golden-Master-Daten. - Vergleichsqueries oder Vergleichsskript. +- HTTP-Smoke-Test fuer sichere UI-Seiten. - Akzeptanzkriterien fuer Migration und neue App. +- Dokumentierte offene Punkte: PDF-Export/TCPDF, PHPMailer-Abhaengigkeit, + GET-Nebenwirkungen bei Mailversand und Jahresauswertung. Abhaengigkeiten: - M0 abgeschlossen. +- PDF-/Mail-/Jahresprozesse werden in M6 gezielt neu gestaltet, statt sie in M1 + per GET-Smoke auszufuehren. ### M2: Technisches Fundament @@ -608,6 +618,7 @@ UX-Pruefungen: | Bestehendes Design driftet weg | Designreferenz und App-Komponenten definieren | | CSV-Upload unsicher | Upload ausserhalb Webroot, Dateityppruefung, Importvorschau | | Mailversand nicht nachvollziehbar | Versandlog, Dry-Run, Job-Status | +| Unvollstaendige Vendor-Kopien fuer PDF/Mail | Dependencies sauber vendoren oder ersetzen, Smoke-Tests danach erweitern | | AD/LDAP blockiert SaaS-Onboarding | E-Mail/Passwort als Basis, SSO spaeter optional | | Kein DB-Schema im Repo | Schema exportieren und Migrationen einfuehren | diff --git a/exportKaffeeliste.php b/exportKaffeeliste.php index 6d65bba..1f68f31 100644 --- a/exportKaffeeliste.php +++ b/exportKaffeeliste.php @@ -54,7 +54,7 @@ $KostenproStrichtemp = $row["KostenproStrich"]; $KostenproStrich = number_format($KostenproStrichtemp, 2, ',', '.'); // TCPDF-Bibliothek einbinden -require_once('tcpdf/tcpdf.php'); +require_once(__DIR__ . '/TCPDF/tcpdf.php'); class MyCustomPDFWithWatermark extends TCPDF { public function Header() { diff --git a/scripts/http-smoke.php b/scripts/http-smoke.php new file mode 100644 index 0000000..49f8d14 --- /dev/null +++ b/scripts/http-smoke.php @@ -0,0 +1,220 @@ + 'Dashboard', + 'path' => 'index.php', + 'contains' => ['Hallo Test Admin', 'Aktueller Stand'], + ], + [ + 'label' => 'Striche erfassen', + 'path' => 'stricheintragen.php', + 'contains' => ['Anzahl der Striche'], + ], + [ + 'label' => 'Einzahlungen erfassen', + 'path' => 'einzahlung.php', + 'contains' => ['Einzahlungen'], + ], + [ + 'label' => 'Kaffeeliste', + 'path' => 'kaffeeliste.php', + 'contains' => ['Aktive Mitarbeiter'], + ], + [ + 'label' => 'Mitgliederverwaltung', + 'path' => 'mitarbeiterverwalten.php', + 'contains' => ['Mitglieder verwalten', 'PayPal-Name'], + ], + [ + 'label' => 'Letzte Eintraege', + 'path' => 'letzteneintraege.php', + 'contains' => ['Letzte 100 Einzahlungen'], + ], + [ + 'label' => 'CSV-Upload', + 'path' => 'csvupload.php', + 'contains' => ['CSV Verarbeitung'], + ], + [ + 'label' => 'Hinweise', + 'path' => 'hinweise.php', + 'contains' => ['Kaffeeliste - Hinweise'], + ], + [ + 'label' => 'FAQ', + 'path' => 'faq.php', + 'contains' => ['FAQ - Kaffeeliste'], + ], + [ + 'label' => 'Namensanpassung', + 'path' => 'namenanpassen.php', + 'contains' => ['Anzeigenamen aktualisieren'], + ], + [ + 'label' => 'Teilnehmerauswertung', + 'path' => 'teilnehmerauswertung.php?user_id=1', + 'contains' => ['Auswertung', 'Test Admin'], + ], + [ + 'label' => 'Mailausgabe', + 'path' => 'mailausgebe.php', + 'contains' => ['admin@test.local'], + ], + [ + 'label' => 'Umfrage', + 'path' => 'umfrage.php', + 'contains' => ['Kaffeeliste', 'Danke'], + ], + [ + 'label' => 'Umfrageergebnisse', + 'path' => 'umfrageergebnisse.php', + 'contains' => ['Umfrage', 'Ergebnisse'], + ], +]; + +$knownIssueChecks = [ + [ + 'label' => 'PDF-Export', + 'path' => 'exportKaffeeliste.php', + 'expected_issue' => '~TCPDF/include/tcpdf_font_data\.php|Failed opening required .*/TCPDF/include/~i', + 'reason' => 'TCPDF ist im Repo nur teilweise vorhanden; das include-Verzeichnis fehlt.', + ], +]; + +$skippedUnsafe = [ + [ + 'path' => 'mailversenden.php', + 'reason' => 'GET kann bei vollstaendiger PHPMailer-Installation E-Mails versenden.', + ], + [ + 'path' => 'jahresauswertung.php', + 'reason' => 'GET kann Jahresbuchungen schreiben und E-Mails versenden; PHPMailer-Abhaengigkeit ist zudem unvollstaendig.', + ], +]; + +function smoke_fetch(string $url): array +{ + $context = stream_context_create([ + 'http' => [ + 'method' => 'GET', + 'timeout' => 15, + 'ignore_errors' => true, + 'header' => "User-Agent: KaffeelisteHttpSmoke/1.0\r\n", + ], + ]); + + $body = @file_get_contents($url, false, $context); + $headers = $http_response_header ?? []; + $status = 0; + + foreach ($headers as $header) { + if (preg_match('~^HTTP/\S+\s+(\d{3})~', $header, $matches) === 1) { + $status = (int)$matches[1]; + } + } + + if ($body === false) { + $error = error_get_last(); + return [ + 'status' => $status, + 'body' => '', + 'error' => $error['message'] ?? 'Unknown HTTP error', + ]; + } + + return [ + 'status' => $status, + 'body' => $body, + 'error' => null, + ]; +} + +function smoke_url(string $baseUrl, string $path): string +{ + return $baseUrl . '/' . ltrim($path, '/'); +} + +$failures = []; +$passes = 0; + +echo "HTTP smoke base URL: {$baseUrl}\n"; + +foreach ($checks as $check) { + $url = smoke_url($baseUrl, $check['path']); + $response = smoke_fetch($url); + $label = $check['label'] . ' (' . $check['path'] . ')'; + + if ($response['error'] !== null) { + $failures[] = "{$label}: {$response['error']}"; + echo "FAIL {$label}\n"; + continue; + } + + if ($response['status'] !== 200) { + $failures[] = "{$label}: expected HTTP 200, got HTTP {$response['status']}"; + echo "FAIL {$label}\n"; + continue; + } + + if (preg_match($issuePattern, $response['body'], $matches) === 1) { + $failures[] = "{$label}: PHP issue marker found ({$matches[0]})"; + echo "FAIL {$label}\n"; + continue; + } + + foreach ($check['contains'] as $needle) { + if (!str_contains($response['body'], $needle)) { + $failures[] = "{$label}: expected text not found: {$needle}"; + echo "FAIL {$label}\n"; + continue 2; + } + } + + $passes++; + echo "PASS {$label}\n"; +} + +foreach ($knownIssueChecks as $check) { + $url = smoke_url($baseUrl, $check['path']); + $response = smoke_fetch($url); + $label = $check['label'] . ' (' . $check['path'] . ')'; + + if ($response['error'] !== null) { + echo "KNOWN {$label}: {$check['reason']} ({$response['error']})\n"; + continue; + } + + if (preg_match($check['expected_issue'], $response['body']) === 1) { + echo "KNOWN {$label}: {$check['reason']}\n"; + continue; + } + + if ($response['status'] === 200 && preg_match($issuePattern, $response['body']) !== 1) { + echo "RESOLVED {$label}: bitte in die regulaeren Smoke-Checks aufnehmen.\n"; + continue; + } + + $failures[] = "{$label}: unexpected response while checking known issue"; + echo "FAIL {$label}\n"; +} + +foreach ($skippedUnsafe as $skip) { + echo "SKIP {$skip['path']}: {$skip['reason']}\n"; +} + +if ($failures !== []) { + echo "\nHTTP smoke failed with " . count($failures) . " failure(s):\n"; + foreach ($failures as $failure) { + echo "- {$failure}\n"; + } + exit(1); +} + +echo "\nHTTP smoke passed with {$passes} checked pages.\n"; + diff --git a/umfrage.php b/umfrage.php index 17efbb8..cf9a286 100644 --- a/umfrage.php +++ b/umfrage.php @@ -55,7 +55,9 @@ function h($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } $errors = []; $success = false; -$emailNorm = mb_strtolower(trim((string)$mailadress)); +$emailNorm = function_exists('mb_strtolower') + ? mb_strtolower(trim((string)$mailadress), 'UTF-8') + : strtolower(trim((string)$mailadress)); if ($emailNorm === '' || !filter_var($emailNorm, FILTER_VALIDATE_EMAIL)) { $errors[] = "Keine gültige E-Mail im System gefunden (Variable \$mailadress)."; } @@ -339,4 +341,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$errors) { \ No newline at end of file +?>