Files
janniclas.loosenandClaude Opus 5.5 e420145cbe fix: anchored input patterns end at the last character (/D)
Without /D, "$" also matches before a trailing "\n", so "custom_meta_telefon\n",
a color setting "5\n" (stored as sent) or a whole reference followed by "\n"
passed VariableRef. Added /D to every ^...$ pattern that validates client or
admin input:
- VariableRef META_KEY, NUMBER, WHOLE (canonical/typeOf; Placeholders::field
  shares META_KEY)
- Cursor::decode, GuideRepository topic names (MCP input and filter keys)
- ExposurePolicy::sanitize ability keys and group ids, LogTab list filters

Not changed: Placeholders::PATTERN (runs on trim()), Warnings hex checks (hints
only), NodeSummary/AttrPathIndex/LogListTable (stored or Divi data, no client
input). Each fixed pattern has a test with a trailing "\n" that is rejected.

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

66 lines
1.9 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\Abilities;
use LIT\MCP\Abilities\Cursor;
use LIT\MCP\Tests\Unit\TestCase;
final class CursorTest extends TestCase
{
public function testRoundTrip(): void
{
self::assertSame(40, Cursor::decode(Cursor::encode(40)));
}
public function testMissingCursorStartsAtZero(): void
{
self::assertSame(0, Cursor::decode(null));
self::assertSame(0, Cursor::decode(''));
}
public function testTamperedCursorIsRejected(): void
{
self::assertNull(Cursor::decode('not-a-cursor'));
self::assertNull(Cursor::decode(base64_encode('o:-5')));
self::assertNull(Cursor::decode(base64_encode('o:12;DROP')));
}
/** "$" without /D also matches before a trailing newline – the cursor must end with the digits. */
public function testCursorWithTrailingNewlineIsRejected(): void
{
self::assertNull(Cursor::decode(rtrim(strtr(base64_encode("o:5\n"), '+/', '-_'), '=')));
}
public function testLimitIsClamped(): void
{
self::assertSame(20, Cursor::limit(null));
self::assertSame(1, Cursor::limit(0));
self::assertSame(100, Cursor::limit(500));
self::assertSame(7, Cursor::limit(7));
}
public function testPageDetectsMoreRows(): void
{
$page = Cursor::page([1, 2, 3], 2, 10);
self::assertSame([1, 2], $page['items']);
self::assertSame(12, Cursor::decode($page['next_cursor']));
self::assertNull(Cursor::page([1, 2], 2, 0)['next_cursor']);
}
public function testInvalidCursorErrorIsAValidationError(): void
{
self::assertToolError(Cursor::invalid(), 'validation_failed');
}
public function testInputPropertiesDescribeLimitAndCursor(): void
{
$properties = Cursor::inputProperties();
self::assertSame(100, $properties['limit']['maximum']);
self::assertSame('string', $properties['cursor']['type']);
}
}