feat(compat): Profile, Gates und Pods über den Kompatibilitätskern
Neu: Tier, Profile, Compatibility, CapabilityLocked, CapabilityGate, CompatibilityComponent, ProvidesCompatibility, DiviProfile sowie PodsFeature, PodsCompatibility und PodsIntegration. Das CapabilityGate sperrt gesperrte und unbekannte Fähigkeits-IDs (übernimmt die Sperre unbekannter IDs vom DiviFeatureGate) und delegiert nur Divi ausdrücklich. Pods-Schema, -Lesen, -Schreiben und -Feldrechte laufen über usable(PodsFeature::…); Schreiben verlangt FieldRead und FieldWrite, damit ein Rollback keine Werte verliert. lit-site-status meldet Pods über die CompatibilityComponent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b511ef6348
commit
c85131603b
+115
-1
@@ -10,6 +10,19 @@ use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\ExposurePolicy;
|
||||
use LIT\MCP\Abilities\ToolAvailability;
|
||||
use LIT\MCP\Admin\SettingsPage;
|
||||
use LIT\MCP\Compatibility\CapabilityGate;
|
||||
use LIT\MCP\Compatibility\Compatibility;
|
||||
use LIT\MCP\Compatibility\Profile;
|
||||
use LIT\MCP\Compatibility\Tier;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeatureGate;
|
||||
use LIT\MCP\Integrations\Pods\PodsFeature;
|
||||
use LIT\MCP\Integrations\Pods\PodsFieldSource;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\SampledMetaSource;
|
||||
use LIT\MCP\Integrations\Core\SiteStatusAbility;
|
||||
use LIT\MCP\Access;
|
||||
use ReflectionProperty;
|
||||
use WP_User;
|
||||
use LIT\MCP\Integrations\IntegrationRegistry;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Limits;
|
||||
@@ -34,7 +47,7 @@ final class PluginTest extends TestCase
|
||||
public function testIntegrationsInRegistryOrder(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder'],
|
||||
['core', 'wordpress', 'media', 'divi', 'divi-library', 'divi-theme-builder', 'pods'],
|
||||
self::registry()->ids()
|
||||
);
|
||||
}
|
||||
@@ -68,4 +81,105 @@ final class PluginTest extends TestCase
|
||||
self::assertTrue($tools->groupWritable('divi-theme-builder'));
|
||||
self::assertFalse($tools->allows('lit/divi-insert'), 'no Divi in unit tests: lit/divi-insert not registered');
|
||||
}
|
||||
|
||||
/** Pods has no tools: it is in the registry for its profile only and shows up as no group. */
|
||||
public function testPodsIsRegisteredWithoutBecomingAToolGroup(): void
|
||||
{
|
||||
$registry = self::registry();
|
||||
|
||||
self::assertContains('pods', $registry->ids());
|
||||
self::assertArrayNotHasKey('pods', $registry->grouped());
|
||||
}
|
||||
|
||||
/** lit-site-status keeps its order: Divi, Pods, The Events Calendar. */
|
||||
public function testSiteStatusListsDiviPodsAndTheEventsCalendarInThatOrder(): void
|
||||
{
|
||||
Functions\when('get_bloginfo')->justReturn('x');
|
||||
Functions\when('home_url')->justReturn('https://example.test');
|
||||
Functions\when('wp_get_current_user')->justReturn(new WP_User(1, ['administrator']));
|
||||
Functions\when('get_option')->justReturn(['administrator']);
|
||||
Functions\when('apply_filters')->returnArg(2);
|
||||
$theme = Mockery::mock();
|
||||
$theme->allows('get_template')->andReturn('twentytwentyfive');
|
||||
Functions\when('wp_get_theme')->justReturn($theme);
|
||||
$status = array_values(array_filter(
|
||||
self::registry()->abilities(),
|
||||
static fn ($ability): bool => $ability instanceof SiteStatusAbility
|
||||
))[0];
|
||||
|
||||
$components = $status->execute([])['components'];
|
||||
|
||||
self::assertSame(['divi', 'pods', 'the-events-calendar'], array_column($components, 'id'));
|
||||
$pods = array_column($components, null, 'id')['pods'];
|
||||
self::assertFalse($pods['active'], 'no Pods in unit tests');
|
||||
self::assertSame([], $pods['problems']);
|
||||
}
|
||||
|
||||
public function testTheCompatibilityCoversAllProfilesWithoutIdCollision(): void
|
||||
{
|
||||
$compat = self::registry()->compatibility();
|
||||
|
||||
self::assertSame(['divi', 'pods'], array_map(static fn (Profile $p): string => $p->id, $compat->profiles()));
|
||||
self::assertNotNull($compat->capability('page-write'));
|
||||
self::assertNotNull($compat->capability('pods-field-read'));
|
||||
self::assertSame('divi', $compat->owner($compat->capability('page-write'))?->id);
|
||||
self::assertSame('pods', $compat->owner($compat->capability('pods-field-write'))?->id);
|
||||
self::assertNull($compat->capability('nobody-knows-me'));
|
||||
}
|
||||
|
||||
/** The lock of unknown IDs lives in CapabilityGate: without it a core tool with an odd ID would run unlocked. */
|
||||
public function testBothGuardsAreWiredDiviFirst(): void
|
||||
{
|
||||
$guards = Plugin::guards(self::registry(), FakeChangeLog::forbidden());
|
||||
|
||||
self::assertCount(2, $guards);
|
||||
self::assertInstanceOf(DiviFeatureGate::class, $guards[0]);
|
||||
self::assertInstanceOf(CapabilityGate::class, $guards[1]);
|
||||
}
|
||||
|
||||
private static function readOnly(object $source): bool
|
||||
{
|
||||
return (new ReflectionProperty($source, 'readOnly'))->getValue($source);
|
||||
}
|
||||
|
||||
private static function podsCompat(bool $usable): Compatibility
|
||||
{
|
||||
return new Compatibility([new Profile(
|
||||
'pods',
|
||||
'Pods',
|
||||
Tier::LockOnFailure,
|
||||
PodsFeature::cases(),
|
||||
active: static fn (): bool => $usable
|
||||
)]);
|
||||
}
|
||||
|
||||
public function testWithoutPodsTheMetaSourcesStayWritableAndPodsIsNotASource(): void
|
||||
{
|
||||
$sources = Plugin::schemaSources(self::podsCompat(false), false);
|
||||
|
||||
self::assertCount(2, $sources);
|
||||
self::assertInstanceOf(RegisteredMetaSource::class, $sources[0]);
|
||||
self::assertInstanceOf(SampledMetaSource::class, $sources[1]);
|
||||
self::assertFalse(self::readOnly($sources[0]));
|
||||
self::assertFalse(self::readOnly($sources[1]));
|
||||
}
|
||||
|
||||
public function testWithUsablePodsItsSourceComesFirstAndTheMetaSourcesStayWritable(): void
|
||||
{
|
||||
$sources = Plugin::schemaSources(self::podsCompat(true), true);
|
||||
|
||||
self::assertInstanceOf(PodsFieldSource::class, $sources[0]);
|
||||
self::assertFalse(self::readOnly($sources[1]));
|
||||
self::assertFalse(self::readOnly($sources[2]));
|
||||
}
|
||||
|
||||
/** Pods active but its schema locked: its meta keys must not be written past Pods. */
|
||||
public function testWithActiveButLockedPodsTheMetaSourcesAreReadOnly(): void
|
||||
{
|
||||
$sources = Plugin::schemaSources(self::podsCompat(false), true);
|
||||
|
||||
self::assertInstanceOf(PodsFieldSource::class, $sources[0]);
|
||||
self::assertTrue(self::readOnly($sources[1]));
|
||||
self::assertTrue(self::readOnly($sources[2]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user