LockFileValidatorTest.php
Namespace
Drupal\Tests\package_manager\KernelFile
-
core/
modules/ package_manager/ tests/ src/ Kernel/ LockFileValidatorTest.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\package_manager\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\ComposerInspector;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreRequireEvent;
use Drupal\package_manager\Exception\StageException;
use Drupal\package_manager\InstalledPackagesList;
use Drupal\package_manager\PathLocator;
use Drupal\package_manager\Validator\LockFileValidator;
use Drupal\package_manager\ValidationResult;
use Drupal\package_manager_bypass\NoOpStager;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\package_manager\Validator\LockFileValidator
* @group package_manager
* @internal
*/
class LockFileValidatorTest extends PackageManagerKernelTestBase {
/**
* The path of the active directory in the test project.
*
* @var string
*/
private $activeDir;
/**
* {@inheritdoc}
*/
protected function setUp() : void {
parent::setUp();
$this->activeDir = $this->container
->get(PathLocator::class)
->getProjectRoot();
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) : void {
parent::register($container);
// Temporarily mock the Composer inspector to prevent it from complaining
// over the lack of a lock file if it's invoked by other validators.
$inspector = $this->prophesize(ComposerInspector::class);
$arguments = Argument::cetera();
$inspector->getConfig('allow-plugins', $arguments)
->willReturn('[]');
$inspector->getConfig('secure-http', $arguments)
->willReturn('true');
$inspector->getConfig('disable-tls', $arguments)
->willReturn('false');
$inspector->getConfig('extra', $arguments)
->willReturn('{}');
$inspector->getConfig('minimum-stability', $arguments)
->willReturn('stable');
$inspector->getInstalledPackagesList($arguments)
->willReturn(new InstalledPackagesList());
$inspector->getAllowPluginsConfig($arguments)
->willReturn([]);
$inspector->validate($arguments);
$inspector->getRootPackageInfo($arguments)
->willReturn([]);
$container->set(ComposerInspector::class, $inspector->reveal());
}
/**
* Tests that if no active lock file exists, a stage cannot be created.
*
* @covers ::storeHash
*/
public function testCreateWithNoLock() : void {
unlink($this->activeDir . '/composer.lock');
$project_root = $this->container
->get(PathLocator::class)
->getProjectRoot();
$lock_file_path = $project_root . DIRECTORY_SEPARATOR . 'composer.lock';
$no_lock = ValidationResult::createError([
t('The active lock file (@file) does not exist.', [
'@file' => $lock_file_path,
]),
]);
$stage = $this->assertResults([
$no_lock,
], PreCreateEvent::class);
// The stage was not created successfully, so the status check should be
// clear.
$this->assertStatusCheckResults([], $stage);
}
/**
* Tests that if an active lock file exists, a stage can be created.
*
* @covers ::storeHash
* @covers ::deleteHash
*/
public function testCreateWithLock() : void {
$this->assertResults([]);
// Change the lock file to ensure the stored hash of the previous version
// has been deleted.
file_put_contents($this->activeDir . '/composer.lock', '{"changed": true}');
$this->assertResults([]);
}
/**
* Tests validation when the lock file has changed.
*
* @dataProvider providerValidateStageEvents
*/
public function testLockFileChanged(string $event_class) : void {
// 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->addEventTestListener(function () {
$lock = json_decode(file_get_contents($this->activeDir . '/composer.lock'), TRUE, flags: JSON_THROW_ON_ERROR);
$lock['extra']['key'] = 'value';
file_put_contents($this->activeDir . '/composer.lock', json_encode($lock, JSON_THROW_ON_ERROR));
}, $event_class);
$result = ValidationResult::createError([
t('Unexpected changes were detected in the active lock file (@file), which indicates that other Composer operations were performed since this Package Manager operation started. This can put the code base into an unreliable state and therefore is not allowed.', [
'@file' => $this->activeDir . '/composer.lock',
]),
], t('Problem detected in lock file during stage operations.'));
$stage = $this->assertResults([
$result,
], $event_class);
// A status check should agree that there is an error here.
$this->assertStatusCheckResults([
$result,
], $stage);
}
/**
* Tests validation when the lock file is deleted.
*
* @dataProvider providerValidateStageEvents
*/
public function testLockFileDeleted(string $event_class) : void {
// 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->addEventTestListener(function () {
unlink($this->activeDir . '/composer.lock');
}, $event_class);
$result = ValidationResult::createError([
t('The active lock file (@file) does not exist.', [
'@file' => $this->activeDir . '/composer.lock',
]),
], t('Problem detected in lock file during stage operations.'));
$stage = $this->assertResults([
$result,
], $event_class);
// A status check should agree that there is an error here.
$this->assertStatusCheckResults([
$result,
], $stage);
}
/**
* Tests exception when a stored hash of the active lock file is unavailable.
*
* @dataProvider providerValidateStageEvents
*/
public function testNoStoredHash(string $event_class) : void {
$reflector = new \ReflectionClassConstant(LockFileValidator::class, 'KEY');
$key = $reflector->getValue();
// Add a listener with an extremely high priority to the same event that
// should throw an exception. Because the validator uses the default
// priority of 0, this listener deletes stored hash before the validator
// runs.
$this->addEventTestListener(function () use ($key) {
$this->container
->get('keyvalue')
->get('package_manager')
->delete($key);
}, $event_class);
$stage = $this->createStage();
$stage->create();
try {
$stage->require([
'drupal/core:9.8.1',
]);
$stage->apply();
} catch (StageException $e) {
$this->assertSame(\LogicException::class, $e->getPrevious()::class);
$this->assertSame('Stored hash key deleted.', $e->getMessage());
}
}
/**
* Tests validation when the staged and active lock files are identical.
*/
public function testApplyWithNoChange() : void {
// Leave the staged lock file alone.
NoOpStager::setLockFileShouldChange(FALSE);
$result = ValidationResult::createError([
t('There appear to be no pending Composer operations because the active lock file (<PROJECT_ROOT>/composer.lock) and the staged lock file (<STAGE_DIR>/composer.lock) are identical.'),
], t('Problem detected in lock file during stage operations.'));
$stage = $this->assertResults([
$result,
], PreApplyEvent::class);
// A status check shouldn't produce raise any errors, because it's only
// during pre-apply that we care if there are any pending Composer
// operations.
$this->assertStatusCheckResults([], $stage);
}
/**
* Tests StatusCheckEvent when the stage is available.
*/
public function testStatusCheckAvailableStage() : void {
$this->assertStatusCheckResults([]);
}
/**
* Data provider for test methods that validate the stage directory.
*
* @return string[][]
* The test cases.
*/
public static function providerValidateStageEvents() : array {
return [
'pre-require' => [
PreRequireEvent::class,
],
'pre-apply' => [
PreApplyEvent::class,
],
];
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
LockFileValidatorTest | @coversDefaultClass \Drupal\package_manager\Validator\LockFileValidator @group package_manager @internal |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.