3 namespace Drupal\Tests\Core\Entity\KeyValueStore;
5 use Drupal\Core\Cache\MemoryCache\MemoryCache;
6 use Drupal\Core\Config\Entity\ConfigEntityInterface;
7 use Drupal\Core\DependencyInjection\ContainerBuilder;
8 use Drupal\Core\Entity\EntityFieldManagerInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\EntityMalformedException;
11 use Drupal\Core\Entity\EntityManager;
12 use Drupal\Core\Entity\EntityStorageException;
13 use Drupal\Core\Entity\EntityTypeManagerInterface;
14 use Drupal\Core\Language\Language;
15 use Drupal\Tests\UnitTestCase;
16 use Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage;
19 * @coversDefaultClass \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
22 class KeyValueEntityStorageTest extends UnitTestCase {
27 * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
29 protected $entityType;
32 * The key value store.
34 * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface|\PHPUnit_Framework_MockObject_MockObject
36 protected $keyValueStore;
41 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
43 protected $moduleHandler;
48 * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
50 protected $uuidService;
53 * The language manager.
55 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
57 protected $languageManager;
60 * @var \Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage
62 protected $entityStorage;
67 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
69 protected $entityManager;
72 * The mocked entity type manager.
74 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
76 protected $entityTypeManager;
79 * The mocked entity field manager.
81 * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject
83 protected $entityFieldManager;
86 * The mocked cache tags invalidator.
88 * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
90 protected $cacheTagsInvalidator;
95 protected function setUp() {
97 $this->entityType = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
101 * Prepares the key value entity storage.
103 * @covers ::__construct
105 * @param string $uuid_key
106 * (optional) The entity key used for the UUID. Defaults to 'uuid'.
108 protected function setUpKeyValueEntityStorage($uuid_key = 'uuid') {
109 $this->entityType->expects($this->atLeastOnce())
111 ->will($this->returnValueMap([
114 ['langcode', 'langcode'],
116 $this->entityType->expects($this->atLeastOnce())
118 ->will($this->returnValue('test_entity_type'));
119 $this->entityType->expects($this->any())
120 ->method('getListCacheTags')
121 ->willReturn(['test_entity_type_list']);
123 $this->entityManager = new EntityManager();
125 $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
126 $this->entityTypeManager->expects($this->any())
127 ->method('getDefinition')
128 ->with('test_entity_type')
129 ->will($this->returnValue($this->entityType));
131 $this->entityFieldManager = $this->getMock(EntityFieldManagerInterface::class);
133 $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidatorInterface');
135 $this->keyValueStore = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
136 $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
137 $this->uuidService = $this->getMock('Drupal\Component\Uuid\UuidInterface');
138 $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
139 $language = new Language(['langcode' => 'en']);
140 $this->languageManager->expects($this->any())
141 ->method('getDefaultLanguage')
142 ->will($this->returnValue($language));
143 $this->languageManager->expects($this->any())
144 ->method('getCurrentLanguage')
145 ->will($this->returnValue($language));
147 $this->entityStorage = new KeyValueEntityStorage($this->entityType, $this->keyValueStore, $this->uuidService, $this->languageManager, new MemoryCache());
148 $this->entityStorage->setModuleHandler($this->moduleHandler);
150 $container = new ContainerBuilder();
151 $container->set('entity.manager', $this->entityManager);
152 $container->set('entity_field.manager', $this->entityFieldManager);
153 $container->set('entity_type.manager', $this->entityTypeManager);
154 $container->set('language_manager', $this->languageManager);
155 $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
156 // Inject the container into entity.manager so it can defer to
157 // entity_type.manager and other services.
158 $this->entityManager->setContainer($container);
159 \Drupal::setContainer($container);
166 public function testCreateWithPredefinedUuid() {
167 $this->entityType->expects($this->once())
169 ->will($this->returnValue(get_class($this->getMockEntity())));
170 $this->setUpKeyValueEntityStorage();
172 $this->moduleHandler->expects($this->at(0))
173 ->method('invokeAll')
174 ->with('test_entity_type_create');
175 $this->moduleHandler->expects($this->at(1))
176 ->method('invokeAll')
177 ->with('entity_create');
178 $this->uuidService->expects($this->never())
179 ->method('generate');
181 $entity = $this->entityStorage->create(['id' => 'foo', 'uuid' => 'baz']);
182 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
183 $this->assertSame('foo', $entity->id());
184 $this->assertSame('baz', $entity->uuid());
191 public function testCreateWithoutUuidKey() {
192 // Set up the entity storage to expect no UUID key.
193 $this->entityType->expects($this->once())
195 ->will($this->returnValue(get_class($this->getMockEntity())));
196 $this->setUpKeyValueEntityStorage(NULL);
198 $this->moduleHandler->expects($this->at(0))
199 ->method('invokeAll')
200 ->with('test_entity_type_create');
201 $this->moduleHandler->expects($this->at(1))
202 ->method('invokeAll')
203 ->with('entity_create');
204 $this->uuidService->expects($this->never())
205 ->method('generate');
207 $entity = $this->entityStorage->create(['id' => 'foo', 'uuid' => 'baz']);
208 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
209 $this->assertSame('foo', $entity->id());
210 $this->assertSame('baz', $entity->uuid());
217 * @return \Drupal\Core\Entity\EntityInterface
219 public function testCreate() {
220 $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', [], ['toArray']);
221 $this->entityType->expects($this->once())
223 ->will($this->returnValue(get_class($entity)));
224 $this->setUpKeyValueEntityStorage();
226 $this->moduleHandler->expects($this->at(0))
227 ->method('invokeAll')
228 ->with('test_entity_type_create');
229 $this->moduleHandler->expects($this->at(1))
230 ->method('invokeAll')
231 ->with('entity_create');
232 $this->uuidService->expects($this->once())
234 ->will($this->returnValue('bar'));
236 $entity = $this->entityStorage->create(['id' => 'foo']);
237 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
238 $this->assertSame('foo', $entity->id());
239 $this->assertSame('bar', $entity->uuid());
247 * @param \Drupal\Core\Entity\EntityInterface $entity
249 * @return \Drupal\Core\Entity\EntityInterface
251 * @depends testCreate
253 public function testSaveInsert(EntityInterface $entity) {
254 $this->entityType->expects($this->once())
256 ->will($this->returnValue(get_class($entity)));
257 $this->setUpKeyValueEntityStorage();
259 $expected = ['id' => 'foo'];
260 $this->keyValueStore->expects($this->exactly(2))
263 ->will($this->returnValue(FALSE));
264 $this->keyValueStore->expects($this->never())
265 ->method('getMultiple');
266 $this->keyValueStore->expects($this->never())
269 $entity->expects($this->atLeastOnce())
271 ->will($this->returnValue($expected));
273 $this->moduleHandler->expects($this->at(0))
274 ->method('invokeAll')
275 ->with('test_entity_type_presave');
276 $this->moduleHandler->expects($this->at(1))
277 ->method('invokeAll')
278 ->with('entity_presave');
279 $this->moduleHandler->expects($this->at(2))
280 ->method('invokeAll')
281 ->with('test_entity_type_insert');
282 $this->moduleHandler->expects($this->at(3))
283 ->method('invokeAll')
284 ->with('entity_insert');
285 $this->keyValueStore->expects($this->once())
287 ->with('foo', $expected);
288 $return = $this->entityStorage->save($entity);
289 $this->assertSame(SAVED_NEW, $return);
297 * @param \Drupal\Core\Entity\EntityInterface $entity
299 * @return \Drupal\Core\Entity\EntityInterface
301 * @depends testSaveInsert
303 public function testSaveUpdate(EntityInterface $entity) {
304 $this->entityType->expects($this->once())
306 ->will($this->returnValue(get_class($entity)));
307 $this->setUpKeyValueEntityStorage();
309 $expected = ['id' => 'foo'];
310 $this->keyValueStore->expects($this->exactly(2))
313 ->will($this->returnValue(TRUE));
314 $this->keyValueStore->expects($this->once())
315 ->method('getMultiple')
317 ->will($this->returnValue([['id' => 'foo']]));
318 $this->keyValueStore->expects($this->never())
321 $this->moduleHandler->expects($this->at(0))
322 ->method('getImplementations')
323 ->with('entity_load')
324 ->will($this->returnValue([]));
325 $this->moduleHandler->expects($this->at(1))
326 ->method('getImplementations')
327 ->with('test_entity_type_load')
328 ->will($this->returnValue([]));
329 $this->moduleHandler->expects($this->at(2))
330 ->method('invokeAll')
331 ->with('test_entity_type_presave');
332 $this->moduleHandler->expects($this->at(3))
333 ->method('invokeAll')
334 ->with('entity_presave');
335 $this->moduleHandler->expects($this->at(4))
336 ->method('invokeAll')
337 ->with('test_entity_type_update');
338 $this->moduleHandler->expects($this->at(5))
339 ->method('invokeAll')
340 ->with('entity_update');
341 $this->keyValueStore->expects($this->once())
343 ->with('foo', $expected);
344 $return = $this->entityStorage->save($entity);
345 $this->assertSame(SAVED_UPDATED, $return);
353 public function testSaveConfigEntity() {
354 $this->setUpKeyValueEntityStorage();
356 $entity = $this->getMockEntity('Drupal\Core\Config\Entity\ConfigEntityBase', [['id' => 'foo']], [
360 $entity->enforceIsNew();
361 // When creating a new entity, the ID is tracked as the original ID.
362 $this->assertSame('foo', $entity->getOriginalId());
364 $expected = ['id' => 'foo'];
365 $entity->expects($this->atLeastOnce())
367 ->will($this->returnValue($expected));
369 $this->keyValueStore->expects($this->exactly(2))
372 ->will($this->returnValue(FALSE));
373 $this->keyValueStore->expects($this->once())
375 ->with('foo', $expected);
376 $this->keyValueStore->expects($this->never())
379 $return = $this->entityStorage->save($entity);
380 $this->assertSame(SAVED_NEW, $return);
388 * @depends testSaveConfigEntity
390 public function testSaveRenameConfigEntity(ConfigEntityInterface $entity) {
391 $this->entityType->expects($this->once())
393 ->will($this->returnValue(get_class($entity)));
394 $this->setUpKeyValueEntityStorage();
396 $this->moduleHandler->expects($this->at(0))
397 ->method('getImplementations')
398 ->with('entity_load')
399 ->will($this->returnValue([]));
400 $this->moduleHandler->expects($this->at(1))
401 ->method('getImplementations')
402 ->with('test_entity_type_load')
403 ->will($this->returnValue([]));
404 $expected = ['id' => 'foo'];
405 $entity->expects($this->once())
407 ->will($this->returnValue($expected));
408 $this->keyValueStore->expects($this->exactly(2))
411 ->will($this->returnValue(TRUE));
412 $this->keyValueStore->expects($this->once())
413 ->method('getMultiple')
415 ->will($this->returnValue([['id' => 'foo']]));
416 $this->keyValueStore->expects($this->once())
419 $this->keyValueStore->expects($this->once())
421 ->with('bar', $expected);
423 // Performing a rename does not change the original ID until saving.
424 $this->assertSame('foo', $entity->getOriginalId());
425 $entity->set('id', 'bar');
426 $this->assertSame('foo', $entity->getOriginalId());
428 $return = $this->entityStorage->save($entity);
429 $this->assertSame(SAVED_UPDATED, $return);
430 $this->assertSame('bar', $entity->getOriginalId());
437 public function testSaveContentEntity() {
438 $this->entityType->expects($this->any())
440 ->will($this->returnValue([
443 $this->setUpKeyValueEntityStorage();
445 $expected = ['id' => 'foo'];
446 $this->keyValueStore->expects($this->exactly(2))
449 ->will($this->returnValue(FALSE));
450 $this->keyValueStore->expects($this->once())
452 ->with('foo', $expected);
453 $this->keyValueStore->expects($this->never())
455 $entity = $this->getMockEntity('Drupal\Core\Entity\ContentEntityBase', [], [
459 $entity->expects($this->atLeastOnce())
461 ->will($this->returnValue('foo'));
462 $entity->expects($this->once())
464 ->will($this->returnValue($expected));
465 $this->entityStorage->save($entity);
472 public function testSaveInvalid() {
473 $this->setUpKeyValueEntityStorage();
475 $entity = $this->getMockEntity('Drupal\Core\Config\Entity\ConfigEntityBase');
476 $this->keyValueStore->expects($this->never())
478 $this->keyValueStore->expects($this->never())
480 $this->keyValueStore->expects($this->never())
482 $this->setExpectedException(EntityMalformedException::class, 'The entity does not have an ID.');
483 $this->entityStorage->save($entity);
490 public function testSaveDuplicate() {
491 $this->setUpKeyValueEntityStorage();
493 $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'foo']]);
494 $entity->enforceIsNew();
495 $this->keyValueStore->expects($this->once())
497 ->will($this->returnValue(TRUE));
498 $this->keyValueStore->expects($this->never())
500 $this->keyValueStore->expects($this->never())
502 $this->setExpectedException(EntityStorageException::class, "'test_entity_type' entity with ID 'foo' already exists");
503 $this->entityStorage->save($entity);
510 public function testLoad() {
511 $entity = $this->getMockEntity();
512 $this->entityType->expects($this->once())
514 ->will($this->returnValue(get_class($entity)));
515 $this->setUpKeyValueEntityStorage();
517 $this->keyValueStore->expects($this->once())
518 ->method('getMultiple')
520 ->will($this->returnValue([['id' => 'foo']]));
521 $this->moduleHandler->expects($this->at(0))
522 ->method('getImplementations')
523 ->with('entity_load')
524 ->will($this->returnValue([]));
525 $this->moduleHandler->expects($this->at(1))
526 ->method('getImplementations')
527 ->with('test_entity_type_load')
528 ->will($this->returnValue([]));
529 $entity = $this->entityStorage->load('foo');
530 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
531 $this->assertSame('foo', $entity->id());
537 public function testLoadMissingEntity() {
538 $this->entityType->expects($this->once())
539 ->method('getClass');
540 $this->setUpKeyValueEntityStorage();
542 $this->keyValueStore->expects($this->once())
543 ->method('getMultiple')
545 ->will($this->returnValue([]));
546 $this->moduleHandler->expects($this->never())
547 ->method('getImplementations');
548 $entity = $this->entityStorage->load('foo');
549 $this->assertNull($entity);
553 * @covers ::loadMultiple
555 * @covers ::mapFromStorageRecords
556 * @covers ::doLoadMultiple
558 public function testLoadMultipleAll() {
559 $expected['foo'] = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'foo']]);
560 $expected['bar'] = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'bar']]);
561 $this->entityType->expects($this->once())
563 ->will($this->returnValue(get_class(reset($expected))));
564 $this->setUpKeyValueEntityStorage();
566 $this->keyValueStore->expects($this->once())
568 ->will($this->returnValue([['id' => 'foo'], ['id' => 'bar']]));
569 $this->moduleHandler->expects($this->at(0))
570 ->method('getImplementations')
571 ->with('entity_load')
572 ->will($this->returnValue([]));
573 $this->moduleHandler->expects($this->at(1))
574 ->method('getImplementations')
575 ->with('test_entity_type_load')
576 ->will($this->returnValue([]));
577 $entities = $this->entityStorage->loadMultiple();
578 foreach ($entities as $id => $entity) {
579 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
580 $this->assertSame($id, $entity->id());
581 $this->assertSame($id, $expected[$id]->id());
586 * @covers ::loadMultiple
588 * @covers ::mapFromStorageRecords
589 * @covers ::doLoadMultiple
591 public function testLoadMultipleIds() {
592 $entity = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'foo']]);
593 $this->entityType->expects($this->once())
595 ->will($this->returnValue(get_class($entity)));
596 $this->setUpKeyValueEntityStorage();
598 $expected[] = $entity;
599 $this->keyValueStore->expects($this->once())
600 ->method('getMultiple')
602 ->will($this->returnValue([['id' => 'foo']]));
603 $this->moduleHandler->expects($this->at(0))
604 ->method('getImplementations')
605 ->with('entity_load')
606 ->will($this->returnValue([]));
607 $this->moduleHandler->expects($this->at(1))
608 ->method('getImplementations')
609 ->with('test_entity_type_load')
610 ->will($this->returnValue([]));
611 $entities = $this->entityStorage->loadMultiple(['foo']);
612 foreach ($entities as $id => $entity) {
613 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
614 $this->assertSame($id, $entity->id());
619 * @covers ::loadRevision
621 public function testLoadRevision() {
622 $this->setUpKeyValueEntityStorage();
624 $this->assertSame(NULL, $this->entityStorage->loadRevision(1));
628 * @covers ::deleteRevision
630 public function testDeleteRevision() {
631 $this->setUpKeyValueEntityStorage();
633 $this->assertSame(NULL, $this->entityStorage->deleteRevision(1));
640 public function testDelete() {
641 $entities['foo'] = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'foo']]);
642 $entities['bar'] = $this->getMockEntity('Drupal\Core\Entity\Entity', [['id' => 'bar']]);
643 $this->entityType->expects($this->once())
645 ->will($this->returnValue(get_class(reset($entities))));
646 $this->setUpKeyValueEntityStorage();
648 $this->moduleHandler->expects($this->at(0))
649 ->method('invokeAll')
650 ->with('test_entity_type_predelete');
651 $this->moduleHandler->expects($this->at(1))
652 ->method('invokeAll')
653 ->with('entity_predelete');
654 $this->moduleHandler->expects($this->at(2))
655 ->method('invokeAll')
656 ->with('test_entity_type_predelete');
657 $this->moduleHandler->expects($this->at(3))
658 ->method('invokeAll')
659 ->with('entity_predelete');
660 $this->moduleHandler->expects($this->at(4))
661 ->method('invokeAll')
662 ->with('test_entity_type_delete');
663 $this->moduleHandler->expects($this->at(5))
664 ->method('invokeAll')
665 ->with('entity_delete');
666 $this->moduleHandler->expects($this->at(6))
667 ->method('invokeAll')
668 ->with('test_entity_type_delete');
669 $this->moduleHandler->expects($this->at(7))
670 ->method('invokeAll')
671 ->with('entity_delete');
673 $this->keyValueStore->expects($this->once())
674 ->method('deleteMultiple')
675 ->with(['foo', 'bar']);
676 $this->entityStorage->delete($entities);
683 public function testDeleteNothing() {
684 $this->setUpKeyValueEntityStorage();
686 $this->moduleHandler->expects($this->never())
687 ->method($this->anything());
688 $this->keyValueStore->expects($this->never())
690 $this->keyValueStore->expects($this->never())
691 ->method('deleteMultiple');
693 $this->entityStorage->delete([]);
697 * Creates an entity with specific methods mocked.
699 * @param string $class
700 * (optional) The concrete entity class to mock. Defaults to
701 * 'Drupal\Core\Entity\Entity'.
702 * @param array $arguments
703 * (optional) Arguments to pass to the constructor. An empty set of values
704 * and an entity type ID will be provided.
705 * @param array $methods
706 * (optional) The methods to mock.
708 * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject
710 public function getMockEntity($class = 'Drupal\Core\Entity\Entity', array $arguments = [], $methods = []) {
711 // Ensure the entity is passed at least an array of values and an entity
713 if (!isset($arguments[0])) {
716 if (!isset($arguments[1])) {
717 $arguments[1] = 'test_entity_type';
719 return $this->getMockForAbstractClass($class, $arguments, '', TRUE, TRUE, TRUE, $methods);
724 namespace Drupal\Core\Entity\KeyValueStore;
726 if (!defined('SAVED_NEW')) {
727 define('SAVED_NEW', 1);
729 if (!defined('SAVED_UPDATED')) {
730 define('SAVED_UPDATED', 2);