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

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

declare(strict_types=1);

namespace Drupal\Tests\automatic_updates\Functional;

use Behat\Mink\Element\NodeElement;
use Drupal\automatic_updates\CronUpdateRunner;
use Drupal\automatic_updates\StatusCheckMailer;
use Drupal\automatic_updates\Validation\StatusChecker;
use Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1;
use Drupal\automatic_updates_test_status_checker\EventSubscriber\TestSubscriber2;
use Drupal\Core\Url;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\ValidationResult;
use Drupal\package_manager_test_validation\EventSubscriber\TestSubscriber;
use Drupal\system\SystemManager;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\Traits\Core\CronRunTrait;

/**
 * Tests status checks.
 *
 * @group automatic_updates
 * @internal
 */
class StatusCheckTest extends AutomaticUpdatesFunctionalTestBase {

  use CronRunTrait;
  use ValidationTestTrait;

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

  /**
   * A user who can view the status report.
   *
   * @var \Drupal\user\Entity\User
   */
  protected $reportViewerUser;

  /**
   * A user who can view the status report and run status checks.
   *
   * @var \Drupal\user\Entity\User
   */
  protected $checkerRunnerUser;

  /**
   * The test checker.
   *
   * @var \Drupal\automatic_updates_test\EventSubscriber\TestSubscriber1
   */
  protected $testChecker;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'package_manager_test_validation',
    'block',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->setReleaseMetadata(static::getDrupalRoot() . '/core/modules/package_manager/tests/fixtures/release-history/drupal.9.8.1-security.xml');
    $this->mockActiveCoreVersion('9.8.1');

