diff --git a/app/Jobs/PacedMail.php b/app/Jobs/PacedMail.php index eaa459c..5571464 100644 --- a/app/Jobs/PacedMail.php +++ b/app/Jobs/PacedMail.php @@ -99,19 +99,31 @@ class PacedMail extends SendQueuedMailable return null; } - // Die Schlange am Mailable, nicht `MailLane::for(...)`: das hier ist - // der Name, den `Mailable::queue()` an `pushOn()` gegeben hat — die - // Spur, in der dieser Auftrag TATSÄCHLICH liegt. `MailLane::for(...)` - // läse die Einstellungen erst im Arbeiter: würde der Betreiber die - // Rechnung von ruhig auf wichtig schieben, während zweihundert davon - // in der ruhigen Schlange warten, drosselte sie ab diesem Moment das - // falsche Kontingent. + // Zuerst die Schlange am Mailable: das ist der Name, den + // `Mailable::queue()` an `pushOn()` gegeben hat — die Spur, in der + // dieser Auftrag TATSÄCHLICH liegt. `MailLane::for(...)` allein wäre + // hier falsch, weil es die Einstellungen erst im Arbeiter läse: würde + // der Betreiber die Rechnung von ruhig auf wichtig schieben, während + // zweihundert davon in der ruhigen Schlange warten, drosselte sie ab + // diesem Moment das falsche Kontingent. // - // `?? null` statt eines nackten Zugriffs, weil `$this->mailable` nur - // die Schnittstelle zusichert: eine Mailklasse, die den Trait nicht - // benutzt, hat die Eigenschaft womöglich gar nicht — und liegt dann - // auch in keiner Spur, gehört also nicht gedrosselt. - return match ($this->mailable->queue ?? null) { + // Steht dort nichts, kommt die Mail von auswärts — aus einem Paket, + // aus Fortify, aus irgendetwas, das jemand eingebunden hat. Sie kennt + // `RidesALane` nicht, hat also keine Spur und liegt auf `default`. + // Dann entscheidet `MailLane::for(...)`, und das ist Absicht: Aufgabe 1 + // lässt eine unbekannte Klasse ausdrücklich in die ruhige Spur fallen, + // weil eine zu langsam verschickte Mail ein kleinerer Fehler ist als + // ein ungedrosselter Schub, den niemand vorhergesehen hat. Ohne diesen + // Rückgriff fiele sie am Auftrag in die Gegenrichtung. + // + // Schlange und Takt fallen für so eine Mail auseinander — sie bleibt + // auf `default` und fährt trotzdem im ruhigen Takt. Das ist kein + // Versehen, sondern die Arbeitsteilung: die Schlange wählt der Trait, + // den Takt wählt dieser Auftrag, und für eine fremde Mailklasse greift + // nur das zweite. + $spur = $this->mailable->queue ?? MailLane::for($this->mailable::class); + + return match ($spur) { MailLane::URGENT => MailLane::URGENT, MailLane::CALM => MailLane::CALM, default => null, diff --git a/tests/Feature/Mail/MailPaceTest.php b/tests/Feature/Mail/MailPaceTest.php index f1b5d7e..9ea5d4e 100644 --- a/tests/Feature/Mail/MailPaceTest.php +++ b/tests/Feature/Mail/MailPaceTest.php @@ -105,6 +105,29 @@ class EigeneGrenzeMail extends Mailable implements ShouldQueue } } +/** + * Eine Mailklasse aus der Fremde: kein Trait, keine Spur am Mailable. + * + * So sieht jede Mail aus, die nicht aus `app/Mail` kommt — Fortify, ein + * künftiges Abhängigkeitspaket, irgendetwas, das jemand einbindet. Sie kennt + * `RidesALane` nicht, also steht in `$mailable->queue` nichts, und sie liegt + * auf `default`. + */ +class FremdeMail extends Mailable implements ShouldQueue +{ + use Queueable, SerializesModels; + + public function envelope(): Envelope + { + return new Envelope(from: 'takt@example.test', subject: 'Fremd'); + } + + public function content(): Content + { + return new Content(htmlString: '

Fremd

'); + } +} + /** * Der Auftrag, so wie ihn das Einreihen erzeugt. * @@ -323,6 +346,30 @@ it('hängt jede gedrosselte Spur an ihre eigene Drossel', function (string $spur ->and(drosselName($middleware[0]))->toBe($spur); })->with([MailLane::URGENT, MailLane::CALM]); +/** + * Eine fremde Mailklasse fährt im ruhigen Takt, nicht ungebremst. + * + * Aufgabe 1 hat sich für diese Richtung entschieden: eine unbekannte Klasse + * fällt in die ruhige Spur, weil eine zu langsam verschickte Mail ein kleinerer + * Fehler ist als ein ungedrosselter Schub, den niemand vorhergesehen hat. Am + * Auftrag fiel sie bis hierher in die Gegenrichtung — ohne Spur am Mailable + * griff gar keine Drossel. + * + * Schlange und Takt fallen dabei auseinander, und das ist kein Versehen: die + * Schlange wählt der Trait, den diese Klasse nicht hat, den Takt wählt der + * Auftrag. Sie liegt also weiter auf `default` und fährt trotzdem im ruhigen + * Takt. + */ +it('drosselt eine Mailklasse ohne den Trait wie die ruhige Spur', function () { + $auftrag = taktAuftrag(new FremdeMail); + $middleware = $auftrag->middleware(); + + expect($auftrag->mailable->queue)->toBeNull() + ->and($middleware)->toHaveCount(1) + ->and(drosselName($middleware[0]))->toBe(MailLane::CALM) + ->and($auftrag->retryUntil())->not->toBeNull(); +}); + /** * Auf der direkten Spur wartet gerade ein Mensch, der eben geklickt hat. * Drosseln nützt dort nichts — die Mails entstehen einzeln und können gar