Skip to content
Snippets Groups Projects
Commit eb064d8c authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Adam G-H
Browse files

Issue #3273017 by kunal.sachdev, tedbow: Create a validator to confirm that...

Issue #3273017 by kunal.sachdev, tedbow: Create a validator to confirm that extensions being updated were installed via Composer
parent 19760c80
No related branches found
No related tags found
No related merge requests found
Showing
with 642 additions and 14 deletions
...@@ -11,6 +11,12 @@ services: ...@@ -11,6 +11,12 @@ services:
- '@event_dispatcher' - '@event_dispatcher'
- '@tempstore.shared' - '@tempstore.shared'
- '@datetime.time' - '@datetime.time'
automatic_updates_extensions.validator.packages_installed_with_composer:
class: Drupal\automatic_updates_extensions\Validator\PackagesInstalledWithComposerValidator
arguments:
- '@string_translation'
tags:
- { name: event_subscriber }
automatic_updates_extensions.validator.target_release: automatic_updates_extensions.validator.target_release:
class: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator class: Drupal\automatic_updates_extensions\Validator\UpdateReleaseValidator
tags: tags:
......
<?php
namespace Drupal\automatic_updates_extensions\Validator;
use Composer\Package\PackageInterface;
use Drupal\automatic_updates_extensions\ExtensionUpdater;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Validates packages are installed via Composer.
*/
class PackagesInstalledWithComposerValidator implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Constructs a InstalledPackagesValidator object.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* The translation service.
*/
public function __construct(TranslationInterface $translation) {
$this->setStringTranslation($translation);
}
/**
* Validates that packages are installed with composer or not.
*
* @param \Drupal\package_manager\Event\PreOperationStageEvent $event
* The event object.
*/
public function checkPackagesInstalledWithComposer(PreOperationStageEvent $event): void {
$stage = $event->getStage();
if ($stage instanceof ExtensionUpdater) {
$active_composer = $stage->getActiveComposer();
$installed_packages = $active_composer->getInstalledPackages();
$missing_packages = [];
if ($event instanceof PreCreateEvent) {
$package_versions = $stage->getPackageVersions();
foreach (['production', 'dev'] as $package_type) {
$missing_packages = array_merge($missing_packages, array_diff_key($package_versions[$package_type], $installed_packages));
}
}
else {
$missing_packages = $stage->getStageComposer()
->getPackagesNotIn($active_composer);
// For new dependency added in the stage will are only concerned with
// ones that are Drupal projects that have Update XML from Drupal.org
// Since the Update module does allow use to check any of these projects
// if they don't exist in the active code base. Other types of projects
// even if they are in the 'drupal/' namespace they would not have
// Update XML on Drupal.org so it doesn't matter if they are in the
// active codebase or not.
$types = [
'drupal-module',
'drupal-theme',
'drupal-profile',
];
$filter = function (PackageInterface $package) use ($types): bool {
return in_array($package->getType(), $types);
};
$missing_packages = array_filter($missing_packages, $filter);
// Saving only the packages whose name starts with drupal/.
$missing_packages = array_filter($missing_packages, function (string $key) {
return strpos($key, 'drupal/') === 0;
}, ARRAY_FILTER_USE_KEY);
}
if ($missing_packages) {
$missing_projects = [];
foreach ($missing_packages as $package => $version) {
// Removing drupal/ from package name for better user presentation.
$project = str_replace('drupal/', '', $package);
$missing_projects[] = $project;
}
if ($missing_projects) {
$event->addError($missing_projects, $this->t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'));
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PreCreateEvent::class => 'checkPackagesInstalledWithComposer',
PreApplyEvent::class => 'checkPackagesInstalledWithComposer',
];
}
}
{
"name": "drupal/new_module",
"type": "drupal-module",
"version": "1.0.0"
}
name: 'New module'
type: module
core_version_requirement: ^9
{
"name": "drupal/new_module",
"type": "drupal-module",
"version": "1.1.0"
}
name: 'New module'
type: module
core_version_requirement: ^9
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/core",
"version": "9.8.0"
},
{
"name": "drupal/my_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/my_dev_module",
"version": "9.8.1",
"type": "drupal-module"
},
{
"name": "drupal/existing_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/existing_theme",
"version": "9.8.0",
"type": "drupal-theme"
},
{
"name": "drupal/existing_profile",
"version": "9.8.0",
"type": "drupal-profile"
}
],
"dev": true,
"dev-package-names": [
"drupal/my_dev_module"
]
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/core",
"version": "9.8.0"
},
{
"name": "drupal/my_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/my_dev_module",
"version": "9.8.1",
"type": "drupal-module"
},
{
"name": "drupal/new_module",
"version": "9.8.0",
"type": "drupal-module"
}
],
"dev": true,
"dev-package-names": [
"drupal/my_dev_module"
]
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/core",
"version": "9.8.0"
},
{
"name": "drupal/my_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/my_dev_module",
"version": "9.8.1",
"type": "drupal-module"
},
{
"name": "drupal/new_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "not-drupal/new_module1",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/new_theme",
"version": "9.8.0",
"type": "drupal-theme"
},
{
"name": "drupal/new_profile",
"version": "9.8.0",
"type": "drupal-profile"
},
{
"name": "drupal/new_dependency",
"version": "9.8.0",
"type": "drupal-library"
}
],
"dev": true,
"dev-package-names": [
"drupal/my_dev_module"
]
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/core",
"version": "9.8.0"
},
{
"name": "drupal/my_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/my_dev_module",
"version": "9.8.1",
"type": "drupal-module"
},
{
"name": "drupal/new_profile",
"version": "9.8.0",
"type": "drupal-profile"
}
],
"dev": true,
"dev-package-names": [
"drupal/my_dev_module"
]
}
{
"packages": [
{
"name": "drupal/core-recommended",
"version": "9.8.0",
"require": {
"drupal/core": "9.8.0"
}
},
{
"name": "drupal/core",
"version": "9.8.0"
},
{
"name": "drupal/my_module",
"version": "9.8.0",
"type": "drupal-module"
},
{
"name": "drupal/my_dev_module",
"version": "9.8.1",
"type": "drupal-module"
},
{
"name": "drupal/new_theme",
"version": "9.8.0",
"type": "drupal-theme"
}
],
"dev": true,
"dev-package-names": [
"drupal/my_dev_module"
]
}
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>New Module</title>
<short_name>new_module</short_name>
<dc:creator>Drupal</dc:creator>
<supported_branches>1.1.,1.0.</supported_branches>
<project_status>published</project_status>
<link>http://example.com/project/new_module</link>
<terms>
<term><name>Projects</name><value>New Module project</value></term>
</terms>
<releases>
<release>
<name>New Module 1.1.0</name>
<version>1.1.0</version>
<tag>1.1.0</tag>
<status>published</status>
<release_link>http://example.com/new_module-1-1-0-release</release_link>
<download_link>http://example.com/new_module-1-1-0.tar.gz</download_link>
<date>1584195300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
<release>
<name>New Module 1.0.0</name>
<version>1.0.0</version>
<tag>1.0.0</tag>
<status>published</status>
<release_link>http://example.com/new_module-1-0-0-release</release_link>
<download_link>http://example.com/new_module-1-0-0.tar.gz</download_link>
<date>1581603300</date>
<terms>
<term><name>Release type</name><value>New features</value></term>
<term><name>Release type</name><value>Bug fixes</value></term>
</terms>
</release>
</releases>
</project>
...@@ -4,6 +4,7 @@ namespace Drupal\Tests\automatic_updates_extensions\Build; ...@@ -4,6 +4,7 @@ namespace Drupal\Tests\automatic_updates_extensions\Build;
use Drupal\Tests\automatic_updates\Build\UpdateTestBase; use Drupal\Tests\automatic_updates\Build\UpdateTestBase;
use Drupal\Tests\automatic_updates_extensions\Traits\FormTestTrait; use Drupal\Tests\automatic_updates_extensions\Traits\FormTestTrait;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
/** /**
* Tests updating modules in a staging area. * Tests updating modules in a staging area.
...@@ -22,11 +23,15 @@ class ModuleUpdateTest extends UpdateTestBase { ...@@ -22,11 +23,15 @@ class ModuleUpdateTest extends UpdateTestBase {
$this->setReleaseMetadata([ $this->setReleaseMetadata([
'drupal' => __DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.1-security.xml', 'drupal' => __DIR__ . '/../../../../tests/fixtures/release-history/drupal.9.8.1-security.xml',
'alpha' => __DIR__ . '/../../fixtures/release-history/alpha.1.1.0.xml', 'alpha' => __DIR__ . '/../../fixtures/release-history/alpha.1.1.0.xml',
'new_module' => __DIR__ . '/../../fixtures/release-history/new_module.1.1.0.xml',
]); ]);
// Set 'version' and 'project' for the 'alpha' module to enable the Update // Set 'version' and 'project' for the 'alpha' and 'new_module' module to
// to determine the update status. // enable the Update to determine the update status.
$system_info = ['alpha' => ['version' => '1.0.0', 'project' => 'alpha']]; $system_info = [
'alpha' => ['version' => '1.0.0', 'project' => 'alpha'],
'new_module' => ['version' => '1.0.0', 'project' => 'new_module'],
];
$system_info = var_export($system_info, TRUE); $system_info = var_export($system_info, TRUE);
$code = <<<END $code = <<<END
\$config['update_test.settings']['system_info'] = $system_info; \$config['update_test.settings']['system_info'] = $system_info;
...@@ -36,8 +41,13 @@ END; ...@@ -36,8 +41,13 @@ END;
$this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.0.0'); $this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.0.0');
$this->runComposer('COMPOSER_MIRROR_PATH_REPOS=1 composer require drupal/alpha --update-with-all-dependencies', 'project'); $this->runComposer('COMPOSER_MIRROR_PATH_REPOS=1 composer require drupal/alpha --update-with-all-dependencies', 'project');
$this->assertModuleVersion('alpha', '1.0.0'); $this->assertModuleVersion('alpha', '1.0.0');
$fs = new SymfonyFilesystem();
$this->installModules(['automatic_updates_extensions_test_api', 'alpha']); $fs->mirror(__DIR__ . '/../../fixtures/new_module', $this->getWorkspaceDirectory() . '/project/web/modules');
$this->installModules([
'automatic_updates_extensions_test_api',
'alpha',
'new_module',
]);
// Change both modules' upstream version. // Change both modules' upstream version.
$this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.1.0'); $this->addRepository('alpha', __DIR__ . '/../../../../package_manager/tests/fixtures/alpha/1.1.0');
...@@ -48,7 +58,22 @@ END; ...@@ -48,7 +58,22 @@ END;
*/ */
public function testApi(): void { public function testApi(): void {
$this->createTestProject('RecommendedProject'); $this->createTestProject('RecommendedProject');
// Use the API endpoint to create a stage and update the 'new_module' module
// to 1.1.0.
// @see \Drupal\automatic_updates_extensions_test_api\ApiController::run()
// There will be error in updating as this module is not installed
// via composer @see \Drupal\Tests\automatic_updates_extensions\Kernel\Validator\PackagesInstalledWithComposerValidatorTest.
$query = http_build_query([
'projects' => [
'new_module' => '1.1.0',
],
]);
$this->visit("/automatic-updates-extensions-test-api?$query");
$mink = $this->getMink();
$mink->assertSession()->statusCodeEquals(500);
$page_text = $mink->getSession()->getPage()->getText();
$this->assertStringContainsString('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:', $page_text);
$this->assertStringContainsString('new_module', $page_text);
// Use the API endpoint to create a stage and update the 'alpha' module to // Use the API endpoint to create a stage and update the 'alpha' module to
// 1.1.0. We ask the API to return the contents of the module's // 1.1.0. We ask the API to return the contents of the module's
// composer.json file, so we can assert that they were updated to the // composer.json file, so we can assert that they were updated to the
...@@ -86,7 +111,17 @@ END; ...@@ -86,7 +111,17 @@ END;
$this->visit('/admin/reports/updates'); $this->visit('/admin/reports/updates');
$page->clickLink('Update Extensions'); $page->clickLink('Update Extensions');
$this->assertUpdateTableRow($assert_session, 'Alpha', '1.0.0', '1.1.0'); $this->assertUpdateTableRow($assert_session, 'Alpha', '1.0.0', '1.1.0', 2);
$this->assertUpdateTableRow($assert_session, 'New module', '1.0.0', '1.1.0', 1);
$page->checkField('projects[new_module]');
$page->pressButton('Update');
$this->waitForBatchJob();
$page_text = $page->getText();
// There will be error in updating 'new_module' as it is not installed via
// composer @see \Drupal\Tests\automatic_updates_extensions\Kernel\Validator\PackagesInstalledWithComposerValidatorTest.
$this->assertStringContainsString('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:', $page_text);
$this->assertStringContainsString('new_module', $page_text);
$page->clickLink('error page');
$page->checkField('projects[alpha]'); $page->checkField('projects[alpha]');
$page->pressButton('Update'); $page->pressButton('Update');
$this->waitForBatchJob(); $this->waitForBatchJob();
......
...@@ -62,6 +62,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -62,6 +62,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function setUp(): void { protected function setUp(): void {
// In this test class, some modules are added and this validator will
// complain because these are not installed via composer. This validator
// already has test coverage.
// @see \Drupal\Tests\automatic_updates_extensions\Build\ModuleUpdateTest
$this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer';
parent::setUp(); parent::setUp();
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/semver_test.1.1.xml'); $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/semver_test.1.1.xml');
$user = $this->createUser([ $user = $this->createUser([
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
namespace Drupal\Tests\automatic_updates_extensions\Kernel; namespace Drupal\Tests\automatic_updates_extensions\Kernel;
use Drupal\automatic_updates_extensions\ExtensionUpdater; use Drupal\automatic_updates_extensions\ExtensionUpdater;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\Exception\StageValidationException; use Drupal\package_manager\Exception\StageValidationException;
use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase; use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
use Drupal\Tests\package_manager\Kernel\TestStageTrait; use Drupal\Tests\package_manager\Kernel\TestStageTrait;
...@@ -26,6 +28,43 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates ...@@ -26,6 +28,43 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
'automatic_updates_test_release_history', 'automatic_updates_test_release_history',
]; ];
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
// Use the test-only implementations of the regular and cron updaters.
$overrides = [
'automatic_updates_extensions.updater' => TestExtensionUpdater::class,
];
foreach ($overrides as $service_id => $class) {
if ($container->hasDefinition($service_id)) {
$container->getDefinition($service_id)->setClass($class);
}
}
}
/**
* Creates a stage object for testing purposes.
*
* @return \Drupal\automatic_updates_extensions\ExtensionUpdater
* A stage object, with test-only modifications.
*/
protected function createUpdater(): ExtensionUpdater {
return new TestExtensionUpdater(
$this->container->get('config.factory'),
$this->container->get('package_manager.path_locator'),
$this->container->get('package_manager.beginner'),
$this->container->get('package_manager.stager'),
$this->container->get('package_manager.committer'),
$this->container->get('file_system'),
$this->container->get('event_dispatcher'),
$this->container->get('tempstore.shared'),
$this->container->get('datetime.time')
);
}
/** /**
* The client. * The client.
* *
...@@ -44,7 +83,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates ...@@ -44,7 +83,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
* (optional) The class of the event which should return the results. Must * (optional) The class of the event which should return the results. Must
* be passed if $expected_results is not empty. * be passed if $expected_results is not empty.
*/ */
protected function assertUpdaterResults(array $project_versions, array $expected_results, string $event_class = NULL): void { protected function assertUpdateResults(array $project_versions, array $expected_results, string $event_class = NULL): void {
$updater = $this->createExtensionUpdater(); $updater = $this->createExtensionUpdater();
try { try {
...@@ -117,7 +156,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates ...@@ -117,7 +156,7 @@ abstract class AutomaticUpdatesExtensionsKernelTestBase extends AutomaticUpdates
} }
/** /**
* Defines a updater specifically for testing purposes. * A test-only version of the regular extension updater to override internals.
*/ */
class TestExtensionUpdater extends ExtensionUpdater { class TestExtensionUpdater extends ExtensionUpdater {
......
<?php
namespace Drupal\Tests\automatic_updates_extensions\Kernel\Validator;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\ValidationResult;
use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsKernelTestBase;
/**
* Validates the installed packages via composer after an update.
*
* @coversDefaultClass \Drupal\automatic_updates_extensions\Validator\PackagesInstalledWithComposerValidator
*
* @group automatic_updates_extensions
*/
class PackagesInstalledWithComposerValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase {
/**
* The active directory in the virtual file system.
*
* @var string
*/
private $activeDir;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
// In this test, we don't focus on validating that the updated projects are
// secure and supported. Therefore, we need to disable the update release
// validator that validates updated projects are secure and supported.
$this->disableValidators[] = 'automatic_updates_extensions.validator.target_release';
parent::setUp();
$this->createTestProject();
$this->activeDir = $this->container->get('package_manager.path_locator')
->getProjectRoot();
}
/**
* Data provider for testPreCreateException().
*
* @return array
* Test cases for testPreCreateException().
*/
public function providerPreCreateException(): array {
return [
'module not installed via composer' => [
[
'new_module' => '9.8.0',
],
[ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
'theme not installed via composer' => [
[
'new_theme' => '9.8.0',
],
[ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
'profile not installed via composer' => [
[
'new_profile' => '9.8.0',
],
[ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
'module_theme_profile_dependency_not_installed_via_composer' => [
[
'new_module' => '9.8.0',
'new_theme' => '9.8.0',
'new_profile' => '9.8.0',
'new_dependency' => '9.8.0',
],
[
ValidationResult::createError(
['new_module', 'new_theme', 'new_profile', 'new_dependency'],
t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')),
],
],
'module_theme_profile_installed_via_composer' => [
[
'existing_module' => '9.8.1',
'existing_theme' => '9.8.1',
'existing_profile' => '9.8.1',
],
[],
],
'existing module installed and new module not installed via composer' => [
[
'existing_module' => '9.8.1',
'new_module' => '9.8.0',
],
[ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
];
}
/**
* Tests the packages installed with composer during pre-create.
*
* @param array $projects
* The projects to install.
* @param array $expected_results
* The expected validation results.
*
* @dataProvider providerPreCreateException
*/
public function testPreCreateException(array $projects, array $expected_results): void {
// Path of `active.installed.json` file. It will be used as the virtual
// project's active `vendor/composer/installed.json` file.
$active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json';
$this->assertFileIsReadable($active_installed);
copy($active_installed, "$this->activeDir/vendor/composer/installed.json");
$this->assertUpdateResults($projects, $expected_results, PreCreateEvent::class);
}
/**
* Data provider for testPreApplyException().
*
* @return array
* Test cases for testPreApplyException().
*/
public function providerPreApplyException(): array {
$fixtures_folder = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator';
return [
'module not installed via composer' => [
"$fixtures_folder/module_not_installed.staged.installed.json",
[ValidationResult::createError(['new_module'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
'theme not installed via composer' => [
"$fixtures_folder/theme_not_installed.staged.installed.json",
[ValidationResult::createError(['new_theme'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
'profile not installed via composer' => [
"$fixtures_folder/profile_not_installed.staged.installed.json",
[ValidationResult::createError(['new_profile'], t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:'))],
],
// Dependency drupal/new_dependency of type 'drupal-library' will not show
// up in the error because it is not one of the covered types
// ('drupal-module', 'drupal-theme' or 'drupal-profile'). Module
// new_module1 will also not show up as it's name doesn't start with
// 'drupal/'.
// @see \Drupal\automatic_updates_extensions\Validator\PackagesInstalledWithComposerValidator
'module_theme_profile_dependency_not_installed_via_composer' => [
"$fixtures_folder/module_theme_profile_dependency_not_installed.staged.installed.json",
[
ValidationResult::createError(
['new_module', 'new_theme', 'new_profile'],
t('Automatic Updates can only update projects that were installed via Composer. The following packages are not installed through composer:')),
],
],
];
}
/**
* Tests the packages installed with composer during pre-apply.
*
* @param string $staged_installed
* Path of `staged.installed.json` file. It will be used as the virtual
* project's staged `vendor/composer/installed.json` file.
* @param array $expected_results
* The expected validation results.
*
* @dataProvider providerPreApplyException
*/
public function testPreApplyException(string $staged_installed, array $expected_results): void {
// Path of `active.installed.json` file. It will be used as the virtual
// project's active `vendor/composer/installed.json` file.
$active_installed = __DIR__ . '/../../../fixtures/packages_installed_with_composer_validator/active.installed.json';
$this->assertFileIsReadable($active_installed);
$this->assertFileIsReadable($staged_installed);
copy($active_installed, "$this->activeDir/vendor/composer/installed.json");
$listener = function (PreApplyEvent $event) use ($staged_installed): void {
$stage_dir = $event->getStage()->getStageDirectory();
copy($staged_installed, $stage_dir . "/vendor/composer/installed.json");
};
$this->container->get('event_dispatcher')->addListener(PreApplyEvent::class, $listener, 1000);
$this->assertUpdateResults([], $expected_results, PreApplyEvent::class);
}
}
...@@ -14,6 +14,14 @@ use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsK ...@@ -14,6 +14,14 @@ use Drupal\Tests\automatic_updates_extensions\Kernel\AutomaticUpdatesExtensionsK
*/ */
class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase { class UpdateReleaseValidatorTest extends AutomaticUpdatesExtensionsKernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->disableValidators[] = 'automatic_updates_extensions.validator.packages_installed_with_composer';
parent::setUp();
}
/** /**
* Tests updating to a release. * Tests updating to a release.
* *
......
...@@ -20,12 +20,14 @@ trait FormTestTrait { ...@@ -20,12 +20,14 @@ trait FormTestTrait {
* The expected installed version. * The expected installed version.
* @param string $expected_target_version * @param string $expected_target_version
* The expected target version. * The expected target version.
* @param int $row
* The row number.
*/ */
private function assertUpdateTableRow(WebAssert $assert, string $expected_project_title, string $expected_installed_version, string $expected_target_version): void { private function assertUpdateTableRow(WebAssert $assert, string $expected_project_title, string $expected_installed_version, string $expected_target_version, int $row = 1): void {
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(2)', $expected_project_title); $row_selector = ".update-recommended tr:nth-of-type($row)";
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(3)', $expected_installed_version); $assert->elementTextContains('css', $row_selector . ' td:nth-of-type(2)', $expected_project_title);
$assert->elementTextContains('css', '.update-recommended td:nth-of-type(4)', $expected_target_version); $assert->elementTextContains('css', $row_selector . ' td:nth-of-type(3)', $expected_installed_version);
$assert->elementsCount('css', '.update-recommended tbody tr', 1); $assert->elementTextContains('css', $row_selector . ' td:nth-of-type(4)', $expected_target_version);
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment