diff --git a/src/Plugin/views/query/SearchApiQuery.php b/src/Plugin/views/query/SearchApiQuery.php index 84f8f9b9..3d8b35ad 100644 --- a/src/Plugin/views/query/SearchApiQuery.php +++ b/src/Plugin/views/query/SearchApiQuery.php @@ -699,6 +699,9 @@ class SearchApiQuery extends QueryPluginBase { return $query->getCacheContexts(); } + // We are not returning the cache contexts from the parent class since these + // are based on the default SQL storage from Views, while our results are + // coming from the search engine. return []; } @@ -717,7 +720,7 @@ class SearchApiQuery extends QueryPluginBase { } /** - * @inheritDoc + * {@inheritdoc} */ public function getCacheMaxAge() { $max_age = parent::getCacheMaxAge(); @@ -730,7 +733,6 @@ class SearchApiQuery extends QueryPluginBase { 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 95074fec..e6761951 100644 --- a/tests/search_api_test_views/search_api_test_views.module +++ b/tests/search_api_test_views/search_api_test_views.module @@ -26,6 +26,7 @@ function search_api_test_views_search_api_query_alter(QueryInterface $query) { if (\Drupal::state()->get('search_api_test_views.alter_query_cacheability_metadata', FALSE)) { if ($query instanceof RefinableCacheableDependencyInterface) { + // Alter in some imaginary cacheability metadata for testing. $query->addCacheContexts(['search_api_test_context']); $query->addCacheTags(['search_api:test_tag']); $query->mergeCacheMaxAge(100); diff --git a/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php b/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php index 12566585..7b549a01 100644 --- a/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php +++ b/tests/src/Kernel/Views/ViewsCacheabilityMetadataExportTest.php @@ -6,7 +6,6 @@ use Drupal\Core\Cache\Context\CacheContextsManager; use Drupal\Core\Config\Config; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\KernelTests\KernelTestBase; -use Drupal\search_api\Entity\Index; use Drupal\views\ViewExecutable; use Prophecy\Argument; @@ -41,13 +40,6 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { */ protected $viewExecutableFactory; - /** - * The current user service. - * - * @var \Drupal\Core\Session\AccountProxyInterface - */ - protected $currentUser; - /** * The state service. * @@ -55,47 +47,16 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { */ protected $state; - /** - * The search index used for testing. - * - * @var \Drupal\search_api\IndexInterface - */ - protected $index; - - /** - * Test users. - * - * @var \Drupal\user\UserInterface[] - */ - protected $users; - - /** - * A test content type. - * - * @var \Drupal\node\NodeTypeInterface - */ - protected $contentType; - - /** - * Test nodes. - * - * @var \Drupal\node\NodeInterface[] - */ - protected $nodes; - /** * {@inheritdoc} */ protected static $modules = [ 'field', 'node', - 'rest', 'search_api', 'search_api_db', - 'search_api_test', 'search_api_test_node_indexing', 'search_api_test_views', - 'serialization', 'system', 'text', 'user', @@ -107,11 +68,12 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { */ public function register(ContainerBuilder $container) { parent::register($container); + + // Use a mocked version of the cache contexts manager so we can use a mocked + // cache context "search_api_test_context" without triggering a validation + // error. $cache_contexts_manager = $this->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()); } @@ -122,28 +84,17 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { 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'); } /** @@ -152,29 +103,60 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { public function testViewExport() { $expected_cacheability_metadata = [ 'contexts' => [ + // Search API uses the core EntityFieldRenderer for rendering content. + // This has support for translatable content, so the result varies by + // content language. + // @see \Drupal\views\Entity\Render\EntityFieldRenderer::getCacheContexts() 'languages:language_content', + // By default, Views always adds the interface language cache context + // since it is very likely that there will be translatable strings in + // the result. + // @see \Drupal\views\Entity\View::addCacheMetadata() 'languages:language_interface', + // Our test view has a pager so we expect it to vary by query arguments. + // @see \Drupal\views\Plugin\views\pager\SqlBase::getCacheContexts() 'url.query_args', + // The test view is a listing of nodes returned as a search result. It + // is expected to have the list cache contexts of the node entity type. + // This is defined in the "list_cache_contexts" key of the node entity + // annotation. 'user.node_grants:view', ], 'tags' => [ + // Our test view depends on the search index, so whenever the index + // configuration changes the cached results should be invalidated. + // @see \Drupal\search_api\Query\Query::getCacheTags() 'config:search_api.index.test_node_index', ], + // By default the result is permanently cached. 'max-age' => -1, ]; + + // Check that our test view has the expected cacheability metadata. $view = $this->getView(); $this->assertViewCacheabilityMetadata($view, $expected_cacheability_metadata); + // For efficiency Views calculates the cacheability metadata whenever a view + // is saved, and includes it in the exported configuration. + // @see \Drupal\views\Entity\View::addCacheMetadata() + // Check that the exported configuration contains the expected 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(); - + // Test that modules are able to alter the cacheability metadata. Our test + // hook implementation will alter all 3 types of metadata. + // @see search_api_test_views_search_api_query_alter() $expected_cacheability_metadata['contexts'][] = 'search_api_test_context'; $expected_cacheability_metadata['tags'][] = 'search_api:test_tag'; $expected_cacheability_metadata['max-age'] = 100; + // Activate the alter hook and resave the view so it will recalculate the + // cacheability metadata. + $this->state->set('search_api_test_views.alter_query_cacheability_metadata', TRUE); + $view->save(); + + // Check that the altered metadata is now present in the view and the + // configuration. $view = $this->getView(); $this->assertViewCacheabilityMetadata($view, $expected_cacheability_metadata); @@ -182,7 +164,19 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { $this->assertViewConfigCacheabilityMetadata($view_config, $expected_cacheability_metadata); } + /** + * Checks that the given view has the expected cacheability metadata. + * + * @param \Drupal\views\ViewExecutable $view + * The view. + * @param array $expected_cacheability_metadata + * An array of cacheability metadata that is expected to be present on the + * view. + */ protected function assertViewCacheabilityMetadata(ViewExecutable $view, array $expected_cacheability_metadata) { + // Cacheability metadata is stored separately for each Views display since + // depending on how the display is configured it might have different + // caching needs. Ensure to check all displays. foreach (self::TEST_VIEW_DISPLAY_IDS as $display_id) { $view->setDisplay($display_id); $display = $view->getDisplay(); @@ -194,7 +188,19 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { } } + /** + * Checks that the given view config has the expected cacheability metadata. + * + * @param \Drupal\Core\Config\Config $config + * The configuration to check. + * @param array $expected_cacheability_metadata + * An array of cacheability metadata that is expected to be present on the + * view configuration. + */ protected function assertViewConfigCacheabilityMetadata(Config $config, array $expected_cacheability_metadata) { + // Cacheability metadata is stored separately for each Views display since + // depending on how the display is configured it might have different + // caching needs. Ensure to check all displays. 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) { @@ -208,6 +214,14 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { } } + /** + * Checks that the given arrays have the same values. + * + * @param array $array1 + * One of the arrays to compare. + * @param array $array2 + * One of the arrays to compare. + */ protected function assertArrayEquals(array $array1, array $array2) { sort($array1); sort($array2); @@ -224,7 +238,7 @@ class ViewsCacheabilityMetadataExportTest extends KernelTestBase { /** @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; }