automatic_updates-8.x-2.x-dev/tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
tests/src/Kernel/AutomaticUpdatesKernelTestBase.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\automatic_updates\Kernel;
use ColinODell\PsrTestLogger\TestLogger;
use Drupal\automatic_updates\CronUpdateRunner;
use Drupal\automatic_updates\ConsoleUpdateSandboxManager;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\package_manager\Validator\SymlinkValidator;
use Drupal\package_manager\Validator\WritableFileSystemValidator;
use Drupal\Tests\automatic_updates\Traits\ValidationTestTrait;
use Drupal\Tests\package_manager\Kernel\PackageManagerKernelTestBase;
/**
* Base class for kernel tests of the Automatic Updates module.
*
* @internal
*/
abstract class AutomaticUpdatesKernelTestBase extends PackageManagerKernelTestBase {
use ValidationTestTrait;
/**
* {@inheritdoc}
*
* TRICKY: due to the way that automatic_updates forcibly disables cron-based
* updating for the end user, we need to override the current default
* configuration BEFORE the module is installed. This triggers config schema
* exceptions. Since none of these tests are interacting with configuration
* anyway, this is a reasonable temporary workaround.
*
* @see ::setUp()
* @see https://www.drupal.org/project/automatic_updates/issues/3284443
* @todo Remove in https://www.drupal.org/project/automatic_updates/issues/3284443
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
// If Package Manager's file system permissions validator is disabled, also
// disable the Automatic Updates validator which wraps it.
if (in_array(WritableFileSystemValidator::class, $this->disableValidators, TRUE)) {
$this->disableValidators[] = 'automatic_updates.validator.file_system_permissions';
}
// If Package Manager's symlink validator is disabled, also disable the
// Automatic Updates validator which wraps it.
if (in_array(SymlinkValidator::class, $this->disableValidators, TRUE)) {
$this->disableValidators[] = 'automatic_updates.validator.symlink';
}
parent::setUp();
// Enable cron updates, which will eventually be the default.
// @todo Remove in https://www.drupal.org/project/automatic_updates/issues/3284443
$this->config('automatic_updates.settings')
->set('unattended', [
'method' => 'web',
'level' => CronUpdateRunner::SECURITY,
])
->save();
// By default, pretend we're running Drupal core 9.8.0 and a non-security
// update to 9.8.1 is available.
$this->setCoreVersion('9.8.0');
$this->setReleaseMetadata([
'drupal' => static::getDrupalRoot() . '/core/modules/package_manager/tests/fixtures/release-history/drupal.9.8.1-security.xml',
]);
// Set a last cron run time so that the cron frequency validator will run
// from a sane state.
// @see \Drupal\automatic_updates\Validator\CronFrequencyValidator
$this->container->get('state')->set('system.cron_last', time());
// Cron updates are not done when running at the command line, so override
// our cron handler's PHP_SAPI constant to a valid value that isn't `cli`.
// The choice of `cgi-fcgi` is arbitrary; see
// https://www.php.net/php_sapi_name for some valid values of PHP_SAPI.
$property = new \ReflectionProperty(CronUpdateRunner::class, 'serverApi');
$property->setValue(NULL, 'cgi-fcgi');
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container): void {
parent::register($container);
// Use the test-only implementations of the regular and cron update runner.
$overrides = [
CronUpdateRunner::class => TestCronUpdateRunner::class,
ConsoleUpdateSandboxManager::class => TestConsoleUpdateStage::class,
];
foreach ($overrides as $service_id => $class) {
if ($container->hasDefinition($service_id)) {
$container->getDefinition($service_id)->setClass($class);
}
}
}
/**
* Performs an update using the console update stage directly.
*/
protected function runConsoleUpdateStage(): void {
$this->container->get(ConsoleUpdateSandboxManager::class)->performUpdate();
}
/**
* Asserts that an exception containing a particular message was logged.
*
* @param string $message
* The message that should have been logged.
* @param \ColinODell\PsrTestLogger\TestLogger $logger
* The logger.
*/
protected function assertExceptionLogged(string $message, TestLogger $logger): void {
$predicate = fn ($record) => str_contains($record['context']['@message'] ?? '', $message);
$this->assertTrue($logger->hasRecordThatPasses($predicate, RfcLogLevel::ERROR));
}
}
/**
* A test-only version of the cron update runner to override and expose internals.
*/
class TestCronUpdateRunner extends CronUpdateRunner {
/**
* {@inheritdoc}
*/
protected function runTerminalUpdateCommand(): never {
// Invoking the terminal command will not work and is not necessary in
// kernel tests. Throw an exception for tests that need to assert that
// the terminal command would have been invoked.
throw new \BadMethodCallException(static::class);
}
}
/**
* A test version of the console update stage to override and expose internals.
*/
class TestConsoleUpdateStage extends ConsoleUpdateSandboxManager {
/**
* {@inheritdoc}
*/
protected string $type = 'automatic_updates:unattended';
/**
* {@inheritdoc}
*/
public function apply(?int $timeout = 600): void {
parent::apply($timeout);
if (\Drupal::state()->get('system.maintenance_mode')) {
$this->logger->info('Unattended update was applied in maintenance mode.');
}
}
/**
* {@inheritdoc}
*/
public function postApply(): void {
if (\Drupal::state()->get('system.maintenance_mode')) {
$this->logger->info('postApply() was called in maintenance mode.');
}
parent::postApply();
}
/**
* {@inheritdoc}
*/
protected function triggerPostApply(string $stage_id): void {
$this->handlePostApply($stage_id);
}
}
