tenant = Tenant::factory()->create(); }); test('POST /api/auth/login возвращает user + requires_2fa=false для обычного user', function () { $user = User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'login-test@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'totp_enabled' => false, 'is_active' => true, ]); $response = $this->postJson('/api/auth/login', [ 'email' => 'login-test@example.ru', 'password' => 'secret-pass-123', ]); $response->assertOk(); $response->assertJsonPath('user.id', $user->id); $response->assertJsonPath('user.email', 'login-test@example.ru'); $response->assertJsonPath('requires_2fa', false); expect($response->json('user'))->not->toHaveKey('password_hash'); }); test('POST /api/auth/login c totp_enabled=true возвращает requires_2fa=true', function () { User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'totp-user@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'totp_enabled' => true, ]); $response = $this->postJson('/api/auth/login', [ 'email' => 'totp-user@example.ru', 'password' => 'secret-pass-123', ]); $response->assertOk(); $response->assertJsonPath('requires_2fa', true); }); test('POST /api/auth/login возвращает 422 при неверном пароле', function () { User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'wrong-pass@example.ru', 'password_hash' => Hash::make('right-pass-123'), ]); $response = $this->postJson('/api/auth/login', [ 'email' => 'wrong-pass@example.ru', 'password' => 'wrong-pass-456', ]); $response->assertStatus(422); $response->assertJsonPath('errors.email.0', 'Неверный email или пароль.'); }); test('POST /api/auth/login возвращает 422 для несуществующего email', function () { $response = $this->postJson('/api/auth/login', [ 'email' => 'nonexistent@example.ru', 'password' => 'any-password', ]); $response->assertStatus(422); }); test('POST /api/auth/login возвращает 422 для заблокированного аккаунта (is_active=false)', function () { User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'blocked@example.ru', 'password_hash' => Hash::make('right-pass-123'), 'is_active' => false, ]); $response = $this->postJson('/api/auth/login', [ 'email' => 'blocked@example.ru', 'password' => 'right-pass-123', ]); $response->assertStatus(422); $response->assertJsonPath('errors.email.0', 'Аккаунт заблокирован.'); }); test('POST /api/auth/login валидирует email format и password min:8', function () { $response = $this->postJson('/api/auth/login', [ 'email' => 'not-an-email', 'password' => 'short', ]); $response->assertStatus(422); $response->assertJsonValidationErrors(['email', 'password']); }); test('POST /api/auth/login обновляет last_login_at у user', function () { $user = User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'lastlogin@example.ru', 'password_hash' => Hash::make('secret-pass-123'), 'last_login_at' => null, ]); $this->postJson('/api/auth/login', [ 'email' => 'lastlogin@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); expect($user->fresh()->last_login_at)->not->toBeNull(); }); test('POST /api/auth/register создаёт user + возвращает 201', function () { $response = $this->postJson('/api/auth/register', [ 'email' => 'new-signup@example.ru', 'password' => 'fresh-pass-123', 'accept_offer' => true, 'accept_pdn' => true, ]); $response->assertStatus(201); $response->assertJsonPath('user.email', 'new-signup@example.ru'); $response->assertJsonPath('requires_2fa', false); $user = User::where('email', 'new-signup@example.ru')->first(); expect($user)->not->toBeNull(); expect(Hash::check('fresh-pass-123', $user->password_hash))->toBeTrue(); }); test('POST /api/auth/register отвергает существующий email (unique)', function () { User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'duplicate@example.ru', ]); $response = $this->postJson('/api/auth/register', [ 'email' => 'duplicate@example.ru', 'password' => 'any-password-123', 'accept_offer' => true, 'accept_pdn' => true, ]); $response->assertStatus(422); $response->assertJsonValidationErrors(['email']); }); test('POST /api/auth/register требует accept_offer=true И accept_pdn=true (ТЗ §1.5/§4.1)', function () { $base = [ 'email' => 'no-consent@example.ru', 'password' => 'fresh-pass-123', ]; // Без оферты. $this->postJson('/api/auth/register', array_merge($base, ['accept_pdn' => true])) ->assertStatus(422) ->assertJsonValidationErrors(['accept_offer']); // Без ПДн. $this->postJson('/api/auth/register', array_merge($base, ['accept_offer' => true])) ->assertStatus(422) ->assertJsonValidationErrors(['accept_pdn']); }); test('GET /api/auth/me возвращает 401 без авторизации', function () { $this->getJson('/api/auth/me')->assertStatus(401); }); test('GET /api/auth/me возвращает user после login', function () { $user = User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'me-test@example.ru', 'password_hash' => Hash::make('secret-pass-123'), ]); $this->postJson('/api/auth/login', [ 'email' => 'me-test@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); $this->getJson('/api/auth/me') ->assertOk() ->assertJsonPath('user.id', $user->id) ->assertJsonPath('user.email', 'me-test@example.ru'); }); test('POST /api/auth/logout успешно завершает сессию (200 + flash-message)', function () { User::factory()->create([ 'tenant_id' => $this->tenant->id, 'email' => 'logout-test@example.ru', 'password_hash' => Hash::make('secret-pass-123'), ]); $this->postJson('/api/auth/login', [ 'email' => 'logout-test@example.ru', 'password' => 'secret-pass-123', ])->assertOk(); // logout возвращает 200 с message. Полное invalidate-session тестирование // проблемно в Pest-runtime (cookie-jar держит session между запросами теста); // это тестируется через Pest browser-mode — отдельный коммит. $this->postJson('/api/auth/logout') ->assertOk() ->assertJsonPath('message', 'Вы вышли из системы.'); });