timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin')); $this->context = LogContext::withData(['user' => 'test', 'action' => 'login']); }); it('can create a log record with required fields', function () { $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($record->getMessage())->toBe('Test message'); expect($record->getLevel())->toBe(LogLevel::INFO); expect($record->getTimestamp())->toBe($this->timestamp); expect($record->getChannel())->toBeNull(); }); it('can create a log record with channel', function () { $record = new LogRecord( message: 'Database query executed', context: $this->context, level: LogLevel::DEBUG, timestamp: $this->timestamp, channel: 'database' ); expect($record->getChannel())->toBe('database'); }); it('can modify message', function () { $record = new LogRecord( message: 'Original message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $modified = $record->withMessage('Modified message'); expect($modified->getMessage())->toBe('Modified message'); expect($record->getMessage())->toBe('Original message'); // Original unverändert }); it('can set channel after creation', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $withChannel = $record->withChannel('security'); expect($withChannel->getChannel())->toBe('security'); expect($record->getChannel())->toBeNull(); // Original unverändert }); it('can format timestamp', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($record->getFormattedTimestamp())->toBe('2024-01-15 10:30:45'); expect($record->getFormattedTimestamp('Y-m-d'))->toBe('2024-01-15'); expect($record->getFormattedTimestamp('H:i:s'))->toBe('10:30:45'); }); it('can add extra data', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $withExtra = $record->withExtra('request_id', 'req-123'); $withBoth = $withExtra->withExtra('process_id', 5678); expect($withBoth->hasExtra('request_id'))->toBeTrue(); expect($withBoth->getExtra('request_id'))->toBe('req-123'); expect($withBoth->getExtra('process_id'))->toBe(5678); expect($withBoth->hasExtra('non_existent'))->toBeFalse(); expect($record->hasExtra('request_id'))->toBeFalse(); // Original unverändert }); it('can add multiple extras at once', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $extras = [ 'server' => 'web-01', 'memory_usage' => 1024000, 'execution_time' => 0.123, ]; $withExtras = $record->withExtras($extras); expect($withExtras->getExtras())->toBe($extras); expect($withExtras->getExtra('server'))->toBe('web-01'); expect($withExtras->getExtra('memory_usage'))->toBe(1024000); expect($withExtras->getExtra('execution_time'))->toBe(0.123); expect($record->getExtras())->toBe([]); // Original unverändert }); it('returns default value for non-existent extra', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($record->getExtra('missing'))->toBeNull(); expect($record->getExtra('missing', 'default'))->toBe('default'); }); it('maintains fluent interface for setters', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $result = $record ->withMessage('New message') ->withChannel('custom') ->withExtra('key1', 'value1') ->withExtras(['key2' => 'value2']); expect($result)->not->toBe($record); // Neues Objekt expect($result->getMessage())->toBe('New message'); expect($result->getChannel())->toBe('custom'); expect($result->getExtra('key1'))->toBe('value1'); expect($result->getExtra('key2'))->toBe('value2'); expect($record->getMessage())->toBe('Test'); // Original unverändert }); it('converts to array correctly', function () { $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::WARNING, timestamp: $this->timestamp, channel: 'test-channel' ); $withExtra = $record->withExtra('request_id', 'req-456'); $array = $withExtra->toArray(); expect($array)->toHaveKeys([ 'message', 'context', 'level', 'level_name', 'timestamp', 'datetime', 'channel', 'extra', ]); expect($array['message'])->toBe('Test message'); expect($array['context'])->toMatchArray(['structured' => ['user' => 'test', 'action' => 'login']]); expect($array['level'])->toBe(LogLevel::WARNING->value); expect($array['level_name'])->toBe('WARNING'); expect($array['timestamp'])->toBe('2024-01-15 10:30:45'); expect($array['datetime'])->toBe($this->timestamp); expect($array['channel'])->toBe('test-channel'); expect($array['extra'])->toBe(['request_id' => 'req-456']); }); it('handles different log levels correctly', function () { $levels = [ LogLevel::DEBUG, LogLevel::INFO, LogLevel::NOTICE, LogLevel::WARNING, LogLevel::ERROR, LogLevel::CRITICAL, LogLevel::ALERT, LogLevel::EMERGENCY, ]; foreach ($levels as $level) { $record = new LogRecord( message: "Test {$level->getName()}", context: $this->context, level: $level, timestamp: $this->timestamp ); expect($record->getLevel())->toBe($level); expect($record->toArray()['level'])->toBe($level->value); expect($record->toArray()['level_name'])->toBe($level->getName()); } }); it('preserves context as LogContext object', function () { $context = LogContext::withData(['test' => 'data']) ->addTags('important', 'audit'); $record = new LogRecord( message: 'Test', context: $context, level: LogLevel::INFO, timestamp: $this->timestamp ); $contextArray = $record->getContext(); expect($contextArray)->toMatchArray([ 'test' => 'data', '_tags' => ['important', 'audit'], ]); }); it('can handle empty extras', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); expect($record->getExtras())->toBe([]); expect($record->hasExtra('any'))->toBeFalse(); }); it('overrides existing extras when adding with same key', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $withOriginal = $record->withExtra('key', 'original'); expect($withOriginal->getExtra('key'))->toBe('original'); $withModified = $withOriginal->withExtra('key', 'modified'); expect($withModified->getExtra('key'))->toBe('modified'); expect($withOriginal->getExtra('key'))->toBe('original'); // Vorherige Version unverändert $withFinal = $withModified->withExtras(['key' => 'final']); expect($withFinal->getExtra('key'))->toBe('final'); expect($withModified->getExtra('key'))->toBe('modified'); // Vorherige Version unverändert });