127 lines
5.3 KiB
PHP
127 lines
5.3 KiB
PHP
<?php
|
|
|
|
use App\Services\Mail\MailLane;
|
|
use App\Support\Readiness;
|
|
use App\Support\Readiness\Check;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Illuminate\Support\Facades\Redis;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* `deliveryCheck` ist in DeliveryChecksTest.php schon vergeben — beide Dateien
|
|
* laufen im selben Prozess, eine zweite Deklaration wäre ein Fataler Fehler.
|
|
*/
|
|
function mailPaceCheck(string $key): ?Check
|
|
{
|
|
return collect(Readiness::all())->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('');
|
|
});
|