Files
2026-11-04 07:55:36 +00:00

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";