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

Issue #3236299 by kunal.sachdev, phenaproxima, Suresh Prabhu Parkala, tedbow:...

Issue #3236299 by kunal.sachdev, phenaproxima, Suresh Prabhu Parkala, tedbow: User should not be allowed to do update if it is from minor version to another minor version
parent 26efea98
No related branches found
No related tags found
No related merge requests found
Showing
with 231 additions and 72 deletions
services:
automatic_updates.readiness_validation_manager:
class: Drupal\automatic_updates\Validation\ReadinessValidationManager
arguments: ['@keyvalue.expirable', '@datetime.time','@event_dispatcher', 24]
arguments: ['@keyvalue.expirable', '@datetime.time', '@automatic_updates.updater', '@event_dispatcher', 24]
automatic_updates.updater:
class: Drupal\automatic_updates\Updater
arguments: ['@state', '@string_translation','@package_manager.beginner', '@package_manager.stager', '@package_manager.cleaner', '@package_manager.committer', '@event_dispatcher', '@automatic_updates.path_locator']
......
<?php
namespace Drupal\automatic_updates\Event;
/**
* Common functionality for events which can carry desired package versions.
*/
trait PackagesAwareTrait {
/**
* The desired package versions to update to, keyed by package name.
*
* @var string[]
*/
protected $packageVersions;
/**
* Constructs a PreStartEvent.
*
* @param string[] $package_versions
* (optional) The desired package versions to update to, keyed by package
* name.
*/
public function __construct(array $package_versions = []) {
$this->packageVersions = $package_versions;
}
/**
* Returns the desired package versions to update to.
*
* @return string[]
* The desired package versions to update to, keyed by package name.
*/
public function getPackageVersions(): array {
return $this->packageVersions;
}
}
......@@ -8,33 +8,6 @@ namespace Drupal\automatic_updates\Event;
class PreStartEvent extends UpdateEvent {
use ExcludedPathsTrait;
/**
* The desired package versions to update to, keyed by package name.
*
* @var string[]
*/
protected $packageVersions;
/**
* Constructs a PreStartEvent.
*
* @param string[] $package_versions
* (optional) The desired package versions to update to, keyed by package
* name.
*/
public function __construct(array $package_versions = []) {
$this->packageVersions = $package_versions;
}
/**
* Returns the desired package versions to update to.
*
* @return string[]
* The desired package versions to update to, keyed by package name.
*/
public function getPackageVersions(): array {
return $this->packageVersions;
}
use PackagesAwareTrait;
}
<?php
namespace Drupal\automatic_updates\Event;
/**
* Event fired when checking if the site could perform an update.
*/
class ReadinessCheckEvent extends UpdateEvent {
use PackagesAwareTrait;
}
......@@ -3,7 +3,9 @@
namespace Drupal\automatic_updates\Validation;
use Drupal\automatic_updates\AutomaticUpdatesEvents;
use Drupal\automatic_updates\Event\UpdateEvent;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\Updater;
use Drupal\automatic_updates\UpdateRecommender;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......@@ -41,6 +43,13 @@ class ReadinessValidationManager {
*/
protected $resultsTimeToLive;
/**
* The updater service.
*
* @var \Drupal\automatic_updates\Updater
*/
protected $updater;
/**
* Constructs a ReadinessValidationManager.
*
......@@ -48,14 +57,17 @@ class ReadinessValidationManager {
* The key/value expirable factory.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Drupal\automatic_updates\Updater $updater
* The updater service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* The event dispatcher service.
* @param int $results_time_to_live
* The number of hours to store results.
*/
public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, TimeInterface $time, EventDispatcherInterface $dispatcher, int $results_time_to_live) {
public function __construct(KeyValueExpirableFactoryInterface $key_value_expirable_factory, TimeInterface $time, Updater $updater, EventDispatcherInterface $dispatcher, int $results_time_to_live) {
$this->keyValueExpirable = $key_value_expirable_factory->get('automatic_updates');
$this->time = $time;
$this->updater = $updater;
$this->eventDispatcher = $dispatcher;
$this->resultsTimeToLive = $results_time_to_live;
}
......@@ -66,7 +78,15 @@ class ReadinessValidationManager {
* @return $this
*/
public function run(): self {
$event = new UpdateEvent();
$recommender = new UpdateRecommender();
$release = $recommender->getRecommendedRelease(TRUE);
if ($release) {
$package_versions = [$this->updater->getCorePackageName() => $release->getVersion()];
}
else {
$package_versions = [];
}
$event = new ReadinessCheckEvent($package_versions);
$this->eventDispatcher->dispatch($event, AutomaticUpdatesEvents::READINESS_CHECK);
$results = $event->getResults();
$this->keyValueExpirable->setWithExpire(
......
......@@ -3,7 +3,8 @@
namespace Drupal\automatic_updates\Validator;
use Drupal\automatic_updates\AutomaticUpdatesEvents;
use Drupal\automatic_updates\Event\PreStartEvent;
use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates\Event\UpdateEvent;
use Drupal\automatic_updates\Updater;
use Drupal\automatic_updates\Validation\ValidationResult;
use Drupal\Core\Extension\ExtensionVersion;
......@@ -52,10 +53,10 @@ class UpdateVersionValidator implements EventSubscriberInterface {
/**
* Validates that core is not being updated to another minor or major version.
*
* @param \Drupal\automatic_updates\Event\PreStartEvent $event
* @param \Drupal\automatic_updates\Event\PreStartEvent|\Drupal\automatic_updates\Event\ReadinessCheckEvent $event
* The event object.
*/
public function checkUpdateVersion(PreStartEvent $event): void {
public function checkUpdateVersion(UpdateEvent $event): void {
$from_version = ExtensionVersion::createFromVersionString($this->getCoreVersion());
$core_package_name = $this->updater->getCorePackageName();
$to_version = ExtensionVersion::createFromVersionString($event->getPackageVersions()[$core_package_name]);
......@@ -74,12 +75,27 @@ class UpdateVersionValidator implements EventSubscriberInterface {
}
}
/**
* Validates readiness check event.
*
* @param \Drupal\automatic_updates\Event\ReadinessCheckEvent $event
* The readiness check event object.
*/
public function checkReadinessUpdateVersion(ReadinessCheckEvent $event): void {
// During readiness checks, we might not know the desired package versions,
// which means there's nothing to validate.
if ($event->getPackageVersions()) {
$this->checkUpdateVersion($event);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
AutomaticUpdatesEvents::PRE_START => 'checkUpdateVersion',
AutomaticUpdatesEvents::READINESS_CHECK => 'checkReadinessUpdateVersion',
];
}
......
<?xml version="1.0" encoding="utf-8"?>
<project xmlns:dc="http://purl.org/dc/elements/1.1/">
<title>Drupal</title>
<short_name>drupal</short_name>
<dc:creator>Drupal</dc:creator>
<supported_branches>9.8.</supported_branches>
<project_status>published</project_status>
<link>http://example.com/project/drupal</link>
<terms>
<term><name>Projects</name><value>Drupal project</value></term>
</terms>
<releases>
<release>
<name>Drupal 9.8.1</name>
<version>9.8.1</version>
<status>published</status>
<release_link>http://example.com/drupal-9-8-1-release</release_link>
<download_link>http://example.com/drupal-9-8-1.tar.gz</download_link>
<date>1250425521</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>Drupal 9.8.0</name>
<version>9.8.0</version>
<status>published</status>
<release_link>http://example.com/drupal-9-8-0-release</release_link>
<download_link>http://example.com/drupal-9-8-0.tar.gz</download_link>
<date>1250424521</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>
......@@ -26,7 +26,7 @@ class CoreUpdateTest extends UpdateTestBase {
// Install Drupal and ensure it's using the fake release metadata to fetch
// information about available updates.
$this->installQuickStart('minimal');
$this->setReleaseMetadata(['drupal' => '0.0']);
$this->setReleaseMetadata(['drupal' => '9.8.1-security']);
$this->formLogin($this->adminUsername, $this->adminPassword);
$this->installModules([
'automatic_updates',
......
......@@ -45,4 +45,15 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase {
->save();
}
/**
* Checks for available updates.
*
* Assumes that a user with appropriate permissions is logged in.
*/
protected function checkForUpdates(): void {
$this->drupalGet('/admin/reports/updates');
$this->getSession()->getPage()->clickLink('Check manually');
$this->checkForMetaRefresh();
}
}
......@@ -90,8 +90,12 @@ class FileSystemOperationsTest extends AutomaticUpdatesFunctionalTestBase {
// \Drupal\automatic_updates\Validator\UpdateVersionValidator, that need to
// fetch release metadata. We need to ensure that those HTTP request(s)
// succeed, so set them up to point to our fake release metadata.
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml');
$this->setCoreVersion('9.8.0');
$this->drupalLogin($this->rootUser);
$this->checkForUpdates();
$this->drupalLogout();
}
/**
......
......@@ -8,7 +8,6 @@ use Drupal\automatic_updates_test2\ReadinessChecker\TestChecker2;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\system\SystemManager;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\Traits\Core\CronRunTrait;
/**
......@@ -16,7 +15,7 @@ use Drupal\Tests\Traits\Core\CronRunTrait;
*
* @group automatic_updates
*/
class ReadinessValidationTest extends BrowserTestBase {
class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
use StringTranslationTrait;
use CronRunTrait;
......@@ -53,6 +52,9 @@ class ReadinessValidationTest extends BrowserTestBase {
*/
protected function setUp(): void {
parent::setUp();
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1.xml');
$this->setCoreVersion('9.8.1');
$this->reportViewerUser = $this->createUser([
'administer site configuration',
'access administration pages',
......@@ -81,6 +83,7 @@ class ReadinessValidationTest extends BrowserTestBase {
// If the site is ready for updates, the users will see the same output
// regardless of whether the user has permission to run updates.
$this->drupalLogin($this->reportViewerUser);
$this->checkForUpdates();
$this->drupalGet('admin/reports/status');
$this->assertReadinessReportMatches('Your site is ready for automatic updates.', 'checked', FALSE);
$this->drupalLogin($this->checkerRunnerUser);
......@@ -300,20 +303,22 @@ class ReadinessValidationTest extends BrowserTestBase {
$this->drupalGet('admin/reports/status');
$assert->pageTextNotContains('Update readiness checks');
$this->container->get('module_installer')->install(['automatic_updates']);
// We have to install the automatic_updates_test module because it provides
// the functionality to retrieve our fake release history metadata.
$this->container->get('module_installer')->install(['automatic_updates', 'automatic_updates_test']);
$this->drupalGet('admin/reports/status');
$this->assertReadinessReportMatches('Your site is ready for automatic updates. Run readiness checks now.', 'checked');
$expected_results = $this->testResults['checker_1']['1 error'];
TestChecker1::setTestResult($expected_results);
$this->container->get('module_installer')->install(['automatic_updates_test']);
TestChecker2::setTestResult($expected_results);
$this->container->get('module_installer')->install(['automatic_updates_test2']);
$this->drupalGet('admin/structure');
$assert->pageTextContainsOnce($expected_results[0]->getMessages()[0]);
// Confirm that installing a module that does not provide a new checker does
// not run the checkers on install.
$unexpected_results = $this->testResults['checker_1']['2 errors 2 warnings'];
TestChecker1::setTestResult($unexpected_results);
TestChecker2::setTestResult($unexpected_results);
$this->container->get('module_installer')->install(['help']);
// Check for message on 'admin/structure' instead of the status report
// because checkers will be run if needed on the status report.
......
......@@ -37,7 +37,10 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
*/
protected function setUp(): void {
parent::setUp();
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml');
$this->drupalLogin($this->rootUser);
$this->checkForUpdates();
}
/**
......@@ -48,8 +51,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
*/
public function testFormNotDisplayedIfAlreadyCurrent(): void {
$this->setCoreVersion('9.8.1');
$this->drupalLogin($this->rootUser);
$this->checkForUpdates();
$this->drupalGet('/admin/modules/automatic-update');
......@@ -66,7 +68,6 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->drupalPlaceBlock('local_tasks_block', ['primary' => TRUE]);
$assert_session = $this->assertSession();
$this->setCoreVersion('9.8.0');
$this->drupalLogin($this->rootUser);
$this->checkForUpdates();
// Navigate to the automatic updates form.
......@@ -85,6 +86,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->assertSame('9.8.1 (Release notes)', $cells[2]->getText());
$release_notes = $assert_session->elementExists('named', ['link', 'Release notes'], $cells[2]);
$this->assertSame('Release notes for Drupal', $release_notes->getAttribute('title'));
$assert_session->buttonExists('Update');
}
/**
......@@ -100,7 +102,6 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$error = ValidationResult::createError([$message]);
TestChecker1::setTestResult([$error]);
$this->drupalLogin($this->rootUser);
$this->drupalGet('/admin/reports/status');
$page->clickLink('Run readiness checks');
$assert_session->pageTextContainsOnce((string) $message);
......@@ -170,6 +171,19 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextContains('Ready to update');
}
/**
* Tests that updating to a different minor version isn't supported.
*/
public function testMinorVersionUpdateNotSupported(): void {
$this->setCoreVersion('9.7.1');
$this->drupalGet('/admin/modules/automatic-update');
$assert_session = $this->assertSession();
$assert_session->pageTextContainsOnce('Updating from one minor version to another is not supported.');
$assert_session->buttonNotExists('Update');
}
/**
* Deletes a staged, failed update.
*/
......@@ -180,15 +194,4 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$session->reload();
}
/**
* Checks for available updates.
*
* Assumes that a user with appropriate permissions is logged in.
*/
private function checkForUpdates(): void {
$this->drupalGet('/admin/reports/updates');
$this->getSession()->getPage()->clickLink('Check manually');
$this->checkForMetaRefresh();
}
}
......@@ -2,6 +2,7 @@
namespace Drupal\Tests\automatic_updates\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\KernelTests\KernelTestBase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
......@@ -19,6 +20,18 @@ abstract class AutomaticUpdatesKernelTestBase extends KernelTestBase {
*/
protected static $modules = ['update', 'update_test'];
/**
* The mocked HTTP client that returns metadata about available updates.
*
* We need to preserve this as a class property so that we can re-inject it
* into the container when a rebuild is triggered by module installation.
*
* @var \GuzzleHttp\Client
*
* @see ::register()
*/
private $client;
/**
* Sets the current (running) version of core, as known to the Update module.
*
......@@ -31,6 +44,19 @@ abstract class AutomaticUpdatesKernelTestBase extends KernelTestBase {
->save();
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
// If we previously set up a mock HTTP client in ::setReleaseMetadata(),
// re-inject it into the container.
if ($this->client) {
$container->set('http_client', $this->client);
}
}
/**
* Sets the release metadata file to use when fetching available updates.
*
......@@ -41,10 +67,10 @@ abstract class AutomaticUpdatesKernelTestBase extends KernelTestBase {
$metadata = Utils::tryFopen($file, 'r');
$response = new Response(200, [], Utils::streamFor($metadata));
$handler = new MockHandler([$response]);
$client = new Client([
$this->client = new Client([
'handler' => HandlerStack::create($handler),
]);
$this->container->set('http_client', $client);
$this->container->set('http_client', $this->client);
}
}
......@@ -4,7 +4,7 @@ namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
use Drupal\automatic_updates\Validation\ValidationResult;
use Drupal\automatic_updates\Validator\ComposerExecutableValidator;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use PhpTuf\ComposerStager\Exception\IOException;
use PhpTuf\ComposerStager\Infrastructure\Process\ExecutableFinderInterface;
......@@ -15,7 +15,7 @@ use Prophecy\Argument;
*
* @group automatic_updates
*/
class ComposerExecutableValidatorTest extends KernelTestBase {
class ComposerExecutableValidatorTest extends AutomaticUpdatesKernelTestBase {
use ValidationTestTrait;
......@@ -25,9 +25,18 @@ class ComposerExecutableValidatorTest extends KernelTestBase {
protected static $modules = [
'automatic_updates',
'package_manager',
'update',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig('update');
$this->setCoreVersion('9.8.0');
$this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1.xml');
}
/**
* Tests that an error is raised if the Composer executable isn't found.
*/
......
......@@ -4,8 +4,8 @@ namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
use Drupal\automatic_updates_test\ReadinessChecker\TestChecker1;
use Drupal\automatic_updates_test2\ReadinessChecker\TestChecker2;
use Drupal\KernelTests\KernelTestBase;
use Drupal\system\SystemManager;
use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
/**
......@@ -13,7 +13,7 @@ use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
*
* @group automatic_updates
*/
class ReadinessValidationManagerTest extends KernelTestBase {
class ReadinessValidationManagerTest extends AutomaticUpdatesKernelTestBase {
use ValidationTestTrait;
......@@ -34,15 +34,19 @@ class ReadinessValidationManagerTest extends KernelTestBase {
$this->installEntitySchema('user');
$this->installSchema('user', ['users_data']);
$this->createTestValidationResults();
$this->installConfig('update');
$this->setCoreVersion('9.8.0');
$this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1.xml');
}
/**
* @covers ::getResults
*/
public function testGetResults(): void {
$this->enableModules(['update', 'automatic_updates', 'automatic_updates_test2']);
$this->installConfig(['update', 'automatic_updates']);
$this->assertSame([], $this->getResultsFromManager(TRUE));
$this->enableModules(['automatic_updates', 'automatic_updates_test2']);
$this->assertCheckerResultsFromManager([], TRUE);
$expected_results = [
array_pop($this->testResults['checker_1']),
array_pop($this->testResults['checker_2']),
......
......@@ -25,6 +25,7 @@ class UpdateRecommenderTest extends AutomaticUpdatesKernelTestBase {
protected function setUp(): void {
parent::setUp();
$this->installConfig('update');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1.xml');
}
/**
......@@ -32,7 +33,6 @@ class UpdateRecommenderTest extends AutomaticUpdatesKernelTestBase {
*/
public function testUpdateAvailable(): void {
$this->setCoreVersion('9.8.0');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml');
$recommender = new UpdateRecommender();
$recommended_release = $recommender->getRecommendedRelease(TRUE);
......@@ -47,7 +47,6 @@ class UpdateRecommenderTest extends AutomaticUpdatesKernelTestBase {
*/
public function testNoUpdateAvailable(): void {
$this->setCoreVersion('9.8.1');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml');
$recommender = new UpdateRecommender();
$recommended_release = $recommender->getRecommendedRelease(TRUE);
......
......@@ -18,13 +18,14 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase {
'automatic_updates',
'automatic_updates_test',
'package_manager',
'package_manager_bypass',
];
/**
* Tests that correct versions are staged after calling ::begin().
*/
public function testCorrectVersionsStaged() {
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.0.0.xml');
$this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml');
// Set the running core version to 9.8.0.
$this->setCoreVersion('9.8.0');
......
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