Skip to content
Snippets Groups Projects
Commit f3c421e7 authored by Kunal Sachdev's avatar Kunal Sachdev Committed by Ted Bowman
Browse files

Issue #3248929 by kunal.sachdev: List update that will be applied on the UpdateReady form

parent 3ee886e8
No related branches found
No related tags found
1 merge request!214Issue #3248929: List update that will be applied on the UpdateReady form
Showing with 238 additions and 32 deletions
name: 'Package Manager Test Fixture'
description: 'Provides a mechanism for functional tests to stage fixture files.'
type: module
package: Testing
dependencies:
- automatic_updates:package_manager
services:
package_manager_test_fixture.stager:
class: Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager
arguments:
- '@state'
- '@package_manager.symfony_file_system'
tags:
- { name: event_subscriber }
<?php
namespace Drupal\package_manager_test_fixture\EventSubscriber;
use Drupal\Core\State\StateInterface;
use Drupal\package_manager\Event\PostRequireEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
/**
* Defines an event subscriber which copies certain files into the staging area.
*
* This is most useful in conjunction with package_manager_bypass, which quietly
* turns all Composer Stager operations into no-ops. In such cases, no staging
* area will be physically created, but if a test needs to simulate certain
* conditions in a staging area without actually staging the active code base,
* this event subscriber is the way to do it.
*/
class FixtureStager implements EventSubscriberInterface {
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The Symfony file system service.
*
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $fileSystem;
/**
* Constructs a FixtureStager.
*
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Symfony\Component\Filesystem\Filesystem $file_system
* The Symfony file system service.
*/
public function __construct(StateInterface $state, Filesystem $file_system) {
$this->state = $state;
$this->fileSystem = $file_system;
}
/**
* Copies files from a fixture into the staging area.
*
* Tests which use this functionality are responsible for cleaning up the
* staging area.
*
* @param \Drupal\package_manager\Event\PostRequireEvent $event
* The event object.
*
* @see \Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase::tearDown()
*/
public function copyFilesFromFixture(PostRequireEvent $event): void {
$fixturePath = $this->state->get(static::class);
if ($fixturePath && is_dir($fixturePath)) {
$this->fileSystem->mirror($fixturePath, $event->getStage()->getStageDirectory(), NULL, [
'override' => TRUE,
'delete' => TRUE,
]);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return [
PostRequireEvent::class => 'copyFilesFromFixture',
];
}
/**
* Sets the path of the fixture to copy into the staging area.
*
* @param string $path
* The path of the fixture to copy into the staging area.
*/
public static function setFixturePath(string $path): void {
\Drupal::state()->set(static::class, $path);
}
}
...@@ -105,46 +105,71 @@ class UpdateReady extends FormBase { ...@@ -105,46 +105,71 @@ class UpdateReady extends FormBase {
return $form; return $form;
} }
// Don't check for pending database updates if the form has been submitted, $messages = [];
// because we don't want to store the warning in the messenger during form
// submit. // If there are any installed modules with database updates in the staging
// area, warn the user that they might be sent to update.php once the
// staged changes have been applied.
$pending_updates = $this->getModulesWithStagedDatabaseUpdates();
if ($pending_updates) {
$messages[MessengerInterface::TYPE_WARNING][] = $this->t('Possible database updates were detected in the following modules; you may be redirected to the database update page in order to complete the update process.');
foreach ($pending_updates as $info) {
$messages[MessengerInterface::TYPE_WARNING][] = $info['name'];
}
}
try {
$staged_core_packages = $this->updater->getStageComposer()
->getCorePackages();
}
catch (\Throwable $exception) {
$messages[MessengerInterface::TYPE_ERROR][] = $this->t('There was an error loading the pending update. Press the <em>Cancel update</em> button to start over.');
}
// Don't set any messages if the form has been submitted, because we don't
// want them to be set during form submit.
if (!$form_state->getUserInput()) { if (!$form_state->getUserInput()) {
// If there are any installed modules with database updates in the staging foreach ($messages as $type => $messages_of_type) {
// area, warn the user that they might be sent to update.php once the foreach ($messages_of_type as $message) {
// staged changes have been applied. $this->messenger()->addMessage($message, $type);
$pending_updates = $this->getModulesWithStagedDatabaseUpdates();
if ($pending_updates) {
$this->messenger()->addWarning($this->t('Possible database updates were detected in the following modules; you may be redirected to the database update page in order to complete the update process.'));
foreach ($pending_updates as $info) {
$this->messenger()->addWarning($info['name']);
} }
} }
} }
$form['actions'] = [
'cancel' => [
'#type' => 'submit',
'#value' => $this->t('Cancel update'),
'#submit' => ['::cancel'],
],
'#type' => 'actions',
];
$form['stage_id'] = [ $form['stage_id'] = [
'#type' => 'value', '#type' => 'value',
'#value' => $stage_id, '#value' => $stage_id,
]; ];
if (empty($staged_core_packages)) {
return $form;
}
$form['update_version'] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $this->t('Drupal core will be updated to %version', [
'%version' => reset($staged_core_packages)->getPrettyVersion(),
]),
];
$form['backup'] = [ $form['backup'] = [
'#prefix' => '<strong>', '#prefix' => '<strong>',
'#markup' => $this->t('Back up your database and site before you continue. <a href=":backup_url">Learn how</a>.', [':backup_url' => 'https://www.drupal.org/node/22281']), '#markup' => $this->t('Back up your database and site before you continue. <a href=":backup_url">Learn how</a>.', [':backup_url' => 'https://www.drupal.org/node/22281']),
'#suffix' => '</strong>', '#suffix' => '</strong>',
]; ];
$form['maintenance_mode'] = [ $form['maintenance_mode'] = [
'#title' => $this->t('Perform updates with site in maintenance mode (strongly recommended)'), '#title' => $this->t('Perform updates with site in maintenance mode (strongly recommended)'),
'#type' => 'checkbox', '#type' => 'checkbox',
'#default_value' => TRUE, '#default_value' => TRUE,
]; ];
$form['actions'] = ['#type' => 'actions'];
$form['actions']['cancel'] = [
'#type' => 'submit',
'#value' => $this->t('Cancel update'),
'#submit' => ['::cancel'],
];
$form['actions']['submit'] = [ $form['actions']['submit'] = [
'#type' => 'submit', '#type' => 'submit',
'#value' => $this->t('Continue'), '#value' => $this->t('Continue'),
......
{
"extra": {
"_readme": [
"This fixture simulates a staging area in which, according to Composer, Drupal core 9.8.1 is installed."
]
}
}
{
"packages": [
{
"name": "drupal/core",
"version": "9.8.1",
"type": "drupal-core"
}
]
}
...@@ -50,6 +50,19 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase { ...@@ -50,6 +50,19 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase {
$this->disableValidators($this->disableValidators); $this->disableValidators($this->disableValidators);
} }
/**
* {@inheritdoc}
*/
protected function tearDown(): void {
// If automatic_updates is installed, ensure any staging area created during
// the test is cleaned up.
$service_id = 'automatic_updates.updater';
if ($this->container->has($service_id)) {
$this->container->get($service_id)->destroy(TRUE);
}
parent::tearDown();
}
/** /**
* Disables validators in the test site's settings. * Disables validators in the test site's settings.
* *
...@@ -124,10 +137,14 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase { ...@@ -124,10 +137,14 @@ abstract class AutomaticUpdatesFunctionalTestBase extends BrowserTestBase {
/** /**
* Asserts that we are on the "update ready" form. * Asserts that we are on the "update ready" form.
*
* @param string $update_version
* The version of Drupal core that we are updating to.
*/ */
protected function assertUpdateReady(): void { protected function assertUpdateReady(string $update_version): void {
$this->assertSession() $assert_session = $this->assertSession();
->addressMatches('/\/admin\/automatic-update-ready\/[a-zA-Z0-9_\-]+$/'); $assert_session->addressMatches('/\/admin\/automatic-update-ready\/[a-zA-Z0-9_\-]+$/');
$assert_session->pageTextContainsOnce('Drupal core will be updated to ' . $update_version);
} }
} }
...@@ -8,6 +8,7 @@ use Drupal\automatic_updates_test\Datetime\TestTime; ...@@ -8,6 +8,7 @@ use Drupal\automatic_updates_test\Datetime\TestTime;
use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
use Drupal\automatic_updates_test2\EventSubscriber\TestSubscriber2; use Drupal\automatic_updates_test2\EventSubscriber\TestSubscriber2;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
use Drupal\system\SystemManager; use Drupal\system\SystemManager;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\Traits\Core\CronRunTrait; use Drupal\Tests\Traits\Core\CronRunTrait;
...@@ -398,6 +399,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -398,6 +399,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
$this->container->get('module_installer')->install([ $this->container->get('module_installer')->install([
'automatic_updates', 'automatic_updates',
'automatic_updates_test', 'automatic_updates_test',
'package_manager_test_fixture',
]); ]);
// Because all actual staging operations are bypassed by // Because all actual staging operations are bypassed by
// package_manager_bypass (enabled by the parent class), disable this // package_manager_bypass (enabled by the parent class), disable this
...@@ -418,6 +420,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -418,6 +420,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
// readiness check (without storing the results), and the checker is no // readiness check (without storing the results), and the checker is no
// longer raising an error. // longer raising an error.
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
$assert_session->buttonExists('Update'); $assert_session->buttonExists('Update');
// Ensure that the previous results are still displayed on another admin // Ensure that the previous results are still displayed on another admin
// page, to confirm that the updater form is not discarding the previous // page, to confirm that the updater form is not discarding the previous
...@@ -428,7 +431,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -428,7 +431,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase {
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$page->pressButton('Continue'); $page->pressButton('Continue');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$assert_session->pageTextContains('Update complete!'); $assert_session->pageTextContains('Update complete!');
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace Drupal\Tests\automatic_updates\Functional; namespace Drupal\Tests\automatic_updates\Functional;
use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
/** /**
* Tests that only one Automatic Update operation can be performed at a time. * Tests that only one Automatic Update operation can be performed at a time.
* *
...@@ -20,6 +22,7 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -20,6 +22,7 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase {
protected static $modules = [ protected static $modules = [
'automatic_updates', 'automatic_updates',
'automatic_updates_test', 'automatic_updates_test',
'package_manager_test_fixture',
]; ];
/** /**
...@@ -48,9 +51,10 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -48,9 +51,10 @@ class UpdateLockTest extends AutomaticUpdatesFunctionalTestBase {
// We should be able to get partway through an update without issue. // We should be able to get partway through an update without issue.
$this->drupalLogin($user_1); $this->drupalLogin($user_1);
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$assert_session->buttonExists('Continue'); $assert_session->buttonExists('Continue');
$url = parse_url($this->getSession()->getCurrentUrl(), PHP_URL_PATH); $url = parse_url($this->getSession()->getCurrentUrl(), PHP_URL_PATH);
......
...@@ -4,11 +4,13 @@ namespace Drupal\Tests\automatic_updates\Functional; ...@@ -4,11 +4,13 @@ namespace Drupal\Tests\automatic_updates\Functional;
use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\automatic_updates\Event\ReadinessCheckEvent;
use Drupal\automatic_updates_test\Datetime\TestTime; use Drupal\automatic_updates_test\Datetime\TestTime;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\package_manager\Event\PostRequireEvent; use Drupal\package_manager\Event\PostRequireEvent;
use Drupal\package_manager\Event\PreApplyEvent; use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\ValidationResult; use Drupal\package_manager\ValidationResult;
use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1; use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
use Drupal\package_manager_test_fixture\EventSubscriber\FixtureStager;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait; use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait;
...@@ -34,6 +36,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -34,6 +36,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
'block', 'block',
'automatic_updates', 'automatic_updates',
'automatic_updates_test', 'automatic_updates_test',
'package_manager_test_fixture',
]; ];
/** /**
...@@ -250,18 +253,19 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -250,18 +253,19 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->checkForUpdates(); $this->checkForUpdates();
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1); $this->assertUpdateStagedTimes(1);
// Confirm we are on the confirmation page. // Confirm we are on the confirmation page.
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$assert_session->buttonExists('Continue'); $assert_session->buttonExists('Continue');
// If we try to return to the start page, we should be redirected back to // If we try to return to the start page, we should be redirected back to
// the confirmation page. // the confirmation page.
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
// Delete the existing update. // Delete the existing update.
$page->pressButton('Cancel update'); $page->pressButton('Cancel update');
...@@ -273,7 +277,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -273,7 +277,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
// Confirm we are on the confirmation page. // Confirm we are on the confirmation page.
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$this->assertUpdateStagedTimes(2); $this->assertUpdateStagedTimes(2);
$assert_session->buttonExists('Continue'); $assert_session->buttonExists('Continue');
...@@ -290,7 +294,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -290,7 +294,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextNotContains($conflict_message); $assert_session->pageTextNotContains($conflict_message);
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
// Stop execution during pre-apply. This should make Package Manager think // Stop execution during pre-apply. This should make Package Manager think
// the staged changes are being applied and raise an error if we try to // the staged changes are being applied and raise an error if we try to
...@@ -332,7 +336,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -332,7 +336,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
TestSubscriber1::setTestResult($results, PreApplyEvent::class); TestSubscriber1::setTestResult($results, PreApplyEvent::class);
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$page->pressButton('Continue'); $page->pressButton('Continue');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$page->clickLink('the error page'); $page->clickLink('the error page');
...@@ -356,13 +360,14 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -356,13 +360,14 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$page = $this->getSession()->getPage(); $page = $this->getSession()->getPage();
$this->drupalGet('/admin/modules/automatic-update'); $this->drupalGet('/admin/modules/automatic-update');
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
// The warning should be visible. // The warning should be visible.
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$assert_session->pageTextContains(reset($messages)); $assert_session->pageTextContains(reset($messages));
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1); $this->assertUpdateStagedTimes(1);
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
// Simulate a staged database update in the automatic_updates_test module. // Simulate a staged database update in the automatic_updates_test module.
// We must do this after the update has started, because the pending updates // We must do this after the update has started, because the pending updates
// validator will prevent an update from starting. // validator will prevent an update from starting.
...@@ -397,10 +402,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -397,10 +402,11 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$page = $this->getSession()->getPage(); $page = $this->getSession()->getPage();
$this->drupalGet($update_form_url); $this->drupalGet($update_form_url);
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
$page->pressButton('Update'); $page->pressButton('Update');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1); $this->assertUpdateStagedTimes(1);
$this->assertUpdateReady(); $this->assertUpdateReady('9.8.1');
$this->assertNotTrue($this->container->get('state')->get('system.maintenance_mode')); $this->assertNotTrue($this->container->get('state')->get('system.maintenance_mode'));
$page->pressButton('Continue'); $page->pressButton('Continue');
$this->checkForMetaRefresh(); $this->checkForMetaRefresh();
...@@ -414,6 +420,39 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { ...@@ -414,6 +420,39 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
$assert_session->pageTextContainsOnce('Update complete!'); $assert_session->pageTextContainsOnce('Update complete!');
} }
/**
* Tests what happens when a staged update is deleted without being destroyed.
*/
public function testStagedUpdateDeletedImproperly(): void {
$this->setCoreVersion('9.8.0');
$this->checkForUpdates();
$page = $this->getSession()->getPage();
$this->drupalGet('/admin/modules/automatic-update');
FixtureStager::setFixturePath(__DIR__ . '/../../fixtures/staged/9.8.1');
$page->pressButton('Update');
$this->checkForMetaRefresh();
$this->assertUpdateStagedTimes(1);
$this->assertUpdateReady('9.8.1');
// Confirm if the staged directory is deleted without using destroy(), then
// an error message will be displayed on the page.
// @see \Drupal\package_manager\Stage::getStagingRoot()
$dir = FileSystem::getOsTemporaryDirectory() . '/.package_manager' . $this->config('system.site')->get('uuid');
$this->assertDirectoryExists($dir);
$this->container->get('file_system')->deleteRecursive($dir);
$this->getSession()->reload();
$assert_session = $this->assertSession();
$error_message = 'There was an error loading the pending update. Press the Cancel update button to start over.';
$assert_session->pageTextContainsOnce($error_message);
// We should be able to start over without any problems, and the error
// message should not be seen on the updater form.
$page->pressButton('Cancel update');
$assert_session->addressEquals('/admin/reports/updates/automatic-update');
$assert_session->pageTextNotContains($error_message);
$assert_session->pageTextContains('The update was successfully cancelled.');
$assert_session->buttonExists('Update');
}
/** /**
* Tests that the update stage is destroyed if an error occurs during require. * Tests that the update stage is destroyed if an error occurs during require.
*/ */
......
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