- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
272 lines
9.3 KiB
PHP
272 lines
9.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Logging\ValueObjects;
|
|
|
|
use App\Framework\Logging\ValueObjects\UserContext;
|
|
|
|
describe('UserContext', function () {
|
|
it('can create authenticated user context', function () {
|
|
$context = UserContext::authenticated(
|
|
userId: '123',
|
|
username: 'john_doe',
|
|
email: 'john@example.com',
|
|
sessionId: 'sess_123',
|
|
roles: ['user', 'admin'],
|
|
permissions: ['read', 'write'],
|
|
authMethod: 'session'
|
|
);
|
|
|
|
expect($context->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();
|
|
});
|
|
});
|