From 82f7c81ec29b63e87678a5dbbbdabb9b5d6eaf73 Mon Sep 17 00:00:00 2001 From: Kevin Wenger Date: Tue, 12 Mar 2024 16:53:55 +0100 Subject: [PATCH 1/3] add configuration settings to enable or disable resolvers --- .github/workflows/ci.yml | 35 ++++++ CHANGELOG.md | 1 + UPGRADE.md | 25 +++++ config/install/timesup.settings.yml | 6 ++ config/schema/timesup.schema.yml | 23 ++++ src/Form/SettingsForm.php | 100 ++++++++++++++++++ src/Periodicity/DailyResolver.php | 7 ++ src/Periodicity/HourlyResolver.php | 7 ++ src/Periodicity/MidnightResolver.php | 7 ++ src/Periodicity/MinutelyResolver.php | 7 ++ src/Periodicity/PeriodicityBaseResolver.php | 11 +- src/Periodicity/WeeklyResolver.php | 7 ++ tests/src/Functional/SettingsFormTest.php | 90 ++++++++++++++++ tests/src/Unit/Resolver/DailyResolverTest.php | 48 ++++++++- .../src/Unit/Resolver/HourlyResolverTest.php | 48 ++++++++- .../Unit/Resolver/MidnightResolverTest.php | 47 +++++++- .../Unit/Resolver/MinutelyResolverTest.php | 48 ++++++++- .../src/Unit/Resolver/WeeklyResolverTest.php | 48 ++++++++- timesup.info.yml | 1 + timesup.install | 30 ++++++ timesup.links.menu.yml | 5 + timesup.permissions.yml | 2 + timesup.routing.yml | 9 ++ timesup.services.yml | 2 +- 24 files changed, 593 insertions(+), 21 deletions(-) create mode 100644 UPGRADE.md create mode 100644 config/install/timesup.settings.yml create mode 100644 config/schema/timesup.schema.yml create mode 100644 src/Form/SettingsForm.php create mode 100644 tests/src/Functional/SettingsFormTest.php create mode 100644 timesup.install create mode 100644 timesup.links.menu.yml create mode 100644 timesup.permissions.yml create mode 100644 timesup.routing.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f70b7d5..f7ac45c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,41 @@ jobs: run: docker-compose -f docker-compose.yml run -u www-data drupal phpunit --no-coverage --group=${{ matrix.module }} --exclude-group=${{ matrix.module }}_functional --configuration=/var/www/html/phpunit.xml continue-on-error: ${{ matrix.experimental }} + + tests-functional: + name: Functional Tests + + runs-on: ubuntu-latest + + strategy: + matrix: + drupal_version: ['9.5', '10.0', '10.1', '10.2', '11.0'] + module: [ 'timesup' ] + experimental: [ false ] + include: + - drupal_version: '11.0' + module: 'timesup' + experimental: true + + steps: + - uses: actions/checkout@v4 + - run: docker-compose -f docker-compose.yml pull --include-deps drupal + - name: Build the docker-compose stack + run: docker-compose -f docker-compose.yml build --pull --build-arg BASE_IMAGE_TAG=${{ matrix.drupal_version }} drupal + continue-on-error: ${{ matrix.experimental }} + - name: Up a persistant Docker Container + run: docker-compose -f docker-compose.yml up -d drupal + - name: wait on Docker to be ready, especially Apache that takes many seconds to be up + run: docker-compose exec -T drupal wait-for-it drupal:80 -t 60 + - name: wait on Docker to be ready, especially MariaDB that takes many seconds to be up + run: docker-compose exec -T drupal wait-for-it db:3306 -t 60 + - name: Bootstrap Drupal + run: docker-compose -f docker-compose.yml exec -T -u www-data drupal drush site-install standard --db-url="mysql://drupal:drupal@db/drupal" -y + continue-on-error: ${{ matrix.experimental }} + - name: Run tests + run: docker-compose -f docker-compose.yml exec -T -u www-data drupal phpunit --no-coverage --group=${{ matrix.module }}_functional --configuration=/var/www/html/phpunit.xml + continue-on-error: ${{ matrix.experimental }} + upgrade-status: name: Upgrade Status runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 14842e9..3a2aecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - add tests with drupal 10.3 +- add configuration settings `timesup.settings.resolvers` to enable or disable resolvers. ## [2.0.1] - 2024-03-01 ### Fixed diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000..5c8fc86 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,25 @@ +# Upgrade guide + +## Upgrade from 2.0.1 to 2.1.x + +This guide is intended to describe the process of upgrading +Times'up from version 2.0.1 to version 2.1.x. + +### Changes in configuration + +Running `drush updb` and exporting the changed configuration should be sufficient +to convert to the new configuration structure. + +The `timesup.settings` configuration object has been introduced. This allows you to enable only the resolvers you actively use, reducing stress on cache and database invalidation. + +```yaml +resolvers: + minutely: false + hourly: true + daily: true + midnight: true + weekly: true +``` + + + diff --git a/config/install/timesup.settings.yml b/config/install/timesup.settings.yml new file mode 100644 index 0000000..898ae41 --- /dev/null +++ b/config/install/timesup.settings.yml @@ -0,0 +1,6 @@ +resolvers: + minutely: false + hourly: true + daily: true + midnight: true + weekly: true diff --git a/config/schema/timesup.schema.yml b/config/schema/timesup.schema.yml new file mode 100644 index 0000000..da94e14 --- /dev/null +++ b/config/schema/timesup.schema.yml @@ -0,0 +1,23 @@ +timesup.settings: + type: config_object + label: Times'up settings + mapping: + resolvers: + type: mapping + label: 'Resolvers' + mapping: + minutely: + type: boolean + label: Enable minutely's Times'up cache resolvers + hourly: + type: boolean + label: Enable daily's Times'up cache resolvers + daily: + type: boolean + label: Enable daily's Times'up cache resolvers + midnight: + type: boolean + label: Enable midnight's Times'up cache resolvers + weekly: + type: boolean + label: Enable weekly's Times'up cache resolvers diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php new file mode 100644 index 0000000..28f3e9d --- /dev/null +++ b/src/Form/SettingsForm.php @@ -0,0 +1,100 @@ +config('timesup.settings')->get('resolvers'); + + // Submitted form values should be nested. + $form['#tree'] = TRUE; + + $form['resolvers'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Resolvers'), + '#description' => $this->t('Please enable resolvers you actively use to avoid cache/database invalidation stress.'), + ]; + + $form['resolvers']['minutely'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Minutely resolver.'), + '#default_value' => !empty($resolvers['minutely']), + '#description' => $this->t('Enable this feature will invalid cache-tags timesup:minutely every minutes (heavy stress sensitive).'), + ]; + + $form['resolvers']['hourly'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Hourly resolver.'), + '#default_value' => !empty($resolvers['hourly']), + '#description' => $this->t('Enable this feature will invalid cache-tags timesup:hourly every hour.'), + ]; + + $form['resolvers']['daily'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Daily resolver.'), + '#default_value' => !empty($resolvers['daily']), + '#description' => $this->t('Enable this feature will invalid cache-tags timesup:daily every day.'), + ]; + + $form['resolvers']['midnight'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Midnight resolver.'), + '#default_value' => !empty($resolvers['midnight']), + '#description' => $this->t('Enable this feature will invalid cache-tags timesup:midnight every day at 00:00:00.'), + ]; + + $form['resolvers']['weekly'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable Weekly resolver.'), + '#default_value' => !empty($resolvers['weekly']), + '#description' => $this->t('Enable this feature will invalid cache-tags timesup:weekly every weeks.'), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + parent::submitForm($form, $form_state); + + $this->config('timesup.settings') + ->set('resolvers', [ + 'minutely' => (bool) $form_state->getValue(['resolvers', 'minutely']), + 'hourly' => (bool) $form_state->getValue(['resolvers', 'hourly']), + 'daily' => (bool) $form_state->getValue(['resolvers', 'daily']), + 'midnight' => (bool) $form_state->getValue(['resolvers', 'midnight']), + 'weekly' => (bool) $form_state->getValue(['resolvers', 'weekly']), + ]) + ->save(); + } + + /** + * {@inheritdoc} + */ + protected function getEditableConfigNames() { + return [ + 'timesup.settings', + ]; + } + +} diff --git a/src/Periodicity/DailyResolver.php b/src/Periodicity/DailyResolver.php index dbb2b6d..59490c8 100644 --- a/src/Periodicity/DailyResolver.php +++ b/src/Periodicity/DailyResolver.php @@ -20,6 +20,13 @@ final class DailyResolver extends PeriodicityBaseResolver { * {@inheritdoc} */ public function shouldApply(): bool { + $settings = $this->configFactory->get('timesup.settings'); + $resolvers = $settings->get('resolvers'); + + if (!isset($resolvers['daily']) || !$resolvers['daily']) { + return FALSE; + } + $last_run_per_day = $this->state->get($this->getLastRunKey()); return !($this->time->getRequestTime() - $last_run_per_day < 86400); } diff --git a/src/Periodicity/HourlyResolver.php b/src/Periodicity/HourlyResolver.php index 5a8e070..4261813 100644 --- a/src/Periodicity/HourlyResolver.php +++ b/src/Periodicity/HourlyResolver.php @@ -20,6 +20,13 @@ final class HourlyResolver extends PeriodicityBaseResolver { * {@inheritdoc} */ public function shouldApply(): bool { + $settings = $this->configFactory->get('timesup.settings'); + $resolvers = $settings->get('resolvers'); + + if (!isset($resolvers['hourly']) || !$resolvers['hourly']) { + return FALSE; + } + $last_run_per_week = $this->state->get($this->getLastRunKey()); return !($this->time->getRequestTime() - $last_run_per_week < 3600); } diff --git a/src/Periodicity/MidnightResolver.php b/src/Periodicity/MidnightResolver.php index 5d2e82a..50da518 100644 --- a/src/Periodicity/MidnightResolver.php +++ b/src/Periodicity/MidnightResolver.php @@ -23,6 +23,13 @@ class MidnightResolver extends PeriodicityBaseResolver { * {@inheritdoc} */ public function shouldApply(): bool { + $settings = $this->configFactory->get('timesup.settings'); + $resolvers = $settings->get('resolvers'); + + if (!isset($resolvers['midnight']) || !$resolvers['midnight']) { + return FALSE; + } + $today_midnight = $this->getTodayMidnight(); $last_run = $this->state->get($this->getLastRunKey()); return $last_run <= $today_midnight->getTimestamp(); diff --git a/src/Periodicity/MinutelyResolver.php b/src/Periodicity/MinutelyResolver.php index 22a017e..2b132c2 100644 --- a/src/Periodicity/MinutelyResolver.php +++ b/src/Periodicity/MinutelyResolver.php @@ -20,6 +20,13 @@ final class MinutelyResolver extends PeriodicityBaseResolver { * {@inheritdoc} */ public function shouldApply(): bool { + $settings = $this->configFactory->get('timesup.settings'); + $resolvers = $settings->get('resolvers'); + + if (!isset($resolvers['minutely']) || !$resolvers['minutely']) { + return FALSE; + } + $last_run_per_minute = $this->state->get($this->getLastRunKey()); return !($this->time->getRequestTime() - $last_run_per_minute < 60); } diff --git a/src/Periodicity/PeriodicityBaseResolver.php b/src/Periodicity/PeriodicityBaseResolver.php index 179b9d8..67a5df7 100644 --- a/src/Periodicity/PeriodicityBaseResolver.php +++ b/src/Periodicity/PeriodicityBaseResolver.php @@ -4,6 +4,7 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -22,6 +23,11 @@ abstract class PeriodicityBaseResolver implements PeriodicityResolverInterface { */ protected $cacheTagsInvalidator; + /** + * The config factory. + */ + protected ConfigFactoryInterface $configFactory; + /** * The state service. * @@ -48,6 +54,8 @@ abstract class PeriodicityBaseResolver implements PeriodicityResolverInterface { * * @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator * The cache tags invalidator. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The factory for configuration objects. * @param \Drupal\Core\State\StateInterface $state * The state service. * @param \Drupal\Component\Datetime\TimeInterface $time @@ -55,8 +63,9 @@ abstract class PeriodicityBaseResolver implements PeriodicityResolverInterface { * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory * The logger channel factory service. */ - public function __construct(CacheTagsInvalidatorInterface $cache_tags_invalidator, StateInterface $state, TimeInterface $time, LoggerChannelFactoryInterface $logger_factory) { + public function __construct(CacheTagsInvalidatorInterface $cache_tags_invalidator, ConfigFactoryInterface $config_factory, StateInterface $state, TimeInterface $time, LoggerChannelFactoryInterface $logger_factory) { $this->cacheTagsInvalidator = $cache_tags_invalidator; + $this->configFactory = $config_factory; $this->state = $state; $this->time = $time; $this->logger = $logger_factory->get('timesup'); diff --git a/src/Periodicity/WeeklyResolver.php b/src/Periodicity/WeeklyResolver.php index f427e15..2a13091 100644 --- a/src/Periodicity/WeeklyResolver.php +++ b/src/Periodicity/WeeklyResolver.php @@ -20,6 +20,13 @@ final class WeeklyResolver extends PeriodicityBaseResolver { * {@inheritdoc} */ public function shouldApply(): bool { + $settings = $this->configFactory->get('timesup.settings'); + $resolvers = $settings->get('resolvers'); + + if (!isset($resolvers['weekly']) || !$resolvers['weekly']) { + return FALSE; + } + $last_run_per_week = $this->state->get($this->getLastRunKey()); return !($this->time->getRequestTime() - $last_run_per_week < 604800); } diff --git a/tests/src/Functional/SettingsFormTest.php b/tests/src/Functional/SettingsFormTest.php new file mode 100644 index 0000000..adb3794 --- /dev/null +++ b/tests/src/Functional/SettingsFormTest.php @@ -0,0 +1,90 @@ +drupalCreateUser(); + $this->drupalLogin($account); + + $this->drupalGet('admin/config/timesup/settings'); + $this->assertSession()->statusCodeEquals(403); + + // Create another user with propre permission for tests. + $account = $this->drupalCreateUser(['administer timesup']); + $this->drupalLogin($account); + + $this->drupalGet('admin/config/timesup/settings'); + $this->assertSession()->statusCodeEquals(200); + } + + /** + * Ensure the configuration storage works as expected. + */ + public function testConfigurationPersistance() { + $settings = $this->container->get('config.factory')->getEditable('timesup.settings'); + $settings->set('resolvers', [ + 'minutely' => FALSE, + 'hourly' => FALSE, + 'daily' => TRUE, + 'midnight' => TRUE, + 'weekly' => TRUE, + ])->save(); + + // Create another user with propre permission for tests. + $account = $this->drupalCreateUser(['administer timesup']); + $this->drupalLogin($account); + + $this->drupalGet('admin/config/timesup/settings'); + $this->assertSession()->statusCodeEquals(200); + + $this->assertSession()->checkboxNotChecked('resolvers[minutely]'); + $this->assertSession()->checkboxNotChecked('resolvers[hourly]'); + $this->assertSession()->checkboxChecked('resolvers[daily]'); + $this->assertSession()->checkboxChecked('resolvers[midnight]'); + $this->assertSession()->checkboxChecked('resolvers[weekly]'); + + // Checking the minutely resolver should be presisted. + $this->submitForm(["resolvers[minutely]" => TRUE, "resolvers[weekly]" => FALSE], 'Save'); + + $this->drupalGet('admin/config/timesup/settings'); + $this->assertSession()->checkboxChecked('resolvers[minutely]'); + $this->assertSession()->checkboxNotChecked('resolvers[hourly]'); + $this->assertSession()->checkboxChecked('resolvers[daily]'); + $this->assertSession()->checkboxChecked('resolvers[midnight]'); + $this->assertSession()->checkboxNotChecked('resolvers[weekly]'); + } + +} diff --git a/tests/src/Unit/Resolver/DailyResolverTest.php b/tests/src/Unit/Resolver/DailyResolverTest.php index 93a9bdf..32488a2 100644 --- a/tests/src/Unit/Resolver/DailyResolverTest.php +++ b/tests/src/Unit/Resolver/DailyResolverTest.php @@ -4,6 +4,8 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\State\StateInterface; @@ -40,6 +42,20 @@ class DailyResolverTest extends UnitTestCase { */ protected $state; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The mocked configuration object timesup.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $settings; + /** * The logger. * @@ -68,6 +84,11 @@ public function setUp(): void { parent::setUp(); $this->cacheTagsInvalidator = $this->createMock(CacheTagsInvalidatorInterface::class); + // Create a mock config factory and config object. + $this->configFactory = $this->createMock(ConfigFactoryInterface::class); + $this->settings = $this->createMock(ImmutableConfig::class); + $this->configFactory->method('get')->with('timesup.settings')->willReturn($this->settings); + $this->state = $this->createMock(StateInterface::class); $this->time = $this->createMock(TimeInterface::class); @@ -83,7 +104,7 @@ public function setUp(): void { * @covers ::getCacheTags */ public function testGetCacheTags() { - $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $tags = $this->invokeMethod($resolver, 'getCacheTags'); $this->assertSame(['timesup', 'timesup:daily'], $tags); } @@ -92,23 +113,42 @@ public function testGetCacheTags() { * @covers ::getLastRunKey */ public function testGetLastRunKey() { - $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $key = $this->invokeMethod($resolver, 'getLastRunKey'); $this->assertEquals('timesup.last_run.DailyResolver', $key); } + /** + * @covers ::shouldApply + */ + public function testShouldApplySettingsDisabled() { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['daily' => FALSE]); + + $this->state->expects($this->never()) + ->method('get')->with('timesup.last_run.DailyResolver'); + $this->time->expects($this->never()) + ->method('getRequestTime'); + + $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); + $this->assertFalse($resolver->shouldApply()); + } + /** * @covers ::shouldApply * * @dataProvider shouldApplyProvider */ public function testShouldApply($request_time, $last_run, $expected) { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['daily' => TRUE]); + $this->state->expects($this->once()) ->method('get')->with('timesup.last_run.DailyResolver')->willReturn($last_run); $this->time->expects($this->once()) ->method('getRequestTime')->willReturn($request_time); - $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $this->assertEquals($expected, $resolver->shouldApply()); } @@ -142,7 +182,7 @@ public function testPurge() { $this->logger->expects($this->once()) ->method('notice')->with("Purging Time's up time-sensitive cache-tags: timesup, timesup:daily."); - $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new DailyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $resolver->purge(); } diff --git a/tests/src/Unit/Resolver/HourlyResolverTest.php b/tests/src/Unit/Resolver/HourlyResolverTest.php index 63ab9a5..8a4dd2c 100644 --- a/tests/src/Unit/Resolver/HourlyResolverTest.php +++ b/tests/src/Unit/Resolver/HourlyResolverTest.php @@ -4,6 +4,8 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\State\StateInterface; @@ -40,6 +42,20 @@ class HourlyResolverTest extends UnitTestCase { */ protected $state; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The mocked configuration object timesup.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $settings; + /** * The logger. * @@ -68,6 +84,11 @@ public function setUp(): void { parent::setUp(); $this->cacheTagsInvalidator = $this->createMock(CacheTagsInvalidatorInterface::class); + // Create a mock config factory and config object. + $this->configFactory = $this->createMock(ConfigFactoryInterface::class); + $this->settings = $this->createMock(ImmutableConfig::class); + $this->configFactory->method('get')->with('timesup.settings')->willReturn($this->settings); + $this->state = $this->createMock(StateInterface::class); $this->time = $this->createMock(TimeInterface::class); @@ -83,7 +104,7 @@ public function setUp(): void { * @covers ::getCacheTags */ public function testGetCacheTags() { - $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $tags = $this->invokeMethod($resolver, 'getCacheTags'); $this->assertSame(['timesup', 'timesup:hourly'], $tags); } @@ -92,23 +113,42 @@ public function testGetCacheTags() { * @covers ::getLastRunKey */ public function testGetLastRunKey() { - $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $key = $this->invokeMethod($resolver, 'getLastRunKey'); $this->assertEquals('timesup.last_run.HourlyResolver', $key); } + /** + * @covers ::shouldApply + */ + public function testShouldApplySettingsDisabled() { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['hourly' => FALSE]); + + $this->state->expects($this->never()) + ->method('get')->with('timesup.last_run.HourlyResolver'); + $this->time->expects($this->never()) + ->method('getRequestTime'); + + $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); + $this->assertFalse($resolver->shouldApply()); + } + /** * @covers ::shouldApply * * @dataProvider shouldApplyProvider */ public function testShouldApply($request_time, $last_run, $expected) { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['hourly' => TRUE]); + $this->state->expects($this->once()) ->method('get')->with('timesup.last_run.HourlyResolver')->willReturn($last_run); $this->time->expects($this->once()) ->method('getRequestTime')->willReturn($request_time); - $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $this->assertEquals($expected, $resolver->shouldApply()); } @@ -142,7 +182,7 @@ public function testPurge() { $this->logger->expects($this->once()) ->method('notice')->with("Purging Time's up time-sensitive cache-tags: timesup, timesup:hourly."); - $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new HourlyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $resolver->purge(); } diff --git a/tests/src/Unit/Resolver/MidnightResolverTest.php b/tests/src/Unit/Resolver/MidnightResolverTest.php index 93b2423..7951835 100644 --- a/tests/src/Unit/Resolver/MidnightResolverTest.php +++ b/tests/src/Unit/Resolver/MidnightResolverTest.php @@ -5,6 +5,8 @@ use Drupal\Component\Datetime\DateTimePlus; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\State\StateInterface; @@ -41,6 +43,20 @@ final class MidnightResolverTest extends UnitTestCase { */ protected $state; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The mocked configuration object timesup.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $settings; + /** * The logger. * @@ -69,6 +85,11 @@ public function setUp(): void { parent::setUp(); $this->cacheTagsInvalidator = $this->createMock(CacheTagsInvalidatorInterface::class); + // Create a mock config factory and config object. + $this->configFactory = $this->createMock(ConfigFactoryInterface::class); + $this->settings = $this->createMock(ImmutableConfig::class); + $this->configFactory->method('get')->with('timesup.settings')->willReturn($this->settings); + $this->state = $this->createMock(StateInterface::class); $this->time = $this->createMock(TimeInterface::class); @@ -84,7 +105,7 @@ public function setUp(): void { * @covers ::getCacheTags */ public function testGetCacheTags() { - $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $tags = $this->invokeMethod($resolver, 'getCacheTags'); $this->assertSame(['timesup', 'timesup:midnight'], $tags); } @@ -93,17 +114,36 @@ public function testGetCacheTags() { * @covers ::getLastRunKey */ public function testGetLastRunKey() { - $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $key = $this->invokeMethod($resolver, 'getLastRunKey'); $this->assertEquals('timesup.last_run.MidnightResolver', $key); } + /** + * @covers ::shouldApply + */ + public function testShouldApplySettingsDisabled() { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['midnight' => FALSE]); + + $this->state->expects($this->never()) + ->method('get')->with('timesup.last_run.MidnightResolver'); + $this->time->expects($this->never()) + ->method('getRequestTime'); + + $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); + $this->assertFalse($resolver->shouldApply()); + } + /** * @covers ::shouldApply * * @dataProvider shouldApplyProvider */ public function testShouldApply($request_time, $last_run, $expected) { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['midnight' => TRUE]); + $this->state->expects($this->any()) ->method('get')->willReturn($last_run); $this->time->expects($this->any()) @@ -116,6 +156,7 @@ public function testShouldApply($request_time, $last_run, $expected) { $midnightResolverMock = $this->getMockBuilder(MidnightResolver::class) ->setConstructorArgs([ $this->cacheTagsInvalidator, + $this->configFactory, $this->state, $this->time, $this->loggerFactory, @@ -163,7 +204,7 @@ public function testPurge() { $this->logger->expects($this->once()) ->method('notice')->with("Purging Time's up time-sensitive cache-tags: timesup, timesup:midnight."); - $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MidnightResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $resolver->purge(); } diff --git a/tests/src/Unit/Resolver/MinutelyResolverTest.php b/tests/src/Unit/Resolver/MinutelyResolverTest.php index a766e3a..ec5f3a2 100644 --- a/tests/src/Unit/Resolver/MinutelyResolverTest.php +++ b/tests/src/Unit/Resolver/MinutelyResolverTest.php @@ -4,6 +4,8 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\State\StateInterface; @@ -40,6 +42,20 @@ class MinutelyResolverTest extends UnitTestCase { */ protected $state; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The mocked configuration object timesup.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $settings; + /** * The logger. * @@ -68,6 +84,11 @@ public function setUp(): void { parent::setUp(); $this->cacheTagsInvalidator = $this->createMock(CacheTagsInvalidatorInterface::class); + // Create a mock config factory and config object. + $this->configFactory = $this->createMock(ConfigFactoryInterface::class); + $this->settings = $this->createMock(ImmutableConfig::class); + $this->configFactory->method('get')->with('timesup.settings')->willReturn($this->settings); + $this->state = $this->createMock(StateInterface::class); $this->time = $this->createMock(TimeInterface::class); @@ -83,7 +104,7 @@ public function setUp(): void { * @covers ::getCacheTags */ public function testGetCacheTags() { - $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $tags = $this->invokeMethod($resolver, 'getCacheTags'); $this->assertSame(['timesup', 'timesup:minutely'], $tags); } @@ -92,23 +113,42 @@ public function testGetCacheTags() { * @covers ::getLastRunKey */ public function testGetLastRunKey() { - $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $key = $this->invokeMethod($resolver, 'getLastRunKey'); $this->assertEquals('timesup.last_run.MinutelyResolver', $key); } + /** + * @covers ::shouldApply + */ + public function testShouldApplySettingsDisabled() { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['minutely' => FALSE]); + + $this->state->expects($this->never()) + ->method('get')->with('timesup.last_run.MinutelyResolver'); + $this->time->expects($this->never()) + ->method('getRequestTime'); + + $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); + $this->assertFalse($resolver->shouldApply()); + } + /** * @covers ::shouldApply * * @dataProvider shouldApplyProvider */ public function testShouldApply($request_time, $last_run, $expected) { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['minutely' => TRUE]); + $this->state->expects($this->once()) ->method('get')->with('timesup.last_run.MinutelyResolver')->willReturn($last_run); $this->time->expects($this->once()) ->method('getRequestTime')->willReturn($request_time); - $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $this->assertEquals($expected, $resolver->shouldApply()); } @@ -144,7 +184,7 @@ public function testPurge() { $this->logger->expects($this->once()) ->method('notice')->with("Purging Time's up time-sensitive cache-tags: timesup, timesup:minutely."); - $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new MinutelyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $resolver->purge(); } diff --git a/tests/src/Unit/Resolver/WeeklyResolverTest.php b/tests/src/Unit/Resolver/WeeklyResolverTest.php index 70be6e4..e391244 100644 --- a/tests/src/Unit/Resolver/WeeklyResolverTest.php +++ b/tests/src/Unit/Resolver/WeeklyResolverTest.php @@ -4,6 +4,8 @@ use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheTagsInvalidatorInterface; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelInterface; use Drupal\Core\State\StateInterface; @@ -40,6 +42,20 @@ class WeeklyResolverTest extends UnitTestCase { */ protected $state; + /** + * The config factory service. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The mocked configuration object timesup.settings. + * + * @var \Drupal\Core\Config\ImmutableConfig + */ + protected $settings; + /** * The logger. * @@ -68,6 +84,11 @@ public function setUp(): void { parent::setUp(); $this->cacheTagsInvalidator = $this->createMock(CacheTagsInvalidatorInterface::class); + // Create a mock config factory and config object. + $this->configFactory = $this->createMock(ConfigFactoryInterface::class); + $this->settings = $this->createMock(ImmutableConfig::class); + $this->configFactory->method('get')->with('timesup.settings')->willReturn($this->settings); + $this->state = $this->createMock(StateInterface::class); $this->time = $this->createMock(TimeInterface::class); @@ -83,7 +104,7 @@ public function setUp(): void { * @covers ::getCacheTags */ public function testGetCacheTags() { - $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $tags = $this->invokeMethod($resolver, 'getCacheTags'); $this->assertSame(['timesup', 'timesup:weekly'], $tags); } @@ -92,23 +113,42 @@ public function testGetCacheTags() { * @covers ::getLastRunKey */ public function testGetLastRunKey() { - $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $key = $this->invokeMethod($resolver, 'getLastRunKey'); $this->assertEquals('timesup.last_run.WeeklyResolver', $key); } + /** + * @covers ::shouldApply + */ + public function testShouldApplySettingsDisabled() { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['weekly' => FALSE]); + + $this->state->expects($this->never()) + ->method('get')->with('timesup.last_run.WeeklyResolver'); + $this->time->expects($this->never()) + ->method('getRequestTime'); + + $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); + $this->assertFalse($resolver->shouldApply()); + } + /** * @covers ::shouldApply * * @dataProvider shouldApplyProvider */ public function testShouldApply($request_time, $last_run, $expected) { + $this->settings->expects($this->once()) + ->method('get')->with('resolvers')->willReturn(['weekly' => TRUE]); + $this->state->expects($this->once()) ->method('get')->with('timesup.last_run.WeeklyResolver')->willReturn($last_run); $this->time->expects($this->once()) ->method('getRequestTime')->willReturn($request_time); - $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $this->assertEquals($expected, $resolver->shouldApply()); } @@ -146,7 +186,7 @@ public function testPurge() { $this->logger->expects($this->once()) ->method('notice')->with("Purging Time's up time-sensitive cache-tags: timesup, timesup:weekly."); - $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->state, $this->time, $this->loggerFactory); + $resolver = new WeeklyResolver($this->cacheTagsInvalidator, $this->configFactory, $this->state, $this->time, $this->loggerFactory); $resolver->purge(); } diff --git a/timesup.info.yml b/timesup.info.yml index 6af014f..b68822c 100644 --- a/timesup.info.yml +++ b/timesup.info.yml @@ -3,3 +3,4 @@ description: 'Provides cache-tags to deal with time sensitive data.' type: module core_version_requirement: ^9.5 || ^10 || ^11 package: Cache +configure: timesup.settings diff --git a/timesup.install b/timesup.install new file mode 100644 index 0000000..ea9a513 --- /dev/null +++ b/timesup.install @@ -0,0 +1,30 @@ +getEditable('timesup.settings'); + + // Set hosts. + $config->set('resolvers', [ + 'minutely' => 1, + 'hourly' => 1, + 'daily' => 1, + 'midnight' => 1, + 'weekly' => 1, + ]); + + // Save the configuration. + $config->save(); +} diff --git a/timesup.links.menu.yml b/timesup.links.menu.yml new file mode 100644 index 0000000..b226670 --- /dev/null +++ b/timesup.links.menu.yml @@ -0,0 +1,5 @@ +timesup.settings: + title: Times'up + route_name: timesup.settings + description: Configure features of Times'up. + parent: system.admin_config_system diff --git a/timesup.permissions.yml b/timesup.permissions.yml new file mode 100644 index 0000000..a4ffe0b --- /dev/null +++ b/timesup.permissions.yml @@ -0,0 +1,2 @@ +'administer timesup': + title: Administer Times'up diff --git a/timesup.routing.yml b/timesup.routing.yml new file mode 100644 index 0000000..8298f26 --- /dev/null +++ b/timesup.routing.yml @@ -0,0 +1,9 @@ +timesup.settings: + path: '/admin/config/timesup/settings' + defaults: + _form: '\Drupal\timesup\Form\SettingsForm' + _title: 'Settings' + requirements: + _permission: 'administer timesup' + options: + _admin_route: TRUE diff --git a/timesup.services.yml b/timesup.services.yml index f23161d..381a5d0 100644 --- a/timesup.services.yml +++ b/timesup.services.yml @@ -7,7 +7,7 @@ services: timesup.periodicity_resolver.base: private: true class: Drupal\timesup\Periodicity\PeriodicityBaseResolver - arguments: ['@cache_tags.invalidator', '@state', '@datetime.time', '@logger.factory'] + arguments: ['@cache_tags.invalidator', '@config.factory', '@state', '@datetime.time', '@logger.factory'] timesup.periodicity_resolver.minutely: class: Drupal\timesup\Periodicity\MinutelyResolver From c3046625eef572e3e55952596b8484b73f4528cb Mon Sep 17 00:00:00 2001 From: Kevin Wenger Date: Tue, 12 Mar 2024 17:12:50 +0100 Subject: [PATCH 2/3] run functional tests on drupal 11 --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7ac45c..54a6d1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - drupal_version: ['9.5', '10.0', '10.1', '10.2', '10.3'] + drupal_version: ['9.5', '10.0', '10.1', '10.2', '10.3', '11.0'] module: ['timesup'] experimental: [ false ] @@ -34,9 +34,13 @@ jobs: strategy: matrix: - drupal_version: ['9.5', '10.0', '10.1', '10.2', '11.0'] + drupal_version: ['9.5', '10.0', '10.1', '10.2', '10.3', '11.0'] module: [ 'timesup' ] experimental: [ false ] + exclude: + - drupal_version: '11.0' + module: 'timesup' + experimental: false include: - drupal_version: '11.0' module: 'timesup' From 1c7ec496ed9d8a4186c0c86da253417fa00fd278 Mon Sep 17 00:00:00 2001 From: Kevin Wenger Date: Thu, 4 Apr 2024 09:16:13 +0200 Subject: [PATCH 3/3] Update config/schema/timesup.schema.yml Co-authored-by: Gilles Doge --- config/schema/timesup.schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/schema/timesup.schema.yml b/config/schema/timesup.schema.yml index da94e14..0765fd8 100644 --- a/config/schema/timesup.schema.yml +++ b/config/schema/timesup.schema.yml @@ -11,7 +11,7 @@ timesup.settings: label: Enable minutely's Times'up cache resolvers hourly: type: boolean - label: Enable daily's Times'up cache resolvers + label: Enable hourly Times'up cache resolvers daily: type: boolean label: Enable daily's Times'up cache resolvers