From f80339a01b755e190a2475e0fc578faff088aafd Mon Sep 17 00:00:00 2001
From: tedbow <tedbow@240860.no-reply.drupal.org>
Date: Thu, 14 Apr 2022 20:02:03 +0000
Subject: [PATCH] Issue #3274830 by tedbow, phenaproxima: Make ProjectInfo work
 with legacy version numbers

---
 src/ProjectInfo.php                           | 16 +--
 src/Validator/UpdateVersionValidator.php      |  2 +-
 .../aaa_automatic_updates_test.9.8.2.xml      | 98 +++++++++++++++++++
 .../release-history/drupal.9.8.1-security.xml | 12 +++
 .../drupal.9.8.2-older-sec-release.xml        | 12 +++
 .../drupal.9.8.2-unsupported_unpublished.xml  | 12 +++
 .../fixtures/release-history/drupal.9.8.2.xml | 12 +++
 .../drupal.9.8.2_unknown_status.xml           | 12 +++
 .../aaa_automatic_updates_test.info.yml       |  4 +
 .../Kernel/AutomaticUpdatesKernelTestBase.php | 17 ++--
 tests/src/Kernel/CronUpdaterTest.php          |  4 +-
 tests/src/Kernel/ProjectInfoTest.php          | 82 ++++++++++++++--
 .../UpdateVersionValidatorTest.php            | 10 +-
 tests/src/Kernel/ReleaseChooserTest.php       |  2 +-
 tests/src/Kernel/UpdaterTest.php              |  2 +-
 15 files changed, 263 insertions(+), 34 deletions(-)
 create mode 100644 tests/fixtures/release-history/aaa_automatic_updates_test.9.8.2.xml
 create mode 100644 tests/modules/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml

diff --git a/src/ProjectInfo.php b/src/ProjectInfo.php
index d8e5fadbea..927a855737 100644
--- a/src/ProjectInfo.php
+++ b/src/ProjectInfo.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\automatic_updates;
 
-use Composer\Semver\Comparator;
 use Drupal\automatic_updates_9_3_shim\ProjectRelease;
 use Drupal\Core\Extension\ExtensionVersion;
 use Drupal\update\UpdateManagerInterface;
