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 ); $record->setMessage('Modified message'); expect($record->getMessage())->toBe('Modified message'); }); it('can set channel after creation', function () { $record = new LogRecord( message: 'Test', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); $record->setChannel('security'); expect($record->getChannel())->toBe('security'); }); 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 ); $record->addExtra('request_id', 'req-123'); $record->addExtra('process_id', 5678); expect($record->hasExtra('request_id'))->toBeTrue(); expect($record->getExtra('request_id'))->toBe('req-123'); expect($record->getExtra('process_id'))->toBe(5678); expect($record->hasExtra('non_existent'))->toBeFalse(); }); 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, ]; $record->addExtras($extras); expect($record->getExtras())->toBe($extras); expect($record->getExtra('server'))->toBe('web-01'); expect($record->getExtra('memory_usage'))->toBe(1024000); expect($record->getExtra('execution_time'))->toBe(0.123); }); 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 ->setMessage('New message') ->setChannel('custom') ->addExtra('key1', 'value1') ->addExtras(['key2' => 'value2']); expect($result)->toBe($record); expect($record->getMessage())->toBe('New message'); expect($record->getChannel())->toBe('custom'); expect($record->getExtra('key1'))->toBe('value1'); expect($record->getExtra('key2'))->toBe('value2'); }); it('converts to array correctly', function () { $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::WARNING, timestamp: $this->timestamp, channel: 'test-channel' ); $record->addExtra('request_id', 'req-456'); $array = $record->toArray(); expect($array)->toHaveKeys([ 'message', 'context', 'level', 'level_name', 'timestamp', 'datetime', 'channel', 'extra', ]); expect($array['message'])->toBe('Test message'); expect($array['context'])->toMatchArray(['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 ); $record->addExtra('key', 'original'); expect($record->getExtra('key'))->toBe('original'); $record->addExtra('key', 'modified'); expect($record->getExtra('key'))->toBe('modified'); $record->addExtras(['key' => 'final']); expect($record->getExtra('key'))->toBe('final'); });