createMock(LogHandler::class); $config = new AggregationConfig( flushIntervalSeconds: 60, minOccurrencesForAggregation: 2 ); $handler = new AggregatingLogHandler($inner, $config); // Keine Ausgabe bis flush $inner->expects($this->never())->method('handle'); // Sende 5 identische Messages for ($i = 0; $i < 5; $i++) { $handler->handle($this->createLogRecord('Identical error message', LogLevel::INFO)); } $this->assertEquals(5, $handler->getTotalProcessed()); $this->assertEquals(4, $handler->getTotalAggregated()); // Erste nicht aggregiert } public function test_normalizes_messages_with_variables(): void { $inner = $this->createMock(LogHandler::class); $config = AggregationConfig::production(); $handler = new AggregatingLogHandler($inner, $config); // Diese Messages sollten als identisch erkannt werden $handler->handle($this->createLogRecord('User 123 logged in', LogLevel::INFO)); $handler->handle($this->createLogRecord('User 456 logged in', LogLevel::INFO)); $handler->handle($this->createLogRecord('User 789 logged in', LogLevel::INFO)); // Sollten aggregiert werden $this->assertEquals(1, $handler->getAggregatedCount()); } public function test_does_not_aggregate_critical_levels(): void { $inner = $this->createMock(LogHandler::class); $config = AggregationConfig::production(); // Aggregiert nur INFO, NOTICE, WARNING $handler = new AggregatingLogHandler($inner, $config); // ERROR sollte direkt durchgereicht werden $inner->expects($this->once())->method('handle'); $handler->handle($this->createLogRecord('Critical error', LogLevel::ERROR)); } public function test_flushes_on_interval(): void { $inner = $this->createMock(LogHandler::class); $config = new AggregationConfig( flushIntervalSeconds: 0, // Sofortiger Flush für Test minOccurrencesForAggregation: 2 ); $handler = new AggregatingLogHandler($inner, $config); $handler->handle($this->createLogRecord('Test', LogLevel::INFO)); $handler->handle($this->createLogRecord('Test', LogLevel::INFO)); // Trigger flush durch neuen Handle $inner->expects($this->atLeastOnce())->method('handle'); $handler->handle($this->createLogRecord('Another', LogLevel::INFO)); } public function test_outputs_aggregation_summary(): void { $actualRecord = null; $inner = $this->createMock(LogHandler::class); $inner->method('handle')->willReturnCallback(function($record) use (&$actualRecord) { $actualRecord = $record; }); $config = new AggregationConfig( minOccurrencesForAggregation: 2 ); $handler = new AggregatingLogHandler($inner, $config); // Sende mehrere identische Messages for ($i = 0; $i < 5; $i++) { $handler->handle($this->createLogRecord('Database query failed', LogLevel::WARNING)); } $handler->flush(); $this->assertNotNull($actualRecord); $this->assertStringContainsString('occurred', $actualRecord->message); $this->assertArrayHasKey('aggregated', $actualRecord->context->structured); $this->assertEquals(5, $actualRecord->context->structured['occurrence_count']); } public function test_health_check(): void { $inner = $this->createMock(LogHandler::class); $handler = new AggregatingLogHandler($inner); $health = $handler->check(); $this->assertEquals(\App\Framework\Health\HealthStatus::HEALTHY, $health->status); $this->assertArrayHasKey('total_processed', $health->details); $this->assertArrayHasKey('aggregation_rate', $health->details); } private function createLogRecord( string $message = 'test', LogLevel $level = LogLevel::INFO ): LogRecord { return new LogRecord( level: $level, message: $message, channel: 'test', context: LogContext::empty(), timestamp: new \DateTimeImmutable() ); } }