docs: consolidate documentation into organized structure

- 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
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,350 @@
<?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);
}
}