101 lines
3.7 KiB
PHP
101 lines
3.7 KiB
PHP
<?php
|
|
|
|
use App\Mail\Concerns\RidesALane;
|
|
use App\Mail\InvoiceMail;
|
|
use App\Mail\ResetPasswordMail;
|
|
use App\Models\Customer;
|
|
use App\Models\Invoice;
|
|
use App\Models\Order;
|
|
use App\Models\User;
|
|
use App\Services\Billing\IssueInvoice;
|
|
use App\Services\Mail\MailLane;
|
|
use App\Support\CompanyProfile;
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
/**
|
|
* Die Spur entsteht beim Einreihen, nicht beim Senden.
|
|
*
|
|
* Der Arbeiter liest die Schlangen in der Reihenfolge ihrer Dringlichkeit —
|
|
* deshalb steht ein Kennwort-Zurücksetzen nie hinter zweihundert Rechnungen.
|
|
* Das trägt nur, wenn die Rechnung wirklich in einer anderen Schlange liegt.
|
|
*
|
|
* Werkzeug: Queue::fake() statt Mail::fake(). Mail::fake() ersetzt den
|
|
* ganzen Mailer — der Aufruf, der onQueue() setzt (Mailer::queue(), der
|
|
* dann Mailable::queue() aufruft), fände dann nie statt, und der Test würde
|
|
* grün, ohne die Spur je berechnet zu haben. Queue::fake() lässt genau
|
|
* diesen Aufruf laufen und fängt erst dahinter ab, an der Queue-Fabrik —
|
|
* die Stelle, an der pushOn() tatsächlich ankommt.
|
|
*/
|
|
function invoiceForTest(): Invoice
|
|
{
|
|
CompanyProfile::put([
|
|
'name' => 'CluPilot Cloud e.U.',
|
|
'address' => 'Dreherstraße 66/1/8',
|
|
'postcode' => '1110',
|
|
'city' => 'Wien',
|
|
'vat_id' => 'ATU00000000',
|
|
]);
|
|
|
|
$customer = Customer::factory()->create(['name' => 'Muster GmbH']);
|
|
$orders = collect([Order::factory()->create([
|
|
'customer_id' => $customer->id, 'amount_cents' => 1900, 'currency' => 'EUR', 'status' => 'paid',
|
|
])]);
|
|
|
|
return app(IssueInvoice::class)->forOrders($customer, $orders);
|
|
}
|
|
|
|
it('reiht jede Mail in die Schlange ihrer Spur ein', function () {
|
|
Queue::fake();
|
|
|
|
Mail::to('kunde@example.test')->queue(new InvoiceMail(invoiceForTest(), 'Muster'));
|
|
|
|
Queue::assertPushedOn(MailLane::CALM, Illuminate\Mail\SendQueuedMailable::class);
|
|
});
|
|
|
|
it('reiht eine Direkt-Mail in die Direkt-Spur ein', function () {
|
|
Queue::fake();
|
|
|
|
$user = User::factory()->create();
|
|
|
|
Mail::to('kunde@example.test')->queue(new ResetPasswordMail($user, 'https://example.test/reset', 60));
|
|
|
|
Queue::assertPushedOn(MailLane::DIRECT, Illuminate\Mail\SendQueuedMailable::class);
|
|
});
|
|
|
|
it('folgt einer verschobenen Zuordnung', function () {
|
|
Queue::fake();
|
|
MailLane::assign(InvoiceMail::class, MailLane::URGENT);
|
|
|
|
Mail::to('kunde@example.test')->queue(new InvoiceMail(invoiceForTest(), 'Muster'));
|
|
|
|
Queue::assertPushedOn(MailLane::URGENT, Illuminate\Mail\SendQueuedMailable::class);
|
|
});
|
|
|
|
/**
|
|
* Rekursiv statt `glob('Mail/*.php')`: `app/Mail` hat mit `Concerns/` und
|
|
* `Transport/` schon zwei Unterordner und wird eher mehr als weniger. Eine
|
|
* fünfzehnte Mailklasse in einem Unterordner sähe der alte `glob()`-Aufruf
|
|
* nie — der Wächter schwiege genau in dem Moment, in dem er gebraucht wird.
|
|
*
|
|
* Eingeschränkt auf echte Mailables (Unterklassen von
|
|
* `Illuminate\Mail\Mailable`) statt auf „liegt irgendwo unter app/Mail":
|
|
* ein Trait wie `RidesALane` selbst, oder eine künftige Basisklasse, ist
|
|
* keine versendbare Mail, braucht keine Spur und soll den Test nicht rot
|
|
* machen, nur weil sie im selben Ordner liegt.
|
|
*/
|
|
it('lässt keine Mailklasse ohne Spur', function () {
|
|
$missing = collect(File::allFiles(app_path('Mail')))
|
|
->map(fn ($file) => 'App\\Mail\\'.str_replace(
|
|
[DIRECTORY_SEPARATOR, '.php'], ['\\', ''], $file->getRelativePathname()
|
|
))
|
|
->filter(fn (string $class) => is_subclass_of($class, Mailable::class))
|
|
->reject(fn (string $class) => in_array(RidesALane::class, class_uses_recursive($class), true))
|
|
->values()
|
|
->all();
|
|
|
|
expect($missing)->toBe([]);
|
|
});
|