config_preview_deploy-1.0.0-alpha3/tests/src/Kernel/ConfigRebaserTest.php

tests/src/Kernel/ConfigRebaserTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\config_preview_deploy\Kernel;

use Drupal\Core\Archiver\ArchiveTar;
use Drupal\config_preview_deploy\ConfigRebaser;
use Drupal\KernelTests\KernelTestBase;

/**
 * Tests the ConfigRebaser service.
 *
 * @group config_preview_deploy
 */
class ConfigRebaserTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['config_preview_deploy', 'system', 'config'];

  /**
   * The config rebaser service.
   *
   * @var \Drupal\config_preview_deploy\ConfigRebaser
   */
  protected ConfigRebaser $configRebaser;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(['system']);

    $this->configRebaser = $this->container->get('config_preview_deploy.config_rebase');
    $this->fileSystem = $this->container->get('file_system');
  }

  /**
   * Tests error handling for invalid tarball paths.
   */
  public function testRebaseWithInvalidTarball(): void {
    // Test 1: Non-existent tarball should be handled gracefully.
    $nonExistentPath = '/path/that/does/not/exist.tar.gz';

    try {
      $result = $this->configRebaser->rebase($nonExistentPath);
      // This should throw an exception before reaching here.
      $this->fail('Expected exception was not thrown for non-existent tarball');
    }
    catch (\Exception $e) {
      // Expected - verify error message is informative.
      $this->assertIsString($e->getMessage());
      $this->assertNotEmpty($e->getMessage());
    }
  }

  /**
   * Tests simple rebase without any preview changes.
   */
  public function testRebaseWithoutChanges(): void {
    // Enable additional modules for more services.
    $this->enableModules(['user', 'node']);

    // Get required services.
    $configDiff = $this->container->get('config_preview_deploy.config_diff');

    // Step 1: Start with config state.
    $this->config('system.site')
      ->set('name', 'Production Site')
      ->set('mail', 'admin@example.com')
      ->set('slogan', 'Production Slogan')
      ->save();

    // Step 2: Export current config to tarball (this represents production).
    $configExporter = $this->container->get('config_preview_deploy.config_export');
    $tarballContent = $configExporter->exportConfigTarball();

    // Save to a temporary file.
    $exportDir = $this->fileSystem->getTempDirectory() . '/export-' . uniqid();
    $this->fileSystem->mkdir($exportDir);
    $tarballPath = $exportDir . '/production-config.tar.gz';
    file_put_contents($tarballPath, $tarballContent);
    $this->assertFileExists($tarballPath, 'Export tarball should be created');

    // Step 3: Change config to simulate preview environment.
    $this->config('system.site')
      ->set('name', 'Preview Site')
      ->save();

    // Step 4: Initialize preview environment checkpoint.
    $configDiff->createBaseCheckpoint();

    // Step 5: Verify no changes from checkpoint (since we just created it).
    $this->assertFalse($configDiff->hasChanges(), 'Should have no changes from checkpoint');

    // Step 6: Perform rebase with production tarball.
    $result = $this->configRebaser->rebase($tarballPath);

    // Step 7: Verify rebase was successful.
    $this->assertTrue($result['success'], 'Rebase should succeed');
    $this->assertEmpty($result['conflicts'], 'Should have no conflicts');

    // Clear caches to ensure config is loaded fresh.
    \Drupal::configFactory()->reset();
    drupal_flush_all_caches();

    // Get fresh config instance.
    $siteConfig = \Drupal::configFactory()->get('system.site');

    // Verify production values were imported.
    $this->assertEquals('Production Site', $siteConfig->get('name'),
      'Production site name should be imported');
    $this->assertEquals('Production Slogan', $siteConfig->get('slogan'),
      'Production slogan should be imported');

    // Clean up.
    $this->fileSystem->deleteRecursive($exportDir);
  }

  /**
   * Tests complete rebase workflow.
   *
   * This integration test verifies that:
   * 1. Production config is imported during rebase
   * 2. Preview changes that can apply cleanly are preserved.
   *
   * Note: Due to the patch-based implementation, only changes that don't
   * conflict with the imported production config will be preserved.
   */
  public function testRebaseIntegrationWorkflow(): void {
    // Enable additional modules for more services.
    $this->enableModules(['user', 'node']);

    // Get required services.
    $configDiff = $this->container->get('config_preview_deploy.config_diff');

    // Step 1: Start with base config state (common base).
    $this->config('system.site')
      ->set('name', 'Common Base Site')
      ->set('mail', 'admin@example.com')
      ->set('slogan', 'Welcome')
      ->set('page.front', '/node')
      ->save();

    // Step 2: Simulate production environment changes.
    // Production updates the site name.
    $this->config('system.site')
      ->set('name', 'Production Site')
      ->save();

    // Export production config to tarball.
    $configExporter = $this->container->get('config_preview_deploy.config_export');
    $tarballContent = $configExporter->exportConfigTarball();

    // Save to a temporary file.
    $exportDir = $this->fileSystem->getTempDirectory() . '/export-' . uniqid();
    $this->fileSystem->mkdir($exportDir);
    $tarballPath = $exportDir . '/production-config.tar.gz';
    file_put_contents($tarballPath, $tarballContent);
    $this->assertFileExists($tarballPath, 'Export tarball should be created');

    // Step 3: Reset back to common base state to simulate preview environment.
    // This should be reverted back to the production state after rebase.
    $this->config('system.site')
      ->set('name', 'Common Base Site')
      ->save();

    // Initialize preview environment checkpoint with common base.
    $configDiff->createBaseCheckpoint();

    // Step 4: Make preview environment changes.
    // Also change a different config file to avoid conflicts.
    $this->config('system.performance')
      ->set('cache.page.max_age', 3600)
      ->save();

    // Verify preview has changes.
    $this->assertTrue($configDiff->hasChanges(), 'Preview should have changes');
    $diff = $configDiff->generateDiff();
    $this->assertStringNotContainsString('system.site', $diff, 'Diff should contain system.site');
    $this->assertStringContainsString('system.performance', $diff, 'Diff should contain system.performance');

    // Step 5: Perform rebase with production tarball.
    $result = $this->configRebaser->rebase($tarballPath);

    // Step 6: Verify rebase was successful.
    if (!$result['success']) {
      $this->fail('Rebase failed: ' . $result['message'] . ' - Conflicts: ' . json_encode($result['conflicts']));
    }
    $this->assertTrue($result['success'], 'Rebase should succeed');
    $this->assertEmpty($result['conflicts'], 'Should have no conflicts');

    // Clear caches to ensure config is loaded fresh.
    \Drupal::configFactory()->reset();
    drupal_flush_all_caches();

    // Get fresh config instance.
    $siteConfig = \Drupal::configFactory()->get('system.site');

    // Verify site name was updated to production value.
    // The preview change from 'Common Base Site' to 'Preview Site' is lost,
    // and production value 'Production Site' is applied.
    $this->assertEquals('Production Site', $siteConfig->get('name'),
      'Site name should be updated to production value');

    // Verify other unchanged fields remain the same.
    $this->assertEquals('admin@example.com', $siteConfig->get('mail'),
      'Site mail should remain unchanged');
    $this->assertEquals('Welcome', $siteConfig->get('slogan'),
      'Site slogan should remain unchanged');

    // Verify non-conflicting change was preserved.
    // (preview-specific change in different config file).
    $performanceConfig = \Drupal::configFactory()->get('system.performance');
    $this->assertEquals(3600, $performanceConfig->get('cache.page.max_age'),
      'Preview performance config change should be preserved');

    // Clean up.
    $this->fileSystem->deleteRecursive($exportDir);
  }

  /**
   * Tests rebase with conflicts.
   *
   * This test covers:
   * 1. Create conflicting changes in production and preview
   * 2. Attempt rebase
   * 3. Verify conflicts are detected and reported.
   */
  public function testRebaseWithConflicts(): void {
    // Enable additional modules.
    $this->enableModules(['user', 'node']);

    // Get required services.
    $configDiff = $this->container->get('config_preview_deploy.config_diff');

    // Step 1: Create production state and export.
    $this->config('system.site')
      ->set('name', 'Production Updated Site')
      ->set('slogan', 'Production New Slogan')
      ->set('page.403', '/production-403')
      ->set('page.404', '/production-404')
      ->set('page.front', '/production-home')
      ->set('mail', 'production@example.com')
      ->save();

    // Export complete configuration.
    $configExporter = $this->container->get('config_preview_deploy.config_export');
    $tarballContent = $configExporter->exportConfigTarball();

    // Save to a temporary file.
    $exportDir = $this->fileSystem->getTempDirectory() . '/export-conflict-' . uniqid();
    $this->fileSystem->mkdir($exportDir);
    $tarballPath = $exportDir . '/production-conflict.tar.gz';
    file_put_contents($tarballPath, $tarballContent);
    $this->assertFileExists($tarballPath);

    // Assert the production tarball contains the expected values.
    $extractDir = $exportDir . '/verify';
    $this->fileSystem->mkdir($extractDir);
    $archiver = new ArchiveTar($tarballPath, 'gz');
    $archiver->extract($extractDir);
    $siteConfigYaml = file_get_contents($extractDir . '/system.site.yml');
    $this->assertStringContainsString("name: 'Production Updated Site'", $siteConfigYaml,
      'Production tarball should contain Production Updated Site name');

    // Step 2: Reset to base state.
    $this->config('system.site')
      ->set('name', 'Base Site')
      ->set('slogan', '')
      ->set('mail', 'admin@example.com')
      ->set('page.403', '')
      ->set('page.404', '')
      ->set('page.front', '/node')
      ->save();

    // Step 3: Initialize base checkpoint AFTER setting base state.
    $configDiff->createBaseCheckpoint();

    // Step 4: Make conflicting changes in preview.
    // Change the same fields that production changed to different values.
    // Also change additional fields to ensure patch context conflicts.
    $this->config('system.site')
      ->set('name', 'Preview Different Site')
      ->set('slogan', 'Preview Different Slogan')
      ->set('mail', 'preview@example.com')
      ->set('page.403', '/preview-403')
      ->set('page.404', '/preview-404')
      ->set('page.front', '/preview-home')
      ->save();

    // Verify preview has changes.
    $this->assertTrue($configDiff->hasChanges(), 'Config diff should detect changes');
    $diff = $configDiff->generateDiff();
    $this->assertNotEmpty($diff, 'Generated diff should not be empty');

    // Assert the diff is correctly between base state and preview.
    // It should show changes FROM base TO preview.
    $this->assertStringContainsString('-name: \'Base Site\'', $diff);
    $this->assertStringContainsString('+name: \'Preview Different Site\'', $diff);
    $this->assertStringContainsString('-slogan: \'\'', $diff);
    $this->assertStringContainsString('+slogan: \'Preview Different Slogan\'', $diff);
    $this->assertStringContainsString('-mail: admin@example.com', $diff);
    $this->assertStringContainsString('+mail: preview@example.com', $diff);

    // Flush all caches before rebase.
    \Drupal::configFactory()->reset();
    drupal_flush_all_caches();

    // Step 5: Attempt rebase.
    // Get a fresh instance of the rebaser service after cache clear.
    $freshRebaser = $this->container->get('config_preview_deploy.config_rebase');
    $result = $freshRebaser->rebase($tarballPath);

    // Step 6: Verify conflicts were detected.
    // When both production and preview change the same fields,
    // the patch should fail to apply.
    $this->assertFalse($result['success'], 'Rebase should fail due to conflicts');
    $this->assertNotEmpty($result['conflicts'], 'Should have conflicts');
    $this->assertStringContainsString('conflicts', (string) $result['message']);

    // Step 7: Verify conflicts file is created.
    $this->assertNotEmpty($result['conflicts_file'], 'Conflicts file path should be provided');
    $this->assertFileExists($result['conflicts_file'], 'Conflicts file should be created');

    // Read the conflicts file.
    $conflictsDiff = file_get_contents($result['conflicts_file']);

    // Verify it contains our preview changes.
    $this->assertStringContainsString('Preview Different Site', $conflictsDiff);
    $this->assertStringContainsString('Preview Different Slogan', $conflictsDiff);

    // Step 8: Verify production config was imported.
    // The rebase imports production config but fails to re-apply
    // preview changes.
    // Clear caches.
    \Drupal::configFactory()->reset();
    drupal_flush_all_caches();

    $siteConfig = \Drupal::configFactory()->get('system.site');
    $this->assertEquals('Production Updated Site', $siteConfig->get('name'),
      'Production config should be imported');
    $this->assertEquals('Production New Slogan', $siteConfig->get('slogan'),
      'Production slogan should be imported');

    // Preview-only change should be lost due to conflict.
    $this->assertNotEquals('/preview-home', $siteConfig->get('page.front'),
      'Preview-only changes may be lost during conflict');

    // Clean up.
    $this->fileSystem->deleteRecursive($exportDir);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc