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
@@ -59,15 +59,6 @@ final class CompatibilityMatrix
return new self($features); return new self($features);
} }
/** @return list<DiviFeature> The features that write (ReleaseGate::assertWritable(), ReleaseStatus::writable()). */
public static function writeFeatures(): array
{
return array_values(array_filter(
DiviFeature::cases(),
static fn (DiviFeature $feature): bool => $feature->isWrite()
));
}
public function of(DiviFeature $feature): FeatureStatus public function of(DiviFeature $feature): FeatureStatus
{ {
return $this->features[$feature->value]; return $this->features[$feature->value];
+13 -7
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release; namespace LIT\MCP\Integrations\Divi\Release;
use LIT\MCP\Abilities\ToolRefusal; use LIT\MCP\Abilities\ToolRefusal;
use LogicException;
use RuntimeException; use RuntimeException;
use Throwable; use Throwable;
use WP_Error; use WP_Error;
@@ -13,26 +14,31 @@ defined('ABSPATH') || exit;
/** /**
* A Divi call refused because a feature it needs is not released (spec §5.5, D6); AbilityRegistrar returns * A Divi call refused because a feature it needs is not released (spec §5.5, D6); AbilityRegistrar returns
* toError(). Without features it is the general write lock of the overall status (texts of spec §5.6). * toError().
*/ */
final class DiviLocked extends RuntimeException implements ToolRefusal final class DiviLocked extends RuntimeException implements ToolRefusal
{ {
/** @param list<DiviFeature> $features The locked features the refused call needs; [] = writing in general. */ /**
public function __construct(public readonly ReleaseStatus $status, public readonly array $features = []) * @param non-empty-list<DiviFeature> $features The locked features the refused call needs.
* @throws LogicException When $features is empty – a lock always names what it locks.
*/
public function __construct(public readonly ReleaseStatus $status, public readonly array $features)
{ {
if ($features === []) {
throw new LogicException('a Divi lock needs the locked features');
}
parent::__construct(sprintf( parent::__construct(sprintf(
'%s is %s for LIT MCP – %s refused', '%s is %s for LIT MCP – %s refused',
$status->versionLabel(), $status->versionLabel(),
$status->state->value, $status->state->value,
$features === [] implode(', ', array_map(static fn (DiviFeature $feature): string => $feature->value, $features))
? 'write'
: implode(', ', array_map(static fn (DiviFeature $feature): string => $feature->value, $features))
)); ));
} }
public function toError(): WP_Error public function toError(): WP_Error
{ {
return $this->features === [] ? $this->status->refusal() : $this->status->refusalFor($this->features); return $this->status->refusalFor($this->features);
} }
/** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */ /** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */
@@ -92,16 +92,6 @@ final class ReleaseGate
} }
} }
/**
* Every writing feature (CompatibilityMatrix::writeFeatures()) – for callers that know no feature.
*
* @throws DiviLocked
*/
public static function assertWritable(?int $postId = null): void
{
self::assertFeatures(CompatibilityMatrix::writeFeatures(), $postId);
}
public static function whileProbing(int $postId, callable $write): mixed public static function whileProbing(int $postId, callable $write): mixed
{ {
$previous = self::$probe; $previous = self::$probe;
@@ -11,8 +11,8 @@ use WP_Error;
defined('ABSPATH') || exit; defined('ABSPATH') || exit;
/** /**
* The release of the installed Divi (spec §5.2): the matrix of its features (E1) and, for callers that know no * The release of the installed Divi (spec §5.2): the matrix of its features (E1), the overall state (the worst
* features, the overall state (the worst feature state) with every problem, and the texts of spec §5.6. * feature state) with every problem, and the refusal texts of a call whose features are locked (D6).
*/ */
final class ReleaseStatus final class ReleaseStatus
{ {
@@ -20,19 +20,6 @@ final class ReleaseStatus
public const SOURCE_DATABASE = 'database'; public const SOURCE_DATABASE = 'database';
public const SOURCE_AUTO = 'auto'; public const SOURCE_AUTO = 'auto';
private const UNTESTED = 'Achtung: %s 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.';
private const UNTESTED_HINT = 'Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben; '
. 'bis dahin ein Werkzeug ohne die betroffene Fähigkeit nutzen. lit-site-status zeigt unter components → '
. 'divi → features, welche Fähigkeiten und Werkzeuge betroffen sind.';
private const INCOMPATIBLE = '%s 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.';
private const INCOMPATIBLE_HINT = 'Den Nutzer informieren, dass LIT MCP aktualisiert werden muss; bis dahin '
. 'ein Werkzeug ohne die betroffene Fähigkeit nutzen. lit-site-status zeigt unter components → divi → '
. 'features, welche Fähigkeiten und Werkzeuge betroffen sind.';
private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP ' private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP '
. 'freigegeben. Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung ' . '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.'; . 'ausführt und die Version freigibt. Es wurde nichts geändert.';
@@ -74,43 +61,11 @@ final class ReleaseStatus
return new self($matrix->state(), $version, $matrix->source(), $matrix->problems(), $release, $matrix); return new self($matrix->state(), $version, $matrix->source(), $matrix->problems(), $release, $matrix);
} }
public static function released(string $version, string $source = self::SOURCE_CODE): self
{
return new self(ReleaseState::Released, $version, $source);
}
/** Every writing feature is usable – for callers that know no feature (general write lock of DiviLocked). */
public function writable(): bool
{
return $this->matrix->usable(CompatibilityMatrix::writeFeatures());
}
public function versionLabel(): string public function versionLabel(): string
{ {
return $this->version === '' ? 'Divi (Version unbekannt)' : 'Divi ' . $this->version; return $this->version === '' ? 'Divi (Version unbekannt)' : 'Divi ' . $this->version;
} }
/** The error of a refused write. @throws LogicException For a released status. */
public function refusal(): WP_Error
{
return match ($this->state) {
ReleaseState::Untested => Errors::make(
'divi_untested',
sprintf(self::UNTESTED, $this->versionLabel()),
self::UNTESTED_HINT,
423
),
ReleaseState::Incompatible => Errors::make(
'divi_incompatible',
sprintf(self::INCOMPATIBLE, $this->versionLabel()),
self::INCOMPATIBLE_HINT,
423,
['problems' => $this->problems]
),
ReleaseState::Released => throw new LogicException('a released Divi refuses nothing'),
};
}
/** /**
* The error of a call refused because some of $features are locked (D6): divi_incompatible when one of them is * The error of a call refused because some of $features are locked (D6): divi_incompatible when one of them is
* incompatible, else divi_untested; HTTP 423. The message names the features in that state, the hint every * incompatible, else divi_untested; HTTP 423. The message names the features in that state, the hint every
+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\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus; 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 final class ReleaseMatrices
{ {
/** @param list<DiviFeature> $locked */ /** @param list<DiviFeature> $locked */
@@ -38,4 +41,27 @@ final class ReleaseMatrices
): ReleaseStatus { ): ReleaseStatus {
return ReleaseStatus::of($version, self::matrix($locked, $state)); 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\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate; use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; 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\Divi\Release\RequiresDiviFeatures;
use LIT\MCP\Integrations\Integration; use LIT\MCP\Integrations\Integration;
use LIT\MCP\Integrations\IntegrationRegistry; use LIT\MCP\Integrations\IntegrationRegistry;
use LIT\MCP\Server\RequestContext; use LIT\MCP\Server\RequestContext;
use LIT\MCP\Tests\Support\DiviFeatureFixtures; use LIT\MCP\Tests\Support\DiviFeatureFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use RuntimeException; use RuntimeException;
@@ -281,7 +281,10 @@ final class AbilityRegistrarTest extends TestCase
public function testALockThrownInsideAWriteBecomesTheLockError(): void 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 = $this->ability();
$ability->allows('execute')->andThrow(WriteFailed::rolledBack('Nicht gespeichert.', $locked)); $ability->allows('execute')->andThrow(WriteFailed::rolledBack('Nicht gespeichert.', $locked));
@@ -302,7 +305,7 @@ final class AbilityRegistrarTest extends TestCase
public function testAPartialWriteStaysPartialEvenWithALockInside(): void 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 = $this->ability();
$ability->allows('execute')->andThrow(WriteFailed::partial('Teilweise gespeichert.', $locked)); $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\FakeChangeLog;
use LIT\MCP\Tests\Support\FeatureFakeTool; use LIT\MCP\Tests\Support\FeatureFakeTool;
use LIT\MCP\Tests\Support\MatrixFixtures; use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use WP_Post; use WP_Post;
@@ -197,7 +198,7 @@ final class DiviTabTest extends TestCase
public function testUntestedWithAFreshGreenReportOffersTheRelease(): void 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('ungetestet', $html);
self::assertStringContainsString('Prüfung starten', $html); self::assertStringContainsString('Prüfung starten', $html);
@@ -207,9 +208,9 @@ final class DiviTabTest extends TestCase
public function testCheckButtonSaysWhatItDoesAndLeadsWhileNothingElseCanBeDone(): void public function testCheckButtonSaysWhatItDoesAndLeadsWhileNothingElseCanBeDone(): void
{ {
$stale = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report(true, '5.2.9')); $stale = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report(true, '5.2.9'));
$fresh = $this->rendered(new ReleaseStatus(ReleaseState::Untested, '5.3.0'), self::report()); $fresh = $this->rendered(ReleaseMatrices::uniform(ReleaseState::Untested), self::report());
$released = $this->rendered(ReleaseStatus::released('5.3.0'), self::report()); $released = $this->rendered(ReleaseMatrices::released('5.3.0'), self::report());
foreach ([$stale, $fresh, $released] as $html) { foreach ([$stale, $fresh, $released] as $html) {
self::assertStringContainsString('legt eine private Testseite an', $html); self::assertStringContainsString('legt eine private Testseite an', $html);
@@ -222,7 +223,7 @@ final class DiviTabTest extends TestCase
public function testUntestedWithAStaleReportAsksForANewCheck(): void 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::assertStringNotContainsString('Divi 5.3.0 freigeben', $html);
self::assertStringContainsString('Prüfung erneut ausführen', $html); self::assertStringContainsString('Prüfung erneut ausführen', $html);
@@ -230,7 +231,7 @@ final class DiviTabTest extends TestCase
public function testReleasedFromCodeOffersNeitherReleaseNorWithdrawal(): void 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('freigegeben', $html);
self::assertStringContainsString('im Plugin als getestet hinterlegt', $html); self::assertStringContainsString('im Plugin als getestet hinterlegt', $html);
@@ -240,7 +241,7 @@ final class DiviTabTest extends TestCase
public function testAnAutomaticReleaseSaysSoAndOffersNeitherReleaseNorWithdrawal(): void 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::assertStringContainsString('Automatisch freigegeben', $html);
self::assertStringNotContainsString('freigeben</button>', $html); self::assertStringNotContainsString('freigeben</button>', $html);
@@ -249,7 +250,7 @@ final class DiviTabTest extends TestCase
public function testADatabaseReleaseCanBeWithdrawn(): void 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, '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 public function testIncompatibleListsTheProblems(): void
{ {
$html = $this->rendered( $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 null
); );
@@ -316,7 +317,7 @@ final class DiviTabTest extends TestCase
$tab = $this->tab(self::report(), Mockery::mock(ReleaseStore::class)); $tab = $this->tab(self::report(), Mockery::mock(ReleaseStore::class));
$tab->removeOrphans(); $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); 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('get_posts')->never();
Functions\expect('wp_delete_post')->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); self::assertStringNotContainsString('Liegengebliebene Prüfseiten', $html);
} }
@@ -367,7 +368,7 @@ final class DiviTabTest extends TestCase
new FeatureStatus(DiviFeature::Catalog, ReleaseState::Untested, [], null), 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 public function testTheFeatureTableShowsStateSourceProblemsAndTools(): void
@@ -403,11 +404,11 @@ final class DiviTabTest extends TestCase
public function testTheTableNamesEverySourceInWords(): void 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( $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, 'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
], matrix: MatrixFixtures::released('database')), ]),
self::report() self::report()
); );
@@ -417,7 +418,7 @@ final class DiviTabTest extends TestCase
public function testTheTabExplainsTheAutomationAndTheFilter(): void 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('gibt LIT MCP jede Fähigkeit einzeln frei', $html);
self::assertStringContainsString('<code>lit_mcp_divi_auto_release</code>', $html); self::assertStringContainsString('<code>lit_mcp_divi_auto_release</code>', $html);
@@ -429,11 +430,7 @@ final class DiviTabTest extends TestCase
public function testTheTabNoLongerSaysWritingIsLockedAsAWhole(): void public function testTheTabNoLongerSaysWritingIsLockedAsAWhole(): void
{ {
$untested = $this->rendered( $untested = $this->rendered(
new ReleaseStatus( ReleaseStatus::of('5.3.0', MatrixFixtures::with(DiviFeature::Catalog, ReleaseState::Untested)),
ReleaseState::Untested,
'5.3.0',
matrix: MatrixFixtures::with(DiviFeature::Catalog, ReleaseState::Untested)
),
null null
); );
$incompatible = $this->rendered(self::mixedStatus(), null); $incompatible = $this->rendered(self::mixedStatus(), null);
@@ -450,9 +447,9 @@ final class DiviTabTest extends TestCase
$matrix = MatrixFixtures::released('database', [ $matrix = MatrixFixtures::released('database', [
new FeatureStatus(DiviFeature::PageWrite, ReleaseState::Incompatible, ['Seiten: x() fehlt'], null), 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, 'released_at' => '2026-10-01T08:00:00+00:00', 'user_id' => 1,
], matrix: $matrix); ]);
self::assertStringContainsString('Freigabe zurückziehen', $this->rendered($status, self::report())); self::assertStringContainsString('Freigabe zurückziehen', $this->rendered($status, self::report()));
} }
@@ -495,7 +492,7 @@ final class DiviTabTest extends TestCase
public function testNoNoticeWhileEveryFeatureIsUsable(): void 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 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\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus; use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\MatrixFixtures; use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use RuntimeException; use RuntimeException;
@@ -354,7 +355,7 @@ final class DiviCheckCommandTest extends TestCase
ReleaseGate::useResolver(static function () use (&$order): ReleaseStatus { ReleaseGate::useResolver(static function () use (&$order): ReleaseStatus {
$order[] = 'resolve'; $order[] = 'resolve';
return ReleaseStatus::released('5.3.0'); return ReleaseMatrices::released('5.3.0');
}); });
$this->command()->run([]); $this->command()->run([]);
@@ -374,7 +374,9 @@ final class DesignChangeTest extends TestCase
public function testLockedDiviWritesNoSnapshot(): void 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]; $stored = ['v' => 1];
$area = self::area($stored); $area = self::area($stored);
$snapshots = Mockery::mock(SnapshotStore::class); $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\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus; use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
final class DesignSystemWriterTest extends TestCase final class DesignSystemWriterTest extends TestCase
@@ -38,7 +39,9 @@ final class DesignSystemWriterTest extends TestCase
public function testWritesNeedAReleasedDivi(): void 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); $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\Design\SnapshotStore;
use LIT\MCP\Integrations\Divi\Library\GlobalUsages; use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
use LIT\MCP\Integrations\Divi\Library\UsageReport; 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\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; 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\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use Mockery\MockInterface; use Mockery\MockInterface;
@@ -378,11 +379,14 @@ final class DiviDesignDetachAbilityTest extends TestCase
{ {
return [ return [
'rolled back' => [WriteFailed::rolledBack('Seite 12 nicht gespeichert.'), 'write_failed'], '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' => [ 'Divi locked inside a rollback' => [
WriteFailed::rolledBack( WriteFailed::rolledBack(
'Seite 12 nicht gespeichert.', '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', '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\DiviPageLoader;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver; use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Pages\SaveScope; 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\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; 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\FakeChangeLog;
use LIT\MCP\Tests\Support\LibraryFixtures; use LIT\MCP\Tests\Support\LibraryFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use Throwable; use Throwable;
@@ -456,7 +457,9 @@ final class PageRewriterTest extends TestCase
public function testLockedDiviBeforeTheFirstPageIsThrown(): void public function testLockedDiviBeforeTheFirstPageIsThrown(): void
{ {
$rewriter = $this->rewriter([self::page(101, self::layout(self::BUTTON))]); $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); $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\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\FeatureFakeTool; use LIT\MCP\Tests\Support\FeatureFakeTool;
use LIT\MCP\Tests\Support\MatrixFixtures; use LIT\MCP\Tests\Support\MatrixFixtures;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
@@ -83,14 +84,7 @@ final class DiviEnvironmentComponentTest extends TestCase
'auto', 'auto',
'Seiten: Signatur von et_pb_x() geändert' 'Seiten: Signatur von et_pb_x() geändert'
); );
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus( ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::of('5.3.0', $matrix));
ReleaseState::Incompatible,
'5.3.0',
null,
[],
null,
matrix: $matrix
));
$overview = new FeatureOverview(static fn (): array => [ $overview = new FeatureOverview(static fn (): array => [
new FeatureFakeTool('lit/divi-outline', true, [DiviFeature::PageRead]), new FeatureFakeTool('lit/divi-outline', true, [DiviFeature::PageRead]),
new FeatureFakeTool('lit/divi-insert', true, [DiviFeature::PageRead, DiviFeature::PageWrite]), 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 { ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus {
++$calls; ++$calls;
return ReleaseStatus::released('5.3.0'); return ReleaseMatrices::released('5.3.0');
}); });
$component = new DiviEnvironmentComponent(new DiviCompatibility([])); $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 public function testToArrayListsEveryFeatureInCaseOrder(): void
{ {
$rows = self::matrix(['tb-write' => [ReleaseState::Incompatible, ['b']]])->toArray(); $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\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked; use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Tests\Support\ReleaseMatrices; use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use LogicException;
final class DiviLockedTest extends TestCase final class DiviLockedTest extends TestCase
{ {
@@ -26,12 +26,11 @@ final class DiviLockedTest extends TestCase
self::assertSame(['preset-write'], array_column($payload['features'], 'id')); 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'); $this->expectException(LogicException::class);
$locked = new DiviLocked($status); $this->expectExceptionMessage('a Divi lock needs the locked features');
self::assertSame('Divi 5.3.0 is untested for LIT MCP – write refused', $locked->getMessage()); new DiviLocked(ReleaseMatrices::uniform(ReleaseState::Untested), []);
self::assertEquals($status->refusal(), $locked->toError());
} }
} }
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release; namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use LIT\MCP\Abilities\WriteFailed; 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\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked; use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate; use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
@@ -19,15 +18,13 @@ final class ReleaseGateTest extends TestCase
{ {
private function untested(): void 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 public function testTheTestCaseDefaultReleasesEveryFeature(): void
{ {
self::assertTrue(ReleaseGate::status()->writable());
self::assertSame([], ReleaseGate::status()->matrix->unusable()); self::assertSame([], ReleaseGate::status()->matrix->unusable());
ReleaseGate::assertWritable(7); ReleaseGate::assertFeatures(DiviFeature::cases(), 7);
ReleaseGate::assertFeatures(DiviFeature::cases());
} }
public function testFeatureReadsTheMatrixOfThisRequest(): void public function testFeatureReadsTheMatrixOfThisRequest(): void
@@ -74,34 +71,12 @@ final class ReleaseGateTest extends TestCase
} }
} }
public function testAssertWritableNeedsEveryWritingFeature(): void public function testAssertFeatureThrowsTheLockWithTheStatus(): 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
{ {
$this->untested(); $this->untested();
try { try {
ReleaseGate::assertWritable(7); ReleaseGate::assertFeature(DiviFeature::PageWrite, 7);
self::fail('an untested Divi must lock writes'); self::fail('an untested Divi must lock writes');
} catch (DiviLocked $locked) { } catch (DiviLocked $locked) {
self::assertSame('5.3.0', $locked->status->version); self::assertSame('5.3.0', $locked->status->version);
@@ -114,20 +89,24 @@ final class ReleaseGateTest extends TestCase
$this->untested(); $this->untested();
$result = ReleaseGate::whileProbing(41, static function (): string { $result = ReleaseGate::whileProbing(41, static function (): string {
ReleaseGate::assertWritable(41);
ReleaseGate::assertFeatures(DiviFeature::cases(), 41); ReleaseGate::assertFeatures(DiviFeature::cases(), 41);
return 'written'; return 'written';
}); });
self::assertSame('written', $result); self::assertSame('written', $result);
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)), foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures(
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()), DiviFeature::cases(),
42
)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeatures(
DiviFeature::cases()
)),
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature( static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature(
DiviFeature::PageWrite, DiviFeature::PageWrite,
42 42
)), )),
static fn () => ReleaseGate::assertWritable(41)] as $attempt) { static fn () => ReleaseGate::assertFeature(DiviFeature::PageWrite, 41)] as $attempt) {
try { try {
$attempt(); $attempt();
self::fail('only post 41, only while probing'); self::fail('only post 41, only while probing');
@@ -149,7 +128,7 @@ final class ReleaseGateTest extends TestCase
} }
$this->expectException(DiviLocked::class); $this->expectException(DiviLocked::class);
ReleaseGate::assertWritable(41); ReleaseGate::assertFeature(DiviFeature::PageWrite, 41);
} }
public function testStatusIsComputedOncePerRequestUntilForgotten(): void public function testStatusIsComputedOncePerRequestUntilForgotten(): void
@@ -158,7 +137,7 @@ final class ReleaseGateTest extends TestCase
ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus { ReleaseGate::useResolver(static function () use (&$calls): ReleaseStatus {
++$calls; ++$calls;
return ReleaseStatus::released('5.2.1'); return ReleaseMatrices::released('5.2.1');
}); });
ReleaseGate::status(); ReleaseGate::status();
@@ -198,7 +177,7 @@ final class ReleaseGateTest extends TestCase
public function testFindsTheLockInsideAWrappedFailure(): void 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)); $wrapped = WriteFailed::rolledBack('Nicht gespeichert.', new RuntimeException('draft', 0, $locked));
self::assertSame($locked, DiviLocked::in($wrapped)); self::assertSame($locked, DiviLocked::in($wrapped));
@@ -145,7 +145,9 @@ final class ReleaseGuardsTest extends TestCase
public function testTheCheckPageMayBeWrittenWhileProbing(): void 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('wp_update_post')->justReturn(41);
Functions\when('update_post_meta')->justReturn(true); Functions\when('update_post_meta')->justReturn(true);
Functions\when('get_post_meta')->alias( Functions\when('get_post_meta')->alias(
@@ -124,7 +124,7 @@ final class ReleaseResolverTest extends TestCase
])->resolve(); ])->resolve();
self::assertSame(self::allBut('released'), self::states($status)); self::assertSame(self::allBut('released'), self::states($status));
self::assertTrue($status->writable()); self::assertSame([], $status->matrix->unusable());
self::assertSame([], $status->problems); 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(['Seiten: et_x()'], $status->matrix->of(DiviFeature::PageWrite)->problems);
self::assertSame(ReleaseState::Incompatible, $status->state); self::assertSame(ReleaseState::Incompatible, $status->state);
self::assertSame(['Seiten: et_x()', 'Katalog: Konstante X'], $status->problems); 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 public function testAProblemWithoutFeaturesLocksEveryFeature(): void
@@ -181,7 +181,7 @@ final class ReleaseResolverTest extends TestCase
self::assertSame(self::allBut('released'), self::states($status)); self::assertSame(self::allBut('released'), self::states($status));
self::assertSame('auto', $status->source); self::assertSame('auto', $status->source);
self::assertSame('auto', $status->matrix->of(DiviFeature::PresetWrite)->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. */ /** Review focus 3: the automatic release needs every signature unchanged. */
@@ -196,7 +196,7 @@ final class ReleaseResolverTest extends TestCase
['Designsystem: x() – Signatur geändert'], ['Designsystem: x() – Signatur geändert'],
$status->matrix->of(DiviFeature::PresetWrite)->problems $status->matrix->of(DiviFeature::PresetWrite)->problems
); );
self::assertFalse($status->writable()); self::assertFalse($status->matrix->of(DiviFeature::PresetWrite)->usable());
} }
public function testAMissingReferenceMakesItsFeatureUntestedUnderAutomaticRelease(): void public function testAMissingReferenceMakesItsFeatureUntestedUnderAutomaticRelease(): void
@@ -15,52 +15,6 @@ use LogicException;
final class ReleaseStatusTest extends TestCase 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 public function testToArrayForSiteStatus(): void
{ {
$status = new ReleaseStatus( $status = new ReleaseStatus(
@@ -82,7 +36,7 @@ final class ReleaseStatusTest extends TestCase
{ {
$untested = new ReleaseStatus(ReleaseState::Untested, '5.3.0'); $untested = new ReleaseStatus(ReleaseState::Untested, '5.3.0');
$broken = new ReleaseStatus(ReleaseState::Incompatible, '5.3.0', null, ['x']); $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) { foreach (DiviFeature::cases() as $feature) {
self::assertSame(ReleaseState::Untested, $untested->matrix->of($feature)->state); 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); 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 public function testAMatrixInAnotherStateThanTheStatusIsRefused(): void
{ {
$this->expectException(LogicException::class); $this->expectException(LogicException::class);
@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release; namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use Brain\Monkey\Functions; use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate; use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; use LIT\MCP\Integrations\Divi\Release\ReleaseState;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus; use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\Divi\Release\WriteProbe; use LIT\MCP\Integrations\Divi\Release\WriteProbe;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer; use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Render\PageRenderer; use LIT\MCP\Integrations\WordPress\Render\PageRenderer;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
use RuntimeException; use RuntimeException;
@@ -25,7 +27,7 @@ final class WriteProbeTest extends TestCase
{ {
parent::setUp(); parent::setUp();
// The probe must work while Divi is untested – that is when it runs. // 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 = []; $this->deleted = [];
Functions\when('wp_insert_post')->justReturn(41); Functions\when('wp_insert_post')->justReturn(41);
Functions\when('get_post')->justReturn( Functions\when('get_post')->justReturn(
@@ -47,7 +49,7 @@ final class WriteProbeTest extends TestCase
$composer = Mockery::mock(ContentComposer::class); $composer = Mockery::mock(ContentComposer::class);
$default = static function (int $id, string $type, array $sections): array { $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. // 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' => '']; return ['warnings' => [], 'outline' => ''];
}; };
@@ -100,7 +102,7 @@ final class WriteProbeTest extends TestCase
self::assertSame(['Schreibtest abgebrochen: Composer kaputt'], $problems); self::assertSame(['Schreibtest abgebrochen: Composer kaputt'], $problems);
self::assertSame([41], $this->deleted); self::assertSame([41], $this->deleted);
$this->expectException(\LIT\MCP\Integrations\Divi\Release\DiviLocked::class); $this->expectException(\LIT\MCP\Integrations\Divi\Release\DiviLocked::class);
ReleaseGate::assertWritable(41); ReleaseGate::assertFeature(DiviFeature::PageWrite, 41);
} }
public function testReportsAPageThatCouldNotBeDeleted(): void public function testReportsAPageThatCouldNotBeDeleted(): void
@@ -11,8 +11,7 @@ use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\ChangeLog\ChangeLog; use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer; use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver; use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Release\ReleaseState; use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer; use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Content\ContentCreateAbility; use LIT\MCP\Integrations\WordPress\Content\ContentCreateAbility;
use LIT\MCP\Integrations\WordPress\Content\NewDraft; 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\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Tests\Support\DiviWriterFixture; use LIT\MCP\Tests\Support\DiviWriterFixture;
use LIT\MCP\Tests\Support\FakeChangeLog; use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Support\SchemaFixture; use LIT\MCP\Tests\Support\SchemaFixture;
use LIT\MCP\Tests\Unit\TestCase; use LIT\MCP\Tests\Unit\TestCase;
use Mockery; use Mockery;
@@ -228,7 +228,7 @@ final class ContentCreateDiviTest extends TestCase
*/ */
public function testLockedDiviRefusesBeforeTheDraftExists(): void 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(); Functions\expect('wp_insert_post')->never();
$result = $this->ability($this->composer(true, true, $refusal))->execute(self::INPUT); $result = $this->ability($this->composer(true, true, $refusal))->execute(self::INPUT);