From 0b5234db4ddbf1d5fa12c42c99e0d2ce64c79d9e Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Wed, 27 Oct 2021 15:58:44 +0000
Subject: [PATCH] Issue #3246203 by phenaproxima, tedbow: Replace
 PreCommitEvent and PostCommitEvent with PreApplyEvent and PostApplyEvent

---
 package_manager/src/Stage.php                 |  22 ++
 src/Event/PostCommitEvent.php                 |  10 -
 src/Event/PreCommitEvent.php                  |  47 -----
 src/Event/UpdateRefreshSubscriber.php         |   3 +-
 src/Updater.php                               |  10 -
 src/Validator/StagedProjectsValidator.php     |  13 +-
 .../src/ReadinessChecker/TestChecker1.php     |  16 +-
 .../StagedProjectsValidatorTest.php           | 148 ++++++++++++++
 .../src/Unit/StagedProjectsValidatorTest.php  | 191 ------------------
 9 files changed, 187 insertions(+), 273 deletions(-)
 delete mode 100644 src/Event/PostCommitEvent.php
 delete mode 100644 src/Event/PreCommitEvent.php
 create mode 100644 tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
 delete mode 100644 tests/src/Unit/StagedProjectsValidatorTest.php

diff --git a/package_manager/src/Stage.php b/package_manager/src/Stage.php
index aef260e920..2a27734761 100644
--- a/package_manager/src/Stage.php
+++ b/package_manager/src/Stage.php
@@ -165,4 +165,26 @@ class Stage {
     }
   }
 
+  /**
+   * Returns a Composer utility object for the active directory.
+   *
+   * @return \Drupal\package_manager\ComposerUtility
+   *   The Composer utility object.
+   */
+  public function getActiveComposer(): ComposerUtility {
+    $dir = $this->pathLocator->getActiveDirectory();
+    return ComposerUtility::createForDirectory($dir);
+  }
+
+  /**
+   * Returns a Composer utility object for the stage directory.
+   *
+   * @return \Drupal\package_manager\ComposerUtility
+   *   The Composer utility object.
+   */
+  public function getStageComposer(): ComposerUtility {
+    $dir = $this->pathLocator->getStageDirectory();
+    return ComposerUtility::createForDirectory($dir);
+  }
+
 }
diff --git a/src/Event/PostCommitEvent.php b/src/Event/PostCommitEvent.php
deleted file mode 100644
index e4e166dff8..0000000000
--- a/src/Event/PostCommitEvent.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace Drupal\automatic_updates\Event;
-
-/**
- * Event fired when a staged update has been committed.
- */
-class PostCommitEvent extends UpdateEvent {
-
-}
diff --git a/src/Event/PreCommitEvent.php b/src/Event/PreCommitEvent.php
deleted file mode 100644
index 010faca493..0000000000
--- a/src/Event/PreCommitEvent.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-namespace Drupal\automatic_updates\Event;
-
-use Drupal\package_manager\ComposerUtility;
-use Drupal\package_manager\Event\ExcludedPathsTrait;
-
-/**
- * Event fired before staged changes are copied into the active site.
- *
- * Validation results added by subscribers are not cached.
- */
-class PreCommitEvent extends UpdateEvent {
-
-  use ExcludedPathsTrait;
-
-  /**
-   * The Composer utility object for the stage directory.
-   *
-   * @var \Drupal\package_manager\ComposerUtility
-   */
-  protected $stageComposer;
-
-  /**
-   * Constructs a new PreCommitEvent object.
-   *
-   * @param \Drupal\package_manager\ComposerUtility $active_composer
-   *   A Composer utility object for the active directory.
-   * @param \Drupal\package_manager\ComposerUtility $stage_composer
-   *   A Composer utility object for the stage directory.
-   */
-  public function __construct(ComposerUtility $active_composer, ComposerUtility $stage_composer) {
-    parent::__construct($active_composer);
-    $this->stageComposer = $stage_composer;
-  }
-
-  /**
-   * Returns a Composer utility object for the stage directory.
-   *
-   * @return \Drupal\package_manager\ComposerUtility
-   *   The Composer utility object for the stage directory.
-   */
-  public function getStageComposer(): ComposerUtility {
-    return $this->stageComposer;
-  }
-
-}
diff --git a/src/Event/UpdateRefreshSubscriber.php b/src/Event/UpdateRefreshSubscriber.php
index fd68151a59..a336783a2d 100644
--- a/src/Event/UpdateRefreshSubscriber.php
+++ b/src/Event/UpdateRefreshSubscriber.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\automatic_updates\Event;
 
