8cb36eb89ebea25a3a207deca4f1e57dd50d9e64
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Entity / EntityUnitTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Entity;
4
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\DependencyInjection\ContainerBuilder;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Entity\EntityTypeManagerInterface;
10 use Drupal\Core\Entity\EntityTypeRepositoryInterface;
11 use Drupal\Core\Language\Language;
12 use Drupal\entity_test\Entity\EntityTestMul;
13 use Drupal\Tests\UnitTestCase;
14
15 /**
16  * @coversDefaultClass \Drupal\Core\Entity\Entity
17  * @group Entity
18  * @group Access
19  */
20 class EntityUnitTest extends UnitTestCase {
21
22   /**
23    * The entity under test.
24    *
25    * @var \Drupal\Core\Entity\Entity|\PHPUnit_Framework_MockObject_MockObject
26    */
27   protected $entity;
28
29   /**
30    * The entity type used for testing.
31    *
32    * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
33    */
34   protected $entityType;
35
36   /**
37    * The entity type manager used for testing.
38    *
39    * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
40    */
41   protected $entityTypeManager;
42
43   /**
44    * The ID of the type of the entity under test.
45    *
46    * @var string
47    */
48   protected $entityTypeId;
49
50   /**
51    * The route provider used for testing.
52    *
53    * @var \Drupal\Core\Routing\RouteProvider|\PHPUnit_Framework_MockObject_MockObject
54    */
55   protected $routeProvider;
56
57   /**
58    * The UUID generator used for testing.
59    *
60    * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
61    */
62   protected $uuid;
63
64   /**
65    * The language manager.
66    *
67    * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
68    */
69   protected $languageManager;
70
71   /**
72    * The mocked cache tags invalidator.
73    *
74    * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
75    */
76   protected $cacheTagsInvalidator;
77
78   /**
79    * The entity values.
80    *
81    * @var array
82    */
83   protected $values;
84
85   /**
86    * {@inheritdoc}
87    */
88   protected function setUp() {
89     $this->values = [
90       'id' => 1,
91       'langcode' => 'en',
92       'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
93     ];
94     $this->entityTypeId = $this->randomMachineName();
95
96     $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
97     $this->entityType->expects($this->any())
98       ->method('getListCacheTags')
99       ->willReturn([$this->entityTypeId . '_list']);
100
101     $this->entityTypeManager = $this->getMockForAbstractClass(EntityTypeManagerInterface::class);
102     $this->entityTypeManager->expects($this->any())
103       ->method('getDefinition')
104       ->with($this->entityTypeId)
105       ->will($this->returnValue($this->entityType));
106
107     $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
108
109     $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
110     $this->languageManager->expects($this->any())
111       ->method('getLanguage')
112       ->with('en')
113       ->will($this->returnValue(new Language(['id' => 'en'])));
114
115     $this->cacheTagsInvalidator = $this->getMock('Drupal\Core\Cache\CacheTagsInvalidator');
116
117     $container = new ContainerBuilder();
118     // Ensure that Entity doesn't use the deprecated entity.manager service.
119     $container->set('entity.manager', NULL);
120     $container->set('entity_type.manager', $this->entityTypeManager);
121     $container->set('uuid', $this->uuid);
122     $container->set('language_manager', $this->languageManager);
123     $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
124     \Drupal::setContainer($container);
125
126     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\Entity', [$this->values, $this->entityTypeId]);
127   }
128
129   /**
130    * @covers ::id
131    */
132   public function testId() {
133     $this->assertSame($this->values['id'], $this->entity->id());
134   }
135
136   /**
137    * @covers ::uuid
138    */
139   public function testUuid() {
140     $this->assertSame($this->values['uuid'], $this->entity->uuid());
141   }
142
143   /**
144    * @covers ::isNew
145    * @covers ::enforceIsNew
146    */
147   public function testIsNew() {
148     // We provided an ID, so the entity is not new.
149     $this->assertFalse($this->entity->isNew());
150     // Force it to be new.
151     $this->assertSame($this->entity, $this->entity->enforceIsNew());
152     $this->assertTrue($this->entity->isNew());
153   }
154
155   /**
156    * @covers ::getEntityType
157    */
158   public function testGetEntityType() {
159     $this->assertSame($this->entityType, $this->entity->getEntityType());
160   }
161
162   /**
163    * @covers ::bundle
164    */
165   public function testBundle() {
166     $this->assertSame($this->entityTypeId, $this->entity->bundle());
167   }
168
169   /**
170    * @covers ::label
171    */
172   public function testLabel() {
173     // Make a mock with one method that we use as the entity's uri_callback. We
174     // check that it is called, and that the entity's label is the callback's
175     // return value.
176     $callback_label = $this->randomMachineName();
177     $property_label = $this->randomMachineName();
178     $callback_container = $this->getMock(get_class());
179     $callback_container->expects($this->once())
180       ->method(__FUNCTION__)
181       ->will($this->returnValue($callback_label));
182     $this->entityType->expects($this->at(0))
183       ->method('getLabelCallback')
184       ->will($this->returnValue([$callback_container, __FUNCTION__]));
185     $this->entityType->expects($this->at(1))
186       ->method('getLabelCallback')
187       ->will($this->returnValue(NULL));
188     $this->entityType->expects($this->at(2))
189       ->method('getKey')
190       ->with('label')
191       ->will($this->returnValue('label'));
192
193     // Set a dummy property on the entity under test to test that the label can
194     // be returned form a property if there is no callback.
195     $this->entityTypeManager->expects($this->at(1))
196       ->method('getDefinition')
197       ->with($this->entityTypeId)
198       ->will($this->returnValue([
199         'entity_keys' => [
200           'label' => 'label',
201         ],
202       ]));
203     $this->entity->label = $property_label;
204
205     $this->assertSame($callback_label, $this->entity->label());
206     $this->assertSame($property_label, $this->entity->label());
207   }
208
209   /**
210    * @covers ::access
211    */
212   public function testAccess() {
213     $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
214     $operation = $this->randomMachineName();
215     $access->expects($this->at(0))
216       ->method('access')
217       ->with($this->entity, $operation)
218       ->will($this->returnValue(AccessResult::allowed()));
219     $access->expects($this->at(1))
220       ->method('createAccess')
221       ->will($this->returnValue(AccessResult::allowed()));
222     $this->entityTypeManager->expects($this->exactly(2))
223       ->method('getAccessControlHandler')
224       ->will($this->returnValue($access));
225
226     $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation));
227     $this->assertEquals(AccessResult::allowed(), $this->entity->access('create'));
228   }
229
230   /**
231    * @covers ::language
232    */
233   public function testLanguage() {
234     $this->entityType->expects($this->any())
235       ->method('getKey')
236       ->will($this->returnValueMap([
237         ['langcode', 'langcode'],
238       ]));
239     $this->assertSame('en', $this->entity->language()->getId());
240   }
241
242   /**
243    * Setup for the tests of the ::load() method.
244    */
245   public function setupTestLoad() {
246     // Base our mocked entity on a real entity class so we can test if calling
247     // Entity::load() on the base class will bubble up to an actual entity.
248     $this->entityTypeId = 'entity_test_mul';
249     $methods = get_class_methods(EntityTestMul::class);
250     unset($methods[array_search('load', $methods)]);
251     unset($methods[array_search('loadMultiple', $methods)]);
252     unset($methods[array_search('create', $methods)]);
253     $this->entity = $this->getMockBuilder(EntityTestMul::class)
254       ->disableOriginalConstructor()
255       ->setMethods($methods)
256       ->getMock();
257
258   }
259
260   /**
261    * @covers ::load
262    *
263    * Tests Entity::load() when called statically on a subclass of Entity.
264    */
265   public function testLoad() {
266     $this->setupTestLoad();
267
268     $class_name = get_class($this->entity);
269
270     $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
271     $entity_type_repository->expects($this->once())
272       ->method('getEntityTypeFromClass')
273       ->with($class_name)
274       ->willReturn($this->entityTypeId);
275
276     $storage = $this->getMock(EntityStorageInterface::class);
277     $storage->expects($this->once())
278       ->method('load')
279       ->with(1)
280       ->will($this->returnValue($this->entity));
281
282     $this->entityTypeManager->expects($this->once())
283       ->method('getStorage')
284       ->with($this->entityTypeId)
285       ->will($this->returnValue($storage));
286
287     \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
288
289     // Call Entity::load statically and check that it returns the mock entity.
290     $this->assertSame($this->entity, $class_name::load(1));
291   }
292
293   /**
294    * @covers ::loadMultiple
295    *
296    * Tests Entity::loadMultiple() when called statically on a subclass of
297    * Entity.
298    */
299   public function testLoadMultiple() {
300     $this->setupTestLoad();
301
302     $class_name = get_class($this->entity);
303
304     $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
305     $entity_type_repository->expects($this->once())
306       ->method('getEntityTypeFromClass')
307       ->with($class_name)
308       ->willReturn($this->entityTypeId);
309
310     $storage = $this->getMock(EntityStorageInterface::class);
311     $storage->expects($this->once())
312       ->method('loadMultiple')
313       ->with([1])
314       ->will($this->returnValue([1 => $this->entity]));
315
316     $this->entityTypeManager->expects($this->once())
317       ->method('getStorage')
318       ->with($this->entityTypeId)
319       ->will($this->returnValue($storage));
320
321     \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
322
323     // Call Entity::loadMultiple statically and check that it returns the mock
324     // entity.
325     $this->assertSame([1 => $this->entity], $class_name::loadMultiple([1]));
326   }
327
328   /**
329    * @covers ::create
330    */
331   public function testCreate() {
332     $this->setupTestLoad();
333
334     $class_name = get_class($this->entity);
335
336     $entity_type_repository = $this->getMockForAbstractClass(EntityTypeRepositoryInterface::class);
337     $entity_type_repository->expects($this->once())
338       ->method('getEntityTypeFromClass')
339       ->with($class_name)
340       ->willReturn($this->entityTypeId);
341
342     $storage = $this->getMock(EntityStorageInterface::class);
343     $storage->expects($this->once())
344       ->method('create')
345       ->with([])
346       ->will($this->returnValue($this->entity));
347
348     $this->entityTypeManager->expects($this->once())
349       ->method('getStorage')
350       ->with($this->entityTypeId)
351       ->will($this->returnValue($storage));
352
353     \Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
354
355     // Call Entity::create() statically and check that it returns the mock
356     // entity.
357     $this->assertSame($this->entity, $class_name::create([]));
358   }
359
360   /**
361    * @covers ::save
362    */
363   public function testSave() {
364     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
365     $storage->expects($this->once())
366       ->method('save')
367       ->with($this->entity);
368
369     $this->entityTypeManager->expects($this->once())
370       ->method('getStorage')
371       ->with($this->entityTypeId)
372       ->will($this->returnValue($storage));
373
374     $this->entity->save();
375   }
376
377   /**
378    * @covers ::delete
379    */
380   public function testDelete() {
381     $this->entity->id = $this->randomMachineName();
382     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
383     // Testing the argument of the delete() method consumes too much memory.
384     $storage->expects($this->once())
385       ->method('delete');
386
387     $this->entityTypeManager->expects($this->once())
388       ->method('getStorage')
389       ->with($this->entityTypeId)
390       ->will($this->returnValue($storage));
391
392     $this->entity->delete();
393   }
394
395   /**
396    * @covers ::getEntityTypeId
397    */
398   public function testGetEntityTypeId() {
399     $this->assertSame($this->entityTypeId, $this->entity->getEntityTypeId());
400   }
401
402   /**
403    * @covers ::preSave
404    */
405   public function testPreSave() {
406     // This method is internal, so check for errors on calling it only.
407     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
408     // Our mocked entity->preSave() returns NULL, so assert that.
409     $this->assertNull($this->entity->preSave($storage));
410   }
411
412   /**
413    * @covers ::postSave
414    */
415   public function testPostSave() {
416     $this->cacheTagsInvalidator->expects($this->at(0))
417       ->method('invalidateTags')
418       ->with([
419         // List cache tag.
420         $this->entityTypeId . '_list',
421       ]);
422     $this->cacheTagsInvalidator->expects($this->at(1))
423       ->method('invalidateTags')
424       ->with([
425         // Own cache tag.
426         $this->entityTypeId . ':' . $this->values['id'],
427         // List cache tag.
428         $this->entityTypeId . '_list',
429       ]);
430
431     // This method is internal, so check for errors on calling it only.
432     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
433
434     // A creation should trigger the invalidation of the "list" cache tag.
435     $this->entity->postSave($storage, FALSE);
436     // An update should trigger the invalidation of both the "list" and the
437     // "own" cache tags.
438     $this->entity->postSave($storage, TRUE);
439   }
440
441   /**
442    * @covers ::preCreate
443    */
444   public function testPreCreate() {
445     // This method is internal, so check for errors on calling it only.
446     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
447     $values = [];
448     // Our mocked entity->preCreate() returns NULL, so assert that.
449     $this->assertNull($this->entity->preCreate($storage, $values));
450   }
451
452   /**
453    * @covers ::postCreate
454    */
455   public function testPostCreate() {
456     // This method is internal, so check for errors on calling it only.
457     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
458     // Our mocked entity->postCreate() returns NULL, so assert that.
459     $this->assertNull($this->entity->postCreate($storage));
460   }
461
462   /**
463    * @covers ::preDelete
464    */
465   public function testPreDelete() {
466     // This method is internal, so check for errors on calling it only.
467     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
468     // Our mocked entity->preDelete() returns NULL, so assert that.
469     $this->assertNull($this->entity->preDelete($storage, [$this->entity]));
470   }
471
472   /**
473    * @covers ::postDelete
474    */
475   public function testPostDelete() {
476     $this->cacheTagsInvalidator->expects($this->once())
477       ->method('invalidateTags')
478       ->with([
479         $this->entityTypeId . ':' . $this->values['id'],
480         $this->entityTypeId . '_list',
481       ]);
482     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
483     $storage->expects($this->once())
484       ->method('getEntityType')
485       ->willReturn($this->entityType);
486
487     $entities = [$this->values['id'] => $this->entity];
488     $this->entity->postDelete($storage, $entities);
489   }
490
491   /**
492    * @covers ::postLoad
493    */
494   public function testPostLoad() {
495     // This method is internal, so check for errors on calling it only.
496     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
497     $entities = [$this->entity];
498     // Our mocked entity->postLoad() returns NULL, so assert that.
499     $this->assertNull($this->entity->postLoad($storage, $entities));
500   }
501
502   /**
503    * @covers ::referencedEntities
504    */
505   public function testReferencedEntities() {
506     $this->assertSame([], $this->entity->referencedEntities());
507   }
508
509   /**
510    * @covers ::getCacheTags
511    * @covers ::getCacheTagsToInvalidate
512    * @covers ::addCacheTags
513    */
514   public function testCacheTags() {
515     // Ensure that both methods return the same by default.
516     $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTags());
517     $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
518
519     // Add an additional cache tag and make sure only getCacheTags() returns
520     // that.
521     $this->entity->addCacheTags(['additional_cache_tag']);
522
523     // EntityTypeId is random so it can shift order. We need to duplicate the
524     // sort from \Drupal\Core\Cache\Cache::mergeTags().
525     $tags = ['additional_cache_tag', $this->entityTypeId . ':' . 1];
526     sort($tags);
527     $this->assertEquals($tags, $this->entity->getCacheTags());
528     $this->assertEquals([$this->entityTypeId . ':' . 1], $this->entity->getCacheTagsToInvalidate());
529   }
530
531   /**
532    * @covers ::getCacheContexts
533    * @covers ::addCacheContexts
534    */
535   public function testCacheContexts() {
536     $cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
537       ->disableOriginalConstructor()
538       ->getMock();
539     $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
540
541     $container = new ContainerBuilder();
542     $container->set('cache_contexts_manager', $cache_contexts_manager);
543     \Drupal::setContainer($container);
544
545     // There are no cache contexts by default.
546     $this->assertEquals([], $this->entity->getCacheContexts());
547
548     // Add an additional cache context.
549     $this->entity->addCacheContexts(['user']);
550     $this->assertEquals(['user'], $this->entity->getCacheContexts());
551   }
552
553   /**
554    * @covers ::getCacheMaxAge
555    * @covers ::mergeCacheMaxAge
556    */
557   public function testCacheMaxAge() {
558     // Cache max age is permanent by default.
559     $this->assertEquals(Cache::PERMANENT, $this->entity->getCacheMaxAge());
560
561     // Set two cache max ages, the lower value is the one that needs to be
562     // returned.
563     $this->entity->mergeCacheMaxAge(600);
564     $this->entity->mergeCacheMaxAge(1800);
565     $this->assertEquals(600, $this->entity->getCacheMaxAge());
566   }
567
568 }