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>
This commit is contained in:
1 parent
339a48c810
commit
a6a334fe68
15 files changed
+973
-42
No files matched your search
@@ -2,14 +2,24 @@
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Content\DocumentValidator;
|
||||
use App\Content\MediaLibrary;
|
||||
use App\Content\MenuRepository;
|
||||
use App\Content\PageRepository;
|
||||
use App\Content\PartialRepository;
|
||||
use App\Content\SiteSettings;
|
||||
use App\Infrastructure\ConnectionFactory;
|
||||
use App\Ops\ArchiveUpdate;
|
||||
use App\Ops\Backup;
|
||||
use App\Ops\CommandRunner;
|
||||
use App\Ops\Download;
|
||||
use App\Ops\Installation;
|
||||
use App\Ops\Updater;
|
||||
use App\Security\CmsUser;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
|
||||
|
||||
/**
|
||||
* Das Update über das Releasearchiv - der Weg für Installationen ohne Git.
|
||||
@@ -272,6 +282,77 @@ final class ArchiveUpdateTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testTheProgressFileFollowsTheUpdateAndAFinishedOneSaysSo(): void
|
||||
{
|
||||
// Die Systemseite fragt diesen Fortschritt ab, während eine andere Anfrage das Update
|
||||
// ausführt (ADR 113) - Phase, Meldung, von welcher auf welche Fassung.
|
||||
$this->releases->release(self::BASE, self::API, '1.1.0', $this->archive('1.1.0'));
|
||||
$updater = $this->updater();
|
||||
self::assertNull($updater->status());
|
||||
self::assertSame('Fortschritt live.', $updater->latest()['notes'] ?? null, 'Die Hinweise der Releaseseite gehören zur neuen Fassung.');
|
||||
|
||||
$ergebnis = $updater->run();
|
||||
self::assertTrue($ergebnis['ok'], $this->explain($ergebnis));
|
||||
$status = $updater->status();
|
||||
self::assertSame(['migrieren', true, '1.0.0', '1.1.0'], [$status['phase'], $status['laeuft'], $status['von'], $status['auf']]);
|
||||
|
||||
$fertig = $this->updater()->finish($this->installation());
|
||||
self::assertTrue($fertig['ok'], $this->explain($fertig));
|
||||
self::assertSame(['fertig', false], [$this->updater()->status()['phase'], $this->updater()->status()['laeuft']]);
|
||||
self::assertFileDoesNotExist($this->root.'/'.Updater::MAINTENANCE_FILE);
|
||||
}
|
||||
|
||||
public function testANewVersionThatDoesNotRunIsSwitchedBackCompletely(): void
|
||||
{
|
||||
// Wie bei reiseSoft: Läuft die neue Fassung nach den Migrationen nicht, kommt die alte
|
||||
// zurück - Code, public/ und Datenbank -, und erst dann geht die Wartungsseite aus.
|
||||
$this->releases->release(self::BASE, self::API, '1.1.0', $this->archive('1.1.0'));
|
||||
$this->db->insert('website', ['id' => 'default', 'name' => 'Vorher', 'default_locale' => 'de', 'theme' => '{}']);
|
||||
$ergebnis = $this->updater()->run();
|
||||
self::assertTrue($ergebnis['ok'], $this->explain($ergebnis));
|
||||
self::assertSame('1.1.0', trim((string) file_get_contents($this->root.'/VERSION')));
|
||||
// Etwas, das die neue Fassung in der Datenbank anrichtet, bevor die Prüfung scheitert.
|
||||
$this->db->update('website', ['name' => 'Nachher'], ['id' => 'default']);
|
||||
|
||||
$ende = $this->updater()->finish($this->installation(), static fn (): string => 'Die Startseite antwortet mit 500.');
|
||||
|
||||
self::assertFalse($ende['ok']);
|
||||
self::assertTrue($ende['zurueckgeschaltet'] ?? false, $this->explain($ende));
|
||||
self::assertSame('1.0.0', trim((string) file_get_contents($this->root.'/VERSION')));
|
||||
self::assertFileExists($this->root.'/src/Alt.php');
|
||||
self::assertFileDoesNotExist($this->root.'/src/Neu.php');
|
||||
self::assertSame("<?php // alt\n", file_get_contents($this->root.'/public/index.php'), 'Auch public/ kommt zurück.');
|
||||
self::assertSame('body{color:alt}', file_get_contents($this->root.'/public/assets/site.css'));
|
||||
self::assertSame('SCHRIFT', file_get_contents($this->root.'/public/assets/fonts/eigene.woff2'));
|
||||
self::assertFileDoesNotExist($this->root.'/public/assets/fonts/andere.woff2');
|
||||
$this->db->close();
|
||||
$this->db = ConnectionFactory::create('sqlite:///'.$this->root.'/var/cms.db');
|
||||
self::assertSame('Vorher', $this->db->fetchOne("SELECT name FROM website WHERE id = 'default'"), 'Die Datenbank kommt aus der Sicherung vor dem Update.');
|
||||
self::assertFileDoesNotExist($this->root.'/'.Updater::MAINTENANCE_FILE, 'Zurückgeschaltet heißt: die Website läuft wieder.');
|
||||
self::assertNull($this->updater()->pending());
|
||||
$status = $this->updater()->status();
|
||||
self::assertSame(['zurueckgerollt', 'pruefen', false], [$status['phase'], $status['bei'], $status['laeuft']], 'Gescheitert ist die Prüfung - genau dieser Schritt wird markiert.');
|
||||
self::assertStringContainsString('Zurückgeschaltet auf 1.0.0', $status['meldung']);
|
||||
}
|
||||
|
||||
private function installation(): Installation
|
||||
{
|
||||
$settings = new SiteSettings($this->db, new MediaLibrary($this->db, $this->root.'/var/media'));
|
||||
$validator = new DocumentValidator();
|
||||
|
||||
return new Installation(
|
||||
$this->db,
|
||||
new PageRepository($this->db, $validator, $settings),
|
||||
new PartialRepository($this->db, $validator),
|
||||
new MenuRepository($this->db, $settings),
|
||||
$settings,
|
||||
new UserPasswordHasher(new PasswordHasherFactory([CmsUser::class => ['algorithm' => 'auto']])),
|
||||
$this->root,
|
||||
$this->root.'/var/media',
|
||||
'test-secret',
|
||||
);
|
||||
}
|
||||
|
||||
private function updater(): ArchiveUpdate
|
||||
{
|
||||
$runner = new CommandRunner();
|
||||
@@ -303,7 +384,8 @@ final class ArchiveUpdateTest extends TestCase
|
||||
'bin/console' => "<?php // neu\n",
|
||||
'config/services.yaml' => "services:\n",
|
||||
'templates/base.html.twig' => 'neu',
|
||||
'migrations.php' => "<?php return ['neu'];\n",
|
||||
'migrations.php' => "<?php return ['table_storage' => ['table_name' => 'migration_versions'], 'migrations_paths' => ['DoctrineMigrations' => __DIR__.'/migrations'], 'all_or_nothing' => false];\n",
|
||||
'migrations/.keep' => '',
|
||||
'cli-config.php' => "<?php // neu\n",
|
||||
'composer.json' => '{"name":"neu"}',
|
||||
'composer.lock' => '{"packages":["neu"]}',
|
||||
|
||||
Reference in new issue
Block a user