All-or-nothing writes: validation (fields, terms, featured image, parent, template) before any write; failed extras delete the new draft (create) or restore revision, old columns, fields, terms and featured image (update). content-update requires rev; Rev::ofPost covers content, title, excerpt, status and post_modified_gmt. MetaFieldSource::write() now throws on failure. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
144 lines
6.0 KiB
PHP
144 lines
6.0 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 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\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 testFailedUpdateThrows(): void
|
|
{
|
|
$field = new FieldDefinition('count', 'count', 'integer', 'meta', 'writable');
|
|
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]);
|
|
}
|
|
}
|