Files
michaelschiemer/tests/Unit/Framework/Logging/ProcessorManagerTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

643 lines
19 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogProcessor;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ProcessorManager;
use App\Framework\Logging\ValueObjects\LogContext;
describe('ProcessorManager', function () {
beforeEach(function () {
$this->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();
});
});
});