Files
WP-LIT-MCP/tests/Unit/Guides/GuideRepositoryTest.php
T
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

100 lines
3.3 KiB
PHP
Raw 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\Guides;
use Brain\Monkey\Filters;
use LIT\MCP\Guides\GuideRepository;
use LIT\MCP\Tests\Unit\TestCase;
final class GuideRepositoryTest extends TestCase
{
private GuideRepository $guides;
protected function setUp(): void
{
parent::setUp();
$this->guides = new GuideRepository(dirname(__DIR__, 2) . '/Fixtures/guides');
}
public function testListsTopicsSortedWithoutExtension(): void
{
self::assertSame(['divi-shortform', 'overview'], $this->guides->topics());
}
public function testReturnsMarkdownOfKnownTopic(): void
{
self::assertStringContainsString('Testinhalt Kurzform', (string) $this->guides->get('divi-shortform'));
}
/**
* @dataProvider maliciousTopics
*/
public function testRejectsAnythingThatIsNotAPlainTopicName(string $topic): void
{
self::assertNull($this->guides->get($topic));
}
/** @return array<string, array{string}> */
public static function maliciousTopics(): array
{
return [
'traversal' => ['../../wp-config'],
'with extension' => ['overview.md'],
'empty' => [''],
'absolute' => ['/etc/passwd'],
'unknown' => ['does-not-exist'],
'uppercase' => ['OVERVIEW'],
];
}
public function testFilesAreTheDefaultWhenNoFilterIsRegistered(): void
{
Filters\expectApplied('lit_mcp_guides')->once()->with([])->andReturn([]);
self::assertSame(['divi-shortform', 'overview'], $this->guides->topics());
self::assertStringContainsString('Testinhalt Kurzform', (string) $this->guides->get('divi-shortform'));
}
public function testTheFilterAddsAndOverridesTopicsAndIgnoresInvalidNames(): void
{
Filters\expectApplied('lit_mcp_guides')->andReturn([
'site-rules' => "# Regeln\n<b>as-is</b>",
'overview' => 'Eigene Übersicht',
'../../wp-config' => 'nope',
'Upper' => 'nope',
'not-string' => ['nope'],
7 => 'nope',
]);
self::assertSame(['divi-shortform', 'overview', 'site-rules'], $this->guides->topics());
self::assertSame("# Regeln\n<b>as-is</b>", $this->guides->get('site-rules'));
self::assertSame('Eigene Übersicht', $this->guides->get('overview'));
self::assertNull($this->guides->get('../../wp-config'));
self::assertNull($this->guides->get('Upper'));
self::assertNull($this->guides->get('not-string'));
}
/** "$" without /D also matches before a trailing newline – such a filter topic is no topic name. */
public function testATopicWithTrailingNewlineIsRejected(): void
{
Filters\expectApplied('lit_mcp_guides')->andReturn(["site-rules\n" => 'eingeschleust']);
self::assertSame(['divi-shortform', 'overview'], $this->guides->topics());
self::assertNull($this->guides->get("site-rules\n"));
}
public function testANonArrayFilterResultIsIgnored(): void
{
Filters\expectApplied('lit_mcp_guides')->andReturn('kaputt');
self::assertSame(['divi-shortform', 'overview'], $this->guides->topics());
}
public function testMissingDirectoryYieldsNoTopics(): void
{
self::assertSame([], (new GuideRepository('/nonexistent/dir'))->topics());
}
}