firstWhere('key', $key); } /** * Legt einen rohen Redis-Auftrag mit einem bestimmten Einreihungszeitpunkt in * eine Spur — dieselbe `createdAt`-Nutzlast, die `RedisQueue::push()` selbst * schreibt (Illuminate\Queue\Queue::createPayloadArray()) und die * `creationTimeOfOldestPendingJob()` ausliest. Der Inhalt des restlichen * Auftrags ist für die Prüfung irrelevant — sie liest nie mehr als dieses eine * Feld —, deshalb bleibt er auf das Nötigste reduziert. */ function legeAufTraegInDieSpur(string $lane, DateTimeInterface $eingereihtAm): void { Queue::connection('redis')->pushRaw(json_encode([ 'uuid' => (string) Str::uuid(), 'job' => 'unbenutzt', 'data' => [], 'id' => Str::random(32), 'attempts' => 0, 'createdAt' => $eingereihtAm->getTimestamp(), ]), $lane); } /** * DB 0 ist die echte Schlange: derselbe Redis, den `docker-compose.yml`s * `queue`-Dienst rund um die Uhr mit `queue:work redis --queue=mail-direkt, * mail-wichtig,mail-ruhig,default` abhört (siehe die laufenden Container in * dieser Umgebung). Ein hier abgelegter Testauftrag liefe Gefahr, vom echten * Arbeiter weggeschnappt zu werden, bevor die Prüfung ihn liest — ein * Wettlauf mit einem fremden Prozess, kein deterministischer Test. DB 15 ist * in keiner Konfiguration dieses Projekts referenziert und für jenen * Arbeiter unsichtbar; `creationTimeOfOldestPendingJob()` selbst bleibt der * echte Weg über echtes Redis (Illuminate\Queue\RedisQueue), nur auf eine * Datenbank verschoben, die niemand sonst anfasst. * * `config()` allein reicht nur, WEIL Laravels TestCase je Testmethode einen * frischen Application-Container baut (`RedisManager` ist ein Singleton, der * seine Konfiguration einmal beim ersten `Redis::connection()` dieses * Containers einliest — in `artisan tinker`, wo EIN Container die ganze * Sitzung lang lebt, kam genau deshalb beim Nachprüfen trotz gesetzter * Config die 0 zurück, weil dort schon vorher etwas 'redis' aufgelöst hatte). * Der Wächter unten prüft die WIRKLICH gewählte Datenbank am Client selbst, * nicht nur die Einstellung, die sie hätte bewirken sollen — sollte diese * Annahme durch eine künftige Änderung (z. B. Parallelisierung der Suite) * doch einmal nicht mehr gelten, bricht der Test laut ab, statt still * `flushdb()` gegen die echte Datenbank 0 abzufeuern. */ beforeEach(function () { config(['database.redis.default.database' => 15]); }); afterEach(function () { $tatsaechlicheDb = Redis::connection('default')->client()->getDbNum(); if ($tatsaechlicheDb !== 15) { throw new RuntimeException( "Erwartete Redis-DB 15 fuer die Aufraeumung, aber die Verbindung steht auf {$tatsaechlicheDb} — flushdb() abgebrochen, um nicht versehentlich die echte Datenbank zu leeren." ); } Redis::connection('default')->flushdb(); }); /** * Eine Spur, die steht, sieht von außen aus wie eine Spur, die leer ist. Die * Grenze liegt bei einer Stunde: 20 je 10 Minuten (der langsamste Takt, Task * 3) sind 120 in der Stunde, und ein Lauf dieser Größe steht bei elf Kunden * nicht an. Schlägt die Prüfung trotzdem an, klemmt etwas. */ it('meldet eine Spur, in der etwas laenger als eine Stunde liegt', function () { legeAufTraegInDieSpur(MailLane::CALM, now()->subHours(2)); expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeFalse(); }); it('meldet eine Spur mit frischer Arbeit nicht', function () { legeAufTraegInDieSpur(MailLane::CALM, now()); expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeTrue(); }); /** * Vollständigkeit: die drei Spuren sind gleichberechtigt vor der Prüfung. * Ohne diesen Test bewiese der obige Fall nur, dass EINE bestimmte Spur * beobachtet wird — mail-direkt oder mail-wichtig könnten unbeobachtet * bleiben, und ein Kunde wartet dort ebenso vergeblich auf eine * Kennwort-Zurücksetzung oder eine Wartungsankündigung. */ it('meldet jede der drei Spuren, wenn sie steht', function (string $lane) { legeAufTraegInDieSpur($lane, now()->subHours(2)); expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeFalse(); })->with([ 'mail-direkt' => [MailLane::DIRECT], 'mail-wichtig' => [MailLane::URGENT], 'mail-ruhig' => [MailLane::CALM], ]); /** * Der Normalzustand: nichts wurde je eingereiht. `creationTimeOfOldestPendingJob()` * liefert dann `null`, nicht 0 — ein Wert, der von einer erfundenen "leer" * nicht zu unterscheiden wäre. Die Prüfung muss `null` als gesund lesen, ohne * je selbst irgendetwas einzureihen. */ it('ist erfuellt, wenn noch nie etwas in einer Spur lag', function () { expect(mailPaceCheck('delivery.mail_lanes_moving')->satisfied)->toBeTrue(); }); it('names what breaks when a mail lane is stuck', function () { expect(mailPaceCheck('delivery.mail_lanes_moving')->breaks)->not->toBe(''); });