scripts/ is on the exclusion list of build-release.php, so an installation that runs from the release archive - the usual case on shared hosting - would never have seen scripts/repair-doubled-entities.php. The 1.20.1 notes told everyone to run it, which was only true for a Git working copy. cms:repair:entities does the same work and ships with the application: a dry run by default, --schreiben to apply, each change validated the way saving validates it (pages with the heading rule, areas without, as in PartialRepository). The script is gone; there is one way to do this now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
108 lines
4.6 KiB
PHP
108 lines
4.6 KiB
PHP
<?php
|
||
|
||
namespace App\Tests;
|
||
|
||
use App\Command\RepairEntitiesCommand;
|
||
use App\Content\DocumentValidator;
|
||
use App\Infrastructure\ConnectionFactory;
|
||
use Doctrine\DBAL\Connection;
|
||
use PHPUnit\Framework\TestCase;
|
||
use Symfony\Component\Console\Tester\CommandTester;
|
||
|
||
/**
|
||
* `cms:repair:entities` räumt auf, was das doppelte Kodieren vor 1.20.1 hinterlassen hat.
|
||
*
|
||
* Geprüft wird beides: dass der Trockenlauf nichts anfasst, und dass das Schreiben genau die
|
||
* verschachtelten Entities entschachtelt – ein einzelnes `&` ist richtig und muss bleiben.
|
||
*/
|
||
final class RepairEntitiesCommandTest extends TestCase
|
||
{
|
||
private string $path;
|
||
private Connection $db;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
$this->path = tempnam(sys_get_temp_dir(), 'kant-repair-');
|
||
$this->db = ConnectionFactory::create('sqlite:///'.str_replace('\\', '/', $this->path));
|
||
TestSchema::build($this->db);
|
||
$this->db->insert('website', ['id' => 'default', 'name' => 'KantSites', 'default_locale' => 'de', 'theme' => '{}']);
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
$this->db->close();
|
||
gc_collect_cycles();
|
||
@unlink($this->path);
|
||
}
|
||
|
||
/** Legt eine Seite mit einem Text an und gibt die Kennung der Fassung zurück. */
|
||
private function seite(string $text): string
|
||
{
|
||
$dokument = [
|
||
'schemaVersion' => 17,
|
||
'title' => 'Start',
|
||
'description' => 'Eine Seite zum Reparieren.',
|
||
'blocks' => [['id' => 's1', 'type' => 'section', 'children' => [
|
||
['id' => 'h1', 'type' => 'heading', 'props' => ['text' => 'Start', 'level' => 1]],
|
||
['id' => 't1', 'type' => 'text', 'props' => ['text' => $text]],
|
||
]]],
|
||
];
|
||
// Erst die Seite, dann ihre Fassung: die Fassung zeigt per Fremdschluessel auf die Seite.
|
||
$revision = bin2hex(random_bytes(16));
|
||
$this->db->insert('page', ['id' => 'page-1', 'slug' => 'home', 'path' => 'home', 'type' => 'page', 'locale' => 'de', 'website_id' => 'default', 'version' => 1, 'created_at' => gmdate('Y-m-d H:i:s')]);
|
||
$this->db->insert('revision', ['id' => $revision, 'page_id' => 'page-1', 'document' => json_encode($dokument, JSON_THROW_ON_ERROR), 'created_at' => gmdate('Y-m-d H:i:s')]);
|
||
$this->db->update('page', ['published_revision_id' => $revision, 'draft_revision_id' => $revision], ['id' => 'page-1']);
|
||
|
||
return $revision;
|
||
}
|
||
|
||
private function text(string $revision): string
|
||
{
|
||
$dokument = json_decode((string) $this->db->fetchOne('SELECT document FROM revision WHERE id = ?', [$revision]), true, 512, JSON_THROW_ON_ERROR);
|
||
|
||
return $dokument['blocks'][0]['children'][1]['props']['text'];
|
||
}
|
||
|
||
private function tester(): CommandTester
|
||
{
|
||
return new CommandTester(new RepairEntitiesCommand($this->db, new DocumentValidator()));
|
||
}
|
||
|
||
public function testTheDryRunReportsWithoutChangingAnything(): void
|
||
{
|
||
$revision = $this->seite('Freudenstadt &amp; Umgebung');
|
||
$tester = $this->tester();
|
||
$tester->execute([]);
|
||
|
||
self::assertStringContainsString('1 Stelle', $tester->getDisplay());
|
||
self::assertSame('Freudenstadt &amp; Umgebung', $this->text($revision), 'Ohne --schreiben bleibt alles stehen.');
|
||
}
|
||
|
||
public function testWritingUnnestsEveryLevelAndLeavesASingleEntityAlone(): void
|
||
{
|
||
// Drei Stufen, wie sie auf einer Seite standen, die dreimal gespeichert wurde.
|
||
$revision = $this->seite('Projekte &amp;amp; Lösungen, „Zitat&quot;, und ein richtiges & dazu');
|
||
$tester = $this->tester();
|
||
$tester->execute(['--schreiben' => true]);
|
||
|
||
self::assertStringContainsString('repariert', $tester->getDisplay());
|
||
self::assertSame('Projekte & Lösungen, „Zitat", und ein richtiges & dazu', $this->text($revision));
|
||
|
||
// Ein zweiter Lauf findet nichts mehr - und macht aus dem richtigen & kein nacktes &.
|
||
$zweiter = $this->tester();
|
||
$zweiter->execute(['--schreiben' => true]);
|
||
self::assertStringContainsString('Nichts zu reparieren', $zweiter->getDisplay());
|
||
self::assertSame('Projekte & Lösungen, „Zitat", und ein richtiges & dazu', $this->text($revision));
|
||
}
|
||
|
||
public function testAnUntouchedInstallationSaysSoAndKeepsQuiet(): void
|
||
{
|
||
$revision = $this->seite('Alles gut & sauber');
|
||
$tester = $this->tester();
|
||
$tester->execute(['--schreiben' => true]);
|
||
|
||
self::assertStringContainsString('Nichts zu reparieren', $tester->getDisplay());
|
||
self::assertSame('Alles gut & sauber', $this->text($revision));
|
||
}
|
||
}
|