diff --git a/src/Plugin/views/query/SearchApiQuery.php b/src/Plugin/views/query/SearchApiQuery.php index 8eb7cd9d..84f8f9b9 100644 --- a/src/Plugin/views/query/SearchApiQuery.php +++ b/src/Plugin/views/query/SearchApiQuery.php @@ -716,6 +716,21 @@ class SearchApiQuery extends QueryPluginBase { return $tags; } + /** + * @inheritDoc + */ + public function getCacheMaxAge() { + $max_age = parent::getCacheMaxAge(); + + $query = $this->getSearchApiQuery(); + if ($query instanceof CacheableDependencyInterface) { + $max_age = Cache::mergeMaxAges($query->getCacheMaxAge(), $max_age); + } + + return $max_age; + } + + /** * Retrieves the conditions placed on this query. * diff --git a/tests/search_api_test_views/search_api_test_views.module b/tests/search_api_test_views/search_api_test_views.module index 739ef892..95074fec 100644 --- a/tests/search_api_test_views/search_api_test_views.module +++ b/tests/search_api_test_views/search_api_test_views.module @@ -5,6 +5,7 @@ * Contains hook implementations for the Search API Views Test module. */ +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityListBuilder; use Drupal\search_api\Query\QueryInterface; @@ -12,14 +13,24 @@ use Drupal\search_api\Query\QueryInterface; /** * Implements hook_search_api_query_alter(). * - * Prints the contents of the "search_api_retrieved_field_values" query option - * to the page (if present) so it can be checked by the testing code. + * - Prints the contents of the "search_api_retrieved_field_values" query option + * to the page (if present) so it can be checked by the testing code. + * - Alters the query to include custom cacheability metadata, so that we can + * test if modules can alter the cacheability of search queries. */ function search_api_test_views_search_api_query_alter(QueryInterface $query) { $fields = $query->getOption('search_api_retrieved_field_values'); if ($fields) { \Drupal::messenger()->addStatus("'" . implode("' '", $fields) . "'"); } + + if (\Drupal::state()->get('search_api_test_views.alter_query_cacheability_metadata', FALSE)) { + if ($query instanceof RefinableCacheableDependencyInterface) { + $query->addCacheContexts(['search_api_test_context']); + $query->addCacheTags(['search_api:test_tag']); + $query->mergeCacheMaxAge(100); + } + } } /** diff --git a/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php b/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php index 84800ff9..e526c623 100644 --- a/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php +++ b/tests/src/Kernel/Views/ViewsCacheInvalidationTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\search_api\Kernel\Views; use Drupal\Core\Cache\Cache; -use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; @@ -31,7 +30,7 @@ class ViewsCacheInvalidationTest extends KernelTestBase { /** * The display ID used in the test. */ - const TEST_VIEW_DISPLAY_ID = 'default'; + const TEST_VIEW_DISPLAY_ID = 'page_1'; /** * The entity type manager. @@ -54,13 +53,6 @@ class ViewsCacheInvalidationTest extends KernelTestBase { */ protected $renderer; - /** - * The render cache. - * - * @var \Drupal\Core\Render\PlaceholderingRenderCache - */ - protected $renderCache; - /** * The cache tags invalidator. * @@ -146,16 +138,9 @@ class ViewsCacheInvalidationTest extends KernelTestBase { $this->entityTypeManager = $this->container->get('entity_type.manager'); $this->viewExecutableFactory = $this->container->get('views.executable'); $this->renderer = $this->container->get('renderer'); - $this->renderCache = $this->container->get('render_cache'); $this->cacheTagsInvalidator = $this->container->get('cache_tags.invalidator'); $this->currentUser = $this->container->get('current_user'); - DateFormat::create([ - 'id' => 'fallback', - 'label' => 'Fallback', - 'pattern' => 'Y-m-d', - ])->save(); - // Use the test search index from the search_api_test_db module. $this->index = Index::load('test_node_index'); @@ -184,9 +169,9 @@ class ViewsCacheInvalidationTest extends KernelTestBase { } /** - * Tests that a cached views display is invalidated at the right occasions. + * Tests that a cached views query result is invalidated at the right moments. */ - public function testDisplayCacheInvalidation() { + public function testQueryCacheInvalidation() { // We are testing two variants of the view, one for users that have // permission to view unpublished entities, and one for users that do not. // Initially both variants should be uncached. diff --git a/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php b/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php new file mode 100644 index 00000000..12566585 --- /dev/null +++ b/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php @@ -0,0 +1,231 @@ +prophesize(CacheContextsManager::class); + $cache_contexts_manager->assertValidTokens(Argument::any())->willReturn(TRUE); +// $cache_contexts_manager = $container->get('cache_contexts_manager'); +// $contexts = $cache_contexts_manager->getAll(); +// $contexts[] = 'search_api_test_context'; + $container->set('cache_contexts_manager', $cache_contexts_manager->reveal()); + } + + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installSchema('node', ['node_access']); + $this->installSchema('search_api', ['search_api_item']); + $this->installSchema('system', ['sequences']); + + $this->installEntitySchema('node'); + $this->installEntitySchema('search_api_task'); + $this->installEntitySchema('user'); + + $this->installConfig([ + 'node', + 'search_api', + 'search_api_test_node_indexing', + 'search_api_test_views', + ]); + + $this->entityTypeManager = $this->container->get('entity_type.manager'); + $this->viewExecutableFactory = $this->container->get('views.executable'); + $this->currentUser = $this->container->get('current_user'); + $this->state = $this->container->get('state'); + + // Use the test search index from the search_api_test_db module. + $this->index = Index::load('test_node_index'); + } + + /** + * Tests that an exported view contains the right cacheability metadata. + */ + public function testViewExport() { + $expected_cacheability_metadata = [ + 'contexts' => [ + 'languages:language_content', + 'languages:language_interface', + 'url.query_args', + 'user.node_grants:view', + ], + 'tags' => [ + 'config:search_api.index.test_node_index', + ], + 'max-age' => -1, + ]; + $view = $this->getView(); + $this->assertViewCacheabilityMetadata($view, $expected_cacheability_metadata); + + $view_config = $this->config('views.view.' . self::TEST_VIEW_ID); + $this->assertViewConfigCacheabilityMetadata($view_config, $expected_cacheability_metadata); + + $this->state->set('search_api_test_views.alter_query_cacheability_metadata', TRUE); + $view->save(); + + $expected_cacheability_metadata['contexts'][] = 'search_api_test_context'; + $expected_cacheability_metadata['tags'][] = 'search_api:test_tag'; + $expected_cacheability_metadata['max-age'] = 100; + + $view = $this->getView(); + $this->assertViewCacheabilityMetadata($view, $expected_cacheability_metadata); + + $view_config = $this->config('views.view.' . self::TEST_VIEW_ID); + $this->assertViewConfigCacheabilityMetadata($view_config, $expected_cacheability_metadata); + } + + protected function assertViewCacheabilityMetadata(ViewExecutable $view, array $expected_cacheability_metadata) { + foreach (self::TEST_VIEW_DISPLAY_IDS as $display_id) { + $view->setDisplay($display_id); + $display = $view->getDisplay(); + $actual_cacheability_metadata = $display->getCacheMetadata(); + + $this->assertArrayEquals($expected_cacheability_metadata['contexts'], $actual_cacheability_metadata->getCacheContexts()); + $this->assertArrayEquals($expected_cacheability_metadata['tags'], $actual_cacheability_metadata->getCacheTags()); + $this->assertEquals($expected_cacheability_metadata['max-age'], $actual_cacheability_metadata->getCacheMaxAge()); + } + } + + protected function assertViewConfigCacheabilityMetadata(Config $config, array $expected_cacheability_metadata) { + foreach (self::TEST_VIEW_DISPLAY_IDS as $display_id) { + $view_config_display = $config->get("display.$display_id"); + foreach ($expected_cacheability_metadata as $cache_key => $value) { + if (is_array($value)) { + $this->assertArrayEquals($value, $view_config_display['cache_metadata'][$cache_key]); + } + else { + $this->assertEquals($value, $view_config_display['cache_metadata'][$cache_key]); + } + } + } + } + + protected function assertArrayEquals(array $array1, array $array2) { + sort($array1); + sort($array2); + $this->assertEquals($array1, $array2); + } + + /** + * Returns the test view. + * + * @return \Drupal\views\ViewExecutable + * The view. + */ + protected function getView() { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = $this->entityTypeManager->getStorage('view')->load(self::TEST_VIEW_ID); + $executable = $this->viewExecutableFactory->get($view); +// $executable->setDisplay(self::TEST_VIEW_DISPLAY_ID); + return $executable; + } + +}