feat: Divi read tools accept library entries (library access, E1/E3)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,8 +9,10 @@ use LIT\MCP\Integrations\Divi\DiviIntegration;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviContentComposer;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use WP_Error;
|
||||
|
||||
final class DiviIntegrationTest extends TestCase
|
||||
{
|
||||
@@ -55,4 +57,23 @@ final class DiviIntegrationTest extends TestCase
|
||||
self::assertInstanceOf(DiviContentComposer::class, $composer);
|
||||
self::assertFalse($composer->available(), 'no Divi classes in unit tests');
|
||||
}
|
||||
|
||||
/** E1/E3: the Divi read tools reach library entries; ruling P9: a hidden library leaves them not found. */
|
||||
public function testTheReadToolsReachTheLibraryUnlessItIsHidden(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1055)]);
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('isManaged')->andReturn(false);
|
||||
$outline = static fn (DiviIntegration $divi): mixed => $divi->abilities()[0]->permission(['post_id' => 1055]);
|
||||
|
||||
// No Divi in unit tests: DiviRoles allows nothing, so a reachable entry is refused, not missing.
|
||||
$visible = new DiviIntegration($schema, FakeChangeLog::forbidden(), null, static fn (): bool => true);
|
||||
self::assertToolError($outline($visible), 'forbidden');
|
||||
self::assertToolError($outline(new DiviIntegration($schema, FakeChangeLog::forbidden())), 'forbidden');
|
||||
|
||||
$hidden = new DiviIntegration($schema, FakeChangeLog::forbidden(), libraryVisible: static fn (): bool => false);
|
||||
$refusal = $outline($hidden);
|
||||
self::assertInstanceOf(WP_Error::class, $refusal);
|
||||
self::assertToolError($refusal, 'not_found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Library;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Library\LibraryAccess;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use WP_Post;
|
||||
|
||||
final class LibraryAccessTest extends TestCase
|
||||
{
|
||||
public function testReadsLibraryItemsAndLeavesOtherIdsToPostGuard(): void
|
||||
{
|
||||
$item = LibraryFixtures::item(1055);
|
||||
LibraryFixtures::given([$item, LibraryFixtures::page('')]);
|
||||
$access = LibraryFixtures::access();
|
||||
|
||||
self::assertSame($item, $access->item(1055, false));
|
||||
self::assertNull($access->item(LibraryFixtures::PAGE, false), 'a page is PostGuard\'s business');
|
||||
self::assertNull($access->item(9999, false));
|
||||
self::assertNull($access->item('abc', false));
|
||||
self::assertNull($access->canList());
|
||||
}
|
||||
|
||||
public function testReadingNeedsEditPostsTheDiviLibraryRoleAndEditPost(): void
|
||||
{
|
||||
$item = LibraryFixtures::item(1055);
|
||||
|
||||
LibraryFixtures::given([$item], ['edit_posts']);
|
||||
self::assertToolError(LibraryFixtures::access()->item(1055, false), 'forbidden');
|
||||
|
||||
LibraryFixtures::given([$item]);
|
||||
self::assertToolError(LibraryFixtures::access(['divi_library'])->item(1055, false), 'forbidden');
|
||||
|
||||
LibraryFixtures::given([$item], ['edit_post']);
|
||||
$refusal = LibraryFixtures::access()->item(1055, false);
|
||||
self::assertToolError($refusal, 'forbidden');
|
||||
self::assertStringContainsString('Bibliothekseintrag 1055', $refusal->get_error_message());
|
||||
}
|
||||
|
||||
public function testTrashedItemsCanBeRead(): void
|
||||
{
|
||||
$item = LibraryFixtures::item(1055, ['post_status' => 'trash']);
|
||||
LibraryFixtures::given([$item]);
|
||||
|
||||
self::assertInstanceOf(WP_Post::class, LibraryFixtures::access()->item(1055, false));
|
||||
}
|
||||
|
||||
public function testGlobalFromTheStore(): void
|
||||
{
|
||||
$access = LibraryFixtures::access();
|
||||
|
||||
self::assertTrue($access->isGlobal(LibraryFixtures::item(1058)));
|
||||
self::assertFalse($access->isGlobal(LibraryFixtures::item(1055)));
|
||||
}
|
||||
|
||||
public function testRefusalsNameNeitherTitleNorContent(): void
|
||||
{
|
||||
$item = LibraryFixtures::item(1055, ['post_status' => 'private', 'post_title' => 'Geheimer Entwurf']);
|
||||
|
||||
foreach ([['edit_posts'], ['edit_post']] as $denied) {
|
||||
LibraryFixtures::given([$item], $denied);
|
||||
$refusal = LibraryFixtures::access()->item(1055, false);
|
||||
self::assertToolError($refusal, 'forbidden');
|
||||
self::assertStringNotContainsString('Geheimer Entwurf', $refusal->get_error_message());
|
||||
self::assertStringNotContainsString('Geheimer Entwurf', (string) wp_json_encode($refusal->get_error_data()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Ruling P9: with the group "Divi-Bibliothek" disabled, library IDs read like IDs that do not exist. */
|
||||
public function testAHiddenLibraryAnswersNotFoundLikeAMissingEntry(): void
|
||||
{
|
||||
$item = LibraryFixtures::item(1055, ['post_title' => 'Geheimer Entwurf']);
|
||||
LibraryFixtures::given([$item, LibraryFixtures::page('')], ['edit_post']);
|
||||
$hidden = new LibraryAccess(LibraryFixtures::store(), LibraryFixtures::roles(), null, static fn (): bool => false);
|
||||
|
||||
foreach ([false, true] as $forWrite) {
|
||||
$refusal = $hidden->item(1055, $forWrite);
|
||||
self::assertToolError($refusal, 'not_found');
|
||||
self::assertStringNotContainsString('Geheimer Entwurf', $refusal->get_error_message());
|
||||
self::assertStringNotContainsString('Bibliothek', $refusal->get_error_message());
|
||||
}
|
||||
self::assertNull($hidden->item(LibraryFixtures::PAGE, false), 'pages stay PostGuard\'s business');
|
||||
self::assertNull($hidden->item(9999, false));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,11 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPage;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use LIT\MCP\Integrations\Divi\Library\LibraryAccess;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use Mockery;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use WP_Error;
|
||||
|
||||
@@ -96,4 +101,49 @@ final class DiviPageLoaderTest extends TestCase
|
||||
DiviFixtures::givenPost("<!-- wp:paragraph -->\n<p>Gutenberg</p>\n<!-- /wp:paragraph -->");
|
||||
self::assertToolError($loader->loadWritable(7), 'unsupported');
|
||||
}
|
||||
|
||||
public function testLoadsALibraryItemForReading(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058)]);
|
||||
|
||||
$page = LibraryFixtures::loader()->load(1058, false);
|
||||
|
||||
self::assertInstanceOf(DiviPage::class, $page);
|
||||
self::assertTrue($page->isLibraryItem());
|
||||
self::assertSame('divi/section', $page->index->find('s0')?->name());
|
||||
self::assertSame('https://kunde.test/wp-admin/post.php?post=1058&action=edit', $page->editUrl());
|
||||
}
|
||||
|
||||
public function testLibraryItemsNeedTheLibraryAccess(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058)]);
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('isManaged')->with('et_pb_layout')->andReturn(false);
|
||||
|
||||
self::assertToolError((new DiviPageLoader($schema))->load(1058, false), 'not_found');
|
||||
self::assertToolError(
|
||||
(new DiviPageLoader($schema, new Parser(), LibraryFixtures::access(['divi_library'])))->load(1058, false),
|
||||
'forbidden'
|
||||
);
|
||||
}
|
||||
|
||||
public function testLibraryItemsAreNotWritableYet(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058)]);
|
||||
$schema = Mockery::mock(ContentSchema::class);
|
||||
$schema->allows('isManaged')->with('et_pb_layout')->andReturn(false);
|
||||
|
||||
$result = (new DiviPageLoader($schema, new Parser(), LibraryFixtures::access()))->load(1058, true);
|
||||
|
||||
self::assertToolError($result, 'not_found');
|
||||
}
|
||||
|
||||
/** Ruling P9: the group "Divi-Bibliothek" disabled hides library entries from the Divi read tools. */
|
||||
public function testAHiddenLibraryLeavesLibraryItemsNotFound(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1058)]);
|
||||
$hidden = new LibraryAccess(LibraryFixtures::store(), LibraryFixtures::roles(), null, static fn (): bool => false);
|
||||
|
||||
self::assertToolError(LibraryFixtures::loader($hidden)->load(1058, false), 'not_found');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use LIT\MCP\Integrations\Divi\Pages\DiviNodeGetAbility;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviOutlineAbility;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use stdClass;
|
||||
@@ -138,4 +139,32 @@ final class DiviReadAbilitiesTest extends TestCase
|
||||
self::assertFalse($global['editable']);
|
||||
self::assertNull($plain['global']);
|
||||
}
|
||||
|
||||
public function testOutlineOfALibraryItem(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1059)]);
|
||||
$ability = new DiviOutlineAbility(LibraryFixtures::loader(), $this->design());
|
||||
|
||||
$result = $ability->execute(['post_id' => 1059]);
|
||||
|
||||
self::assertSame(['m0'], array_column($result['nodes'], 'id'));
|
||||
self::assertStringStartsWith('m0 text "Globales Modul Öffnungszeiten"', $result['outline']);
|
||||
self::assertSame('https://kunde.test/wp-admin/post.php?post=1059&action=edit', $result['edit_url']);
|
||||
}
|
||||
|
||||
public function testNodeGetOfALibraryItemAndRefusalsWithoutTheLibraryRight(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1057, ['post_status' => 'private', 'post_title' => 'Geheim'])]);
|
||||
$ability = new DiviNodeGetAbility(LibraryFixtures::loader(), $this->design());
|
||||
|
||||
$result = $ability->execute(['post_id' => 1057, 'id' => 'm0']);
|
||||
self::assertSame('m0', $result['id']);
|
||||
|
||||
$denied = new DiviNodeGetAbility(LibraryFixtures::loader(LibraryFixtures::access(['divi_library'])), $this->design());
|
||||
$refusal = $denied->permission(['post_id' => 1057, 'id' => 'm0']);
|
||||
self::assertToolError($refusal, 'forbidden');
|
||||
self::assertStringNotContainsString('Geheim', $refusal->get_error_message());
|
||||
$outline = new DiviOutlineAbility(LibraryFixtures::loader(LibraryFixtures::access(['divi_library'])), $this->design());
|
||||
self::assertToolError($outline->execute(['post_id' => 1057]), 'forbidden');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user