+use Drupal\package_manager\Event\PostApplyEvent;
 use Drupal\update\UpdateManagerInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
@@ -40,7 +41,7 @@ class UpdateRefreshSubscriber implements EventSubscriberInterface {
    */
   public static function getSubscribedEvents() {
     return [
-      PostCommitEvent::class => ['clearData', 1000],
+      PostApplyEvent::class => ['clearData', 1000],
     ];
   }
 
diff --git a/src/Updater.php b/src/Updater.php
index 3ca468e408..b5d01335a3 100644
--- a/src/Updater.php
+++ b/src/Updater.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\automatic_updates;
 
-use Drupal\automatic_updates\Event\PostCommitEvent;
-use Drupal\automatic_updates\Event\PreCommitEvent;
 use Drupal\automatic_updates\Event\PreStartEvent;
 use Drupal\automatic_updates\Event\UpdateEvent;
 use Drupal\automatic_updates\Exception\UpdateException;
@@ -134,15 +132,7 @@ class Updater {
    * Commits the current update.
    */
   public function commit(): void {
-    $active_dir = $this->pathLocator->getActiveDirectory();
-    $active_composer = ComposerUtility::createForDirectory($active_dir);
-
-    $stage_dir = $this->pathLocator->getStageDirectory();
-    $stage_composer = ComposerUtility::createForDirectory($stage_dir);
-
-    $this->dispatchUpdateEvent(new PreCommitEvent($active_composer, $stage_composer));
     $this->stage->apply();
-    $this->dispatchUpdateEvent(new PostCommitEvent($active_composer));
   }
 
   /**
diff --git a/src/Validator/StagedProjectsValidator.php b/src/Validator/StagedProjectsValidator.php
index 1ae41cce3c..18bccc02a5 100644
--- a/src/Validator/StagedProjectsValidator.php
+++ b/src/Validator/StagedProjectsValidator.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\automatic_updates\Validator;
 
-use Drupal\automatic_updates\Event\PreCommitEvent;
+use Drupal\package_manager\Event\PreApplyEvent;
 use Drupal\package_manager\ValidationResult;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -28,13 +28,14 @@ final class StagedProjectsValidator implements EventSubscriberInterface {
   /**
    * Validates the staged packages.
    *
-   * @param \Drupal\automatic_updates\Event\PreCommitEvent $event
+   * @param \Drupal\package_manager\Event\PreApplyEvent $event
    *   The event object.
    */
-  public function validateStagedProjects(PreCommitEvent $event): void {
+  public function validateStagedProjects(PreApplyEvent $event): void {
+    $stage = $event->getStage();
     try {
-      $active_packages = $event->getActiveComposer()->getDrupalExtensionPackages();
-      $staged_packages = $event->getStageComposer()->getDrupalExtensionPackages();
+      $active_packages = $stage->getActiveComposer()->getDrupalExtensionPackages();
+      $staged_packages = $stage->getStageComposer()->getDrupalExtensionPackages();
     }
     catch (\Throwable $e) {
       $result = ValidationResult::createError([
@@ -123,7 +124,7 @@ final class StagedProjectsValidator implements EventSubscriberInterface {
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    $events[PreCommitEvent::class][] = ['validateStagedProjects'];
+    $events[PreApplyEvent::class][] = ['validateStagedProjects'];
     return $events;
   }
 
diff --git a/tests/modules/automatic_updates_test/src/ReadinessChecker/TestChecker1.php b/tests/modules/automatic_updates_test/src/ReadinessChecker/TestChecker1.php
index 3fc71760d3..3a7efd8032 100644
--- a/tests/modules/automatic_updates_test/src/ReadinessChecker/TestChecker1.php
+++ b/tests/modules/automatic_updates_test/src/ReadinessChecker/TestChecker1.php
@@ -2,11 +2,11 @@
 
 namespace Drupal\automatic_updates_test\ReadinessChecker;
 
-use Drupal\automatic_updates\Event\PreCommitEvent;
 use Drupal\automatic_updates\Event\PreStartEvent;
 use Drupal\automatic_updates\Event\ReadinessCheckEvent;
 use Drupal\automatic_updates\Event\UpdateEvent;
 use Drupal\Core\State\StateInterface;
+use Drupal\package_manager\Event\PreApplyEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -63,12 +63,12 @@ class TestChecker1 implements EventSubscriberInterface {
   /**
    * Adds test result to an update event from a state setting.
    *
-   * @param \Drupal\automatic_updates\Event\UpdateEvent $event
+   * @param object $event
    *   The update event.
    * @param string $state_key
    *   The state key.
    */
-  protected function addResults(UpdateEvent $event, string $state_key): void {
+  protected function addResults(object $event, string $state_key): void {
     $results = $this->state->get($state_key, []);
     if ($results instanceof \Throwable) {
       throw $results;
@@ -89,13 +89,13 @@ class TestChecker1 implements EventSubscriberInterface {
   }
 
   /**
-   * Adds test results for the pre-commit event.
+   * Adds test results for the pre-apply event.
    *
-   * @param \Drupal\automatic_updates\Event\UpdateEvent $event
+   * @param \Drupal\package_manager\Event\PreApplyEvent $event
    *   The update event.
    */
-  public function runPreCommitChecks(UpdateEvent $event): void {
-    $this->addResults($event, static::STATE_KEY . "." . PreCommitEvent::class);
+  public function runPreApplyChecks(PreApplyEvent $event): void {
+    $this->addResults($event, static::STATE_KEY . "." . PreApplyEvent::class);
   }
 
   /**
@@ -115,7 +115,7 @@ class TestChecker1 implements EventSubscriberInterface {
     $priority = defined('AUTOMATIC_UPDATES_TEST_SET_PRIORITY') ? AUTOMATIC_UPDATES_TEST_SET_PRIORITY : 5;
     $events[ReadinessCheckEvent::class][] = ['runPreChecks', $priority];
     $events[PreStartEvent::class][] = ['runStartChecks', $priority];
-    $events[PreCommitEvent::class][] = ['runPreCommitChecks', $priority];
+    $events[PreApplyEvent::class][] = ['runPreApplyChecks', $priority];
     return $events;
   }
 
diff --git a/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
new file mode 100644
index 0000000000..b552398246
--- /dev/null
+++ b/tests/src/Kernel/ReadinessValidation/StagedProjectsValidatorTest.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Drupal\Tests\automatic_updates\Kernel\ReadinessValidation;
+
+use Drupal\package_manager\Event\PreApplyEvent;
+use Drupal\package_manager\PathLocator;
+use Drupal\Tests\automatic_updates\Kernel\AutomaticUpdatesKernelTestBase;
+
+/**
+ * @covers \Drupal\automatic_updates\Validator\StagedProjectsValidator
+ *
+ * @group automatic_updates
+ */
+class StagedProjectsValidatorTest extends AutomaticUpdatesKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'automatic_updates',
+    'package_manager',
+  ];
+
+  /**
+   * Runs the validator under test against an arbitrary pair of directories.
+   *
+   * @param string $active_dir
+   *   The active directory to validate.
+   * @param string $stage_dir
+   *   The stage directory to validate.
+   *
+   * @return \Drupal\package_manager\ValidationResult[]
+   *   The validation results.
+   */
+  private function validate(string $active_dir, string $stage_dir): array {
+    $locator = $this->prophesize(PathLocator::class);
+    $locator->getActiveDirectory()->willReturn($active_dir);
+    $locator->getStageDirectory()->willReturn($stage_dir);
+    $this->container->set('package_manager.path_locator', $locator->reveal());
+
+    $event = new PreApplyEvent(
+      $this->container->get('package_manager.stage')
+    );
+
+    $this->container->get('event_dispatcher')->dispatch($event);
+    return $event->getResults();
+  }
+
+  /**
+   * Tests that if an exception is thrown, the event will absorb it.
+   */
+  public function testEventConsumesExceptionResults(): void {
+    $results = $this->validate('/fake/active/dir', '/fake/stage/dir');
+    $this->assertCount(1, $results);
+    $messages = reset($results)->getMessages();
+    $this->assertCount(1, $messages);
+    $this->assertStringContainsString('Composer could not find the config file: /fake/active/dir/composer.json', (string) reset($messages));
+  }
+
+  /**
+   * Tests validations errors.
+   *
+   * @param string $fixtures_dir
+   *   The fixtures directory that provides the active and staged composer.lock
+   *   files.
+   * @param string $expected_summary
+   *   The expected error summary.
+   * @param string[] $expected_messages
+   *   The expected error messages.
+   *
+   * @dataProvider providerErrors
+   */
+  public function testErrors(string $fixtures_dir, string $expected_summary, array $expected_messages): void {
+    $this->assertNotEmpty($fixtures_dir);
+    $this->assertDirectoryExists($fixtures_dir);
+
+    $results = $this->validate("$fixtures_dir/active", "$fixtures_dir/staged");
+    $this->assertCount(1, $results);
+    $result = array_pop($results);
+    $this->assertSame($expected_summary, (string) $result->getSummary());
+    $actual_messages = $result->getMessages();
+    $this->assertCount(count($expected_messages), $actual_messages);
+    foreach ($expected_messages as $message) {
+      $actual_message = array_shift($actual_messages);
+      $this->assertSame($message, (string) $actual_message);
+    }
+  }
+
+  /**
+   * Data provider for testErrors().
+   *
+   * @return \string[][]
+   *   Test cases for testErrors().
+   */
+  public function providerErrors(): array {
+    $fixtures_folder = realpath(__DIR__ . '/../../../fixtures/project_staged_validation');
+    return [
+      'new_project_added' => [
+        "$fixtures_folder/new_project_added",
+        'The update cannot proceed because the following Drupal projects were installed during the update.',
+        [
+          "module 'drupal/test_module2' installed.",
+          "custom module 'drupal/dev-test_module2' installed.",
+        ],
+      ],
+      'project_removed' => [
+        "$fixtures_folder/project_removed",
+        'The update cannot proceed because the following Drupal projects were removed during the update.',
+        [
+          "theme 'drupal/test_theme' removed.",
+          "custom theme 'drupal/dev-test_theme' removed.",
+        ],
+      ],
+      'version_changed' => [
+        "$fixtures_folder/version_changed",
+        'The update cannot proceed because the following Drupal projects were unexpectedly updated. Only Drupal Core updates are currently supported.',
+        [
+          "module 'drupal/test_module' from 1.3.0 to  1.3.1.",
+          "module 'drupal/dev-test_module' from 1.3.0 to  1.3.1.",
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Tests validation when there are no errors.
+   */
+  public function testNoErrors(): void {
+    $fixtures_dir = realpath(__DIR__ . '/../../../fixtures/project_staged_validation/no_errors');
+    $results = $this->validate("$fixtures_dir/active", "$fixtures_dir/staged");
+    $this->assertIsArray($results);
+    $this->assertEmpty($results);
+  }
+
+  /**
+   * Tests validation when a composer.lock file is not found.
+   */
+  public function testNoLockFile(): void {
+    $fixtures_dir = realpath(__DIR__ . '/../../../fixtures/project_staged_validation/no_errors');
+
+    $results = $this->validate("$fixtures_dir/active", $fixtures_dir);
+    $this->assertCount(1, $results);
+    $result = array_pop($results);
+    $this->assertSame("No lockfile found. Unable to read locked packages", (string) $result->getMessages()[0]);
+    $this->assertSame('', (string) $result->getSummary());
+  }
+
+}
diff --git a/tests/src/Unit/StagedProjectsValidatorTest.php b/tests/src/Unit/StagedProjectsValidatorTest.php
deleted file mode 100644
index 3c26777206..0000000000
--- a/tests/src/Unit/StagedProjectsValidatorTest.php
+++ /dev/null
@@ -1,191 +0,0 @@
-<?php
-
-namespace Drupal\Tests\automatic_updates\Unit;
-
-use Drupal\automatic_updates\Event\PreCommitEvent;
-use Drupal\automatic_updates\Validator\StagedProjectsValidator;
-use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Drupal\package_manager\ComposerUtility;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * @coversDefaultClass \Drupal\automatic_updates\Validator\StagedProjectsValidator
- *
- * @group automatic_updates
- */
-class StagedProjectsValidatorTest extends UnitTestCase {
-
-  /**
-   * Creates a pre-commit event object for testing.
-   *
-   * @param string $active_dir
-   *   The active directory.
-   * @param string $stage_dir
-   *   The stage directory.
-   *
-   * @return \Drupal\automatic_updates\Event\PreCommitEvent
-   *   The event object.
-   */
-  private function createEvent(string $active_dir, string $stage_dir): PreCommitEvent {
-    return new PreCommitEvent(
-      ComposerUtility::createForDirectory($active_dir),
-      ComposerUtility::createForDirectory($stage_dir)
-    );
-  }
-
-  /**
-   * Tests that if an exception is thrown, the update event will absorb it.
-   */
-  public function testUpdateEventConsumesExceptionResults(): void {
-    $message = 'An exception thrown by Composer at runtime.';
-
-    $composer = $this->prophesize(ComposerUtility::class);
-    $composer->getDrupalExtensionPackages()
-      ->willThrow(new \RuntimeException($message));
-    $event = new PreCommitEvent($composer->reveal(), $composer->reveal());
-
-    $validator = new StagedProjectsValidator(new TestTranslationManager());
-    $validator->validateStagedProjects($event);
-    $results = $event->getResults();
-    $this->assertCount(1, $results);
-    $messages = reset($results)->getMessages();
-    $this->assertCount(1, $messages);
-    $this->assertSame($message, (string) reset($messages));
-  }
-
-  /**
-   * Tests validations errors.
-   *
-   * @param string $fixtures_dir
-   *   The fixtures directory that provides the active and staged composer.lock
-   *   files.
-   * @param string $expected_summary
-   *   The expected error summary.
-   * @param string[] $expected_messages
-   *   The expected error messages.
-   *
-   * @dataProvider providerErrors
-   *
-   * @covers ::validateStagedProjects
-   */
-  public function testErrors(string $fixtures_dir, string $expected_summary, array $expected_messages): void {
-    $this->assertNotEmpty($fixtures_dir);
-    $this->assertDirectoryExists($fixtures_dir);
-
-    $event = $this->createEvent("$fixtures_dir/active", "$fixtures_dir/staged");
-    $validator = new StagedProjectsValidator(new TestTranslationManager());
-    $validator->validateStagedProjects($event);
-    $results = $event->getResults();
-    $this->assertCount(1, $results);
-    $result = array_pop($results);
-    $this->assertSame($expected_summary, (string) $result->getSummary());
-    $actual_messages = $result->getMessages();
-    $this->assertCount(count($expected_messages), $actual_messages);
-    foreach ($expected_messages as $message) {
-      $actual_message = array_shift($actual_messages);
-      $this->assertSame($message, (string) $actual_message);
-    }
-
-  }
-
-  /**
-   * Data provider for testErrors().
-   *
-   * @return \string[][]
-   *   Test cases for testErrors().
-   */
-  public function providerErrors(): array {
-    $fixtures_folder = realpath(__DIR__ . '/../../fixtures/project_staged_validation');
-    return [
-      'new_project_added' => [
-        "$fixtures_folder/new_project_added",
-        'The update cannot proceed because the following Drupal projects were installed during the update.',
-        [
-          "module 'drupal/test_module2' installed.",
-          "custom module 'drupal/dev-test_module2' installed.",
-        ],
-      ],
-      'project_removed' => [
-        "$fixtures_folder/project_removed",
-        'The update cannot proceed because the following Drupal projects were removed during the update.',
-        [
-          "theme 'drupal/test_theme' removed.",
-          "custom theme 'drupal/dev-test_theme' removed.",
-        ],
-      ],
-      'version_changed' => [
-        "$fixtures_folder/version_changed",
-        'The update cannot proceed because the following Drupal projects were unexpectedly updated. Only Drupal Core updates are currently supported.',
-        [
-          "module 'drupal/test_module' from 1.3.0 to  1.3.1.",
-          "module 'drupal/dev-test_module' from 1.3.0 to  1.3.1.",
-        ],
-      ],
-    ];
-  }
-
-  /**
-   * Tests validation when there are no errors.
-   *
-   * @covers ::validateStagedProjects
-   */
-  public function testNoErrors(): void {
-    $fixtures_dir = realpath(__DIR__ . '/../../fixtures/project_staged_validation/no_errors');
-    $event = $this->createEvent("$fixtures_dir/active", "$fixtures_dir/staged");
-    $validator = new StagedProjectsValidator(new TestTranslationManager());
-    $validator->validateStagedProjects($event);
-    $results = $event->getResults();
-    $this->assertIsArray($results);
-    $this->assertEmpty($results);
-  }
-
-  /**
-   * Tests validation when a composer.lock file is not found.
-   */
-  public function testNoLockFile(): void {
-    $fixtures_dir = realpath(__DIR__ . '/../../fixtures/project_staged_validation/no_errors');
-
-    $event = $this->createEvent("$fixtures_dir/active", $fixtures_dir);
-    $validator = new StagedProjectsValidator(new TestTranslationManager());
-    $validator->validateStagedProjects($event);
-    $results = $event->getResults();
-    $this->assertCount(1, $results);
-    $result = array_pop($results);
-    $this->assertSame("No lockfile found. Unable to read locked packages", (string) $result->getMessages()[0]);
-    $this->assertSame('', (string) $result->getSummary());
-  }
-
-}
-
-/**
- * Implements a translation manager in tests.
- *
- * @todo Copied from core/modules/user/tests/src/Unit/PermissionHandlerTest.php
- *   when moving to core open an issue consolidate this test class.
- */
-class TestTranslationManager implements TranslationInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function translate($string, array $args = [], array $options = []) {
-    return new TranslatableMarkup($string, $args, $options, $this);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function translateString(TranslatableMarkup $translated_string) {
-    return $translated_string->getUntranslatedString();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function formatPlural($count, $singular, $plural, array $args = [], array $options = []) {
-    return new PluralTranslatableMarkup($count, $singular, $plural, $args, $options, $this);
-  }
-
-}
-- 
GitLab