tenant = Tenant::factory()->create(); $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]); $this->actingAs($this->user); }); function insertSession(int $userId, string $token, string $ua = 'Mozilla/5.0 (Windows NT 10.0) Chrome/120'): int { return DB::table('user_sessions')->insertGetId([ 'user_id' => $userId, 'token_hash' => $token, 'ip_address' => '10.0.0.1', 'user_agent' => $ua, 'last_active_at' => now(), 'created_at' => now(), 'expires_at' => now()->addHours(2), ]); } test('GET /api/account/security возвращает активные сессии', function () { insertSession($this->user->id, 'sess-token-aaa'); $response = $this->getJson('/api/account/security'); $response->assertOk(); expect($response->json('sessions'))->toBeArray()->toHaveCount(1); expect($response->json('sessions.0.device'))->toContain('Chrome'); expect($response->json('sessions.0.id'))->toBeInt(); }); test('DELETE /api/account/sessions/{id} отзывает свою сессию', function () { $id = insertSession($this->user->id, 'sess-token-bbb'); $this->deleteJson("/api/account/sessions/{$id}")->assertOk(); expect(DB::table('user_sessions')->where('id', $id)->exists())->toBeFalse(); }); test('DELETE чужой сессии: 404, чужая строка цела', function () { $other = User::factory()->create(['tenant_id' => $this->tenant->id]); $id = insertSession($other->id, 'sess-token-ccc'); $this->deleteJson("/api/account/sessions/{$id}")->assertStatus(404); expect(DB::table('user_sessions')->where('id', $id)->exists())->toBeTrue(); }); test('просроченные сессии не показываются', function () { DB::table('user_sessions')->insert([ 'user_id' => $this->user->id, 'token_hash' => 'sess-token-expired', 'ip_address' => '10.0.0.2', 'user_agent' => 'old', 'last_active_at' => now()->subDay(), 'created_at' => now()->subDay(), 'expires_at' => now()->subHour(), ]); $response = $this->getJson('/api/account/security'); $response->assertOk(); expect($response->json('sessions'))->toHaveCount(0); });