Files
portal/app/tests/Feature/Sales/SalesOwnershipScopeTest.php
T

226 lines
7.9 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Http\Controllers\Concerns\ScopesSalesOwnership;
use App\Models\SalesClientAssignment;
use App\Models\SalesUser;
use App\Models\Tenant;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
2026-07-18 15:59:56 +03:00
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
/**
* TDD: трейт ScopesSalesOwnership.
*
* Проверяет логику ограничения ownership:
* - Менеджер видит только своих клиентов (tenant_ids из sales_client_assignments).
2026-07-18 15:59:56 +03:00
* - Начальник (role=head) на личных экранах видит ТОЛЬКО свои привязки;
* весь отдел — через visibleTenantIds + ?scope=department (§23).
*
* Изоляция: DatabaseTransactions — каждый тест откатывается.
* Используем DEFAULT connection (pgsql → liderra_testing).
*
* Spec: docs/superpowers/plans/2026-06-30-sales-portal.md (Task 0.4)
*/
uses(DatabaseTransactions::class);
/**
* Зонд для тестирования трейта без реального контроллера.
*/
class OwnershipProbe
{
use ScopesSalesOwnership;
public function __construct(private SalesUser $u) {}
2026-07-18 15:59:56 +03:00
/** @return list<int> */
public function ids(): array
{
return $this->ownedTenantIds($this->u);
}
2026-07-18 15:59:56 +03:00
/** @return list<int>|null */
public function visible(Request $request): ?array
{
return $this->visibleTenantIds($this->u, $request);
}
public function scoped(Builder $query): Builder
{
return $this->scopeByOwnership($query, $this->u);
}
}
// ── helpers ────────────────────────────────────────────────────────────────
function makeSalesUserOwn(string $role = 'manager'): SalesUser
{
return SalesUser::create([
'name' => ucfirst($role).' '.uniqid(),
'email' => $role.uniqid().'@scope.local',
'password' => Hash::make('secret'),
'role' => $role,
]);
}
// ── 1. ownedTenantIds ──────────────────────────────────────────────────────
test('ownedTenantIds для менеджера возвращает список его tenant_id', function () {
$manager = makeSalesUserOwn('manager');
$t1 = Tenant::factory()->create();
$t2 = Tenant::factory()->create();
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t1->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t2->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
$probe = new OwnershipProbe($manager);
$ids = $probe->ids();
expect($ids)->toBeArray()
->toEqualCanonicalizing([$t1->id, $t2->id]);
});
2026-07-18 15:59:56 +03:00
test('ownedTenantIds для head возвращает ЕГО ЛИЧНЫЕ привязки, а не весь отдел', function () {
$head = makeSalesUserOwn('head');
2026-07-18 15:59:56 +03:00
$manager = makeSalesUserOwn('manager');
2026-07-18 15:59:56 +03:00
$mine = Tenant::factory()->create();
$his = Tenant::factory()->create();
SalesClientAssignment::create([
'sales_user_id' => $head->id, 'tenant_id' => $mine->id,
'tariff_params' => [], 'assigned_at' => now(),
]);
SalesClientAssignment::create([
'sales_user_id' => $manager->id, 'tenant_id' => $his->id,
'tariff_params' => [], 'assigned_at' => now(),
]);
$ids = (new OwnershipProbe($head))->ids();
expect($ids)->toContain($mine->id)->not->toContain($his->id);
});
test('visibleTenantIds: весь отдел только начальнику и только по scope=department', function () {
$head = makeSalesUserOwn('head');
$manager = makeSalesUserOwn('manager');
$probe = fn (SalesUser $u, string $qs) => (new OwnershipProbe($u))
->visible(Request::create('/api/sales/clients'.$qs));
2026-07-18 15:59:56 +03:00
// Начальник без scope — только личное (у него привязок нет → пусто).
expect($probe($head, ''))->toBe([]);
// Начальник с scope=department — без ограничения.
expect($probe($head, '?scope=department'))->toBeNull();
// Менеджеру scope не помогает.
expect($probe($manager, '?scope=department'))->toBe([]);
});
test('ownedTenantIds для менеджера без клиентов возвращает пустой массив', function () {
$manager = makeSalesUserOwn('manager');
$probe = new OwnershipProbe($manager);
$ids = $probe->ids();
expect($ids)->toBeArray()->toBeEmpty();
});
// ── 2. scopeByOwnership ────────────────────────────────────────────────────
test('scopeByOwnership для менеджера ограничивает запрос его tenant_id', function () {
$manager = makeSalesUserOwn('manager');
$other = makeSalesUserOwn('manager');
$t1 = Tenant::factory()->create();
$t2 = Tenant::factory()->create();
$t3 = Tenant::factory()->create();
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t1->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t2->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
// t3 принадлежит другому менеджеру
SalesClientAssignment::create([
'sales_user_id' => $other->id,
'tenant_id' => $t3->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
$probe = new OwnershipProbe($manager);
$results = $probe->scoped(SalesClientAssignment::query())->get();
$tenantIds = $results->pluck('tenant_id')->sort()->values()->all();
expect($tenantIds)->toEqualCanonicalizing([$t1->id, $t2->id]);
});
2026-07-18 15:59:56 +03:00
test('scopeByOwnership ограничивает и начальника — только его привязки', function () {
$head = makeSalesUserOwn('head');
$manager = makeSalesUserOwn('manager');
$t1 = Tenant::factory()->create();
$t2 = Tenant::factory()->create();
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t1->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
SalesClientAssignment::create([
'sales_user_id' => $manager->id,
'tenant_id' => $t2->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
$probe = new OwnershipProbe($head);
$results = $probe->scoped(SalesClientAssignment::query())->get();
2026-07-18 15:59:56 +03:00
// У начальника своих привязок нет → чужие записи менеджера не видны.
expect($results->pluck('tenant_id')->all())
->not->toContain($t1->id)
->not->toContain($t2->id);
});
test('scopeByOwnership для менеджера без клиентов возвращает пустую коллекцию', function () {
$manager = makeSalesUserOwn('manager');
// Создаём другого менеджера с клиентом, чтобы таблица не была пустой
$other = makeSalesUserOwn('manager');
$t1 = Tenant::factory()->create();
SalesClientAssignment::create([
'sales_user_id' => $other->id,
'tenant_id' => $t1->id,
'tariff_params' => [],
'assigned_at' => now(),
]);
$probe = new OwnershipProbe($manager);
$results = $probe->scoped(SalesClientAssignment::query())->get();
expect($results)->toBeEmpty();
});