userId)->toBe('123'); expect($context->username)->toBe('john_doe'); expect($context->email)->toBe('john@example.com'); expect($context->sessionId)->toBe('sess_123'); expect($context->roles)->toBe(['user', 'admin']); expect($context->permissions)->toBe(['read', 'write']); expect($context->authMethod)->toBe('session'); expect($context->isAuthenticated)->toBeTrue(); }); it('can create anonymous user context', function () { $context = UserContext::anonymous('sess_456'); expect($context->userId)->toBeNull(); expect($context->username)->toBeNull(); expect($context->email)->toBeNull(); expect($context->sessionId)->toBe('sess_456'); expect($context->roles)->toBe([]); expect($context->permissions)->toBe([]); expect($context->authMethod)->toBeNull(); expect($context->isAuthenticated)->toBeFalse(); }); it('can create from session data with user_id key', function () { $sessionData = [ 'user_id' => '123', 'username' => 'john', 'email' => 'john@example.com', 'roles' => ['user'], 'permissions' => ['read'], 'session_id' => 'sess_123', 'auth_method' => 'oauth', 'extra_data' => 'some_value', ]; $context = UserContext::fromSession($sessionData); expect($context->userId)->toBe('123'); expect($context->username)->toBe('john'); expect($context->email)->toBe('john@example.com'); expect($context->roles)->toBe(['user']); expect($context->permissions)->toBe(['read']); expect($context->sessionId)->toBe('sess_123'); expect($context->authMethod)->toBe('oauth'); expect($context->isAuthenticated)->toBeTrue(); expect($context->metadata)->toBe(['extra_data' => 'some_value']); }); it('can create from session data with alternative user id keys', function () { $sessionData = [ 'id' => '456', 'name' => 'jane', ]; $context = UserContext::fromSession($sessionData); expect($context->userId)->toBe('456'); expect($context->username)->toBe('jane'); expect($context->isAuthenticated)->toBeTrue(); }); it('creates anonymous context when no user id in session', function () { $sessionData = [ 'some_other_data' => 'value', ]; $context = UserContext::fromSession($sessionData); expect($context->userId)->toBeNull(); expect($context->isAuthenticated)->toBeFalse(); expect($context->metadata)->toBe(['some_other_data' => 'value']); }); it('can add role immutably', function () { $original = UserContext::authenticated('123', roles: ['user']); $modified = $original->withRole('admin'); expect($original->roles)->toBe(['user']); expect($modified->roles)->toBe(['user', 'admin']); }); it('removes duplicate roles when adding', function () { $original = UserContext::authenticated('123', roles: ['user', 'admin']); $modified = $original->withRole('user'); expect($modified->roles)->toBe(['user', 'admin']); }); it('can add permission immutably', function () { $original = UserContext::authenticated('123', permissions: ['read']); $modified = $original->withPermission('write'); expect($original->permissions)->toBe(['read']); expect($modified->permissions)->toBe(['read', 'write']); }); it('removes duplicate permissions when adding', function () { $original = UserContext::authenticated('123', permissions: ['read', 'write']); $modified = $original->withPermission('read'); expect($modified->permissions)->toBe(['read', 'write']); }); it('can add metadata immutably', function () { $original = UserContext::authenticated('123'); $modified = $original->withMetadata('source', 'api'); expect($original->metadata)->toBe([]); expect($modified->metadata)->toBe(['source' => 'api']); }); it('can check if user has role', function () { $context = UserContext::authenticated('123', roles: ['user', 'admin']); expect($context->hasRole('user'))->toBeTrue(); expect($context->hasRole('admin'))->toBeTrue(); expect($context->hasRole('superuser'))->toBeFalse(); }); it('can check if user has permission', function () { $context = UserContext::authenticated('123', permissions: ['read', 'write']); expect($context->hasPermission('read'))->toBeTrue(); expect($context->hasPermission('write'))->toBeTrue(); expect($context->hasPermission('delete'))->toBeFalse(); }); it('generates anonymized id from user id', function () { $context = UserContext::authenticated('123456789'); $anonymizedId = $context->getAnonymizedId(); expect($anonymizedId)->not->toBeNull(); expect($anonymizedId)->toHaveLength(8); expect($anonymizedId)->not->toBe('123456789'); // Same user ID should generate same anonymized ID $context2 = UserContext::authenticated('123456789'); expect($context2->getAnonymizedId())->toBe($anonymizedId); }); it('returns null anonymized id for anonymous users', function () { $context = UserContext::anonymous(); expect($context->getAnonymizedId())->toBeNull(); }); it('masks email addresses', function () { $context = UserContext::authenticated('123', email: 'john.doe@example.com'); $maskedEmail = $context->getMaskedEmail(); expect($maskedEmail)->toBe('j******e@example.com'); }); it('masks short email addresses', function () { $context = UserContext::authenticated('123', email: 'a@b.com'); $maskedEmail = $context->getMaskedEmail(); expect($maskedEmail)->toBe('*@b.com'); }); it('handles invalid email format', function () { $context = UserContext::authenticated('123', email: 'invalid-email'); $maskedEmail = $context->getMaskedEmail(); expect($maskedEmail)->toBe('***@***'); }); it('returns null masked email when no email', function () { $context = UserContext::authenticated('123'); expect($context->getMaskedEmail())->toBeNull(); }); it('converts to array with all data', function () { $context = UserContext::authenticated( userId: '123', username: 'john', email: 'john@example.com', sessionId: 'sess_123', roles: ['user'], permissions: ['read'], authMethod: 'session' )->withMetadata('source', 'test'); $array = $context->toArray(); expect($array)->toBe([ 'user_id' => '123', 'is_authenticated' => true, 'username' => 'john', 'email_masked' => 'j**n@example.com', 'session_id' => 'sess_123', 'roles' => ['user'], 'permissions' => ['read'], 'auth_method' => 'session', 'metadata' => ['source' => 'test'], ]); }); it('converts to array with minimal data', function () { $context = UserContext::authenticated('123'); $array = $context->toArray(); expect($array)->toBe([ 'user_id' => '123', 'is_authenticated' => true, 'auth_method' => 'session', ]); }); it('converts to privacy safe array', function () { $context = UserContext::authenticated( userId: '123', username: 'john', email: 'john@example.com', sessionId: 'sess_123', roles: ['user', 'admin'], permissions: ['read', 'write', 'delete'], authMethod: 'oauth' ); $array = $context->toPrivacySafeArray(); expect($array)->toBe([ 'user_id_anonymized' => $context->getAnonymizedId(), 'is_authenticated' => true, 'roles_count' => 2, 'permissions_count' => 3, 'auth_method' => 'oauth', 'has_session' => true, ]); }); it('extracts user id from object in session data', function () { $userObject = new class () { public $id = '789'; }; $sessionData = ['user' => $userObject]; $context = UserContext::fromSession($sessionData); expect($context->userId)->toBe('789'); expect($context->isAuthenticated)->toBeTrue(); }); it('extracts user id from array in session data', function () { $sessionData = ['logged_in_user' => ['id' => '999']]; $context = UserContext::fromSession($sessionData); expect($context->userId)->toBe('999'); expect($context->isAuthenticated)->toBeTrue(); }); });