CoreUpdateTest.php in Automatic Updates 8.2
Namespace
Drupal\Tests\automatic_updates\BuildFile
tests/src/Build/CoreUpdateTest.phpView source
<?php
namespace Drupal\Tests\automatic_updates\Build;
/**
* Tests an end-to-end update of Drupal core.
*
* @group automatic_updates
*/
class CoreUpdateTest extends UpdateTestBase {
/**
* {@inheritdoc}
*/
protected function createTestSite(string $template) : void {
// Build the test site and alter its copy of core so that it thinks it's
// running Drupal 9.8.0, which will never actually exist in the real world.
// Then, prepare a secondary copy of the core code base, masquerading as
// Drupal 9.8.1, which will be the version of core we update to. These two
// versions are referenced in the fake release metadata in our fake release
// metadata (see fixtures/release-history/drupal.0.0.xml).
parent::createTestSite($template);
$this
->setCoreVersion($this
->getWebRoot() . '/core', '9.8.0');
$this
->alterPackage($this
->getWorkspaceDirectory(), $this
->getConfigurationForUpdate('9.8.1'));
// Install Drupal and ensure it's using the fake release metadata to fetch
// information about available updates.
$this
->installQuickStart('minimal');
$this
->setReleaseMetadata([
'drupal' => '9.8.1-security',
]);
$this
->formLogin($this->adminUsername, $this->adminPassword);
$this
->installModules([
'automatic_updates',
'automatic_updates_test',
'update_test',
]);
// Ensure that Drupal thinks we are running 9.8.0, then refresh information
// about available updates.
$this
->assertCoreVersion('9.8.0');
$this
->checkForUpdates();
// Ensure that an update to 9.8.1 is available.
$this
->visit('/admin/modules/automatic-update');
$this
->getMink()
->assertSession()
->pageTextContains('9.8.1');
}
/**
* {@inheritdoc}
*/
protected function tearDown() : void {
if ($this->destroyBuild) {
$this
->deleteCopiedPackages();
}
parent::tearDown();
}
/**
* Modifies a Drupal core code base to set its version.
*
* @param string $dir
* The directory of the Drupal core code base.
* @param string $version
* The version number to set.
*/
private function setCoreVersion(string $dir, string $version) : void {
$this
->alterPackage($dir, [
'version' => $version,
]);
$drupal_php = "{$dir}/lib/Drupal.php";
$this
->assertIsWritable($drupal_php);
$code = file_get_contents($drupal_php);
$code = preg_replace("/const VERSION = '([0-9]+\\.?){3}(-dev)?';/", "const VERSION = '{$version}';", $code);
file_put_contents($drupal_php, $code);
}
/**
* Returns composer.json changes that are needed to update core.
*
* This will clone the following packages into temporary directories:
* - drupal/core
* - drupal/core-recommended
* - drupal/core-project-message
* - drupal/core-composer-scaffold
* The cloned packages will be assigned the given version number, and the test
* site's composer.json will use the clones as path repositories.
*
* @param string $version
* The version of core we will be updating to.
*
* @return array
* The changes to merge into the test site's composer.json.
*/
protected function getConfigurationForUpdate(string $version) : array {
$repositories = [];
// Create a fake version of core with the given version number, and change
// its README so that we can actually be certain that we update to this
// fake version.
$dir = $this
->copyPackage($this
->getWebRoot() . '/core');
$this
->setCoreVersion($dir, $version);
file_put_contents("{$dir}/README.txt", "Placeholder for Drupal core {$version}.");
$repositories['drupal/core'] = $this
->createPathRepository($dir);
$drupal_root = $this
->getDrupalRoot();
// Create a fake version of drupal/core-recommended which itself requires
// the fake version of core we just created.
$dir = $this
->copyPackage("{$drupal_root}/composer/Metapackage/CoreRecommended");
$this
->alterPackage($dir, [
'require' => [
'drupal/core' => $version,
],
'version' => $version,
]);
$repositories['drupal/core-recommended'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-project-message.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/ProjectMessage");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-project-message'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-composer-scaffold.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/Scaffold");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-composer-scaffold'] = $this
->createPathRepository($dir);
// Create a fake version of drupal/core-vendor-hardening.
$dir = $this
->copyPackage("{$drupal_root}/composer/Plugin/VendorHardening");
$this
->alterPackage($dir, [
'version' => $version,
]);
$repositories['drupal/core-vendor-hardening'] = $this
->createPathRepository($dir);
return [
'repositories' => $repositories,
];
}
/**
* Data provider for end-to-end update tests.
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public function providerTemplate() : array {
return [
[
'drupal/recommended-project',
],
[
'drupal/legacy-project',
],
];
}
/**
* Tests an end-to-end core update via the API.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testApi(string $template) : void {
$this
->createTestSite($template);
$mink = $this
->getMink();
$assert_session = $mink
->assertSession();
// Ensure that the update is prevented if the web root and/or vendor
// directories are not writable.
$this
->assertReadOnlyFileSystemError($template, '/automatic-update-test/update/9.8.1');
$mink
->getSession()
->reload();
$assert_session
->pageTextContains('9.8.1');
}
/**
* Tests an end-to-end core update via the UI.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testUi(string $template) : void {
$this
->createTestSite($template);
$mink = $this
->getMink();
$session = $mink
->getSession();
$page = $session
->getPage();
$assert_session = $mink
->assertSession();
$this
->visit('/admin/modules');
$assert_session
->pageTextContains('There is a security update available for your version of Drupal.');
$page
->clickLink('Update');
// Ensure that the update is prevented if the web root and/or vendor
// directories are not writable.
$this
->assertReadOnlyFileSystemError($template, parse_url($session
->getCurrentUrl(), PHP_URL_PATH));
$session
->reload();
$assert_session
->pageTextNotContains('There is a security update available for your version of Drupal.');
$page
->pressButton('Update');
$this
->waitForBatchJob();
$assert_session
->pageTextContains('Ready to update');
$page
->pressButton('Continue');
$this
->waitForBatchJob();
$assert_session
->pageTextContains('Update complete!');
$assert_session
->pageTextNotContains('There is a security update available for your version of Drupal.');
$this
->assertUpdateSuccessful();
}
/**
* Tests an end-to-end core update via cron.
*
* @param string $template
* The template project from which to build the test site.
*
* @dataProvider providerTemplate
*/
public function testCron(string $template) : void {
$this
->createTestSite($template);
$this
->visit('/admin/reports/status');
$this
->getMink()
->getSession()
->getPage()
->clickLink('Run cron');
$this
->assertUpdateSuccessful();
}
/**
* Asserts that the update is prevented if the filesystem isn't writable.
*
* @param string $template
* The project template used to build the test site. See ::createTestSite()
* for the possible values.
* @param string $url
* A URL where we can see the error message which is raised when parts of
* the file system are not writable. This URL will be visited twice: once
* for the web root, and once for the vendor directory.
*/
private function assertReadOnlyFileSystemError(string $template, string $url) : void {
$directories = [
'Drupal' => rtrim($this
->getWebRoot(), './'),
];
// The location of the vendor directory depends on which project template
// was used to build the test site.
if ($template === 'drupal/recommended-project') {
$directories['vendor'] = $this
->getWorkspaceDirectory() . '/vendor';
}
elseif ($template === 'drupal/legacy-project') {
$directories['vendor'] = $directories['Drupal'] . '/vendor';
}
$assert_session = $this
->getMink()
->assertSession();
foreach ($directories as $type => $path) {
chmod($path, 0555);
$this
->assertDirectoryIsNotWritable($path);
$this
->visit($url);
$assert_session
->pageTextContains("The {$type} directory \"{$path}\" is not writable.");
chmod($path, 0755);
$this
->assertDirectoryIsWritable($path);
}
}
/**
* Asserts that Drupal core was successfully updated.
*/
private function assertUpdateSuccessful() : void {
// The update form should not have any available updates.
// @todo Figure out why this assertion fails when the batch processor
// redirects directly to the update form, instead of update.status, when
// updating via the UI.
$this
->visit('/admin/modules/automatic-update');
$this
->getMink()
->assertSession()
->pageTextContains('No update available');
// The status page should report that we're running Drupal 9.8.1.
$this
->assertCoreVersion('9.8.1');
// The fake placeholder text from ::getConfigurationForUpdate() should be
// present in the README.
$placeholder = file_get_contents($this
->getWebRoot() . '/core/README.txt');
$this
->assertSame('Placeholder for Drupal core 9.8.1.', $placeholder);
}
}
Classes
Name | Description |
---|---|
CoreUpdateTest | Tests an end-to-end update of Drupal core. |