Commit bd732ee7 authored by catch's avatar catch

Issue #2188565 by Wim Leers, Berdir: Test coverage for Comment, Custom Block,...

Issue #2188565 by Wim Leers, Berdir: Test coverage for Comment, Custom Block, Node, Taxonomy Term and User entity cache tags.
parent e42ac277
......@@ -320,6 +320,9 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
*/
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
$this->onSaveOrDelete();
if ($update) {
$this->onUpdateBundleEntity();
}
}
/**
......@@ -381,6 +384,21 @@ protected function onSaveOrDelete() {
}
}
/**
* Acts on entities of which this entity is a bundle entity type.
*/
protected function onUpdateBundleEntity() {
// If this entity is a bundle entity type of another entity type, and we're
// updating an existing entity, and that other entity type has a view
// builder class, then invalidate the render cache of entities for which
// this entity is a bundle.
$bundle_of = $this->getEntityType()->getBundleOf();
$entity_manager = \Drupal::entityManager();
if ($bundle_of !== FALSE && $entity_manager->hasController($bundle_of, 'view_builder')) {
$entity_manager->getViewBuilder($bundle_of)->resetCache();
}
}
/**
* Wraps the URL generator.
*
......
......@@ -146,19 +146,21 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
"#{$this->entityTypeId}" => $entity,
'#view_mode' => $view_mode,
'#langcode' => $langcode,
'#cache' => array(
'tags' => array(
$this->entityTypeId . '_view' => TRUE,
$this->entityTypeId => array($entity->id()),
),
)
);
// Cache the rendered output if permitted by the view mode and global entity
// type configuration.
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
$return['#cache'] = array(
$return['#cache'] += array(
'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode),
'granularity' => DRUPAL_CACHE_PER_ROLE,
'bin' => $this->cacheBin,
'tags' => array(
$this->entityTypeId . '_view' => TRUE,
$this->entityTypeId => array($entity->id()),
),
);
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
......
<?php
/**
* @file
* Contains \Drupal\custom_block\Tests\CustomBlockCacheTagsTest.
*/
namespace Drupal\custom_block\Tests;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
* Tests the Custom Block entity's cache tags.
*/
class CustomBlockCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('custom_block');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Custom Block', 'Custom Block');
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" custom block.
$custom_block = entity_create('custom_block', array(
'info' => 'Llama',
'type' => 'basic',
'body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
));
$custom_block->save();
return $custom_block;
}
}
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentCacheTagsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Comment entity's cache tags.
*/
class CommentCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('comment');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Comment', 'Comment');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view comments, so that we can verify
// the cache tags of cached versions of comment pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('access comments');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "bar" bundle for the "entity_test" entity type and create.
$bundle = 'bar';
entity_test_create_bundle($bundle, NULL, 'entity_test');
// Create a comment field on this bundle.
\Drupal::service('comment.manager')->addDefaultField('entity_test', 'bar');
// Create a "Camelids" test entity.
$entity_test = entity_create('entity_test', array(
'name' => 'Camelids',
'type' => 'bar',
));
$entity_test->save();
// Create a "Llama" taxonomy term.
$comment = entity_create('comment', array(
'subject' => 'Llama',
'comment_body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'entity_id' => $entity_test->id(),
'entity_type' => 'entity_test',
'field_name' => 'comment',
'status' => \Drupal\comment\CommentInterface::PUBLISHED,
));
$comment->save();
return $comment;
}
}
......@@ -54,6 +54,7 @@ public function testLocks() {
unset($methods[array_search('preSave', $methods)]);
unset($methods[array_search('postSave', $methods)]);
$methods[] = 'onSaveOrDelete';
$methods[] = 'onUpdateBundleEntity';
$comment = $this->getMockBuilder('Drupal\comment\Entity\Comment')
->disableOriginalConstructor()
->setMethods($methods)
......
......@@ -366,6 +366,15 @@ protected function preSaveUpdated(EntityStorageControllerInterface $storage_cont
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
// Clear the cache.
field_cache_clear();
if ($update) {
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasController($entity_type, 'view_builder')) {
$entity_manager->getViewBuilder($entity_type)->resetCache();
}
}
}
/**
......
......@@ -361,6 +361,13 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
// Clear the cache.
field_cache_clear();
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasController($entity_type, 'view_builder')) {
$entity_manager->getViewBuilder($entity_type)->resetCache();
}
}
/**
......
<?php
/**
* @file
* Contains \Drupal\menu\Tests\MenuCacheTagsTest.
*/
namespace Drupal\menu\Tests;
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
/**
* Tests the Menu and Menu Link entities' cache tags.
*/
class MenuCacheTagsTest extends PageCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('menu', 'block', 'test_page_test');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => "Menu & Menu link entities cache tags",
'description' => "Test the Menu & Menu link entities' cache tags.",
'group' => 'Menu',
);
}
/**
* Tests cache tags presence and invalidation of the Menu entity.
*
* Tests the following cache tags:
* - "menu:<menu ID>"
*/
public function testMenuBlock() {
$path = 'test-page';
// Create a Llama menu, add a link to it and place the corresponding block.
$menu = entity_create('menu', array(
'id' => 'llama',
'label' => 'Llama',
'description' => 'Description text',
));
$menu->save();
$menu_link = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Vicuña',
'menu_name' => 'llama',
));
$menu_link->save();
$block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
// Prime the page cache.
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
// Verify that after modifying the menu, there is a cache miss.
$this->pass('Test modification of menu.', 'Debug');
$menu->label = 'Awesome llama';
$menu->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after modifying the menu link, there is a cache miss.
$this->pass('Test modification of menu link.', 'Debug');
$menu_link->link_title = 'Guanaco';
$menu_link->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after adding a menu link, there is a cache miss.
$this->pass('Test addition of menu link.', 'Debug');
$menu_link_2 = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Alpaca',
'menu_name' => 'llama',
));
$menu_link_2->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after deleting the first menu link, there is a cache miss.
$this->pass('Test deletion of menu link.', 'Debug');
$menu_link->delete();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
// Verify that after deleting the menu, there is a cache miss.
$this->pass('Test deletion of menu.', 'Debug');
$menu->delete();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT', array('content:1'));
}
}
......@@ -478,66 +478,6 @@ public function testBlockContextualLinks() {
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="menu-edit"><a href="' . base_path() . 'admin/structure/menu/manage/tools">Edit menu</a></li></ul>');
}
/**
* Test that cache tags are properly set and bubbled up to the page cache.
*
* Ensures that invalidation of the "menu:<menu name>" cache tags works.
*/
public function testMenuBlockPageCacheTags() {
// Enable page caching.
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 300);
$config->save();
// Create a Llama menu, add a link to it and place the corresponding block.
$menu = entity_create('menu', array(
'id' => 'llama',
'label' => 'Llama',
'description' => 'Description text',
));
$menu->save();
$menu_link = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Vicuña',
'menu_name' => 'llama',
));
$menu_link->save();
$block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
// Prime the page cache.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$cid_parts = array(url('test-page', array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, array('content:1', 'menu:llama'));
// The "Llama" menu is modified.
$menu->label = 'Awesome llama';
$menu->save();
// Verify that after the modified menu, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
// A link in the "Llama" menu is modified.
$menu_link->link_title = 'Guanaco';
$menu_link->save();
// Verify that after the modified menu link, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
}
/**
* Tests menu link bundles.
*/
......
......@@ -188,9 +188,6 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
else {
// Invalidate the cache tag of the updated node type only.
Cache::invalidateTags(array('node_type' => $this->id()));
// Invalidate the render cache for all nodes.
\Drupal::entityManager()->getViewBuilder('node')->resetCache();
}
}
......
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeCacheTagsTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Node entity's cache tags.
*/
class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Node', 'Node');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view nodes, so that we can verify the
// cache tags of cached versions of node pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('acess content');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
entity_create('node_type', array(
'name' => 'Camelids',
'type' => 'camelids',
))->save();
// Create a "Llama" node.
$node = entity_create('node', array('type' => 'camelids'));
$node->setTitle('Llama')
->setPublished(TRUE)
->save();
return $node;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return array('user:' . $node->getOwnerId());
}
}
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodePageCacheTest.
*/
namespace Drupal\node\Tests;
/**
* Tests the cache invalidation of node operations.
*/
class NodePageCacheTest extends NodeTestBase {
/**
* An admin user with administrative permissions for nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
public static $modules = array('views');
public static function getInfo() {
return array(
'name' => 'Node page cache test',
'description' => 'Test cache invalidation of node operations.',
'group' => 'Node',
);
}
function setUp() {
parent::setUp();
$this->container->get('config.factory')->get('system.performance')
->set('cache.page.use_internal', 1)
->set('cache.page.max_age', 300)
->save();
$this->adminUser = $this->drupalCreateUser(array(
'bypass node access',
'access content overview',
'administer nodes',
));
}
/**
* Tests deleting nodes clears page cache.
*/
public function testNodeDelete() {
$author = $this->drupalCreateUser();
$node_id = $this->drupalCreateNode(array('uid' => $author->id()))->id();
$node_path = 'node/' . $node_id;
// Populate page cache.
$this->drupalGet($node_path);
// Verify the presence of the correct cache tags.
$cid_parts = array(url($node_path, array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, array('content:1', 'node_view:' . $node_id, 'node:' . $node_id, 'user:' . $author->id(), 'filter_format:plain_text'));
// Login and delete the node.
$this->drupalLogin($this->adminUser);
$this->drupalGet($node_path . '/delete');
$this->drupalPostForm(NULL, array(), t('Delete'));
// Logout and check the node is not available.
$this->drupalLogout();
$this->drupalGet($node_path);
$this->assertResponse(404);
// Create two new nodes.
$this->drupalCreateNode();
$node_path = 'node/' . $this->drupalCreateNode()->id();
// Populate page cache.
$this->drupalGet($node_path);
// Login and delete the nodes.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content');
$edit = array(
'action' => 'node_delete_action',
'node_bulk_form[0]' => 1,
'node_bulk_form[1]' => 1,
);
$this->drupalPostForm(NULL, $edit, t('Apply'));
$this->drupalPostForm(NULL, array(), t('Delete'));
// Logout and check the node is not available.
$this->drupalLogout();
$this->drupalGet($node_path);
$this->assertResponse(404);
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Cache\PageCacheTagsTestBase.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\simpletest\WebTestBase;
use Drupal\Component\Utility\String;
/**
* Provides helper methods for page cache tags tests.
*/
abstract class PageCacheTagsTestBase extends WebTestBase {
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Enable page caching.
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 3600);
$config->save();
}
/**
* Verify that when loading a given page, it's a page cache hit or miss.
*
* @param string $path
* The page at this path will be loaded.
* @param string $hit_or_miss
* 'HIT' if a page cache hit is expected, 'MISS' otherwise.
*
* @param array|FALSE $tags
* When expecting a page cache hit, you may optionally specify an array of
* expected cache tags. While FALSE, the cache tags will not be verified.
*/
protected function verifyPageCache($path, $hit_or_miss, $tags = FALSE) {
$this->drupalGet($path);
$message = String::format('Page cache @hit_or_miss for %path.', array('@hit_or_miss' => $hit_or_miss, '%path' => $path));
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
if ($hit_or_miss === 'HIT' && is_array($tags)) {
$cid_parts = array(url($path, array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, $tags);
}
}
}
......@@ -48,7 +48,7 @@ public function testEntityViewBuilderCache() {
// Test that new entities (before they are saved for the first time) do not
// generate a cache entry.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertFalse(isset($build['#cache']), 'The render array element of new (unsaved) entities is not cached.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.');
// Get a fully built entity view render array.
$entity_test->save();
......@@ -144,17 +144,17 @@ public function testEntityViewBuilderCacheToggling() {
// Test a view mode in default conditions: render caching is enabled for
// the entity type and the view mode.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertTrue(isset($build['#cache']), 'A view mode with render cache enabled has the correct output.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'keys', 'granularity', 'bin') , 'A view mode with render cache enabled has the correct output (cache tags, keys, granularity and bin).');
// Test that a view mode can opt out of render caching.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
$this->assertFalse(isset($build['#cache']), 'A view mode with render cache disabled has the correct output.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'A view mode with render cache disabled has the correct output (only cache tags).');
// Test that an entity type can opt out of render caching completely.
$entity_test_no_cache = $this->createTestEntity('entity_test_label');
$entity_test_no_cache->save();
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full');
$this->assertFalse(isset($build['#cache']), 'An entity type can opt out of render caching regardless of view mode configuration.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags set.');
}
/**
......
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase.
*/
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
* Provides helper methods for Entity cache tags tests; for entities with URIs.
*/
abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {