timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin')); $this->context = LogContext::withData(['test' => 'data']); }); describe('constructor', function () { it('accepts custom minimum level', function () { $handler = new WebHandler( minLevel: LogLevel::WARNING, debugOnly: false ); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('uses default values when not specified', function () { $handler = new WebHandler(); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('accepts LogLevel enum as minLevel', function () { $handler = new WebHandler(minLevel: LogLevel::ERROR); expect($handler)->toBeInstanceOf(WebHandler::class); }); }); describe('isHandling()', function () { it('returns false in CLI mode (test environment)', function () { // Note: Tests run in CLI mode, so WebHandler always returns false expect(PHP_SAPI)->toBe('cli'); $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // In CLI mode, handler should not handle any records expect($handler->isHandling($record))->toBeFalse(); }); it('respects minimum level when not in CLI', function () { // This test documents behavior, even though it won't execute in web mode during tests $handler = new WebHandler( minLevel: LogLevel::ERROR, debugOnly: false ); $infoRecord = new LogRecord( message: 'Info message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); $errorRecord = new LogRecord( message: 'Error message', context: LogContext::empty(), level: LogLevel::ERROR, timestamp: $this->timestamp ); // In CLI, both return false (PHP_SAPI check comes first) expect($handler->isHandling($infoRecord))->toBeFalse(); expect($handler->isHandling($errorRecord))->toBeFalse(); }); it('checks debug mode when debugOnly is true', function () { // Save original APP_DEBUG value $originalDebug = getenv('APP_DEBUG'); // Test with debug enabled putenv('APP_DEBUG=true'); $handler = new WebHandler(debugOnly: true); $record = new LogRecord( message: 'Test message', context: $this->context, level: LogLevel::INFO, timestamp: $this->timestamp ); // Still false because we're in CLI expect($handler->isHandling($record))->toBeFalse(); // Restore original value if ($originalDebug !== false) { putenv("APP_DEBUG={$originalDebug}"); } else { putenv('APP_DEBUG'); } }); }); describe('handle() - format testing', function () { it('formats basic log message', function () { $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Test log message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); // In CLI mode, handler would not normally be called, but we can test the method directly // The handle() method should not throw errors even in CLI $handler->handle($record); expect(true)->toBeTrue(); }); it('includes request_id when present in extras', function () { $handler = new WebHandler(debugOnly: false); $record = (new LogRecord( message: 'Test message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ))->addExtra('request_id', 'req-123'); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); it('includes channel when present', function () { $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Test message', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp, channel: 'security' ); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); it('includes context data', function () { $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Test message', context: LogContext::withData(['user_id' => 123, 'action' => 'login']), level: LogLevel::INFO, timestamp: $this->timestamp ); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); it('handles all log levels', function () { $handler = new WebHandler( minLevel: LogLevel::DEBUG, debugOnly: false ); $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: "{$level->getName()} message", context: LogContext::empty(), level: $level, timestamp: $this->timestamp ); // Should not throw exception $handler->handle($record); } expect(true)->toBeTrue(); }); it('handles empty context', function () { $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Message without context', context: LogContext::empty(), level: LogLevel::INFO, timestamp: $this->timestamp ); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); it('handles complex context with special characters', function () { $handler = new WebHandler(debugOnly: false); $record = new LogRecord( message: 'Test message', context: LogContext::withData([ 'email' => 'test@example.com', 'path' => '/api/users/123', 'special' => "line1\nline2\ttabbed", 'unicode' => '日本語', ]), level: LogLevel::INFO, timestamp: $this->timestamp ); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); it('handles record with both request_id and channel', function () { $handler = new WebHandler(debugOnly: false); $record = (new LogRecord( message: 'Complete log', context: LogContext::withData(['key' => 'value']), level: LogLevel::WARNING, timestamp: $this->timestamp, channel: 'api' ))->addExtra('request_id', 'req-456'); // Should not throw exception $handler->handle($record); expect(true)->toBeTrue(); }); }); describe('debug mode behavior', function () { it('creates handler with debugOnly enabled by default', function () { $handler = new WebHandler(); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('creates handler with debugOnly disabled', function () { $handler = new WebHandler(debugOnly: false); expect($handler)->toBeInstanceOf(WebHandler::class); }); }); describe('minimum level configuration', function () { it('accepts DEBUG level', function () { $handler = new WebHandler(minLevel: LogLevel::DEBUG); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('accepts WARNING level', function () { $handler = new WebHandler(minLevel: LogLevel::WARNING); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('accepts ERROR level', function () { $handler = new WebHandler(minLevel: LogLevel::ERROR); expect($handler)->toBeInstanceOf(WebHandler::class); }); it('accepts CRITICAL level', function () { $handler = new WebHandler(minLevel: LogLevel::CRITICAL); expect($handler)->toBeInstanceOf(WebHandler::class); }); }); describe('readonly behavior', function () { it('is a readonly class', function () { $reflection = new ReflectionClass(WebHandler::class); expect($reflection->isReadOnly())->toBeTrue(); }); }); });