diff --git a/VERSION b/VERSION index f5301e2..b500665 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.47 +1.3.48 diff --git a/app/Livewire/Admin/Overview.php b/app/Livewire/Admin/Overview.php index 87435ca..bc5e15a 100644 --- a/app/Livewire/Admin/Overview.php +++ b/app/Livewire/Admin/Overview.php @@ -382,6 +382,23 @@ class Overview extends Component ]; } + // MAIL_MAILER=log. Every purpose mailer short-circuits to the log + // transport then — see MailboxTransport::NON_DELIVERING — however well + // the mailboxes are configured, and nothing is delivered to anybody. + // + // Worth its own notice because the failure is SILENT in every direction: + // the queue reports success, no job fails, the console's own mailbox test + // reports success (MailboxTester builds its own transport on purpose), and + // the mail sits in storage/logs. Somebody registering and waiting for a + // verification mail has no way to find that out. Asked exactly that way. + if (in_array(config('mail.default'), ['log', 'array', null], true)) { + $notices[] = [ + 'level' => 'warning', + 'text' => __('admin.notice.mail_not_delivering', ['mailer' => (string) (config('mail.default') ?? 'null')]), + 'route' => 'admin.mail', + ]; + } + // No SITE_HOST. The marketing site is then registered without a // hostname, so it answers on the PORTAL's host as well — and every // route() it generates points at APP_URL, which is why a link on the diff --git a/lang/de/admin.php b/lang/de/admin.php index 74e1715..4fb00d9 100644 --- a/lang/de/admin.php +++ b/lang/de/admin.php @@ -88,6 +88,7 @@ return [ ], 'notice' => [ + 'mail_not_delivering' => 'MAIL_MAILER steht auf „:mailer“ — es wird keine E-Mail zugestellt, alles landet in storage/logs. Auch der Postfach-Test meldet trotzdem Erfolg, weil er seinen eigenen Weg nimmt.', 'site_hidden' => 'Website und Kundenbereich sind ausgeblendet — Besucher außerhalb des VPN bekommen die Platzhalterseite (503). Umschalten unter Einstellungen → Installation.', 'no_site_host' => 'SITE_HOST ist nicht gesetzt. Die Website antwortet dadurch auch auf :app, und Links auf ihr zeigen dorthin statt auf den eigenen Namen.', 'no_capacity' => 'Kein Host hat noch Platz für „:plan“ — das Paket braucht :needs GB, der freieste Host hat :largest GB. Eine bezahlte Bestellung dafür würde bei der Reservierung scheitern.', diff --git a/lang/en/admin.php b/lang/en/admin.php index a2098cd..b6fbe70 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -88,6 +88,7 @@ return [ ], 'notice' => [ + 'mail_not_delivering' => 'MAIL_MAILER is set to ":mailer" — no mail is delivered to anybody, it all goes to storage/logs. The mailbox test still reports success, because it builds its own transport.', 'site_hidden' => 'The website and the customer portal are hidden — a visitor outside the VPN gets the placeholder page (503). The switch is under Settings → Installation.', 'no_site_host' => 'SITE_HOST is not set. The website therefore also answers on :app, and links on it point there instead of at its own name.', 'no_capacity' => 'No host has room for “:plan” any more — the package needs :needs GB and the roomiest host has :largest GB. A paid order for it would fail at reservation.', diff --git a/resources/views/livewire/admin/mail.blade.php b/resources/views/livewire/admin/mail.blade.php index 3b00c8e..e491f38 100644 --- a/resources/views/livewire/admin/mail.blade.php +++ b/resources/views/livewire/admin/mail.blade.php @@ -4,6 +4,19 @@

{{ __('mail_settings.subtitle') }}

