From e1dac5fbf5f2fe8c3e7f90762bf2e7963d7bc1dc Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Thu, 26 May 2022 16:26:23 +0000
Subject: [PATCH] Issue #3281397 by phenaproxima: VersionPolicyValidator should
 throw an exception if it can't determine the target version of Drupal

---
 src/Validator/VersionPolicyValidator.php      | 27 ++++++--
 .../Kernel/AutomaticUpdatesKernelTestBase.php |  7 ++
 .../VersionPolicyValidatorTest.php            | 64 +++++++++++++++++++
 3 files changed, 91 insertions(+), 7 deletions(-)

diff --git a/src/Validator/VersionPolicyValidator.php b/src/Validator/VersionPolicyValidator.php
index 37c26e68d5..e20086c370 100644
--- a/src/Validator/VersionPolicyValidator.php
+++ b/src/Validator/VersionPolicyValidator.php
@@ -172,10 +172,15 @@ final class VersionPolicyValidator implements EventSubscriberInterface {
    *   The event object.
    *
    * @return string|null
-   *   The target version of Drupal core, or NULL if it could not be determined.
+   *   The target version of Drupal core, or NULL if it could not be determined
+   *   during a readiness check.
    *
-   * @todo Throw an exception in certain (maybe all) situations if we cannot
-   *   figure out the target version in https://www.drupal.org/i/3280180.
+   * @throws \LogicException
+   *   Thrown if the target version cannot be determined due to unexpected
+   *   conditions. This can happen if, during a stage life cycle event (i.e.,
+   *   NOT a readiness check), the event or updater does not have a list of
+   *   desired package versions, or the list of package versions does not
+   *   include any Drupal core packages.
    */
   private function getTargetVersion(StageEvent $event): ?string {
     $updater = $event->getStage();
@@ -187,16 +192,24 @@ final class VersionPolicyValidator implements EventSubscriberInterface {
       $package_versions = $updater->getPackageVersions()['production'];
     }
 
+    $unknown_target = new \LogicException('The target version of Drupal core could not be determined.');
+
     if ($package_versions) {
       $core_package_name = key($updater->getActiveComposer()->getCorePackages());
-      return $package_versions[$core_package_name];
+
+      if ($core_package_name && array_key_exists($core_package_name, $package_versions)) {
+        return $package_versions[$core_package_name];
+      }
+      else {
+        throw $unknown_target;
+      }
     }
     elseif ($event instanceof ReadinessCheckEvent) {
+      // It's okay if this returns NULL; it means there's nothing to update to.
       return $this->getTargetVersionFromAvailableReleases($updater);
     }
-    else {
-      return NULL;
-    }
+    // If we got here, something has gone very wrong.
+    throw $unknown_target;
   }
 
   /**
diff --git a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
index 3788fb4bb4..8900b78412 100644
--- a/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
+++ b/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
@@ -152,6 +152,13 @@ class TestUpdater extends Updater {
 
   use TestStageTrait;
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setMetadata(string $key, $data): void {
+    parent::setMetadata($key, $data);
+  }
+
 }
 
 /**
diff --git a/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php b/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
index 97bcf17920..d8cd1aa8ca 100644
--- a/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
+++ b/tests/src/Kernel/ReadinessValidation/VersionPolicyValidatorTest.php
@@ -3,6 +3,8 @@
 namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
 
 use Drupal\automatic_updates\CronUpdater;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Exception\StageException;
 use Drupal\package_manager\Exception\StageValidationException;
 use Drupal\package_manager\ValidationResult;
 use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
@@ -462,4 +464,66 @@ class VersionPolicyValidatorTest extends AutomaticUpdatesKernelTestBase {
     return ValidationResult::createError($messages, $summary);
   }
 
+  /**
+   * Tests that an error is raised if there are no stored package versions.
+   *
+   * This is a contrived situation that should never happen in real life, but
+   * just in case it does, we need to be sure that it's an error condition.
+   */
+  public function testNoStagedPackageVersions(): void {
+    // Remove the stored package versions from the updater's metadata.
+    $listener = function (PreCreateEvent $event): void {
+      /** @var \Drupal\Tests\automatic_updates\Kernel\TestUpdater $updater */
+      $updater = $event->getStage();
+      $updater->setMetadata('packages', [
+        'production' => [],
+      ]);
+    };
+    $this->assertTargetVersionNotDiscoverable($listener);
+  }
+
+  /**
+   * Tests that an error is raised if no core packages are installed.
+   *
+   * This is a contrived situation that should never happen in real life, but
+   * just in case it does, we need to be sure that it's an error condition.
+   */
+  public function testNoCorePackagesInstalled(): void {
+    // Clear the list of packages in the active directory's installed.json.
+    $listener = function (PreCreateEvent $event): void {
+      // We should have staged package versions.
+      /** @var \Drupal\automatic_updates\Updater $updater */
+      $updater = $event->getStage();
+      $this->assertNotEmpty($updater->getPackageVersions());
+
+      $active_dir = $this->container->get('package_manager.path_locator')
+        ->getProjectRoot();
+      $installed = $active_dir . '/vendor/composer/installed.json';
+      $this->assertFileIsWritable($installed);
+      file_put_contents($installed, '{"packages": []}');
+    };
+    $this->assertTargetVersionNotDiscoverable($listener);
+  }
+
+  /**
+   * Asserts that an error is raised if the target version of Drupal is unknown.
+   *
+   * @param \Closure $listener
+   *   A pre-create event listener to run before all validators. This should put
+   *   the virtual project and/or updater into a state which will cause
+   *   \Drupal\automatic_updates\Validator\VersionPolicyValidator::getTargetVersion()
+   *   to throw an exception because the target version of Drupal core is not
+   *   known.
+   */
+  private function assertTargetVersionNotDiscoverable(\Closure $listener): void {
+    $this->createTestProject();
+    $this->container->get('event_dispatcher')
+      ->addListener(PreCreateEvent::class, $listener, PHP_INT_MAX);
+
+    $this->expectException(StageException::class);
+    $this->expectExceptionMessage('The target version of Drupal core could not be determined.');
+    $this->container->get('automatic_updates.updater')
+      ->begin(['drupal' => '9.8.1']);
+  }
+
 }
-- 
GitLab