automatic_updates-8.x-2.x-dev/tests/src/Functional/UpdateErrorTest.php

tests/src/Functional/UpdateErrorTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\automatic_updates\Functional;

use Drupal\package_manager\Event\PostApplyEvent;
use Drupal\package_manager\Event\PostCreateEvent;
use Drupal\package_manager\Event\PostRequireEvent;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Event\PreRequireEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\ValidationResult;
use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
use Drupal\system\SystemManager;

/**
 * @covers \Drupal\automatic_updates\Form\UpdaterForm
 * @group automatic_updates
 * @internal
 *
 * @todo Consolidate and remove duplicate test coverage in
 *   https://drupal.org/i/3354325.
 */
class UpdateErrorTest extends UpdaterFormTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->config('system.logging')
      ->set('error_level', ERROR_REPORTING_DISPLAY_VERBOSE)
      ->save();
  }

  /**
   * Tests that update cannot be completed via the UI if a status check fails.
   */
  public function testStatusCheckErrorPreventsUpdate(): void {
    $session = $this->getSession();
    $assert_session = $this->assertSession();
    $page = $session->getPage();
    $this->mockActiveCoreVersion('9.8.0');
    $this->checkForUpdates();
    $this->drupalGet('/admin/reports/updates/update');
    $page->pressButton('Update to 9.8.1');
    $this->checkForMetaRefresh();
    $this->assertUpdateStagedTimes(1);

    $error_messages = [
      t("The only thing we're allowed to do is to"),
      t("believe that we won't regret the choice"),
      t("we made."),
    ];
    $summary = t('some generic summary');
    $error = ValidationResult::createError($error_messages, $summary);
    TestSubscriber::setTestResult([$error], StatusCheckEvent::class);
    $this->getSession()->reload();
    $this->assertStatusMessageContainsResult($error);
    $assert_session->buttonNotExists('Continue');
    $assert_session->buttonExists('Cancel update');

    // An error with only one message should also show the summary.
    $error = ValidationResult::createError([t('Yet another smarmy error.')], $summary);
    TestSubscriber::setTestResult([$error], StatusCheckEvent::class);
    $this->getSession()->reload();
    $this->assertStatusMessageContainsResult($error);
    $assert_session->buttonNotExists('Continue');
    $assert_session->buttonExists('Cancel update');
  }

  /**
   * Tests that throwables will be displayed properly.
   */
  public function testDisplayErrorCreatedFromThrowable(): void {
    $throwable = new \Exception("I want to be the pirate king because he's the freest man alive.");
    $result = ValidationResult::createErrorFromThrowable($throwable);
    TestSubscriber1::setTestResult([$result], StatusCheckEvent::class);
    $this->drupalGet('/admin/reports/status');
    $this->clickLink('Rerun readiness checks');
    $this->drupalGet('/admin');
    $assert_session = $this->assertSession();
    $assert_session->statusCodeEquals(200);
    $assert_session->statusMessageContains($throwable->getMessage(), 'error');
  }

  /**
   * Tests the display of errors and warnings during status check.
   */
  public function testStatusCheckErrorDisplay(): void {
    $session = $this->getSession();
    $assert_session = $this->assertSession();

    $cached_message = $this->setAndAssertCachedMessage();
    // Ensure that the fake error is cached.
    $session->reload();
    $assert_session->pageTextContainsOnce((string) $cached_message);

    $this->mockActiveCoreVersion('9.8.0');
    $this->checkForUpdates();

    // Set up a new fake error. Use an error with multiple messages so we can
    // ensure that they're all displayed, along with their summary.
    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR, 2)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);

    // If a validator raises an error during status checking, the form should
    // not have a submit button.
    $this->drupalGet('/admin/reports/updates/update');
    $this->assertNoUpdateButtons();
    // Since this is an administrative page, the error message should be visible
    // thanks to automatic_updates_page_top(). The status checks were re-run
    // during the form build, which means the new error should be cached and
    // displayed instead of the previously cached error.
    $this->assertStatusMessageContainsResult($expected_results[0]);
    $assert_session->pageTextContainsOnce(static::$errorsExplanation);
    $assert_session->pageTextNotContains(static::$warningsExplanation);
    $assert_session->pageTextNotContains($cached_message->render());
    TestSubscriber1::setTestResult(NULL, StatusCheckEvent::class);

    // Set up an error with one message and a summary. We should see both when
    // we refresh the form.
    $expected_result = $this->createValidationResult(SystemManager::REQUIREMENT_ERROR, 1);
    TestSubscriber1::setTestResult([$expected_result], StatusCheckEvent::class);
    $this->getSession()->reload();
    $this->assertNoUpdateButtons();
    $this->assertStatusMessageContainsResult($expected_result);
    $assert_session->pageTextContainsOnce(static::$errorsExplanation);
    $assert_session->pageTextNotContains(static::$warningsExplanation);
    $assert_session->pageTextNotContains($cached_message->render());
    TestSubscriber1::setTestResult(NULL, StatusCheckEvent::class);
  }

  /**
   * Tests handling of exceptions and errors raised by event subscribers.
   *
   * @param string $event
   *   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 providerUpdateStoppedByEventSubscriber
   */
  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.
    if ($event !== StatusCheckEvent::class && $event !== PreCreateEvent::class) {
      $this->getStageFixtureManipulator()->setCorePackageVersion('9.8.1');
    }

    $session = $this->getSession();
    $page = $session->getPage();
    $assert_session = $this->assertSession();

    $this->mockActiveCoreVersion('9.8.0');
    $this->checkForUpdates();
    $this->drupalGet('/admin/reports/updates/update');

    // 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) {
      // 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;
    }

    // Start the update.
    $page->pressButton('Update to 9.8.1');
    $this->checkForMetaRefresh();
    // If the batch job fails, proceed to the error page. If it failed because
    // of the exception we set up, we're done.
    if ($page->hasLink('the error page')) {
      // We should see the exception's backtrace.
      $assert_session->responseContains('<pre class="backtrace">');
      $page->clickLink('the error page');
      $assert_session->statusMessageContains($expected_message, 'error');
      // We should be on the start page.
      $assert_session->addressEquals('/admin/reports/updates/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
      // (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.
    $this->assertStringContainsString('/admin/automatic-update-ready/', $session->getCurrentUrl());
    // 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, [
      PreApplyEvent::class,
      PostApplyEvent::class,
    ]);
    // Try to finish the update.
    $page->pressButton('Continue');
    $this->checkForMetaRefresh();
    // As we did before, proceed to the error page if the batch job fails. If it
    // failed because of the exception we set up, we're done here.
    if ($page->hasLink('the error page')) {
      // We should see the exception's backtrace.
      $assert_session->responseContains('<pre class="backtrace">');
      $page->clickLink('the error page');
      // 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($expected_message, 'error');
    }
  }

  /**
   * Data provider for ::testUpdateStoppedByEventSubscriber().
   *
   * @return array[]
   *   The test cases.
   */
  public static function providerUpdateStoppedByEventSubscriber(): array {
    $events = [
      StatusCheckEvent::class,
      PreCreateEvent::class,
      PostCreateEvent::class,
      PreRequireEvent::class,
      PostRequireEvent::class,
      PreApplyEvent::class,
      PostApplyEvent::class,
    ];
    $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;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc