fix: lit/divi-colors-set und lit/divi-variables-set lehnen unbekannte Item-Felder ab (delete, Tippfehler)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
bb2377991d
commit
02ed5bdbd3
@@ -99,6 +99,7 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
'maxItems' => self::MAX_ITEMS,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'additionalProperties' => false,
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'type' => 'string',
|
||||
@@ -226,6 +227,13 @@ final class DiviColorsSetAbility implements Ability, RequiresDiviFeatures
|
||||
private function apply(ColorBook $book, array $item, string $field, array &$errors): ColorBook
|
||||
{
|
||||
$known = count($errors);
|
||||
ItemKeys::refuseUnknown(
|
||||
$item,
|
||||
['id', 'label', 'color', 'relative', 'folder', 'status'],
|
||||
$field,
|
||||
$errors,
|
||||
'Farben werden nicht gelöscht, nur deaktiviert (status: inactive)'
|
||||
);
|
||||
$id = array_key_exists('id', $item) ? self::existing($book, $item['id'], $field . '.id', $errors) : null;
|
||||
if (count($errors) > $known) {
|
||||
return $book;
|
||||
|
||||
@@ -87,6 +87,7 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
||||
'maxItems' => self::MAX_ITEMS,
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'additionalProperties' => false,
|
||||
'properties' => [
|
||||
'id' => ['type' => 'string', 'description' => 'Vorhandene Variable: ID oder Name.'],
|
||||
'type' => [
|
||||
@@ -201,6 +202,13 @@ final class DiviVariablesSetAbility implements Ability, RequiresDiviFeatures
|
||||
private function apply(VariableBook $book, array $item, string $field, array &$errors): VariableBook
|
||||
{
|
||||
$known = count($errors);
|
||||
ItemKeys::refuseUnknown(
|
||||
$item,
|
||||
['id', 'type', 'label', 'value', 'status'],
|
||||
$field,
|
||||
$errors,
|
||||
'Variablen werden nicht gelöscht, nur archiviert (status: archived)'
|
||||
);
|
||||
$named = array_key_exists('id', $item);
|
||||
$found = $named ? self::existing($book, $item, $field, $errors) : null;
|
||||
$type = $found['type'] ?? ($named ? null : self::newType($item, $field, $errors));
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Design;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Refuses item keys a tool does not know, so "delete" or a typo is never taken for a successful no-op. */
|
||||
final class ItemKeys
|
||||
{
|
||||
/**
|
||||
* @param array<array-key, mixed> $item
|
||||
* @param list<string> $allowed
|
||||
* @param array<string, string> $errors gets one entry per unknown key (field.key)
|
||||
*/
|
||||
public static function refuseUnknown(array $item, array $allowed, string $field, array &$errors, string $deleteHint): void
|
||||
{
|
||||
foreach (array_diff(array_map('strval', array_keys($item)), $allowed) as $key) {
|
||||
$errors[$field . '.' . $key] = $key === 'delete' ? 'unbekanntes Feld; ' . $deleteHint : 'unbekanntes Feld';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,23 @@ final class DiviColorsSetAbilityTest extends TestCase
|
||||
self::assertSame([], $this->saves);
|
||||
}
|
||||
|
||||
public function testUnknownItemKeysAreRefusedAndCollectedWithOtherErrors(): void
|
||||
{
|
||||
$error = $this->ability()->execute(['items' => [
|
||||
['id' => 'gcid-holz000001', 'delete' => true],
|
||||
['id' => 'gcid-holz000001', 'stauts' => 'inactive'],
|
||||
['id' => 'Fehlt', 'color' => '#ffffff'],
|
||||
]]);
|
||||
|
||||
self::assertToolError($error, 'validation_failed');
|
||||
$fields = self::errorFields($error);
|
||||
self::assertSame(['items[0].delete', 'items[1].stauts', 'items[2].id'], array_keys($fields));
|
||||
self::assertStringContainsString('inactive', $fields['items[0].delete']);
|
||||
self::assertSame('unbekanntes Feld', $fields['items[1].stauts']);
|
||||
self::assertSame([], $this->saves);
|
||||
self::assertFalse($this->ability()->inputSchema()['properties']['items']['items']['additionalProperties']);
|
||||
}
|
||||
|
||||
public function testAFailedWriteIsRolledBack(): void
|
||||
{
|
||||
$calls = [];
|
||||
|
||||
@@ -275,6 +275,24 @@ final class DiviVariablesSetAbilityTest extends TestCase
|
||||
self::assertSame([], $this->saves);
|
||||
}
|
||||
|
||||
/** Ruling V1: "delete" (or a typo) must not look like a successful no-op. */
|
||||
public function testUnknownItemKeysAreRefusedAndCollectedWithOtherErrors(): void
|
||||
{
|
||||
$error = $this->ability()->execute(['items' => [
|
||||
['id' => 'Slogan', 'delete' => true],
|
||||
['id' => 'Slogan', 'stauts' => 'archived'],
|
||||
['id' => 'Fehlt', 'value' => '1px'],
|
||||
]]);
|
||||
|
||||
self::assertToolError($error, 'validation_failed');
|
||||
$fields = self::errorFields($error);
|
||||
self::assertSame(['items[0].delete', 'items[1].stauts', 'items[2].id'], array_keys($fields));
|
||||
self::assertStringContainsString('archiviert', $fields['items[0].delete']);
|
||||
self::assertSame('unbekanntes Feld', $fields['items[1].stauts']);
|
||||
self::assertSame([], $this->saves);
|
||||
self::assertFalse($this->ability()->inputSchema()['properties']['items']['items']['additionalProperties']);
|
||||
}
|
||||
|
||||
/** Divi silently drops variables with an empty label or the label "0" (sanitize_global_variables_data). */
|
||||
public function testEmptyAndZeroLabelsAreRefused(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user