diff --git a/tests/src/Functional/UpdateErrorTest.php b/tests/src/Functional/UpdateErrorTest.php index eba186952512b1dfbef8000c0c4169de3d910a02..803ebea0e806695f48a7d9f00825aa6cb3a04e1e 100644 --- a/tests/src/Functional/UpdateErrorTest.php +++ b/tests/src/Functional/UpdateErrorTest.php @@ -11,6 +11,7 @@ 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\PreOperationStageEvent; use Drupal\package_manager\Event\PreRequireEvent; use Drupal\package_manager\Event\StatusCheckEvent; use Drupal\package_manager\ValidationResult; @@ -39,37 +40,10 @@ class UpdateErrorTest extends UpdaterFormTestBase { ->save(); } - /** - * Tests that the update stage is destroyed if an error occurs during require. - */ - public function testStageDestroyedOnError(): void { - $session = $this->getSession(); - $assert_session = $this->assertSession(); - $page = $session->getPage(); - $this->mockActiveCoreVersion('9.8.0'); - $this->checkForUpdates(); - - $this->drupalGet('/admin/modules/update'); - $error = new \Exception('Some Exception'); - TestSubscriber1::setException($error, PostRequireEvent::class); - $assert_session->pageTextNotContains(static::$errorsExplanation); - $assert_session->pageTextNotContains(static::$warningsExplanation); - $page->pressButton('Update to 9.8.1'); - $this->checkForMetaRefresh(); - $this->assertUpdateStagedTimes(1); - $assert_session->pageTextContainsOnce('An error has occurred.'); - $page->clickLink('the error page'); - $assert_session->addressEquals('/admin/modules/update'); - $assert_session->pageTextNotContains('Cannot begin an update because another Composer operation is currently in progress.'); - $assert_session->buttonNotExists('Delete existing update'); - $assert_session->pageTextContains('Some Exception'); - $assert_session->buttonExists('Update'); - } - /** * Tests that update cannot be completed via the UI if a status check fails. */ - public function testNoContinueOnError(): void { + public function testStatusCheckErrorPreventsUpdate(): void { $session = $this->getSession(); $assert_session = $this->assertSession(); $page = $session->getPage(); @@ -103,9 +77,9 @@ class UpdateErrorTest extends UpdaterFormTestBase { } /** - * Tests handling of errors and warnings during the update process. + * Tests the display of errors and warnings during status check. */ - public function testUpdateErrors(): void { + public function testStatusCheckErrorDisplay(): void { $session = $this->getSession(); $assert_session = $this->assertSession(); $page = $session->getPage(); @@ -148,53 +122,33 @@ class UpdateErrorTest extends UpdaterFormTestBase { $assert_session->pageTextNotContains(static::$warningsExplanation); $assert_session->pageTextNotContains($cached_message); TestSubscriber1::setTestResult(NULL, StatusCheckEvent::class); - - // Make the validator throw an exception during pre-create. - $error = new \Exception('The update exploded.'); - TestSubscriber1::setException($error, PreCreateEvent::class); - $session->reload(); - $assert_session->pageTextNotContains(static::$errorsExplanation); - $assert_session->pageTextNotContains(static::$warningsExplanation); - $assert_session->pageTextNotContains($cached_message); - $page->pressButton('Update to 9.8.1'); - $this->checkForMetaRefresh(); - $this->assertUpdateStagedTimes(0); - $assert_session->pageTextContainsOnce('An error has occurred.'); - $page->clickLink('the error page'); - // We should see the exception message, but not the validation result's - // messages or summary, because exceptions thrown directly by event - // subscribers are wrapped in simple exceptions and re-thrown. - $assert_session->pageTextContainsOnce($error->getMessage()); - $assert_session->pageTextNotContains((string) $expected_results[0]->messages[0]); - $assert_session->pageTextNotContains($expected_results[0]->summary); - $assert_session->pageTextNotContains($cached_message); - // Since the error occurred during pre-create, there should be no existing - // update to delete. - $assert_session->buttonNotExists('Delete existing update'); - - // If a validator flags an error, but doesn't throw, the update should still - // be halted. - TestSubscriber1::setTestResult($expected_results, PreCreateEvent::class); - $page->pressButton('Update to 9.8.1'); - $this->checkForMetaRefresh(); - $this->assertUpdateStagedTimes(0); - $assert_session->pageTextContainsOnce('An error has occurred.'); - $page->clickLink('the error page'); - $this->assertStatusMessageContainsResult($expected_results[0]); - $assert_session->pageTextNotContains($cached_message); } /** - * Tests handling of exceptions thrown by event subscribers. + * Tests handling of exceptions and errors raised by event subscribers. * * @param string $event - * The event that should throw an exception. + * The event that should cause a problem. + * @param string $stopped_by + * Either 'exception' to throw an exception on the given event, or + * 'validation error' to flag a validation error instead. * - * @dataProvider providerExceptionFromEventSubscriber + * @dataProvider providerUpdateStoppedByEventSubscriber */ - public function testExceptionFromEventSubscriber(string $event): void { - $exception = new \Exception('Bad news bears!'); - TestSubscriber::setException($exception, $event); + public function testUpdateStoppedByEventSubscriber(string $event, string $stopped_by): void { + $expected_message = 'Bad news bears!'; + + if ($stopped_by === 'validation error') { + $result = ValidationResult::createError([ + // @codingStandardsIgnoreLine + t($expected_message), + ]); + TestSubscriber::setTestResult([$result], $event); + } + else { + $this->assertSame('exception', $stopped_by); + TestSubscriber::setException(new \Exception($expected_message), $event); + } // Only simulate a staged update if we're going to get far enough that the // stage directory will be created. @@ -213,7 +167,16 @@ class UpdateErrorTest extends UpdaterFormTestBase { // StatusCheckEvent runs very early, before we can even start the update. // If it raises the error we're expecting, we're done. if ($event === StatusCheckEvent::class) { - $assert_session->statusMessageContains($exception->getMessage(), 'error'); + // If we are flagging a validation error, we should see an explanatory + // message. If we're throwing an exception, we shouldn't. + if ($stopped_by === 'validation error') { + $assert_session->statusMessageContains(static::$errorsExplanation, 'error'); + } + else { + $assert_session->pageTextNotContains(static::$errorsExplanation); + } + $assert_session->pageTextNotContains(static::$warningsExplanation); + $assert_session->statusMessageContains($expected_message, 'error'); // We shouldn't be able to start the update. $assert_session->buttonNotExists('Update to 9.8.1'); return; @@ -228,40 +191,31 @@ class UpdateErrorTest extends UpdaterFormTestBase { // We should see the exception's backtrace. $assert_session->responseContains('<pre class="backtrace">'); $page->clickLink('the error page'); - $assert_session->statusMessageContains($exception->getMessage(), 'error'); + $assert_session->statusMessageContains($expected_message, 'error'); // We should be on the start page. $assert_session->addressEquals('/admin/modules/update'); // If we failed during post-create, the stage is not destroyed, so we // should not be able to start the update anew without destroying the // stage first. In all other cases, the stage should have been destroyed - // and we should be able to try again. + // (or never created at all) and we should be able to try again. // @todo Delete the existing update on behalf of the user in // https://drupal.org/i/3346644. if ($event === PostCreateEvent::class) { + $assert_session->pageTextContains('Cannot begin an update because another Composer operation is currently in progress.'); $assert_session->buttonNotExists('Update to 9.8.1'); $assert_session->buttonExists('Delete existing update'); } else { + $assert_session->pageTextNotContains('Cannot begin an update because another Composer operation is currently in progress.'); $assert_session->buttonExists('Update to 9.8.1'); $assert_session->buttonNotExists('Delete existing update'); } return; } - // We should now be ready to finish the update... + // We should now be ready to finish the update. $this->assertStringContainsString('/admin/automatic-update-ready/', $session->getCurrentUrl()); - // ...but if we set it up to fail on PostRequireEvent, and we see the error - // message from that, we're done. - // @todo In https://drupal.org/i/3346644, ensure that PostRequireEvent - // behaves the same way as PreCreateEvent, PostCreateEvent, and - // PreRequireEvent. - if ($event === PostRequireEvent::class) { - $assert_session->statusMessageContains($exception->getMessage(), 'error'); - $assert_session->buttonNotExists('Continue'); - return; - } - // Ensure that we are expecting a failure from an event that is dispatched // during the second phase (apply and destroy) of the update. $this->assertContains($event, [ @@ -282,7 +236,7 @@ class UpdateErrorTest extends UpdaterFormTestBase { // We should be back on the "ready to update" page, and the exception // message should be visible. $this->assertStringContainsString('/admin/automatic-update-ready/', $session->getCurrentUrl()); - $assert_session->statusMessageContains($exception->getMessage(), 'error'); + $assert_session->statusMessageContains($expected_message, 'error'); return; } @@ -292,7 +246,7 @@ class UpdateErrorTest extends UpdaterFormTestBase { // We should have been forwarded to the main update page, and the exception // message should be visible. $assert_session->addressEquals('/admin/reports/updates'); - $assert_session->statusMessageContains($exception->getMessage(), 'error'); + $assert_session->statusMessageContains($expected_message, 'error'); // If the exception was thrown during PreDestroyEvent, the stage was not // destroyed, so pretend there's another available update, and ensure that @@ -321,12 +275,12 @@ class UpdateErrorTest extends UpdaterFormTestBase { } /** - * Data provider for ::testExceptionFromEventSubscriber(). + * Data provider for ::testUpdateStoppedByEventSubscriber(). * * @return array[] * The test cases. */ - public function providerExceptionFromEventSubscriber(): array { + public function providerUpdateStoppedByEventSubscriber(): array { $events = [ StatusCheckEvent::class, PreCreateEvent::class, @@ -338,7 +292,16 @@ class UpdateErrorTest extends UpdaterFormTestBase { PreDestroyEvent::class, PostDestroyEvent::class, ]; - return array_combine($events, array_map(fn ($event) => [$event], $events)); + $data = []; + foreach ($events as $event) { + $data["exception from $event"] = [$event, 'exception']; + + // Only the pre-operation events support flagging validation errors. + if (is_subclass_of($event, PreOperationStageEvent::class)) { + $data["validation error from $event"] = [$event, 'validation error']; + } + } + return $data; } }