fix: library IDs stay hidden without library access (ruling P13)
Outline, node-get, write outlines, the render summary and replace-text's opaque-node match name no library ID unless the library group is visible and the user has divi_library; read-only still names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||
use LIT\MCP\Integrations\Divi\Library\LibraryAccess;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviNodeGetAbility;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviOutlineAbility;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
@@ -128,7 +129,7 @@ final class DiviReadAbilitiesTest extends TestCase
|
||||
public function testNodeGetNamesTheLibraryEntryOfAGlobalModule(): void
|
||||
{
|
||||
DiviFixtures::givenPost(DiviFixtures::markup('stage3/page--globale-instanzen.html'));
|
||||
$ability = new DiviNodeGetAbility(new DiviPageLoader(DiviFixtures::schema()), $this->design());
|
||||
$ability = new DiviNodeGetAbility(LibraryFixtures::loader(), $this->design());
|
||||
|
||||
$global = $ability->execute(['post_id' => 7, 'id' => 'g0']);
|
||||
$plain = $ability->execute(['post_id' => 7, 'id' => 's0']);
|
||||
@@ -140,6 +141,56 @@ final class DiviReadAbilitiesTest extends TestCase
|
||||
self::assertNull($plain['global']);
|
||||
}
|
||||
|
||||
/** Ruling P13: without the library right node-get and outline name no library ID. */
|
||||
public function testWithoutLibraryAccessNoLibraryIdIsNamed(): void
|
||||
{
|
||||
DiviFixtures::givenPost(DiviFixtures::markup('stage3/page--globale-instanzen.html'));
|
||||
$loaders = [
|
||||
'no divi_library right' => LibraryFixtures::loader(LibraryFixtures::access(['divi_library'])),
|
||||
'group disabled' => LibraryFixtures::loader(new LibraryAccess(
|
||||
LibraryFixtures::store(),
|
||||
LibraryFixtures::roles(),
|
||||
null,
|
||||
static fn (): bool => false
|
||||
)),
|
||||
'no library access at all' => new DiviPageLoader(DiviFixtures::schema()),
|
||||
];
|
||||
|
||||
foreach ($loaders as $case => $loader) {
|
||||
$nodeGet = new DiviNodeGetAbility($loader, $this->design());
|
||||
$global = $nodeGet->execute(['post_id' => 7, 'id' => 'g0']);
|
||||
$outline = (new DiviOutlineAbility($loader, $this->design()))->execute(['post_id' => 7]);
|
||||
|
||||
self::assertNull($global['global']['id'], $case);
|
||||
self::assertSame('divi/section', $global['global']['block_name'], $case);
|
||||
self::assertStringContainsString('Divi-Bibliothek', $global['global']['hint'], $case);
|
||||
self::assertArrayNotHasKey('globalModule', $global['attrs'], $case);
|
||||
self::assertFalse($global['editable'], $case);
|
||||
self::assertStringNotContainsString('1058', (string) json_encode($global), $case);
|
||||
self::assertStringNotContainsString('1058', (string) json_encode($outline), $case);
|
||||
self::assertStringNotContainsString('1059', (string) json_encode($outline), $case);
|
||||
self::assertStringNotContainsString('[global', (string) json_encode($outline), $case);
|
||||
foreach (array_column($outline['nodes'], 'id') as $id) {
|
||||
$node = $nodeGet->execute(['post_id' => 7, 'id' => $id]);
|
||||
self::assertStringNotContainsString('1058', (string) json_encode($node), $case . ' ' . $id);
|
||||
self::assertStringNotContainsString('1059', (string) json_encode($node), $case . ' ' . $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Ruling P13: "nur lesen" (group read-only) does not hide library IDs. */
|
||||
public function testAReadOnlyLibraryStillNamesLibraryIds(): void
|
||||
{
|
||||
DiviFixtures::givenPost(DiviFixtures::markup('stage3/page--globale-instanzen.html'));
|
||||
$loader = LibraryFixtures::loader(LibraryFixtures::access([], false));
|
||||
|
||||
$global = (new DiviNodeGetAbility($loader, $this->design()))->execute(['post_id' => 7, 'id' => 'g0']);
|
||||
$outline = (new DiviOutlineAbility($loader, $this->design()))->execute(['post_id' => 7]);
|
||||
|
||||
self::assertSame('1058', $global['global']['id']);
|
||||
self::assertStringContainsString('[global 1058]', $outline['outline']);
|
||||
}
|
||||
|
||||
public function testOutlineOfALibraryItem(): void
|
||||
{
|
||||
LibraryFixtures::given([LibraryFixtures::item(1059)]);
|
||||
|
||||
@@ -15,6 +15,7 @@ use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviReplaceTextAbility;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Tests\Support\DiviWriterFixture;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
@@ -38,13 +39,19 @@ final class DiviReplaceTextAbilityTest extends TestCase
|
||||
private array $failures = [];
|
||||
|
||||
/** @param list<int> $ids Rows of the prefilter query. */
|
||||
private function ability(?string $content = null, bool $unfiltered = false, array $ids = [7]): DiviReplaceTextAbility
|
||||
{
|
||||
private function ability(
|
||||
?string $content = null,
|
||||
bool $unfiltered = false,
|
||||
array $ids = [7],
|
||||
bool $library = false
|
||||
): DiviReplaceTextAbility {
|
||||
$this->post = DiviFixtures::givenPost($content ?? DiviFixtures::markup('synthetic-basic.html'));
|
||||
Functions\when('get_post_type_object')->justReturn((object) ['cap' => (object) ['edit_posts' => 'edit_pages', 'edit_others_posts' => 'edit_others_pages']]);
|
||||
Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true');
|
||||
$schema = DiviFixtures::schema();
|
||||
$loader = new DiviPageLoader($schema);
|
||||
$loader = $library
|
||||
? new DiviPageLoader($schema, new Parser(), LibraryFixtures::access())
|
||||
: new DiviPageLoader($schema);
|
||||
$db = Mockery::mock(\wpdb::class)->makePartial();
|
||||
$db->allows('get_col')->andReturnUsing(function (string $sql) use ($ids): array {
|
||||
$this->queries[] = $sql;
|
||||
@@ -420,6 +427,43 @@ final class DiviReplaceTextAbilityTest extends TestCase
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling P13: without library access a search for a library ID finds nothing – the ID a global
|
||||
* instance (and its children's globalParent) stores is as good as absent; with access it is reported.
|
||||
*/
|
||||
public function testWithoutLibraryAccessLibraryIdsAreNoHits(): void
|
||||
{
|
||||
$content = DiviFixtures::markup('stage3/page--globale-instanzen.html');
|
||||
foreach (['1058', '1059', '"globalModule":"1058"', 'globalParent":"1058'] as $search) {
|
||||
$ability = $this->ability($content);
|
||||
$dry = $ability->execute(['search' => $search, 'replace' => 'x']);
|
||||
$result = $this->replaceFor($ability, ['search' => $search, 'replace' => 'x']);
|
||||
|
||||
self::assertSame([], $dry['skipped_nodes'], $search);
|
||||
self::assertSame([], $dry['matches'], $search);
|
||||
self::assertSame([], $result['skipped_nodes'], $search);
|
||||
self::assertStringNotContainsString('1058', (string) json_encode([$dry, $result]), $search);
|
||||
self::assertStringNotContainsString('1059', (string) json_encode([$dry, $result]), $search);
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
$shown = $this->ability($content, false, [7], true)->execute(['search' => '1058', 'replace' => 'x']);
|
||||
self::assertSame(['g0'], array_column($shown['skipped_nodes'], 'id'));
|
||||
}
|
||||
|
||||
/** Ruling P13: text of a global instance is still reported without library access (not an ID). */
|
||||
public function testWithoutLibraryAccessGlobalTextIsStillReported(): void
|
||||
{
|
||||
$global = ['divi/text', ['globalModule' => '123', 'content' => ['innerContent' => ['desktop' => ['value' => '<p>Müller</p>']]]]];
|
||||
$ability = $this->ability(self::page([$global]));
|
||||
|
||||
$dry = $ability->execute(['search' => 'Müller', 'replace' => 'Schmidt']);
|
||||
$byId = $ability->execute(['search' => '123', 'replace' => 'x']);
|
||||
|
||||
self::assertSame(['s0.r0.c0.g0'], array_column($dry['skipped_nodes'], 'id'));
|
||||
self::assertSame([], $byId['skipped_nodes']);
|
||||
}
|
||||
|
||||
/** No effective change (search == replacement, cleaned replacement == match): nothing saved. */
|
||||
public function testNoEffectiveChangeSavesNothing(): void
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ use LIT\MCP\Abilities\Rev;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||||
use LIT\MCP\Tests\Support\DiviWriterFixture;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use WP_Error;
|
||||
|
||||
final class DiviUpdateAbilityTest extends DiviWriteAbilityTestCase
|
||||
@@ -240,4 +242,24 @@ final class DiviUpdateAbilityTest extends DiviWriteAbilityTestCase
|
||||
self::assertStringContainsString('nur im Divi-Builder', $client['fields']['changes[0].attrs.module.decoration.attributes']);
|
||||
self::assertSame([], $this->saved);
|
||||
}
|
||||
|
||||
/** Ruling P13: the outline of a write result names library IDs only with library access. */
|
||||
public function testTheWriteOutlineNamesLibraryIdsOnlyWithLibraryAccess(): void
|
||||
{
|
||||
$content = DiviFixtures::markup('stage3/page--globale-instanzen.html');
|
||||
$changes = [['id' => 's0.r0.c0.m0', 'set' => ['text' => 'Termin buchen']]];
|
||||
|
||||
$hidden = $this->updateOn($content, $changes);
|
||||
$failures = [];
|
||||
$this->writer = DiviWriterFixture::writer($this->saved, $failures, false, LibraryFixtures::loader());
|
||||
$shown = $this->updateOn($content, $changes);
|
||||
|
||||
self::assertTrue($hidden['ok']);
|
||||
self::assertStringContainsString('s0.r0.c0.g0 text', (string) $hidden['outline']);
|
||||
self::assertStringContainsString('(nur lesbar)', (string) $hidden['outline']);
|
||||
self::assertStringNotContainsString('1059', (string) json_encode($hidden));
|
||||
self::assertStringNotContainsString('1058', (string) json_encode($hidden));
|
||||
self::assertStringContainsString('[global 1059]', (string) $shown['outline']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user