ReleaseStore, CheckReportStore, CheckReport, CheckStep und CompatibilityCheck liegen im Kern und arbeiten je Profil (Optionen lit_mcp_<id>_releases|_check_report; Divi behält Namen und Schlüssel divi_version). Divi hängt Katalog-, Format- und Schreibtest über Profile::extraSteps an. Ein roter Prüfbericht für den aktuellen Stand sperrt LockOnFailure-Profile bis zur nächsten grünen Prüfung; Sperrgründe nennen nur Schritt-Labels, Ausnahmen gehen ins Error-Log. Profile bekommt testedVersions, stateKey, extraSteps, manifestCheck und reportVersionKey und wird mit benannten Argumenten gebaut. 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(id: 'fake', label: 'Fake Plugin', tier: Tier::LockOnFailure, capabilities: [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');
|
|
}
|
|
}
|