docs: consolidate documentation into organized structure

- 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
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Logging\ValueObjects;
use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Logging\ValueObjects\RequestContext;
use App\Framework\Logging\ValueObjects\UserContext;
use App\Framework\Random\SecureRandomGenerator;
use App\Framework\Tracing\TraceContext;
describe('LogContext', function () {
it('can be created empty', function () {
$context = LogContext::empty();
expect($context->structured)->toBe([]);
expect($context->tags)->toBe([]);
expect($context->trace)->toBeNull();
expect($context->user)->toBeNull();
expect($context->request)->toBeNull();
expect($context->metadata)->toBe([]);
});
it('can be created with structured data', function () {
$data = ['user_id' => 123, 'action' => 'login'];
$context = LogContext::withData($data);
expect($context->structured)->toBe($data);
expect($context->hasStructuredData())->toBeTrue();
});
it('can be created with tags', function () {
$context = LogContext::withTags('auth', 'user-action');
expect($context->tags)->toBe(['auth', 'user-action']);
expect($context->hasTags())->toBeTrue();
expect($context->hasTag('auth'))->toBeTrue();
expect($context->hasTag('missing'))->toBeFalse();
});
it('can add structured data immutably', function () {
$original = LogContext::withData(['key1' => 'value1']);
$modified = $original->addData('key2', 'value2');
expect($original->structured)->toBe(['key1' => 'value1']);
expect($modified->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']);
});
it('can merge structured data immutably', function () {
$original = LogContext::withData(['key1' => 'value1']);
$modified = $original->mergeData(['key2' => 'value2', 'key3' => 'value3']);
expect($original->structured)->toBe(['key1' => 'value1']);
expect($modified->structured)->toBe([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
]);
});
it('can add tags immutably', function () {
$original = LogContext::withTags('tag1');
$modified = $original->addTags('tag2', 'tag3');
expect($original->tags)->toBe(['tag1']);
expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']);
});
it('removes duplicate tags when adding', function () {
$original = LogContext::withTags('tag1', 'tag2');
$modified = $original->addTags('tag2', 'tag3');
expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']);
});
it('can add user context immutably', function () {
$userContext = UserContext::authenticated('123', 'john');
$original = LogContext::empty();
$modified = $original->withUser($userContext);
expect($original->user)->toBeNull();
expect($modified->user)->toBe($userContext);
});
it('can add request context immutably', function () {
$requestContext = RequestContext::empty();
$original = LogContext::empty();
$modified = $original->withRequest($requestContext);
expect($original->request)->toBeNull();
expect($modified->request)->toBe($requestContext);
});
it('can add trace context immutably', function () {
$traceContext = TraceContext::start(new SecureRandomGenerator());
$original = LogContext::empty();
$modified = $original->withTrace($traceContext);
expect($original->trace)->toBeNull();
expect($modified->trace)->toBe($traceContext);
});
it('can add metadata immutably', function () {
$original = LogContext::empty();
$modified = $original->addMetadata('source', 'test');
expect($original->metadata)->toBe([]);
expect($modified->metadata)->toBe(['source' => 'test']);
});
it('can merge two contexts', function () {
$context1 = LogContext::withData(['key1' => 'value1'])
->addTags('tag1')
->addMetadata('meta1', 'value1');
$context2 = LogContext::withData(['key2' => 'value2'])
->addTags('tag2')
->addMetadata('meta2', 'value2');
$merged = $context1->merge($context2);
expect($merged->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']);
expect($merged->tags)->toBe(['tag1', 'tag2']);
expect($merged->metadata)->toBe(['meta1' => 'value1', 'meta2' => 'value2']);
});
it('prioritizes second context in merge for trace, user, request', function () {
$user1 = UserContext::authenticated('123');
$user2 = UserContext::authenticated('456');
$request1 = RequestContext::empty();
$request2 = RequestContext::empty();
$trace1 = TraceContext::start(new SecureRandomGenerator());
$trace2 = TraceContext::start(new SecureRandomGenerator());
$context1 = LogContext::empty()
->withUser($user1)
->withRequest($request1)
->withTrace($trace1);
$context2 = LogContext::empty()
->withUser($user2)
->withRequest($request2)
->withTrace($trace2);
$merged = $context1->merge($context2);
expect($merged->user)->toBe($user2);
expect($merged->request)->toBe($request2);
expect($merged->trace)->toBe($trace2);
});
it('can convert to array', function () {
$userContext = UserContext::authenticated('123', 'john');
$requestContext = RequestContext::empty();
$traceContext = TraceContext::start(new SecureRandomGenerator());
$context = LogContext::withData(['key' => 'value'])
->addTags('tag1', 'tag2')
->withUser($userContext)
->withRequest($requestContext)
->withTrace($traceContext)
->addMetadata('source', 'test');
$array = $context->toArray();
expect($array)->toHaveKeys([
'structured',
'tags',
'trace',
'user',
'request',
'metadata',
]);
expect($array['structured'])->toBe(['key' => 'value']);
expect($array['tags'])->toBe(['tag1', 'tag2']);
expect($array['metadata'])->toBe(['source' => 'test']);
});
it('only includes non-empty sections in toArray', function () {
$context = LogContext::withData(['key' => 'value']);
$array = $context->toArray();
expect($array)->toHaveKey('structured');
expect($array)->not->toHaveKey('tags');
expect($array)->not->toHaveKey('trace');
expect($array)->not->toHaveKey('user');
expect($array)->not->toHaveKey('request');
expect($array)->not->toHaveKey('metadata');
});
it('can create context with current trace', function () {
// Clear any existing trace
TraceContext::clear();
// Start a new trace
$trace = TraceContext::start(new SecureRandomGenerator());
$context = LogContext::withCurrentTrace();
expect($context->trace)->toBe($trace);
// Cleanup
TraceContext::clear();
});
it('creates context with null trace when no current trace', function () {
TraceContext::clear();
$context = LogContext::withCurrentTrace();
expect($context->trace)->toBeNull();
});
});

View File

@@ -0,0 +1,271 @@
<?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();
});
});