create([ 'tier_no' => 1, 'leads_in_tier' => null, 'price_per_lead_kopecks' => 5000, 'is_active' => true, 'effective_from' => now(), ]); }); it('sends reminder ~1 day after freeze', function () { Mail::fake(); // frozen 25h назад — попадает в окно reminder (24-48h). Carbon::setTestNow('2026-05-25 12:00:00'); $tenant = Tenant::factory()->create([ 'balance_rub' => '0.00', 'frozen_by_balance_at' => Carbon::now()->subHours(25), ]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalanceFrozenReminderJob)->handle(); Mail::assertQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); Mail::assertNotQueued(BalanceFrozenFinalMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); }); it('sends final ~3 days after freeze', function () { Mail::fake(); // frozen 73h назад — попадает в окно final (72-96h). Carbon::setTestNow('2026-05-25 12:00:00'); $tenant = Tenant::factory()->create([ 'balance_rub' => '0.00', 'frozen_by_balance_at' => Carbon::now()->subHours(73), ]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalanceFrozenReminderJob)->handle(); Mail::assertQueued(BalanceFrozenFinalMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); Mail::assertNotQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); }); it('sends nothing for freshly frozen tenant', function () { Mail::fake(); // frozen 2h назад — окно ещё не открылось. Carbon::setTestNow('2026-05-25 12:00:00'); $tenant = Tenant::factory()->create([ 'balance_rub' => '0.00', 'frozen_by_balance_at' => Carbon::now()->subHours(2), ]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalanceFrozenReminderJob)->handle(); Mail::assertNotQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); Mail::assertNotQueued(BalanceFrozenFinalMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); }); it('is throttled — does not re-send reminder for same tenant in window', function () { Mail::fake(); Carbon::setTestNow('2026-05-25 12:00:00'); $tenant = Tenant::factory()->create([ 'balance_rub' => '0.00', 'frozen_by_balance_at' => Carbon::now()->subHours(25), ]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); // Первый прогон — отправляет reminder. (new BalanceFrozenReminderJob)->handle(); Mail::assertQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); // Сброс fake и второй прогон в том же окне — повторного письма быть не должно. Mail::fake(); (new BalanceFrozenReminderJob)->handle(); Mail::assertNotQueued(BalanceFrozenReminderMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); });