filesystem = new Filesystem(); $this->workDir = sys_get_temp_dir().'/get-installer-bootstrap-controller-test-'.bin2hex(random_bytes(6)); $this->filesystem->mkdir($this->workDir); } protected function tearDown(): void { $this->filesystem->remove($this->workDir); parent::tearDown(); } public function testNewMappingRequiresAuthentication(): void { $this->client->request('GET', '/admin/mappings/new'); self::assertResponseRedirects('/admin/login'); } public function testDashboardUsesUtilityAdminLayout(): void { $this->loginAsAdmin(); $mapping = (new ScriptMapping()) ->setPublicPath('mcp/graylog/install.sh') ->setRepositoryUrl('https://forge.lclr.dev/AI/graylog-mcp.git') ->setGitRef('main') ->setRepositoryFilePath('install.sh') ->setActive(true); $this->entityManager->persist($mapping); $this->entityManager->flush(); $this->client->request('GET', '/admin'); self::assertSelectorExists('body.admin-page'); self::assertSelectorExists('.admin-toolbar'); self::assertSelectorExists('.admin-table'); self::assertSelectorExists('.status-badge'); } public function testAdminCanCreateMappingWithNormalizedPaths(): void { $this->loginAsAdmin(); $crawler = $this->client->request('GET', '/admin/mappings/new'); $form = $crawler->selectButton('Enregistrer')->form([ 'script_mapping[publicPath]' => '/mcp/graylog/install.sh', 'script_mapping[repositoryUrl]' => 'https://forge.lclr.dev/AI/graylog-mcp.git', 'script_mapping[gitRef]' => 'main', 'script_mapping[repositoryFilePath]' => '/install.sh', 'script_mapping[active]' => '1', ]); $this->client->submit($form); self::assertResponseRedirects('/admin'); $mapping = $this->entityManager->getRepository(ScriptMapping::class)->findOneBy([ 'publicPath' => 'mcp/graylog/install.sh', ]); self::assertInstanceOf(ScriptMapping::class, $mapping); self::assertSame('install.sh', $mapping->getRepositoryFilePath()); } public function testInvalidPublicPathRendersValidationError(): void { $this->loginAsAdmin(); $crawler = $this->client->request('GET', '/admin/mappings/new'); $form = $crawler->selectButton('Enregistrer')->form([ 'script_mapping[publicPath]' => 'mcp/graylog/install.txt', 'script_mapping[repositoryUrl]' => 'https://forge.lclr.dev/AI/graylog-mcp.git', 'script_mapping[gitRef]' => 'main', 'script_mapping[repositoryFilePath]' => 'install.sh', 'script_mapping[active]' => '1', ]); $this->client->submit($form); self::assertResponseStatusCodeSame(422); self::assertSelectorTextContains('form', 'Public path must end with .sh.'); } public function testAdminCanSynchronizeMapping(): void { $this->loginAsAdmin(); $repositoryDir = $this->createRepository([ 'install.sh' => "#!/usr/bin/env bash\nprintf 'synced'\n", ]); $mapping = (new ScriptMapping()) ->setPublicPath('mcp/graylog/install.sh') ->setRepositoryUrl($repositoryDir) ->setGitRef('main') ->setRepositoryFilePath('install.sh') ->setActive(true); $this->entityManager->persist($mapping); $this->entityManager->flush(); $crawler = $this->client->request('GET', '/admin'); $form = $crawler->selectButton('Synchroniser')->form(); $this->client->submit($form); self::assertResponseRedirects('/admin'); $syncedMapping = $this->entityManager->getRepository(ScriptMapping::class)->find($mapping->getId()); self::assertInstanceOf(ScriptMapping::class, $syncedMapping); self::assertSame( ScriptMapping::SYNC_STATUS_SYNCED, $syncedMapping->getLastSyncStatus(), (string) $syncedMapping->getLastSyncError() ); self::assertSame('scripts/'.$mapping->getId().'.sh', $syncedMapping->getCacheKey()); } private function loginAsAdmin(): void { $user = (new User())->setUsername('admin')->setPasswordHash('unused'); $this->entityManager->persist($user); $this->entityManager->flush(); $this->client->loginUser($user); } /** @param array $files */ private function createRepository(array $files): string { $repositoryDir = $this->workDir.'/repo'; $this->filesystem->mkdir($repositoryDir); foreach ($files as $path => $content) { $fullPath = $repositoryDir.'/'.$path; $this->filesystem->mkdir(dirname($fullPath)); file_put_contents($fullPath, $content); } $this->runGit(['init'], $repositoryDir); $this->runGit(['config', 'user.email', 'tests@example.com'], $repositoryDir); $this->runGit(['config', 'user.name', 'Tests'], $repositoryDir); $this->runGit(['add', '.'], $repositoryDir); $this->runGit(['commit', '-m', 'Initial commit'], $repositoryDir); $this->runGit(['branch', '-M', 'main'], $repositoryDir); return $repositoryDir; } /** @param list $arguments */ private function runGit(array $arguments, string $cwd): void { (new Process(['git', ...$arguments], $cwd))->mustRun(); } }