feat: group Divi-Bibliothek with lit/library-list
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\Ability;
|
||||
use LIT\MCP\Integrations\Divi\DiviIntegration;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer;
|
||||
@@ -48,6 +49,24 @@ final class DiviIntegrationTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testProvidesTheLibraryGroup(): void
|
||||
{
|
||||
Functions\when('__')->returnArg();
|
||||
$library = (new DiviIntegration(Mockery::mock(ContentSchema::class), FakeChangeLog::forbidden()))->library();
|
||||
|
||||
self::assertSame('divi-library', $library->id());
|
||||
self::assertSame('Divi-Bibliothek', $library->label());
|
||||
self::assertFalse($library->isAvailable(), 'no Divi classes in unit tests');
|
||||
self::assertSame(
|
||||
['lit/library-list'],
|
||||
array_map(static fn (Ability $ability): string => $ability->name(), $library->abilities())
|
||||
);
|
||||
foreach ($library->abilities() as $ability) {
|
||||
self::assertFalse($ability->inputSchema()['additionalProperties'], $ability->name());
|
||||
self::assertNotNull($ability->outputSchema(), $ability->name());
|
||||
}
|
||||
}
|
||||
|
||||
public function testGivesContentCreateTheDiviShortForm(): void
|
||||
{
|
||||
$integration = new DiviIntegration(Mockery::mock(ContentSchema::class), FakeChangeLog::forbidden());
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Library;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\Cursor;
|
||||
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
||||
use LIT\MCP\Integrations\Divi\Library\LibraryListAbility;
|
||||
use LIT\MCP\Integrations\WordPress\PostGuard;
|
||||
use LIT\MCP\Tests\Support\DiviWriterFixture;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use WP_Error;
|
||||
|
||||
final class LibraryListAbilityTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
Functions\when('get_the_terms')->justReturn(false);
|
||||
Functions\when('get_post_meta')->justReturn('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $deniedRoles
|
||||
* @param list<int> $ids Entries the store lists (in this order).
|
||||
*/
|
||||
private function ability(array $deniedRoles = [], array $ids = [1055, 1058, 1059]): LibraryListAbility
|
||||
{
|
||||
$store = LibraryFixtures::store();
|
||||
$store->allows('find')->andReturnUsing(static fn (array $filters, int $limit, int $offset): array => array_slice(
|
||||
array_map(static fn (int $id) => LibraryFixtures::item($id), $ids),
|
||||
$offset,
|
||||
$limit + 1
|
||||
));
|
||||
$access = LibraryFixtures::access($deniedRoles, true, $store);
|
||||
$usages = Mockery::mock(GlobalUsages::class);
|
||||
$usages->allows('of')->with(1058)->andReturn([
|
||||
'items' => [['post_id' => 1053, 'title' => 'Globale Instanzen', 'status' => 'draft', 'type' => 'page']],
|
||||
'count' => 1,
|
||||
]);
|
||||
|
||||
return new LibraryListAbility($access, LibraryFixtures::loader($access), DiviWriterFixture::designReader(), $usages);
|
||||
}
|
||||
|
||||
/** A user with every capability but edit_post on the entries $hidden. */
|
||||
private static function withoutEditPostOn(array $hidden): void
|
||||
{
|
||||
Functions\when('current_user_can')->alias(
|
||||
static fn (string $cap, mixed ...$args): bool => ! ($cap === 'edit_post' && in_array((int) ($args[0] ?? 0), $hidden, true))
|
||||
);
|
||||
}
|
||||
|
||||
public function testListsAPageWithCursor(): void
|
||||
{
|
||||
LibraryFixtures::given([]);
|
||||
|
||||
$first = $this->ability()->execute(['limit' => 2]);
|
||||
$second = $this->ability()->execute(['limit' => 2, 'cursor' => $first['next_cursor']]);
|
||||
|
||||
self::assertSame([1055, 1058], array_column($first['items'], 'id'));
|
||||
self::assertSame(['normal', 'global'], array_column($first['items'], 'scope'));
|
||||
self::assertSame('section', $first['items'][0]['layout_type']);
|
||||
self::assertSame(Cursor::encode(2), $first['next_cursor']);
|
||||
self::assertSame([1059], array_column($second['items'], 'id'));
|
||||
self::assertNull($second['next_cursor']);
|
||||
}
|
||||
|
||||
public function testPassesTheFilters(): void
|
||||
{
|
||||
LibraryFixtures::given([]);
|
||||
$store = LibraryFixtures::store();
|
||||
$store->expects('find')->with(['layout_type' => 'module', 'scope' => 'global', 'search' => 'Zeiten'], 20, 0)
|
||||
->andReturn([]);
|
||||
$access = LibraryFixtures::access([], true, $store);
|
||||
$ability = new LibraryListAbility(
|
||||
$access,
|
||||
LibraryFixtures::loader($access),
|
||||
DiviWriterFixture::designReader(),
|
||||
Mockery::mock(GlobalUsages::class)
|
||||
);
|
||||
|
||||
$result = $ability->execute(['layout_type' => 'module', 'scope' => 'global', 'search' => 'Zeiten', 'category' => '']);
|
||||
|
||||
self::assertSame(['items' => [], 'next_cursor' => null], $result);
|
||||
}
|
||||
|
||||
public function testDetailOfAGlobalEntryWithOutlineAndAffectedPages(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058)]);
|
||||
|
||||
$result = $this->ability()->execute(['id' => 1058]);
|
||||
|
||||
self::assertSame(1058, $result['items'][0]['id']);
|
||||
self::assertNull($result['next_cursor']);
|
||||
self::assertStringStartsWith('s0 section "Global Abschnitt"', $result['outline']);
|
||||
self::assertSame(12, strlen($result['tree_rev']));
|
||||
self::assertSame(1, $result['affected_count']);
|
||||
self::assertSame('https://kunde.test/wp-admin/post.php?post=1058&action=edit', $result['edit_url']);
|
||||
}
|
||||
|
||||
public function testDetailOfSomethingElseIsNotFoundAndTheRoleIsNeeded(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::page('')]);
|
||||
|
||||
self::assertToolError($this->ability()->execute(['id' => LibraryFixtures::PAGE]), 'not_found');
|
||||
self::assertToolError($this->ability(['divi_library'])->permission([]), 'forbidden');
|
||||
self::assertToolError($this->ability()->execute(['cursor' => '!!']), 'validation_failed');
|
||||
self::assertTrue($this->ability()->annotations()['readonly']);
|
||||
self::assertFalse($this->ability()->isDirect());
|
||||
}
|
||||
|
||||
/** Output ruling: entries without edit_post are left out – no title, no count, the cursor still pages right. */
|
||||
public function testLeavesOutEntriesTheUserMayNotEdit(): void
|
||||
{
|
||||
LibraryFixtures::given([]);
|
||||
self::withoutEditPostOn([1055, 1057]);
|
||||
$ids = [1054, 1055, 1056, 1057, 1058, 1059];
|
||||
|
||||
$first = $this->ability([], $ids)->execute(['limit' => 2]);
|
||||
$second = $this->ability([], $ids)->execute(['limit' => 2, 'cursor' => $first['next_cursor']]);
|
||||
|
||||
self::assertSame([1054, 1056], array_column($first['items'], 'id'));
|
||||
self::assertNotNull($first['next_cursor']);
|
||||
self::assertSame([1058, 1059], array_column($second['items'], 'id'));
|
||||
self::assertNull($second['next_cursor']);
|
||||
$json = (string) json_encode([$first, $second]);
|
||||
self::assertStringNotContainsString('[Fixture] 1055', $json);
|
||||
self::assertStringNotContainsString('[Fixture] 1057', $json);
|
||||
self::assertSame(['items', 'next_cursor'], array_keys($first));
|
||||
}
|
||||
|
||||
public function testNoNextCursorWhenOnlyHiddenEntriesFollow(): void
|
||||
{
|
||||
LibraryFixtures::given([]);
|
||||
self::withoutEditPostOn([1058, 1059]);
|
||||
|
||||
$result = $this->ability()->execute(['limit' => 1]);
|
||||
|
||||
self::assertSame([1055], array_column($result['items'], 'id'));
|
||||
self::assertNull($result['next_cursor']);
|
||||
}
|
||||
|
||||
/** Ruling P12: without the library right, an id reads exactly like a missing ID; the list says forbidden. */
|
||||
public function testWithoutTheLibraryRightAnIdIsNotFoundLikeAMissingOne(): void
|
||||
{
|
||||
$missing = PostGuard::notFound(1058);
|
||||
$cases = [
|
||||
'no divi_library' => [[], ['divi_library']],
|
||||
'no edit_posts' => [['edit_posts'], []],
|
||||
];
|
||||
foreach ($cases as $case => [$deniedCaps, $deniedRoles]) {
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058), LibraryFixtures::page('')], $deniedCaps);
|
||||
$ability = $this->ability($deniedRoles);
|
||||
foreach ([$ability->permission(['id' => 1058]), $ability->execute(['id' => 1058])] as $result) {
|
||||
self::assertInstanceOf(WP_Error::class, $result, $case);
|
||||
self::assertSame($missing->get_error_code(), $result->get_error_code(), $case);
|
||||
self::assertSame($missing->get_error_message(), $result->get_error_message(), $case);
|
||||
self::assertSame($missing->get_error_data(), $result->get_error_data(), $case);
|
||||
}
|
||||
self::assertToolError($ability->execute(['id' => 99999]), 'not_found');
|
||||
self::assertToolError($ability->execute(['id' => LibraryFixtures::PAGE]), 'not_found');
|
||||
self::assertToolError($ability->execute([]), 'forbidden');
|
||||
self::assertToolError($ability->permission([]), 'forbidden');
|
||||
}
|
||||
}
|
||||
|
||||
public function testAnInvalidIdIsAValidationError(): void
|
||||
{
|
||||
LibraryFixtures::given([]);
|
||||
|
||||
self::assertToolError($this->ability()->execute(['id' => 'abc']), 'validation_failed');
|
||||
}
|
||||
|
||||
public function testDetailWithoutEditPostIsForbiddenWithoutTheTitle(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058, ['post_title' => 'Geheim'])]);
|
||||
self::withoutEditPostOn([1058]);
|
||||
|
||||
$result = $this->ability()->execute(['id' => 1058]);
|
||||
|
||||
self::assertToolError($result, 'forbidden');
|
||||
self::assertStringNotContainsString('Geheim', (string) json_encode([$result->get_error_message(), $result->get_error_data()]));
|
||||
self::assertToolError($this->ability()->permission(['id' => 1058]), 'forbidden');
|
||||
}
|
||||
|
||||
public function testDetailOfANormalEntryHasNoAffectedPages(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1055)]);
|
||||
|
||||
$result = $this->ability()->execute(['id' => 1055]);
|
||||
|
||||
self::assertSame('normal', $result['items'][0]['scope']);
|
||||
self::assertArrayNotHasKey('affected', $result);
|
||||
self::assertTrue($this->ability()->permission(['id' => 1055]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user