1, 'leads_in_tier' => null, 'price_per_lead_kopecks' => $priceKopecks, 'is_active' => true, 'effective_from' => now()->toDateString(), ]); } it('passes when balance covers committed + new', function () { seedTier(); // 100 ₽/лид $t = Tenant::factory()->create(['balance_rub' => '1000.00', 'delivered_in_month' => 0]); // ёмкость 10 лидов $r = app(LaunchBalanceGate::class)->evaluate($t, additionalLeads: 5); expect($r->passes)->toBeTrue(); }); it('fails and computes topupRub in deficit', function () { seedTier(); // 100 ₽/лид $t = Tenant::factory()->create(['balance_rub' => '300.00', 'delivered_in_month' => 0]); // ёмкость 3 лида $r = app(LaunchBalanceGate::class)->evaluate($t, additionalLeads: 5); // нужно 5, не хватает 2 expect($r->passes)->toBeFalse(); expect($r->deficitLeads)->toBe(2); expect($r->topupRub)->toBe('200.00'); // 2 лида × 100 ₽ }); it('excludes given project from committed', function () { seedTier(); $t = Tenant::factory()->create(['balance_rub' => '1000.00', 'delivered_in_month' => 0]); // 10 лидов $p = Project::factory()->for($t)->create(['is_active' => true, 'daily_limit_target' => 8, 'preflight_blocked_at' => null]); // без исключения: committed=8, +5 = 13 > 10 → fail; с исключением self: committed=0, +5=5 → pass expect(app(LaunchBalanceGate::class)->evaluate($t, 5)->passes)->toBeFalse(); expect(app(LaunchBalanceGate::class)->evaluate($t, 5, [$p->id])->passes)->toBeTrue(); }); it('fail-closed when no active tiers and flag on', function () { config()->set('billing.launch_requires_active_tiers', true); $t = Tenant::factory()->create(['balance_rub' => '1000000.00', 'delivered_in_month' => 0]); expect(app(LaunchBalanceGate::class)->evaluate($t, 1)->passes)->toBeFalse(); }); it('open when no active tiers and flag off (legacy/tests)', function () { config()->set('billing.launch_requires_active_tiers', false); $t = Tenant::factory()->create(['balance_rub' => '0.00', 'delivered_in_month' => 0]); expect(app(LaunchBalanceGate::class)->evaluate($t, 1)->passes)->toBeTrue(); });