From 5696206258ec7cecd248f13b5fba4b111d5d396f Mon Sep 17 00:00:00 2001 From: phenaproxima <phenaproxima@205645.no-reply.drupal.org> Date: Tue, 19 Oct 2021 18:40:19 +0000 Subject: [PATCH] Issue #3244337 by phenaproxima, tedbow: On status report, if validation results do not have summaries, then messages from different validator get concatenated into one long string --- src/Validation/ReadinessRequirements.php | 5 +- .../Functional/ReadinessValidationTest.php | 152 ++++++++++++++---- 2 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/Validation/ReadinessRequirements.php b/src/Validation/ReadinessRequirements.php index 902eb392c6..9265232c0f 100644 --- a/src/Validation/ReadinessRequirements.php +++ b/src/Validation/ReadinessRequirements.php @@ -137,7 +137,10 @@ final class ReadinessRequirements implements ContainerInjectionInterface { 'severity' => $severity, 'value' => $this->getFailureMessageForSeverity($severity), 'description' => [ - 'messages' => $severity_messages, + 'messages' => [ + '#theme' => 'item_list', + '#items' => $severity_messages, + ], ], ]; if ($run_link = $this->createRunLink()) { diff --git a/tests/src/Functional/ReadinessValidationTest.php b/tests/src/Functional/ReadinessValidationTest.php index 4db87cdf04..16e2b42a4a 100644 --- a/tests/src/Functional/ReadinessValidationTest.php +++ b/tests/src/Functional/ReadinessValidationTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\automatic_updates\Functional; +use Behat\Mink\Element\NodeElement; use Drupal\automatic_updates_test\Datetime\TestTime; use Drupal\automatic_updates_test\ReadinessChecker\TestChecker1; use Drupal\automatic_updates_test2\ReadinessChecker\TestChecker2; @@ -85,10 +86,10 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $this->drupalLogin($this->reportViewerUser); $this->checkForUpdates(); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates.', 'checked', FALSE); + $this->assertNoErrors(); $this->drupalLogin($this->checkerRunnerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates. Run readiness checks now.', 'checked', FALSE); + $this->assertNoErrors(TRUE); // Confirm a user without the permission to run readiness checks does not // have a link to run the checks when the checks need to be run again. @@ -99,19 +100,19 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $key_value->delete('readiness_validation_last_run'); $this->drupalLogin($this->reportViewerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates.', 'checked', FALSE); + $this->assertNoErrors(); $this->drupalLogin($this->checkerRunnerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates. Run readiness checks now.', 'checked', FALSE); + $this->assertNoErrors(TRUE); // Confirm a user with the permission to run readiness 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->assertReadinessReportMatches('Your site is ready for automatic updates.', 'checked', FALSE); + $this->assertNoErrors(); $this->drupalLogin($this->checkerRunnerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates. Run readiness checks now.', 'checked', FALSE); + $this->assertNoErrors(TRUE); /** @var \Drupal\automatic_updates\Validation\ValidationResult[] $expected_results */ $expected_results = $this->testResults['checker_1']['1 error']; TestChecker1::setTestResult($expected_results); @@ -125,14 +126,14 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { // 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->assertReadinessReportMatches($expected_results[0]->getMessages()[0] . 'Run readiness checks now.', 'error', static::$errorsExplanation); + $this->assertErrors($expected_results, TRUE); // @todo Should we always show when the checks were last run and a link to // run when there is an error? // Confirm a user without permission to run the checks sees the same error. $this->drupalLogin($this->reportViewerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches($expected_results[0]->getMessages()[0], 'error', static::$errorsExplanation); + $this->assertErrors($expected_results); $expected_results = $this->testResults['checker_1']['1 error 1 warning']; TestChecker1::setTestResult($expected_results); @@ -141,8 +142,8 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $this->drupalGet('admin/reports/status'); // Confirm that on the status page if there is only 1 warning or error the // the summaries will not be displayed. - $this->assertReadinessReportMatches($expected_results['1:error']->getMessages()[0], 'error', static::$errorsExplanation); - $this->assertReadinessReportMatches($expected_results['1:warning']->getMessages()[0], 'warning', static::$warningsExplanation); + $this->assertErrors([$expected_results['1:error']]); + $this->assertWarnings([$expected_results['1:warning']]); $assert->pageTextNotContains($expected_results['1:error']->getSummary()); $assert->pageTextNotContains($expected_results['1:warning']->getSummary()); @@ -152,8 +153,8 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $this->drupalGet('admin/reports/status'); // Confirm that both messages and summaries will be displayed on status // report when there multiple messages. - $this->assertReadinessReportMatches($expected_results['1:errors']->getSummary() . ' ' . implode('', $expected_results['1:errors']->getMessages()), 'error', static::$errorsExplanation); - $this->assertReadinessReportMatches($expected_results['1:warnings']->getSummary() . ' ' . implode('', $expected_results['1:warnings']->getMessages()), 'warning', static::$warningsExplanation); + $this->assertErrors([$expected_results['1:errors']]); + $this->assertWarnings([$expected_results['1:warnings']]); $key_value->delete('readiness_validation_last_run'); $expected_results = $this->testResults['checker_1']['2 warnings']; @@ -162,14 +163,14 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { $assert->pageTextContainsOnce('Update readiness checks'); // Confirm that warnings will display on the status report if there are no // errors. - $this->assertReadinessReportMatches($expected_results[0]->getSummary() . ' ' . implode('', $expected_results[0]->getMessages()), 'warning', static::$warningsExplanation); + $this->assertWarnings($expected_results); $key_value->delete('readiness_validation_last_run'); $expected_results = $this->testResults['checker_1']['1 warning']; TestChecker1::setTestResult($expected_results); $this->drupalGet('admin/reports/status'); $assert->pageTextContainsOnce('Update readiness checks'); - $this->assertReadinessReportMatches($expected_results[0]->getMessages()[0], 'warning', static::$warningsExplanation); + $this->assertWarnings($expected_results); } /** @@ -188,7 +189,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { // If site is ready for updates no message will be displayed on admin pages. $this->drupalLogin($this->reportViewerUser); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates.', 'checked', FALSE); + $this->assertNoErrors(); $this->drupalGet('admin/structure'); $assert->elementNotExists('css', $messages_section_selector); @@ -307,7 +308,7 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { // the functionality to retrieve our fake release history metadata. $this->container->get('module_installer')->install(['automatic_updates', 'automatic_updates_test']); $this->drupalGet('admin/reports/status'); - $this->assertReadinessReportMatches('Your site is ready for automatic updates. Run readiness checks now.', 'checked'); + $this->assertNoErrors(TRUE); $expected_results = $this->testResults['checker_1']['1 error']; TestChecker2::setTestResult($expected_results); @@ -368,23 +369,114 @@ class ReadinessValidationTest extends AutomaticUpdatesFunctionalTestBase { } /** - * Asserts status report readiness report item matches a format. + * Asserts that the readiness requirement displays no errors or warnings. + * + * @param bool $run_link + * (optional) Whether there should be a link to run the readiness 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 readiness requirement contains warnings. + * + * @param \Drupal\automatic_updates\Validation\ValidationResult[] $expected_results + * The readiness check results that should be visible. + * @param bool $run_link + * (optional) Whether there should be a link to run the readiness 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 readiness requirement contains errors. + * + * @param \Drupal\automatic_updates\Validation\ValidationResult[] $expected_results + * The readiness check results that should be visible. + * @param bool $run_link + * (optional) Whether there should be a link to run the readiness 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 readiness requirement is correct. * - * @param string $format - * The string to match. * @param string $section - * The section of the status report in which the string should appear. - * @param string $message_prefix - * The prefix for before the string. + * 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\automatic_updates\Validation\ValidationResult[] $expected_results + * The expected readiness 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 readiness 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->getMessages(); + if (count($messages) > 1) { + $expected_messages[] = $result->getSummary(); + } + $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('Run readiness checks')); + } + + /** + * Extracts the readiness result messages from the requirement element. + * + * @param \Behat\Mink\Element\NodeElement $requirement + * The page element containing the readiness check results. + * + * @return string[] + * The readiness result messages (including summaries), in the order they + * appear on the page. */ - private function assertReadinessReportMatches(string $format, string $section = 'error', string $message_prefix = ''): void { - $format = 'Update readiness checks ' . ($message_prefix ? "$message_prefix " : '') . $format; - - $text = $this->getSession()->getPage()->find( - 'css', - "h3#$section ~ details.system-status-report__entry:contains('Update readiness checks')" - )->getText(); - $this->assertStringMatchesFormat($format, $text); + 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); } /** -- GitLab