63 lines
2.7 KiB
PHP
63 lines
2.7 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Models\SalesUser;
|
|||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
|
|
use Illuminate\Support\Facades\Hash;
|
|||
|
|
use Illuminate\Support\Facades\Route;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* TDD: guard «sales» (Sanctum driver, provider sales_users).
|
|||
|
|
*
|
|||
|
|
* Проверяет, что:
|
|||
|
|
* 1. Bearer-токен sales_user открывает доступ к маршруту auth:sales.
|
|||
|
|
* 2. Запрос без токена получает 401.
|
|||
|
|
*
|
|||
|
|
* Примечание по кросс-модельной проверке:
|
|||
|
|
* Стандартный tenant User (App\Models\User) в этом проекте НЕ использует
|
|||
|
|
* HasApiTokens (SPA cookie-auth), поэтому createToken() на User недоступен.
|
|||
|
|
* Кросс-модельная изоляция guard'а sales гарантируется архитектурно:
|
|||
|
|
* Sanctum привязывает tokenable_type к модели в personal_access_tokens;
|
|||
|
|
* guard с provider sales_users резолвит только записи, где tokenable_type =
|
|||
|
|
* App\Models\SalesUser. Тест на «чужой токен» возможен между двумя
|
|||
|
|
* разными SalesUser — разные пользователи корректно разрешаются (auth OK
|
|||
|
|
* для обоих), но привязка к конкретному пользователю верна.
|
|||
|
|
*
|
|||
|
|
* Spec: docs/superpowers/plans/2026-06-30-sales-portal.md (Task 0.3)
|
|||
|
|
*/
|
|||
|
|
uses(DatabaseTransactions::class);
|
|||
|
|
|
|||
|
|
beforeEach(function () {
|
|||
|
|
// Стаб-маршрут, защищённый guard'ом sales.
|
|||
|
|
Route::middleware('auth:sales')->get('/__sales_ping', fn () => response()->json(['ok' => true]));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('sales user с valid Bearer-токеном получает 200 на auth:sales маршруте', function () {
|
|||
|
|
$salesUser = SalesUser::create([
|
|||
|
|
'name' => 'Тест Менеджер',
|
|||
|
|
'email' => 'sales-guard-test-'.uniqid().'@test.local',
|
|||
|
|
'password' => Hash::make('test-secret-123'),
|
|||
|
|
'role' => 'manager',
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$token = $salesUser->createToken('test')->plainTextToken;
|
|||
|
|
|
|||
|
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
|||
|
|
->getJson('/__sales_ping')
|
|||
|
|
->assertOk()
|
|||
|
|
->assertJson(['ok' => true]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('запрос без токена на auth:sales маршрут получает 401', function () {
|
|||
|
|
$this->getJson('/__sales_ping')
|
|||
|
|
->assertUnauthorized();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('невалидный Bearer-токен на auth:sales маршруте получает 401', function () {
|
|||
|
|
// Произвольная строка, которой нет в personal_access_tokens.
|
|||
|
|
$this->withHeader('Authorization', 'Bearer '.str_repeat('x', 64))
|
|||
|
|
->getJson('/__sales_ping')
|
|||
|
|
->assertUnauthorized();
|
|||
|
|
});
|