Files
michaelschiemer/tests/Unit/Framework/Logging/Handlers/RotatingFileHandlerTest.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

308 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Logging\Handlers\RotatingFileHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;
describe('RotatingFileHandler', function () {
beforeEach(function () {
$this->testDir = sys_get_temp_dir() . '/rotating_handler_test_' . uniqid();
mkdir($this->testDir, 0777, true);
$this->testLogFile = $this->testDir . '/test.log';
});
afterEach(function () {
// Clean up test files
$files = glob($this->testDir . '/*');
if ($files) {
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
}
if (is_dir($this->testDir)) {
rmdir($this->testDir);
}
});
describe('withSizeRotation()', function () {
it('creates handler with size-based rotation', function () {
$handler = RotatingFileHandler::withSizeRotation(
$this->testLogFile,
maxFileSize: Byte::fromKilobytes(1),
maxFiles: 3
);
expect($handler)->toBeInstanceOf(RotatingFileHandler::class);
// Write small log
$record = new LogRecord(
message: 'Test message',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
expect(file_exists($this->testLogFile))->toBeTrue();
});
it('rotates log when size limit exceeded', function () {
$handler = RotatingFileHandler::withSizeRotation(
$this->testLogFile,
maxFileSize: Byte::fromBytes(100), // Very small size
maxFiles: 3
);
// Write multiple large messages to exceed size
for ($i = 0; $i < 10; $i++) {
$record = new LogRecord(
message: str_repeat('X', 50), // 50 bytes per message
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
}
// Check that rotation happened (should have .1 file)
expect(file_exists($this->testLogFile . '.1'))->toBeTrue();
});
});
describe('daily()', function () {
it('creates handler with daily rotation strategy', function () {
$handler = RotatingFileHandler::daily($this->testLogFile);
expect($handler)->toBeInstanceOf(RotatingFileHandler::class);
$strategy = $handler->getRotationStrategy();
expect($strategy['time_based'])->toBe('daily');
expect($strategy['size_based'])->toBeTrue();
});
it('rotates log when file is from previous day', function () {
// Create old log file
file_put_contents($this->testLogFile, "Old log content\n");
// Set file modification time to yesterday
touch($this->testLogFile, strtotime('yesterday'));
$handler = RotatingFileHandler::daily($this->testLogFile);
// Write new log
$record = new LogRecord(
message: 'New log entry',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
// File should have been rotated
expect(file_exists($this->testLogFile . '.1'))->toBeTrue();
// New file should contain only new entry
$content = file_get_contents($this->testLogFile);
expect($content)->toContain('New log entry');
expect($content)->not->toContain('Old log content');
});
it('does not rotate log when file is from today', function () {
// Create log file
file_put_contents($this->testLogFile, "Today's log\n");
$handler = RotatingFileHandler::daily($this->testLogFile);
// Write new log
$record = new LogRecord(
message: 'Another entry',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
// No rotation should have occurred
expect(file_exists($this->testLogFile . '.1'))->toBeFalse();
// File should contain both entries
$content = file_get_contents($this->testLogFile);
expect($content)->toContain("Today's log");
expect($content)->toContain('Another entry');
});
});
describe('weekly()', function () {
it('creates handler with weekly rotation strategy', function () {
$handler = RotatingFileHandler::weekly($this->testLogFile);
$strategy = $handler->getRotationStrategy();
expect($strategy['time_based'])->toBe('weekly');
});
it('rotates log when file is from previous week', function () {
// Create old log file
file_put_contents($this->testLogFile, "Old week log\n");
// Set file modification time to last week
touch($this->testLogFile, strtotime('last week'));
$handler = RotatingFileHandler::weekly($this->testLogFile);
// Write new log
$record = new LogRecord(
message: 'New week entry',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
// File should have been rotated
expect(file_exists($this->testLogFile . '.1'))->toBeTrue();
});
});
describe('monthly()', function () {
it('creates handler with monthly rotation strategy', function () {
$handler = RotatingFileHandler::monthly($this->testLogFile);
$strategy = $handler->getRotationStrategy();
expect($strategy['time_based'])->toBe('monthly');
});
it('rotates log when file is from previous month', function () {
// Create old log file
file_put_contents($this->testLogFile, "Old month log\n");
// Set file modification time to last month
touch($this->testLogFile, strtotime('last month'));
$handler = RotatingFileHandler::monthly($this->testLogFile);
// Write new log
$record = new LogRecord(
message: 'New month entry',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record);
// File should have been rotated
expect(file_exists($this->testLogFile . '.1'))->toBeTrue();
});
});
describe('production()', function () {
it('creates handler with production-optimized settings', function () {
$handler = RotatingFileHandler::production($this->testLogFile);
expect($handler)->toBeInstanceOf(RotatingFileHandler::class);
$strategy = $handler->getRotationStrategy();
expect($strategy['time_based'])->toBe('daily');
expect($strategy['size_based'])->toBeTrue();
});
it('uses INFO as default level for production', function () {
$handler = RotatingFileHandler::production($this->testLogFile);
// DEBUG should not be handled
$debugRecord = new LogRecord(
message: 'Debug message',
context: LogContext::empty(),
level: LogLevel::DEBUG,
timestamp: new DateTimeImmutable()
);
expect($handler->isHandling($debugRecord))->toBeFalse();
// INFO should be handled
$infoRecord = new LogRecord(
message: 'Info message',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
expect($handler->isHandling($infoRecord))->toBeTrue();
});
});
describe('withMaxSize()', function () {
it('allows configuring max size after creation', function () {
$handler = RotatingFileHandler::daily($this->testLogFile);
$handler->withMaxSize(Byte::fromBytes(50), maxFiles: 5);
expect($handler)->toBeInstanceOf(RotatingFileHandler::class);
});
});
describe('getRotationStrategy()', function () {
it('returns rotation strategy info', function () {
$handler = RotatingFileHandler::daily($this->testLogFile);
$strategy = $handler->getRotationStrategy();
expect($strategy)->toHaveKeys(['time_based', 'size_based', 'last_check']);
expect($strategy['time_based'])->toBe('daily');
expect($strategy['size_based'])->toBeTrue();
});
it('returns none for size-only rotation', function () {
$handler = RotatingFileHandler::withSizeRotation($this->testLogFile);
$strategy = $handler->getRotationStrategy();
expect($strategy['time_based'])->toBe('none');
});
});
describe('caching behavior', function () {
it('caches rotation checks for performance', function () {
$handler = RotatingFileHandler::daily($this->testLogFile);
// First write
$record1 = new LogRecord(
message: 'First message',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record1);
$strategy1 = $handler->getRotationStrategy();
$lastCheck1 = $strategy1['last_check'];
// Immediate second write (within cache window)
$record2 = new LogRecord(
message: 'Second message',
context: LogContext::empty(),
level: LogLevel::INFO,
timestamp: new DateTimeImmutable()
);
$handler->handle($record2);
$strategy2 = $handler->getRotationStrategy();
$lastCheck2 = $strategy2['last_check'];
// Last check should be same (cached)
expect($lastCheck1)->toBe($lastCheck2);
});
});
});