feat: content-types tool and WordPress integration

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-25 22:21:29 +02:00
co-authored by Claude Opus 5.5
parent 4a652240a3
commit 98a910f056
7 changed files with 296 additions and 7 deletions
@@ -25,4 +25,17 @@ final class InstructionsBuilderTest extends TestCase
self::assertStringContainsString('Status', $text);
self::assertLessThan(2000, strlen($text), 'Instructions must stay short – they are loaded in every conversation.');
}
public function testListsPostTypesWhenAProviderIsGiven(): void
{
$core = Mockery::mock(Integration::class);
$core->allows('id')->andReturn('core');
$core->allows('isAvailable')->andReturn(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);
}
}
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\WordPress\Content;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\WordPress\Content\ContentTypesAbility;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use WP_Error;
final class ContentTypesAbilityTest extends TestCase
{
public function testListsAllTypesWithoutFields(): void
{
$schema = Mockery::mock(ContentSchema::class);
$schema->expects('describePostTypes')->andReturn([['name' => 'page'], ['name' => 'post']]);
$result = (new ContentTypesAbility($schema))->execute([]);
self::assertSame(['post_types' => [['name' => 'page'], ['name' => 'post']]], $result);
}
public function testSingleTypeIncludesFields(): void
{
$schema = Mockery::mock(ContentSchema::class);
$schema->expects('describe')->with('lit_person', true)->andReturn(['name' => 'lit_person', 'fields' => []]);
$result = (new ContentTypesAbility($schema))->execute(['post_type' => 'lit_person']);
self::assertSame([['name' => 'lit_person', 'fields' => []]], $result['post_types']);
}
public function testUnknownTypeIsNotFoundWithTheAvailableTypes(): void
{
$schema = Mockery::mock(ContentSchema::class);
$schema->allows('describe')->andReturn(null);
$schema->allows('postTypeNames')->andReturn(['page', 'post']);
$result = (new ContentTypesAbility($schema))->execute(['post_type' => 'nope']);
self::assertInstanceOf(WP_Error::class, $result);
self::assertSame('lit_not_found', $result->get_error_code());
self::assertStringContainsString('page, post', $result->get_error_message());
}
public function testPermissionRequiresEditPosts(): void
{
Functions\when('current_user_can')->justReturn(false);
$ability = new ContentTypesAbility(Mockery::mock(ContentSchema::class));
self::assertSame('lit_forbidden', $ability->permission([])->get_error_code());
}
public function testMetadata(): void
{
$ability = new ContentTypesAbility(Mockery::mock(ContentSchema::class));
self::assertSame('lit/content-types', $ability->name());
self::assertTrue($ability->isDirect());
self::assertSame(['readonly' => true, 'destructive' => false, 'idempotent' => true], $ability->annotations());
self::assertFalse($ability->inputSchema()['additionalProperties']);
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\WordPress;
use LIT\MCP\Abilities\Ability;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Integrations\WordPress\WordPressIntegration;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class WordPressIntegrationTest extends TestCase
{
/** Grows with every task that adds abilities – keep in the order of abilities(). */
private const EXPECTED = [
'lit/content-types',
];
public function testProvidesTheWordPressAbilitiesInOrder(): void
{
$integration = new WordPressIntegration(Mockery::mock(ContentSchema::class), Mockery::mock(ChangeLog::class));
self::assertSame('wordpress', $integration->id());
self::assertTrue($integration->isAvailable());
self::assertSame(
self::EXPECTED,
array_map(static fn (Ability $ability): string => $ability->name(), $integration->abilities())
);
}
public function testEveryAbilityHasAnObjectInputSchemaThatRejectsUnknownKeys(): void
{
$integration = new WordPressIntegration(Mockery::mock(ContentSchema::class), Mockery::mock(ChangeLog::class));
foreach ($integration->abilities() as $ability) {
$schema = $ability->inputSchema();
self::assertSame('object', $schema['type'], $ability->name());
self::assertFalse($schema['additionalProperties'], $ability->name());
self::assertMatchesRegularExpression('#^lit/[a-z]+(-[a-z]+)+$#', $ability->name());
}
}
}