3 namespace Drupal\Tests\Core\Config\Entity;
5 use Drupal\Component\Uuid\UuidInterface;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
8 use Drupal\Core\Config\Config;
9 use Drupal\Core\Config\ConfigDuplicateUUIDException;
10 use Drupal\Core\Config\ConfigFactoryInterface;
11 use Drupal\Core\Config\ConfigManagerInterface;
12 use Drupal\Core\Config\Entity\ConfigEntityBase;
13 use Drupal\Core\Config\Entity\ConfigEntityInterface;
14 use Drupal\Core\Config\Entity\ConfigEntityStorage;
15 use Drupal\Core\Config\Entity\ConfigEntityType;
16 use Drupal\Core\Config\ImmutableConfig;
17 use Drupal\Core\Config\TypedConfigManagerInterface;
18 use Drupal\Core\Entity\EntityInterface;
19 use Drupal\Core\Entity\EntityMalformedException;
20 use Drupal\Core\Entity\EntityStorageException;
21 use Drupal\Core\Entity\EntityTypeManagerInterface;
22 use Drupal\Core\Entity\Query\QueryFactoryInterface;
23 use Drupal\Core\Entity\Query\QueryInterface;
24 use Drupal\Core\Extension\ModuleHandlerInterface;
25 use Drupal\Core\Language\Language;
26 use Drupal\Core\Language\LanguageManagerInterface;
27 use Drupal\Tests\UnitTestCase;
28 use Prophecy\Argument;
29 use Symfony\Component\DependencyInjection\ContainerBuilder;
32 * @coversDefaultClass \Drupal\Core\Config\Entity\ConfigEntityStorage
35 class ConfigEntityStorageTest extends UnitTestCase {
38 * The type ID of the entity under test.
42 protected $entityTypeId;
47 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\Prophecy\Prophecy\ProphecyInterface
49 protected $moduleHandler;
54 * @var \Drupal\Component\Uuid\UuidInterface|\Prophecy\Prophecy\ProphecyInterface
56 protected $uuidService;
59 * The language manager.
61 * @var \Drupal\Core\Language\LanguageManagerInterface|\Prophecy\Prophecy\ProphecyInterface
63 protected $languageManager;
68 * @var \Drupal\Core\Config\Entity\ConfigEntityStorage
70 protected $entityStorage;
73 * The config factory service.
75 * @var \Drupal\Core\Config\ConfigFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
77 protected $configFactory;
82 * @var \Drupal\Core\Entity\Query\QueryInterface|\Prophecy\Prophecy\ProphecyInterface
84 protected $entityQuery;
87 * The mocked cache backend.
89 * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\Prophecy\Prophecy\ProphecyInterface
91 protected $cacheTagsInvalidator;
94 * The configuration manager.
96 * @var \Drupal\Core\Config\ConfigManagerInterface|\Prophecy\Prophecy\ProphecyInterface
98 protected $configManager;
103 * @covers ::__construct
105 protected function setUp() {
108 $this->entityTypeId = 'test_entity_type';
110 $entity_type = new ConfigEntityType([
111 'id' => $this->entityTypeId,
112 'class' => get_class($this->getMockEntity()),
113 'provider' => 'the_provider',
114 'config_prefix' => 'the_config_prefix',
118 'langcode' => 'langcode',
120 'list_cache_tags' => [$this->entityTypeId . '_list'],
123 $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
125 $this->uuidService = $this->prophesize(UuidInterface::class);
127 $this->languageManager = $this->prophesize(LanguageManagerInterface::class);
128 $this->languageManager->getCurrentLanguage()->willReturn(new Language(['id' => 'hu']));
130 $this->configFactory = $this->prophesize(ConfigFactoryInterface::class);
132 $this->entityQuery = $this->prophesize(QueryInterface::class);
133 $entity_query_factory = $this->prophesize(QueryFactoryInterface::class);
134 $entity_query_factory->get($entity_type, 'AND')->willReturn($this->entityQuery->reveal());
136 $this->entityStorage = new ConfigEntityStorage($entity_type, $this->configFactory->reveal(), $this->uuidService->reveal(), $this->languageManager->reveal());
137 $this->entityStorage->setModuleHandler($this->moduleHandler->reveal());
139 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
140 $entity_type_manager->getDefinition('test_entity_type')->willReturn($entity_type);
142 $this->cacheTagsInvalidator = $this->prophesize(CacheTagsInvalidatorInterface::class);
144 $typed_config_manager = $this->prophesize(TypedConfigManagerInterface::class);
145 $typed_config_manager
146 ->getDefinition(Argument::containingString('the_provider.the_config_prefix.'))
147 ->willReturn(['mapping' => ['id' => '', 'uuid' => '', 'dependencies' => '']]);
149 $this->configManager = $this->prophesize(ConfigManagerInterface::class);
151 $container = new ContainerBuilder();
152 $container->set('entity_type.manager', $entity_type_manager->reveal());
153 $container->set('entity.query.config', $entity_query_factory->reveal());
154 $container->set('config.typed', $typed_config_manager->reveal());
155 $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator->reveal());
156 $container->set('config.manager', $this->configManager->reveal());
157 $container->set('language_manager', $this->languageManager->reveal());
158 \Drupal::setContainer($container);
166 public function testCreateWithPredefinedUuid() {
167 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())->shouldNotBeCalled();
169 $entity = $this->getMockEntity();
170 $entity->set('id', 'foo');
171 $entity->set('langcode', 'hu');
172 $entity->set('uuid', 'baz');
173 $entity->setOriginalId('foo');
174 $entity->enforceIsNew();
176 $this->moduleHandler->invokeAll('test_entity_type_create', [$entity])
178 $this->moduleHandler->invokeAll('entity_create', [$entity, 'test_entity_type'])
181 $this->uuidService->generate()->shouldNotBeCalled();
183 $entity = $this->entityStorage->create(['id' => 'foo', 'uuid' => 'baz']);
184 $this->assertInstanceOf(EntityInterface::class, $entity);
185 $this->assertSame('foo', $entity->id());
186 $this->assertSame('baz', $entity->uuid());
193 * @return \Drupal\Core\Entity\EntityInterface
195 public function testCreate() {
196 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())->shouldNotBeCalled();
198 $entity = $this->getMockEntity();
199 $entity->set('id', 'foo');
200 $entity->set('langcode', 'hu');
201 $entity->set('uuid', 'bar');
202 $entity->setOriginalId('foo');
203 $entity->enforceIsNew();
205 $this->moduleHandler->invokeAll('test_entity_type_create', [$entity])
207 $this->moduleHandler->invokeAll('entity_create', [$entity, 'test_entity_type'])
210 $this->uuidService->generate()->willReturn('bar');
212 $entity = $this->entityStorage->create(['id' => 'foo']);
213 $this->assertInstanceOf(EntityInterface::class, $entity);
214 $this->assertSame('foo', $entity->id());
215 $this->assertSame('bar', $entity->uuid());
223 public function testCreateWithCurrentLanguage() {
224 $this->languageManager->getLanguage('hu')->willReturn(new Language(['id' => 'hu']));
226 $entity = $this->entityStorage->create(['id' => 'foo']);
227 $this->assertSame('hu', $entity->language()->getId());
234 public function testCreateWithExplicitLanguage() {
235 $this->languageManager->getLanguage('en')->willReturn(new Language(['id' => 'en']));
237 $entity = $this->entityStorage->create(['id' => 'foo', 'langcode' => 'en']);
238 $this->assertSame('en', $entity->language()->getId());
245 * @param \Drupal\Core\Entity\EntityInterface $entity
247 * @return \Drupal\Core\Entity\EntityInterface
249 * @depends testCreate
251 public function testSaveInsert(EntityInterface $entity) {
252 $immutable_config_object = $this->prophesize(ImmutableConfig::class);
253 $immutable_config_object->isNew()->willReturn(TRUE);
255 $config_object = $this->prophesize(Config::class);
256 $config_object->setData(['id' => 'foo', 'uuid' => 'bar', 'dependencies' => []])
258 $config_object->save(FALSE)->shouldBeCalled();
259 $config_object->get()->willReturn([]);
261 $this->cacheTagsInvalidator->invalidateTags([$this->entityTypeId . '_list'])
264 $this->configFactory->get('the_provider.the_config_prefix.foo')
265 ->willReturn($immutable_config_object->reveal());
266 $this->configFactory->getEditable('the_provider.the_config_prefix.foo')
267 ->willReturn($config_object->reveal());
269 $this->moduleHandler->invokeAll('test_entity_type_presave', [$entity])
271 $this->moduleHandler->invokeAll('entity_presave', [$entity, 'test_entity_type'])
273 $this->moduleHandler->invokeAll('test_entity_type_insert', [$entity])
275 $this->moduleHandler->invokeAll('entity_insert', [$entity, 'test_entity_type'])
278 $this->entityQuery->condition('uuid', 'bar')->willReturn($this->entityQuery);
279 $this->entityQuery->execute()->willReturn([]);
281 $return = $this->entityStorage->save($entity);
282 $this->assertSame(SAVED_NEW, $return);
290 * @param \Drupal\Core\Entity\EntityInterface $entity
292 * @return \Drupal\Core\Entity\EntityInterface
294 * @depends testSaveInsert
296 public function testSaveUpdate(EntityInterface $entity) {
297 $immutable_config_object = $this->prophesize(ImmutableConfig::class);
298 $immutable_config_object->isNew()->willReturn(FALSE);
300 $config_object = $this->prophesize(Config::class);
301 $config_object->setData(['id' => 'foo', 'uuid' => 'bar', 'dependencies' => []])
303 $config_object->save(FALSE)->shouldBeCalled();
304 $config_object->get()->willReturn([]);
306 $this->cacheTagsInvalidator->invalidateTags([$this->entityTypeId . '_list'])
309 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo'])
311 ->shouldBeCalledTimes(2);
313 ->get('the_provider.the_config_prefix.foo')
314 ->willReturn($immutable_config_object->reveal())
315 ->shouldBeCalledTimes(1);
317 ->getEditable('the_provider.the_config_prefix.foo')
318 ->willReturn($config_object->reveal())
319 ->shouldBeCalledTimes(1);
321 $this->moduleHandler->invokeAll('test_entity_type_presave', [$entity])
323 $this->moduleHandler->invokeAll('entity_presave', [$entity, 'test_entity_type'])
325 $this->moduleHandler->invokeAll('test_entity_type_update', [$entity])
327 $this->moduleHandler->invokeAll('entity_update', [$entity, 'test_entity_type'])
330 $this->entityQuery->condition('uuid', 'bar')->willReturn($this->entityQuery);
331 $this->entityQuery->execute()->willReturn([$entity->id()]);
333 $return = $this->entityStorage->save($entity);
334 $this->assertSame(SAVED_UPDATED, $return);
342 * @depends testSaveInsert
344 public function testSaveRename(ConfigEntityInterface $entity) {
345 $immutable_config_object = $this->prophesize(ImmutableConfig::class);
346 $immutable_config_object->isNew()->willReturn(FALSE);
348 $config_object = $this->prophesize(Config::class);
349 $config_object->setData(['id' => 'bar', 'uuid' => 'bar', 'dependencies' => []])
351 $config_object->save(FALSE)
353 $config_object->get()->willReturn([]);
355 $this->cacheTagsInvalidator->invalidateTags([$this->entityTypeId . '_list'])
358 $this->configFactory->get('the_provider.the_config_prefix.foo')
359 ->willReturn($immutable_config_object->reveal());
360 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo'])
362 $this->configFactory->rename('the_provider.the_config_prefix.foo', 'the_provider.the_config_prefix.bar')
364 $this->configFactory->getEditable('the_provider.the_config_prefix.bar')
365 ->willReturn($config_object->reveal());
367 // Performing a rename does not change the original ID until saving.
368 $this->assertSame('foo', $entity->getOriginalId());
369 $entity->set('id', 'bar');
370 $this->assertSame('foo', $entity->getOriginalId());
372 $this->entityQuery->condition('uuid', 'bar')->willReturn($this->entityQuery);
373 $this->entityQuery->execute()->willReturn([$entity->id()]);
375 $return = $this->entityStorage->save($entity);
376 $this->assertSame(SAVED_UPDATED, $return);
377 $this->assertSame('bar', $entity->getOriginalId());
383 public function testSaveInvalid() {
384 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())
385 ->shouldNotBeCalled();
387 $entity = $this->getMockEntity();
388 $this->setExpectedException(EntityMalformedException::class, 'The entity does not have an ID.');
389 $this->entityStorage->save($entity);
396 public function testSaveDuplicate() {
397 $config_object = $this->prophesize(ImmutableConfig::class);
398 $config_object->isNew()->willReturn(FALSE);
400 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())
401 ->shouldNotBeCalled();
403 $this->configFactory->get('the_provider.the_config_prefix.foo')
404 ->willReturn($config_object->reveal());
406 $entity = $this->getMockEntity(['id' => 'foo']);
407 $entity->enforceIsNew();
409 $this->setExpectedException(EntityStorageException::class);
410 $this->entityStorage->save($entity);
417 public function testSaveMismatch() {
418 $config_object = $this->prophesize(ImmutableConfig::class);
419 $config_object->isNew()->willReturn(TRUE);
421 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())
422 ->shouldNotBeCalled();
424 $this->configFactory->get('the_provider.the_config_prefix.foo')
425 ->willReturn($config_object->reveal());
427 $this->entityQuery->condition('uuid', NULL)->willReturn($this->entityQuery);
428 $this->entityQuery->execute()->willReturn(['baz']);
430 $entity = $this->getMockEntity(['id' => 'foo']);
431 $this->setExpectedException(ConfigDuplicateUUIDException::class, 'when this UUID is already used for');
432 $this->entityStorage->save($entity);
439 public function testSaveNoMismatch() {
440 $immutable_config_object = $this->prophesize(ImmutableConfig::class);
441 $immutable_config_object->isNew()->willReturn(TRUE);
443 $config_object = $this->prophesize(Config::class);
444 $config_object->get()->willReturn([]);
445 $config_object->setData(['id' => 'foo', 'uuid' => NULL, 'dependencies' => []])
447 $config_object->save(FALSE)->shouldBeCalled();
449 $this->cacheTagsInvalidator->invalidateTags([$this->entityTypeId . '_list'])
452 $this->configFactory->get('the_provider.the_config_prefix.baz')
453 ->willReturn($immutable_config_object->reveal())
455 $this->configFactory->rename('the_provider.the_config_prefix.baz', 'the_provider.the_config_prefix.foo')
457 $this->configFactory->getEditable('the_provider.the_config_prefix.foo')
458 ->willReturn($config_object->reveal())
461 $this->entityQuery->condition('uuid', NULL)->willReturn($this->entityQuery);
462 $this->entityQuery->execute()->willReturn(['baz']);
464 $entity = $this->getMockEntity(['id' => 'foo']);
465 $entity->setOriginalId('baz');
466 $entity->enforceIsNew();
467 $this->entityStorage->save($entity);
474 public function testSaveChangedUuid() {
475 $config_object = $this->prophesize(ImmutableConfig::class);
476 $config_object->get()->willReturn(['id' => 'foo']);
477 $config_object->get('id')->willReturn('foo');
478 $config_object->isNew()->willReturn(FALSE);
479 $config_object->getName()->willReturn('foo');
480 $config_object->getCacheContexts()->willReturn([]);
481 $config_object->getCacheTags()->willReturn(['config:foo']);
482 $config_object->getCacheMaxAge()->willReturn(Cache::PERMANENT);
484 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())
485 ->shouldNotBeCalled();
487 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo'])
488 ->willReturn([$config_object->reveal()]);
489 $this->configFactory->get('the_provider.the_config_prefix.foo')
490 ->willReturn($config_object->reveal());
491 $this->configFactory->rename(Argument::cetera())->shouldNotBeCalled();
493 $this->moduleHandler->getImplementations('entity_load')->willReturn([]);
494 $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]);
496 $this->entityQuery->condition('uuid', 'baz')->willReturn($this->entityQuery);
497 $this->entityQuery->execute()->willReturn(['foo']);
499 $entity = $this->getMockEntity(['id' => 'foo']);
501 $entity->set('uuid', 'baz');
502 $this->setExpectedException(ConfigDuplicateUUIDException::class, 'when this entity already exists with UUID');
503 $this->entityStorage->save($entity);
509 * @covers ::mapFromStorageRecords
510 * @covers ::doLoadMultiple
512 public function testLoad() {
513 $config_object = $this->prophesize(ImmutableConfig::class);
514 $config_object->get()->willReturn(['id' => 'foo']);
515 $config_object->get('id')->willReturn('foo');
516 $config_object->getCacheContexts()->willReturn([]);
517 $config_object->getCacheTags()->willReturn(['config:foo']);
518 $config_object->getCacheMaxAge()->willReturn(Cache::PERMANENT);
519 $config_object->getName()->willReturn('foo');
521 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo'])
522 ->willReturn([$config_object->reveal()]);
524 $this->moduleHandler->getImplementations('entity_load')->willReturn([]);
525 $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]);
527 $entity = $this->entityStorage->load('foo');
528 $this->assertInstanceOf(EntityInterface::class, $entity);
529 $this->assertSame('foo', $entity->id());
533 * @covers ::loadMultiple
535 * @covers ::mapFromStorageRecords
536 * @covers ::doLoadMultiple
538 public function testLoadMultipleAll() {
539 $foo_config_object = $this->prophesize(ImmutableConfig::class);
540 $foo_config_object->get()->willReturn(['id' => 'foo']);
541 $foo_config_object->get('id')->willReturn('foo');
542 $foo_config_object->getCacheContexts()->willReturn([]);
543 $foo_config_object->getCacheTags()->willReturn(['config:foo']);
544 $foo_config_object->getCacheMaxAge()->willReturn(Cache::PERMANENT);
545 $foo_config_object->getName()->willReturn('foo');
547 $bar_config_object = $this->prophesize(ImmutableConfig::class);
548 $bar_config_object->get()->willReturn(['id' => 'bar']);
549 $bar_config_object->get('id')->willReturn('bar');
550 $bar_config_object->getCacheContexts()->willReturn([]);
551 $bar_config_object->getCacheTags()->willReturn(['config:bar']);
552 $bar_config_object->getCacheMaxAge()->willReturn(Cache::PERMANENT);
553 $bar_config_object->getName()->willReturn('foo');
555 $this->configFactory->listAll('the_provider.the_config_prefix.')
556 ->willReturn(['the_provider.the_config_prefix.foo', 'the_provider.the_config_prefix.bar']);
557 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo', 'the_provider.the_config_prefix.bar'])
558 ->willReturn([$foo_config_object->reveal(), $bar_config_object->reveal()]);
560 $this->moduleHandler->getImplementations('entity_load')->willReturn([]);
561 $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]);
563 $entities = $this->entityStorage->loadMultiple();
564 $expected['foo'] = 'foo';
565 $expected['bar'] = 'bar';
566 $this->assertContainsOnlyInstancesOf(EntityInterface::class, $entities);
567 foreach ($entities as $id => $entity) {
568 $this->assertSame($id, $entity->id());
569 $this->assertSame($expected[$id], $entity->id());
574 * @covers ::loadMultiple
576 * @covers ::mapFromStorageRecords
577 * @covers ::doLoadMultiple
579 public function testLoadMultipleIds() {
580 $config_object = $this->prophesize(ImmutableConfig::class);
581 $config_object->get()->willReturn(['id' => 'foo']);
582 $config_object->get('id')->willReturn('foo');
583 $config_object->getCacheContexts()->willReturn([]);
584 $config_object->getCacheTags()->willReturn(['config:foo']);
585 $config_object->getCacheMaxAge()->willReturn(Cache::PERMANENT);
586 $config_object->getName()->willReturn('foo');
588 $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo'])
589 ->willReturn([$config_object->reveal()]);
591 $this->moduleHandler->getImplementations('entity_load')->willReturn([]);
592 $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]);
594 $entities = $this->entityStorage->loadMultiple(['foo']);
595 $this->assertContainsOnlyInstancesOf(EntityInterface::class, $entities);
596 foreach ($entities as $id => $entity) {
597 $this->assertSame($id, $entity->id());
602 * @covers ::loadRevision
604 public function testLoadRevision() {
605 $this->assertSame(NULL, $this->entityStorage->loadRevision(1));
609 * @covers ::deleteRevision
611 public function testDeleteRevision() {
612 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())
613 ->shouldNotBeCalled();
615 $this->assertSame(NULL, $this->entityStorage->deleteRevision(1));
622 public function testDelete() {
623 // Dependencies are tested in
624 // \Drupal\Tests\config\Kernel\ConfigDependencyTest.
626 ->getConfigEntitiesToChangeOnDependencyRemoval('config', ['the_provider.the_config_prefix.foo'], FALSE)
627 ->willReturn(['update' => [], 'delete' => [], 'unchanged' => []]);
629 ->getConfigEntitiesToChangeOnDependencyRemoval('config', ['the_provider.the_config_prefix.bar'], FALSE)
630 ->willReturn(['update' => [], 'delete' => [], 'unchanged' => []]);
633 foreach (['foo', 'bar'] as $id) {
634 $entity = $this->getMockEntity(['id' => $id]);
635 $entities[] = $entity;
637 $config_object = $this->prophesize(Config::class);
638 $config_object->delete()->shouldBeCalled();
640 $this->configFactory->getEditable("the_provider.the_config_prefix.$id")
641 ->willReturn($config_object->reveal());
643 $this->moduleHandler->invokeAll('test_entity_type_predelete', [$entity])
645 $this->moduleHandler->invokeAll('entity_predelete', [$entity, 'test_entity_type'])
648 $this->moduleHandler->invokeAll('test_entity_type_delete', [$entity])
650 $this->moduleHandler->invokeAll('entity_delete', [$entity, 'test_entity_type'])
654 $this->cacheTagsInvalidator->invalidateTags([$this->entityTypeId . '_list'])
657 $this->entityStorage->delete($entities);
664 public function testDeleteNothing() {
665 $this->moduleHandler->getImplementations(Argument::cetera())->shouldNotBeCalled();
666 $this->moduleHandler->invokeAll(Argument::cetera())->shouldNotBeCalled();
668 $this->configFactory->get(Argument::cetera())->shouldNotBeCalled();
669 $this->configFactory->getEditable(Argument::cetera())->shouldNotBeCalled();
671 $this->cacheTagsInvalidator->invalidateTags(Argument::cetera())->shouldNotBeCalled();
673 $this->entityStorage->delete([]);
677 * Creates an entity with specific methods mocked.
679 * @param array $values
680 * (optional) Values to pass to the constructor.
681 * @param array $methods
682 * (optional) The methods to mock.
684 * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject
686 public function getMockEntity(array $values = [], $methods = []) {
687 return $this->getMockForAbstractClass(ConfigEntityBase::class, [$values, 'test_entity_type'], '', TRUE, TRUE, TRUE, $methods);
692 namespace Drupal\Core\Config\Entity;
694 if (!defined('SAVED_NEW')) {
695 define('SAVED_NEW', 1);
697 if (!defined('SAVED_UPDATED')) {
698 define('SAVED_UPDATED', 2);