- 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.
305 lines
9.9 KiB
PHP
305 lines
9.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Logging\Handlers\WebHandler;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use App\Framework\Logging\ValueObjects\LogContext;
|
|
|
|
describe('WebHandler', function () {
|
|
beforeEach(function () {
|
|
$this->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();
|
|
});
|
|
});
|
|
});
|