normalizer = new PathNormalizer(); } public function testPublicPathIsNormalized(): void { self::assertSame('mcp/graylog/install.sh', $this->normalizer->publicPath('/mcp/graylog/install.sh')); } public function testPublicPathMustEndWithShellExtension(): void { $this->expectException(InvalidArgumentException::class); $this->normalizer->publicPath('mcp/graylog/install.txt'); } public function testPublicPathRejectsTraversal(): void { $this->expectException(InvalidArgumentException::class); $this->normalizer->publicPath('mcp/../install.sh'); } public function testRepositoryPathAllowsNestedFileWithoutShellRequirement(): void { self::assertSame('scripts/install', $this->normalizer->repositoryPath('/scripts/install')); } public function testRepositoryPathRejectsTraversal(): void { $this->expectException(InvalidArgumentException::class); $this->normalizer->repositoryPath('../install.sh'); } }