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

151 lines
6.6 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\SalesUser;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Tests\Concerns\SharesSupplierPdo;
/**
* TDD: SalesAuthController — login / me / logout.
*
* Покрывает Task 0.5 + 0.6 портала продаж:
* - POST /api/sales/auth/login → 200 (token + user) / 422 (неверный логин) / 403 (отключён)
* - GET /api/sales/auth/me → 401 без токена / 200 с токеном
* - POST /api/sales/auth/logout → 200; повторный GET /me → 401
*
* Маршруты идут через middleware admin-db (UseAdminConnection), который
* переключает default-соединение на pgsql_admin (crm_admin_user). В тестах
* SharesAdminPdo (глобально в Pest.php) связывает pgsql и pgsql_admin через
* один PDO, чтобы данные, засеянные через pgsql, были видны запросу.
*
* Spec: docs/superpowers/plans/2026-06-30-sales-portal.md (Tasks 0.50.6)
*/
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
// ─── helpers ────────────────────────────────────────────────────────────────
/**
* Создаёт SalesUser с известным паролем и возвращает [user, plainPassword].
*
* @return array{0: SalesUser, 1: string}
*/
function makeSalesUserWithPassword(bool $active = true): array
{
$plain = 'SecretPass-'.uniqid();
$user = SalesUser::create([
'name' => 'Тест Менеджер '.uniqid(),
'email' => 'salesauth-'.uniqid().'@test.local',
'password' => Hash::make($plain),
'role' => 'manager',
'is_active' => $active,
]);
return [$user, $plain];
}
// ─── login ───────────────────────────────────────────────────────────────────
test('login: верные данные активного пользователя → 200, token и user.role', function () {
[$user, $plain] = makeSalesUserWithPassword();
$response = $this->postJson('/api/sales/auth/login', [
'email' => $user->email,
'password' => $plain,
]);
$response->assertOk();
expect($response->json('token'))->not->toBeNull()->not->toBe('');
expect($response->json('user.role'))->toBe('manager');
expect($response->json('user.email'))->toBe($user->email);
});
test('login: неверный пароль → 422 с сообщением', function () {
[$user] = makeSalesUserWithPassword();
$response = $this->postJson('/api/sales/auth/login', [
'email' => $user->email,
'password' => 'wrong-password-xyz',
]);
$response->assertStatus(422);
expect($response->json('message'))->toBe('Неверный логин или пароль.');
});
test('login: несуществующий email → 422 с сообщением', function () {
$response = $this->postJson('/api/sales/auth/login', [
'email' => 'nonexistent-'.uniqid().'@test.local',
'password' => 'some-password',
]);
$response->assertStatus(422);
expect($response->json('message'))->toBe('Неверный логин или пароль.');
});
test('login: неактивный пользователь с верным паролем → 403', function () {
[$user, $plain] = makeSalesUserWithPassword(active: false);
$response = $this->postJson('/api/sales/auth/login', [
'email' => $user->email,
'password' => $plain,
]);
$response->assertStatus(403);
expect($response->json('message'))->toBe('Аккаунт отключён, обратитесь к начальнику.');
});
// ─── me ──────────────────────────────────────────────────────────────────────
test('me: запрос без токена → 401', function () {
$this->getJson('/api/sales/auth/me')
->assertUnauthorized();
});
test('me: запрос с валидным Bearer-токеном → 200 с данными пользователя', function () {
[$user, $plain] = makeSalesUserWithPassword();
// Получаем токен через логин.
$loginResponse = $this->postJson('/api/sales/auth/login', [
'email' => $user->email,
'password' => $plain,
]);
$loginResponse->assertOk();
$token = $loginResponse->json('token');
// Запрашиваем /me с токеном.
$meResponse = $this->withHeader('Authorization', 'Bearer '.$token)
->getJson('/api/sales/auth/me');
$meResponse->assertOk();
expect($meResponse->json('email'))->toBe($user->email);
expect($meResponse->json('role'))->toBe('manager');
});
// ─── logout ──────────────────────────────────────────────────────────────────
test('logout: выход инвалидирует токен, повторный /me → 401', function () {
[$user, $plain] = makeSalesUserWithPassword();
// Создаём токен напрямую (без login endpoint) — стабильнее в тестах.
// Проверка самого login-эндпоинта покрыта первым тестом выше.
$plainToken = $user->createToken('sales')->plainTextToken;
// Выход через endpoint.
$logoutResponse = $this->withHeader('Authorization', 'Bearer '.$plainToken)
->postJson('/api/sales/auth/logout');
$logoutResponse->assertOk();
expect($logoutResponse->json('message'))->toBe('Вы вышли.');
// Сбрасываем кэш guard-инстансов в AuthManager (общий singleton в контейнере).
// Без этого guard('sales') возвращает пользователя, кэшированного из предыдущего
// logout-запроса, даже если токен уже удалён из БД.
Auth::forgetGuards();
// Повторный /me с тем же токеном должен вернуть 401.
$this->withHeader('Authorization', 'Bearer '.$plainToken)
->getJson('/api/sales/auth/me')
->assertUnauthorized();
});