['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 = '

Hallo Team

Zweiter Absatz mit fett

Logo'; $mail = "From: Olaf Andresen \r\nTo: einsendungen@example.org\r\nSubject: Neue Geschichte Teil 12\r\nMessage-ID: \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: \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'] ?? ''), 'fett') && 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'] === '' && (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";