. */ declare(strict_types=1); namespace App\Tests\Controller; use App\Entity\UserSystem\User; use App\Services\System\BackupManager; use App\Services\System\UpdateExecutor; use PHPUnit\Framework\Attributes\Group; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; #[Group("slow")] #[Group("DB")] final class UpdateManagerControllerTest extends WebTestCase { private function loginAsAdmin($client): void { $entityManager = $client->getContainer()->get('doctrine')->getManager(); $userRepository = $entityManager->getRepository(User::class); $user = $userRepository->findOneBy(['name' => 'admin']); if (!$user) { $this->markTestSkipped('Admin user not found'); } $client->loginUser($user); } /** * Extract a CSRF token from the rendered update manager page. */ private function getCsrfTokenFromPage($crawler, string $formAction): string { $form = $crawler->filter('form[action*="' . $formAction . '"]'); if ($form->count() === 0) { $this->fail('Form with action containing "' . $formAction . '" not found on page'); } return $form->filter('input[name="_token"]')->attr('value'); } public function testIndexPageRequiresAuth(): void { $client = static::createClient(); $client->request('GET', '/en/system/update-manager'); // Should deny access (401 with HTTP Basic auth in test env) $this->assertResponseStatusCodeSame(401); } public function testIndexPageAccessibleByAdmin(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('GET', '/en/system/update-manager'); $this->assertResponseIsSuccessful(); } public function testCreateBackupRequiresCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('POST', '/en/system/update-manager/backup', [ '_token' => 'invalid', ]); // Should redirect with error flash $this->assertResponseRedirects(); } public function testCreateBackupWithValidCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); // Load the page and extract CSRF token from the backup form $crawler = $client->request('GET', '/en/system/update-manager'); $csrfToken = $this->getCsrfTokenFromPage($crawler, 'backup'); $client->request('POST', '/en/system/update-manager/backup', [ '_token' => $csrfToken, ]); $this->assertResponseRedirects(); // Clean up: delete the backup that was just created $backupManager = $client->getContainer()->get(BackupManager::class); $backups = $backupManager->getBackups(); foreach ($backups as $backup) { if (str_contains($backup['file'], 'manual')) { $backupManager->deleteBackup($backup['file']); } } } public function testDeleteBackupRequiresCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('POST', '/en/system/update-manager/backup/delete', [ '_token' => 'invalid', 'filename' => 'test.zip', ]); $this->assertResponseRedirects(); } public function testDeleteBackupWithValidCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); // Create a temporary backup file so the page shows the delete form $backupManager = $client->getContainer()->get(BackupManager::class); $backupDir = $backupManager->getBackupDir(); if (!is_dir($backupDir)) { mkdir($backupDir, 0755, true); } $testFile = 'test-delete-' . uniqid() . '.zip'; file_put_contents($backupDir . '/' . $testFile, 'test'); // Load the page and extract CSRF token from the delete form $crawler = $client->request('GET', '/en/system/update-manager'); $csrfToken = $this->getCsrfTokenFromPage($crawler, 'backup/delete'); $client->request('POST', '/en/system/update-manager/backup/delete', [ '_token' => $csrfToken, 'filename' => $testFile, ]); $this->assertResponseRedirects(); $this->assertFileDoesNotExist($backupDir . '/' . $testFile); } public function testDeleteLogRequiresCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('POST', '/en/system/update-manager/log/delete', [ '_token' => 'invalid', 'filename' => 'test.log', ]); $this->assertResponseRedirects(); } public function testDeleteLogWithValidCsrf(): void { $client = static::createClient(); $this->loginAsAdmin($client); // Create a temporary log file so the page shows the delete form $projectDir = $client->getContainer()->getParameter('kernel.project_dir'); $logDir = $projectDir . '/var/log/updates'; if (!is_dir($logDir)) { mkdir($logDir, 0755, true); } $testFile = 'update-test-delete-' . uniqid() . '.log'; file_put_contents($logDir . '/' . $testFile, 'test log content'); // Load the page and extract CSRF token from the log delete form $crawler = $client->request('GET', '/en/system/update-manager'); $csrfToken = $this->getCsrfTokenFromPage($crawler, 'log/delete'); $client->request('POST', '/en/system/update-manager/log/delete', [ '_token' => $csrfToken, 'filename' => $testFile, ]); $this->assertResponseRedirects(); $this->assertFileDoesNotExist($logDir . '/' . $testFile); } public function testDownloadBackupReturns404ForNonExistent(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('GET', '/en/system/update-manager/backup/download/nonexistent.zip'); $this->assertResponseStatusCodeSame(404); } public function testDownloadBackupSuccess(): void { $client = static::createClient(); $this->loginAsAdmin($client); // Create a temporary backup file to download $backupManager = $client->getContainer()->get(BackupManager::class); $backupDir = $backupManager->getBackupDir(); if (!is_dir($backupDir)) { mkdir($backupDir, 0755, true); } $testFile = 'test-download-' . uniqid() . '.zip'; file_put_contents($backupDir . '/' . $testFile, 'fake zip content'); $client->request('GET', '/en/system/update-manager/backup/download/' . $testFile); $this->assertResponseIsSuccessful(); $this->assertResponseHeaderSame('content-disposition', 'attachment; filename=' . $testFile); // Clean up @unlink($backupDir . '/' . $testFile); } public function testBackupDetailsReturns404ForNonExistent(): void { $client = static::createClient(); $this->loginAsAdmin($client); $client->request('GET', '/en/system/update-manager/backup/nonexistent.zip'); $this->assertResponseStatusCodeSame(404); } public function testRestoreBlockedWhenDisabled(): void { $client = static::createClient(); $this->loginAsAdmin($client); // DISABLE_BACKUP_RESTORE=1 is the default in .env, so this should return 403 $client->request('POST', '/en/system/update-manager/restore', [ '_token' => 'invalid', 'filename' => 'test.zip', ]); $this->assertResponseStatusCodeSame(403); } public function testCreateBackupBlockedWhenLocked(): void { $client = static::createClient(); $this->loginAsAdmin($client); // Load the page first to get CSRF token before locking $crawler = $client->request('GET', '/en/system/update-manager'); $csrfToken = $this->getCsrfTokenFromPage($crawler, 'backup'); // Acquire lock to simulate update in progress $updateExecutor = $client->getContainer()->get(UpdateExecutor::class); $updateExecutor->acquireLock(); try { $client->request('POST', '/en/system/update-manager/backup', [ '_token' => $csrfToken, ]); $this->assertResponseRedirects(); } finally { // Always release lock $updateExecutor->releaseLock(); } } }