+ {{-- The one thing that makes every mailbox on this page decorative. Said + here as well as on the front page, because this is where somebody comes + to find out why no mail arrived — and the test button below reports + success regardless, since MailboxTester builds its own transport on + purpose (a check that honoured MAIL_MAILER=log would report success + while writing to a file). --}} + @if (in_array(config('mail.default'), ['log', 'array', null], true)) + + {{ __('admin.notice.mail_not_delivering', ['mailer' => (string) (config('mail.default') ?? 'null')]) }} + + @endif + + @if (! $usable) {{ __('mail_settings.no_key') }} @endif diff --git a/tests/Feature/Admin/ConsoleReportsRealDataTest.php b/tests/Feature/Admin/ConsoleReportsRealDataTest.php index 1ae3419..8452747 100644 --- a/tests/Feature/Admin/ConsoleReportsRealDataTest.php +++ b/tests/Feature/Admin/ConsoleReportsRealDataTest.php @@ -25,6 +25,12 @@ it('reports an empty estate as empty, not as a going concern', function () { // console still claimed 42 customers, 39 instances, 4 hosts and €7,842 a // month. The figures are asserted directly rather than searched for in the // markup — "42" also occurs inside Livewire's own component ids. + // + // The mailer is pinned to a delivering one: phpunit.xml forces + // MAIL_MAILER=array so a test suite cannot send mail, and the console + // rightly warns that nothing is being delivered. That warning is about the + // ENVIRONMENT, not about the estate, and this test is about an empty estate. + config()->set('mail.default', 'smtp'); Livewire::actingAs(operator('Owner'), 'operator') ->test(Overview::class) ->assertViewHas('kpis', function (array $kpis) { @@ -107,6 +113,10 @@ it('states monthly revenue off the contracts, with a yearly term divided', funct }); it('raises a notice only when something is actually wrong', function () { + // See above: the test suite deliberately delivers no mail, and the console + // says so. Pinned here so "clean" means an estate with nothing wrong in it. + config()->set('mail.default', 'smtp'); + $clean = Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class); $clean->assertSee(__('admin.systems_ok')); diff --git a/tests/Feature/Admin/MailRegisterTest.php b/tests/Feature/Admin/MailRegisterTest.php index 25b4572..1093418 100644 --- a/tests/Feature/Admin/MailRegisterTest.php +++ b/tests/Feature/Admin/MailRegisterTest.php @@ -300,3 +300,50 @@ it('says when the website has no hostname of its own', function () { ->test(Overview::class) ->assertDontSee(__('admin.notice.no_site_host', ['app' => 'app.dev.example.test'])); }); + +it('says when no mail is being delivered at all', function () { + // The report: "ich versuche mich zu registrieren, erhalte aber keine Verify- + // Mail, auch nicht beim erneuten Senden". MAIL_MAILER=log short-circuits + // every purpose mailer to the log transport (MailboxTransport:: + // NON_DELIVERING) however well the mailboxes are configured — and the + // failure is silent in every direction: the queue reports success, no job + // fails, and the console's own mailbox test reports success too because it + // builds its own transport on purpose. Nothing on any page said so. + config()->set('mail.default', 'log'); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertSee(__('admin.notice.mail_not_delivering', ['mailer' => 'log'])) + ->assertSee(route('admin.mail'), false); + + // And on the page somebody actually opens to find out why. + Livewire::actingAs(operator('Owner'), 'operator') + ->test(App\Livewire\Admin\Mail::class) + ->assertSee(__('admin.notice.mail_not_delivering', ['mailer' => 'log'])); +}); + +it('stays quiet once mail really goes out', function () { + config()->set('mail.default', 'smtp'); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertDontSee(__('admin.notice.mail_not_delivering', ['mailer' => 'log'])); +}); + +it('names every mailer that swallows mail, not only the log one', function () { + // array and an unset default do the same thing, and the transport already + // treats all three alike. A notice that only knew about 'log' would be + // silent in the two cases nobody thinks to check. + $swallowing = (new ReflectionClass(App\Mail\Transport\MailboxTransport::class)) + ->getConstant('NON_DELIVERING'); + + expect($swallowing)->toBe(['log', 'array', null]); + + foreach (['array', null] as $mailer) { + config()->set('mail.default', $mailer); + + Livewire::actingAs(operator('Owner'), 'operator') + ->test(Overview::class) + ->assertSee(__('admin.notice.mail_not_delivering', ['mailer' => $mailer ?? 'null'])); + } +});