Files
WP-LIT-MCP/tests/Unit/Integrations/WordPress/Content/PostPresenterTest.php
T
janniclas.loosenandClaude Opus 5.5 bb61c370a0 fix(security): honour Pods field restrictions (audit M4)
PodsFieldSource maps hidden, read_only, admin_only and
restrict_role/-capability like Pods' own view: hidden -> new status
HIDDEN, read_only -> READONLY, restricted and not permitted -> READONLY
with read_only_restricted, else HIDDEN. HIDDEN fields are not described,
read out, queried or written (they look unknown) but stay in the schema
for the media usage search; write() re-checks before Pods' save().
Required-but-restricted fields no longer block a create. Pods is not
active on DDEV: covered by unit tests with a field-option fixture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 19:29:29 +02:00

140 lines
6.8 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\WordPress\Content;
use Brain\Monkey\Functions;
use LIT\MCP\Abilities\Rev;
use LIT\MCP\Integrations\WordPress\Content\PostPresenter;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
use LIT\MCP\Integrations\WordPress\Schema\FieldSource;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use stdClass;
use WP_Post;
use WP_Term;
final class PostPresenterTest extends TestCase
{
private function stubLinks(): void
{
Functions\when('get_permalink')->justReturn('https://kunde.de/team/');
Functions\when('get_edit_post_link')->justReturn('https://kunde.de/wp-admin/post.php?post=5&action=edit');
Functions\when('get_preview_post_link')->justReturn('https://kunde.de/?p=5&preview=true');
Functions\when('get_the_author_meta')->justReturn('Jan');
Functions\when('get_page_template_slug')->justReturn('');
}
public function testSummaryContainsCoreDataAndUrls(): void
{
$this->stubLinks();
Functions\when('get_post_meta')->justReturn('');
$post = new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_title' => 'Team', 'post_name' => 'team']);
$summary = (new PostPresenter(Mockery::mock(ContentSchema::class)))->summary($post);
self::assertSame(5, $summary['id']);
self::assertSame('Team', $summary['title']);
self::assertSame('team', $summary['slug']);
self::assertSame('https://kunde.de/team/', $summary['url']);
self::assertFalse($summary['divi']);
}
public function testFullReadsTermsFieldsAndFeaturedImage(): void
{
$this->stubLinks();
Functions\when('get_post_meta')->alias(static fn (int $id, string $key) => match ($key) {
'_wp_attachment_image_alt' => 'Porträt',
default => '',
});
Functions\when('wp_get_post_terms')->justReturn([new WP_Term(['term_id' => 4, 'name' => 'News', 'slug' => 'news'])]);
Functions\when('get_post_thumbnail_id')->justReturn(33);
Functions\when('wp_get_attachment_url')->justReturn('https://kunde.de/wp-content/uploads/foto.jpg');
$role = new FieldDefinition('role', 'Funktion', 'string', 'pods', 'writable');
$source = Mockery::mock(FieldSource::class);
$source->expects('read')->with(5, $role)->andReturn('Leitung');
$schema = Mockery::mock(ContentSchema::class);
$schema->allows('taxonomies')->andReturn([['name' => 'category', 'label' => 'Kategorien', 'hierarchical' => true]]);
$schema->allows('fields')->andReturn(['role' => $role]);
$schema->allows('sourceOf')->andReturn($source);
$post = new WP_Post(['ID' => 5, 'post_type' => 'lit_person', 'post_content' => '<p>Hallo</p>', 'post_author' => '2']);
$full = (new PostPresenter($schema))->full($post);
self::assertSame('<p>Hallo</p>', $full['content']);
self::assertSame(['category' => [['id' => 4, 'name' => 'News', 'slug' => 'news']]], $full['terms']);
self::assertSame(['role' => 'Leitung'], $full['fields']);
self::assertSame(['id' => 33, 'url' => 'https://kunde.de/wp-content/uploads/foto.jpg', 'alt' => 'Porträt'], $full['featured_image']);
self::assertSame(['id' => 2, 'name' => 'Jan'], $full['author']);
self::assertSame(Rev::ofPost($post), $full['rev']);
self::assertSame([], $full['hints']);
}
public function testDiviContentIsWithheldAndPointsToTheDiviOutline(): void
{
$this->stubLinks();
Functions\when('get_post_meta')->alias(static fn (int $id, string $key) => $key === '_et_pb_use_builder' ? 'on' : '');
Functions\when('get_post_thumbnail_id')->justReturn(0);
$schema = Mockery::mock(ContentSchema::class);
$schema->allows('taxonomies')->andReturn([]);
$schema->allows('fields')->andReturn([]);
$post = new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_content' => '<!-- wp:divi/section /-->']);
$full = (new PostPresenter($schema))->full($post);
self::assertTrue($full['divi']);
self::assertNull($full['content']);
self::assertNull($full['featured_image']);
self::assertSame([PostPresenter::diviHint(false)], $full['hints']);
self::assertInstanceOf(stdClass::class, $full['terms']);
self::assertInstanceOf(stdClass::class, $full['fields']);
self::assertSame(Rev::ofPost($post), $full['rev']);
}
public function testDiviHintNamesTheDiviToolsOnlyWhenDiviIsActive(): void
{
self::assertStringContainsString('lit-divi-outline', PostPresenter::diviHint(true));
self::assertStringContainsString('lit-content-update', PostPresenter::diviHint(true));
foreach (['lit-divi-insert', 'lit-divi-update', 'lit-divi-move', 'lit-divi-delete'] as $tool) {
self::assertStringContainsString($tool, PostPresenter::diviHint(true));
}
self::assertStringNotContainsString('vorerst im Divi-Builder', PostPresenter::diviHint(true));
self::assertStringNotContainsString('lit-divi', PostPresenter::diviHint(false));
self::assertStringContainsString(PostPresenter::DIVI_INACTIVE_NOTE, PostPresenter::diviHint());
}
public function testPasswordAndProtectedReadonlyMetaFieldsAreOmitted(): void
{
$this->stubLinks();
Functions\when('get_post_meta')->justReturn('');
Functions\when('get_post_thumbnail_id')->justReturn(0);
$password = new FieldDefinition('secret', 'Secret', 'password', 'pods', 'writable');
$protected = new FieldDefinition('_internal', 'Internal', 'string', 'meta', 'readonly');
$visible = new FieldDefinition('role', 'Funktion', 'string', 'pods', 'writable');
$restricted = new FieldDefinition('boss', 'Boss', 'string', 'pods', FieldDefinition::HIDDEN);
$source = Mockery::mock(FieldSource::class);
$source->expects('read')->once()->with(5, $visible)->andReturn('Leitung');
$schema = Mockery::mock(ContentSchema::class);
$schema->allows('taxonomies')->andReturn([]);
$schema->allows('fields')->andReturn([
'secret' => $password, '_internal' => $protected, 'role' => $visible, 'boss' => $restricted,
]);
$schema->allows('sourceOf')->andReturn($source);
$post = new WP_Post(['ID' => 5, 'post_type' => 'lit_person']);
$full = (new PostPresenter($schema))->full($post);
self::assertSame(['role' => 'Leitung'], $full['fields']);
}
public function testDiviIsAlsoDetectedFromBlockMarkup(): void
{
Functions\when('get_post_meta')->justReturn('');
self::assertTrue(PostPresenter::usesDivi(new WP_Post(['post_content' => 'x <!-- wp:divi/text {} /--> y'])));
self::assertFalse(PostPresenter::usesDivi(new WP_Post(['post_content' => '<!-- wp:paragraph -->'])));
}
}