timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin')); }); describe('constructor', function () { it('accepts no processors', function () { $manager = new ProcessorManager(); expect($manager instanceof ProcessorManager)->toBeTrue(); }); it('accepts single processor', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'test'; } }; $manager = new ProcessorManager($processor); expect($manager instanceof ProcessorManager)->toBeTrue(); }); it('accepts multiple processors', function () { $processor1 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'test1'; } }; $processor2 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 20; } public function getName(): string { return 'test2'; } }; $manager = new ProcessorManager($processor1, $processor2); expect($manager instanceof ProcessorManager)->toBeTrue(); }); }); describe('addProcessor()', function () { it('returns new instance with added processor', function () { $manager = new ProcessorManager(); $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'new_processor'; } }; $newManager = $manager->addProcessor($processor); // Should return new instance (readonly pattern) expect($newManager !== $manager)->toBeTrue(); }); it('adds processor to existing list', function () { $processor1 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'first'; } }; $manager = new ProcessorManager($processor1); $processor2 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 20; } public function getName(): string { return 'second'; } }; $newManager = $manager->addProcessor($processor2); $list = $newManager->getProcessorList(); expect(count($list))->toBe(2); expect(isset($list['first']))->toBeTrue(); expect(isset($list['second']))->toBeTrue(); }); it('maintains priority sorting after adding processor', function () { $lowPriority = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('low', true); } public function getPriority(): int { return 5; } public function getName(): string { return 'low'; } }; $manager = new ProcessorManager($lowPriority); $highPriority = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('high', true); } public function getPriority(): int { return 20; } public function getName(): string { return 'high'; } }; $newManager = $manager->addProcessor($highPriority); $record = new LogRecord( message: 'Test', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $newManager->processRecord($record); // High priority should run first $extras = $processed->getExtras(); expect(isset($extras['high']))->toBeTrue(); expect(isset($extras['low']))->toBeTrue(); }); }); describe('processRecord()', function () { it('returns original record when no processors', function () { $manager = new ProcessorManager(); $record = new LogRecord( message: 'Test message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $manager->processRecord($record); expect($processed->getMessage())->toBe('Test message'); }); it('applies single processor to record', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('processed', true); } public function getPriority(): int { return 10; } public function getName(): string { return 'test_processor'; } }; $manager = new ProcessorManager($processor); $record = new LogRecord( message: 'Test', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $manager->processRecord($record); $extras = $processed->getExtras(); expect(isset($extras['processed']))->toBeTrue(); expect($extras['processed'])->toBe(true); }); it('applies multiple processors in priority order', function () { $processor1 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('order', 'first'); } public function getPriority(): int { return 20; // Higher priority = runs first } public function getName(): string { return 'high_priority'; } }; $processor2 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { $extras = $record->getExtras(); // Should have 'order' from first processor return $record->addExtra('second_saw_first', isset($extras['order'])); } public function getPriority(): int { return 10; // Lower priority = runs second } public function getName(): string { return 'low_priority'; } }; $manager = new ProcessorManager($processor1, $processor2); $record = new LogRecord( message: 'Test', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $manager->processRecord($record); $extras = $processed->getExtras(); expect($extras['order'])->toBe('first'); expect($extras['second_saw_first'])->toBeTrue(); }); it('chains processor transformations', function () { $addPrefix = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->withMessage('[PREFIX] ' . $record->getMessage()); } public function getPriority(): int { return 20; } public function getName(): string { return 'prefix'; } }; $addSuffix = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->withMessage($record->getMessage() . ' [SUFFIX]'); } public function getPriority(): int { return 10; } public function getName(): string { return 'suffix'; } }; $manager = new ProcessorManager($addPrefix, $addSuffix); $record = new LogRecord( message: 'Message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $manager->processRecord($record); expect($processed->getMessage())->toBe('[PREFIX] Message [SUFFIX]'); }); }); describe('getProcessorList()', function () { it('returns empty array when no processors', function () { $manager = new ProcessorManager(); $list = $manager->getProcessorList(); expect($list)->toBeArray(); expect($list)->toBeEmpty(); }); it('returns array with processor names as keys', function () { $processor1 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'first_processor'; } }; $processor2 = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 20; } public function getName(): string { return 'second_processor'; } }; $manager = new ProcessorManager($processor1, $processor2); $list = $manager->getProcessorList(); expect(array_key_exists('first_processor', $list))->toBeTrue(); expect(array_key_exists('second_processor', $list))->toBeTrue(); }); it('returns priorities as values', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 42; } public function getName(): string { return 'test'; } }; $manager = new ProcessorManager($processor); $list = $manager->getProcessorList(); expect($list['test'])->toBe(42); }); }); describe('hasProcessor()', function () { it('returns false when no processors', function () { $manager = new ProcessorManager(); expect($manager->hasProcessor('any_name'))->toBeFalse(); }); it('returns true when processor exists', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'existing_processor'; } }; $manager = new ProcessorManager($processor); expect($manager->hasProcessor('existing_processor'))->toBeTrue(); }); it('returns false when processor does not exist', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'existing'; } }; $manager = new ProcessorManager($processor); expect($manager->hasProcessor('nonexistent'))->toBeFalse(); }); }); describe('getProcessor()', function () { it('returns null when no processors', function () { $manager = new ProcessorManager(); expect($manager->getProcessor('any_name'))->toBeNull(); }); it('returns processor when exists', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'findme'; } }; $manager = new ProcessorManager($processor); $found = $manager->getProcessor('findme'); expect($found !== null)->toBeTrue(); expect($found->getName())->toBe('findme'); }); it('returns null when processor does not exist', function () { $processor = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record; } public function getPriority(): int { return 10; } public function getName(): string { return 'existing'; } }; $manager = new ProcessorManager($processor); expect($manager->getProcessor('nonexistent'))->toBeNull(); }); }); describe('readonly behavior', function () { it('is a readonly class', function () { $reflection = new ReflectionClass(ProcessorManager::class); expect($reflection->isReadOnly())->toBeTrue(); }); }); describe('priority sorting', function () { it('sorts processors by priority descending', function () { $lowPriority = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('low', true); } public function getPriority(): int { return 5; } public function getName(): string { return 'low'; } }; $mediumPriority = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('medium', true); } public function getPriority(): int { return 15; } public function getName(): string { return 'medium'; } }; $highPriority = new class implements LogProcessor { public function processRecord(LogRecord $record): LogRecord { return $record->addExtra('high', true); } public function getPriority(): int { return 25; } public function getName(): string { return 'high'; } }; // Add in random order $manager = new ProcessorManager($lowPriority, $highPriority, $mediumPriority); $record = new LogRecord( message: 'Test', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $processed = $manager->processRecord($record); // All extras should be present (processors were executed) $extras = $processed->getExtras(); expect(isset($extras['high']))->toBeTrue(); expect(isset($extras['medium']))->toBeTrue(); expect(isset($extras['low']))->toBeTrue(); // Verify they all have value true expect($extras['high'] === true)->toBeTrue(); expect($extras['medium'] === true)->toBeTrue(); expect($extras['low'] === true)->toBeTrue(); }); }); });