From 685ba03a3388a50f3603c564ec432eb4d2d36b41 Mon Sep 17 00:00:00 2001
From: Ted Bowman <41201-tedbow@users.noreply.drupalcode.org>
Date: Wed, 7 Dec 2022 18:01:09 +0000
Subject: [PATCH] Issue #3319045 by tedbow, kunal.sachdev, Wim Leers: Assert
 that all expected package manager events are fired during build tests

---
 ...package_manager_test_event_logger.info.yml |  6 ++
 ...age_manager_test_event_logger.services.yml |  5 ++
 .../EventSubscriber/EventLogSubscriber.php    | 49 +++++++++++++
 .../tests/src/Build/PackageUpdateTest.php     |  4 ++
 .../src/Build/TemplateProjectTestBase.php     | 70 +++++++++++++++++++
 tests/src/Build/CoreUpdateTest.php            | 40 +++++++++--
 6 files changed, 170 insertions(+), 4 deletions(-)
 create mode 100644 package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.info.yml
 create mode 100644 package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.services.yml
 create mode 100644 package_manager/tests/modules/package_manager_test_event_logger/src/EventSubscriber/EventLogSubscriber.php

diff --git a/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.info.yml b/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.info.yml
new file mode 100644
index 0000000000..0cf8dd1e8a
--- /dev/null
+++ b/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.info.yml
@@ -0,0 +1,6 @@
+name: 'Package Manager Test Event Logger'
+description: 'Provides an event subscriber to test logging during events in Package Manager'
+type: module
+package: Testing
+dependencies:
+  - automatic_updates:package_manager
diff --git a/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.services.yml b/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.services.yml
new file mode 100644
index 0000000000..408eba84e4
--- /dev/null
+++ b/package_manager/tests/modules/package_manager_test_event_logger/package_manager_test_event_logger.services.yml
@@ -0,0 +1,5 @@
+services:
+  package_manager_test_event_logger.subscriber:
+    class: Drupal\package_manager_test_event_logger\EventSubscriber\EventLogSubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/package_manager/tests/modules/package_manager_test_event_logger/src/EventSubscriber/EventLogSubscriber.php b/package_manager/tests/modules/package_manager_test_event_logger/src/EventSubscriber/EventLogSubscriber.php
new file mode 100644
index 0000000000..3d597f948f
--- /dev/null
+++ b/package_manager/tests/modules/package_manager_test_event_logger/src/EventSubscriber/EventLogSubscriber.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\package_manager_test_event_logger\EventSubscriber;
+
+use Drupal\package_manager\Event\PostApplyEvent;
+use Drupal\package_manager\Event\PostCreateEvent;
+use Drupal\package_manager\Event\PostDestroyEvent;
+use Drupal\package_manager\Event\PostRequireEvent;
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Event\PreDestroyEvent;
+use Drupal\package_manager\Event\PreRequireEvent;
+use Drupal\package_manager\Event\StageEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Defines an event subscriber to test logging during events in Package Manager.
+ */
+final class EventLogSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Logs all events in the stage life cycle.
+   *
+   * @param \Drupal\package_manager\Event\StageEvent $event
+   *   The event object.
+   */
+  public function logEventInfo(StageEvent $event): void {
+    \Drupal::logger('package_manager_test_event_logger')->info('package_manager_test_event_logger-start: Event: ' . get_class($event) . ', Stage instance of: ' . get_class($event->getStage()) . ':package_manager_test_event_logger-end');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      PreCreateEvent::class => ['logEventInfo'],
+      PostCreateEvent::class => ['logEventInfo'],
+      PreRequireEvent::class => ['logEventInfo'],
+      PostRequireEvent::class => ['logEventInfo'],
+      PreApplyEvent::class => ['logEventInfo'],
+      PostApplyEvent::class => ['logEventInfo'],
+      PreDestroyEvent::class => ['logEventInfo'],
+      PostDestroyEvent::class => ['logEventInfo'],
+    ];
+  }
+
+}
diff --git a/package_manager/tests/src/Build/PackageUpdateTest.php b/package_manager/tests/src/Build/PackageUpdateTest.php
index 44f88d60d6..fc400a2756 100644
--- a/package_manager/tests/src/Build/PackageUpdateTest.php
+++ b/package_manager/tests/src/Build/PackageUpdateTest.php
@@ -4,6 +4,8 @@ declare(strict_types = 1);
 
 namespace Drupal\Tests\package_manager\Build;
 
