From 94803912f6691b249fb526e3a0210ae74f8200a9 Mon Sep 17 00:00:00 2001
From: tedbow <tedbow@240860.no-reply.drupal.org>
Date: Wed, 13 Oct 2021 14:32:39 +0000
Subject: [PATCH] Issue #3241105 by phenaproxima, tedbow: Ensure core package
 is validated in readiness check and pre-start

---
 automatic_updates.services.yml                |  4 ++
 package_manager/src/ComposerUtility.php       | 12 ----
 src/Validator/CoreComposerValidator.php       | 49 +++++++++++++++
 .../no_core_requirements/composer.json        | 15 +++++
 .../CoreComposerValidatorTest.php             | 60 +++++++++++++++++++
 5 files changed, 128 insertions(+), 12 deletions(-)
 create mode 100644 src/Validator/CoreComposerValidator.php
 create mode 100644 tests/fixtures/project_staged_validation/no_core_requirements/composer.json
 create mode 100644 tests/src/Kernel/ReadinessValidation/CoreComposerValidatorTest.php

diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index 51d5714ee9..d908294df4 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -74,6 +74,10 @@ services:
       - '%app.root%'
     tags:
       - { name: event_subscriber }
+  automatic_updates.validator.core_composer:
+    class: Drupal\automatic_updates\Validator\CoreComposerValidator
+    tags:
+      - { name: event_subscriber }
   automatic_updates.path_locator:
     class: Drupal\automatic_updates\PathLocator
     arguments:
diff --git a/package_manager/src/ComposerUtility.php b/package_manager/src/ComposerUtility.php
index 9404cbc11f..121d067647 100644
--- a/package_manager/src/ComposerUtility.php
+++ b/package_manager/src/ComposerUtility.php
@@ -99,18 +99,6 @@ class ComposerUtility {
    */
   public function getCorePackageNames(): array {
     $requirements = array_keys($this->composer->getPackage()->getRequires());
-
-    // Ensure that either drupal/core or drupal/core-recommended are required.
-    // If neither is, then core cannot be updated, which we consider an error
-    // condition.
-    // @todo Move this check to an update validator as part of
-    //   https://www.drupal.org/project/automatic_updates/issues/3241105
-    $core_requirements = array_intersect(['drupal/core', 'drupal/core-recommended'], $requirements);
-    if (empty($core_requirements)) {
-      $file = $this->composer->getConfig()->getConfigSource()->getName();
-      throw new \LogicException("Drupal core does not appear to be required in $file.");
-    }
-
     return array_intersect(static::getCorePackageList(), $requirements);
   }
 
diff --git a/src/Validator/CoreComposerValidator.php b/src/Validator/CoreComposerValidator.php
new file mode 100644
index 0000000000..928da62046
--- /dev/null
+++ b/src/Validator/CoreComposerValidator.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\automatic_updates\Validator;
+
+use Drupal\automatic_updates\AutomaticUpdatesEvents;
+use Drupal\automatic_updates\Event\ReadinessCheckEvent;
+use Drupal\automatic_updates\Validation\ValidationResult;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Validates the Drupal core requirements defined in composer.json.
+ */
+class CoreComposerValidator implements EventSubscriberInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * Validates the Drupal core requirements in composer.json.
+   *
+   * @param \Drupal\automatic_updates\Event\ReadinessCheckEvent $event
+   *   The event object.
+   */
+  public function checkCoreRequirements(ReadinessCheckEvent $event): void {
+    // Ensure that either drupal/core or drupal/core-recommended is required.
+    // If neither is, then core cannot be updated, which we consider an error
+    // condition.
+    $core_requirements = array_intersect(
+      $event->getActiveComposer()->getCorePackageNames(),
+      ['drupal/core', 'drupal/core-recommended']
+    );
+    if (empty($core_requirements)) {
+      $error = ValidationResult::createError([
+        $this->t('Drupal core does not appear to be required in the project-level composer.json.'),
+      ]);
+      $event->addValidationResult($error);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [
+      AutomaticUpdatesEvents::READINESS_CHECK => ['checkCoreRequirements', 1000],
+    ];
+  }
+
+}
diff --git a/tests/fixtures/project_staged_validation/no_core_requirements/composer.json b/tests/fixtures/project_staged_validation/no_core_requirements/composer.json
new file mode 100644
index 0000000000..157b54bc9a
--- /dev/null
+++ b/tests/fixtures/project_staged_validation/no_core_requirements/composer.json
@@ -0,0 +1,15 @@
+{
+    "require": {
+        "drupal/core-composer-scaffold": "*",
+        "drupal/core-vendor-hardening": "*",
+        "drupal/core-project-message": "*",
+        "drupal/core-dev": "*",
+        "drupal/core-dev-pinned": "*"
+    },
+    "extra": {
+        "_comment": [
+            "This is an example composer.json that does not require Drupal core.",
+            "@see \\Drupal\\Tests\\automatic_updates\\Kernel\\ReadinessValidation\\RequirementsValidatorTest"
+        ]
+    }
+}
diff --git a/tests/src/Kernel/ReadinessValidation/CoreComposerValidatorTest.php b/tests/src/Kernel/ReadinessValidation/CoreComposerValidatorTest.php
new file mode 100644
index 0000000000..bb1ad91372
--- /dev/null
+++ b/tests/src/Kernel/ReadinessValidation/CoreComposerValidatorTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
+
+use Drupal\automatic_updates\PathLocator;
+use Drupal\automatic_updates\Validation\ValidationResult;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
+use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
+
+/**
+ * @covers \Drupal\automatic_updates\Validator\CoreComposerValidator
+ *
+ * @group automatic_updates
+ */
+class CoreComposerValidatorTest extends AutomaticUpdatesKernelTestBase {
+
+  use ValidationTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'automatic_updates',
+    'package_manager',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    parent::register($container);
+    // Disable validators which interfere with the validator under test.
+    $container->removeDefinition('automatic_updates.disk_space_validator');
+  }
+
+  /**
+   * Tests that an error is raised if core is not required in composer.json.
+   */
+  public function testCoreNotRequired(): void {
+    $this->installConfig('update');
+    $this->setCoreVersion('9.8.0');
+    $this->setReleaseMetadata(__DIR__ . '/../../../fixtures/release-history/drupal.9.8.1.xml');
+
+    // Point to a valid composer.json with no requirements.
+    $locator = $this->prophesize(PathLocator::class);
+    $locator->getActiveDirectory()->willReturn(__DIR__ . '/../../../fixtures/project_staged_validation/no_core_requirements');
+    $this->container->set('automatic_updates.path_locator', $locator->reveal());
+
+    $results = $this->container->get('automatic_updates.readiness_validation_manager')
+      ->run()
+      ->getResults();
+
+    $error = ValidationResult::createError([
+      'Drupal core does not appear to be required in the project-level composer.json.',
+    ]);
+    $this->assertValidationResultsEqual([$error], $results);
+  }
+
+}
-- 
GitLab