fix(audit L4): Restore-Planer für Farben, Variablen und Presets berechnen die Fundstellen (affected, P8a blocked_ids) für geänderte, entfernte und deaktivierte Einträge
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
4a8a889f93
commit
bd80ab1394
@@ -11,7 +11,10 @@ use LIT\MCP\Integrations\Divi\Design\ColorRestorePlanner;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignPlan;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\ReferenceUsageQuery;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Tests\Support\UsageFixture;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
@@ -41,12 +44,16 @@ final class ColorRestorePlannerTest extends TestCase
|
||||
return ['gcid-brand00001' => self::color('Marke', '#0000ff')];
|
||||
}
|
||||
|
||||
private static function planner(bool $visualBuilder = true): ColorRestorePlanner
|
||||
private static function planner(bool $visualBuilder = true, ?GlobalUsages $usages = null): ColorRestorePlanner
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn($visualBuilder);
|
||||
|
||||
return new ColorRestorePlanner($writer, Mockery::mock(GlobalColorsAdapter::class));
|
||||
return new ColorRestorePlanner(
|
||||
$writer,
|
||||
Mockery::mock(GlobalColorsAdapter::class),
|
||||
$usages ?? UsageFixture::none()
|
||||
);
|
||||
}
|
||||
|
||||
public function testAreaFeaturesAndDesignArea(): void
|
||||
@@ -104,4 +111,32 @@ final class ColorRestorePlannerTest extends TestCase
|
||||
self::assertTrue($plan->isEmpty());
|
||||
self::assertSame([], $plan->warnings);
|
||||
}
|
||||
|
||||
/** Audit L4: the usages of changed and removed colors (not added ones) go into the plan, so P8a can block. */
|
||||
public function testReportsThePagesUsingChangedOrRemovedColors(): void
|
||||
{
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->expects('reportFor')->once()->with(Mockery::on(
|
||||
static fn (mixed $query): bool => $query instanceof ReferenceUsageQuery
|
||||
&& $query->matches('gcid-brand00001') && $query->matches('gcid-akzent0001')
|
||||
&& ! $query->matches('gcid-other00001')
|
||||
))->andReturn(UsageFixture::blockedReport());
|
||||
|
||||
$plan = self::planner(true, $usages)->plan(self::current(), self::snapshot(), self::SNAPSHOT_ID);
|
||||
|
||||
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||
self::assertSame([7], $plan->usages?->blocked);
|
||||
self::assertTrue($plan->usages->blocks());
|
||||
}
|
||||
|
||||
public function testNoUsageLookupWithoutAChangedOrRemovedColor(): void
|
||||
{
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->expects('reportFor')->never();
|
||||
|
||||
$plan = self::planner(true, $usages)->plan(self::snapshot(), self::snapshot(), self::SNAPSHOT_ID);
|
||||
|
||||
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||
self::assertNull($plan->usages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ use LIT\MCP\Integrations\Divi\Design\ThemeOptionBook;
|
||||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsRestorePlanner;
|
||||
use LIT\MCP\Integrations\Divi\Design\VariableRestorePlanner;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||
use LIT\MCP\Tests\Support\LockTable;
|
||||
use LIT\MCP\Tests\Support\UsageFixture;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
@@ -51,7 +53,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
$ability = new DiviDesignRestoreAbility(
|
||||
new DesignChange($snapshots, new FakeChangeLog(8)),
|
||||
$snapshots,
|
||||
new PresetRestorePlanner($writer)
|
||||
new PresetRestorePlanner($writer, UsageFixture::none())
|
||||
);
|
||||
|
||||
$dry = self::assertToolError($ability->execute(['snapshot_id' => 'presets-20261016T100000Z-aaaaaa']), 'confirm_required');
|
||||
@@ -80,7 +82,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
return new DiviDesignRestoreAbility(
|
||||
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||
$snapshots,
|
||||
new PresetRestorePlanner($writer, static fn (): bool => $unfiltered)
|
||||
new PresetRestorePlanner($writer, UsageFixture::none(), static fn (): bool => $unfiltered)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
$ability = new DiviDesignRestoreAbility(
|
||||
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||
$snapshots,
|
||||
new PresetRestorePlanner($writer)
|
||||
new PresetRestorePlanner($writer, UsageFixture::none())
|
||||
);
|
||||
$input = ['snapshot_id' => 'presets-20261016T100000Z-aaaaaa', 'confirm' => true, 'design_rev' => 'x'];
|
||||
|
||||
@@ -148,7 +150,7 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
$ability = new DiviDesignRestoreAbility(
|
||||
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||
$snapshots,
|
||||
new PresetRestorePlanner($writer)
|
||||
new PresetRestorePlanner($writer, UsageFixture::none())
|
||||
);
|
||||
|
||||
$error = self::assertToolError(
|
||||
@@ -164,7 +166,8 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
GlobalColorsAdapter $colors,
|
||||
GlobalVariablesAdapter $variables,
|
||||
?FakeChangeLog $log = null,
|
||||
?ThemeOptionsAdapter $options = null
|
||||
?ThemeOptionsAdapter $options = null,
|
||||
?GlobalUsages $usages = null
|
||||
): DiviDesignRestoreAbility {
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
@@ -176,9 +179,9 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
return new DiviDesignRestoreAbility(
|
||||
new DesignChange($snapshots, $log ?? FakeChangeLog::forbidden()),
|
||||
$snapshots,
|
||||
new PresetRestorePlanner($writer),
|
||||
new ColorRestorePlanner($writer, $colors),
|
||||
new VariableRestorePlanner($writer, $variables),
|
||||
new PresetRestorePlanner($writer, $usages ?? UsageFixture::none()),
|
||||
new ColorRestorePlanner($writer, $colors, $usages ?? UsageFixture::none()),
|
||||
new VariableRestorePlanner($writer, $variables, $usages ?? UsageFixture::none()),
|
||||
new ThemeOptionsRestorePlanner($options ?? self::optionStore($noOptions))
|
||||
);
|
||||
}
|
||||
@@ -240,6 +243,35 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
||||
'log_id' => 2, 'size' => 1];
|
||||
}
|
||||
|
||||
/** Audit L4 / ruling P8a: a colors restore touching a color a live page the caller may not edit uses is refused. */
|
||||
public function testRestoringAColorUsedOnAnUneditableLivePageIsForbidden(): void
|
||||
{
|
||||
$id = ColorRestorePlannerTest::SNAPSHOT_ID;
|
||||
$colors = self::colorStore(ColorRestorePlannerTest::current());
|
||||
$colors->shouldNotReceive('save');
|
||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||
$snapshots->allows('get')->with('colors', $id)
|
||||
->andReturn(self::snapshotIn('colors', $id, ColorRestorePlannerTest::snapshot()));
|
||||
$snapshots->shouldNotReceive('save');
|
||||
$ability = self::allAreas(
|
||||
$snapshots,
|
||||
$colors,
|
||||
self::variableStore([]),
|
||||
FakeChangeLog::forbidden(),
|
||||
null,
|
||||
UsageFixture::blocked()
|
||||
);
|
||||
|
||||
$dry = self::assertToolError($ability->execute(['snapshot_id' => $id]), 'confirm_required');
|
||||
self::assertSame(7, $dry['affected'][0]['post_id'] ?? null);
|
||||
|
||||
$error = self::assertToolError(
|
||||
$ability->execute(['snapshot_id' => $id, 'confirm' => true, 'design_rev' => $dry['design_rev']]),
|
||||
'forbidden'
|
||||
);
|
||||
self::assertSame([7], $error['blocked_ids']);
|
||||
}
|
||||
|
||||
public function testRestoresColorsWithDryRunAndASnapshotOfTheCurrentColors(): void
|
||||
{
|
||||
$id = ColorRestorePlannerTest::SNAPSHOT_ID;
|
||||
|
||||
@@ -8,15 +8,21 @@ use LIT\MCP\Integrations\Divi\Design\DesignPlan;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||
use LIT\MCP\Integrations\Divi\Design\PresetArea;
|
||||
use LIT\MCP\Integrations\Divi\Design\PresetRestorePlanner;
|
||||
use LIT\MCP\Integrations\Divi\Design\PresetUsageQuery;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Tests\Support\UsageFixture;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
final class PresetRestorePlannerTest extends TestCase
|
||||
{
|
||||
private static function planner(bool $unfiltered = true): PresetRestorePlanner
|
||||
private static function planner(bool $unfiltered = true, ?GlobalUsages $usages = null): PresetRestorePlanner
|
||||
{
|
||||
return new PresetRestorePlanner(Mockery::mock(DesignSystemWriter::class), static fn (): bool => $unfiltered);
|
||||
return new PresetRestorePlanner(
|
||||
Mockery::mock(DesignSystemWriter::class),
|
||||
$usages ?? UsageFixture::none(),
|
||||
static fn (): bool => $unfiltered);
|
||||
}
|
||||
|
||||
public function testAreaFeaturesAndDesignArea(): void
|
||||
@@ -51,4 +57,36 @@ final class PresetRestorePlannerTest extends TestCase
|
||||
|
||||
self::assertToolError($plan, 'forbidden');
|
||||
}
|
||||
|
||||
/** Audit L4: removed and changed presets are looked up (spec §5 orphan warning stays); P8a can block. */
|
||||
public function testReportsThePagesUsingRemovedPresets(): void
|
||||
{
|
||||
$older = PresetBookTest::stored();
|
||||
unset($older['module']['divi/button']['items']['bbbbbbbbbb']);
|
||||
$older['module']['divi/button']['items']['aaaaaaaaaa']['attrs'] = ['button' => ['x' => 2]];
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->expects('reportFor')->once()->with(Mockery::on(
|
||||
static fn (mixed $query): bool => $query instanceof PresetUsageQuery
|
||||
&& $query->matches('<!-- wp:divi/button {"modulePreset":["bbbbbbbbbb"]} /-->')
|
||||
&& $query->matches('<!-- wp:divi/button {"modulePreset":["aaaaaaaaaa"]} /-->')
|
||||
&& ! $query->matches('<!-- wp:divi/button {"modulePreset":["cccccccccc"]} /-->')
|
||||
))->andReturn(UsageFixture::blockedReport());
|
||||
|
||||
$plan = self::planner(true, $usages)->plan(PresetBookTest::stored(), $older, 'presets-1');
|
||||
|
||||
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||
self::assertTrue($plan->usages?->blocks());
|
||||
self::assertStringContainsString('Standard-Preset', $plan->warnings[0]);
|
||||
}
|
||||
|
||||
public function testNoUsageLookupWithoutChangedOrRemovedPresets(): void
|
||||
{
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->expects('reportFor')->never();
|
||||
|
||||
$plan = self::planner(true, $usages)->plan(PresetBookTest::stored(), PresetBookTest::stored(), 'presets-1');
|
||||
|
||||
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||
self::assertNull($plan->usages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\VariableArea;
|
||||
use LIT\MCP\Integrations\Divi\Design\VariableBook;
|
||||
use LIT\MCP\Integrations\Divi\Design\VariableRestorePlanner;
|
||||
use LIT\MCP\Integrations\Divi\Design\ReferenceUsageQuery;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Tests\Support\UsageFixture;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
@@ -41,14 +44,14 @@ final class VariableRestorePlannerTest extends TestCase
|
||||
return ['numbers' => ['gvid-radius0001' => self::number('gvid-radius0001', 'Eckenradius', '4px')]];
|
||||
}
|
||||
|
||||
private static function planner(bool $manage = true): VariableRestorePlanner
|
||||
private static function planner(bool $manage = true, ?GlobalUsages $usages = null): VariableRestorePlanner
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||
$variables = Mockery::mock(GlobalVariablesAdapter::class);
|
||||
$variables->allows('canManage')->andReturn($manage);
|
||||
|
||||
return new VariableRestorePlanner($writer, $variables);
|
||||
return new VariableRestorePlanner($writer, $variables, $usages ?? UsageFixture::none());
|
||||
}
|
||||
|
||||
public function testAreaFeaturesAndDesignArea(): void
|
||||
@@ -89,4 +92,19 @@ final class VariableRestorePlannerTest extends TestCase
|
||||
$plan->warnings
|
||||
);
|
||||
}
|
||||
|
||||
/** Audit L4: changed and archived variables are looked up; the plan carries the report for P8a. */
|
||||
public function testReportsThePagesUsingChangedOrArchivedVariables(): void
|
||||
{
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->expects('reportFor')->once()->with(Mockery::on(
|
||||
static fn (mixed $query): bool => $query instanceof ReferenceUsageQuery
|
||||
&& $query->matches('gvid-radius0001') && $query->matches('gvid-neu0000001')
|
||||
))->andReturn(UsageFixture::blockedReport());
|
||||
|
||||
$plan = self::planner(true, $usages)->plan(self::current(), self::snapshot(), self::SNAPSHOT_ID);
|
||||
|
||||
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||
self::assertTrue($plan->usages?->blocks());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user