Http::response('', 204), ]); config([ 'logging.channels.discord_errors.url' => 'https://discord.example.test/webhook', 'logging.channels.discord_errors.mention' => '@here', 'logging.channels.discord_errors.title' => '## KanbanGit error', ]); Log::forgetChannel('discord_errors'); Log::channel('discord_errors')->error('Unhandled exception while syncing board', [ 'exception' => new RuntimeException('Discord logger boom'), 'project' => 'CORE', ]); Http::assertSentCount(1); Http::assertSent(function (Request $request): bool { $content = $request->data()['content'] ?? ''; return $request->method() === 'POST' && $request->url() === 'https://discord.example.test/webhook' && $request->hasHeader('Content-Type', 'application/json') && str_contains($content, '@here') && str_contains($content, '## KanbanGit error') && str_contains($content, 'Unhandled exception while syncing board') && str_contains($content, 'Discord logger boom') && str_contains($content, 'RuntimeException') && str_contains($content, '"project": "CORE"'); }); } /** * Ensure the Discord error logger becomes a no-op when no webhook is configured. */ public function test_discord_error_logger_skips_delivery_without_a_webhook(): void { Http::preventStrayRequests(); Http::fake(); config([ 'logging.channels.discord_errors.url' => null, ]); Log::forgetChannel('discord_errors'); Log::channel('discord_errors')->error('This should stay local'); Http::assertNothingSent(); } /** * Ensure the Discord error logger truncates oversized payloads safely. */ public function test_discord_error_logger_truncates_oversized_payloads(): void { Http::preventStrayRequests(); Http::fake([ 'https://discord.example.test/webhook' => Http::response('', 204), ]); config([ 'logging.channels.discord_errors.url' => 'https://discord.example.test/webhook', 'logging.channels.discord_errors.max_content_length' => 180, ]); Log::forgetChannel('discord_errors'); Log::channel('discord_errors')->error(str_repeat('A', 600)); Http::assertSent(function (Request $request): bool { $content = $request->data()['content'] ?? ''; return mb_strlen($content) <= 180 && str_ends_with($content, '...(truncated)'); }); } }