Files
portal/app/tests/Feature/Billing/BalanceStatusTest.php
T

108 lines
4.0 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Database\Seeders\PricingTierSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
uses(DatabaseTransactions::class);
/**
* GET /api/billing/balance-status — статус баланса для UI префлайта (Billing v2
* Spec C Task 1.10): питает баннер заморозки (BalanceFrozenBanner) и индикатор
* ёмкости (BalanceCapacityIndicator).
*
* PricingTierSeeder: ступень 1 — 100 лидов × 500₽ (см. BillingOverviewControllerTest).
*/
beforeEach(function () {
$this->seed(PricingTierSeeder::class);
$this->tenant = Tenant::factory()->create([
'balance_rub' => '5000.00',
'delivered_in_month' => 0,
'frozen_by_balance_at' => null,
]);
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
$this->actingAs($this->user);
});
test('GET /api/billing/balance-status: структура ответа', function () {
$this->getJson('/api/billing/balance-status')
->assertOk()
->assertJsonStructure([
'frozen_by_balance_at',
'balance_rub',
'capacity_leads',
'required_leads_per_day',
'deficit_leads',
'deficit_rub',
]);
});
test('balance-status: хватает баланса — deficit=0, не заморожен', function () {
// 5000₽ при ступени 1 (500₽/лид) = 10 лидов ёмкости. Проект лимит 5 — впритык.
Project::factory()->create([
'tenant_id' => $this->tenant->id,
'is_active' => true,
'daily_limit_target' => 5,
]);
$resp = $this->getJson('/api/billing/balance-status')->assertOk();
expect($resp->json('balance_rub'))->toBe('5000.00');
expect($resp->json('capacity_leads'))->toBe(10);
expect($resp->json('required_leads_per_day'))->toBe(5);
expect($resp->json('deficit_leads'))->toBe(0);
expect($resp->json('deficit_rub'))->toBe('0.00');
expect($resp->json('frozen_by_balance_at'))->toBeNull();
});
test('balance-status: не хватает — deficit_leads + deficit_rub точные', function () {
// Ёмкость = 10 лидов. Проект лимит 25 → нужно 25, дефицит 15 лидов.
// minBalanceForLeads(25) = 25 × 500₽ = 12500₽ → deficit_rub = 12500 5000 = 7500.00.
Project::factory()->create([
'tenant_id' => $this->tenant->id,
'is_active' => true,
'daily_limit_target' => 25,
]);
$resp = $this->getJson('/api/billing/balance-status')->assertOk();
expect($resp->json('capacity_leads'))->toBe(10);
expect($resp->json('required_leads_per_day'))->toBe(25);
expect($resp->json('deficit_leads'))->toBe(15);
expect($resp->json('deficit_rub'))->toBe('7500.00');
});
test('balance-status: required исключает inactive и preflight_blocked проекты', function () {
Project::factory()->create([
'tenant_id' => $this->tenant->id, 'is_active' => true, 'daily_limit_target' => 5,
]);
Project::factory()->create([
'tenant_id' => $this->tenant->id, 'is_active' => false, 'daily_limit_target' => 100,
]);
Project::factory()->create([
'tenant_id' => $this->tenant->id, 'is_active' => true, 'daily_limit_target' => 100,
'preflight_blocked_at' => now(),
]);
$resp = $this->getJson('/api/billing/balance-status')->assertOk();
expect($resp->json('required_leads_per_day'))->toBe(5);
});
test('balance-status: возвращает frozen_by_balance_at когда установлен', function () {
$this->tenant->update(['frozen_by_balance_at' => now()]);
$resp = $this->getJson('/api/billing/balance-status')->assertOk();
expect($resp->json('frozen_by_balance_at'))->not->toBeNull();
});
test('GET /api/billing/balance-status без auth: 401', function () {
auth()->logout();
$this->getJson('/api/billing/balance-status')->assertStatus(401);
});