feat: add user impersonation service

This commit is contained in:
idevakk
2025-11-17 10:44:19 -08:00
parent f60c986e07
commit a7029b5f57
21 changed files with 1343 additions and 6 deletions

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
use App\Models\ImpersonationLog;
use App\Models\User;
use App\Services\ImpersonationService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
uses(RefreshDatabase::class);
test('super admin can impersonate normal user', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
expect($impersonationService->canImpersonate($admin, $targetUser))->toBeTrue();
});
test('normal user cannot impersonate anyone', function (): void {
$normalUser = User::factory()->normalUser()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
expect($impersonationService->canImpersonate($normalUser, $targetUser))->toBeFalse();
});
test('super admin cannot impersonate another super admin', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetAdmin = User::factory()->superAdmin()->create();
$impersonationService = app(ImpersonationService::class);
expect($impersonationService->canImpersonate($admin, $targetAdmin))->toBeFalse();
});
test('cannot impersonate self', function (): void {
$admin = User::factory()->superAdmin()->create();
$impersonationService = app(ImpersonationService::class);
expect($impersonationService->canImpersonate($admin, $admin))->toBeFalse();
});
test('cannot impersonate banned user', function (): void {
$admin = User::factory()->superAdmin()->create();
$bannedUser = User::factory()->bannedUser()->create();
$impersonationService = app(ImpersonationService::class);
expect($impersonationService->canImpersonate($admin, $bannedUser))->toBeFalse();
});
test('cannot impersonate when already impersonating', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
// Simulate active impersonation
Session::put('impersonation', true);
Session::put('original_admin_user_id', $admin->id);
Session::put('impersonation_start_time', now());
expect($impersonationService->canImpersonate($admin, $targetUser))->toBeFalse();
});
test('start impersonation creates log entry', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
$request = Request::create('/');
$result = $impersonationService->startImpersonation($admin, $targetUser, $request);
expect($result)->toBeTrue();
expect($impersonationService->isImpersonating())->toBeTrue();
expect(Session::get('original_admin_user_id'))->toBe($admin->id);
$this->assertDatabaseHas('impersonation_logs', [
'admin_id' => $admin->id,
'target_user_id' => $targetUser->id,
'status' => 'active',
]);
});
test('stop impersonation ends session and updates log', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
$request = Request::create('/');
// Start impersonation
$impersonationService->startImpersonation($admin, $targetUser, $request);
$logId = Session::get('impersonation');
// Stop impersonation
$result = $impersonationService->stopImpersonation($request);
expect($result)->toBeTrue();
expect($impersonationService->isImpersonating())->toBeFalse();
expect(Session::get('original_admin_user_id'))->toBeNull();
$this->assertDatabaseHas('impersonation_logs', [
'id' => $logId,
'admin_id' => $admin->id,
'target_user_id' => $targetUser->id,
'status' => 'completed',
]);
});
test('impersonation timeout is detected correctly', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
$request = Request::create('/');
// Start impersonation
$impersonationService->startImpersonation($admin, $targetUser, $request);
// Simulate expired session
Session::put('impersonation_start_time', now()->subMinutes(35));
expect($impersonationService->isImpersonationExpired())->toBeTrue();
});
test('remaining minutes calculation works correctly', function (): void {
$admin = User::factory()->superAdmin()->create();
$targetUser = User::factory()->normalUser()->create();
$impersonationService = app(ImpersonationService::class);
$request = Request::create('/');
// Start impersonation
$impersonationService->startImpersonation($admin, $targetUser, $request);
// Test with 10 minutes elapsed
Session::put('impersonation_start_time', now()->subMinutes(10));
expect($impersonationService->getRemainingMinutes())->toBeBetween(19, 20);
});
test('force stop all impersonations terminates active sessions', function (): void {
// Create multiple active impersonation logs
ImpersonationLog::factory()->count(3)->active()->create();
$impersonationService = app(ImpersonationService::class);
$impersonationService->forceStopAllImpersonations();
$this->assertDatabaseMissing('impersonation_logs', [
'status' => 'active',
]);
$this->assertDatabaseCount('impersonation_logs', 3);
});