    $this->reportViewerUser = $this->createUser([
      'administer site configuration',
      'access administration pages',
      'administer blocks',
    ]);
    $this->checkerRunnerUser = $this->createUser([
      'administer site configuration',
      'administer software updates',
      'access administration pages',
      'access site in maintenance mode',
      'administer modules',
      'administer blocks',
    ]);
    $this->drupalLogin($this->reportViewerUser);
  }

  /**
   * Tests status checks are displayed after Automatic Updates is installed.
   *
   * @dataProvider providerTestModuleFormInstallDisplay
   */
  public function testModuleFormInstallDisplay(int $results_severity): void {
    // Uninstall Automatic Updates as it is installed in TestBase setup().
    $this->container->get('module_installer')->uninstall(['automatic_updates']);
    $expected_result = $this->createValidationResult($results_severity);
    TestSubscriber::setTestResult([$expected_result], StatusCheckEvent::class);

    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet('admin/modules');
    $page = $this->getSession()->getPage();
    $page->checkField('modules[automatic_updates][enable]');
    $page->pressButton('Install');
    // CORE_MR_ONLY:// Confirm installing the experimental module.
    // CORE_MR_ONLY:$page->pressButton('Continue');
    // Cron Updates will always be disabled on installation as per
    // automatic_updates.settings.yml .
    $session = $this->assertSession();
    $session->pageTextNotContains($expected_result->messages[0]->render());
    $session->linkExists('See status report for more details.');
  }

  /**
   * Provides data for testModuleFormInstallDisplay.
   */
  public static function providerTestModuleFormInstallDisplay(): array {
    return [
      'Error' => [
        SystemManager::REQUIREMENT_ERROR,
      ],
      'Warning' => [
        SystemManager::REQUIREMENT_WARNING,
      ],
    ];
  }

  /**
   * Tests status checks on status report page.
   */
  public function testStatusChecksOnStatusReport(): void {
    $assert = $this->assertSession();
    $page = $this->getSession()->getPage();
    $this->container->get('module_installer')->install(['automatic_updates', 'automatic_updates_test']);

    // If the site is ready for updates, the users will see the same output
    // regardless of whether the user has permission to run updates.
    $this->drupalLogin($this->reportViewerUser);
    $this->checkForUpdates();
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors();
    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors(TRUE);

    // Confirm a user without the permission to run status checks does not
    // have a link to run the checks when the checks need to be run again.
    /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value */
    $key_value = $this->container->get('keyvalue.expirable')->get('automatic_updates');
    $key_value->delete('status_check_last_run');
    $this->drupalLogin($this->reportViewerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors();
    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors(TRUE);

    // Confirm a user with the permission to run status checks does have a link
    // to run the checks when the checks need to be run again.
    $this->drupalLogin($this->reportViewerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors();
    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors(TRUE);
    /** @var \Drupal\package_manager\ValidationResult[] $expected_results */
    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);

    // Run the status checks.
    $this->clickLink('Rerun readiness checks');
    $assert->statusCodeEquals(200);
    // Confirm redirect back to status report page.
    $assert->addressEquals('/admin/reports/status');
    // Assert that when the runners are run manually the message that updates
    // will not be performed because of errors is displayed on the top of the
    // page in message.
    $assert->pageTextMatchesCount(2, '/' . preg_quote(static::$errorsExplanation) . '/');
    $this->assertErrors($expected_results, TRUE);

    // Confirm a user without permission to run the checks sees the same error.
    $this->drupalLogin($this->reportViewerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertErrors($expected_results);

    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet('/admin/reports/status');

    $expected_results = [
      'error' => $this->createValidationResult(SystemManager::REQUIREMENT_ERROR),
      'warning' => $this->createValidationResult(SystemManager::REQUIREMENT_WARNING),
    ];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $page->clickLink('Rerun readiness checks');
    // We should see the summaries and messages, even if there's only 1 message.
    $this->assertErrors([$expected_results['error']], TRUE);
    $this->assertWarnings([$expected_results['warning']], TRUE);

    // If there's a result with only one message, but no summary, ensure that
    // message is displayed.
    $result = ValidationResult::createError([t('A lone message, with no summary.')]);
    TestSubscriber1::setTestResult([$result], StatusCheckEvent::class);
    $page->clickLink('Rerun readiness checks');
    $this->assertErrors([$result], TRUE);

    $expected_results = [
      'error' => $this->createValidationResult(SystemManager::REQUIREMENT_ERROR, 2),
      'warning' => $this->createValidationResult(SystemManager::REQUIREMENT_WARNING, 2),
    ];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $page->clickLink('Rerun readiness checks');
    // Confirm that both messages and summaries will be displayed when there are
    // multiple messages.
    $this->assertErrors([$expected_results['error']], TRUE);
    $this->assertWarnings([$expected_results['warning']], TRUE);

    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_WARNING, 2)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $page->clickLink('Rerun readiness checks');
    $assert->pageTextContainsOnce('Update readiness checks');
    // Confirm that warnings will display on the status report if there are no
    // errors.
    $this->assertWarnings($expected_results, TRUE);

    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_WARNING)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $page->clickLink('Rerun readiness checks');
    $assert->pageTextContainsOnce('Update readiness checks');
    $this->assertWarnings($expected_results, TRUE);
  }

  /**
   * Data provider for URLs to the admin page.
   *
   * These particular admin routes are tested as status checks are disabled on
   * certain routes but not on these.
   *
   * @see \Drupal\automatic_updates\Routing\RouteSubscriber::alterRoutes()
   *
   * @return string[][]
   *   The test cases.
   */
  public static function providerAdminRoutes(): array {
    return [
      'Structure Page' => ['system.admin_structure'],
      'Update settings Page' => ['update.settings'],
    ];
  }

  /**
   * Tests status check results on admin pages.
   *
   * @param string $admin_route
   *   The admin route to check.
   *
   * @dataProvider providerAdminRoutes
   */
  public function testStatusChecksOnAdminPages(string $admin_route): void {
    $assert = $this->assertSession();

    $this->container->get('module_installer')->install(['automatic_updates', 'automatic_updates_test']);

    // If site is ready for updates no message will be displayed on admin pages.
    $this->drupalLogin($this->reportViewerUser);
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors();
    $this->drupalGet(Url::fromRoute($admin_route));

    $assert->statusMessageNotExists();

    // Confirm a user without the permission to run status checks does not have
    // a link to run the checks when the checks need to be run again.
    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $key_value */
    $key_value = $this->container->get('keyvalue.expirable')->get('automatic_updates');
    $key_value->delete('status_check_last_run');
    // A user without the permission to run the checkers will not see a message
    // on other pages if the checkers need to be run again.
    $this->drupalGet(Url::fromRoute($admin_route));
    $assert->statusMessageNotExists();

    // Confirm that a user with the correct permission can also run the checkers
    // on another admin page.
    $this->drupalLogin($this->checkerRunnerUser);
    $this->drupalGet(Url::fromRoute($admin_route));
    $assert->statusMessageContains('Your site has not recently run an update readiness check. Rerun readiness checks now.');
    $this->clickLink('Rerun readiness checks now.');
    $assert->addressEquals(Url::fromRoute($admin_route));
    $assert->pageTextContainsOnce((string) $expected_results[0]->summary);

    $expected_results = [
      '1 error' => $this->createValidationResult(SystemManager::REQUIREMENT_ERROR),
      '1 warning' => $this->createValidationResult(SystemManager::REQUIREMENT_WARNING),
    ];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $this->runStatusChecks();
    $this->drupalGet(Url::fromRoute($admin_route));
    $assert->pageTextContainsOnce(static::$errorsExplanation);
    // Confirm on admin pages that the summary will be displayed.
    $this->assertSame(SystemManager::REQUIREMENT_ERROR, $expected_results['1 error']->severity);
    $assert->pageTextContainsOnce((string) $expected_results['1 error']->summary);
    $assert->pageTextNotContains($expected_results['1 error']->messages[0]->render());
    // Warnings are not displayed on admin pages if there are any errors.
    $this->assertSame(SystemManager::REQUIREMENT_WARNING, $expected_results['1 warning']->severity);
    $assert->pageTextNotContains($expected_results['1 warning']->messages[0]->render());
    $assert->pageTextNotContains($expected_results['1 warning']->summary->render());

    // Confirm the status check event is not dispatched on every admin page
    // load.
    $unexpected_results = [
      '2 errors' => $this->createValidationResult(SystemManager::REQUIREMENT_ERROR, 2),
      '2 warnings' => $this->createValidationResult(SystemManager::REQUIREMENT_WARNING, 2),
    ];
    TestSubscriber1::setTestResult($unexpected_results, StatusCheckEvent::class);
    $this->drupalGet(Url::fromRoute($admin_route));
    $assert->pageTextNotContains($unexpected_results['2 errors']->summary->render());
    $assert->pageTextContainsOnce((string) $expected_results['1 error']->summary->render());
    $assert->pageTextNotContains($unexpected_results['2 warnings']->summary->render());
    $assert->pageTextNotContains($expected_results['1 warning']->messages[0]->render());

    // Confirm the updated results will be shown when status checks are run
    // again.
    $this->runStatusChecks();
    $expected_results = $unexpected_results;
    $this->drupalGet(Url::fromRoute($admin_route));
    // Confirm on admin pages only the error summary will be displayed if there
    // is more than 1 error.
    $this->assertSame(SystemManager::REQUIREMENT_ERROR, $expected_results['2 errors']->severity);
    $assert->pageTextNotContains($expected_results['2 errors']->messages[0]->render());
    $assert->pageTextNotContains($expected_results['2 errors']->messages[1]->render());
    $assert->pageTextContainsOnce($expected_results['2 errors']->summary->render());
    $assert->pageTextContainsOnce(static::$errorsExplanation);
    // Warnings are not displayed on admin pages if there are any errors.
    $this->assertSame(SystemManager::REQUIREMENT_WARNING, $expected_results['2 warnings']->severity);
    $assert->pageTextNotContains($expected_results['2 warnings']->messages[0]->render());
    $assert->pageTextNotContains($expected_results['2 warnings']->messages[1]->render());
    $assert->pageTextNotContains($expected_results['2 warnings']->summary->render());

    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_WARNING, 2)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $this->runStatusChecks();
    $this->drupalGet(Url::fromRoute($admin_route));
    // Confirm that the warnings summary is displayed on admin pages if there
    // are no errors.
    $assert->pageTextNotContains(static::$errorsExplanation);
    $this->assertSame(SystemManager::REQUIREMENT_WARNING, $expected_results[0]->severity);
    $assert->pageTextNotContains($expected_results[0]->messages[0]->render());
    $assert->pageTextNotContains($expected_results[0]->messages[1]->render());
    $assert->pageTextContainsOnce(static::$warningsExplanation);
    $assert->pageTextContainsOnce($expected_results[0]->summary->render());

    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_WARNING)];
    TestSubscriber1::setTestResult($expected_results, StatusCheckEvent::class);
    $this->runStatusChecks();
    $this->drupalGet(Url::fromRoute($admin_route));
    $assert->pageTextNotContains(static::$errorsExplanation);
    // Confirm that a single warning is displayed and not the summary on admin
    // pages if there is only 1 warning and there are no errors.
    $this->assertSame(SystemManager::REQUIREMENT_WARNING, $expected_results[0]->severity);
    $assert->pageTextContainsOnce(static::$warningsExplanation);
    $assert->pageTextContainsOnce((string) $expected_results[0]->summary->render());
    $assert->pageTextNotContains($expected_results[0]->messages[0]->render());

    // Confirm status check messages are not displayed when cron updates are
    // disabled.
    $this->config('automatic_updates.settings')
      ->set('unattended.level', CronUpdateRunner::DISABLED)
      ->save();
    $this->drupalGet('admin/structure');
    $this->checkForMetaRefresh();
    $assert->pageTextNotContains(static::$warningsExplanation);
    $assert->pageTextNotContains($expected_results[0]->messages[0]->render());
  }

  /**
   * Tests installing a module with a checker before installing Automatic Updates.
   */
  public function testStatusCheckAfterInstall(): void {
    $assert = $this->assertSession();
    $this->drupalLogin($this->checkerRunnerUser);
    $this->container->get('module_installer')->uninstall(['automatic_updates']);

    $this->drupalGet('admin/reports/status');
    $assert->pageTextNotContains('Update readiness checks');

    // We have to install the automatic_updates_test module because it provides
    // the functionality to retrieve our fake release history metadata.
    $this->container->get('module_installer')->install(['automatic_updates', 'automatic_updates_test']);
    // @todo Remove in https://www.drupal.org/project/automatic_updates/issues/3284443
    $this->config('automatic_updates.settings')
      ->set('unattended.level', CronUpdateRunner::SECURITY)
      ->save();
    $this->drupalGet('admin/reports/status');
    $this->assertNoErrors(TRUE);

    $expected_results = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber2::setTestResult($expected_results, StatusCheckEvent::class);
    $this->container->get('module_installer')->install(['automatic_updates_test_status_checker']);
    $this->drupalGet('admin/structure');
    $assert->pageTextContainsOnce((string) $expected_results[0]->summary->render());

    // Confirm that installing a module runs the checkers, even if the new
    // module does not provide any validators.
    $previous_results = $expected_results;
    $expected_results = [
      '2 errors' => $this->createValidationResult(SystemManager::REQUIREMENT_ERROR, 2),
      '2 warnings' => $this->createValidationResult(SystemManager::REQUIREMENT_WARNING, 2),
    ];
    TestSubscriber2::setTestResult($expected_results, StatusCheckEvent::class);
    $this->container->get('module_installer')->install(['help']);
    // Check for messages on 'admin/structure' instead of the status report,
    // because validators will be run if needed on the status report.
    $this->drupalGet('admin/structure');
    // Confirm that new checker messages are displayed.
    $assert->pageTextNotContains($previous_results[0]->messages[0]->render());
    $assert->pageTextNotContains($expected_results['2 errors']->messages[0]->render());
    $assert->pageTextContainsOnce($expected_results['2 errors']->summary->render());
  }

  /**
   * Tests that checker message for an uninstalled module is not displayed.
   */
  public function testStatusCheckUninstall(): void {
    $assert = $this->assertSession();
    $this->drupalLogin($this->checkerRunnerUser);

    $expected_results_1 = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber1::setTestResult($expected_results_1, StatusCheckEvent::class);
    $expected_results_2 = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber2::setTestResult($expected_results_2, StatusCheckEvent::class);
    $this->container->get('module_installer')->install([
      'automatic_updates',
      'automatic_updates_test',
      'automatic_updates_test_status_checker',
    ]);
    // Check for message on 'admin/structure' instead of the status report
    // because checkers will be run if needed on the status report.
    $this->drupalGet('admin/structure');
    $assert->pageTextContainsOnce($expected_results_1[0]->summary->render());
    $assert->pageTextContainsOnce($expected_results_2[0]->summary->render());

    // Confirm that when on of the module is uninstalled the other module's
    // checker result is still displayed.
    $this->container->get('module_installer')->uninstall(['automatic_updates_test_status_checker']);
    $this->drupalGet('admin/structure');
    $assert->pageTextNotContains($expected_results_2[0]->summary->render());
    $assert->pageTextContainsOnce($expected_results_1[0]->summary->render());

    // Confirm that when on of the module is uninstalled the other module's
    // checker result is still displayed.
    $this->container->get('module_installer')->uninstall(['automatic_updates_test']);
    $this->drupalGet('admin/structure');
    $assert->pageTextNotContains($expected_results_2[0]->messages[0]->render());
    $assert->pageTextNotContains($expected_results_1[0]->messages[0]->render());
  }

  /**
   * Tests that stored validation results are deleted after an update.
   */
  public function testStoredResultsClearedAfterUpdate(): void {
    $this->getStageFixtureManipulator()->setCorePackageVersion('9.8.1');
    $assert_session = $this->assertSession();
    $page = $this->getSession()->getPage();
    $this->drupalLogin($this->checkerRunnerUser);

    // The current release is 9.8.1 (see ::setUp()), so ensure we're on an older
    // version.
    $this->mockActiveCoreVersion('9.8.0');

    // Flag a validation error, whose summary will be displayed in the messages
    // area.
    $results = [$this->createValidationResult(SystemManager::REQUIREMENT_ERROR)];
    TestSubscriber1::setTestResult($results, StatusCheckEvent::class);
    $message = $results[0]->summary;

    $this->container->get('module_installer')->install([
      'automatic_updates',
      'automatic_updates_test',
    ]);
    $this->checkForUpdates();
    // The error should be persistently visible, even after the checker stops
    // flagging it.
    $this->drupalGet('/admin/structure');
    $assert_session->pageTextContains($message->render());
    TestSubscriber1::setTestResult(NULL, StatusCheckEvent::class);
    $this->getSession()->reload();
    $assert_session->pageTextContains($message->render());

    // Do the update; we don't expect any errors or special conditions to appear
    // during it. The Update button is displayed because the form does its own
    // status check (without storing the results), and the checker is no
    // longer raising an error.
    $this->drupalGet('/admin/reports/updates/update');
    $assert_session->buttonExists('Update to 9.8.1');
    // Ensure that the previous results are still displayed on another admin
    // page, to confirm that the updater form is not discarding the previous
    // results by doing its checks.
    $this->drupalGet('/admin/structure');
    $assert_session->pageTextContains($message->render());
    // Proceed with the update.
    $this->drupalGet('/admin/reports/updates/update');
    $page->pressButton('Update to 9.8.1');
    $this->checkForMetaRefresh();
    $this->assertUpdateReady('9.8.1');
    $page->pressButton('Continue');
    $this->checkForMetaRefresh();
    $assert_session->pageTextContains('Update complete!');

    // The warning should not be visible anymore.
    $this->drupalGet('/admin/structure');
    $assert_session->pageTextNotContains($message->render());
  }

  /**
   * Tests that stored results are deleted after certain config changes.
   */
  public function testStoredResultsClearedAfterConfigChanges(): void {
    $this->drupalLogin($this->checkerRunnerUser);

    // Flag a validation error, whose summary will be displayed in the messages
    // area.
    $result = $this->createValidationResult(SystemManager::REQUIREMENT_ERROR);
    TestSubscriber1::setTestResult([$result], StatusCheckEvent::class);
    $message = $result->summary;

    $this->container->get('module_installer')->install([
      'automatic_updates',
      'automatic_updates_test',
    ]);
    $this->container = $this->container->get('kernel')->getContainer();

    // The error should be persistently visible, even after the checker stops
    // flagging it.
    $this->drupalGet('/admin/structure');
    $assert_session = $this->assertSession();
    $assert_session->pageTextContains($message->render());
    TestSubscriber1::setTestResult(NULL, StatusCheckEvent::class);
    $session = $this->getSession();
    $session->reload();
    $assert_session->pageTextContains($message->render());

    $config = $this->config('automatic_updates.settings');
    // If we disable notifications, stored results should not be cleared.
    $config->set('status_check_mail', StatusCheckMailer::DISABLED)->save();
    $session->reload();
    $assert_session->pageTextContains($message->render());

    // If we re-enable them, though, they should be cleared.
    $config->set('status_check_mail', StatusCheckMailer::ERRORS_ONLY)->save();
    $session->reload();
    $assert_session->pageTextNotContains($message->render());
    $no_results_message = 'Your site has not recently run an update readiness check.';
    $assert_session->pageTextContains($no_results_message);

    // If we flag an error again, and keep notifications enabled but change
    // their sensitivity level, the stored results should be cleared.
    TestSubscriber1::setTestResult([$result], StatusCheckEvent::class);
    $session->getPage()->clickLink('Rerun readiness checks now');
    $this->drupalGet('/admin/structure');
    $assert_session->pageTextContains($message->render());
    $config->set('status_check_mail', StatusCheckMailer::ALL)->save();
    $session->reload();
    $assert_session->pageTextNotContains($message->render());
    $assert_session->pageTextContains($no_results_message);
  }

  /**
   * Tests that the status report shows cached status check results.
   */
  public function testStatusReportShowsCachedResults(): void {
    $session = $this->getSession();
    $this->drupalLogin($this->checkerRunnerUser);

    $this->container->get('module_installer')->install([
      'automatic_updates',
      'automatic_updates_test',
    ]);
    $this->container = $this->container->get('kernel')->getContainer();

    // Clear stored results that were collected when the module was installed.
    $this->container->get(StatusChecker::class)->clearStoredResults();

    // Flag a validation error, whose summary will be displayed in the messages
    // area.
    $result = $this->createValidationResult(SystemManager::REQUIREMENT_ERROR);
    TestSubscriber1::setTestResult([$result], StatusCheckEvent::class);

    $this->drupalGet('/admin/reports/status');
    $this->assertErrors([$result], TRUE);

    // Clear the result, and ensure that it's still visible because it is
    // cached.
    TestSubscriber::setTestResult(NULL, StatusCheckEvent::class);
    $session->reload();
    $this->assertErrors([$result], TRUE);

    // If unattended updates are configured to run via the command line, we
    // should see a warning that the status checks have not run recently. This
    // is because changing the configuration clears the cached results, since
    // they may be affected by the change.
    // @see \Drupal\automatic_updates\Validation\StatusChecker::onConfigSave()
    $this->config('automatic_updates.settings')
      ->set('unattended.method', 'console')
      ->save();
    $session->reload();
    $assert_session = $this->assertSession();
    $assert_session->pageTextContainsOnce('Unattended updates are configured to run via the console, but do not appear to have run recently.');
    $assert_session->pageTextNotContains((string) $result->messages[0]->render());
  }

  /**
   * Tests the status checks when unattended updates are run via the console.
   */
  public function testUnattendedUpdatesRunFromConsole(): void {
    $this->container->get('module_installer')->install(['automatic_updates']);
    $this->container = $this->container->get('kernel')->getContainer();

    // Clear stored results that were collected when the module was installed.
    $this->container->get(StatusChecker::class)->clearStoredResults();

    $this->config('automatic_updates.settings')
      ->set('unattended.method', 'console')
      ->save();

    // If we visit the status report, we should see an error requirement because
    // unattended updates are configured to run via the terminal, and there are
    // no stored status check results, which means that the console command has
    // probably not run recently (or ever).
    $this->drupalGet('/admin/reports/status');
    $this->assertRequirement('error', 'Unattended updates are configured to run via the console, but do not appear to have run recently.', [], FALSE);

    // We should see a similar message on any other admin page.
    $this->drupalGet('/admin/structure');
    $this->assertSession()
      ->statusMessageContains('Unattended updates are configured to run via the console, but not appear to have run recently.', 'error');
  }

  /**
   * Asserts that the status check requirement displays no errors or warnings.
   *
   * @param bool $run_link
   *   (optional) Whether there should be a link to run the status checks.
   *   Defaults to FALSE.
   */
  private function assertNoErrors(bool $run_link = FALSE): void {
    $this->assertRequirement('checked', 'Your site is ready for automatic updates.', [], $run_link);
  }

  /**
   * Asserts that the displayed status check requirement contains warnings.
   *
   * @param \Drupal\package_manager\ValidationResult[] $expected_results
   *   The status check results that should be visible.
   * @param bool $run_link
   *   (optional) Whether there should be a link to run the status checks.
   *   Defaults to FALSE.
   */
  private function assertWarnings(array $expected_results, bool $run_link = FALSE): void {
    $this->assertRequirement('warning', static::$warningsExplanation, $expected_results, $run_link);
  }

  /**
   * Asserts that the displayed status check requirement contains errors.
   *
   * @param \Drupal\package_manager\ValidationResult[] $expected_results
   *   The status check results that should be visible.
   * @param bool $run_link
   *   (optional) Whether there should be a link to run the status checks.
   *   Defaults to FALSE.
   */
  private function assertErrors(array $expected_results, bool $run_link = FALSE): void {
    $this->assertRequirement('error', static::$errorsExplanation, $expected_results, $run_link);
  }

  /**
   * Asserts that the status check requirement is correct.
   *
   * @param string $section
   *   The section of the status report in which the requirement is expected to
   *   be. Can be one of 'error', 'warning', 'checked', or 'ok'.
   * @param string $preamble
   *   The text that should appear before the result messages.
   * @param \Drupal\package_manager\ValidationResult[] $expected_results
   *   The expected status check results, in the order we expect them to be
   *   displayed.
   * @param bool $run_link
   *   (optional) Whether there should be a link to run the status checks.
   *   Defaults to FALSE.
   *
   * @see \Drupal\Core\Render\Element\StatusReport::getInfo()
   */
  private function assertRequirement(string $section, string $preamble, array $expected_results, bool $run_link = FALSE): void {
    // Get the meaty part of the requirement element, and ensure that it begins
    // with the preamble, if any.
    $requirement = $this->assertSession()
      ->elementExists('css', "h3#$section ~ details.system-status-report__entry:contains('Update readiness checks') .system-status-report__entry__value");

    if ($preamble) {
      $this->assertStringStartsWith($preamble, $requirement->getText());
    }

    // Convert the expected results into strings.
    $expected_messages = [];
    foreach ($expected_results as $result) {
      $messages = $result->messages;
      $summary = $result->summary;
      if ($summary) {
        $expected_messages[] = $summary;
      }
      $expected_messages = array_merge($expected_messages, $messages);
    }
    $expected_messages = array_map('strval', $expected_messages);

    // The results should appear in the given order.
    $this->assertSame($expected_messages, $this->getMessagesFromRequirement($requirement));
    // Check for the presence or absence of a link to run the checks.
    $this->assertSame($run_link, $requirement->hasLink('Rerun readiness checks'));
  }

  /**
   * Extracts the status check result messages from the requirement element.
   *
   * @param \Behat\Mink\Element\NodeElement $requirement
   *   The page element containing the status check results.
   *
   * @return string[]
   *   The status result messages (including summaries), in the order they
   *   appear on the page.
   */
  private function getMessagesFromRequirement(NodeElement $requirement): array {
    $messages = [];

    // Each list item will either contain a simple string (for results with only
    // one message), or a details element with a series of messages.
    $items = $requirement->findAll('css', 'li');
    foreach ($items as $item) {
      $details = $item->find('css', 'details');

      if ($details) {
        $messages[] = $details->find('css', 'summary')->getText();
        $messages = array_merge($messages, $this->getMessagesFromRequirement($details));
      }
      else {
        $messages[] = $item->getText();
      }
    }
    return array_unique($messages);
  }

  /**
   * Runs status checks.
   */
  private function runStatusChecks(): void {
    $this->drupalGet('/admin/reports/status');
    $this->clickLink('Rerun readiness checks');
  }

}

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

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