Say when no mail is being delivered at all
"Ich versuche mich zu registrieren, erhalte aber keine Verify-Mail, auch nicht beim erneuten Senden." Traced rather than guessed, and the evidence is in storage/logs: the address appears three times, so the mails were built and handed to a transport that writes them to a file. MAIL_MAILER=log. MailboxTransport::resolution() short-circuits on it — NON_DELIVERING is ['log', 'array', null] — so every purpose mailer becomes the log transport however well the five mailboxes are configured, which is correct for a development machine and exactly what was happening here. The reason it deserves a notice is that the failure is silent in every direction an operator would look. Mail::queue() succeeds. No job fails. The queue empties. And the console's own mailbox test reports SUCCESS, because MailboxTester builds its own transport on purpose — a check that honoured MAIL_MAILER=log would report success while writing to a file. So every instrument on the page said fine. Now the front page says it, linked to the mail page, and the mail page says it above the mailboxes it makes decorative. All three swallowing values are named, not only 'log': array and an unset default do the same thing, and a notice that only knew about one would be silent in the two cases nobody thinks to check. Two existing tests asserted that a clean installation raises no notice, and went red — correctly, because phpunit.xml forces MAIL_MAILER=array so a suite cannot send mail. They pin a delivering mailer now: the warning is about the environment, those tests are about the estate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feature/betriebsmodus^2 v1.3.48
parent
70fa136e22
commit
f49f1dca09
|
|
@ -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
|
// No SITE_HOST. The marketing site is then registered without a
|
||||||
// hostname, so it answers on the PORTAL's host as well — and every
|
// 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
|
// route() it generates points at APP_URL, which is why a link on the
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'notice' => [
|
'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.',
|
'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_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.',
|
'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.',
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ return [
|
||||||
],
|
],
|
||||||
|
|
||||||
'notice' => [
|
'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.',
|
'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_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.',
|
'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.',
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,19 @@
|
||||||
<p class="mt-1 text-sm text-muted">{{ __('mail_settings.subtitle') }}</p>
|
<p class="mt-1 text-sm text-muted">{{ __('mail_settings.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- 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))
|
||||||
|
<x-ui.alert variant="warning">
|
||||||
|
{{ __('admin.notice.mail_not_delivering', ['mailer' => (string) (config('mail.default') ?? 'null')]) }}
|
||||||
|
</x-ui.alert>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
@if (! $usable)
|
@if (! $usable)
|
||||||
<x-ui.alert variant="warning">{{ __('mail_settings.no_key') }}</x-ui.alert>
|
<x-ui.alert variant="warning">{{ __('mail_settings.no_key') }}</x-ui.alert>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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
|
// month. The figures are asserted directly rather than searched for in the
|
||||||
// markup — "42" also occurs inside Livewire's own component ids.
|
// 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')
|
Livewire::actingAs(operator('Owner'), 'operator')
|
||||||
->test(Overview::class)
|
->test(Overview::class)
|
||||||
->assertViewHas('kpis', function (array $kpis) {
|
->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 () {
|
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 = Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class);
|
||||||
$clean->assertSee(__('admin.systems_ok'));
|
$clean->assertSee(__('admin.systems_ok'));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,3 +300,50 @@ it('says when the website has no hostname of its own', function () {
|
||||||
->test(Overview::class)
|
->test(Overview::class)
|
||||||
->assertDontSee(__('admin.notice.no_site_host', ['app' => 'app.dev.example.test']));
|
->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']));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue