From 04013d234167f0724ef4ebc4a843e8f6af9ea0b1 Mon Sep 17 00:00:00 2001
From: tedbow <tedbow@240860.no-reply.drupal.org>
Date: Tue, 12 Apr 2022 15:12:45 +0000
Subject: [PATCH] Issue #3274269 by tedbow: Ensure all installable releases are
 exposed

---
 src/ProjectInfo.php                           |  73 ++++--
 .../release-history/drupal.9.8.1-security.xml |  13 ++
 .../drupal.9.8.2-unsupported_unpublished.xml  | 135 ++++++++++++
 .../fixtures/release-history/drupal.9.8.2.xml |  36 +++
 .../drupal.9.8.2_unknown_status.xml           | 134 ++++++++++++
 tests/src/Kernel/CronUpdaterTest.php          |   2 +-
 tests/src/Kernel/ProjectInfoTest.php          |  95 ++++++++
 .../UpdateVersionValidatorTest.php            |  25 ++-
 tests/src/Unit/ProjectInfoTest.php            | 207 ------------------
 9 files changed, 480 insertions(+), 240 deletions(-)
 create mode 100644 tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml
 create mode 100644 tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
 create mode 100644 tests/src/Kernel/ProjectInfoTest.php
 delete mode 100644 tests/src/Unit/ProjectInfoTest.php

diff --git a/src/ProjectInfo.php b/src/ProjectInfo.php
index fce9a1ae6d..d8e5fadbea 100644
--- a/src/ProjectInfo.php
+++ b/src/ProjectInfo.php
@@ -3,8 +3,8 @@
 namespace Drupal\automatic_updates;
 
 use Composer\Semver\Comparator;
-use Composer\Semver\Semver;
 use Drupal\automatic_updates_9_3_shim\ProjectRelease;
