Files
KantSites/tests/RepairEntitiesCommandTest.php
T
kantarciandClaude Opus 5 f4ec0d9877 Make the entity repair a console command, not a script
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>
2026-09-16 09:05:11 +02:00

108 lines
4.6 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\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 `&amp;` 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;amp; Umgebung');
$tester = $this->tester();
$tester->execute([]);
self::assertStringContainsString('1 Stelle', $tester->getDisplay());
self::assertSame('Freudenstadt &amp;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;amp; Lösungen, „Zitat&amp;quot;, und ein richtiges &amp; dazu');
$tester = $this->tester();
$tester->execute(['--schreiben' => true]);
self::assertStringContainsString('repariert', $tester->getDisplay());
self::assertSame('Projekte &amp; Lösungen, „Zitat&quot;, und ein richtiges &amp; dazu', $this->text($revision));
// Ein zweiter Lauf findet nichts mehr - und macht aus dem richtigen &amp; kein nacktes &.
$zweiter = $this->tester();
$zweiter->execute(['--schreiben' => true]);
self::assertStringContainsString('Nichts zu reparieren', $zweiter->getDisplay());
self::assertSame('Projekte &amp; Lösungen, „Zitat&quot;, und ein richtiges &amp; dazu', $this->text($revision));
}
public function testAnUntouchedInstallationSaysSoAndKeepsQuiet(): void
{
$revision = $this->seite('Alles gut &amp; sauber');
$tester = $this->tester();
$tester->execute(['--schreiben' => true]);
self::assertStringContainsString('Nichts zu reparieren', $tester->getDisplay());
self::assertSame('Alles gut &amp; sauber', $this->text($revision));
}
}