feat: Divi-Adapter sperren je Fähigkeit – ReleaseGate::feature/assertFeature(s), DiviLocked und Ablehnung nennen die betroffenen Fähigkeiten (D6), DiviSaver prüft Seiten schreiben und die Fähigkeit des Speicherbereichs (D5), DesignArea::feature; Test-Standard alle Fähigkeiten freigegeben

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 22:10:41 +02:00
co-authored by Claude Opus 5.5
parent c4efeb3ba4
commit e387d8d29e
20 changed files with 525 additions and 65 deletions
@@ -11,12 +11,14 @@ use LIT\MCP\Integrations\Divi\Design\DesignPlan;
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
use LIT\MCP\Integrations\Divi\Design\SnapshotTooLarge;
use LIT\MCP\Integrations\Divi\Library\UsageReport;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Brain\Monkey\Functions;
use Mockery;
@@ -80,6 +82,11 @@ final class DesignChangeTest extends TestCase
return 'Presets';
}
public function feature(): DiviFeature
{
return DiviFeature::PresetWrite;
}
public function raw(): array
{
return $this->stored;
@@ -383,6 +390,41 @@ final class DesignChangeTest extends TestCase
);
}
public function testTheLockFollowsTheFeatureOfTheArea(): void
{
$others = array_values(array_filter(
DiviFeature::cases(),
static fn (DiviFeature $feature): bool => $feature !== DiviFeature::PresetWrite
));
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status($others));
$stored = ['v' => 1];
$area = self::area($stored);
$snapshots = Mockery::mock(SnapshotStore::class);
$snapshots->expects('save')->andReturn('s1');
$snapshots->expects('attachLog');
$written = (new DesignChange($snapshots, new FakeChangeLog(9)))->run(
'lit/divi-presets-set',
$area,
['confirm' => true, 'design_rev' => $area->rev($stored)],
self::plan(['v' => 2])
);
self::assertTrue($written['ok'], 'every other feature locked: presets are still written');
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::PresetWrite]));
try {
(new DesignChange(Mockery::mock(SnapshotStore::class), FakeChangeLog::forbidden()))->run(
'lit/divi-presets-set',
$area,
['confirm' => true, 'design_rev' => $area->rev($stored)],
self::plan(['v' => 3])
);
self::fail('preset-write locked must refuse');
} catch (DiviLocked $locked) {
self::assertSame([DiviFeature::PresetWrite], $locked->features);
}
}
public function testTooLargeSnapshotRefusesTheChange(): void
{
$stored = ['v' => 1];
@@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Design\PresetArea;
use LIT\MCP\Integrations\Divi\Design\PresetBook;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
@@ -23,6 +24,7 @@ final class PresetAreaTest extends TestCase
$area = new PresetArea($writer);
self::assertSame(['presets', 'Presets'], [$area->name(), $area->label()]);
self::assertSame(DiviFeature::PresetWrite, $area->feature());
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->raw());
self::assertSame(PresetBook::fromDivi($stored)->rev(), $area->rev($stored));
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->write($stored, true));
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use PHPUnit\Framework\TestCase;
final class SaveScopeTest extends TestCase
@@ -18,4 +19,16 @@ final class SaveScopeTest extends TestCase
self::assertFalse(SaveScope::Page->clearsSiteCss());
self::assertFalse(SaveScope::LibraryItem->setsFlags());
}
/** D5: every save needs page-write, a library or Theme Builder save the feature of its area too. */
public function testFeaturesOfEachScope(): void
{
self::assertSame([DiviFeature::PageWrite], SaveScope::Page->features());
self::assertSame([DiviFeature::PageWrite, DiviFeature::LibraryWrite], SaveScope::LibraryItem->features());
self::assertSame([DiviFeature::PageWrite, DiviFeature::LibraryWrite], SaveScope::GlobalItem->features());
self::assertSame(
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
SaveScope::ThemeBuilderLayout->features()
);
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use LIT\MCP\Abilities\ToolRefusal;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
final class DiviLockedTest extends TestCase
{
public function testWithFeaturesTheErrorIsTheRefusalOfThoseFeatures(): void
{
$status = ReleaseMatrices::status([DiviFeature::PresetWrite], ReleaseState::Incompatible);
$locked = new DiviLocked($status, [DiviFeature::PresetWrite]);
self::assertInstanceOf(ToolRefusal::class, $locked);
self::assertSame('Divi 5.3.0 is incompatible for LIT MCP – preset-write refused', $locked->getMessage());
self::assertEquals($status->refusalFor([DiviFeature::PresetWrite]), $locked->toError());
$payload = self::assertToolError($locked->toError(), 'divi_incompatible');
self::assertSame(['preset-write'], array_column($payload['features'], 'id'));
}
public function testWithoutFeaturesTheErrorIsTheGeneralWriteLock(): void
{
$status = new ReleaseStatus(ReleaseState::Untested, '5.3.0');
$locked = new DiviLocked($status);
self::assertSame('Divi 5.3.0 is untested for LIT MCP – write refused', $locked->getMessage());
self::assertEquals($status->refusal(), $locked->toError());
}
}
@@ -5,11 +5,13 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
@@ -20,10 +22,78 @@ final class ReleaseGateTest extends TestCase
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
}
public function testTheTestCaseDefaultIsReleased(): void
public function testTheTestCaseDefaultReleasesEveryFeature(): void
{
self::assertTrue(ReleaseGate::status()->writable());
self::assertSame([], ReleaseGate::status()->matrix->unusable());
ReleaseGate::assertWritable(7);
ReleaseGate::assertFeatures(DiviFeature::cases());
}
public function testFeatureReadsTheMatrixOfThisRequest(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::Catalog]));
self::assertSame(ReleaseState::Untested, ReleaseGate::feature(DiviFeature::Catalog)->state);
self::assertTrue(ReleaseGate::feature(DiviFeature::PageRead)->usable());
}
public function testAssertFeatureRefusesOnlyALockedFeature(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status(
[DiviFeature::PresetWrite],
ReleaseState::Incompatible
));
ReleaseGate::assertFeature(DiviFeature::PageWrite);
try {
ReleaseGate::assertFeature(DiviFeature::PresetWrite, 7);
self::fail('a locked feature must refuse');
} catch (DiviLocked $locked) {
self::assertSame([DiviFeature::PresetWrite], $locked->features);
self::assertToolError($locked->toError(), 'divi_incompatible');
}
}
public function testAssertFeaturesNamesEveryLockedFeatureOnce(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status(
[DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite]
));
try {
ReleaseGate::assertFeatures([
DiviFeature::PageWrite,
DiviFeature::ThemeBuilderWrite,
DiviFeature::LibraryWrite,
DiviFeature::ThemeBuilderWrite,
]);
self::fail('locked features must refuse');
} catch (DiviLocked $locked) {
self::assertSame([DiviFeature::ThemeBuilderWrite, DiviFeature::LibraryWrite], $locked->features);
}
}
public function testAssertWritableNeedsEveryWritingFeature(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::Catalog]));
ReleaseGate::assertWritable();
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::LibraryWrite]));
try {
ReleaseGate::assertWritable();
self::fail('a locked writing feature must refuse');
} catch (DiviLocked $locked) {
self::assertSame([DiviFeature::LibraryWrite], $locked->features);
}
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
try {
ReleaseGate::assertWritable();
self::fail('an untested Divi must lock writes');
} catch (DiviLocked $locked) {
self::assertSame(CompatibilityMatrix::writeFeatures(), $locked->features);
}
}
public function testAssertWritableThrowsTheLockWithTheStatus(): void
@@ -45,6 +115,7 @@ final class ReleaseGateTest extends TestCase
$result = ReleaseGate::whileProbing(41, static function (): string {
ReleaseGate::assertWritable(41);
ReleaseGate::assertFeatures(DiviFeature::cases(), 41);
return 'written';
});
@@ -52,6 +123,10 @@ final class ReleaseGateTest extends TestCase
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature(
DiviFeature::PageWrite,
42
)),
static fn () => ReleaseGate::assertWritable(41)] as $attempt) {
try {
$attempt();
@@ -118,7 +193,7 @@ final class ReleaseGateTest extends TestCase
self::assertStringContainsString('theme API gone', (string) file_get_contents($log));
unlink($log);
$this->expectException(DiviLocked::class);
ReleaseGate::assertWritable();
ReleaseGate::assertFeature(DiviFeature::PageRead);
}
public function testFindsTheLockInsideAWrappedFailure(): void
@@ -5,10 +5,13 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
use LIT\MCP\Integrations\Divi\Library\LibraryItemSpec;
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
@@ -16,19 +19,26 @@ use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
use LIT\MCP\Integrations\WordPress\Content\NewDraft;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
use Throwable;
use WP_Post;
/** Spec §5.5: every writing adapter method refuses before its first write while Divi is not released. */
/**
* Spec §5.5, D5: every writing adapter method refuses before its first write exactly when a feature it needs is
* locked – and passes the gate when only other features are locked. (DesignChange: DesignChangeTest.)
*/
final class ReleaseGuardsTest extends TestCase
{
private const WRITES = ['wp_update_post', 'wp_insert_post', 'update_post_meta', 'add_post_meta', 'delete_post_meta',
'update_option', 'delete_option', 'wp_set_object_terms'];
protected function setUp(): void
{
parent::setUp();
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
// Nothing may be written: every write function WordPress offers fails the test.
foreach (['wp_update_post', 'wp_insert_post', 'update_post_meta', 'add_post_meta', 'delete_post_meta',
'update_option', 'delete_option', 'wp_set_object_terms'] as $write) {
// Nothing may be written while locked: every write function WordPress offers fails the test.
foreach (self::WRITES as $write) {
Functions\when($write)->alias(static function () use ($write): never {
self::fail($write . ' ran although Divi is locked');
});
@@ -36,41 +46,106 @@ final class ReleaseGuardsTest extends TestCase
Functions\when('current_user_can')->justReturn(true);
}
/** @return iterable<string, array{callable(): mixed}> */
/** @return iterable<string, array{callable(): mixed, list<DiviFeature>}> */
public static function writes(): iterable
{
$post = new WP_Post(['ID' => 7, 'post_content' => '', 'post_type' => 'page']);
$spec = new LibraryItemSpec('Hero', 'section', false, 'regular', null, 'page');
$page = [DiviFeature::PageWrite];
$tb = [DiviFeature::ThemeBuilderWrite];
yield 'save' => [static fn () => (new DiviSaver())->save($post, '<!-- wp:divi/section /-->')];
yield 'saveNew' => [static fn () => (new DiviSaver())->saveNew(7, '<!-- wp:divi/section /-->')];
yield 'save' => [static fn () => (new DiviSaver())->save($post, '<!-- wp:divi/section /-->'), $page];
yield 'save library item' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::LibraryItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'save global item' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::GlobalItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'save tb layout' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::ThemeBuilderLayout),
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
];
yield 'saveNew' => [static fn () => (new DiviSaver())->saveNew(7, '<!-- wp:divi/section /-->'), $page];
yield 'saveNew library item' => [
static fn () => (new DiviSaver())->saveNew(7, 'x', SaveScope::LibraryItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'pages setFlags' => [static fn () => DiviPages::setFlags(7), $page];
yield 'library create' => [static fn () => (new DiviLibraryStore())->create(
$spec,
new NewDraft(null, FakeChangeLog::forbidden(), 'lit/library-save', 'et_pb_layout', 'Bibliothekseintrag')
)];
yield 'tb insertLayout' => [static fn () => (new DiviThemeBuilder())->insertLayout('header', 'Kopf')];
), [DiviFeature::LibraryWrite]];
yield 'tb liveId create' => [static fn () => (new DiviThemeBuilder())->liveId(true), $tb];
yield 'tb insertLayout' => [static fn () => (new DiviThemeBuilder())->insertLayout('header', 'Kopf'), $tb];
yield 'tb storeTemplate' => [static fn () => (new DiviThemeBuilder())->storeTemplate(3, [
'title' => 'V', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
])];
yield 'tb attach' => [static fn () => (new DiviThemeBuilder())->attach(3, 9)];
yield 'tb detach' => [static fn () => (new DiviThemeBuilder())->detach(3, 9)];
yield 'tb setConditions' => [static fn () => (new DiviThemeBuilder())->setConditions(9, ['singular'], [])];
yield 'tb setEnabled' => [static fn () => (new DiviThemeBuilder())->setEnabled(9, true)];
yield 'tb unmarkUnused' => [static fn () => (new DiviThemeBuilder())->unmarkUnused(9)];
yield 'pages setFlags' => [static fn () => DiviPages::setFlags(7)];
yield 'tb liveId create' => [static fn () => (new DiviThemeBuilder())->liveId(true)];
]), $tb];
yield 'tb attach' => [static fn () => (new DiviThemeBuilder())->attach(3, 9), $tb];
yield 'tb detach' => [static fn () => (new DiviThemeBuilder())->detach(3, 9), $tb];
yield 'tb setConditions' => [static fn () => (new DiviThemeBuilder())->setConditions(9, ['singular'], []), $tb];
yield 'tb setEnabled' => [static fn () => (new DiviThemeBuilder())->setEnabled(9, true), $tb];
yield 'tb unmarkUnused' => [static fn () => (new DiviThemeBuilder())->unmarkUnused(9), $tb];
yield 'design savePresets' => [
static fn () => (new DesignSystemWriter())->savePresets(['module' => [], 'group' => []], false),
[DiviFeature::PresetWrite],
];
}
/** @dataProvider writes */
public function testRefusesBeforeTheFirstWrite(callable $write): void
/**
* @dataProvider writes
* @param list<DiviFeature> $needs
*/
public function testRefusesBeforeTheFirstWriteWhenAFeatureItNeedsIsLocked(callable $write, array $needs): void
{
$this->expectException(DiviLocked::class);
foreach ([ReleaseState::Untested, ReleaseState::Incompatible] as $state) {
foreach ($needs as $feature) {
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([$feature], $state));
$write();
try {
$write();
self::fail(sprintf('%s %s must refuse', $feature->value, $state->value));
} catch (DiviLocked $locked) {
self::assertSame([$feature], $locked->features);
}
}
}
}
/**
* @dataProvider writes
* @param list<DiviFeature> $needs
*/
public function testPassesTheGateWhenOnlyOtherFeaturesAreLocked(callable $write, array $needs): void
{
$others = array_values(array_filter(
DiviFeature::cases(),
static fn (DiviFeature $feature): bool => ! in_array($feature, $needs, true)
));
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::status($others, ReleaseState::Incompatible)
);
// Past the gate the method may write or fail on the missing Divi – anything but the lock.
foreach (self::WRITES as $name) {
Functions\when($name)->alias(static function () use ($name): never {
throw new RuntimeException($name . ' reached');
});
}
try {
$write();
} catch (DiviLocked $locked) {
self::fail('refused although only other features are locked: ' . $locked->getMessage());
} catch (Throwable) {
// Reached the code behind the gate.
}
self::addToAssertionCount(1);
}
public function testTheCheckPageMayBeWrittenWhileProbing(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
Functions\when('wp_update_post')->justReturn(41);
Functions\when('update_post_meta')->justReturn(true);
Functions\when('get_post_meta')->alias(
@@ -6,6 +6,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\FeatureStatus;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\ReleaseMatrices;
@@ -132,4 +133,78 @@ final class ReleaseStatusTest extends TestCase
new ReleaseStatus(ReleaseState::Released, '5.2.1', 'code', [], null, CompatibilityMatrix::allIncompatible('x'));
}
public function testRefusalForAnUntestedFeatureNamesItAndKeepsTheOthersWorking(): void
{
$error = ReleaseMatrices::status([DiviFeature::PageWrite])
->refusalFor([DiviFeature::PageWrite, DiviFeature::PageRead]);
$payload = self::assertToolError($error, 'divi_untested');
self::assertSame(423, $error->get_error_data()['status']);
self::assertSame(
'Achtung: Divi 5.3.0 ist auf dieser Website für „Seiten schreiben“ noch nicht für LIT MCP freigegeben. '
. 'Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung ausführt '
. 'und die Version freigibt. Es wurde nichts geändert.',
$payload['message']
);
self::assertSame(
'Gesperrt: „Seiten schreiben“. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre Fähigkeiten '
. 'freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.',
$payload['hint']
);
self::assertSame(
[['id' => 'page-write', 'label' => 'Seiten schreiben', 'state' => 'untested', 'problems' => []]],
$payload['features']
);
self::assertArrayNotHasKey('problems', $payload);
}
public function testRefusalForTakesTheWorstStateAndListsEveryLockedFeature(): void
{
$matrix = ReleaseMatrices::matrix([DiviFeature::ThemeBuilderWrite], ReleaseState::Incompatible);
$features = [];
foreach (DiviFeature::cases() as $feature) {
$features[$feature->value] = $matrix->of($feature);
}
$features['library-write'] = new FeatureStatus(DiviFeature::LibraryWrite, ReleaseState::Untested);
$status = ReleaseStatus::of('5.99.0', new CompatibilityMatrix($features));
$payload = self::assertToolError(
$status->refusalFor([DiviFeature::PageWrite, DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite]),
'divi_incompatible'
);
self::assertSame(
'Divi 5.99.0 hat Stellen geändert, die LIT MCP für „Theme Builder schreiben“ nutzt. Das ist gesperrt, bis '
. 'LIT MCP aktualisiert wurde. Es wurde nichts geändert.',
$payload['message']
);
self::assertStringStartsWith(
'Gesperrt: „Bibliothek schreiben“ und „Theme Builder schreiben“. Andere Divi-Werkzeuge',
$payload['hint']
);
self::assertStringEndsWith('dass LIT MCP aktualisiert werden muss.', $payload['hint']);
self::assertSame(['library-write', 'tb-write'], array_column($payload['features'], 'id'));
self::assertSame(['untested', 'incompatible'], array_column($payload['features'], 'state'));
self::assertSame(['Seiten: et_x() – Signatur geändert'], $payload['problems']);
}
public function testRefusalForNamesThreeFeatures(): void
{
$locked = [DiviFeature::PageWrite, DiviFeature::LibraryWrite, DiviFeature::PresetWrite];
$payload = self::assertToolError(ReleaseMatrices::status($locked)->refusalFor($locked), 'divi_untested');
self::assertStringStartsWith(
'Gesperrt: „Seiten schreiben“, „Bibliothek schreiben“ und „Presets schreiben“.',
$payload['hint']
);
}
public function testUsableFeaturesHaveNoRefusal(): void
{
$this->expectException(LogicException::class);
ReleaseMatrices::status([DiviFeature::Catalog])->refusalFor([DiviFeature::PageWrite, DiviFeature::PageRead]);
}
}
+5 -2
View File
@@ -6,6 +6,7 @@ namespace LIT\MCP\Tests\Unit;
use Brain\Monkey;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\AssertsToolErrors;
@@ -35,8 +36,10 @@ abstract class TestCase extends BaseTestCase
'sanitize_text_field' => static fn ($value): string => trim(strip_tags((string) $value)),
]);
// Divi is released unless a test says otherwise (the release gate would ask WordPress and Divi).
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::released('5.2.1'));
// Every Divi feature is released unless a test says otherwise (the gate would ask WordPress and Divi).
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseStatus::of('5.2.1', CompatibilityMatrix::allReleased())
);
}
protected function tearDown(): void