Files
janniclas.loosenandClaude Opus 5.5 4137882008 fix: harden the Divi BlockTree against opaque/invalid writes
Final fix wave for stage 2a, load-bearing for 2b's writers:
- Block::setAttrs()/Container::insertChild()/removeChild() refuse on an opaque
  block (undecodable attrs or a global module) instead of silently corrupting it.
- Container::append() is now @internal (Parser only) and refuses on a void
  block, so a child can no longer be silently dropped from the serialized output.
- Block::create() validates the block name against namespace/block-name.
- Json::decodeObject() returns null (-> opaque) for a top-level object whose
  keys are a plain list ("0","1",...) rather than returning [] and letting the
  Serializer re-encode it as a corrupting JSON array.
- AttrMap::set() rejects empty path segments and keeps an existing stdClass
  value an object when rewrapping it (no silent object->list flip); AttrMap::get()
  is updated in step so it can still read back through such an object.
- Block::copy() deep-clones stdClass attribute values instead of sharing them.
- Json::encode()'s existing JsonException-on-invalid-UTF-8 behaviour is documented.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 13:12:49 +02:00

52 lines
1.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\BlockTree;
use JsonException;
use LIT\MCP\Integrations\Divi\BlockTree\Json;
use PHPUnit\Framework\TestCase;
use stdClass;
final class JsonTest extends TestCase
{
public function testDecodesAPlainObject(): void
{
self::assertSame(['a' => 1], Json::decodeObject('{"a":1}'));
self::assertSame([], Json::decodeObject('{}'));
}
public function testRejectsNonObjectTopLevelJson(): void
{
self::assertNull(Json::decodeObject('[1,2,3]'));
self::assertNull(Json::decodeObject('"a string"'));
self::assertNull(Json::decodeObject('not json'));
}
/** A JSON object whose keys are "0", "1", … decodes to a PHP list – re-encoding it would
* silently turn it back into a JSON array (Json.php docblock, EmptyArrayCorruptionMigration). */
public function testTreatsAListLikeTopLevelObjectAsOpaqueRatherThanAnArray(): void
{
self::assertNull(Json::decodeObject('{"0":"a","1":"b"}'));
self::assertNull(Json::decodeObject('{"0":"a"}'));
}
public function testDoesNotFlagAGenuineObjectWithNonSequentialNumericKeys(): void
{
self::assertSame(['0' => 'a', '2' => 'b'], Json::decodeObject('{"0":"a","2":"b"}'));
}
public function testEncodeThrowsOnInvalidUtf8(): void
{
$this->expectException(JsonException::class);
Json::encode("\xB1\x31");
}
public function testEncodeRoundtripsAnEmptyObject(): void
{
self::assertSame('{}', Json::encode(new stdClass()));
}
}