testDir = sys_get_temp_dir() . '/logging_integration_test_' . uniqid(); mkdir($this->testDir, 0777, true); mkdir($this->testDir . '/storage/logs', 0777, true); mkdir($this->testDir . '/storage/logs/app', 0777, true); mkdir($this->testDir . '/storage/logs/debug', 0777, true); mkdir($this->testDir . '/storage/logs/security', 0777, true); }); afterEach(function () { // Cleanup all test directories and files recursiveDelete($this->testDir); }); describe('RotatingFileHandler Integration', function () { it('handles complete rotation lifecycle with real files', function () { $logFile = $this->testDir . '/storage/logs/app.log'; // Create handler with very small size limit for testing $handler = RotatingFileHandler::withSizeRotation( $logFile, maxFileSize: Byte::fromBytes(200), maxFiles: 3, compress: false // Disable compression for easier testing ); // Write logs until rotation occurs for ($i = 0; $i < 10; $i++) { $record = new LogRecord( message: "Log entry #{$i} - " . str_repeat('X', 50), context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); } // Verify rotation occurred expect(file_exists($logFile))->toBeTrue(); expect(file_exists($logFile . '.1'))->toBeTrue(); // Verify current log file is within size limit (with some buffer for timestamps) $currentSize = filesize($logFile); expect($currentSize)->toBeLessThan(400); // Allow buffer for log formatting // Verify rotated file exists and contains data $rotatedContent = file_get_contents($logFile . '.1'); expect($rotatedContent)->not->toBeEmpty(); expect($rotatedContent)->toContain('Log entry'); }); it('integrates daily rotation with file system', function () { $logFile = $this->testDir . '/storage/logs/daily.log'; // Create old log file from yesterday file_put_contents($logFile, "Yesterday's log entry\n"); touch($logFile, strtotime('yesterday')); // Create handler with daily rotation $handler = RotatingFileHandler::daily($logFile, maxFiles: 7, compress: false); // Write new log entry $record = new LogRecord( message: "Today's log entry", context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); // Verify rotation occurred expect(file_exists($logFile . '.1'))->toBeTrue(); // Verify old content is in rotated file $rotatedContent = file_get_contents($logFile . '.1'); expect($rotatedContent)->toContain("Yesterday's log"); // Verify new log only contains today's entry $currentContent = file_get_contents($logFile); expect($currentContent)->toContain("Today's log"); expect($currentContent)->not->toContain("Yesterday's log"); }); it('handles production configuration correctly', function () { $logFile = $this->testDir . '/storage/logs/production.log'; $handler = RotatingFileHandler::production($logFile); // Verify INFO level logs are handled $infoRecord = new LogRecord( message: 'Production info log', context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); expect($handler->isHandling($infoRecord))->toBeTrue(); $handler->handle($infoRecord); expect(file_exists($logFile))->toBeTrue(); $content = file_get_contents($logFile); expect($content)->toContain('Production info log'); // Verify DEBUG logs are NOT handled by production handler $debugRecord = new LogRecord( message: 'Debug log should not appear', context: LogContext::empty(), level: LogLevel::DEBUG, timestamp: new DateTimeImmutable() ); expect($handler->isHandling($debugRecord))->toBeFalse(); }); it('maintains rotation across multiple log files in subdirectories', function () { $appLog = $this->testDir . '/storage/logs/app/application.log'; $debugLog = $this->testDir . '/storage/logs/debug/debug.log'; $securityLog = $this->testDir . '/storage/logs/security/security.log'; // Create handlers for different log types $appHandler = RotatingFileHandler::withSizeRotation($appLog, maxFileSize: Byte::fromBytes(100)); $debugHandler = RotatingFileHandler::withSizeRotation($debugLog, maxFileSize: Byte::fromBytes(100)); $securityHandler = RotatingFileHandler::withSizeRotation($securityLog, maxFileSize: Byte::fromBytes(100)); // Write logs to all handlers for ($i = 0; $i < 5; $i++) { $record = new LogRecord( message: str_repeat('X', 30), context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $appHandler->handle($record); $debugHandler->handle($record); $securityHandler->handle($record); } // Verify all logs exist expect(file_exists($appLog))->toBeTrue(); expect(file_exists($debugLog))->toBeTrue(); expect(file_exists($securityLog))->toBeTrue(); // At least one should have triggered rotation $rotationOccurred = file_exists($appLog . '.1') || file_exists($debugLog . '.1') || file_exists($securityLog . '.1'); expect($rotationOccurred)->toBeTrue(); }); }); describe('LogHealthCheckCommand Integration', function () { it('performs complete health check on log infrastructure', function () { // Save current directory and change to test directory $originalDir = getcwd(); chdir($this->testDir); try { // Create command $command = new LogHealthCheckCommand(); // Create input without options $output = new ConsoleOutput(); $input = new ConsoleInput(['test'], $output, null); // Capture output ob_start(); $exitCode = $command->execute($input); $output = ob_get_clean(); // Verify all checks were performed expect($output)->toContain('Checking log directories'); expect($output)->toContain('Checking write permissions'); expect($output)->toContain('Checking disk space'); expect($output)->toContain('Checking log files'); expect($output)->toContain('Summary'); // Should pass all checks (directories exist, permissions OK, disk space OK, no large files) expect($exitCode)->toBe(ExitCode::SUCCESS); expect($output)->toContain('All checks passed'); } finally { // Restore original directory chdir($originalDir); } }); it('detects missing directories and can auto-fix with --fix-permissions', function () { // Remove debug directory rmdir($this->testDir . '/storage/logs/debug'); $originalDir = getcwd(); chdir($this->testDir); try { // First run without fix - should fail $command1 = new LogHealthCheckCommand(); $output = new ConsoleOutput(); $input1 = new ConsoleInput(['test'], $output, null); ob_start(); $exitCode1 = $command1->execute($input1); $output1 = ob_get_clean(); expect($exitCode1)->toBe(ExitCode::GENERAL_ERROR); expect($output1)->toContain('(missing)'); // Second run with --fix-permissions $command2 = new LogHealthCheckCommand(); $output2 = new ConsoleOutput(); $input2 = new ConsoleInput(['test', '--fix-permissions'], $output2, null); ob_start(); $exitCode2 = $command2->execute($input2); $output2 = ob_get_clean(); // Should pass after fix expect($exitCode2)->toBe(ExitCode::SUCCESS); expect(is_dir($this->testDir . '/storage/logs/debug'))->toBeTrue(); } finally { chdir($originalDir); } }); it('reports detailed information with --detailed flag', function () { // Create some log files file_put_contents($this->testDir . '/storage/logs/test1.log', str_repeat('X', 1000)); file_put_contents($this->testDir . '/storage/logs/test2.log', str_repeat('Y', 2000)); $originalDir = getcwd(); chdir($this->testDir); try { $command = new LogHealthCheckCommand(); $output = new ConsoleOutput(); $input = new ConsoleInput(['test', '--detailed'], $output, null); ob_start(); $command->execute($input); $output = ob_get_clean(); // Verify detailed output shows file information expect($output)->toContain('test1.log'); expect($output)->toContain('test2.log'); expect($output)->toContain('Size:'); expect($output)->toContain('Modified:'); } finally { chdir($originalDir); } }); }); describe('Full Logging Pipeline Integration', function () { it('completes full workflow: write logs -> rotate -> health check', function () { $logFile = $this->testDir . '/storage/logs/app/workflow.log'; // Step 1: Write logs with rotation $handler = RotatingFileHandler::withSizeRotation( $logFile, maxFileSize: Byte::fromBytes(500), maxFiles: 3, compress: false ); for ($i = 0; $i < 20; $i++) { $record = new LogRecord( message: "Workflow log entry #{$i} - " . str_repeat('X', 50), context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); } // Verify logs and rotation expect(file_exists($logFile))->toBeTrue(); expect(file_exists($logFile . '.1'))->toBeTrue(); // Step 2: Run health check $originalDir = getcwd(); chdir($this->testDir); try { $command = new LogHealthCheckCommand(); $output = new ConsoleOutput(); $input = new ConsoleInput(['test', '--detailed'], $output, null); ob_start(); $exitCode = $command->execute($input); $healthOutput = ob_get_clean(); // Verify health check passes expect($exitCode)->toBe(ExitCode::SUCCESS); expect($healthOutput)->toContain('log file(s)'); // At least 2 files (workflow.log + workflow.log.1) expect($healthOutput)->toContain('All checks passed'); } finally { chdir($originalDir); } }); it('handles concurrent logging with multiple handlers and health monitoring', function () { $appLog = $this->testDir . '/storage/logs/app/app.log'; $debugLog = $this->testDir . '/storage/logs/debug/debug.log'; $securityLog = $this->testDir . '/storage/logs/security/security.log'; // Create multiple handlers $handlers = [ RotatingFileHandler::production($appLog), RotatingFileHandler::withSizeRotation($debugLog, maxFileSize: Byte::fromKilobytes(1)), RotatingFileHandler::daily($securityLog), ]; // Write logs concurrently to all handlers foreach ($handlers as $handler) { for ($i = 0; $i < 10; $i++) { $record = new LogRecord( message: "Concurrent log entry #{$i}", context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); } } // Verify all logs exist expect(file_exists($appLog))->toBeTrue(); expect(file_exists($debugLog))->toBeTrue(); expect(file_exists($securityLog))->toBeTrue(); // Run health check on entire infrastructure $originalDir = getcwd(); chdir($this->testDir); try { $command = new LogHealthCheckCommand(); $output = new ConsoleOutput(); $input = new ConsoleInput(['test'], $output, null); ob_start(); $exitCode = $command->execute($input); $healthOutput = ob_get_clean(); // Should pass all checks expect($exitCode)->toBe(ExitCode::SUCCESS); expect($healthOutput)->toContain('log file(s)'); // At least 3 log files } finally { chdir($originalDir); } }); }); describe('Performance and Scalability Integration', function () { it('handles high-volume logging efficiently', function () { $logFile = $this->testDir . '/storage/logs/performance.log'; $handler = RotatingFileHandler::withSizeRotation( $logFile, maxFileSize: Byte::fromKilobytes(10), maxFiles: 5 ); $startTime = microtime(true); // Write 1000 log entries for ($i = 0; $i < 1000; $i++) { $record = new LogRecord( message: "Performance test entry #{$i}", context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); } $endTime = microtime(true); $executionTime = $endTime - $startTime; // Should complete in reasonable time (< 5 seconds for 1000 entries) expect($executionTime)->toBeLessThan(5.0); // Verify logs were written and rotated expect(file_exists($logFile))->toBeTrue(); // Count rotation files (should have multiple due to small size limit) $rotationCount = 0; for ($i = 1; $i <= 5; $i++) { if (file_exists($logFile . '.' . $i)) { $rotationCount++; } } expect($rotationCount)->toBeGreaterThan(0); }); it('maintains performance with health checks on large log directories', function () { // Create many small log files for ($i = 0; $i < 50; $i++) { file_put_contents( $this->testDir . "/storage/logs/file{$i}.log", str_repeat('X', 100) ); } $originalDir = getcwd(); chdir($this->testDir); try { $command = new LogHealthCheckCommand(); $output = new ConsoleOutput(); $input = new ConsoleInput(['test', '--detailed'], $output, null); $startTime = microtime(true); ob_start(); $exitCode = $command->execute($input); $healthOutput = ob_get_clean(); $endTime = microtime(true); $executionTime = $endTime - $startTime; // Should complete quickly even with 50 files (< 2 seconds) expect($executionTime)->toBeLessThan(2.0); expect($exitCode)->toBe(ExitCode::SUCCESS); expect($healthOutput)->toContain('Found 50 log file(s)'); } finally { chdir($originalDir); } }); }); describe('Docker Volume Integration (Simulated)', function () { it('works with host-mounted log directories', function () { // Simulate host mount scenario with correct permissions $mountedLogDir = $this->testDir . '/storage/logs'; chmod($mountedLogDir, 0777); $logFile = $mountedLogDir . '/docker-test.log'; $handler = RotatingFileHandler::production($logFile); // Write logs as if running in Docker container for ($i = 0; $i < 5; $i++) { $record = new LogRecord( message: "Docker container log #{$i}", context: LogContext::empty(), level: LogLevel::INFO, timestamp: new DateTimeImmutable() ); $handler->handle($record); } // Verify logs are accessible from "host" (same directory in this test) expect(file_exists($logFile))->toBeTrue(); expect(is_readable($logFile))->toBeTrue(); $content = file_get_contents($logFile); expect($content)->toContain('Docker container log'); }); }); });