98 lines
6.7 KiB
PHP
98 lines
6.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Ganzer Weg des E-Mail-Abrufs gegen einen kleinen IMAP-Server (tests/fixtures/fake_imap_server.php), gestartet über den Abruf von Hand:
|
|
* Aus der Mail wird ein Ticket mit Text (samt Absätzen), HTML-Fassung, eingebettetem Bild und Anhang; die Mail wird als gelesen markiert,
|
|
* ein zweiter Abruf findet nichts mehr, und der Abruf von Hand lässt sich nicht kurz hintereinander doppelt starten.
|
|
*/
|
|
|
|
if (!function_exists('imap_open')) { echo "Abruf (ganzer Weg): übersprungen, PHP-IMAP fehlt.\n"; exit(0); }
|
|
|
|
$tmp = sys_get_temp_dir() . '/deskhub-e2e-' . bin2hex(random_bytes(4));
|
|
mkdir($tmp . '/uploads', 0755, true);
|
|
putenv('DESKHUB_TESTING=1');
|
|
putenv('DESKHUB_SQLITE_PATH=:memory:');
|
|
putenv('DESKHUB_CONFIG_JSON=' . json_encode(['uploads' => ['path' => $tmp . '/uploads', 'max_size_bytes' => 100000, 'allowed_extensions' => ['png', 'pdf', 'txt', 'docx']]]));
|
|
require_once dirname(__DIR__) . '/includes/bootstrap.php';
|
|
require_once dirname(__DIR__) . '/includes/mail_fetch_manual.php';
|
|
|
|
$failures = [];
|
|
$check = function (bool $ok, string $what) use (&$failures): void {
|
|
if (!$ok) $failures[] = $what;
|
|
};
|
|
$pdo = get_db();
|
|
|
|
$docx = random_bytes(700);
|
|
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==');
|
|
$html = '<html><body><p>Hallo Team</p><p>Zweiter Absatz mit <b>fett</b></p><img src="cid:logo1@example.de" alt="Logo"></body></html>';
|
|
$mail = "From: Olaf Andresen <olaf@example.de>\r\nTo: einsendungen@example.org\r\nSubject: Neue Geschichte Teil 12\r\nMessage-ID: <e2e1@example.de>\r\nDate: Mon, 03 Nov 2026 00:39:00 +0100\r\nMIME-Version: 1.0\r\n"
|
|
. "Content-Type: multipart/mixed; boundary=\"BND1\"\r\n\r\n"
|
|
. "--BND1\r\nContent-Type: multipart/related; boundary=\"BND2\"\r\n\r\n"
|
|
. "--BND2\r\nContent-Type: multipart/alternative; boundary=\"BND3\"\r\n\r\n"
|
|
. "--BND3\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\nHallo Team\r\n\r\nZweiter Absatz mit fett\r\n"
|
|
. "--BND3\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($html))
|
|
. "--BND3--\r\n"
|
|
. "--BND2\r\nContent-Type: image/png; name=\"logo.png\"\r\nContent-Disposition: inline; filename=\"logo.png\"\r\nContent-ID: <logo1@example.de>\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($png))
|
|
. "--BND2--\r\n"
|
|
. "--BND1\r\nContent-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name=\"Teil 12.docx\"\r\nContent-Disposition: attachment; filename=\"Teil 12.docx\"\r\nContent-Transfer-Encoding: base64\r\n\r\n" . chunk_split(base64_encode($docx))
|
|
. "--BND1--\r\n";
|
|
file_put_contents($tmp . '/mail.eml', $mail);
|
|
|
|
$port = 0;
|
|
for ($try = 0; $try < 20 && !$port; $try++) {
|
|
$p = random_int(20000, 60000);
|
|
$s = @stream_socket_server('tcp://127.0.0.1:' . $p);
|
|
if ($s) { fclose($s); $port = $p; }
|
|
}
|
|
$proc = proc_open([PHP_BINARY, __DIR__ . '/fixtures/fake_imap_server.php', (string) $port, $tmp . '/mail.eml', $tmp . '/imap.log'], [0 => ['file', 'php://stdin', 'r'], 1 => ['file', $tmp . '/srv.out', 'w'], 2 => ['file', $tmp . '/srv.err', 'w']], $pipes);
|
|
for ($i = 0; $i < 50; $i++) {
|
|
$c = @fsockopen('127.0.0.1', $port, $en, $es, 0.2);
|
|
if ($c) { fclose($c); break; }
|
|
usleep(100000);
|
|
}
|
|
|
|
try {
|
|
$deptId = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
|
|
$pdo->prepare("INSERT INTO imap_accounts (label, host, port, encryption, username, password_enc, folder, after_fetch, department_id, priority_id, is_active) VALUES ('Test','127.0.0.1',?,'none','u',?,'INBOX','seen',?,2,1)")
|
|
->execute([$port, encrypt_secret('geheim'), $deptId]);
|
|
|
|
$r = mail_fetch_manual($pdo);
|
|
$check($r['ok'] && $r['processed'] === 1 && $r['new_tickets'] === 1, 'Abruf von Hand: 1 neues Ticket: ' . json_encode($r) . ' | ' . @file_get_contents($tmp . '/srv.err'));
|
|
$t = $pdo->query("SELECT * FROM tickets WHERE subject = 'Neue Geschichte Teil 12'")->fetch(PDO::FETCH_ASSOC);
|
|
$check((bool) $t && $t['source'] === 'email' && $t['requester_email'] === 'olaf@example.de', 'Ticket aus der Mail angelegt');
|
|
$th = $t ? $pdo->query('SELECT * FROM ticket_threads WHERE ticket_id = ' . (int) $t['id'])->fetch(PDO::FETCH_ASSOC) : [];
|
|
$check(($th['message'] ?? '') === "Hallo Team\n\nZweiter Absatz mit fett", 'Text mit Absatz: ' . json_encode($th['message'] ?? null));
|
|
$check(str_contains((string) ($th['message_html'] ?? ''), '<b>fett</b>') && str_contains((string) ($th['message_html'] ?? ''), 'cid:logo1@example.de'), 'HTML-Fassung gespeichert');
|
|
$att = $th ? $pdo->query('SELECT * FROM ticket_attachments WHERE thread_id = ' . (int) $th['id'] . ' ORDER BY is_inline')->fetchAll(PDO::FETCH_ASSOC) : [];
|
|
$check(count($att) === 2 && $att[0]['original_name'] === 'Teil 12.docx' && (int) $att[1]['is_inline'] === 1 && $att[1]['content_id'] === 'logo1@example.de', 'Anhang und eingebettetes Bild gespeichert: ' . json_encode(array_column($att, 'original_name')));
|
|
$check($att && file_get_contents($tmp . '/uploads/' . $att[0]['stored_name']) === $docx, 'Inhalt des Anhangs stimmt');
|
|
$src = $t ? $pdo->query('SELECT * FROM ticket_email_sources WHERE ticket_id = ' . (int) $t['id'])->fetch(PDO::FETCH_ASSOC) : false;
|
|
$check((bool) $src && $src['message_id'] === '<e2e1@example.de>' && (int) $src['message_uid'] === 7, 'Herkunft der Mail (UID, Message-ID) ist vermerkt');
|
|
|
|
// Direkt danach: gesperrt
|
|
$r2 = mail_fetch_manual($pdo);
|
|
$check(!$r2['ok'] && str_contains($r2['message'], 'gerade eben'), 'zweiter Klick direkt danach wird abgewiesen');
|
|
// Nach der Sperrzeit: nichts Neues (Mail ist als gelesen markiert)
|
|
$pdo->prepare("UPDATE settings SET setting_value = ? WHERE setting_key = 'cron_fetch_emails_last_run'")->execute([date('Y-m-d H:i:s', time() - 120)]);
|
|
$r3 = mail_fetch_manual($pdo);
|
|
$check($r3['ok'] && $r3['processed'] === 0 && str_contains($r3['message'], 'Keine neuen E-Mails'), 'zweiter Abruf findet nichts mehr: ' . json_encode($r3));
|
|
$check((int) $pdo->query("SELECT COUNT(*) FROM tickets WHERE subject = 'Neue Geschichte Teil 12'")->fetchColumn() === 1, 'es gibt weiterhin genau ein Ticket');
|
|
} finally {
|
|
if (is_resource($proc)) { proc_terminate($proc); proc_close($proc); }
|
|
}
|
|
|
|
$logTxt = (string) @file_get_contents($tmp . '/imap.log');
|
|
$rm = function (string $d) use (&$rm) {
|
|
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
|
|
@rmdir($d);
|
|
};
|
|
if ($failures) $failures[] = 'IMAP-Protokoll (Ende): ' . substr($logTxt, -1200);
|
|
$rm($tmp);
|
|
|
|
if ($failures) {
|
|
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
|
|
exit(1);
|
|
}
|
|
echo "Mail fetch end-to-end checks passed.\n";
|