100 lines
3.6 KiB
PHP
100 lines
3.6 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Models\SalesUser;
|
|||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* TDD: единый разбор периода на ВСЕХ экранах портала продаж.
|
|||
|
|
*
|
|||
|
|
* Владелец пожаловался «выборка по срокам не работает»: выбрав «Произвольный»,
|
|||
|
|
* менеджер отправлял period=custom БЕЗ дат, резолвер бросал InvalidArgumentException,
|
|||
|
|
* и экран получал 500. На «Моих клиентах» это уже починено (422), а экраны
|
|||
|
|
* начальника (сводка отдела, результативность, тарифы, выплаты) и «Мой доход»
|
|||
|
|
* продолжали падать.
|
|||
|
|
*
|
|||
|
|
* Здесь фиксируем инвариант для всех точек сразу:
|
|||
|
|
* 1. period=custom без дат → 422 с понятным текстом, а не 500.
|
|||
|
|
* 2. from позже to → 422.
|
|||
|
|
* 3. Новые периоды (today/yesterday/d7/d30) принимаются везде.
|
|||
|
|
* 4. Период по умолчанию — d30 (как в PeriodPicker), а не месяц.
|
|||
|
|
*
|
|||
|
|
* Изоляция: DatabaseTransactions. DB_DATABASE=liderra_testing обязателен.
|
|||
|
|
*/
|
|||
|
|
uses(DatabaseTransactions::class);
|
|||
|
|
|
|||
|
|
function per_head(): SalesUser
|
|||
|
|
{
|
|||
|
|
return SalesUser::create([
|
|||
|
|
'name' => 'Начальник '.uniqid(),
|
|||
|
|
'email' => 'perhead'.uniqid().'@sales.local',
|
|||
|
|
'password' => bcrypt('secret'),
|
|||
|
|
'role' => 'head',
|
|||
|
|
'is_active' => true,
|
|||
|
|
]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Все GET-точки, которые принимают период. Начальник видит все,
|
|||
|
|
* поэтому одним пользователем покрываем и менеджерские, и boss-only.
|
|||
|
|
*
|
|||
|
|
* @return list<string>
|
|||
|
|
*/
|
|||
|
|
function per_endpoints(): array
|
|||
|
|
{
|
|||
|
|
return [
|
|||
|
|
'/api/sales/clients',
|
|||
|
|
'/api/sales/dashboard/overview',
|
|||
|
|
'/api/sales/income',
|
|||
|
|
'/api/sales/managers/performance',
|
|||
|
|
'/api/sales/payouts/remaining',
|
|||
|
|
'/api/sales/tariffs',
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
it('произвольный период без дат отвечает 422, а не падает', function () {
|
|||
|
|
$head = per_head();
|
|||
|
|
|
|||
|
|
foreach (per_endpoints() as $url) {
|
|||
|
|
$this->actingAs($head, 'sales')
|
|||
|
|
->getJson($url.'?period=custom')
|
|||
|
|
->assertStatus(422)
|
|||
|
|
->assertJsonValidationErrors('period');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('произвольный период с одной датой отвечает 422', function () {
|
|||
|
|
$head = per_head();
|
|||
|
|
|
|||
|
|
foreach (per_endpoints() as $url) {
|
|||
|
|
$this->actingAs($head, 'sales')
|
|||
|
|
->getJson($url.'?period=custom&from=2026-07-01')
|
|||
|
|
->assertStatus(422)
|
|||
|
|
->assertJsonValidationErrors('period');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('перевёрнутый произвольный период отвечает 422', function () {
|
|||
|
|
$head = per_head();
|
|||
|
|
|
|||
|
|
foreach (per_endpoints() as $url) {
|
|||
|
|
$this->actingAs($head, 'sales')
|
|||
|
|
->getJson($url.'?period=custom&from=2026-07-10&to=2026-07-01')
|
|||
|
|
->assertStatus(422)
|
|||
|
|
->assertJsonValidationErrors('period');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('новые периоды календаря принимаются на всех экранах', function () {
|
|||
|
|
$head = per_head();
|
|||
|
|
|
|||
|
|
foreach (per_endpoints() as $url) {
|
|||
|
|
foreach (['today', 'yesterday', 'd7', 'd30'] as $kind) {
|
|||
|
|
$this->actingAs($head, 'sales')
|
|||
|
|
->getJson($url.'?period='.$kind)
|
|||
|
|
->assertOk();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|