seed(RolesAndPermissionsSeeder::class); $this->admin = User::factory()->approved()->create(); $this->admin->assignRole('admin'); $this->target = User::factory()->approved()->create(); } public function test_admin_can_list_a_users_active_sessions(): void { $token = $this->createActiveToken($this->target, 'Demo App'); $response = $this->actingAs($this->admin)->getJson("/web-api/admin/users/{$this->target->id}/sessions"); $response->assertOk(); $response->assertJsonFragment(['id' => $token->id, 'client_name' => 'Demo App']); } public function test_revoked_and_expired_tokens_are_not_listed(): void { $this->createActiveToken($this->target, 'Revoked App', ['revoked' => true]); $this->createActiveToken($this->target, 'Expired App', ['expires_at' => now()->subDay()]); $active = $this->createActiveToken($this->target, 'Active App'); $response = $this->actingAs($this->admin)->getJson("/web-api/admin/users/{$this->target->id}/sessions"); $response->assertOk()->assertJsonCount(1); $response->assertJsonFragment(['id' => $active->id]); } public function test_non_admin_cannot_list_sessions(): void { $user = User::factory()->approved()->create(); $this->actingAs($user) ->getJson("/web-api/admin/users/{$this->target->id}/sessions") ->assertForbidden(); } public function test_admin_can_revoke_a_single_session(): void { $token = $this->createActiveToken($this->target); $response = $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$this->target->id}/sessions/{$token->id}"); $response->assertNoContent(); $this->assertTrue($token->fresh()->revoked); $this->assertDatabaseHas('audit_logs', [ 'action' => 'user.session_revoked', 'subject_id' => (string) $this->target->id, ]); } public function test_revoking_nonexistent_session_returns_404(): void { $this->actingAs($this->admin) ->deleteJson('/web-api/admin/users/'.$this->target->id.'/sessions/'.Str::random(80)) ->assertNotFound(); } public function test_admin_can_revoke_all_sessions(): void { $this->createActiveToken($this->target, 'App One'); $this->createActiveToken($this->target, 'App Two'); $response = $this->actingAs($this->admin) ->deleteJson("/web-api/admin/users/{$this->target->id}/sessions"); $response->assertNoContent(); $this->assertSame(0, Token::where('user_id', $this->target->id)->where('revoked', false)->count()); $this->assertDatabaseHas('audit_logs', [ 'action' => 'user.sessions_revoked_all', 'subject_id' => (string) $this->target->id, ]); } public function test_non_admin_cannot_revoke_sessions(): void { $user = User::factory()->approved()->create(); $token = $this->createActiveToken($this->target); $this->actingAs($user) ->deleteJson("/web-api/admin/users/{$this->target->id}/sessions/{$token->id}") ->assertForbidden(); $this->assertFalse($token->fresh()->revoked); } private function createActiveToken(User $user, string $clientName = 'Test Client', array $overrides = []): Token { $client = Client::create([ 'name' => $clientName, 'secret' => null, 'provider' => null, 'redirect_uris' => [], 'grant_types' => ['authorization_code', 'refresh_token'], 'revoked' => false, ]); return Token::create(array_merge([ 'id' => Str::random(80), 'user_id' => $user->id, 'client_id' => $client->id, 'name' => null, 'scopes' => ['users:read'], 'revoked' => false, 'created_at' => now(), 'updated_at' => now(), 'expires_at' => now()->addDays(15), ], $overrides)); } }