true, 'services.discord.webhook_url' => 'https://discord.test/hook', 'services.discord.ai_cohort_webhook_url' => 'https://discord.test/hook', ]); Http::fake(['discord.test/*' => Http::response('', 204)]); ReportSummaryAgent::fake(['Sin cambios relevantes frente al periodo anterior.']); }); it('keeps every scheduled report inside Discord\'s embed limits', function (string $command) { if ($command === 'stats:daily-report') { bindMockStripeClientForStats(['active' => [], 'trialing' => []]); } artisan($command)->assertSuccessful(); $embed = null; Http::assertSent(function ($request) use (&$embed) { $embed = $request['embeds'][0]; return true; }); expect(mb_strlen($embed['description'] ?? ''))->toBeLessThanOrEqual(4096); foreach ($embed['fields'] ?? [] as $field) { expect(mb_strlen($field['value']))->toBeLessThanOrEqual(1024) // The webhook trims over-long text and marks it with an ellipsis, so // a trimmed field means the copy itself has to be shortened. ->and($field['value'])->not->toEndWith('…'); } })->with([ 'stats:daily-report', 'stats:subscription-funnel', 'stats:ai-cohort-report', ]); it('trims over-long embed text instead of letting Discord reject the report', function () { (new DiscordWebhook('https://discord.test/hook'))->send('', [[ 'title' => 'Informe', 'description' => str_repeat('a', 4106), 'fields' => [ ['name' => 'Leyenda', 'value' => str_repeat('b', 1034), 'inline' => false], ['name' => 'Corto', 'value' => 'cabe de sobra', 'inline' => false], ], ]]); Http::assertSent(function ($request) { $embed = $request['embeds'][0]; return mb_strlen($embed['description']) === 4096 && mb_strlen($embed['fields'][0]['value']) === 1024 && str_ends_with($embed['fields'][0]['value'], '…') && $embed['fields'][1]['value'] === 'cabe de sobra'; }); });