Files
WP-LIT-MCP/tests/Unit/Integrations/WordPress/Schema/RegisteredMetaSourceTest.php
T
Jan-Niclas LoosenandClaude Opus 5.5 234fd1edac fix(security): Pods-Meta nur lesbar, wenn Pods aktiv, aber nicht nutzbar
Ist Pods aktiv, seine API aber nicht nutzbar, melden gesampelte und registrierte Meta-Schlüssel READONLY statt schreibbar. Sonst würden Pods-Felder (auch über Pods' register_meta-Integration) an Pods vorbei geschrieben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:30:05 +01:00

167 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\WordPress\Schema;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
final class RegisteredMetaSourceTest extends TestCase
{
/** @return array<string, array<string, mixed>> */
private function fieldsByKey(): array
{
Functions\when('get_registered_meta_keys')->alias(static function (string $type, string $sub = ''): array {
if ($sub === '') {
return ['footnotes' => ['type' => 'string', 'single' => true, 'show_in_rest' => true]];
}
return [
'_internal' => ['type' => 'integer', 'single' => true, 'show_in_rest' => false],
'tags_list' => ['type' => 'array', 'single' => false, 'description' => 'Liste'],
];
});
Functions\when('is_protected_meta')->alias(static fn (string $key): bool => str_starts_with($key, '_'));
$fields = (new RegisteredMetaSource())->fieldsFor('event');
return array_column(array_map(static fn (FieldDefinition $f): array => $f->toArray(), $fields), null, 'key');
}
public function testDescribesGlobalAndSubtypeMetaKeys(): void
{
$fields = $this->fieldsByKey();
self::assertSame(['footnotes', '_internal', 'tags_list'], array_keys($fields));
self::assertSame('string', $fields['footnotes']['type']);
self::assertSame(FieldDefinition::WRITABLE, $fields['footnotes']['status']);
self::assertSame('meta', $fields['footnotes']['source']);
self::assertFalse($fields['footnotes']['multiple']);
}
public function testReadOnlyMakesEveryRegisteredKeyReadOnly(): void
{
Functions\when('get_registered_meta_keys')->justReturn(['city' => ['type' => 'string']]);
Functions\when('is_protected_meta')->justReturn(false);
$fields = (new RegisteredMetaSource(readOnly: true))->fieldsFor('lit_person');
self::assertSame(FieldDefinition::READONLY, $fields[0]->status);
}
public function testProtectedMetaHiddenFromRestIsReadonly(): void
{
$fields = $this->fieldsByKey();
self::assertSame(FieldDefinition::READONLY, $fields['_internal']['status']);
self::assertSame('integer', $fields['_internal']['type']);
}
public function testNonSingleMetaIsMultiple(): void
{
$fields = $this->fieldsByKey();
self::assertTrue($fields['tags_list']['multiple']);
self::assertSame('Liste', $fields['tags_list']['description']);
}
public function testReadUsesSingleOrMultipleMeta(): void
{
Functions\expect('get_post_meta')->once()->with(5, 'footnotes', true)->andReturn('x');
Functions\expect('get_post_meta')->once()->with(5, 'tags_list', false)->andReturn(['a', 'b']);
$source = new RegisteredMetaSource();
self::assertSame('x', $source->read(5, new FieldDefinition('footnotes', 'footnotes', 'string', 'meta', 'writable')));
self::assertSame(
['a', 'b'],
$source->read(5, new FieldDefinition('tags_list', 'tags_list', 'array', 'meta', 'writable', false, true))
);
}
public function testWriteUpdatesReplacesListsAndDeletesNull(): void
{
$single = new FieldDefinition('footnotes', 'footnotes', 'string', 'meta', 'writable');
$multi = new FieldDefinition('tags_list', 'tags_list', 'array', 'meta', 'writable', false, true);
$gone = new FieldDefinition('old', 'old', 'string', 'meta', 'writable');
Functions\expect('update_post_meta')->once()->with(5, 'footnotes', 'neu');
Functions\expect('delete_post_meta')->once()->with(5, 'tags_list');
Functions\expect('add_post_meta')->once()->with(5, 'tags_list', 'a');
Functions\expect('add_post_meta')->once()->with(5, 'tags_list', 'b');
Functions\expect('delete_post_meta')->once()->with(5, 'old');
(new RegisteredMetaSource())->write(
5,
'event',
['footnotes' => 'neu', 'tags_list' => ['a', 'b'], 'old' => null],
['footnotes' => $single, 'tags_list' => $multi, 'old' => $gone]
);
}
public function testUnchangedValueIsNotAFailure(): void
{
$field = new FieldDefinition('count', 'count', 'integer', 'meta', 'writable');
Functions\when('sanitize_meta')->returnArg(2);
Functions\expect('update_post_meta')->once()->with(5, 'count', 3)->andReturn(false);
Functions\expect('get_post_meta')->once()->with(5, 'count', false)->andReturn(['3']);
(new RegisteredMetaSource())->write(5, 'event', ['count' => 3], ['count' => $field]);
self::addToAssertionCount(1);
}
public function testTheComparisonUsesTheSanitizedValue(): void
{
$field = new FieldDefinition('slug_meta', 'slug_meta', 'string', 'meta', 'writable');
Functions\expect('sanitize_meta')->once()->with('slug_meta', ' Trier ', 'post', 'event')->andReturn('trier');
Functions\when('update_post_meta')->justReturn(false);
Functions\when('get_post_meta')->justReturn(['trier']);
(new RegisteredMetaSource())->write(5, 'event', ['slug_meta' => ' Trier '], ['slug_meta' => $field]);
self::addToAssertionCount(1);
}
public function testFailedUpdateThrows(): void
{
$field = new FieldDefinition('count', 'count', 'integer', 'meta', 'writable');
Functions\when('sanitize_meta')->returnArg(2);
Functions\when('update_post_meta')->justReturn(false);
Functions\when('get_post_meta')->justReturn(['2']);
$this->expectException(RuntimeException::class);
(new RegisteredMetaSource())->write(5, 'event', ['count' => 3], ['count' => $field]);
}
public function testFailedDeleteOfAnExistingKeyThrows(): void
{
$field = new FieldDefinition('old', 'old', 'string', 'meta', 'writable');
Functions\when('delete_post_meta')->justReturn(false);
Functions\expect('metadata_exists')->once()->with('post', 5, 'old')->andReturn(true);
$this->expectException(RuntimeException::class);
(new RegisteredMetaSource())->write(5, 'event', ['old' => null], ['old' => $field]);
}
public function testDeletingAMissingKeyIsNotAFailure(): void
{
$field = new FieldDefinition('old', 'old', 'string', 'meta', 'writable');
Functions\when('delete_post_meta')->justReturn(false);
Functions\expect('metadata_exists')->once()->with('post', 5, 'old')->andReturn(false);
(new RegisteredMetaSource())->write(5, 'event', ['old' => null], ['old' => $field]);
self::addToAssertionCount(1);
}
public function testFailedAddOfAListItemThrows(): void
{
$multi = new FieldDefinition('tags_list', 'tags_list', 'array', 'meta', 'writable', false, true);
Functions\when('delete_post_meta')->justReturn(true);
Functions\when('add_post_meta')->justReturn(false);
$this->expectException(RuntimeException::class);
(new RegisteredMetaSource())->write(5, 'event', ['tags_list' => ['a']], ['tags_list' => $multi]);
}
}