From dc42bf129b914b3312b8e2bd5f3649043c6ee3a6 Mon Sep 17 00:00:00 2001
From: "Theresa.Grannum" <theresa.grannum@3688861.no-reply.drupal.org>
Date: Wed, 30 Mar 2022 18:23:49 +0000
Subject: [PATCH] Issue #3267386 by Theresa.Grannum, tedbow: Automatic Updates
 Extensions: run readiness checks to determine if an update is possible

---
 .../automatic_updates_extensions.routing.yml  |  1 +
 .../src/Form/UpdaterForm.php                  | 65 ++++++++++++---
 .../tests/src/Functional/UpdaterFormTest.php  | 81 ++++++++++++++++---
 3 files changed, 128 insertions(+), 19 deletions(-)

diff --git a/automatic_updates_extensions/automatic_updates_extensions.routing.yml b/automatic_updates_extensions/automatic_updates_extensions.routing.yml
index db8214748b..ca8b733fb2 100644
--- a/automatic_updates_extensions/automatic_updates_extensions.routing.yml
+++ b/automatic_updates_extensions/automatic_updates_extensions.routing.yml
@@ -7,3 +7,4 @@ automatic_updates_extensions.update:
     _permission: 'administer software updates'
   options:
     _admin_route: TRUE
+    _automatic_updates_readiness_messages: skip
diff --git a/automatic_updates_extensions/src/Form/UpdaterForm.php b/automatic_updates_extensions/src/Form/UpdaterForm.php
index f7a3a31f85..a032b5773c 100644
--- a/automatic_updates_extensions/src/Form/UpdaterForm.php
+++ b/automatic_updates_extensions/src/Form/UpdaterForm.php
@@ -2,39 +2,71 @@
 
 namespace Drupal\automatic_updates_extensions\Form;
 
