Fix AdminNotificationService crashing on string throttle_minutes config

env('ERROR_NOTIFICATION_THROTTLE_MINUTES', 15) returns the literal string
"15" from production's .env (env() doesn't cast numeric strings), which
Carbon 3.11's addMinutes() rejects with a TypeError. That crash happened
before the throttle-dedup cache write, so it silently masked the *original*
exception on every notify-worthy error, indefinitely (discovered while
investigating a missing workshop-registration email — the real SMTP error
was hidden behind this crash). Cast to (int).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Naser Mansour
2026-09-28 00:45:27 +03:00
parent ac22a0b982
commit 6ae226f5f2
4 changed files with 118 additions and 1 deletions
@@ -184,4 +184,27 @@ describe('notifyError', function () {
Notification::assertSentOnDemandTimes(SystemErrorNotification::class, 2);
});
it('does not throw and still sends notification when throttle_minutes config is a string', function () {
// Reproduces a live production bug: env('ERROR_NOTIFICATION_THROTTLE_MINUTES', 15) returns
// the string "15" (env() never casts numeric-looking strings to int), which used to be
// passed straight into now()->addMinutes(), and Carbon 3.11 throws a TypeError for a
// string argument there. That TypeError masked the original exception being reported and
// notifyError() never actually sent the admin notification.
Notification::fake();
Cache::flush();
config(['libra.notifications.throttle_minutes' => '15']);
$service = new AdminNotificationService;
$exception = new TransportException('SMTP error');
$service->notifyError($exception);
Notification::assertSentOnDemand(
SystemErrorNotification::class,
function ($notification, $channels, $notifiable) {
return $notifiable->routes['mail'] === 'admin@test.com';
}
);
});
});