Files
portal/app/tests/Feature/Sales/SalesGuardTest.php
T
Дмитрий bdcb82f8f7 feat(sales): guard sales (sanctum) + provider + таблица токенов
Task 0.3: guard 'sales' (driver sanctum, provider sales_users). Тест SalesGuardTest 3/3 (valid Bearer→200, без токена→401, мусор→401). Миграция personal_access_tokens (Sanctum Bearer; раньше не было — основной кабинет SPA cookie), DDL через pgsql_supplier, гранты crm_admin_user, CHANGELOG v8.60. Larastan baseline: +SalesGuardTest (Pest TestCall false-pos), SetTenantContext int→mixed (второй provider расширил тип $request->user()). План: admin-db ДО auth:sales. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:36:13 +03:00

63 lines
2.7 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\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();
});