- Move 12 markdown files from root to docs/ subdirectories - Organize documentation by category: • docs/troubleshooting/ (1 file) - Technical troubleshooting guides • docs/deployment/ (4 files) - Deployment and security documentation • docs/guides/ (3 files) - Feature-specific guides • docs/planning/ (4 files) - Planning and improvement proposals Root directory cleanup: - Reduced from 16 to 4 markdown files in root - Only essential project files remain: • CLAUDE.md (AI instructions) • README.md (Main project readme) • CLEANUP_PLAN.md (Current cleanup plan) • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements) This improves: ✅ Documentation discoverability ✅ Logical organization by purpose ✅ Clean root directory ✅ Better maintainability
351 lines
10 KiB
PHP
351 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Framework\Logging\Handlers;
|
|
|
|
use App\Framework\Core\PathProvider;
|
|
use App\Framework\Logging\Handlers\MultiFileHandler;
|
|
use App\Framework\Logging\LogConfig;
|
|
use App\Framework\Logging\LogLevel;
|
|
use App\Framework\Logging\LogRecord;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
// Test-spezifische LogConfig Implementation
|
|
final class TestLogConfig
|
|
{
|
|
public function __construct(private string $testDir)
|
|
{
|
|
}
|
|
|
|
public function getLogPath(string $type): string
|
|
{
|
|
return match ($type) {
|
|
'security' => $this->testDir . '/security/security.log',
|
|
'cache' => $this->testDir . '/debug/cache.log',
|
|
'database' => $this->testDir . '/debug/database.log',
|
|
'framework' => $this->testDir . '/debug/framework.log',
|
|
'error' => $this->testDir . '/app/error.log',
|
|
default => $this->testDir . '/app/app.log'
|
|
};
|
|
}
|
|
|
|
public function getAllLogPaths(): array
|
|
{
|
|
return [
|
|
'security' => $this->getLogPath('security'),
|
|
'cache' => $this->getLogPath('cache'),
|
|
'database' => $this->getLogPath('database'),
|
|
'framework' => $this->getLogPath('framework'),
|
|
'error' => $this->getLogPath('error'),
|
|
'app' => $this->getLogPath('app'),
|
|
];
|
|
}
|
|
|
|
public function getBaseLogPath(): string
|
|
{
|
|
return $this->testDir;
|
|
}
|
|
|
|
public function ensureLogDirectoriesExist(): void
|
|
{
|
|
// No-op for testing
|
|
}
|
|
}
|
|
|
|
// Test-spezifische PathProvider Implementation
|
|
final class TestPathProvider
|
|
{
|
|
public function resolvePath(string $path): string
|
|
{
|
|
return $path; // Return path as-is for testing
|
|
}
|
|
}
|
|
|
|
final class MultiFileHandlerTest extends TestCase
|
|
{
|
|
private string $testLogDir;
|
|
|
|
private TestLogConfig $mockLogConfig;
|
|
|
|
private TestPathProvider $mockPathProvider;
|
|
|
|
private MultiFileHandler $handler;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
// Temporäres Test-Verzeichnis erstellen
|
|
$this->testLogDir = sys_get_temp_dir() . '/test_logs_' . uniqid();
|
|
mkdir($this->testLogDir, 0755, true);
|
|
|
|
// Test-spezifische LogConfig mit direkten Pfaden
|
|
$this->mockLogConfig = $this->createLogConfigWithTestPaths();
|
|
$this->mockPathProvider = new TestPathProvider();
|
|
|
|
$this->handler = new MultiFileHandler(
|
|
$this->mockLogConfig,
|
|
$this->mockPathProvider,
|
|
LogLevel::DEBUG,
|
|
'[{timestamp}] [{level_name}] [{channel}] {message}'
|
|
);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
// Cleanup: Test-Verzeichnis löschen
|
|
if (is_dir($this->testLogDir)) {
|
|
$this->deleteDirectory($this->testLogDir);
|
|
}
|
|
}
|
|
|
|
private function deleteDirectory(string $dir): void
|
|
{
|
|
if (! is_dir($dir)) {
|
|
return;
|
|
}
|
|
|
|
$files = array_diff(scandir($dir), ['.', '..']);
|
|
foreach ($files as $file) {
|
|
$path = $dir . '/' . $file;
|
|
is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
|
|
}
|
|
rmdir($dir);
|
|
}
|
|
|
|
public function test_is_handling_respects_min_level(): void
|
|
{
|
|
$debugRecord = new LogRecord('Debug message', [], LogLevel::DEBUG, new DateTimeImmutable());
|
|
$errorRecord = new LogRecord('Error message', [], LogLevel::ERROR, new DateTimeImmutable());
|
|
|
|
expect($this->handler->isHandling($debugRecord))->toBeTrue();
|
|
expect($this->handler->isHandling($errorRecord))->toBeTrue();
|
|
|
|
// Handler mit höherem Min-Level
|
|
$errorHandler = new MultiFileHandler(
|
|
$this->mockLogConfig,
|
|
$this->mockPathProvider,
|
|
LogLevel::ERROR
|
|
);
|
|
|
|
expect($errorHandler->isHandling($debugRecord))->toBeFalse();
|
|
expect($errorHandler->isHandling($errorRecord))->toBeTrue();
|
|
}
|
|
|
|
public function test_handles_security_channel_logs(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Security alert',
|
|
['ip' => '192.168.1.1'],
|
|
LogLevel::WARNING,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'security'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/security/security.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [WARNING] [security] Security alert');
|
|
expect($content)->toContain('{"ip":"192.168.1.1"}');
|
|
}
|
|
|
|
public function test_handles_cache_channel_logs(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Cache miss',
|
|
['key' => 'user_123'],
|
|
LogLevel::DEBUG,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'cache'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/debug/cache.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [DEBUG] [cache] Cache miss');
|
|
expect($content)->toContain('{"key":"user_123"}');
|
|
}
|
|
|
|
public function test_handles_database_channel_logs(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Query failed',
|
|
['query' => 'SELECT * FROM users'],
|
|
LogLevel::ERROR,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'database'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/debug/database.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [ERROR] [database] Query failed');
|
|
expect($content)->toContain('{"query":"SELECT * FROM users"}');
|
|
}
|
|
|
|
public function test_handles_framework_channel_logs(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Route registered',
|
|
['route' => '/api/users'],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'framework'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/debug/framework.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [INFO] [framework] Route registered');
|
|
expect($content)->toContain('{"route":"/api/users"}');
|
|
}
|
|
|
|
public function test_handles_error_channel_logs(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'System failure',
|
|
['component' => 'payment'],
|
|
LogLevel::CRITICAL,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'error'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/app/error.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [CRITICAL] [error] System failure');
|
|
expect($content)->toContain('{"component":"payment"}');
|
|
}
|
|
|
|
public function test_handles_app_channel_fallback(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Default message',
|
|
[],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'unknown_channel'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/app/app.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [INFO] [unknown_channel] Default message');
|
|
}
|
|
|
|
public function test_handles_null_channel(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'No channel message',
|
|
[],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
null
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/app/app.log';
|
|
expect(file_exists($logFile))->toBeTrue();
|
|
|
|
$content = file_get_contents($logFile);
|
|
expect($content)->toContain('[2023-12-25 10:30:45] [INFO] [app] No channel message');
|
|
}
|
|
|
|
public function test_creates_directories_automatically(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Test message',
|
|
[],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable(),
|
|
'security'
|
|
);
|
|
|
|
// Verzeichnis sollte nicht existieren
|
|
expect(is_dir($this->testLogDir . '/security'))->toBeFalse();
|
|
|
|
$this->handler->handle($record);
|
|
|
|
// Verzeichnis sollte automatisch erstellt worden sein
|
|
expect(is_dir($this->testLogDir . '/security'))->toBeTrue();
|
|
expect(file_exists($this->testLogDir . '/security/security.log'))->toBeTrue();
|
|
}
|
|
|
|
public function test_appends_to_existing_files(): void
|
|
{
|
|
$record1 = new LogRecord(
|
|
'First message',
|
|
[],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'security'
|
|
);
|
|
|
|
$record2 = new LogRecord(
|
|
'Second message',
|
|
[],
|
|
LogLevel::WARNING,
|
|
new DateTimeImmutable('2023-12-25 10:31:45'),
|
|
'security'
|
|
);
|
|
|
|
$this->handler->handle($record1);
|
|
$this->handler->handle($record2);
|
|
|
|
$logFile = $this->testLogDir . '/security/security.log';
|
|
$content = file_get_contents($logFile);
|
|
|
|
expect($content)->toContain('First message');
|
|
expect($content)->toContain('Second message');
|
|
|
|
// Zwei Zeilen sollten vorhanden sein
|
|
$lines = explode("\n", trim($content));
|
|
expect($lines)->toHaveCount(2);
|
|
}
|
|
|
|
public function test_handles_empty_context(): void
|
|
{
|
|
$record = new LogRecord(
|
|
'Message without context',
|
|
[],
|
|
LogLevel::INFO,
|
|
new DateTimeImmutable('2023-12-25 10:30:45'),
|
|
'security'
|
|
);
|
|
|
|
$this->handler->handle($record);
|
|
|
|
$logFile = $this->testLogDir . '/security/security.log';
|
|
$content = file_get_contents($logFile);
|
|
|
|
// Sollte keine JSON-Context-Info enthalten
|
|
expect($content)->toContain('Message without context');
|
|
expect($content)->not->toContain('{}');
|
|
expect($content)->not->toContain('[]');
|
|
}
|
|
|
|
private function createLogConfigWithTestPaths(): TestLogConfig
|
|
{
|
|
return new TestLogConfig($this->testLogDir);
|
|
}
|
|
}
|