c366614fcd
Task 0.5+0.6: SalesAuthController (login 200/422/403, me, logout) + маршруты /api/sales/auth и зона данных. Порядок middleware admin-db ДО auth:sales. Тест SalesAuthTest 7/7, весь sales-набор 25/25. Logout инвалидирует токен (в тесте Auth::forgetGuards() — артефакт мульти-запросов; в бою каждый запрос свежий). Larastan baseline: Pest false-pos SalesAuthTest. Заодно pint-канон моделей/трейта/SalesModelsTest. Один эскейп на сессию. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
182 lines
6.9 KiB
PHP
182 lines
6.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\SalesAttachmentRequest;
|
||
use App\Models\SalesClientAssignment;
|
||
use App\Models\SalesPayout;
|
||
use App\Models\SalesTariff;
|
||
use App\Models\SalesUser;
|
||
use App\Models\Tenant;
|
||
use Carbon\Carbon;
|
||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||
use Illuminate\Database\QueryException;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
|
||
/**
|
||
* Smoke-тесты Eloquent-моделей портала продаж.
|
||
* Проверяют: создание записей, каsты, связи, вспомогательные методы.
|
||
*
|
||
* Изоляция: DatabaseTransactions — каждый тест оборачивается в транзакцию
|
||
* и откатывается, не оставляя строк в liderra_testing.
|
||
*
|
||
* Используем DEFAULT connection (pgsql → liderra_testing в тестах).
|
||
* sales_* таблицы созданы Task 0.1 (migration 2026_07_01_100000_create_sales_portal_tables.php).
|
||
*/
|
||
uses(DatabaseTransactions::class);
|
||
|
||
// ── helpers ────────────────────────────────────────────────────────────────
|
||
|
||
function makeSalesTariff(array $attrs = []): SalesTariff
|
||
{
|
||
return SalesTariff::create(array_merge([
|
||
'name' => 'Test tariff '.uniqid(),
|
||
'kind' => 'topup_step',
|
||
'params' => ['step' => 1000, 'bonus' => 100],
|
||
'is_active' => true,
|
||
], $attrs));
|
||
}
|
||
|
||
function makeSalesUser(array $attrs = []): SalesUser
|
||
{
|
||
return SalesUser::create(array_merge([
|
||
'name' => 'Manager '.uniqid(),
|
||
'email' => 'mgr'.uniqid().'@test.local',
|
||
'password' => bcrypt('secret'),
|
||
'role' => 'manager',
|
||
], $attrs));
|
||
}
|
||
|
||
// ── 1. SalesTariff ─────────────────────────────────────────────────────────
|
||
|
||
test('SalesTariff можно создать и params отдаётся массивом', function () {
|
||
$tariff = makeSalesTariff([
|
||
'params' => ['step' => 5000, 'bonus' => 500],
|
||
]);
|
||
|
||
expect($tariff->id)->toBeInt()
|
||
->and($tariff->name)->toStartWith('Test tariff')
|
||
->and($tariff->kind)->toBe('topup_step')
|
||
->and($tariff->params)->toBeArray()
|
||
->and($tariff->params['step'])->toBe(5000)
|
||
->and($tariff->is_active)->toBeTrue();
|
||
});
|
||
|
||
// ── 2. SalesUser ───────────────────────────────────────────────────────────
|
||
|
||
test('SalesUser создаётся с role=manager и current_tariff_id', function () {
|
||
$tariff = makeSalesTariff();
|
||
$user = makeSalesUser([
|
||
'role' => 'manager',
|
||
'current_tariff_id' => $tariff->id,
|
||
'base_salary_rub' => '50000.00',
|
||
]);
|
||
|
||
expect($user->id)->toBeInt()
|
||
->and($user->role)->toBe('manager')
|
||
->and($user->current_tariff_id)->toBe($tariff->id)
|
||
->and($user->fresh()->is_active)->toBeTrue(); // DB default=TRUE, fresh() синхронизирует
|
||
});
|
||
|
||
test('SalesUser::isHead() верен для role=head и false для role=manager', function () {
|
||
$head = makeSalesUser(['role' => 'head']);
|
||
$manager = makeSalesUser(['role' => 'manager']);
|
||
|
||
expect($head->isHead())->toBeTrue()
|
||
->and($manager->isHead())->toBeFalse();
|
||
});
|
||
|
||
test('SalesUser is_active приходит булевым', function () {
|
||
$user = makeSalesUser(['is_active' => true]);
|
||
|
||
expect($user->is_active)->toBeBool()->toBeTrue();
|
||
});
|
||
|
||
// ── 3. SalesClientAssignment ───────────────────────────────────────────────
|
||
|
||
test('SalesClientAssignment связывает пользователя с тенантом и tariff_params — массив', function () {
|
||
$tariff = makeSalesTariff(['kind' => 'percent_oborot', 'params' => ['percent' => 5]]);
|
||
$user = makeSalesUser(['current_tariff_id' => $tariff->id]);
|
||
$tenant = Tenant::factory()->create(); // реальный тенант (FK tenants.id)
|
||
|
||
$assignment = SalesClientAssignment::create([
|
||
'sales_user_id' => $user->id,
|
||
'tenant_id' => $tenant->id,
|
||
'tariff_id' => $tariff->id,
|
||
'tariff_kind' => $tariff->kind,
|
||
'tariff_params' => ['percent' => 5],
|
||
'assigned_at' => now(),
|
||
]);
|
||
|
||
expect($assignment->id)->toBeInt()
|
||
->and($assignment->tariff_params)->toBeArray()
|
||
->and($assignment->tariff_params['percent'])->toBe(5)
|
||
->and($assignment->assigned_at)->toBeInstanceOf(Carbon::class);
|
||
});
|
||
|
||
test('SalesUser->assignments() возвращает HasMany-коллекцию', function () {
|
||
$user = makeSalesUser();
|
||
$tenant = Tenant::factory()->create();
|
||
|
||
SalesClientAssignment::create([
|
||
'sales_user_id' => $user->id,
|
||
'tenant_id' => $tenant->id,
|
||
'tariff_params' => [],
|
||
'assigned_at' => now(),
|
||
]);
|
||
|
||
$relation = $user->assignments();
|
||
expect($relation)->toBeInstanceOf(HasMany::class);
|
||
|
||
$collection = $user->assignments;
|
||
expect($collection)->toHaveCount(1)
|
||
->and($collection->first())->toBeInstanceOf(SalesClientAssignment::class);
|
||
});
|
||
|
||
// ── 4. SalesAttachmentRequest ──────────────────────────────────────────────
|
||
|
||
test('SalesAttachmentRequest создаётся со статусом pending', function () {
|
||
$user = makeSalesUser();
|
||
|
||
$req = SalesAttachmentRequest::create([
|
||
'sales_user_id' => $user->id,
|
||
'login_input' => 'client@example.com',
|
||
'status' => 'pending',
|
||
]);
|
||
|
||
expect($req->id)->toBeInt()
|
||
->and($req->status)->toBe('pending')
|
||
->and($req->decided_at)->toBeNull();
|
||
});
|
||
|
||
// ── 5. SalesPayout ─────────────────────────────────────────────────────────
|
||
|
||
test('SalesPayout создаётся, amount_rub — decimal, paid_on — date', function () {
|
||
$user = makeSalesUser();
|
||
|
||
$payout = SalesPayout::create([
|
||
'sales_user_id' => $user->id,
|
||
'amount_rub' => '12500.50',
|
||
'paid_on' => today(),
|
||
'created_by' => $user->id,
|
||
]);
|
||
|
||
expect($payout->id)->toBeInt()
|
||
->and((float) $payout->amount_rub)->toBe(12500.50)
|
||
->and($payout->paid_on)->toBeInstanceOf(Carbon::class);
|
||
});
|
||
|
||
test('SalesPayout append-only: UPDATE бросает исключение', function () {
|
||
$user = makeSalesUser();
|
||
|
||
$payout = SalesPayout::create([
|
||
'sales_user_id' => $user->id,
|
||
'amount_rub' => '100.00',
|
||
'paid_on' => today(),
|
||
'created_by' => $user->id,
|
||
]);
|
||
|
||
expect(fn () => $payout->update(['amount_rub' => '999.00']))
|
||
->toThrow(QueryException::class);
|
||
});
|