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>
114 lines
4.0 KiB
PHP
114 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Abilities;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Abilities\Ability;
|
|
use LIT\MCP\Abilities\AbilityRegistrar;
|
|
use LIT\MCP\Abilities\DeclaresRequirements;
|
|
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\DiviFeature;
|
|
use LIT\MCP\Integrations\Divi\Release\DiviFeatureGate;
|
|
use LIT\MCP\Integrations\Divi\Release\DiviProfile;
|
|
use LIT\MCP\Integrations\Integration;
|
|
use LIT\MCP\Integrations\IntegrationRegistry;
|
|
use LIT\MCP\Integrations\Pods\PodsCompatibility;
|
|
use LIT\MCP\Server\RequestContext;
|
|
use LIT\MCP\Tests\Support\DiviFeatureFixtures;
|
|
use LIT\MCP\Tests\Support\FakeCapability;
|
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use WP_Error;
|
|
|
|
/**
|
|
* The two guards Plugin hands to AbilityRegistrar, [DiviFeatureGate, CapabilityGate], through the real
|
|
* permission callback: each ID is judged by exactly one of them and none passes both unjudged.
|
|
*/
|
|
final class CompatibilityGatesTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
RequestContext::reset(true);
|
|
Functions\when('current_user_can')->justReturn(true);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
RequestContext::reset();
|
|
parent::tearDown();
|
|
}
|
|
|
|
/** @param list<string> $ids */
|
|
private function permission(array $ids): bool|WP_Error
|
|
{
|
|
$ability = Mockery::mock(Ability::class, DeclaresRequirements::class);
|
|
$ability->allows('name')->andReturn('lit/test');
|
|
$ability->allows('label')->andReturn('Test');
|
|
$ability->allows('description')->andReturn('Test');
|
|
$ability->allows('inputSchema')->andReturn(['type' => 'object', 'properties' => []]);
|
|
$ability->allows('outputSchema')->andReturn(null);
|
|
$ability->allows('annotations')->andReturn(['readonly' => true]);
|
|
$ability->allows('isDirect')->andReturn(true);
|
|
$ability->allows('requirements')->andReturn($ids);
|
|
$ability->allows('permission')->andReturn(true);
|
|
|
|
$free = new Profile('fake', 'Fake Plugin', Tier::LockOnFailure, [FakeCapability::Read]);
|
|
$compat = new Compatibility([DiviProfile::profile(), PodsCompatibility::profile(), $free]);
|
|
$integration = Mockery::mock(Integration::class);
|
|
$integration->allows('id')->andReturn('core');
|
|
$integration->allows('isAvailable')->andReturn(true);
|
|
$integration->allows('abilities')->andReturn([$ability]);
|
|
$registrar = new AbilityRegistrar(
|
|
new IntegrationRegistry($integration),
|
|
null,
|
|
[new DiviFeatureGate(FakeChangeLog::forbidden()), new CapabilityGate($compat, FakeChangeLog::forbidden())]
|
|
);
|
|
|
|
return ($registrar->argsFor($ability)['permission_callback'])([]);
|
|
}
|
|
|
|
public function testAFreeNonDiviIdPassesTheDiviGateAndTheCapabilityGate(): void
|
|
{
|
|
self::assertTrue($this->permission(['fake-read']));
|
|
}
|
|
|
|
public function testALockedPodsIdPassesTheDiviGateAndIsRefusedByTheCapabilityGate(): void
|
|
{
|
|
DiviFeatureFixtures::useStates([]);
|
|
|
|
$error = $this->permission(['pods-field-read']);
|
|
|
|
self::assertToolError($error, 'compat_incompatible');
|
|
}
|
|
|
|
public function testAnUnknownIdIsRefusedByTheCapabilityGate(): void
|
|
{
|
|
DiviFeatureFixtures::useStates([]);
|
|
|
|
$payload = self::assertToolError($this->permission(['page-write', 'nobody-knows-me']), 'compat_incompatible');
|
|
|
|
self::assertSame(['nobody-knows-me'], $payload['unknown']);
|
|
}
|
|
|
|
public function testAReleasedDiviIdPassesBothGates(): void
|
|
{
|
|
DiviFeatureFixtures::useStates([]);
|
|
|
|
self::assertTrue($this->permission(['page-write']));
|
|
}
|
|
|
|
public function testALockedDiviIdIsRefusedByTheDiviGateAloneNotTwice(): void
|
|
{
|
|
DiviFeatureFixtures::lock(DiviFeature::PageWrite);
|
|
|
|
self::assertToolError($this->permission(['page-write']), 'divi_untested');
|
|
}
|
|
}
|