root = str_replace('\\', '/', sys_get_temp_dir()).'/kant-notfall-'.bin2hex(random_bytes(6)); mkdir($this->root, 0o775, true); Notfall::einschalten($this->root); } protected function tearDown(): void { foreach (glob($this->root.'/var/log/*') ?: [] as $datei) { unlink($datei); } @rmdir($this->root.'/var/log'); @rmdir($this->root.'/var'); @rmdir($this->root); } private function ereignis(\Throwable $fehler): ExceptionEvent { $kernel = $this->createStub(HttpKernelInterface::class); return new ExceptionEvent($kernel, Request::create('/leistungen'), HttpKernelInterface::MAIN_REQUEST, $fehler); } public function testAServerErrorBecomesAMaintenancePageAndALogLine(): void { $ursache = new \PDOException('file is not a database'); $ereignis = $this->ereignis(new \RuntimeException('Abfrage fehlgeschlagen', 0, $ursache)); (new ErrorLogSubscriber(debug: false))->onException($ereignis); $antwort = $ereignis->getResponse(); self::assertNotNull($antwort); self::assertSame(503, $antwort->getStatusCode(), 'Für Suchmaschinen: gleich wieder da, nicht kaputt.'); self::assertSame('300', $antwort->headers->get('Retry-After')); self::assertStringContainsString('Die Website ist gleich wieder da.', (string) $antwort->getContent()); self::assertStringNotContainsString('database', (string) $antwort->getContent(), 'Einzelheiten gehören ins Protokoll, nicht auf die Seite.'); $protokoll = (string) file_get_contents($this->root.'/'.Notfall::PROTOKOLL); self::assertStringContainsString('GET /leistungen – RuntimeException: Abfrage fehlgeschlagen', $protokoll); self::assertStringContainsString('Ursache: PDOException: file is not a database', $protokoll, 'Die eigentliche Ursache steckt eine Ebene tiefer.'); } public function testANotFoundIsNoEmergencyAndDevelopmentKeepsItsDetailedPage(): void { $nichtGefunden = $this->ereignis(new NotFoundHttpException('Gibt es nicht')); (new ErrorLogSubscriber(debug: false))->onException($nichtGefunden); self::assertNull($nichtGefunden->getResponse()); self::assertFileDoesNotExist($this->root.'/'.Notfall::PROTOKOLL); // „Zugriff verweigert“ führt zur Anmeldung - die Sicherheitsschicht entscheidet, nicht der Notfall. $verweigert = $this->ereignis(new \Symfony\Component\Security\Core\Exception\AccessDeniedException()); (new ErrorLogSubscriber(debug: false))->onException($verweigert); self::assertNull($verweigert->getResponse()); self::assertFileDoesNotExist($this->root.'/'.Notfall::PROTOKOLL); $entwicklung = $this->ereignis(new \LogicException('Fehler beim Entwickeln')); (new ErrorLogSubscriber(debug: true))->onException($entwicklung); self::assertNull($entwicklung->getResponse(), 'Im Entwicklungsmodus bleibt die ausführliche Fehlerseite.'); self::assertStringContainsString('LogicException: Fehler beim Entwickeln', (string) file_get_contents($this->root.'/'.Notfall::PROTOKOLL)); } public function testTheFrontControllerLoadsTheEmergencyLayerBeforeAnythingElse(): void { $einstieg = (string) file_get_contents(dirname(__DIR__).'/public/index.php'); self::assertLessThan(strpos($einstieg, 'config/bootstrap.php'), strpos($einstieg, 'Notfall::einschalten'), 'Scheitert schon der Start, muss die Notfallschicht stehen.'); self::assertStringContainsString('catch (Throwable $fehler)', $einstieg); } }