Files
kaffeekasse-saas/scripts/http-smoke.php
T
clemensandClaude Sonnet 5 536ef2ead2 M6: Jahresabschluss als generisches Feature statt AOK-spezifischem Bonus-Skript
jahresauswertung.php verband sich bisher mit fest codierten (kaputten)
Zugangsdaten selbst zur Datenbank statt ueber config.php, hatte keine
Zugriffskontrolle und kein CSRF, und verteilte bei jedem Aufruf sofort
einen hart codierten Bonus-Topf (490 Striche a 0,20 Euro) per PHPMailer
(dessen Quelldateien im Repo fehlen) mit AOK-spezifischem Mailtext.

Nach Abstimmung mit dem Kunden als generisches, mandantenfaehiges Feature
neu gebaut statt nur deaktiviert oder rein lesend umgesetzt:

- Admin gibt einen frei waehlbaren Gesamtbetrag ein, das System verteilt
  ihn proportional zu den Jahresstrichen auf alle aktiven Mitglieder.
- Standardmaessig aktive Dry-Run-Checkbox zeigt die Verteilung, ohne zu
  buchen oder Mails zu verschicken.
- Bestaetigter Lauf bucht ueber dasselbe Zweig-Muster wie ueberall
  (Default-Mandant Dual-Write, andere Mandanten ledger_record_payment)
  und verschickt personalisierte Mails ueber saas_send_mail(), protokolliert
  im outbound_emails-Versandlog.
- Zugriffskontrolle ergaenzt (owner/admin/treasurer + Legacy-Fallback).
- http-smoke.php: jahresauswertung.php jetzt regulaerer Check statt
  uebersprungenem unsicherem Aufruf; damit sind keine Seiten mehr
  uebersprungen oder als bekannter offener Punkt markiert (26/26 gruen).

Live getestet: Dry-Run mit korrekter proportionaler Verteilung (Summe
ergibt exakt den Gesamtbetrag), Live-Lauf bucht und versendet korrekt,
Testdaten anschliessend vollstaendig entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 17:21:34 +02:00

285 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
$baseUrl = rtrim((string)(getenv('SMOKE_BASE_URL') ?: 'http://127.0.0.1:8080'), '/');
$issuePattern = '~(?:Deprecated|Warning|Fatal error|Parse error|Notice|Uncaught (?:Error|Exception))~i';
$checks = [
[
'label' => 'Landingpage',
'path' => 'landing.php',
'contains' => ['Kaffeeliste', 'Kundenkonto anlegen', 'Mandantenfähig'],
],
[
'label' => 'Dashboard',
'path' => 'index.php',
'contains' => ['Hallo Test Admin', 'Aktueller Stand', 'Jahresübersicht', 'Letzte Einzahlungen'],
],
[
'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',
'teilnehmerauswertung.php?user_id=',
'GM Negativ',
'gm-negative@test.local',
'-2,00 €',
'GM Positiv',
'23,60 €',
'GM Ohne Buchungen',
'0,00 €',
],
'not_contains' => ['GM Inaktiv'],
],
[
'label' => 'Ledger Preview',
'path' => 'ledger-preview.php',
'contains' => ['Ledger Preview', 'Tenant-Summen', 'Letzte Ledger-Buchungen'],
],
[
'label' => 'Mitgliederverwaltung',
'path' => 'mitarbeiterverwalten.php',
'contains' => ['Mitglieder verwalten', 'PayPal-Name'],
],
[
'label' => 'Letzte Einträge',
'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' => 'Login',
'path' => 'login.php',
'contains' => ['Login', 'Registrieren', 'Passwort vergessen'],
],
[
'label' => 'Registrierung',
'path' => 'register.php',
'contains' => ['Registrierung', 'Kundenkonto anlegen'],
],
[
'label' => 'Passwort vergessen',
'path' => 'passwort-vergessen.php',
'contains' => ['Passwort vergessen', 'Link vorbereiten'],
],
[
'label' => 'Mandantenauswahl Login-Schutz',
'path' => 'mandant-auswahl.php',
'contains' => ['Login'],
],
[
'label' => 'Passwort zurücksetzen Invalid',
'path' => 'passwort-zuruecksetzen.php',
'contains' => ['Passwort zurücksetzen', 'ungültig oder abgelaufen'],
],
[
'label' => 'E-Mail verifizieren Invalid',
'path' => 'email-verifizieren.php',
'contains' => ['E-Mail verifizieren', 'ungültig oder abgelaufen'],
],
[
'label' => 'Mandant-Einstellungen Login-Schutz',
'path' => 'mandant-einstellungen.php',
'contains' => ['Login'],
],
[
'label' => 'Namensanpassung',
'path' => 'namenanpassen.php',
'contains' => ['Anzeigenamen aktualisieren'],
],
[
'label' => 'Teilnehmerauswertung',
'path' => 'teilnehmerauswertung.php?user_id=1',
'contains' => ['Auswertung', 'Test Admin', 'Jahresübersicht', 'Letzte Einzahlungen'],
],
[
'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'],
],
[
'label' => 'PDF-Export',
'path' => 'exportKaffeeliste.php',
'contains' => ['%PDF'],
],
[
'label' => 'Mailversand',
'path' => 'mailversenden.php',
'contains' => ['Info-Mail versenden', 'Dry-Run', 'Versandlog'],
],
[
'label' => 'Jahresabschluss',
'path' => 'jahresauswertung.php',
'contains' => ['Jahresabschluss', 'Dry-Run', 'Jahresstriche gesamt'],
],
];
$knownIssueChecks = [
];
$skippedUnsafe = [
];
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;
}
}
foreach (($check['not_contains'] ?? []) as $needle) {
if (str_contains($response['body'], $needle)) {
$failures[] = "{$label}: unexpected text 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 regulären 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";