7f5c5e340ffe33fad002285b8b146822d3cfb8fe
[yaffs-website] / web / core / modules / system / src / Tests / Entity / EntityCacheTagsTestBase.php
1 <?php
2
3 namespace Drupal\system\Tests\Entity;
4
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\Url;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
14 use Drupal\user\Entity\Role;
15 use Drupal\user\RoleInterface;
16
17 /**
18  * Provides helper methods for Entity cache tags tests.
19  *
20  * @deprecated Scheduled for removal in Drupal 9.0.0.
21  *   Use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase instead.
22  */
23 abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
24
25   /**
26    * Modules to enable.
27    *
28    * @var array
29    */
30   public static $modules = ['entity_test', 'field_test'];
31
32   /**
33    * The main entity used for testing.
34    *
35    * @var \Drupal\Core\Entity\EntityInterface
36    */
37   protected $entity;
38
39   /**
40    * The entity instance referencing the main entity.
41    *
42    * @var \Drupal\Core\Entity\EntityInterface
43    */
44   protected $referencingEntity;
45
46   /**
47    * The entity instance not referencing the main entity.
48    *
49    * @var \Drupal\Core\Entity\EntityInterface
50    */
51   protected $nonReferencingEntity;
52
53   /**
54    * {@inheritdoc}
55    */
56   protected function setUp() {
57     parent::setUp();
58
59     // Give anonymous users permission to view test entities, so that we can
60     // verify the cache tags of cached versions of test entity pages.
61     $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
62     $user_role->grantPermission('view test entity');
63     $user_role->save();
64
65     // Create an entity.
66     $this->entity = $this->createEntity();
67
68     // If this is an entity with field UI enabled, then add a configurable
69     // field. We will use this configurable field in later tests to ensure that
70     // field configuration invalidate render cache entries.
71     if ($this->entity->getEntityType()->get('field_ui_base_route')) {
72       // Add field, so we can modify the field storage and field entities to
73       // verify that changes to those indeed clear cache tags.
74       FieldStorageConfig::create([
75         'field_name' => 'configurable_field',
76         'entity_type' => $this->entity->getEntityTypeId(),
77         'type' => 'test_field',
78         'settings' => [],
79       ])->save();
80       FieldConfig::create([
81         'entity_type' => $this->entity->getEntityTypeId(),
82         'bundle' => $this->entity->bundle(),
83         'field_name' => 'configurable_field',
84         'label' => 'Configurable field',
85         'settings' => [],
86       ])->save();
87
88       // Reload the entity now that a new field has been added to it.
89       $storage = $this->container
90         ->get('entity.manager')
91         ->getStorage($this->entity->getEntityTypeId());
92       $storage->resetCache();
93       $this->entity = $storage->load($this->entity->id());
94     }
95
96     // Create a referencing and a non-referencing entity.
97     list(
98       $this->referencingEntity,
99       $this->nonReferencingEntity,
100     ) = $this->createReferenceTestEntities($this->entity);
101   }
102
103   /**
104    * Generates standardized entity cache tags test info.
105    *
106    * @param string $entity_type_label
107    *   The label of the entity type whose cache tags to test.
108    * @param string $group
109    *   The test group.
110    *
111    * @return array
112    *
113    * @see \Drupal\simpletest\TestBase::getInfo()
114    */
115   protected static function generateStandardizedInfo($entity_type_label, $group) {
116     return [
117       'name' => "$entity_type_label entity cache tags",
118       'description' => "Test the $entity_type_label entity's cache tags.",
119       'group' => $group,
120     ];
121   }
122
123   /**
124    * Creates the entity to be tested.
125    *
126    * @return \Drupal\Core\Entity\EntityInterface
127    *   The entity to be tested.
128    */
129   abstract protected function createEntity();
130
131   /**
132    * Returns the access cache contexts for the tested entity.
133    *
134    * Only list cache contexts that aren't part of the required cache contexts.
135    *
136    * @param \Drupal\Core\Entity\EntityInterface $entity
137    *   The entity to be tested, as created by createEntity().
138    *
139    * @return string[]
140    *   An array of the additional cache contexts.
141    *
142    * @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface
143    */
144   protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
145     return [];
146   }
147
148   /**
149    * Returns the additional (non-standard) cache contexts for the tested entity.
150    *
151    * Only list cache contexts that aren't part of the required cache contexts.
152    *
153    * @param \Drupal\Core\Entity\EntityInterface $entity
154    *   The entity to be tested, as created by createEntity().
155    *
156    * @return string[]
157    *   An array of the additional cache contexts.
158    *
159    * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
160    */
161   protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
162     return [];
163   }
164
165   /**
166    * Returns the additional (non-standard) cache tags for the tested entity.
167    *
168    * @param \Drupal\Core\Entity\EntityInterface $entity
169    *   The entity to be tested, as created by createEntity().
170    * @return array
171    *   An array of the additional cache tags.
172    *
173    * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
174    */
175   protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
176     return [];
177   }
178
179   /**
180    * Returns the additional cache tags for the tested entity's listing by type.
181    *
182    * @return string[]
183    *   An array of the additional cache contexts.
184    */
185   protected function getAdditionalCacheContextsForEntityListing() {
186     return [];
187   }
188
189   /**
190    * Returns the additional cache tags for the tested entity's listing by type.
191    *
192    * Necessary when there are unavoidable default entities of this type, e.g.
193    * the anonymous and administrator User entities always exist.
194    *
195    * @return array
196    *   An array of the additional cache tags.
197    */
198   protected function getAdditionalCacheTagsForEntityListing() {
199     return [];
200   }
201
202   /**
203    * Selects the preferred view mode for the given entity type.
204    *
205    * Prefers 'full', picks the first one otherwise, and if none are available,
206    * chooses 'default'.
207    */
208   protected function selectViewMode($entity_type) {
209     $view_modes = \Drupal::entityManager()
210       ->getStorage('entity_view_mode')
211       ->loadByProperties(['targetEntityType' => $entity_type]);
212
213     if (empty($view_modes)) {
214       return 'default';
215     }
216     else {
217       // Prefer the "full" display mode.
218       if (isset($view_modes[$entity_type . '.full'])) {
219         return 'full';
220       }
221       else {
222         $view_modes = array_keys($view_modes);
223         return substr($view_modes[0], strlen($entity_type) + 1);
224       }
225     }
226   }
227
228   /**
229    * Creates a referencing and a non-referencing entity for testing purposes.
230    *
231    * @param \Drupal\Core\Entity\EntityInterface $referenced_entity
232    *   The entity that the referencing entity should reference.
233    *
234    * @return \Drupal\Core\Entity\EntityInterface[]
235    *   An array containing a referencing entity and a non-referencing entity.
236    */
237   protected function createReferenceTestEntities($referenced_entity) {
238     // All referencing entities should be of the type 'entity_test'.
239     $entity_type = 'entity_test';
240
241     // Create a "foo" bundle for the given entity type.
242     $bundle = 'foo';
243     entity_test_create_bundle($bundle, NULL, $entity_type);
244
245     // Add a field of the given type to the given entity type's "foo" bundle.
246     $field_name = $referenced_entity->getEntityTypeId() . '_reference';
247     FieldStorageConfig::create([
248       'field_name' => $field_name,
249       'entity_type' => $entity_type,
250       'type' => 'entity_reference',
251       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
252       'settings' => [
253         'target_type' => $referenced_entity->getEntityTypeId(),
254       ],
255     ])->save();
256     FieldConfig::create([
257       'field_name' => $field_name,
258       'entity_type' => $entity_type,
259       'bundle' => $bundle,
260       'settings' => [
261         'handler' => 'default',
262         'handler_settings' => [
263           'target_bundles' => [
264             $referenced_entity->bundle() => $referenced_entity->bundle(),
265           ],
266           'sort' => ['field' => '_none'],
267           'auto_create' => FALSE,
268         ],
269       ],
270     ])->save();
271     if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) {
272       entity_get_display($entity_type, $bundle, 'full')
273         ->setComponent($field_name, [
274           'type' => 'entity_reference_label',
275         ])
276         ->save();
277     }
278     else {
279       $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
280       entity_get_display($entity_type, $bundle, 'full')
281         ->setComponent($field_name, [
282           'type' => 'entity_reference_entity_view',
283           'settings' => [
284             'view_mode' => $referenced_entity_view_mode,
285           ],
286         ])
287         ->save();
288     }
289
290     // Create an entity that does reference the entity being tested.
291     $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
292     $referencing_entity = $this->container->get('entity_type.manager')
293       ->getStorage($entity_type)
294       ->create([
295         $label_key => 'Referencing ' . $entity_type,
296         'status' => 1,
297         'type' => $bundle,
298         $field_name => ['target_id' => $referenced_entity->id()],
299       ]);
300     $referencing_entity->save();
301
302     // Create an entity that does not reference the entity being tested.
303     $non_referencing_entity = $this->container->get('entity_type.manager')
304       ->getStorage($entity_type)
305       ->create([
306         $label_key => 'Non-referencing ' . $entity_type,
307         'status' => 1,
308         'type' => $bundle,
309       ]);
310     $non_referencing_entity->save();
311
312     return [
313       $referencing_entity,
314       $non_referencing_entity,
315     ];
316   }
317
318   /**
319    * Tests cache tags presence and invalidation of the entity when referenced.
320    *
321    * Tests the following cache tags:
322    * - entity type view cache tag: "<entity type>_view"
323    * - entity cache tag: "<entity type>:<entity ID>"
324    * - entity type list cache tag: "<entity type>_list"
325    * - referencing entity type view cache tag: "<referencing entity type>_view"
326    * - referencing entity type cache tag: "<referencing entity type>:<referencing entity ID>"
327    */
328   public function testReferencedEntity() {
329     $entity_type = $this->entity->getEntityTypeId();
330     $referencing_entity_url = $this->referencingEntity->urlInfo('canonical');
331     $non_referencing_entity_url = $this->nonReferencingEntity->urlInfo('canonical');
332     $listing_url = Url::fromRoute('entity.entity_test.collection_referencing_entities', [
333       'entity_reference_field_name' => $entity_type . '_reference',
334       'referenced_entity_type' => $entity_type,
335       'referenced_entity_id' => $this->entity->id(),
336     ]);
337     $empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', ['entity_type_id' => $entity_type]);
338     $nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
339
340     // The default cache contexts for rendered entities.
341     $default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
342     $entity_cache_contexts = $default_cache_contexts;
343     $page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
344
345     // Cache tags present on every rendered page.
346     // 'user.permissions' is a required cache context, and responses that vary
347     // by this cache context when requested by anonymous users automatically
348     // also get this cache tag, to ensure correct invalidation.
349     $page_cache_tags = Cache::mergeTags(['http_response', 'rendered'], ['config:user.role.anonymous']);
350     // If the block module is used, the Block page display variant is used,
351     // which adds the block config entity type's list cache tags.
352     $page_cache_tags = Cache::mergeTags($page_cache_tags, \Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list'] : []);
353
354     $page_cache_tags_referencing_entity = in_array('user.permissions', $this->getAccessCacheContextsForEntity($this->referencingEntity)) ? ['config:user.role.anonymous'] : [];
355
356     $view_cache_tag = [];
357     if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
358       $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)
359         ->getCacheTags();
360     }
361
362     $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($entity_cache_contexts);
363     $cache_context_tags = $context_metadata->getCacheTags();
364
365     // Generate the cache tags for the (non) referencing entities.
366     $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
367     // Includes the main entity's cache tags, since this entity references it.
368     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity->getCacheTags());
369     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
370     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $view_cache_tag);
371     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags);
372     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['rendered']);
373
374     $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
375     $non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, ['rendered']);
376
377     // Generate the cache tags for all two possible entity listing paths.
378     // 1. list cache tag only (listing query has no match)
379     // 2. list cache tag plus entity cache tag (listing query has a match)
380     $empty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $page_cache_tags);
381
382     $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->entity->getCacheTags());
383     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $this->getAdditionalCacheTagsForEntityListing($this->entity));
384     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
385
386     $this->pass("Test referencing entity.", 'Debug');
387     $this->verifyPageCache($referencing_entity_url, 'MISS');
388
389     // Verify a cache hit, but also the presence of the correct cache tags.
390     $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
391     $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
392     $this->verifyPageCache($referencing_entity_url, 'HIT', $expected_tags);
393
394     // Also verify the existence of an entity render cache entry.
395     $cache_keys = ['entity_view', 'entity_test', $this->referencingEntity->id(), 'full'];
396     $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
397     $access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
398     $additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->referencingEntity);
399     $redirected_cid = NULL;
400     if (count($access_cache_contexts) || count($additional_cache_contexts)) {
401       $cache_contexts = Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts);
402       $cache_contexts = Cache::mergeContexts($cache_contexts, $access_cache_contexts);
403       $redirected_cid = $this->createCacheId($cache_keys, $cache_contexts);
404       $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts);
405       $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags());
406     }
407     $this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
408
409     $this->pass("Test non-referencing entity.", 'Debug');
410     $this->verifyPageCache($non_referencing_entity_url, 'MISS');
411     // Verify a cache hit, but also the presence of the correct cache tags.
412     $this->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
413     // Also verify the existence of an entity render cache entry.
414     $cache_keys = ['entity_view', 'entity_test', $this->nonReferencingEntity->id(), 'full'];
415     $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
416     $this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
417
418
419     $this->pass("Test listing of referencing entities.", 'Debug');
420     // Prime the page cache for the listing of referencing entities.
421     $this->verifyPageCache($listing_url, 'MISS');
422
423     // Verify a cache hit, but also the presence of the correct cache tags.
424     $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
425     $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
426     $this->verifyPageCache($listing_url, 'HIT', $expected_tags);
427
428     $this->pass("Test empty listing.", 'Debug');
429     // Prime the page cache for the empty listing.
430     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
431     // Verify a cache hit, but also the presence of the correct cache tags.
432     $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
433     // Verify the entity type's list cache contexts are present.
434     $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
435     $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
436
437
438     $this->pass("Test listing containing referenced entity.", 'Debug');
439     // Prime the page cache for the listing containing the referenced entity.
440     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
441     // Verify a cache hit, but also the presence of the correct cache tags.
442     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
443     // Verify the entity type's list cache contexts are present.
444     $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
445     $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
446
447
448     // Verify that after modifying the referenced entity, there is a cache miss
449     // for every route except the one for the non-referencing entity.
450     $this->pass("Test modification of referenced entity.", 'Debug');
451     $this->entity->save();
452     $this->verifyPageCache($referencing_entity_url, 'MISS');
453     $this->verifyPageCache($listing_url, 'MISS');
454     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
455     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
456     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
457
458     // Verify cache hits.
459     $this->verifyPageCache($referencing_entity_url, 'HIT');
460     $this->verifyPageCache($listing_url, 'HIT');
461     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
462     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
463
464
465     // Verify that after modifying the referencing entity, there is a cache miss
466     // for every route except the ones for the non-referencing entity and the
467     // empty entity listing.
468     $this->pass("Test modification of referencing entity.", 'Debug');
469     $this->referencingEntity->save();
470     $this->verifyPageCache($referencing_entity_url, 'MISS');
471     $this->verifyPageCache($listing_url, 'MISS');
472     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
473     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
474     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
475
476     // Verify cache hits.
477     $this->verifyPageCache($referencing_entity_url, 'HIT');
478     $this->verifyPageCache($listing_url, 'HIT');
479     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
480
481
482     // Verify that after modifying the non-referencing entity, there is a cache
483     // miss only for the non-referencing entity route.
484     $this->pass("Test modification of non-referencing entity.", 'Debug');
485     $this->nonReferencingEntity->save();
486     $this->verifyPageCache($referencing_entity_url, 'HIT');
487     $this->verifyPageCache($listing_url, 'HIT');
488     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
489     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
490     $this->verifyPageCache($non_referencing_entity_url, 'MISS');
491
492     // Verify cache hits.
493     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
494
495
496     if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
497       // Verify that after modifying the entity's display, there is a cache miss
498       // for both the referencing entity, and the listing of referencing
499       // entities, but not for any other routes.
500       $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
501       $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug');
502       $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode);
503       $entity_display->save();
504       $this->verifyPageCache($referencing_entity_url, 'MISS');
505       $this->verifyPageCache($listing_url, 'MISS');
506       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
507       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
508       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
509
510       // Verify cache hits.
511       $this->verifyPageCache($referencing_entity_url, 'HIT');
512       $this->verifyPageCache($listing_url, 'HIT');
513     }
514
515
516     if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
517       // Verify that after modifying the corresponding bundle entity, there is a
518       // cache miss for both the referencing entity, and the listing of
519       // referencing entities, but not for any other routes.
520       $this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
521       $bundle_entity = $this->container->get('entity_type.manager')
522         ->getStorage($bundle_entity_type_id)
523         ->load($this->entity->bundle());
524       $bundle_entity->save();
525       $this->verifyPageCache($referencing_entity_url, 'MISS');
526       $this->verifyPageCache($listing_url, 'MISS');
527       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
528       // Special case: entity types may choose to use their bundle entity type
529       // cache tags, to avoid having excessively granular invalidation.
530       $is_special_case = $bundle_entity->getCacheTags() == $this->entity->getCacheTags() && $bundle_entity->getEntityType()->getListCacheTags() == $this->entity->getEntityType()->getListCacheTags();
531       if ($is_special_case) {
532         $this->verifyPageCache($empty_entity_listing_url, 'MISS');
533         $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
534       }
535       else {
536         $this->verifyPageCache($empty_entity_listing_url, 'HIT');
537         $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
538       }
539
540       // Verify cache hits.
541       $this->verifyPageCache($referencing_entity_url, 'HIT');
542       $this->verifyPageCache($listing_url, 'HIT');
543       if ($is_special_case) {
544         $this->verifyPageCache($empty_entity_listing_url, 'HIT');
545         $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
546       }
547     }
548
549
550     if ($this->entity->getEntityType()->get('field_ui_base_route')) {
551       // Verify that after modifying a configurable field on the entity, there
552       // is a cache miss.
553       $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
554       $field_storage_name = $this->entity->getEntityTypeId() . '.configurable_field';
555       $field_storage = FieldStorageConfig::load($field_storage_name);
556       $field_storage->save();
557       $this->verifyPageCache($referencing_entity_url, 'MISS');
558       $this->verifyPageCache($listing_url, 'MISS');
559       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
560       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
561       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
562
563       // Verify cache hits.
564       $this->verifyPageCache($referencing_entity_url, 'HIT');
565       $this->verifyPageCache($listing_url, 'HIT');
566
567
568       // Verify that after modifying a configurable field on the entity, there
569       // is a cache miss.
570       $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
571       $field_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
572       $field = FieldConfig::load($field_name);
573       $field->save();
574       $this->verifyPageCache($referencing_entity_url, 'MISS');
575       $this->verifyPageCache($listing_url, 'MISS');
576       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
577       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
578       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
579
580       // Verify cache hits.
581       $this->verifyPageCache($referencing_entity_url, 'HIT');
582       $this->verifyPageCache($listing_url, 'HIT');
583     }
584
585
586     // Verify that after invalidating the entity's cache tag directly, there is
587     // a cache miss for every route except the ones for the non-referencing
588     // entity and the empty entity listing.
589     $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
590     Cache::invalidateTags($this->entity->getCacheTagsToInvalidate());
591     $this->verifyPageCache($referencing_entity_url, 'MISS');
592     $this->verifyPageCache($listing_url, 'MISS');
593     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
594     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
595     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
596
597     // Verify cache hits.
598     $this->verifyPageCache($referencing_entity_url, 'HIT');
599     $this->verifyPageCache($listing_url, 'HIT');
600     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
601
602     // Verify that after invalidating the entity's list cache tag directly,
603     // there is a cache miss for both the empty entity listing and the non-empty
604     // entity listing routes, but not for other routes.
605     $this->pass("Test invalidation of referenced entity's list cache tag.", 'Debug');
606     Cache::invalidateTags($this->entity->getEntityType()->getListCacheTags());
607     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
608     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
609     $this->verifyPageCache($referencing_entity_url, 'HIT');
610     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
611     $this->verifyPageCache($listing_url, 'HIT');
612
613     // Verify cache hits.
614     $this->verifyPageCache($empty_entity_listing_url, 'HIT');
615     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
616
617
618     if (!empty($view_cache_tag)) {
619       // Verify that after invalidating the generic entity type's view cache tag
620       // directly, there is a cache miss for both the referencing entity, and the
621       // listing of referencing entities, but not for other routes.
622       $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
623       Cache::invalidateTags($view_cache_tag);
624       $this->verifyPageCache($referencing_entity_url, 'MISS');
625       $this->verifyPageCache($listing_url, 'MISS');
626       $this->verifyPageCache($non_referencing_entity_url, 'HIT');
627       $this->verifyPageCache($empty_entity_listing_url, 'HIT');
628       $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
629
630       // Verify cache hits.
631       $this->verifyPageCache($referencing_entity_url, 'HIT');
632       $this->verifyPageCache($listing_url, 'HIT');
633     }
634
635     // Verify that after deleting the entity, there is a cache miss for every
636     // route except for the non-referencing entity one.
637     $this->pass('Test deletion of referenced entity.', 'Debug');
638     $this->entity->delete();
639     $this->verifyPageCache($referencing_entity_url, 'MISS');
640     $this->verifyPageCache($listing_url, 'MISS');
641     $this->verifyPageCache($empty_entity_listing_url, 'MISS');
642     $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
643     $this->verifyPageCache($non_referencing_entity_url, 'HIT');
644
645     // Verify cache hits.
646     $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
647     $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['http_response', 'rendered']);
648
649     $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing());
650     $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
651
652     $this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
653     $this->verifyPageCache($listing_url, 'HIT', $page_cache_tags);
654     $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
655     $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
656   }
657
658   /**
659    * Creates a cache ID from a list of cache keys and a set of cache contexts.
660    *
661    * @param string[] $keys
662    *   A list of cache keys.
663    * @param string[] $contexts
664    *   A set of cache contexts.
665    *
666    * @return string
667    *   The cache ID string.
668    */
669   protected function createCacheId(array $keys, array $contexts) {
670     $cid_parts = $keys;
671
672     $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
673     $cid_parts = array_merge($cid_parts, $contexts->getKeys());
674
675     return implode(':', $cid_parts);
676   }
677
678   /**
679    * Verify that a given render cache entry exists, with the correct cache tags.
680    *
681    * @param string $cid
682    *   The render cache item ID.
683    * @param array $tags
684    *   An array of expected cache tags.
685    * @param string|null $redirected_cid
686    *   (optional) The redirected render cache item ID.
687    */
688   protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) {
689     // Also verify the existence of an entity render cache entry.
690     $cache_entry = \Drupal::cache('render')->get($cid);
691     $this->assertTrue($cache_entry, 'A render cache entry exists.');
692     sort($cache_entry->tags);
693     sort($tags);
694     $this->assertIdentical($cache_entry->tags, $tags);
695     $is_redirecting_cache_item = isset($cache_entry->data['#cache_redirect']);
696     if ($redirected_cid === NULL) {
697       $this->assertFalse($is_redirecting_cache_item, 'Render cache entry is not a redirect.');
698       // If this is a redirecting cache item unlike we expected, log it.
699       if ($is_redirecting_cache_item) {
700         debug($cache_entry->data);
701       }
702     }
703     else {
704       // Verify that $cid contains a cache redirect.
705       $this->assertTrue($is_redirecting_cache_item, 'Render cache entry is a redirect.');
706       // If this is not a redirecting cache item unlike we expected, log it.
707       if (!$is_redirecting_cache_item) {
708         debug($cache_entry->data);
709       }
710       // Verify that the cache redirect points to the expected CID.
711       $redirect_cache_metadata = $cache_entry->data['#cache'];
712       $actual_redirection_cid = $this->createCacheId(
713         $redirect_cache_metadata['keys'],
714         $redirect_cache_metadata['contexts']
715       );
716       $this->assertIdentical($redirected_cid, $actual_redirection_cid);
717       // Finally, verify that the redirected CID exists and has the same cache
718       // tags.
719       $this->verifyRenderCache($redirected_cid, $tags);
720     }
721   }
722
723 }