test: shared Divi fixtures in tests/Support (T1)

- DiviFixtures: markup(), givenPost(), schema(), designSystem(), catalog(),
  dynamicContentRegistry(), maliciousRefs() - moved verbatim out of
  DiviPageLoaderTest, DesignSystemTest, ModuleCatalogTest, DynamicContentMapTest
  and VariableRefTest (its malicious() provider now returns maliciousRefs()).
- ComposeFixture moves to tests/Support.
- DiviWriterFixture: modules(), designReader(), composition(), a recording saver(),
  log() and writer(&$saved, &$failures, unfiltered, loader) for the five tests that
  wired the Divi write stack by hand.
No test cases or assertions changed (1397 / 5834).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 17:47:11 +02:00
co-authored by Claude Opus 5.5
parent d2cc8497c9
commit 3d96a41366
24 changed files with 489 additions and 404 deletions
@@ -6,7 +6,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose;
use InvalidArgumentException;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
use LIT\MCP\Tests\Unit\Integrations\Divi\Design\DesignSystemTest;
use LIT\MCP\Tests\Support\ComposeFixture;
use LIT\MCP\Tests\Support\DiviFixtures;
use PHPUnit\Framework\TestCase;
/** Ruling S1/S2/S12/S21/S22: every $variable(…)$ value is parsed, strictly checked, cleaned and re-serialized. */
@@ -15,7 +16,7 @@ final class VariableRefTest extends TestCase
/** @param array<string, array<string, string>>|null $dynamicContent */
private static function ref(?array $dynamicContent = null): VariableRef
{
return new VariableRef(DesignSystemTest::sample(), $dynamicContent);
return new VariableRef(DiviFixtures::designSystem(), $dynamicContent);
}
private static function content(string $name, string $settings): string
@@ -125,65 +126,7 @@ final class VariableRefTest extends TestCase
*/
public static function malicious(): array
{
$end = self::escape('0029') . '$';
return [
'escaped end marker in before' => [self::content('post_title', '{"before":"x' . $end . ' <b>y</b>","after":""}'), 'allein im Wert'],
'escaped end marker in after' => [self::content('post_title', '{"before":"","after":"' . $end . '"}'), 'allein im Wert'],
'escaped end marker in date format' => [self::content('post_date', '{"custom_date_format":"d' . $end . '"}'), 'allein im Wert'],
'escaped end marker in name' => [self::content('post_title' . $end, '{}'), 'Unbekannter Name'],
'select with markup' => [self::content('post_date', '{"date_format":"<b>d</b>"}'), 'date_format'],
'type evil' => ['$variable({"type":"evil","value":{"name":"gcid-a1b2","settings":{}}})$', 'Typ'],
'unknown gcid' => ['$variable({"type":"color","value":{"name":"gcid-nope","settings":{}}})$', 'gcid-nope'],
'unknown gvid' => [self::content('gvid-nope', '{}'), 'gvid-nope'],
'content gvid as color' => ['$variable({"type":"color","value":{"name":"gvid-s1","settings":{}}})$', 'gvid-s1'],
'color gcid as content' => [self::content('gcid-a1b2', '{}'), 'gcid-a1b2'],
'name with paren' => ['$variable({"type":"color","value":{"name":"gcid-a1b2)","settings":{}}})$', 'Name'],
'name with style breakout' => [
'$variable({"type":"color","value":{"name":"gcid-a1b2;}</style><script>alert(1)</script>","settings":{}}})$',
'Name',
],
'settings on a global variable' => [self::content('gvid-s1', '{"before":"<b>x</b>"}'), 'before'],
'settings on a theme font' => [self::content('--et_global_heading_font', '{"after":""}'), 'after'],
'unknown settings key' => [self::content('post_title', '{"before":"","foo":"1"}'), 'foo'],
'setting the option lacks' => [self::content('post_featured_image', '{"before":"x"}'), 'before'],
'enable_html' => [self::content('custom_meta_telefon', '{"before":"","after":"","enable_html":"on"}'), 'enable_html'],
'color setting not numeric' => [
'$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{"opacity":"1;}</style>"}}})$',
'opacity',
],
'protected meta key' => [self::content('custom_meta__edit_lock', '{}'), 'custom_meta__edit_lock'],
'doubled meta prefix' => [
self::content('custom_meta_custom_meta__edit_lock', '{}'),
'custom_meta_custom_meta__edit_lock',
],
'loop content outside a loop' => [self::content('loop_post_title', '{}'), 'loop_post_title'],
'unknown dynamic content' => [self::content('post_meta_key', '{}'), 'post_meta_key'],
'extra top-level key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{}},"x":1})$', 'x'],
'extra value key' => ['$variable({"type":"color","value":{"name":"gcid-a1b2","settings":{},"x":1}})$', 'x'],
'greedy smuggling' => ['$variable({"a":"})$<script>alert(1)</script>"})$', 'allein im Wert'],
'nested reference in before' => [self::content('post_title', '{"before":"$variable(x)$","after":""}'), 'allein im Wert'],
// S28: a marker the sanitizer rebuilds inside a cleaned setting (html(), text()).
'tag-split marker in before' => [self::content('post_title', '{"before":"$variable<x>(y","after":""}'), 'allein im Wert'],
'NUL-split marker in after' => [
self::content('post_title', '{"before":"","after":"$variable' . self::escape('0000') . '(y"}'),
'allein im Wert',
],
'octet-split marker in date format' => [self::content('post_date', '{"custom_date_format":"$variable%41(y"}'), 'allein im Wert'],
// S32: text() keeps control characters (like sanitize_text_field); VariableRef strips them.
'NUL-split marker in date format' => [
self::content('post_date', '{"custom_date_format":"$variable' . self::escape('0000') . '(y"}'),
'allein im Wert',
],
'DEL-split marker in date format' => [
self::content('post_date', '{"custom_date_format":"$variable' . self::escape('007f') . '(y"}'),
'allein im Wert',
],
'nested marker in before' => [self::content('post_title', '{"before":"$variable(","after":""}'), 'allein im Wert'],
'broken json' => ['$variable(kaputt)$', 'JSON'],
'trailing text' => [self::content('post_title', '{}') . ' ', 'allein im Wert'],
'embedded in text' => ['<p>Hi ' . self::content('post_title', '{"before":"","after":""}') . '</p>', 'allein im Wert'],
];
return DiviFixtures::maliciousRefs();
}
/** @dataProvider malicious */