Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Tickets\MailDeliveryIncomplete;
use OpenNIT\Tickets\TicketManager;
/**
* Deckt die Bausteine ab, die Mail-Verlust im Ticketmodul verhindern.
*
* Abruf: Die Unterscheidung „dieselbe Mail erneut gesehen" (Aufräumen war
* fehlgeschlagen) gegen „andere Mail mit wiederverwendeter Message-ID"
* (Scanner, Monitoring) entscheidet, ob eine Mail übersprungen oder als
* eigenes Ticket aufgenommen wird.
*
* Versand: Ein teilweise gescheiterter Versand trägt die bereits versorgten
* Empfänger, damit der nächste Versuch weder Empfänger auslässt noch doppelt
* zustellt.
*/
return static function (): void {
// ── Abruf: Mail-Identität gegen das gespeicherte Ticket ────────────────
test('Dieselbe Mail wird am Ticket wiedererkannt', function () {
$ticket = ['from_email' => 'kunde@firma.de', 'subject' => 'Drucker defekt'];
assertTrue(TicketManager::isSameMailIdentity($ticket, 'kunde@firma.de', 'Drucker defekt'));
// Groß-/Kleinschreibung der Adresse und Randabstände sind unerheblich
assertTrue(TicketManager::isSameMailIdentity($ticket, ' Kunde@Firma.DE ', 'Drucker defekt'));
});
test('Eine andere Mail mit recycelter Message-ID wird als solche erkannt', function () {
$ticket = ['from_email' => 'scanner@firma.de', 'subject' => 'Scan 2026-08-20'];
// Gleicher Absender (das Gerät), aber anderer Betreff → andere Mail
assertFalse(TicketManager::isSameMailIdentity($ticket, 'scanner@firma.de', 'Scan 2026-08-21'));
// Anderer Absender → andere Mail, unabhängig vom Betreff
assertFalse(TicketManager::isSameMailIdentity($ticket, 'monitoring@firma.de', 'Scan 2026-08-20'));
// Ohne Absender ist keine Zuordnung möglich
assertFalse(TicketManager::isSameMailIdentity($ticket, '', 'Scan 2026-08-20'));
});
test('Der beim Anlegen gekürzte Betreff wird beim Vergleich gleich behandelt', function () {
$lang = str_repeat('A', 620);
$gekuerzt = mb_substr($lang, 0, 500);
$ticket = ['from_email' => 'kunde@firma.de', 'subject' => $gekuerzt];
assertTrue(TicketManager::isSameMailIdentity($ticket, 'kunde@firma.de', $lang));
});
test('Eine Mail ohne Betreff trifft die gespeicherten Platzhalter', function () {
assertTrue(TicketManager::isSameMailIdentity(
['from_email' => 'kunde@firma.de', 'subject' => '(kein Betreff)'],
'kunde@firma.de',
''
));
assertTrue(TicketManager::isSameMailIdentity(
['from_email' => 'kunde@firma.de', 'subject' => '(E-Mail ohne lesbaren Betreff)'],
'kunde@firma.de',
''
));
// Leerer Betreff gegen einen echten Betreff bleibt eine andere Mail
assertFalse(TicketManager::isSameMailIdentity(
['from_email' => 'kunde@firma.de', 'subject' => 'Drucker defekt'],
'kunde@firma.de',
''
));
});
// ── Versand: teilweiser Fehlschlag trägt den Zustellfortschritt ────────
test('MailDeliveryIncomplete transportiert die versorgten Empfänger', function () {
$e = new MailDeliveryIncomplete('2 Empfänger nicht erreicht', ['kunde@firma.de', 'cc@firma.de']);
assertTrue($e instanceof \RuntimeException);
assertSame(['kunde@firma.de', 'cc@firma.de'], $e->sentTo);
assertTrue(str_contains($e->getMessage(), 'nicht erreicht'));
});
// ── SMTP: RFC-konformer DATA-Block ─────────────────────────────────────
test('Nackte Zeilenumbrüche werden auf CRLF normalisiert', function () {
assertSame(
"Zeile 1\r\nZeile 2\r\nZeile 3",
\OpenNIT\Notifications\Mailer::prepareDataBlock("Zeile 1\nZeile 2\rZeile 3")
);
// Bereits korrekte CRLF bleiben unverändert
assertSame("a\r\nb", \OpenNIT\Notifications\Mailer::prepareDataBlock("a\r\nb"));
});
test('Zeilen mit führendem Punkt werden geschützt (Dot-Stuffing)', function () {
// Eine Zeile, die nur aus einem Punkt besteht, darf die Übertragung
// nicht beenden — sie wird verdoppelt und vom Server wieder entstufft.
assertSame(
"Text\r\n..\r\nMehr Text",
\OpenNIT\Notifications\Mailer::prepareDataBlock("Text\n.\nMehr Text")
);
assertSame(
"Text\r\n..NET ist toll",
\OpenNIT\Notifications\Mailer::prepareDataBlock("Text\n.NET ist toll")
);
// Auch am Anfang des Blocks
assertSame("..punkt", \OpenNIT\Notifications\Mailer::prepareDataBlock(".punkt"));
// Punkte mitten in der Zeile bleiben unangetastet
assertSame("a.b\r\nc.d", \OpenNIT\Notifications\Mailer::prepareDataBlock("a.b\nc.d"));
});
test('Die Ursache eines Abbruchs bleibt als previous erhalten', function () {
$ursache = new \RuntimeException('SMTP-Verbindung abgerissen');
$e = new MailDeliveryIncomplete('abgebrochen', ['kunde@firma.de'], $ursache);
assertSame($ursache, $e->getPrevious());
assertSame(['kunde@firma.de'], $e->sentTo);
});
};