@@ -42,18 +41,17 @@ class ProjectInfo {
    *
    * @return bool
    *   TRUE if the release is installable, otherwise FALSE. A release will be
-   *   considered installable if it is secure, published, supported, in
-   *   a supported branch, and newer than currently installed version.
+   *   considered installable if it is secure, published, supported, and in
+   *   a supported branch.
    */
   private function isInstallable(ProjectRelease $release, array $support_branches): bool {
     if ($release->isInsecure() || !$release->isPublished() || $release->isUnsupported()) {
       return FALSE;
     }
-    $installed_version = $this->getInstalledVersion();
-    if (Comparator::lessThanOrEqualTo($release->getVersion(), $installed_version)) {
+    $version = ExtensionVersion::createFromVersionString($release->getVersion());
+    if ($version->getVersionExtra() === 'dev') {
       return FALSE;
     }
-    $version = ExtensionVersion::createFromVersionString($release->getVersion());
     foreach ($support_branches as $support_branch) {
       $support_branch_version = ExtensionVersion::createFromSupportBranch($support_branch);
       if ($support_branch_version->getMajorVersion() === $version->getMajorVersion() && $support_branch_version->getMinorVersion() === $version->getMinorVersion()) {
@@ -117,7 +115,8 @@ class ProjectInfo {
     foreach ($available_updates['releases'] as $release_info) {
       $release = ProjectRelease::createFromArray($release_info);
       $version = $release->getVersion();
-      if (Comparator::lessThanOrEqualTo($version, $installed_version)) {
+      if ($version === $installed_version) {
+        // Stop searching for releases as soon as we find the installed version.
         break;
       }
       if ($this->isInstallable($release, $support_branches)) {
@@ -136,6 +135,9 @@ class ProjectInfo {
    */
   public function getInstalledVersion(): ?string {
     if ($project_data = $this->getProjectInfo()) {
+      if (empty($project_data['existing_version'])) {
+        throw new \UnexpectedValueException("Project '{$this->name}' does not have 'existing_version' set");
+      }
       return $project_data['existing_version'];
     }
     return NULL;
diff --git a/src/Validator/UpdateVersionValidator.php b/src/Validator/UpdateVersionValidator.php
index 4f767653ed..1445114961 100644
--- a/src/Validator/UpdateVersionValidator.php
+++ b/src/Validator/UpdateVersionValidator.php
@@ -163,7 +163,7 @@ class UpdateVersionValidator implements EventSubscriberInterface {
     //   https://www.drupal.org/project/automatic_updates/issues/3272068.
     if ($from_version->getVersionExtra() === 'dev') {
       return ValidationResult::createError([
-        $this->t('Drupal cannot be automatically updated from its current version, @from_version, to the recommended version, @to_version, because automatic updates from a dev version to any other version are not supported.', $variables),
+        $this->t('Drupal cannot be automatically updated from the installed version, @from_version, because automatic updates from a dev version to any other version are not supported.', $variables),
       ]);
     }
     if (Comparator::lessThan($to_version_string, $from_version_string)) {
diff --git a/tests/fixtures/release-history/aaa_automatic_updates_test.9.8.2.xml b/tests/fixtures/release-history/aaa_automatic_updates_test.9.8.2.xml
new file mode 100644
index 0000000000..0d0e8477cb
--- /dev/null
+++ b/tests/fixtures/release-history/aaa_automatic_updates_test.9.8.2.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project xmlns:dc="http://purl.org/dc/elements/1.1/">
+<title>AAA</title>
+<short_name>aaa_automatic_updates_test</short_name>
+<dc:creator>AAA</dc:creator>
+<supported_branches>7.0.,8.x-6.</supported_branches>
+<project_status>published</project_status>
+<link>http://example.com/project/aaa_automatic_updates_test</link>
+  <terms>
+   <term><name>Projects</name><value>AAA project</value></term>
+  </terms>
+<releases>
+    <release>
+        <name>AAA 7.0.1</name>
+        <version>7.0.1</version>
+        <status>published</status>
+        <release_link>http://example.com/aaa_automatic_updates_test-9-7-1-release</release_link>
+        <download_link>http://example.com/aaa_automatic_updates_test-9-7-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>AAA 7.0.0</name>
+        <version>7.0.0</version>
+        <status>published</status>
+        <release_link>http://example.com/aaa_automatic_updates_test-9-7-0-release</release_link>
+        <download_link>http://example.com/aaa_automatic_updates_test-9-7-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>
+    <release>
+        <name>AAA 7.0.0-alpha1</name>
+        <version>7.0.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/aaa_automatic_updates_test-9-7-0-alpha1-release</release_link>
+        <download_link>http://example.com/aaa_automatic_updates_test-9-7-0-alpha1.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>
+  <release>
+    <name>AAA 8.x-6.2</name>
+    <version>8.x-6.2</version>
+    <status>published</status>
+    <release_link>http://example.com/aaa_automatic_updates_test-9-8-2-release</release_link>
+    <download_link>http://example.com/aaa_automatic_updates_test-9-8-2.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>AAA 8.x-6.1</name>
+  <version>8.x-6.1</version>
+  <status>published</status>
+  <release_link>http://example.com/aaa_automatic_updates_test-9-8-1-release</release_link>
+  <download_link>http://example.com/aaa_automatic_updates_test-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>AAA 8.x-6.0</name>
+   <version>8.x-6.0</version>
+   <status>published</status>
+   <release_link>http://example.com/aaa_automatic_updates_test-9-8-0-release</release_link>
+   <download_link>http://example.com/aaa_automatic_updates_test-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>
+  <release>
+    <name>AAA 8.x-6.0-alpha1</name>
+    <version>8.x-6.0-alpha1</version>
+    <status>published</status>
+    <release_link>http://example.com/aaa_automatic_updates_test-9-8-0-alpha1-release</release_link>
+    <download_link>http://example.com/aaa_automatic_updates_test-9-8-0-alpha1.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>
diff --git a/tests/fixtures/release-history/drupal.9.8.1-security.xml b/tests/fixtures/release-history/drupal.9.8.1-security.xml
index c53813e51e..5466a623be 100644
--- a/tests/fixtures/release-history/drupal.9.8.1-security.xml
+++ b/tests/fixtures/release-history/drupal.9.8.1-security.xml
@@ -49,5 +49,17 @@
             <term><name>Release type</name><value>Insecure</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.8.x-dev</name>
+        <version>9.8.x-dev</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-x-dex-release</release_link>
+        <download_link>http://example.com/drupal-9-8-x-dex.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>
diff --git a/tests/fixtures/release-history/drupal.9.8.2-older-sec-release.xml b/tests/fixtures/release-history/drupal.9.8.2-older-sec-release.xml
index cc65b78a69..0d29b6e115 100644
--- a/tests/fixtures/release-history/drupal.9.8.2-older-sec-release.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2-older-sec-release.xml
@@ -98,5 +98,17 @@
             <term><name>Release type</name><value>Bug fixes</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.8.x-dev</name>
+        <version>9.8.x-dev</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-x-dex-release</release_link>
+        <download_link>http://example.com/drupal-9-8-x-dex.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>
diff --git a/tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml b/tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml
index 90df01ce03..ecf62618b0 100644
--- a/tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml
@@ -131,5 +131,17 @@
             <term><name>Release type</name><value>Bug fixes</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.8.x-dev</name>
+        <version>9.8.x-dev</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-x-dex-release</release_link>
+        <download_link>http://example.com/drupal-9-8-x-dex.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>
diff --git a/tests/fixtures/release-history/drupal.9.8.2.xml b/tests/fixtures/release-history/drupal.9.8.2.xml
index 2c323fa9ea..a830e4d023 100644
--- a/tests/fixtures/release-history/drupal.9.8.2.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2.xml
@@ -130,5 +130,17 @@
             <term><name>Release type</name><value>Bug fixes</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.8.x-dev</name>
+        <version>9.8.x-dev</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-x-dex-release</release_link>
+        <download_link>http://example.com/drupal-9-8-x-dex.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>
diff --git a/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml b/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
index c34b707727..2ec503e5a5 100644
--- a/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
@@ -130,5 +130,17 @@
             <term><name>Release type</name><value>Bug fixes</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.8.x-dev</name>
+        <version>9.8.x-dev</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-x-dex-release</release_link>
+        <download_link>http://example.com/drupal-9-8-x-dex.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>
diff --git a/tests/modules/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml b/tests/modules/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml
new file mode 100644
index 0000000000..79010053c9
--- /dev/null
+++ b/tests/modules/aaa_automatic_updates_test/aaa_automatic_updates_test.info.yml
@@ -0,0 +1,4 @@
+name: AAA test module
+description: A module to test updates
+type: module
+package: Testing
diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
index be9866e599..8431880532 100644
--- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
+++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
@@ -61,7 +61,7 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa
     // By default, pretend we're running Drupal core 9.8.0 and a non-security
     // update to 9.8.1 is available.
     $this->setCoreVersion('9.8.1');
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2.xml');
+    $this->setReleaseMetadata([__DIR__ . '/../../fixtures/release-history/drupal.9.8.2.xml']);
 
     // Set a last cron run time so that the cron frequency validator will run
     // from a sane state.
@@ -108,13 +108,16 @@ abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBa
   /**
    * Sets the release metadata file to use when fetching available updates.
    *
-   * @param string $file
-   *   The path of the XML metadata file to use.
+   * @param string[] $files
+   *   The paths of the XML metadata files to use.
    */
-  protected function setReleaseMetadata(string $file): void {
-    $metadata = Utils::tryFopen($file, 'r');
-    $response = new Response(200, [], Utils::streamFor($metadata));
-    $handler = new MockHandler([$response]);
+  protected function setReleaseMetadata(array $files): void {
+    $responses = [];
+    foreach ($files as $file) {
+      $metadata = Utils::tryFopen($file, 'r');
+      $responses[] = new Response(200, [], Utils::streamFor($metadata));
+    }
+    $handler = new MockHandler($responses);
     $this->client = new Client([
       'handler' => HandlerStack::create($handler),
     ]);
diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index 4c07670396..db15b37a8f 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -137,7 +137,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
   public function testUpdaterCalled(string $setting, string $release_data, bool $will_update): void {
     // Our form alter does not refresh information on available updates, so
     // ensure that the appropriate update data is loaded beforehand.
-    $this->setReleaseMetadata($release_data);
+    $this->setReleaseMetadata([$release_data]);
     $this->setCoreVersion('9.8.0');
     update_get_available(TRUE);
 
@@ -287,7 +287,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
     $this->installConfig('automatic_updates');
     $this->setCoreVersion('9.8.0');
     // Ensure that there is a security release to which we should update.
-    $this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/drupal.9.8.1-security.xml");
+    $this->setReleaseMetadata([__DIR__ . "/../../fixtures/release-history/drupal.9.8.1-security.xml"]);
 
     // If the pre- or post-destroy events throw an exception, it will not be
     // caught by the cron updater, but it *will* be caught by the main cron
diff --git a/tests/src/Kernel/ProjectInfoTest.php b/tests/src/Kernel/ProjectInfoTest.php
index 97186ada3a..15c8c14238 100644
--- a/tests/src/Kernel/ProjectInfoTest.php
+++ b/tests/src/Kernel/ProjectInfoTest.php
@@ -3,6 +3,12 @@
 namespace Drupal\Tests\automatic_updates\Kernel;
 
 use Drupal\automatic_updates\ProjectInfo;
+use GuzzleHttp\Client;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Psr7\Utils;
+use Psr\Http\Message\RequestInterface;
 
 /**
  * @coversDefaultClass \Drupal\automatic_updates\ProjectInfo
@@ -24,9 +30,27 @@ class ProjectInfoTest extends AutomaticUpdatesKernelTestBase {
    * @dataProvider providerGetInstallableReleases
    */
   public function testGetInstallableReleases(string $fixture, string $installed_version, array $expected_versions) {
-    $this->setReleaseMetadata(__DIR__ . "/../../fixtures/release-history/$fixture");
-    $this->setCoreVersion($installed_version);
-    $project_info = new ProjectInfo('drupal');
+    [$project] = explode('.', $fixture);
+    $fixtures_directory = __DIR__ . '/../../fixtures/release-history/';
+    if ($project === 'drupal') {
+      $this->setCoreVersion($installed_version);
+    }
+    else {
+      // Update the version and the project of the project.
+      $this->enableModules(['aaa_automatic_updates_test']);
+      $extension_info_update = [
+        'version' => $installed_version,
+        'project' => 'aaa_automatic_updates_test',
+      ];
+      $this->config('update_test.settings')
+        ->set("system_info.$project", $extension_info_update)
+        ->save();
+      // The Update module will always request Drupal core's update XML.
+      $metadata_fixtures['drupal'] = $fixtures_directory . 'drupal.9.8.2.xml';
+    }
+    $metadata_fixtures[$project] = "$fixtures_directory$fixture";
+    $this->setReleaseMetadataForProjects($metadata_fixtures);
+    $project_info = new ProjectInfo($project);
     $actual_releases = $project_info->getInstallableReleases();
     // Assert that we returned the correct releases in the expected order.
     $this->assertSame($expected_versions, array_keys($actual_releases));
@@ -44,38 +68,48 @@ class ProjectInfoTest extends AutomaticUpdatesKernelTestBase {
    */
   public function providerGetInstallableReleases() {
     return [
-      'no updates' => [
+      'core, no updates' => [
         'drupal.9.8.2.xml',
         '9.8.2',
         [],
       ],
-      'on unsupported branch, updates in multiple supported branches' => [
+      'core, on unsupported branch, updates in multiple supported branches' => [
         'drupal.9.8.2.xml',
         '9.6.0-alpha1',
         ['9.8.2', '9.8.1', '9.8.0', '9.8.0-alpha1', '9.7.1', '9.7.0', '9.7.0-alpha1'],
       ],
       // A test case with an unpublished release, 9.8.0, and unsupported
       // release, 9.8.1, both of these releases should not be returned.
-      'filter out unsupported and unpublished releases' => [
+      'core, filter out unsupported and unpublished releases' => [
         'drupal.9.8.2-unsupported_unpublished.xml',
         '9.6.0-alpha1',
         ['9.8.2', '9.8.0-alpha1', '9.7.1', '9.7.0', '9.7.0-alpha1'],
       ],
-      'supported branches before and after installed release' => [
+      'core, supported branches before and after installed release' => [
         'drupal.9.8.2.xml',
         '9.8.0-alpha1',
         ['9.8.2', '9.8.1', '9.8.0'],
       ],
-      'one insecure release filtered out' => [
+      'core, one insecure release filtered out' => [
         'drupal.9.8.1-security.xml',
         '9.8.0-alpha1',
         ['9.8.1'],
       ],
-      'skip insecure releases and return secure releases' => [
+      'core, skip insecure releases and return secure releases' => [
         'drupal.9.8.2-older-sec-release.xml',
         '9.7.0-alpha1',
         ['9.8.2', '9.8.1', '9.8.0-alpha1', '9.7.1'],
       ],
+      'contrib, semver and legacy' => [
+        'aaa_automatic_updates_test.9.8.2.xml',
+        '8.x-6.0-alpha1',
+        ['7.0.1', '7.0.0', '7.0.0-alpha1', '8.x-6.2', '8.x-6.1', '8.x-6.0'],
+      ],
+      'contrib, semver and legacy, some lower' => [
+        'aaa_automatic_updates_test.9.8.2.xml',
+        '8.x-6.1',
+        ['7.0.1', '7.0.0', '7.0.0-alpha1', '8.x-6.2'],
+      ],
     ];
   }
 
@@ -85,11 +119,39 @@ class ProjectInfoTest extends AutomaticUpdatesKernelTestBase {
    * @covers ::getInstallableReleases()
    */
   public function testNotPublishedProject() {
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2_unknown_status.xml');
+    $this->setReleaseMetadataForProjects(['drupal' => __DIR__ . '/../../fixtures/release-history/drupal.9.8.2_unknown_status.xml']);
     $project_info = new ProjectInfo('drupal');
     $this->expectException('RuntimeException');
     $this->expectExceptionMessage("The project 'drupal' can not be updated because its status is any status besides published");
     $project_info->getInstallableReleases();
   }
 
+  /**
+   * Sets the release metadata file to use when fetching available updates.
+   *
+   * @param string[] $files
+   *   The paths of the XML metadata files to use, keyed by project name.
+   */
+  protected function setReleaseMetadataForProjects(array $files): void {
+    $responses = [];
+
+    foreach ($files as $project => $file) {
+      $metadata = Utils::tryFopen($file, 'r');
+      $responses["/release-history/$project/current"] = new Response(200, [], Utils::streamFor($metadata));
+    }
+    $callable = function (RequestInterface $request) use ($responses): Response {
+      return $responses[$request->getUri()->getPath()] ?? new Response(404);
+    };
+
+    // The mock handler's queue consist of same callable as many times as the
+    // number of requests we expect to be made for update XML because it will
+    // retrieve one item off the queue for each request.
+    // @see \GuzzleHttp\Handler\MockHandler::__invoke()
+    $handler = new MockHandler(array_fill(0, count($responses), $callable));
+    $this->client = new Client([
+      'handler' => HandlerStack::create($handler),
+    ]);
+    $this->container->set('http_client', $this->client);
+  }
+
 }
diff --git a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
index ade86fb727..b477950c40 100644
--- a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
@@ -147,7 +147,7 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
     // In order to test what happens when only security updates are enabled
     // during cron (the default behavior), ensure that the latest available
     // release is a security update.
-    $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1-security.xml');
+    $this->setReleaseMetadata([__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1-security.xml']);
 
     $this->setCoreVersion('9.7.1');
     $this->assertCheckerResultsFromManager($expected_results, TRUE);
@@ -288,7 +288,7 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
       'Drupal cannot be automatically updated during cron from its current version, 9.8.0-alpha1, because Automatic Updates only supports updating from stable versions during cron.',
     ]);
     $dev_current_version = ValidationResult::createError([
-      'Drupal cannot be automatically updated from its current version, 9.8.0-dev, to the recommended version, 9.8.0-alpha1, because automatic updates from a dev version to any other version are not supported.',
+      'Drupal cannot be automatically updated from the installed version, 9.8.x-dev, because automatic updates from a dev version to any other version are not supported.',
     ]);
     $different_major_version = ValidationResult::createError([
       'Drupal cannot be automatically updated from its current version, 8.9.1, to the recommended version, 9.7.0-alpha1, because automatic updates from one major version to another are not supported.',
@@ -315,17 +315,17 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
       ],
       'dev current version, cron disabled' => [
         CronUpdater::DISABLED,
-        '9.8.0-dev',
+        '9.8.x-dev',
         [$dev_current_version],
       ],
       'dev current version, security updates allowed' => [
         CronUpdater::SECURITY,
-        '9.8.0-dev',
+        '9.8.x-dev',
         [$dev_current_version],
       ],
       'dev current version, all updates allowed' => [
         CronUpdater::ALL,
-        '9.8.0-dev',
+        '9.8.x-dev',
         [$dev_current_version],
       ],
       'different current major, cron disabled' => [
diff --git a/tests/src/Kernel/ReleaseChooserTest.php b/tests/src/Kernel/ReleaseChooserTest.php
index 9b4470b282..10d2177d80 100644
--- a/tests/src/Kernel/ReleaseChooserTest.php
+++ b/tests/src/Kernel/ReleaseChooserTest.php
@@ -21,7 +21,7 @@ class ReleaseChooserTest extends AutomaticUpdatesKernelTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.2-older-sec-release.xml');
+    $this->setReleaseMetadata([__DIR__ . '/../../fixtures/release-history/drupal.9.8.2-older-sec-release.xml']);
 
   }
 
diff --git a/tests/src/Kernel/UpdaterTest.php b/tests/src/Kernel/UpdaterTest.php
index 8dbaeaafb6..613764d670 100644
--- a/tests/src/Kernel/UpdaterTest.php
+++ b/tests/src/Kernel/UpdaterTest.php
@@ -37,7 +37,7 @@ class UpdaterTest extends AutomaticUpdatesKernelTestBase {
     // Simulate that we're running Drupal 9.8.0 and a security update to 9.8.1
     // is available.
     $this->setCoreVersion('9.8.0');
-    $this->setReleaseMetadata(__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml');
+    $this->setReleaseMetadata([__DIR__ . '/../../fixtures/release-history/drupal.9.8.1-security.xml']);
 
     // Create a user who will own the stage even after the container is rebuilt.
     $user = $this->createUser([], NULL, TRUE, ['uid' => 2]);
-- 
GitLab