refactor: Übergangs-API der Gesamtsperre entfernt (assertWritable, writable, refusal, writeFeatures, released); DiviLocked verlangt die gesperrten Fähigkeiten, Tests bauen Zustände über ReleaseMatrices

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 22:53:00 +02:00
co-authored by Claude Opus 5.5
parent c036a92b60
commit d0d3488df8
21 changed files with 129 additions and 239 deletions
+27 -1
View File
@@ -10,7 +10,10 @@ use LIT\MCP\Integrations\Divi\Release\FeatureStatus;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
/** Release statuses for tests: every feature released except the named ones. */
/**
* Release statuses for tests, built the way production builds them (ReleaseStatus::of() with a matrix): every feature
* released except the named ones, all released, or all in one state.
*/
final class ReleaseMatrices
{
/** @param list<DiviFeature> $locked */
@@ -38,4 +41,27 @@ final class ReleaseMatrices
): ReleaseStatus {
return ReleaseStatus::of($version, self::matrix($locked, $state));
}
/**
* Every feature released from $source (DiviReleases::TESTED = code, ReleaseStore = database, auto).
*
* @param array<string, mixed>|null $release The database entry of a SOURCE_DATABASE release.
*/
public static function released(
string $version = '5.2.1',
string $source = ReleaseStatus::SOURCE_CODE,
?array $release = null
): ReleaseStatus {
return ReleaseStatus::of($version, CompatibilityMatrix::allReleased($source), $release);
}
/**
* Every feature in $state with $problems (an unreleased or broken Divi as a whole).
*
* @param list<string> $problems
*/
public static function uniform(ReleaseState $state, string $version = '5.3.0', array $problems = []): ReleaseStatus
{
return ReleaseStatus::of($version, CompatibilityMatrix::uniform($state, $problems));
}
}
@@ -17,12 +17,12 @@ use LIT\MCP\Integrations\Divi\Release\DiviFeatureGate;
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\Integrations\Divi\Release\RequiresDiviFeatures;
use LIT\MCP\Integrations\Integration;
use LIT\MCP\Integrations\IntegrationRegistry;
use LIT\MCP\Server\RequestContext;
use LIT\MCP\Tests\Support\DiviFeatureFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use RuntimeException;
@@ -281,7 +281,10 @@ final class AbilityRegistrarTest extends TestCase
public function testALockThrownInsideAWriteBecomesTheLockError(): void
{
$locked = new DiviLocked(new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['x']));
$locked = new DiviLocked(
ReleaseMatrices::uniform(ReleaseState::Incompatible, '5.3.0', ['x']),
[DiviFeature::PageWrite]
);
$ability = $this->ability();
$ability->allows('execute')->andThrow(WriteFailed::rolledBack('Nicht gespeichert.', $locked));
@@ -302,7 +305,7 @@ final class AbilityRegistrarTest extends TestCase
public function testAPartialWriteStaysPartialEvenWithALockInside(): void
{
$locked = new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
$locked = new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested, '5.3.0'), [DiviFeature::PageWrite]);
$ability = $this->ability();
$ability->allows('execute')->andThrow(WriteFailed::partial('Teilweise gespeichert.', $locked));
+21 -24
View File
@@ -23,6 +23,7 @@ use LIT\MCP\Plugin;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\FeatureFakeTool;
use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use WP_Post;
@@ -197,7 +198,7 @@ final class DiviTabTest extends TestCase
public function testUntestedWithAFreshGreenReportOffersTheRelease(): void
{
$html = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report());
$html = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report());
self::assertStringContainsString('ungetestet', $html);
self::assertStringContainsString('Prüfung starten', $html);
@@ -207,9 +208,9 @@ final class DiviTabTest extends TestCase
public function testCheckButtonSaysWhatItDoesAndLeadsWhileNothingElseCanBeDone(): void
{
$stale = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report(true, '5.2.9'));
$fresh = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report());
$released = $this->rendered(ReleaseStatus::released('5.3.0'), self::report());
$stale = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report(true, '5.2.9'));
$fresh = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report());
$released = $this->rendered(ReleaseMatrices::released('5.3.0'), self::report());
foreach ([$stale, $fresh, $released] as $html) {
self::assertStringContainsString('legt eine private Testseite an', $html);
@@ -222,7 +223,7 @@ final class DiviTabTest extends TestCase
public function testUntestedWithAStaleReportAsksForANewCheck(): void
{
$html = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report(true, '5.2.9'));
$html = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report(true, '5.2.9'));
self::assertStringNotContainsString('Divi 5.3.0 freigeben', $html);
self::assertStringContainsString('Prüfung erneut ausführen', $html);
@@ -230,7 +231,7 @@ final class DiviTabTest extends TestCase
public function testReleasedFromCodeOffersNeitherReleaseNorWithdrawal(): void
{
$html = $this->rendered(ReleaseStatus::released('5.3.0'), self::report());
$html = $this->rendered(ReleaseMatrices::released('5.3.0'), self::report());
self::assertStringContainsString('freigegeben', $html);
self::assertStringContainsString('im Plugin als getestet hinterlegt', $html);
@@ -240,7 +241,7 @@ final class DiviTabTest extends TestCase
public function testAnAutomaticReleaseSaysSoAndOffersNeitherReleaseNorWithdrawal(): void
{
$html = $this->rendered(ReleaseStatus::released('5.99.0', ReleaseStatus::SOURCE_AUTO), self::report());
$html = $this->rendered(ReleaseMatrices::released('5.99.0', ReleaseStatus::SOURCE_AUTO), self::report());
self::assertStringContainsString('Automatisch freigegeben', $html);
self::assertStringNotContainsString('freigeben</button>', $html);
@@ -249,7 +250,7 @@ final class DiviTabTest extends TestCase
public function testADatabaseReleaseCanBeWithdrawn(): void
{
$status = new ReleaseStatus(ReleaseState::Released, '5.3.0', ReleaseStatus::SOURCE_DATABASE, [], [
$status = ReleaseMatrices::released('5.3.0', ReleaseStatus::SOURCE_DATABASE, [
'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
]);
@@ -287,7 +288,7 @@ final class DiviTabTest extends TestCase
public function testIncompatibleListsTheProblems(): void
{
$html = $this->rendered(
new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['Theme Builder: Nachbau von x() – y']),
ReleaseMatrices::uniform(ReleaseState::Incompatible, '5.3.0', ['Theme Builder: Nachbau von x() – y']),
null
);
@@ -316,7 +317,7 @@ final class DiviTabTest extends TestCase
$tab = $this->tab(self::report(), Mockery::mock(ReleaseStore::class));
$tab->removeOrphans();
$html = $this->rendered(ReleaseStatus::released('5.3.0'), self::report(), $tab);
$html = $this->rendered(ReleaseMatrices::released('5.3.0'), self::report(), $tab);
self::assertStringContainsString('Liegengebliebene Prüfseiten gelöscht: 51', $html);
}
@@ -326,7 +327,7 @@ final class DiviTabTest extends TestCase
Functions\expect('get_posts')->never();
Functions\expect('wp_delete_post')->never();
$html = $this->rendered(ReleaseStatus::released('5.3.0'), self::report());
$html = $this->rendered(ReleaseMatrices::released('5.3.0'), self::report());
self::assertStringNotContainsString('Liegengebliebene Prüfseiten', $html);
}
@@ -367,7 +368,7 @@ final class DiviTabTest extends TestCase
new FeatureStatus(DiviFeature::Catalog, ReleaseState::Untested, [], null),
]);
return new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, [], null, matrix: $matrix);
return ReleaseStatus::of('5.3.0', $matrix);
}
public function testTheFeatureTableShowsStateSourceProblemsAndTools(): void
@@ -403,11 +404,11 @@ final class DiviTabTest extends TestCase
public function testTheTableNamesEverySourceInWords(): void
{
$code = $this->rendered(ReleaseStatus::released('5.3.0'), null);
$code = $this->rendered(ReleaseMatrices::released('5.3.0'), null);
$database = $this->rendered(
new ReleaseStatus(ReleaseState::Released, '5.3.0', ReleaseStatus::SOURCE_DATABASE, [], [
ReleaseStatus::of('5.3.0', MatrixFixtures::released('database'), [
'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
], matrix: MatrixFixtures::released('database')),
]),
self::report()
);
@@ -417,7 +418,7 @@ final class DiviTabTest extends TestCase
public function testTheTabExplainsTheAutomationAndTheFilter(): void
{
$html = $this->rendered(ReleaseStatus::released('5.3.0'), null);
$html = $this->rendered(ReleaseMatrices::released('5.3.0'), null);
self::assertStringContainsString('gibt LIT MCP jede Fähigkeit einzeln frei', $html);
self::assertStringContainsString('<code>lit_mcp_divi_auto_release</code>', $html);
@@ -429,11 +430,7 @@ final class DiviTabTest extends TestCase
public function testTheTabNoLongerSaysWritingIsLockedAsAWhole(): void
{
$untested = $this->rendered(
new ReleaseStatus(
ReleaseState::Untested,
'5.3.0',
matrix: MatrixFixtures::with(DiviFeature::Catalog, ReleaseState::Untested)
),
ReleaseStatus::of('5.3.0', MatrixFixtures::with(DiviFeature::Catalog, ReleaseState::Untested)),
null
);
$incompatible = $this->rendered(self::mixedStatus(), null);
@@ -450,9 +447,9 @@ final class DiviTabTest extends TestCase
$matrix = MatrixFixtures::released('database', [
new FeatureStatus(DiviFeature::PageWrite, ReleaseState::Incompatible, ['Seiten: x() fehlt'], null),
]);
$status = new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', ReleaseStatus::SOURCE_DATABASE, [], [
$status = ReleaseStatus::of('5.3.0', $matrix, [
'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
], matrix: $matrix);
]);
self::assertStringContainsString('Freigabe zurückziehen', $this->rendered($status, self::report()));
}
@@ -495,7 +492,7 @@ final class DiviTabTest extends TestCase
public function testNoNoticeWhileEveryFeatureIsUsable(): void
{
self::assertSame('', $this->notice(ReleaseStatus::released('5.3.0')));
self::assertSame('', $this->notice(ReleaseMatrices::released('5.3.0')));
}
public function testNoNoticeForUsersWithoutManageOptions(): void
+2 -1
View File
@@ -18,6 +18,7 @@ use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use RuntimeException;
@@ -354,7 +355,7 @@ final class DiviCheckCommandTest extends TestCase
ReleaseGate::useResolver(static function () use (&$order): ReleaseStatus {
$order[] = 'resolve';
return ReleaseStatus::released('5.3.0');
return ReleaseMatrices::released('5.3.0');
});
$this->command()->run([]);
@@ -374,7 +374,9 @@ final class DesignChangeTest extends TestCase
public function testLockedDiviWritesNoSnapshot(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.16.0'));
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
);
$stored = ['v' => 1];
$area = self::area($stored);
$snapshots = Mockery::mock(SnapshotStore::class);
@@ -12,6 +12,7 @@ use LIT\MCP\Integrations\Divi\Release\Manifest;
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;
final class DesignSystemWriterTest extends TestCase
@@ -38,7 +39,9 @@ final class DesignSystemWriterTest extends TestCase
public function testWritesNeedAReleasedDivi(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.16.0'));
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
);
$this->expectException(DiviLocked::class);
@@ -18,10 +18,11 @@ use LIT\MCP\Integrations\Divi\Design\PresetUsageQuery;
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
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\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use Mockery\MockInterface;
@@ -378,11 +379,14 @@ final class DiviDesignDetachAbilityTest extends TestCase
{
return [
'rolled back' => [WriteFailed::rolledBack('Seite 12 nicht gespeichert.'), 'write_failed'],
'Divi locked' => [new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.16.0')), 'divi_untested'],
'Divi locked' => [new DiviLocked(
ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0'),
[DiviFeature::PageWrite]
), 'divi_untested'],
'Divi locked inside a rollback' => [
WriteFailed::rolledBack(
'Seite 12 nicht gespeichert.',
new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.16.0'))
new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0'), [DiviFeature::PageWrite])
),
'divi_untested',
],
@@ -17,11 +17,12 @@ use LIT\MCP\Integrations\Divi\Library\UsageReport;
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
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\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\LibraryFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use Throwable;
@@ -456,7 +457,9 @@ final class PageRewriterTest extends TestCase
public function testLockedDiviBeforeTheFirstPageIsThrown(): void
{
$rewriter = $this->rewriter([self::page(101, self::layout(self::BUTTON))]);
$this->failures = [new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.16.0'))];
$this->failures = [
new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0'), [DiviFeature::PageWrite]),
];
$this->expectException(DiviLocked::class);
@@ -15,6 +15,7 @@ use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\FeatureFakeTool;
use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
@@ -83,14 +84,7 @@ final class DiviEnvironmentComponentTest extends TestCase
'auto',
'Seiten: Signatur von et_pb_x() geändert'
);
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(
ReleaseState::Incompatible,
'5.3.0',
null,
[],
null,
matrix: $matrix
));
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::of('5.3.0', $matrix));
$overview = new FeatureOverview(static fn (): array => [
new FeatureFakeTool('lit/divi-outline', true, [DiviFeature::PageRead]),
new FeatureFakeTool('lit/divi-insert', true, [DiviFeature::PageRead, DiviFeature::PageWrite]),
@@ -119,7 +113,7 @@ final class DiviEnvironmentComponentTest extends TestCase
ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus {
++$calls;
return ReleaseStatus::released('5.3.0');
return ReleaseMatrices::released('5.3.0');
});
$component = new DiviEnvironmentComponent(new DiviCompatibility([]));
@@ -84,16 +84,6 @@ final class CompatibilityMatrixTest extends TestCase
);
}
public function testTheWriteFeaturesAreTheWritingCases(): void
{
self::assertSame(
array_values(array_filter(DiviFeature::cases(), static fn (DiviFeature $f): bool => $f->isWrite())),
CompatibilityMatrix::writeFeatures()
);
self::assertContains(DiviFeature::PageWrite, CompatibilityMatrix::writeFeatures());
self::assertNotContains(DiviFeature::PageRead, CompatibilityMatrix::writeFeatures());
}
public function testToArrayListsEveryFeatureInCaseOrder(): void
{
$rows = self::matrix(['tb-write' => [ReleaseState::Incompatible, ['b']]])->toArray();
@@ -8,9 +8,9 @@ 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;
use LogicException;
final class DiviLockedTest extends TestCase
{
@@ -26,12 +26,11 @@ final class DiviLockedTest extends TestCase
self::assertSame(['preset-write'], array_column($payload['features'], 'id'));
}
public function testWithoutFeaturesTheErrorIsTheGeneralWriteLock(): void
public function testALockWithoutFeaturesIsAProgrammingError(): void
{
$status = new ReleaseStatus(ReleaseState::Untested, '5.3.0');
$locked = new DiviLocked($status);
$this->expectException(LogicException::class);
$this->expectExceptionMessage('a Divi lock needs the locked features');
self::assertSame('Divi 5.3.0 is untested for LIT MCP – write refused', $locked->getMessage());
self::assertEquals($status->refusal(), $locked->toError());
new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested), []);
}
}
@@ -5,7 +5,6 @@ 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;
@@ -19,15 +18,13 @@ final class ReleaseGateTest extends TestCase
{
private function untested(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested));
}
public function testTheTestCaseDefaultReleasesEveryFeature(): void
{
self::assertTrue(ReleaseGate::status()->writable());
self::assertSame([], ReleaseGate::status()->matrix->unusable());
ReleaseGate::assertWritable(7);
ReleaseGate::assertFeatures(DiviFeature::cases());
ReleaseGate::assertFeatures(DiviFeature::cases(), 7);
}
public function testFeatureReadsTheMatrixOfThisRequest(): void
@@ -74,34 +71,12 @@ final class ReleaseGateTest extends TestCase
}
}
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
public function testAssertFeatureThrowsTheLockWithTheStatus(): void
{
$this->untested();
try {
ReleaseGate::assertWritable(7);
ReleaseGate::assertFeature(DiviFeature::PageWrite, 7);
self::fail('an untested Divi must lock writes');
} catch (DiviLocked $locked) {
self::assertSame('5.3.0', $locked->status->version);
@@ -114,20 +89,24 @@ final class ReleaseGateTest extends TestCase
$this->untested();
$result = ReleaseGate::whileProbing(41, static function (): string {
ReleaseGate::assertWritable(41);
ReleaseGate::assertFeatures(DiviFeature::cases(), 41);
return 'written';
});
self::assertSame('written', $result);
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()),
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures(
DiviFeature::cases(),
42
)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures(
DiviFeature::cases()
)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature(
DiviFeature::PageWrite,
42
)),
static fn () => ReleaseGate::assertWritable(41)] as $attempt) {
static fn () => ReleaseGate::assertFeature(DiviFeature::PageWrite, 41)] as $attempt) {
try {
$attempt();
self::fail('only post 41, only while probing');
@@ -149,7 +128,7 @@ final class ReleaseGateTest extends TestCase
}
$this->expectException(DiviLocked::class);
ReleaseGate::assertWritable(41);
ReleaseGate::assertFeature(DiviFeature::PageWrite, 41);
}
public function testStatusIsComputedOncePerRequestUntilForgotten(): void
@@ -158,7 +137,7 @@ final class ReleaseGateTest extends TestCase
ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus {
++$calls;
return ReleaseStatus::released('5.2.1');
return ReleaseMatrices::released('5.2.1');
});
ReleaseGate::status();
@@ -198,7 +177,7 @@ final class ReleaseGateTest extends TestCase
public function testFindsTheLockInsideAWrappedFailure(): void
{
$locked = new DiviLocked(new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
$locked = new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested, '5.3.0'), [DiviFeature::PageWrite]);
$wrapped = WriteFailed::rolledBack('Nicht gespeichert.', new RuntimeException('draft', 0, $locked));
self::assertSame($locked, DiviLocked::in($wrapped));
@@ -145,7 +145,9 @@ final class ReleaseGuardsTest extends TestCase
public function testTheCheckPageMayBeWrittenWhileProbing(): void
{
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::uniform(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(
@@ -124,7 +124,7 @@ final class ReleaseResolverTest extends TestCase
])->resolve();
self::assertSame(self::allBut('released'), self::states($status));
self::assertTrue($status->writable());
self::assertSame([], $status->matrix->unusable());
self::assertSame([], $status->problems);
}
@@ -142,7 +142,7 @@ final class ReleaseResolverTest extends TestCase
self::assertSame(['Seiten: et_x()'], $status->matrix->of(DiviFeature::PageWrite)->problems);
self::assertSame(ReleaseState::Incompatible, $status->state);
self::assertSame(['Seiten: et_x()', 'Katalog: Konstante X'], $status->problems);
self::assertFalse($status->writable());
self::assertFalse($status->matrix->of(DiviFeature::PageWrite)->usable());
}
public function testAProblemWithoutFeaturesLocksEveryFeature(): void
@@ -181,7 +181,7 @@ final class ReleaseResolverTest extends TestCase
self::assertSame(self::allBut('released'), self::states($status));
self::assertSame('auto', $status->source);
self::assertSame('auto', $status->matrix->of(DiviFeature::PresetWrite)->source);
self::assertTrue($status->writable());
self::assertSame([], $status->matrix->unusable());
}
/** Review focus 3: the automatic release needs every signature unchanged. */
@@ -196,7 +196,7 @@ final class ReleaseResolverTest extends TestCase
['Designsystem: x() – Signatur geändert'],
$status->matrix->of(DiviFeature::PresetWrite)->problems
);
self::assertFalse($status->writable());
self::assertFalse($status->matrix->of(DiviFeature::PresetWrite)->usable());
}
public function testAMissingReferenceMakesItsFeatureUntestedUnderAutomaticRelease(): void
@@ -15,52 +15,6 @@ use LogicException;
final class ReleaseStatusTest extends TestCase
{
public function testOnlyAReleasedVersionIsWritable(): void
{
self::assertTrue(ReleaseStatus::released('5.2.1')->writable());
self::assertFalse((new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->writable());
self::assertFalse((new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['x']))->writable());
}
public function testUntestedRefusalNamesTheVersionAndTheWayOut(): void
{
$payload = self::assertToolError(
(new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->refusal(),
'divi_untested'
);
self::assertSame(
'Achtung: Divi 5.3.0 ist auf dieser Website noch nicht für LIT MCP freigegeben. Dieses Werkzeug braucht '
. 'Divi-Fähigkeiten, die noch nicht geprüft sind; andere Divi-Werkzeuge laufen weiter. Ein '
. 'Administrator kann unter Einstellungen → LIT MCP → Divi die Prüfung ausführen und die Version '
. 'freigeben. Es wurde nichts geändert.',
$payload['message']
);
self::assertStringContainsString('ein Werkzeug ohne die betroffene Fähigkeit nutzen', $payload['hint']);
self::assertStringContainsString('components → divi → features', $payload['hint']);
}
public function testIncompatibleRefusalListsTheProblems(): void
{
$status = new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['Theme Builder: Nachbau von x()']);
$payload = self::assertToolError($status->refusal(), 'divi_incompatible');
self::assertSame(
'Divi 5.3.0 hat Stellen geändert, die LIT MCP für dieses Werkzeug nutzt; andere Divi-Werkzeuge laufen '
. 'weiter. Die betroffenen Werkzeuge bleiben gesperrt, bis LIT MCP aktualisiert wurde. Es wurde '
. 'nichts geändert.',
$payload['message']
);
self::assertSame(['Theme Builder: Nachbau von x()'], $payload['problems']);
}
public function testAReleasedStatusHasNoRefusal(): void
{
$this->expectException(LogicException::class);
ReleaseStatus::released('5.2.1')->refusal();
}
public function testToArrayForSiteStatus(): void
{
$status = new ReleaseStatus(
@@ -82,7 +36,7 @@ final class ReleaseStatusTest extends TestCase
{
$untested = new ReleaseStatus(ReleaseState::Untested, '5.3.0');
$broken = new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['x']);
$released = ReleaseStatus::released('5.2.1', ReleaseStatus::SOURCE_DATABASE);
$released = ReleaseMatrices::released('5.2.1', ReleaseStatus::SOURCE_DATABASE);
foreach (DiviFeature::cases() as $feature) {
self::assertSame(ReleaseState::Untested, $untested->matrix->of($feature)->state);
@@ -106,17 +60,6 @@ final class ReleaseStatusTest extends TestCase
self::assertSame('auto', ReleaseStatus::of('5.99.0', CompatibilityMatrix::allReleased('auto'))->source);
}
public function testWritableAsksOnlyTheWritingFeatures(): void
{
$readLocked = ReleaseMatrices::status([DiviFeature::Catalog, DiviFeature::PageRead]);
self::assertSame(ReleaseState::Untested, $readLocked->state);
self::assertTrue($readLocked->writable());
foreach (CompatibilityMatrix::writeFeatures() as $feature) {
self::assertFalse(ReleaseMatrices::status([$feature])->writable(), $feature->value);
}
}
public function testAMatrixInAnotherStateThanTheStatusIsRefused(): void
{
$this->expectException(LogicException::class);
@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\Divi\Release\WriteProbe;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Render\PageRenderer;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use RuntimeException;
@@ -25,7 +27,7 @@ final class WriteProbeTest extends TestCase
{
parent::setUp();
// The probe must work while Divi is untested – that is when it runs.
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested));
$this->deleted = [];
Functions\when('wp_insert_post')->justReturn(41);
Functions\when('get_post')->justReturn(
@@ -47,7 +49,7 @@ final class WriteProbeTest extends TestCase
$composer = Mockery::mock(ContentComposer::class);
$default = static function (int $id, string $type, array $sections): array {
// The composer writes through the saver, which asks the gate: the probe must have opened it for 41.
ReleaseGate::assertWritable($id);
ReleaseGate::assertFeatures(DiviFeature::cases(), $id);
return ['warnings' => [], 'outline' => ''];
};
@@ -100,7 +102,7 @@ final class WriteProbeTest extends TestCase
self::assertSame(['Schreibtest abgebrochen: Composer kaputt'], $problems);
self::assertSame([41], $this->deleted);
$this->expectException(\LIT\MCP\Integrations\Divi\Release\DiviLocked::class);
ReleaseGate::assertWritable(41);
ReleaseGate::assertFeature(DiviFeature::PageWrite, 41);
}
public function testReportsAPageThatCouldNotBeDeleted(): void
@@ -11,8 +11,7 @@ use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Content\ContentCreateAbility;
use LIT\MCP\Integrations\WordPress\Content\NewDraft;
@@ -20,6 +19,7 @@ use LIT\MCP\Integrations\WordPress\Content\PostWriter;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Tests\Support\DiviWriterFixture;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Support\SchemaFixture;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
@@ -228,7 +228,7 @@ final class ContentCreateDiviTest extends TestCase
*/
public function testLockedDiviRefusesBeforeTheDraftExists(): void
{
$refusal = (new ReleaseStatus(ReleaseState::Untested, '5.3.0'))->refusal();
$refusal = ReleaseMatrices::status([DiviFeature::PageWrite])->refusalFor([DiviFeature::PageWrite]);
Functions\expect('wp_insert_post')->never();
$result = $this->ability($this->composer(true, true, $refusal))->execute(self::INPUT);