diff --git a/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..3d80b39d74211adfa1726c2d53f460d3f52fd430 --- /dev/null +++ b/package_manager/tests/src/Traits/PackageManagerBypassTestTrait.php @@ -0,0 +1,33 @@ +<?php + +namespace Drupal\Tests\package_manager\Traits; + +/** + * Common functions for testing using the package_manager_bypass module. + */ +trait PackageManagerBypassTestTrait { + + /** + * Asserts the number of times an update was staged. + * + * @param int $attempted_times + * The expected number of times an update was staged. + */ + private function assertUpdateStagedTimes(int $attempted_times): void { + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $beginner */ + $beginner = $this->container->get('package_manager.beginner'); + $this->assertCount($attempted_times, $beginner->getInvocationArguments()); + + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ + $stager = $this->container->get('package_manager.stager'); + // If an update was attempted, then there will be two calls to the stager: + // one to change the constraints in composer.json, and another to actually + // update the installed dependencies. + $this->assertCount($attempted_times * 2, $stager->getInvocationArguments()); + + /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $committer */ + $committer = $this->container->get('package_manager.committer'); + $this->assertEmpty($committer->getInvocationArguments()); + } + +} diff --git a/src/CronUpdater.php b/src/CronUpdater.php index d6ca0318240c3fdf22b6dcfb4419c169d9b79e0b..ce166e335f1f420ffa4ee0b486290803d147e0a3 100644 --- a/src/CronUpdater.php +++ b/src/CronUpdater.php @@ -4,6 +4,7 @@ namespace Drupal\automatic_updates; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\package_manager\Exception\StageValidationException; /** * Defines a service that updates via cron. @@ -114,6 +115,10 @@ class CronUpdater extends Updater { $this->apply(); $this->destroy(); } + catch (StageValidationException $e) { + $this->logger->error($this->getLogMessageForValidationException($e)); + return; + } catch (\Throwable $e) { $this->logger->error($e->getMessage()); return; @@ -128,4 +133,31 @@ class CronUpdater extends Updater { ); } + /** + * Generates a log message from a stage validation exception. + * + * @param \Drupal\package_manager\Exception\StageValidationException $exception + * The validation exception. + * + * @return string + * The formatted log message, including all the validation results. + */ + protected function getLogMessageForValidationException(StageValidationException $exception): string { + $log_message = ''; + foreach ($exception->getResults() as $result) { + $summary = $result->getSummary(); + if ($summary) { + $log_message .= "<h3>$summary</h3><ul>"; + foreach ($result->getMessages() as $message) { + $log_message .= "<li>$message</li>"; + } + $log_message .= "</ul>"; + } + else { + $log_message .= ($log_message ? ' ' : '') . $result->getMessages()[0]; + } + } + return "<h2>{$exception->getMessage()}</h2>$log_message"; + } + } diff --git a/tests/src/Functional/UpdaterFormTest.php b/tests/src/Functional/UpdaterFormTest.php index e98f123d6f40efcce6aed95909537927aeb0955a..28362db1b56c8625eeee4a776c18705d3eaca854 100644 --- a/tests/src/Functional/UpdaterFormTest.php +++ b/tests/src/Functional/UpdaterFormTest.php @@ -6,6 +6,7 @@ use Drupal\automatic_updates\Event\ReadinessCheckEvent; use Drupal\package_manager\Event\PreCreateEvent; use Drupal\package_manager\ValidationResult; use Drupal\automatic_updates_test\ReadinessChecker\TestChecker1; +use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait; use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; /** @@ -16,6 +17,7 @@ use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait; class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { use ValidationTestTrait; + use PackageManagerBypassTestTrait; /** * {@inheritdoc} @@ -268,27 +270,4 @@ class UpdaterFormTest extends AutomaticUpdatesFunctionalTestBase { $assert_session->buttonExists('Continue'); } - /** - * Asserts the number of times an update was staged. - * - * @param int $attempted_times - * The expected number of times an update was staged. - */ - private function assertUpdateStagedTimes(int $attempted_times): void { - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $beginner */ - $beginner = $this->container->get('package_manager.beginner'); - $this->assertCount($attempted_times, $beginner->getInvocationArguments()); - - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $stager */ - $stager = $this->container->get('package_manager.stager'); - // If an update was attempted, then there will be two calls to the stager: - // one to change the constraints in composer.json, and another to actually - // update the installed dependencies. - $this->assertCount($attempted_times * 2, $stager->getInvocationArguments()); - - /** @var \Drupal\package_manager_bypass\InvocationRecorderBase $committer */ - $committer = $this->container->get('package_manager.committer'); - $this->assertEmpty($committer->getInvocationArguments()); - } - } diff --git a/tests/src/Kernel/CronUpdaterTest.php b/tests/src/Kernel/CronUpdaterTest.php index efed1a66738b89473e12ee04e0dfc116b3a1e2aa..55afedf23a7dc9a3ca718897939224c6a6417f21 100644 --- a/tests/src/Kernel/CronUpdaterTest.php +++ b/tests/src/Kernel/CronUpdaterTest.php @@ -3,8 +3,14 @@ namespace Drupal\Tests\automatic_updates\Kernel; use Drupal\automatic_updates\CronUpdater; +use Drupal\automatic_updates_test\ReadinessChecker\TestChecker1; use Drupal\Core\Form\FormState; +use Drupal\Core\Logger\RfcLogLevel; +use Drupal\package_manager\Event\PreCreateEvent; +use Drupal\package_manager\ValidationResult; +use Drupal\Tests\package_manager\Traits\PackageManagerBypassTestTrait; use Drupal\update\UpdateSettingsForm; +use Psr\Log\Test\TestLogger; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -15,6 +21,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; */ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { + use PackageManagerBypassTestTrait; + /** * {@inheritdoc} */ @@ -22,6 +30,7 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { 'automatic_updates', 'package_manager', 'package_manager_bypass', + 'automatic_updates_test', ]; /** @@ -117,4 +126,67 @@ class CronUpdaterTest extends AutomaticUpdatesKernelTestBase { $this->assertCount($will_update, $this->container->get('package_manager.committer')->getInvocationArguments()); } + /** + * Data provider for testErrors(). + * + * @return array[] + * The test cases for testErrors(). + */ + public function providerErrors(): array { + $messages = [ + 'Precreate Event Error', + 'Precreate Event Error 2', + ]; + $summary = 'There were errors in updates'; + $result_no_summary = ValidationResult::createError([$messages[0]]); + $result_with_summary = ValidationResult::createError($messages, t($summary)); + $result_with_summary_message = "<h3>{$summary}</h3><ul><li>{$messages[0]}</li><li>{$messages[1]}</li></ul>"; + + return [ + '1 result with summary' => [ + [$result_with_summary], + $result_with_summary_message, + ], + '2 results with summary' => [ + [$result_with_summary, $result_with_summary], + "$result_with_summary_message$result_with_summary_message", + ], + '1 result without summary' => [ + [$result_no_summary], + $messages[0], + ], + '2 results without summary' => [ + [$result_no_summary, $result_no_summary], + $messages[0] . ' ' . $messages[0], + ], + '1 result with summary, 1 result without summary' => [ + [$result_with_summary, $result_no_summary], + $result_with_summary_message . ' ' . $messages[0], + ], + ]; + } + + /** + * Tests errors during a cron update attempt. + * + * @param \Drupal\package_manager\ValidationResult[] $validation_results + * The expected validation results which should be logged. + * @param string $expected_log_message + * The error message should be logged. + * + * @dataProvider providerErrors + */ + public function testErrors(array $validation_results, string $expected_log_message): void { + TestChecker1::setTestResult($validation_results, PreCreateEvent::class); + + $logger = new TestLogger(); + $this->container->get('logger.factory') + ->get('automatic_updates') + ->addLogger($logger); + + $this->container->get('cron')->run(); + $this->assertUpdateStagedTimes(0); + $this->assertTrue($logger->hasRecord("<h2>Unable to complete the update because of errors.</h2>$expected_log_message", RfcLogLevel::ERROR)); + } + }