Files
portal/app/tests/Feature/RunwayCalculatorTest.php
T
Дмитрий 31f0e86972 fix/dashboard: хватит на N дней от дневного заказа B1 синхронно с биллингом
B1 из прогона тупой пользователь 24.06: RunwayCalculator считал от расхода
за 30 дней, из-за чего на дашборде показывал 0 дней при полном балансе и
раздувал число на короткой истории. Теперь считает от дневного заказа
активных проектов requiredLeadsForTomorrow, как витрина ёмкости биллинга,
и дашборд совпадает с биллингом. Тесты переписаны под новое поведение.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 05:19:41 +03:00

77 lines
3.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\Tenant;
use App\Services\Billing\RunwayCalculator;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
/**
* B1-fix (24.06.2026): runway считается от ДНЕВНОГО ЗАКАЗА активных проектов
* (sum daily_limit_target), а не от прошлого расхода. Так дашборд перестаёт врать
* на новых аккаунтах («0 дней» при полном балансе) и совпадает с витриной биллинга.
* daysLeft(tenantId, affordableLeads):
* affordable<=0 → 0; нет активных проектов (заказ=0) → null;
* иначе floor(affordable / дневной_заказ).
*/
uses(DatabaseTransactions::class);
/** Создаёт активный проект тенанта с заданным дневным лимитом. */
function makeActiveProject(int $tenantId, int $dailyLimit): void
{
DB::statement('SET app.current_tenant_id = '.$tenantId);
Project::factory()->create([
'tenant_id' => $tenantId,
'is_active' => true,
'daily_limit_target' => $dailyLimit,
]);
}
test('daysLeft = 0 если affordableLeads <= 0', function () {
$tenant = Tenant::factory()->create();
makeActiveProject($tenant->id, 50);
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 0))->toBe(0);
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, -5))->toBe(0);
});
test('daysLeft = null если нет активных проектов (нечего заказывать)', function () {
$tenant = Tenant::factory()->create();
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 100))->toBeNull();
});
test('daysLeft = floor(affordable / дневной заказ активных проектов)', function () {
$tenant = Tenant::factory()->create();
makeActiveProject($tenant->id, 50);
// 223 лида по карману, заказ 50/день → 4 полных дня (не «0» как раньше).
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 223))->toBe(4);
});
test('мало денег при большом заказе → 0 дней, а не раздутое число', function () {
$tenant = Tenant::factory()->create();
makeActiveProject($tenant->id, 50);
// 1 лид по карману, заказ 50/день → 0 дней (раньше выдавало «10 из 7»).
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 1))->toBe(0);
});
test('daysLeft суммирует лимиты нескольких активных проектов', function () {
$tenant = Tenant::factory()->create();
makeActiveProject($tenant->id, 30);
makeActiveProject($tenant->id, 20); // суммарный заказ 50/день
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 100))->toBe(2);
});
test('daysLeft игнорирует неактивные проекты', function () {
$tenant = Tenant::factory()->create();
makeActiveProject($tenant->id, 50);
DB::statement('SET app.current_tenant_id = '.$tenant->id);
Project::factory()->create([
'tenant_id' => $tenant->id,
'is_active' => false,
'daily_limit_target' => 1000,
]);
// считаем только активный (223/50=4), неактивный 1000 не учитывается.
expect(app(RunwayCalculator::class)->daysLeft($tenant->id, 223))->toBe(4);
});