-use Drupal\automatic_updates\Updater;
+use Drupal\automatic_updates\Event\ReadinessCheckEvent;
+use Drupal\automatic_updates\Validation\ReadinessTrait;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\package_manager\Stage;
+use Drupal\system\SystemManager;
 use Drupal\update\UpdateManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * A form for selecting extension updates.
  */
 class UpdaterForm extends FormBase {
 
+  use ReadinessTrait;
+
   /**
    * The updater service.
    *
    * @var \Drupal\automatic_updates\Updater
    */
-  private $updater;
+  private $stage;
+
+  /**
+   * The event dispatcher service.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  private $eventDispatcher;
 
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    return new static($container->get('automatic_updates.updater'));
+    // @todo Create a our servcie that extends stage instead of creating a
+    //   generic stage class here.
+    $stage = new Stage(
+      $container->get('config.factory'),
+      $container->get('package_manager.path_locator'),
+      $container->get('package_manager.beginner'),
+      $container->get('package_manager.stager'),
+      $container->get('package_manager.committer'),
+      $container->get('file_system'),
+      $container->get('event_dispatcher'),
+      $container->get('tempstore.shared'),
+      $container->get('datetime.time')
+    );
+    return new static(
+      $stage,
+      $container->get('event_dispatcher'),
+    );
   }
 
   /**
    * Constructs a new UpdaterForm object.
    *
-   * @param \Drupal\automatic_updates\Updater $updater
-   *   The extension updater service.
+   * @param \Drupal\package_manager\Stage $stage
+   *   The stage service.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+   *   The extension event dispatcher service.
    */
-  public function __construct(Updater $updater) {
-    $this->updater = $updater;
+  public function __construct(Stage $stage, EventDispatcherInterface $event_dispatcher) {
+    $this->stage = $stage;
+    $this->eventDispatcher = $event_dispatcher;
   }
 
   /**
@@ -87,9 +119,22 @@ class UpdaterForm extends FormBase {
       '#empty' => $this->t('There are no available updates.'),
       '#attributes' => ['class' => ['update-recommended']],
     ];
-    if ($update_projects) {
+
+    if ($form_state->getUserInput()) {
+      $results = [];
+    }
+    else {
+      $event = new ReadinessCheckEvent($this->stage);
+      $this->eventDispatcher->dispatch($event);
+      $results = $event->getResults();
+    }
+    $this->displayResults($results, $this->messenger());
+    $security_level = $this->getOverallSeverity($results);
+
+    if ($update_projects && $security_level !== SystemManager::REQUIREMENT_ERROR) {
       $form['actions'] = $this->actions($form_state);
     }
+
     return $form;
   }
 
@@ -104,7 +149,7 @@ class UpdaterForm extends FormBase {
    */
   protected function actions(FormStateInterface $form_state): array {
     $actions = ['#type' => 'actions'];
-    if (!$this->updater->isAvailable()) {
+    if (!$this->stage->isAvailable()) {
       // If the form has been submitted do not display this error message
       // because ::deleteExistingUpdate() may run on submit. The message will
       // still be displayed on form build if needed.
@@ -130,7 +175,7 @@ class UpdaterForm extends FormBase {
    * Submit function to delete an existing in-progress update.
    */
   public function deleteExistingUpdate(): void {
-    $this->updater->destroy(TRUE);
+    $this->stage->destroy(TRUE);
     $this->messenger()->addMessage($this->t("Staged update deleted"));
   }
 
diff --git a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
index b4a1ff5055..73b062bc80 100644
--- a/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
+++ b/automatic_updates_extensions/tests/src/Functional/UpdaterFormTest.php
@@ -2,7 +2,11 @@
 
 namespace Drupal\Tests\automatic_updates_extensions\Functional;
 
+use Drupal\automatic_updates\Event\ReadinessCheckEvent;
+use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
+use Drupal\package_manager\ValidationResult;
 use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase;
+use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
 
 /**
  * Tests updating using the form.
@@ -11,6 +15,8 @@ use Drupal\Tests\automatic_updates\Functional\AutomaticUpdatesFunctionalTestBase
  */
 class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
 
+  use ValidationTestTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -35,7 +41,7 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp():void {
+  protected function setUp(): void {
     parent::setUp();
     $this->setReleaseMetadata(__DIR__ . '/../../../../tests/fixtures/release-history/semver_test.1.1.xml');
   }
@@ -63,13 +69,26 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
         'hidden' => FALSE,
       ],
     ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    $this->config('update_test.settings')
+      ->set('system_info', $system_info)
+      ->save();
+  }
+
+  /**
+   * Asserts the table shows the updates.
+   */
+  private function assertTableShowsUpdates() {
+    $assert = $this->assertSession();
+    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(2)', 'Semver Test');
+    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(3)', '8.1.0');
+    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(4)', '8.1.1');
+    $assert->elementsCount('css', '.update-recommended tbody tr', 1);
   }
 
   /**
    * Tests the form when a module requires an update.
    */
-  public function testHasUpdate():void {
+  public function testHasUpdate(): void {
     $assert = $this->assertSession();
     $user = $this->createUser(['administer site configuration']);
     $this->drupalLogin($user);
@@ -81,20 +100,18 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     $user = $this->createUser(['administer software updates']);
     $this->drupalLogin($user);
     $this->drupalGet('/admin/automatic-update-extensions');
+    $this->assertTableShowsUpdates();
     $assert->pageTextContains('Automatic Updates Form');
-    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(2)', 'Semver Test');
-    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(3)', '8.1.0');
-    $assert->elementTextContains('css', '.update-recommended td:nth-of-type(4)', '8.1.1');
-    $assert->elementsCount('css', '.update-recommended tbody tr', 1);
     $assert->buttonExists('Update');
   }
 
   /**
    * Tests the form when there are no available updates.
    */
-  public function testNoUpdate():void {
+  public function testNoUpdate(): void {
     $assert = $this->assertSession();
-    $user = $this->createUser(['administer site configuration',
+    $user = $this->createUser([
+      'administer site configuration',
       'administer software updates',
     ]);
     $this->drupalLogin($user);
@@ -105,4 +122,50 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase {
     $assert->buttonNotExists('Update');
   }
 
+  /**
+   * Test the form for errors.
+   */
+  public function testErrors(): void {
+    $assert = $this->assertSession();
+    $user = $this->createUser([
+      'administer site configuration',
+      'administer software updates',
+    ]);
+    $this->drupalLogin($user);
+    $this->setProjectInstalledVersion('8.1.0');
+    $this->checkForUpdates();
+    $this->drupalGet('/admin/automatic-update-extensions');
+    $this->assertTableShowsUpdates();
+    $message = t("You've not experienced Shakespeare until you have read him in the original Klingon.");
+    $error = ValidationResult::createError([$message]);
+    TestSubscriber1::setTestResult([$error], ReadinessCheckEvent::class);
+    $this->getSession()->reload();
+    $assert->pageTextContains($message);
+    $assert->pageTextContains(static::$errorsExplanation);
+    $assert->pageTextNotContains(static::$warningsExplanation);
+    $assert->buttonNotExists('Update');
+  }
+
+  /**
+   * Test the form for warning messages.
+   */
+  public function testWarnings(): void {
+    $assert = $this->assertSession();
+    $user = $this->createUser([
+      'administer site configuration',
+      'administer software updates',
+    ]);
+    $this->drupalLogin($user);
+    $this->setProjectInstalledVersion('8.1.0');
+    $this->checkForUpdates();
+    $message = t("Warning! Updating this module may cause an error.");
+    $warning = ValidationResult::createWarning([$message]);
+    TestSubscriber1::setTestResult([$warning], ReadinessCheckEvent::class);
+    $this->drupalGet('/admin/automatic-update-extensions');
+    $this->assertTableShowsUpdates();
+    $assert->pageTextContains(static::$warningsExplanation);
+    $assert->pageTextNotContains(static::$errorsExplanation);
+    $assert->buttonExists('Update');
+  }
+
 }
-- 
GitLab