config_preview_deploy-1.0.0-alpha3/tests/src/Kernel/ConfigExportControllerTest.php
tests/src/Kernel/ConfigExportControllerTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\config_preview_deploy\Kernel;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\KernelTests\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Drupal\config_preview_deploy\Controller\ProductionController;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the config export controller endpoint.
*
* @group config_preview_deploy
*/
class ConfigExportControllerTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'user',
'key',
'file',
'serialization',
'config_preview_deploy',
];
/**
* The production controller.
*
* @var \Drupal\config_preview_deploy\Controller\ProductionController
*/
protected ProductionController $controller;
/**
* The hash verification service.
*
* @var \Drupal\config_preview_deploy\HashVerification
*/
protected $hashVerification;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['system', 'config_preview_deploy']);
$this->installEntitySchema('user');
// Set up hash salt for testing.
$this->setSetting('hash_salt', 'test_hash_salt');
// Mock HTTP_HOST for hash verification.
$_SERVER['HTTP_HOST'] = 'localhost';
// Create controller instance.
$this->controller = ProductionController::create($this->container);
$this->hashVerification = $this->container->get('config_preview_deploy.hash_verification');
// Set environment as production.
$this->container->get('state')->set('config_preview_deploy.is_production', TRUE);
}
/**
* Tests successful config export with valid authentication.
*/
public function testConfigExportSuccess(): void {
// Create some test configuration.
$config = $this->config('system.site');
$config->set('name', 'Test Site');
$config->set('mail', 'test@example.com');
$config->save();
// Generate valid authentication.
$timestamp = time();
$hash = $this->hashVerification->generateVerificationHash('localhost', $timestamp);
// Create request with valid auth headers.
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$request->headers->set('X-Config-Deploy-Hash', $hash);
$request->headers->set('X-Config-Deploy-Timestamp', (string) $timestamp);
// Mock the OAuth authentication by setting current user.
$user = $this->createUser(['accept config deployments']);
$this->container->get('current_user')->setAccount($user);
// Execute the export.
$response = $this->controller->exportConfig($request);
// Assert response.
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('application/gzip', $response->headers->get('Content-Type'));
$this->assertEquals('attachment; filename="config.tar.gz"', $response->headers->get('Content-Disposition'));
// Verify tarball contents.
$content = $response->getContent();
$this->assertNotEmpty($content);
// Extract and verify the tarball.
$tempFile = tempnam(sys_get_temp_dir(), 'config_test');
file_put_contents($tempFile, $content);
$archiver = new ArchiveTar($tempFile, 'gz');
$files = $archiver->listContent();
// Check that our test config is in the archive.
$fileNames = array_column($files, 'filename');
$this->assertContains('system.site.yml', $fileNames);
// Extract and verify content.
$tempDir = sys_get_temp_dir() . '/config_test_' . uniqid();
mkdir($tempDir);
$archiver->extract($tempDir);
$configContent = file_get_contents($tempDir . '/system.site.yml');
$configData = Yaml::decode($configContent);
$this->assertEquals('Test Site', $configData['name']);
$this->assertEquals('test@example.com', $configData['mail']);
// Clean up.
unlink($tempFile);
$this->removeDirectory($tempDir);
}
/**
* Tests config export with missing hash authentication.
*/
public function testConfigExportMissingHashOnly(): void {
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$response = $this->controller->exportConfig($request);
$this->assertEquals(401, $response->getStatusCode());
$data = json_decode($response->getContent(), TRUE);
$this->assertEquals('Missing authentication headers', $data['error']);
}
/**
* Tests config export with partial hash authentication.
*/
public function testConfigExportPartialHashAuth(): void {
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$request->headers->set('X-Config-Deploy-Hash', 'some-hash');
// Missing timestamp.
$response = $this->controller->exportConfig($request);
$this->assertEquals(401, $response->getStatusCode());
$data = json_decode($response->getContent(), TRUE);
$this->assertEquals('Missing authentication headers', $data['error']);
}
/**
* Tests config export with invalid hash.
*/
public function testConfigExportInvalidHash(): void {
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$request->headers->set('X-Config-Deploy-Hash', 'invalid-hash');
$request->headers->set('X-Config-Deploy-Timestamp', (string) time());
$response = $this->controller->exportConfig($request);
$this->assertEquals(403, $response->getStatusCode());
$data = json_decode($response->getContent(), TRUE);
$this->assertEquals('Invalid authentication hash', $data['error']);
}
/**
* Tests config export with expired timestamp.
*/
public function testConfigExportExpiredTimestamp(): void {
// Generate hash with old timestamp.
// More than 5 minutes ago.
$timestamp = time() - 400;
$hash = $this->hashVerification->generateVerificationHash('localhost', $timestamp);
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$request->headers->set('X-Config-Deploy-Hash', $hash);
$request->headers->set('X-Config-Deploy-Timestamp', (string) $timestamp);
$response = $this->controller->exportConfig($request);
$this->assertEquals(403, $response->getStatusCode());
$data = json_decode($response->getContent(), TRUE);
$this->assertEquals('Invalid authentication hash', $data['error']);
}
/**
* Tests that exported config matches core's config export format.
*/
public function testConfigExportFormatMatchesCore(): void {
// Create various types of configuration.
$this->config('system.site')
->set('name', 'Test Site')
->set('mail', 'test@example.com')
->save();
$this->config('user.settings')
->set('register', 'visitors')
->set('verify_mail', TRUE)
->save();
// Generate valid authentication.
$timestamp = time();
$hash = $this->hashVerification->generateVerificationHash('localhost', $timestamp);
// Create request.
$request = Request::create('/api/config-preview-deploy/export', 'GET');
$request->headers->set('X-Config-Deploy-Hash', $hash);
$request->headers->set('X-Config-Deploy-Timestamp', (string) $timestamp);
// Mock user.
$user = $this->createUser(['accept config deployments']);
$this->container->get('current_user')->setAccount($user);
// Get export.
$response = $this->controller->exportConfig($request);
$content = $response->getContent();
// Extract tarball.
$tempFile = tempnam(sys_get_temp_dir(), 'config_test');
file_put_contents($tempFile, $content);
$tempDir = sys_get_temp_dir() . '/config_test_' . uniqid();
mkdir($tempDir);
$archiver = new ArchiveTar($tempFile, 'gz');
$archiver->extract($tempDir);
// Verify the structure matches core export.
$this->assertFileExists($tempDir . '/system.site.yml');
$this->assertFileExists($tempDir . '/user.settings.yml');
// Verify YAML format.
$siteConfig = Yaml::decode(
file_get_contents($tempDir . '/system.site.yml')
);
$this->assertEquals('Test Site', $siteConfig['name']);
$this->assertEquals('test@example.com', $siteConfig['mail']);
// Clean up.
unlink($tempFile);
$this->removeDirectory($tempDir);
}
/**
* Helper to recursively remove a directory.
*/
protected function removeDirectory(string $dir): void {
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
}