+use Drupal\Core\Extension\ExtensionVersion;
 use Drupal\update\UpdateManagerInterface;
 
 /**
@@ -32,6 +32,37 @@ class ProjectInfo {
     $this->name = $name;
   }
 
+  /**
+   * Determines if a release can be installed.
+   *
+   * @param \Drupal\automatic_updates_9_3_shim\ProjectRelease $release
+   *   The project release.
+   * @param string[] $support_branches
+   *   The supported branches.
+   *
+   * @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.
+   */
+  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)) {
+      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()) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
   /**
    * Returns up-to-date project information.
    *
@@ -66,36 +97,34 @@ class ProjectInfo {
     if (!$project) {
       return NULL;
     }
-    $installed_version = $this->getInstalledVersion();
-    // If we were able to get available releases we should always have at least
-    // the current release stored.
-    if (empty($project['releases'])) {
-      throw new \RuntimeException('There was a problem getting update information. Try again later.');
+    $available_updates = update_get_available()[$this->name];
+    if ($available_updates['project_status'] !== 'published') {
+      throw new \RuntimeException("The project '{$this->name}' can not be updated because its status is " . $available_updates['project_status']);
     }
+
     // If we're already up-to-date, there's nothing else we need to do.
     if ($project['status'] === UpdateManagerInterface::CURRENT) {
       return [];
     }
-    elseif (empty($project['recommended'])) {
-      // If we don't know what to recommend they update to, time to freak out.
-      throw new \LogicException("The '{$this->name}' project is out of date, but the recommended version could not be determined.");
+
+    if (empty($available_updates['releases'])) {
+      // If project is not current we should always have at least one release.
+      throw new \RuntimeException('There was a problem getting update information. Try again later.');
     }
+    $installed_version = $this->getInstalledVersion();
+    $support_branches = explode(',', $available_updates['supported_branches']);
     $installable_releases = [];
-    if (Comparator::greaterThan($project['recommended'], $installed_version)) {
-      $release = ProjectRelease::createFromArray($project['releases'][$project['recommended']]);
-      $installable_releases[$release->getVersion()] = $release;
-    }
-    if (!empty($project['security updates'])) {
-      foreach ($project['security updates'] as $security_update) {
-        $release = ProjectRelease::createFromArray($security_update);
-        $version = $release->getVersion();
-        if (Comparator::greaterThan($version, $installed_version)) {
-          $installable_releases[$version] = $release;
-        }
+    foreach ($available_updates['releases'] as $release_info) {
+      $release = ProjectRelease::createFromArray($release_info);
+      $version = $release->getVersion();
+      if (Comparator::lessThanOrEqualTo($version, $installed_version)) {
+        break;
+      }
+      if ($this->isInstallable($release, $support_branches)) {
+        $installable_releases[$version] = $release;
       }
     }
-    $sorted_versions = Semver::rsort(array_keys($installable_releases));
-    return array_replace(array_flip($sorted_versions), $installable_releases);
+    return $installable_releases;
   }
 
   /**
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 dbcb825a86..c53813e51e 100644
--- a/tests/fixtures/release-history/drupal.9.8.1-security.xml
+++ b/tests/fixtures/release-history/drupal.9.8.1-security.xml
@@ -36,5 +36,18 @@
      <term><name>Release type</name><value>Insecure</value></term>
    </terms>
  </release>
+    <release>
+        <name>Drupal 9.8.0-alpha1</name>
+        <version>9.8.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-8-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-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>
+            <term><name>Release type</name><value>Insecure</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
new file mode 100644
index 0000000000..90df01ce03
--- /dev/null
+++ b/tests/fixtures/release-history/drupal.9.8.2-unsupported_unpublished.xml
@@ -0,0 +1,135 @@
+<?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.7.,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.2</name>
+    <version>9.8.2</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-2-release</release_link>
+    <download_link>http://example.com/drupal-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>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>
+    <term><name>Release type</name><value>Unsupported</value></term>
+  </terms>
+ </release>
+ <release>
+   <name>Drupal 9.8.0</name>
+   <version>9.8.0</version>
+   <status>unpublished</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>
+  <release>
+    <name>Drupal 9.8.0-alpha1</name>
+    <version>9.8.0-alpha1</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-0-alpha1-release</release_link>
+    <download_link>http://example.com/drupal-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>
+    <release>
+        <name>Drupal 9.7.1</name>
+        <version>9.7.1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-1-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.7.0</name>
+        <version>9.7.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-0-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.7.0-alpha1</name>
+        <version>9.7.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.6.1</name>
+        <version>9.6.1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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.6.0</name>
+        <version>9.6.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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>Drupal 9.6.0-alpha1</name>
+        <version>9.6.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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.2.xml b/tests/fixtures/release-history/drupal.9.8.2.xml
index 2de1077e5e..2c323fa9ea 100644
--- a/tests/fixtures/release-history/drupal.9.8.2.xml
+++ b/tests/fixtures/release-history/drupal.9.8.2.xml
@@ -94,5 +94,41 @@
             <term><name>Release type</name><value>Bug fixes</value></term>
         </terms>
     </release>
+    <release>
+        <name>Drupal 9.6.1</name>
+        <version>9.6.1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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.6.0</name>
+        <version>9.6.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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>Drupal 9.6.0-alpha1</name>
+        <version>9.6.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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.2_unknown_status.xml b/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
new file mode 100644
index 0000000000..c34b707727
--- /dev/null
+++ b/tests/fixtures/release-history/drupal.9.8.2_unknown_status.xml
@@ -0,0 +1,134 @@
+<?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.7.,9.8.</supported_branches>
+<project_status>any status besides 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.2</name>
+    <version>9.8.2</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-2-release</release_link>
+    <download_link>http://example.com/drupal-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>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>
+  <release>
+    <name>Drupal 9.8.0-alpha1</name>
+    <version>9.8.0-alpha1</version>
+    <status>published</status>
+    <release_link>http://example.com/drupal-9-8-0-alpha1-release</release_link>
+    <download_link>http://example.com/drupal-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>
+    <release>
+        <name>Drupal 9.7.1</name>
+        <version>9.7.1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-1-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.7.0</name>
+        <version>9.7.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-0-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.7.0-alpha1</name>
+        <version>9.7.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-7-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-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>Drupal 9.6.1</name>
+        <version>9.6.1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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.6.0</name>
+        <version>9.6.0</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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>Drupal 9.6.0-alpha1</name>
+        <version>9.6.0-alpha1</version>
+        <status>published</status>
+        <release_link>http://example.com/drupal-9-6-0-alpha1-release</release_link>
+        <download_link>http://example.com/drupal-9-6-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/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php
index e9a56e7d60..4c07670396 100644
--- a/tests/src/Kernel/CronUpdaterTest.php
+++ b/tests/src/Kernel/CronUpdaterTest.php
@@ -110,7 +110,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase {
       'enabled, normal release' => [
         CronUpdater::ALL,
         "$fixture_dir/drupal.9.8.2.xml",
-        FALSE,
+        TRUE,
       ],
       'enabled, security release' => [
         CronUpdater::ALL,
diff --git a/tests/src/Kernel/ProjectInfoTest.php b/tests/src/Kernel/ProjectInfoTest.php
new file mode 100644
index 0000000000..97186ada3a
--- /dev/null
+++ b/tests/src/Kernel/ProjectInfoTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel;
+
+use Drupal\automatic_updates\ProjectInfo;
+
+/**
+ * @coversDefaultClass \Drupal\automatic_updates\ProjectInfo
+ *
+ * @group automatic_updates
+ */
+class ProjectInfoTest extends AutomaticUpdatesKernelTestBase {
+
+  /**
+   * @covers ::getInstallableReleases()
+   *
+   * @param string $fixture
+   *   The fixture file name.
+   * @param string $installed_version
+   *   The installed version core version to set.
+   * @param string[] $expected_versions
+   *   The expected versions.
+   *
+   * @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');
+    $actual_releases = $project_info->getInstallableReleases();
+    // Assert that we returned the correct releases in the expected order.
+    $this->assertSame($expected_versions, array_keys($actual_releases));
+    // Assert that we version keys match the actual releases.
+    foreach ($actual_releases as $version => $release) {
+      $this->assertSame($version, $release->getVersion());
+    }
+  }
+
+  /**
+   * Data provider for testGetInstallableReleases().
+   *
+   * @return array[]
+   *   The test cases.
+   */
+  public function providerGetInstallableReleases() {
+    return [
+      'no updates' => [
+        'drupal.9.8.2.xml',
+        '9.8.2',
+        [],
+      ],
+      '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' => [
+        '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' => [
+        'drupal.9.8.2.xml',
+        '9.8.0-alpha1',
+        ['9.8.2', '9.8.1', '9.8.0'],
+      ],
+      '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' => [
+        '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'],
+      ],
+    ];
+  }
+
+  /**
+   * Tests a project with a status other than "published".
+   *
+   * @covers ::getInstallableReleases()
+   */
+  public function testNotPublishedProject() {
+    $this->setReleaseMetadata(__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();
+  }
+
+}
diff --git a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
index 720f27707b..ade86fb727 100644
--- a/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/UpdateVersionValidatorTest.php
@@ -180,22 +180,25 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
    *   Sets of arguments to pass to the test method.
    */
   public function providerCronUpdateTwoPatchReleasesAhead(): array {
-    $update_disallowed = ValidationResult::createError([
-      'Drupal cannot be automatically updated during cron from its current version, 9.8.0, to the recommended version, 9.8.2, because Automatic Updates only supports 1 patch version update during cron.',
-    ]);
-
     return [
       'disabled' => [
         CronUpdater::DISABLED,
         [],
+        0,
       ],
       'security only' => [
         CronUpdater::SECURITY,
-        [$update_disallowed],
+        [
+          ValidationResult::createError([
+            'Drupal cannot be automatically updated during cron from its current version, 9.8.0, to the recommended version, 9.8.1, because 9.8.1 is not a security release.',
+          ]),
+        ],
+        0,
       ],
       'all' => [
         CronUpdater::ALL,
-        [$update_disallowed],
+        [],
+        1,
       ],
     ];
   }
@@ -208,10 +211,12 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
    * @param \Drupal\package_manager\ValidationResult[] $expected_results
    *   The expected validation results, which should be logged as errors if the
    *   update is attempted during cron.
+   * @param int $expected_stage_times
+   *   The expected number of times the update should have been staged.
    *
    * @dataProvider providerCronUpdateTwoPatchReleasesAhead
    */
-  public function testCronUpdateTwoPatchReleasesAhead(string $cron_setting, array $expected_results): void {
+  public function testCronUpdateTwoPatchReleasesAhead(string $cron_setting, array $expected_results, int $expected_stage_times): void {
     $this->setCoreVersion('9.8.0');
     $this->config('automatic_updates.settings')
       ->set('cron', $cron_setting)
@@ -219,7 +224,7 @@ class UpdateVersionValidatorTest extends AutomaticUpdatesKernelTestBase {
 
     $this->assertCheckerResultsFromManager($expected_results, TRUE);
     $this->container->get('cron')->run();
-    $this->assertUpdateStagedTimes(0);
+    $this->assertUpdateStagedTimes($expected_stage_times);
   }
 
   /**
@@ -283,10 +288,10 @@ 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.2, because automatic updates from a dev version to any other version are not supported.',
+      '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.',
     ]);
     $different_major_version = ValidationResult::createError([
-      'Drupal cannot be automatically updated from its current version, 8.9.1, to the recommended version, 9.8.2, because automatic updates from one major version to another are not supported.',
+      '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.',
     ]);
 
     return [
diff --git a/tests/src/Unit/ProjectInfoTest.php b/tests/src/Unit/ProjectInfoTest.php
deleted file mode 100644
index 6578069d10..0000000000
--- a/tests/src/Unit/ProjectInfoTest.php
+++ /dev/null
@@ -1,207 +0,0 @@
-<?php
-
-namespace Drupal\Tests\automatic_updates\Unit;
-
-use Drupal\automatic_updates\ProjectInfo;
-use Drupal\automatic_updates_9_3_shim\ProjectRelease;
-use Drupal\Tests\UnitTestCase;
-use Drupal\update\UpdateManagerInterface;
-
-/**
- * @coversDefaultClass \Drupal\automatic_updates\ProjectInfo
- *
- * @group automatic_updates
- */
-class ProjectInfoTest extends UnitTestCase {
-
-  /**
-   * Creates release data for testing.
-   *
-   * @return string[][]
-   *   The release information.
-   */
-  private static function createTestReleases(): array {
-    $versions = ['8.2.5', '8.2.4', '8.2.3', '8.2.3-alpha'];
-    foreach ($versions as $version) {
-      $release_arrays[$version] = [
-        'status' => 'published',
-        'version' => $version,
-        'release_link' => "https://example.drupal.org/project/drupal/releases/$version",
-      ];
-    }
-    return $release_arrays;
-  }
-
-  /**
-   * Data provider for testGetInstallableReleases().
-   *
-   * @return array[][]
-   *   The test cases.
-   */
-  public function providerGetInstallableReleases(): array {
-    $release_arrays = static::createTestReleases();
-    foreach ($release_arrays as $version => $release_array) {
-      $release_objects[$version] = ProjectRelease::createFromArray($release_array);
-    }
-    return [
-      'current' => [
-        [
-          'status' => UpdateManagerInterface::CURRENT,
-          'existing_version' => '1.2.3',
-        ],
-        [],
-      ],
-      '1 release' => [
-        [
-          'status' => UpdateManagerInterface::NOT_CURRENT,
-          'existing_version' => '8.2.4',
-          'recommended' => '8.2.5',
-          'releases' => [
-            '8.2.5' => $release_arrays['8.2.5'],
-          ],
-        ],
-        [
-          '8.2.5' => $release_objects['8.2.5'],
-        ],
-      ],
-      '1 releases, also security' => [
-        [
-          'status' => UpdateManagerInterface::NOT_CURRENT,
-          'existing_version' => '8.2.4',
-          'recommended' => '8.2.5',
-          'releases' => [
-            '8.2.5' => $release_arrays['8.2.5'],
-          ],
-          'security updates' => [
-            $release_arrays['8.2.5'],
-          ],
-        ],
-        [
-          '8.2.5' => $release_objects['8.2.5'],
-        ],
-      ],
-      '1 release, other security' => [
-        [
-          'status' => UpdateManagerInterface::NOT_CURRENT,
-          'existing_version' => '8.2.2',
-          'recommended' => '8.2.5',
-          'releases' => [
-            '8.2.5' => $release_arrays['8.2.5'],
-          ],
-          'security updates' => [
-            // Set out of order security releases to ensure results are sorted.
-            $release_arrays['8.2.3-alpha'],
-            $release_arrays['8.2.3'],
-            $release_arrays['8.2.4'],
-          ],
-        ],
-        [
-          '8.2.5' => $release_objects['8.2.5'],
-          '8.2.4' => $release_objects['8.2.4'],
-          '8.2.3' => $release_objects['8.2.3'],
-          '8.2.3-alpha' => $release_objects['8.2.3-alpha'],
-        ],
-      ],
-      '1 releases, other security lower than current version' => [
-        [
-          'status' => UpdateManagerInterface::NOT_CURRENT,
-          'existing_version' => '8.2.3',
-          'recommended' => '8.2.5',
-          'releases' => [
-            '8.2.5' => $release_arrays['8.2.5'],
-          ],
-          'security updates' => [
-            // Set out of order security releases to ensure results are sorted.
-            $release_arrays['8.2.3-alpha'],
-            $release_arrays['8.2.3'],
-            $release_arrays['8.2.4'],
-          ],
-        ],
-        [
-          '8.2.5' => $release_objects['8.2.5'],
-          '8.2.4' => $release_objects['8.2.4'],
-        ],
-      ],
-      'no data' => [
-        NULL,
-        NULL,
-      ],
-    ];
-  }
-
-  /**
-   * @covers ::getInstallableReleases
-   *
-   * @param array|null $project_data
-   *   The project data to return from ::getProjectInfo().
-   * @param \Drupal\automatic_updates_9_3_shim\ProjectRelease[]|null $expected_releases
-   *   The expected releases.
-   *
-   * @dataProvider providerGetInstallableReleases
-   */
-  public function testGetInstallableReleases(?array $project_data, ?array $expected_releases): void {
-    $project_info = $this->getMockedProjectInfo($project_data);
-
-    // If data is returned, but there are no releases, we should get an
-    // exception.
-    if (isset($project_data, $expected_releases) && empty($project_data['releases'])) {
-      $this->expectExceptionMessage('There was a problem getting update information. Try again later.');
-    }
-    $this->assertEqualsCanonicalizing($expected_releases, $project_info->getInstallableReleases());
-  }
-
-  /**
-   * @covers ::getInstallableReleases
-   */
-  public function testInvalidProjectData(): void {
-    $release_arrays = static::createTestReleases();
-    $project_data = [
-      'status' => UpdateManagerInterface::NOT_CURRENT,
-      'existing_version' => '1.2.3',
-      'releases' => [
-        '8.2.5' => $release_arrays['8.2.5'],
-      ],
-      'security updates' => [
-        $release_arrays['8.2.4'],
-        $release_arrays['8.2.3'],
-        $release_arrays['8.2.3-alpha'],
-      ],
-    ];
-    $project_info = $this->getMockedProjectInfo($project_data);
-    $this->expectException('LogicException');
-    $this->expectExceptionMessage("The 'drupal' project is out of date, but the recommended version could not be determined.");
-    $project_info->getInstallableReleases();
-  }
-
-  /**
-   * @covers ::getInstalledVersion
-   */
-  public function testGetInstalledVersion(): void {
-    $project_info = $this->getMockedProjectInfo(['existing_version' => '1.2.3']);
-    $this->assertSame('1.2.3', $project_info->getInstalledVersion());
-    $project_info = $this->getMockedProjectInfo(NULL);
-    $this->assertSame(NULL, $project_info->getInstalledVersion());
-  }
-
-  /**
-   * Mocks a ProjectInfo object.
-   *
-   * @param array|null $project_data
-   *   The project info that should be returned by the mock's ::getProjectInfo()
-   *   method.
-   *
-   * @return \Drupal\automatic_updates\ProjectInfo
-   *   The mocked object.
-   */
-  private function getMockedProjectInfo(?array $project_data): ProjectInfo {
-    $project_info = $this->getMockBuilder(ProjectInfo::class)
-      ->setConstructorArgs(['drupal'])
-      ->onlyMethods(['getProjectInfo'])
-      ->getMock();
-    $project_info->expects($this->any())
-      ->method('getProjectInfo')
-      ->willReturn($project_data);
-    return $project_info;
-  }
-
-}
-- 
GitLab