+use Drupal\package_manager\Stage;
+
 /**
  * Tests updating packages in a staging area.
  *
@@ -70,6 +72,8 @@ class PackageUpdateTest extends TemplateProjectTestBase {
     // created this file.
     // @see \Drupal\updated_module\PostApplySubscriber::postApply()
     $this->assertSame('Bravo!', $file_contents['bravo.txt']);
+
+    $this->assertExpectedStageEventsFired(Stage::class);
   }
 
 }
diff --git a/package_manager/tests/src/Build/TemplateProjectTestBase.php b/package_manager/tests/src/Build/TemplateProjectTestBase.php
index f657bc276b..188692753d 100644
--- a/package_manager/tests/src/Build/TemplateProjectTestBase.php
+++ b/package_manager/tests/src/Build/TemplateProjectTestBase.php
@@ -6,6 +6,14 @@ namespace Drupal\Tests\package_manager\Build;
 
 use Drupal\BuildTests\QuickStart\QuickStartTestBase;
 use Drupal\Composer\Composer;
+use Drupal\package_manager\Event\PostApplyEvent;
+use Drupal\package_manager\Event\PostCreateEvent;
+use Drupal\package_manager\Event\PostDestroyEvent;
+use Drupal\package_manager\Event\PostRequireEvent;
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Event\PreDestroyEvent;
+use Drupal\package_manager\Event\PreRequireEvent;
 use Drupal\Tests\package_manager\Traits\FixtureUtilityTrait;
 use Drupal\Tests\RandomGeneratorTrait;
 
@@ -293,6 +301,7 @@ END;
     // Install helpful modules.
     $this->installModules([
       'package_manager_test_api',
+      'package_manager_test_event_logger',
       'package_manager_test_release_history',
     ]);
   }
@@ -466,6 +475,67 @@ END;
     return $temp_directory;
   }
 
+  /**
+   * Asserts stage events were fired in a specific order.
+   *
+   * @param string $expected_stage_class
+   *   The expected stage class for the events.
+   * @param array|null $expected_events
+   *   (optional) The expected stage events that should have been fired in the
+   *   order in which they should have been fired. Events can be specified more
+   *   that once if they will be fired multiple times. If there are no events
+   *   specified all life cycle events from PreCreateEvent to PostDestroyEvent
+   *   will be asserted.
+   *
+   * @see \Drupal\package_manager_test_event_logger\EventSubscriber\EventLogSubscriber::logEventInfo
+   */
+  protected function assertExpectedStageEventsFired(string $expected_stage_class, ?array $expected_events = NULL): void {
+    if ($expected_events === NULL) {
+      $expected_events = [
+        PreCreateEvent::class,
+        PostCreateEvent::class,
+        PreRequireEvent::class,
+        PostRequireEvent::class,
+        PreApplyEvent::class,
+        PostApplyEvent::class,
+        PreDestroyEvent::class,
+        PostDestroyEvent::class,
+      ];
+    }
+    else {
+      // The view at 'admin/reports/dblog' currently only shows 50 entries but
+      // this view could be changed to show fewer and our test would not fail.
+      // We need to be sure we are seeing all entries, not just first page.
+      // Since we don't need to log anywhere near 50 entries use 25 to be overly
+      // cautious of the view changing.
+      // @todo Find a better solution than a view that could change to ensure
+      //   ensure these events have fired in https://drupal.org/i/3319768.
+      $this->assertLessThan(25, count($expected_events), 'More than 25 events may not appear on one page of the log view');
+    }
+    $assert_session = $this->getMink()->assertSession();
+    $page = $this->getMink()->getSession()->getPage();
+    $this->visit('/admin/reports/dblog');
+    $assert_session->statusCodeEquals(200);
+    $page->selectFieldOption('Type', 'package_manager_test_event_logger');
+    $page->pressButton('Filter');
+    $assert_session->statusCodeEquals(200);
+
+    // The log entries will not appear completely in the page text but they will
+    // appear in the title attribute of the links.
+    $links = $page->findAll('css', 'a[title^=package_manager_test_event_logger-start]');
+    $actual_titles = [];
+    // Loop through the links in reverse order because the most recent entries
+    // will be first.
+    foreach (array_reverse($links) as $link) {
+      $actual_titles[] = $link->getAttribute('title');
+    }
+    $expected_titles = [];
+    foreach ($expected_events as $event) {
+      $expected_titles[] = "package_manager_test_event_logger-start: Event: $event, Stage instance of: $expected_stage_class:package_manager_test_event_logger-end";
+    }
+    $this->assertSame($expected_titles, $actual_titles);
+  }
+
   // BEGIN: DELETE FROM CORE MERGE REQUEST.
 
   /**
diff --git a/tests/src/Build/CoreUpdateTest.php b/tests/src/Build/CoreUpdateTest.php
index 97be7f9b61..6c1cede81f 100644
--- a/tests/src/Build/CoreUpdateTest.php
+++ b/tests/src/Build/CoreUpdateTest.php
@@ -4,7 +4,17 @@ declare(strict_types = 1);
 
 namespace Drupal\Tests\automatic_updates\Build;
 
+use Drupal\automatic_updates\CronUpdater;
+use Drupal\automatic_updates\Updater;
 use Drupal\Composer\Composer;
+use Drupal\package_manager\Event\PostApplyEvent;
+use Drupal\package_manager\Event\PostCreateEvent;
+use Drupal\package_manager\Event\PostDestroyEvent;
+use Drupal\package_manager\Event\PostRequireEvent;
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\Event\PreCreateEvent;
+use Drupal\package_manager\Event\PreDestroyEvent;
+use Drupal\package_manager\Event\PreRequireEvent;
 use Drupal\Tests\WebAssert;
 
 /**
@@ -97,13 +107,32 @@ class CoreUpdateTest extends UpdateTestBase {
     $mink = $this->getMink();
     $session = $mink->getSession();
     $session->reload();
-    $mink->assertSession()->statusCodeEquals(200);
+    $update_status_code = $session->getStatusCode();
     $file_contents = $session->getPage()->getContent();
-    $file_contents = json_decode($file_contents, TRUE, 512, JSON_THROW_ON_ERROR);
-    $this->assertStringContainsString("const VERSION = '9.8.1';", $file_contents['web/core/lib/Drupal.php']);
+    $this->assertExpectedStageEventsFired(
+      Updater::class,
+      [
+        // ::assertReadOnlyFileSystemError attempts to start an update
+        // multiple times so 'PreCreateEvent' will be fired multiple times.
+        // @see \Drupal\Tests\automatic_updates\Build\CoreUpdateTest::assertReadOnlyFileSystemError()
+        PreCreateEvent::class,
+        PreCreateEvent::class,
+        PreCreateEvent::class,
+        PostCreateEvent::class,
+        PreRequireEvent::class,
+        PostRequireEvent::class,
+        PreApplyEvent::class,
+        PostApplyEvent::class,
+        PreDestroyEvent::class,
+        PostDestroyEvent::class,
+      ]
+    );
     // Even though the response is what we expect, assert the status code as
     // well, to be extra-certain that there was no kind of server-side error.
+    $this->assertSame(200, $update_status_code);
+    $file_contents = json_decode($file_contents, TRUE, 512, JSON_THROW_ON_ERROR);
 
+    $this->assertStringContainsString("const VERSION = '9.8.1';", $file_contents['web/core/lib/Drupal.php']);
     $this->assertUpdateSuccessful('9.8.1');
   }
 
@@ -134,6 +163,7 @@ class CoreUpdateTest extends UpdateTestBase {
     $page->pressButton('Continue');
     $this->waitForBatchJob();
     $assert_session->pageTextContains('Update complete!');
+    $this->assertExpectedStageEventsFired(Updater::class);
     $assert_session->pageTextNotContains('There is a security update available for your version of Drupal.');
     $this->assertUpdateSuccessful('9.8.1');
   }
@@ -164,7 +194,9 @@ class CoreUpdateTest extends UpdateTestBase {
 
     $assert_session->pageTextContains('Your site is ready for automatic updates.');
     $page->clickLink('Run cron');
-    $assert_session->statusCodeEquals(200);
+    $cron_run_status_code = $mink->getSession()->getStatusCode();
+    $this->assertExpectedStageEventsFired(CronUpdater::class);
+    $this->assertSame(200, $cron_run_status_code);
 
     // There should be log messages, but no errors or warnings should have been
     // logged by Automatic Updates.
-- 
GitLab