106 lines
6.7 KiB
PHP
106 lines
6.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Prüft die Mail-Warteschlange: Eine fehlgeschlagene Mail wird gespeichert (Test-Mails nicht), später erneut versucht (mit wachsenden
|
|
* Abständen), nach Ende der Störung gesendet, nach zu vielen Versuchen aufgegeben, nie doppelt gesendet; dazu Ausschalter,
|
|
* Gesundheitsanzeige und Cron-Job.
|
|
* Der SMTP-Server wird durch tests/fixtures/smtp_server.php ersetzt: erst nicht erreichbar, dann gestartet.
|
|
*/
|
|
|
|
$db = sys_get_temp_dir() . '/deskhub-mq-' . bin2hex(random_bytes(4)) . '.sqlite';
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_SQLITE_PATH=' . $db);
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; };
|
|
$pdo = get_db();
|
|
$port = random_int(33000, 33499);
|
|
$log = sys_get_temp_dir() . '/deskhub-mq-smtp-' . bin2hex(random_bytes(4)) . '.log';
|
|
set_setting('mail_enabled', '1');
|
|
set_setting('smtp_host', '127.0.0.1');
|
|
set_setting('smtp_port', (string) $port);
|
|
set_setting('smtp_encryption', 'none');
|
|
set_setting('smtp_username', '');
|
|
set_setting('smtp_from_email', 'support@firma.example');
|
|
$count = fn(): int => (int) $pdo->query('SELECT COUNT(*) FROM mail_queue')->fetchColumn();
|
|
|
|
// 1) Server nicht erreichbar: Mail wird gespeichert
|
|
$ok = send_mail('kunde@firma.example', 'Ihre Antwort', '<p>Hallo</p>');
|
|
$check($ok === false && mail_last_queued() === true, 'Fehlgeschlagene Mail kommt in die Warteschlange');
|
|
$q = $pdo->query('SELECT * FROM mail_queue')->fetchAll();
|
|
$check(count($q) === 1 && $q[0]['status'] === 'pending' && $q[0]['to_email'] === 'kunde@firma.example' && (int) $q[0]['attempts'] === 1 && $q[0]['body'] === '<p>Hallo</p>', 'Eintrag enthält Empfänger, Text und den ersten Versuch');
|
|
$check($q && (string) $q[0]['last_error'] !== '', 'Der Fehlergrund wird gespeichert');
|
|
$check($q && strtotime((string) $q[0]['next_attempt_at']) > time() + 240, 'Nächster Versuch frühestens in etwa 5 Minuten');
|
|
|
|
// Test-Mails und .invalid-Adressen werden nicht eingereiht
|
|
$ok2 = send_mail('test@firma.example', 'Test', '<p>t</p>', null, false);
|
|
$check($ok2 === false && mail_last_queued() === false && $count() === 1, 'Test-Mails (queue=false) kommen nicht in die Warteschlange');
|
|
$check(send_mail('bot@x.invalid', 'x', 'x') === true && $count() === 1, 'Adressen mit .invalid werden übersprungen');
|
|
|
|
// 2) Noch nicht fällig: nichts passiert
|
|
$r = mail_queue_run($pdo);
|
|
$check($r['sent'] === 0 && $r['failed'] === 0, 'Noch nicht fällige Mails werden nicht versucht');
|
|
|
|
// 3) Fällig, Server weiter tot: nächster Versuch später (wachsender Abstand)
|
|
$pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'");
|
|
$r = mail_queue_run($pdo);
|
|
$row = $pdo->query('SELECT * FROM mail_queue')->fetch();
|
|
$check($r['retry'] === 1 && (int) $row['attempts'] === 2 && $row['status'] === 'pending', 'Zweiter Fehlversuch bleibt in der Warteschlange');
|
|
$check(strtotime((string) $row['next_attempt_at']) > time() + 9 * 60 && strtotime((string) $row['next_attempt_at']) < time() + 11 * 60, 'Abstand wächst auf 10 Minuten');
|
|
|
|
// Gesundheitsanzeige
|
|
$check(in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt die wartenden Mails');
|
|
|
|
// 4) Ausschalter
|
|
set_setting('mail_queue_enabled', '0');
|
|
send_mail('zweiter@firma.example', 'Aus', '<p>x</p>');
|
|
$check($count() === 1, 'Ausgeschaltete Warteschlange nimmt nichts mehr an');
|
|
set_setting('mail_queue_enabled', '1');
|
|
|
|
// 5) Server wieder da: Mail geht raus, genau einmal
|
|
$proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/smtp_server.php', (string) $port, $log], [1 => ['file', sys_get_temp_dir() . '/deskhub-mq-out.txt', 'w'], 2 => ['file', sys_get_temp_dir() . '/deskhub-mq-err.txt', 'w']], $pipes);
|
|
for ($i = 0; $i < 50 && !is_file($log); $i++) usleep(100000);
|
|
$pdo->exec("UPDATE mail_queue SET next_attempt_at = '2000-01-01 00:00:00'");
|
|
$r = mail_queue_run($pdo);
|
|
$row = $pdo->query('SELECT * FROM mail_queue')->fetch();
|
|
$check($r['sent'] === 1 && $row['status'] === 'sent' && $row['sent_at'] !== null && $row['last_error'] === null, 'Nach Ende der Störung wird die Mail nachträglich gesendet');
|
|
$r = mail_queue_run($pdo);
|
|
$check($r['sent'] === 0, 'Eine gesendete Mail wird nicht noch einmal gesendet');
|
|
$received = is_file($log) ? substr_count((string) file_get_contents($log), 'MAIL kunde@firma.example') : 0;
|
|
$check($received === 1, 'Der Server hat die Mail genau einmal erhalten (erhalten: ' . $received . ')');
|
|
$check(!in_array('mail_queue_pending', array_column(system_health($pdo), 'code'), true), 'Statusanzeige ist wieder sauber');
|
|
if (is_resource($proc)) { proc_terminate($proc); proc_close($proc); }
|
|
usleep(300000);
|
|
|
|
// 6) Aufgeben nach zu vielen Versuchen und nach 48 Stunden
|
|
$ins = $pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES (?, 'S', '<p>a</p>', 'pending', ?, '2000-01-01 00:00:00', ?)");
|
|
$ins->execute(['alt@firma.example', 9, db_now()]);
|
|
$r = mail_queue_run($pdo);
|
|
$check($r['gave_up'] === 1 && $pdo->query("SELECT status FROM mail_queue WHERE to_email = 'alt@firma.example'")->fetchColumn() === 'failed', 'Nach dem 10. Fehlversuch wird die Mail aufgegeben');
|
|
$check(in_array('mail_queue_failed', array_column(system_health($pdo), 'code'), true), 'Statusanzeige nennt aufgegebene Mails');
|
|
$ins->execute(['uralt@firma.example', 1, date('Y-m-d H:i:s', time() - 3 * 86400)]);
|
|
mail_queue_run($pdo);
|
|
$check($pdo->query("SELECT status FROM mail_queue WHERE to_email = 'uralt@firma.example'")->fetchColumn() === 'failed', 'Nach 48 Stunden wird die Mail aufgegeben');
|
|
|
|
// 7) Reservierung: Eine bereits reservierte (noch nicht fällige) Mail wird nicht bearbeitet
|
|
$pdo->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, created_at) VALUES ('res@firma.example','Res','<p>r</p>','pending',1,?,?)")->execute([date('Y-m-d H:i:s', time() + 600), db_now()]);
|
|
$r = mail_queue_run($pdo);
|
|
$check($r['sent'] === 0 && $r['failed'] === 0, 'Eine reservierte Mail wird nicht doppelt bearbeitet');
|
|
|
|
// 8) Cron-Job und Menü
|
|
require_once dirname(__DIR__) . '/cron/process_mail_queue.php';
|
|
$res = run_process_mail_queue($pdo);
|
|
$check(isset($res['log'][0]) && str_contains($res['log'][0], 'Mail-Warteschlange') && get_setting('cron_process_mail_queue_last_run', '') !== '', 'Cron-Job läuft und merkt sich den Zeitpunkt');
|
|
$check(isset(pseudo_cron_job_definitions()['process_mail_queue']), 'Der eingebaute Hintergrundbetrieb kennt den Job');
|
|
$check(str_contains((string) file_get_contents(dirname(__DIR__) . '/staff/_header.php'), '/staff/mail_queue.php'), 'Verwaltungsseite ist im Menü verlinkt');
|
|
|
|
@unlink($db);
|
|
@unlink($log);
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "Mail queue checks passed.\n";
|