Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LockFileValidatorTest.php 5.92 KiB
<?php

namespace Drupal\Tests\package_manager\Kernel;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreRequireEvent;
use Drupal\package_manager\EventSubscriber\LockFileValidator;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\ValidationResult;
use org\bovigo\vfs\vfsStream;

/**
 * @coversDefaultClass \Drupal\package_manager\EventSubscriber\LockFileValidator
 *
 * @group package_manager
 */
class LockFileValidatorTest extends PackageManagerKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $vendor = vfsStream::newDirectory('vendor');
    $this->vfsRoot->addChild($vendor);

    $path_locator = $this->prophesize(PathLocator::class);
    $path_locator->getActiveDirectory()->willReturn($this->vfsRoot->url());
    $path_locator->getProjectRoot()->willReturn($this->vfsRoot->url());
    $path_locator->getWebRoot()->willReturn('');
    $path_locator->getVendorDirectory()->willReturn($vendor->url());
    $this->container->set('package_manager.path_locator', $path_locator->reveal());
  }

  /**
   * {@inheritdoc}
   */
  protected function disableValidators(ContainerBuilder $container): void {
    parent::disableValidators($container);

    // Disable the disk space validator, since it tries to inspect the file
    // system in ways that vfsStream doesn't support, like calling stat() and
    // disk_free_space().
    $container->removeDefinition('package_manager.validator.disk_space');

    // Disable the Composer settings validator, since it tries to read Composer
    // files that may not exist in this test.
    $container->removeDefinition('package_manager.validator.composer_settings');
  }

  /**
   * Tests that if no active lock file exists, a stage cannot be created.
   *
   * @covers ::storeHash
   */
  public function testCreateWithNoLock(): void {
    $no_lock = ValidationResult::createError(['Could not hash the active lock file.']);
    $this->assertResults([$no_lock], PreCreateEvent::class);
  }

  /**
   * Tests that if an active lock file exists, a stage can be created.
   *
   * @covers ::storeHash
   * @covers ::deleteHash
   */
  public function testCreateWithLock(): void {
    $this->createActiveLockFile();
    $this->assertResults([]);

    // Change the lock file to ensure the stored hash of the previous version
    // has been deleted.
    $this->vfsRoot->getChild('composer.lock')->setContent('"changed"');
    $this->assertResults([]);
  }

  /**
   * Tests validation when the lock file has changed.
   *
   * @dataProvider providerValidateStageEvents
   */
  public function testLockFileChanged(string $event_class): void {
    $this->createActiveLockFile();

    // Add a listener with an extremely high priority to the same event that
    // should raise the validation error. Because the validator uses the default
    // priority of 0, this listener changes lock file before the validator
    // runs.
    $this->addListener($event_class, function () {
      $this->vfsRoot->getChild('composer.lock')->setContent('"changed"');
    });
    $result = ValidationResult::createError([
      'Stored lock file hash does not match the active lock file.',
    ]);
    $this->assertResults([$result], $event_class);
  }

  /**
   * Tests validation when the lock file is deleted.
   *
   * @dataProvider providerValidateStageEvents
   */
  public function testLockFileDeleted(string $event_class): void {
    $this->createActiveLockFile();

    // Add a listener with an extremely high priority to the same event that
    // should raise the validation error. Because the validator uses the default
    // priority of 0, this listener deletes lock file before the validator
    // runs.
    $this->addListener($event_class, function () {
      $this->vfsRoot->removeChild('composer.lock');
    });
    $result = ValidationResult::createError([
      'Could not hash the active lock file.',
    ]);
    $this->assertResults([$result], $event_class);
  }

  /**
   * Tests validation when a stored hash of the active lock file is unavailable.
   *
   * @dataProvider providerValidateStageEvents
   */
  public function testNoStoredHash(string $event_class): void {
    $this->createActiveLockFile();

    $reflector = new \ReflectionClassConstant(LockFileValidator::class, 'STATE_KEY');
    $state_key = $reflector->getValue();

    // Add a listener with an extremely high priority to the same event that
    // should raise the validation error. Because the validator uses the default
    // priority of 0, this listener deletes stored hash before the validator
    // runs.
    $this->addListener($event_class, function () use ($state_key) {
      $this->container->get('state')->delete($state_key);
    });
    $result = ValidationResult::createError([
      'Could not retrieve stored hash of the active lock file.',
    ]);
    $this->assertResults([$result], $event_class);
  }

  /**
   * Data provider for test methods that validate the staging area.
   *
   * @return array[]
   *   Sets of arguments to pass to the test method.
   */
  public function providerValidateStageEvents(): array {
    return [
      'pre-require' => [
        PreRequireEvent::class,
      ],
      'pre-apply' => [
        PreApplyEvent::class,
      ],
    ];
  }

  /**
   * Creates a 'composer.lock' file in the active directory.
   */
  private function createActiveLockFile(): void {
    $lock_file = vfsStream::newFile('composer.lock')->setContent('{}');
    $this->vfsRoot->addChild($lock_file);
  }

  /**
   * Adds an event listener with the highest possible priority.
   *
   * @param string $event_class
   *   The event class to listen for.
   * @param callable $listener
   *   The listener to add.
   */
  private function addListener(string $event_class, callable $listener): void {
    $this->container->get('event_dispatcher')
      ->addListener($event_class, $listener, PHP_INT_MAX);
  }

}