Files
KantSites/tests/NotfallTest.php
T
kantarciandClaude Opus 5.5 a6a334fe68 Show a maintenance page instead of a blank 500 and update in one click
Emergency layer logs fatal errors to var/log/notfall.log and answers 503.
The system page checks for a new release on load, shows its notes, runs
backup, download, switch, migration and health check in one flow with live
progress, and switches back automatically when the new version fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:33:53 +02:00

92 lines
4.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Tests;
use App\EventSubscriber\ErrorLogSubscriber;
use App\Ops\Notfall;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
/**
* Kein leerer 500er mehr (ADR 113).
*
* Kantarci Reisen am 09.10.2026: Datenbank per FTP ersetzt, danach eine weiße Seite auf jeder
* Adresse und kein Hinweis darauf, warum. Seitdem sehen Besucher eine Wartungsseite mit 503, und
* der Grund steht in var/log/notfall.log.
*/
final class NotfallTest extends TestCase
{
private string $root;
protected function setUp(): void
{
$this->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);
}
}