Files
WP-LIT-MCP/tests/Unit/Guides/InstructionsBuilderTest.php
T
janniclas.loosenandClaude Opus 5.5 776dbf8f4a feat: group tools by integration with inherited group mode and "nur lesen" switch
Tools tab shows one block per integration with a group mode (inherited by its
tools) and a read-only switch that disables all non-readonly abilities of the
group. ExposurePolicy resolves tool override > group override > tool default;
lit/guide keeps its default, direct-only tools fall back to direct under a
group "indirekt". Group entries live in lit_mcp_exposure under "@group:<id>"
and "@readonly:<id>"; the old flat format stays valid. The ability->group map
comes lazily from IntegrationRegistry (Divi is not loaded at plugins_loaded).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:17:05 +02:00

122 lines
4.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Guides;
use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\ExposurePolicy;
use LIT\MCP\Guides\InstructionsBuilder;
use LIT\MCP\Integrations\Integration;
use LIT\MCP\Integrations\IntegrationRegistry;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class InstructionsBuilderTest extends TestCase
{
private function ability(string $name, bool $direct): Ability
{
$ability = Mockery::mock(Ability::class);
$ability->allows('name')->andReturn($name);
$ability->allows('isDirect')->andReturn($direct);
return $ability;
}
private function coreWith(Ability ...$abilities): Integration
{
$core = Mockery::mock(Integration::class);
$core->allows('id')->andReturn('core');
$core->allows('isAvailable')->andReturn(true);
$core->allows('abilities')->andReturn(array_values($abilities));
return $core;
}
public function testNamesActiveIntegrationsAndCoreRules(): void
{
$core = $this->coreWith($this->ability('lit/guide', true), $this->ability('lit/menu-list', false));
$text = (new InstructionsBuilder(new IntegrationRegistry($core)))->build();
self::assertStringContainsString('Aktive Integrationen: core', $text);
self::assertStringContainsString('lit-guide', $text);
self::assertStringContainsString('Status', $text);
self::assertLessThan(2000, strlen($text), 'Instructions must stay short – they are loaded in every conversation.');
}
public function testAddsTheDiviRuleOnlyWhenDiviIsActive(): void
{
$divi = Mockery::mock(Integration::class);
$divi->allows('id')->andReturn('divi');
$divi->allows('isAvailable')->andReturn(true);
$divi->allows('abilities')->andReturn([$this->ability('lit/divi-outline', true)]);
$core = $this->coreWith($this->ability('lit/guide', true));
$with = (new InstructionsBuilder(new IntegrationRegistry($core, $divi)))->build();
$without = (new InstructionsBuilder(new IntegrationRegistry($core)))->build();
self::assertStringContainsString('lit-divi-outline und lit-divi-design-system lesen', $with);
self::assertStringNotContainsString('lit-divi-outline und', $without);
self::assertLessThan(2000, strlen($with));
}
public function testListsPostTypesWhenAProviderIsGiven(): void
{
$core = $this->coreWith($this->ability('lit/guide', true));
$text = (new InstructionsBuilder(new IntegrationRegistry($core), static fn (): array => ['page', 'post']))->build();
self::assertStringContainsString('Inhaltstypen: page, post', $text);
self::assertStringContainsString('lit-content-types', $text);
self::assertStringContainsString('confirm: true', $text);
}
public function testListsTheCurrentlyDirectToolsByToolNameAndNamesTheRestNeutrally(): void
{
$core = $this->coreWith(
$this->ability('lit/guide', true),
$this->ability('lit/menu-list', false)
);
$text = (new InstructionsBuilder(new IntegrationRegistry($core)))->build();
self::assertStringContainsString('Direkt aufrufbare Tools: lit-guide', $text);
self::assertStringContainsString('mcp-adapter-execute-ability', $text);
self::assertStringNotContainsString('lit-menu-list', $text);
}
public function testDirectToolListReflectsExposurePolicyOverrides(): void
{
$core = $this->coreWith(
$this->ability('lit/guide', true),
$this->ability('lit/menu-list', false)
);
$policy = new ExposurePolicy(['lit/menu-list' => 'direct']);
$text = (new InstructionsBuilder(new IntegrationRegistry($core), null, $policy))->build();
self::assertStringContainsString('lit-menu-list', $text);
}
public function testGroupDisabledAndReadonlyToolsAreNotListedAsDirect(): void
{
$write = $this->ability('lit/divi-update', true);
$write->allows('annotations')->andReturn(['readonly' => false]);
$read = $this->ability('lit/divi-outline', true);
$read->allows('annotations')->andReturn(['readonly' => true]);
$divi = Mockery::mock(Integration::class);
$divi->allows('id')->andReturn('divi');
$divi->allows('isAvailable')->andReturn(true);
$divi->allows('abilities')->andReturn([$write, $read]);
$core = $this->coreWith($this->ability('lit/guide', true), $this->ability('lit/menu-list', true));
$registry = new IntegrationRegistry($core, $divi);
$policy = (new ExposurePolicy(['@group:core' => 'disabled', '@readonly:divi' => true]))
->withGroups($registry->groupMap());
$text = (new InstructionsBuilder($registry, null, $policy))->build();
self::assertStringContainsString('Direkt aufrufbare Tools: lit-guide, lit-divi-outline.', $text);
}
}