create(['tier_no' => 1, 'leads_in_tier' => null, 'price_per_lead_kopecks' => 5000, 'is_active' => true, 'effective_from' => now()]); }); it('freezes tenant whose balance no longer covers daily limit', function () { Mail::fake(); // 500₽ / 50₽ = 10 лидов; проекты хотят 25 → заморозка. $tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalancePreflightSweepJob)->handle(); expect($tenant->fresh()->frozen_by_balance_at)->not->toBeNull(); Mail::assertQueued(BalanceFrozenMail::class); }); it('unfreezes tenant whose balance now covers daily limit', function () { Mail::fake(); // 2000₽ / 50₽ = 40 лидов; хотят 25 → разморозка. $tenant = Tenant::factory()->create(['balance_rub' => '2000.00', 'frozen_by_balance_at' => now()->subDay()]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalancePreflightSweepJob)->handle(); expect($tenant->fresh()->frozen_by_balance_at)->toBeNull(); }); it('is idempotent — does not re-freeze already frozen tenant', function () { Mail::fake(); $frozenAt = now()->subDay(); $tenant = Tenant::factory()->create(['balance_rub' => '0.00', 'frozen_by_balance_at' => $frozenAt]); Project::factory()->for($tenant)->create(['is_active' => true, 'daily_limit_target' => 25]); (new BalancePreflightSweepJob)->handle(); // Дата заморозки не перезаписана; для ЭТОГО tenant повторного письма нет. // NB: per-tenant фильтр, т.к. liderra_testing persistent (DemoSeeder тенанты // могут попасть в sweep и тоже получить BalanceFrozenMail — не наш ответ). expect($tenant->fresh()->frozen_by_balance_at->timestamp)->toBe($frozenAt->timestamp); Mail::assertNotQueued(BalanceFrozenMail::class, fn ($mail) => $mail->tenant->id === $tenant->id); });