541ec88e2a42534f3baa0d8a55503f6ad5dd3700
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Entity / ContentEntityBaseUnitTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Entity;
4
5 use Drupal\Core\Access\AccessResult;
6 use Drupal\Core\DependencyInjection\ContainerBuilder;
7 use Drupal\Core\Entity\ContentEntityInterface;
8 use Drupal\Core\Field\BaseFieldDefinition;
9 use Drupal\Core\Language\LanguageInterface;
10 use Drupal\Core\TypedData\TypedDataManagerInterface;
11 use Drupal\Tests\UnitTestCase;
12 use Drupal\Core\Language\Language;
13 use Symfony\Component\Validator\Validator\ValidatorInterface;
14
15 /**
16  * @coversDefaultClass \Drupal\Core\Entity\ContentEntityBase
17  * @group Entity
18  * @group Access
19  */
20 class ContentEntityBaseUnitTest extends UnitTestCase {
21
22   /**
23    * The bundle of the entity under test.
24    *
25    * @var string
26    */
27   protected $bundle;
28
29   /**
30    * The entity under test.
31    *
32    * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
33    */
34   protected $entity;
35
36   /**
37    * An entity with no defined language to test.
38    *
39    * @var \Drupal\Core\Entity\ContentEntityBase|\PHPUnit_Framework_MockObject_MockObject
40    */
41   protected $entityUnd;
42
43   /**
44    * The entity type used for testing.
45    *
46    * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
47    */
48   protected $entityType;
49
50   /**
51    * The entity manager used for testing.
52    *
53    * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
54    */
55   protected $entityManager;
56
57   /**
58    * The type ID of the entity under test.
59    *
60    * @var string
61    */
62   protected $entityTypeId;
63
64   /**
65    * The typed data manager used for testing.
66    *
67    * @var \Drupal\Core\TypedData\TypedDataManager|\PHPUnit_Framework_MockObject_MockObject
68    */
69   protected $typedDataManager;
70
71   /**
72    * The field type manager used for testing.
73    *
74    * @var \Drupal\Core\Field\FieldTypePluginManager|\PHPUnit_Framework_MockObject_MockObject
75    */
76   protected $fieldTypePluginManager;
77
78   /**
79    * The language manager.
80    *
81    * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
82    */
83   protected $languageManager;
84
85   /**
86    * The UUID generator used for testing.
87    *
88    * @var \Drupal\Component\Uuid\UuidInterface|\PHPUnit_Framework_MockObject_MockObject
89    */
90   protected $uuid;
91
92   /**
93    * The entity ID.
94    *
95    * @var int
96    */
97   protected $id;
98
99   /**
100    * Field definitions.
101    *
102    * @var \Drupal\Core\Field\BaseFieldDefinition[]
103    */
104   protected $fieldDefinitions;
105
106   /**
107    * {@inheritdoc}
108    */
109   protected function setUp() {
110     $this->id = 1;
111     $values = [
112       'id' => $this->id,
113       'uuid' => '3bb9ee60-bea5-4622-b89b-a63319d10b3a',
114       'defaultLangcode' => [LanguageInterface::LANGCODE_DEFAULT => 'en'],
115     ];
116     $this->entityTypeId = $this->randomMachineName();
117     $this->bundle = $this->randomMachineName();
118
119     $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
120     $this->entityType->expects($this->any())
121       ->method('getKeys')
122       ->will($this->returnValue([
123         'id' => 'id',
124         'uuid' => 'uuid',
125     ]));
126
127     $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface');
128     $this->entityManager->expects($this->any())
129       ->method('getDefinition')
130       ->with($this->entityTypeId)
131       ->will($this->returnValue($this->entityType));
132
133     $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface');
134
135     $this->typedDataManager = $this->getMock(TypedDataManagerInterface::class);
136     $this->typedDataManager->expects($this->any())
137       ->method('getDefinition')
138       ->with('entity')
139       ->will($this->returnValue(['class' => '\Drupal\Core\Entity\Plugin\DataType\EntityAdapter']));
140
141     $english = new Language(['id' => 'en']);
142     $not_specified = new Language(['id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE]);
143     $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
144     $this->languageManager->expects($this->any())
145       ->method('getLanguages')
146       ->will($this->returnValue(['en' => $english, LanguageInterface::LANGCODE_NOT_SPECIFIED => $not_specified]));
147     $this->languageManager->expects($this->any())
148       ->method('getLanguage')
149       ->with('en')
150       ->will($this->returnValue($english));
151     $this->languageManager->expects($this->any())
152       ->method('getLanguage')
153       ->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
154       ->will($this->returnValue($not_specified));
155
156     $this->fieldTypePluginManager = $this->getMockBuilder('\Drupal\Core\Field\FieldTypePluginManager')
157       ->disableOriginalConstructor()
158       ->getMock();
159     $this->fieldTypePluginManager->expects($this->any())
160       ->method('getDefaultStorageSettings')
161       ->will($this->returnValue([]));
162     $this->fieldTypePluginManager->expects($this->any())
163       ->method('getDefaultFieldSettings')
164       ->will($this->returnValue([]));
165     $this->fieldTypePluginManager->expects($this->any())
166       ->method('createFieldItemList')
167       ->will($this->returnValue($this->getMock('Drupal\Core\Field\FieldItemListInterface')));
168
169     $container = new ContainerBuilder();
170     $container->set('entity.manager', $this->entityManager);
171     $container->set('uuid', $this->uuid);
172     $container->set('typed_data_manager', $this->typedDataManager);
173     $container->set('language_manager', $this->languageManager);
174     $container->set('plugin.manager.field.field_type', $this->fieldTypePluginManager);
175     \Drupal::setContainer($container);
176
177     $this->fieldDefinitions = [
178       'id' => BaseFieldDefinition::create('integer'),
179       'revision_id' => BaseFieldDefinition::create('integer'),
180     ];
181
182     $this->entityManager->expects($this->any())
183       ->method('getFieldDefinitions')
184       ->with($this->entityTypeId, $this->bundle)
185       ->will($this->returnValue($this->fieldDefinitions));
186
187     $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle], '', TRUE, TRUE, TRUE, ['isNew']);
188     $values['defaultLangcode'] = [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED];
189     $this->entityUnd = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle]);
190   }
191
192   /**
193    * @covers ::isNewRevision
194    * @covers ::setNewRevision
195    */
196   public function testIsNewRevision() {
197     // Set up the entity type so that on the first call there is no revision key
198     // and on the second call there is one.
199     $this->entityType->expects($this->at(0))
200       ->method('hasKey')
201       ->with('revision')
202       ->will($this->returnValue(FALSE));
203     $this->entityType->expects($this->at(1))
204       ->method('hasKey')
205       ->with('revision')
206       ->will($this->returnValue(TRUE));
207     $this->entityType->expects($this->at(2))
208       ->method('hasKey')
209       ->with('revision')
210       ->will($this->returnValue(TRUE));
211     $this->entityType->expects($this->at(3))
212       ->method('getKey')
213       ->with('revision')
214       ->will($this->returnValue('revision_id'));
215     $this->entityType->expects($this->at(4))
216       ->method('hasKey')
217       ->with('revision')
218       ->will($this->returnValue(TRUE));
219     $this->entityType->expects($this->at(5))
220       ->method('getKey')
221       ->with('revision')
222       ->will($this->returnValue('revision_id'));
223
224     $field_item_list = $this->getMockBuilder('\Drupal\Core\Field\FieldItemList')
225       ->disableOriginalConstructor()
226       ->getMock();
227     $field_item = $this->getMockBuilder('\Drupal\Core\Field\FieldItemBase')
228       ->disableOriginalConstructor()
229       ->getMockForAbstractClass();
230
231     $this->fieldTypePluginManager->expects($this->any())
232       ->method('createFieldItemList')
233       ->with($this->entity, 'revision_id', NULL)
234       ->will($this->returnValue($field_item_list));
235
236     $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item));
237
238     $this->assertFalse($this->entity->isNewRevision());
239     $this->assertTrue($this->entity->isNewRevision());
240     $this->entity->setNewRevision(TRUE);
241     $this->assertTrue($this->entity->isNewRevision());
242   }
243
244   /**
245    * @covers ::setNewRevision
246    */
247   public function testSetNewRevisionException() {
248     $this->entityType->expects($this->once())
249       ->method('hasKey')
250       ->with('revision')
251       ->will($this->returnValue(FALSE));
252     $this->setExpectedException('LogicException', 'Entity type ' . $this->entityTypeId . ' does not support revisions.');
253     $this->entity->setNewRevision();
254   }
255
256   /**
257    * @covers ::isDefaultRevision
258    */
259   public function testIsDefaultRevision() {
260     // The default value is TRUE.
261     $this->assertTrue($this->entity->isDefaultRevision());
262     // Change the default revision, verify that the old value is returned.
263     $this->assertTrue($this->entity->isDefaultRevision(FALSE));
264     // The last call changed the return value for this call.
265     $this->assertFalse($this->entity->isDefaultRevision());
266     // The revision for a new entity should always be the default revision.
267     $this->entity->expects($this->any())
268       ->method('isNew')
269       ->will($this->returnValue(TRUE));
270     $this->entity->isDefaultRevision(FALSE);
271     $this->assertTrue($this->entity->isDefaultRevision());
272   }
273
274   /**
275    * @covers ::getRevisionId
276    */
277   public function testGetRevisionId() {
278     // The default getRevisionId() implementation returns NULL.
279     $this->assertNull($this->entity->getRevisionId());
280   }
281
282   /**
283    * @covers ::isTranslatable
284    */
285   public function testIsTranslatable() {
286     $this->entityManager->expects($this->any())
287       ->method('getBundleInfo')
288       ->with($this->entityTypeId)
289       ->will($this->returnValue([
290         $this->bundle => [
291           'translatable' => TRUE,
292         ],
293       ]));
294     $this->languageManager->expects($this->any())
295       ->method('isMultilingual')
296       ->will($this->returnValue(TRUE));
297     $this->assertTrue($this->entity->language()->getId() == 'en');
298     $this->assertFalse($this->entity->language()->isLocked());
299     $this->assertTrue($this->entity->isTranslatable());
300
301     $this->assertTrue($this->entityUnd->language()->getId() == LanguageInterface::LANGCODE_NOT_SPECIFIED);
302     $this->assertTrue($this->entityUnd->language()->isLocked());
303     $this->assertFalse($this->entityUnd->isTranslatable());
304   }
305
306   /**
307    * @covers ::isTranslatable
308    */
309   public function testIsTranslatableForMonolingual() {
310     $this->languageManager->expects($this->any())
311       ->method('isMultilingual')
312       ->will($this->returnValue(FALSE));
313     $this->assertFalse($this->entity->isTranslatable());
314   }
315
316   /**
317    * @covers ::preSaveRevision
318    */
319   public function testPreSaveRevision() {
320     // This method is internal, so check for errors on calling it only.
321     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
322     $record = new \stdClass();
323     // Our mocked entity->preSaveRevision() returns NULL, so assert that.
324     $this->assertNull($this->entity->preSaveRevision($storage, $record));
325   }
326
327   /**
328    * @covers ::validate
329    */
330   public function testValidate() {
331     $validator = $this->getMock(ValidatorInterface::class);
332     /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
333     $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
334       ->setMethods(NULL)
335       ->getMock();
336     $non_empty_violation_list = clone $empty_violation_list;
337     $violation = $this->getMock('\Symfony\Component\Validator\ConstraintViolationInterface');
338     $non_empty_violation_list->add($violation);
339     $validator->expects($this->at(0))
340       ->method('validate')
341       ->with($this->entity->getTypedData())
342       ->will($this->returnValue($empty_violation_list));
343     $validator->expects($this->at(1))
344       ->method('validate')
345       ->with($this->entity->getTypedData())
346       ->will($this->returnValue($non_empty_violation_list));
347     $this->typedDataManager->expects($this->exactly(2))
348       ->method('getValidator')
349       ->will($this->returnValue($validator));
350     $this->assertSame(0, count($this->entity->validate()));
351     $this->assertSame(1, count($this->entity->validate()));
352   }
353
354   /**
355    * Tests required validation.
356    *
357    * @covers ::validate
358    * @covers ::isValidationRequired
359    * @covers ::setValidationRequired
360    * @covers ::save
361    * @covers ::preSave
362    */
363   public function testRequiredValidation() {
364     $validator = $this->getMock(ValidatorInterface::class);
365     /** @var \Symfony\Component\Validator\ConstraintViolationList|\PHPUnit_Framework_MockObject_MockObject $empty_violation_list */
366     $empty_violation_list = $this->getMockBuilder('\Symfony\Component\Validator\ConstraintViolationList')
367       ->setMethods(NULL)
368       ->getMock();
369     $validator->expects($this->at(0))
370       ->method('validate')
371       ->with($this->entity->getTypedData())
372       ->will($this->returnValue($empty_violation_list));
373     $this->typedDataManager->expects($this->any())
374       ->method('getValidator')
375       ->will($this->returnValue($validator));
376
377     /** @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit_Framework_MockObject_MockObject $storage */
378     $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface');
379     $storage->expects($this->any())
380       ->method('save')
381       ->willReturnCallback(function (ContentEntityInterface $entity) use ($storage) {
382         $entity->preSave($storage);
383       });
384
385     $this->entityManager->expects($this->any())
386       ->method('getStorage')
387       ->with($this->entityTypeId)
388       ->will($this->returnValue($storage));
389
390     // Check that entities can be saved normally when validation is not
391     // required.
392     $this->assertFalse($this->entity->isValidationRequired());
393     $this->entity->save();
394
395     // Make validation required and check that if the entity is validated, it
396     // can be saved normally.
397     $this->entity->setValidationRequired(TRUE);
398     $this->assertTrue($this->entity->isValidationRequired());
399     $this->entity->validate();
400     $this->entity->save();
401
402     // Check that the "validated" status is reset after saving the entity and
403     // that trying to save a non-validated entity when validation is required
404     // results in an exception.
405     $this->assertTrue($this->entity->isValidationRequired());
406     $this->setExpectedException(\LogicException::class, 'Entity validation was skipped.');
407     $this->entity->save();
408   }
409
410   /**
411    * @covers ::bundle
412    */
413   public function testBundle() {
414     $this->assertSame($this->bundle, $this->entity->bundle());
415   }
416
417   /**
418    * @covers ::access
419    */
420   public function testAccess() {
421     $access = $this->getMock('\Drupal\Core\Entity\EntityAccessControlHandlerInterface');
422     $operation = $this->randomMachineName();
423     $access->expects($this->at(0))
424       ->method('access')
425       ->with($this->entity, $operation)
426       ->will($this->returnValue(TRUE));
427     $access->expects($this->at(1))
428       ->method('access')
429       ->with($this->entity, $operation)
430       ->will($this->returnValue(AccessResult::allowed()));
431     $access->expects($this->at(2))
432       ->method('createAccess')
433       ->will($this->returnValue(TRUE));
434     $access->expects($this->at(3))
435       ->method('createAccess')
436       ->will($this->returnValue(AccessResult::allowed()));
437     $this->entityManager->expects($this->exactly(4))
438       ->method('getAccessControlHandler')
439       ->will($this->returnValue($access));
440     $this->assertTrue($this->entity->access($operation));
441     $this->assertEquals(AccessResult::allowed(), $this->entity->access($operation, NULL, TRUE));
442     $this->assertTrue($this->entity->access('create'));
443     $this->assertEquals(AccessResult::allowed(), $this->entity->access('create', NULL, TRUE));
444   }
445
446   /**
447    * @covers ::label
448    */
449   public function testLabel() {
450     // Make a mock with one method that we use as the entity's label callback.
451     // We check that it is called, and that the entity's label is the callback's
452     // return value.
453     $callback_label = $this->randomMachineName();
454     $callback_container = $this->getMock(get_class());
455     $callback_container->expects($this->once())
456       ->method(__FUNCTION__)
457       ->will($this->returnValue($callback_label));
458     $this->entityType->expects($this->once())
459       ->method('getLabelCallback')
460       ->will($this->returnValue([$callback_container, __FUNCTION__]));
461
462     $this->assertSame($callback_label, $this->entity->label());
463   }
464
465   /**
466    * Data provider for testGet().
467    *
468    * @returns
469    *   - Expected output from get().
470    *   - Field name parameter to get().
471    *   - Language code for $activeLanguage.
472    *   - Fields array for $fields.
473    */
474   public function providerGet() {
475     return [
476       // Populated fields array.
477       ['result', 'field_name', 'langcode', ['field_name' => ['langcode' => 'result']]],
478       // Incomplete fields array.
479       ['getTranslatedField_result', 'field_name', 'langcode', ['field_name' => 'no_langcode']],
480       // Empty fields array.
481       ['getTranslatedField_result', 'field_name', 'langcode', []],
482     ];
483   }
484
485   /**
486    * @covers ::get
487    * @dataProvider providerGet
488    */
489   public function testGet($expected, $field_name, $active_langcode, $fields) {
490     // Mock ContentEntityBase.
491     $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
492       ->disableOriginalConstructor()
493       ->setMethods(['getTranslatedField'])
494       ->getMockForAbstractClass();
495
496     // Set up expectations for getTranslatedField() method. In get(),
497     // getTranslatedField() is only called if the field name and language code
498     // are not present as keys in the fields array.
499     if (isset($fields[$field_name][$active_langcode])) {
500       $mock_base->expects($this->never())
501         ->method('getTranslatedField');
502     }
503     else {
504       $mock_base->expects($this->once())
505         ->method('getTranslatedField')
506         ->with(
507           $this->equalTo($field_name),
508           $this->equalTo($active_langcode)
509         )
510         ->willReturn($expected);
511     }
512
513     // Poke in activeLangcode.
514     $ref_langcode = new \ReflectionProperty($mock_base, 'activeLangcode');
515     $ref_langcode->setAccessible(TRUE);
516     $ref_langcode->setValue($mock_base, $active_langcode);
517
518     // Poke in fields.
519     $ref_fields = new \ReflectionProperty($mock_base, 'fields');
520     $ref_fields->setAccessible(TRUE);
521     $ref_fields->setValue($mock_base, $fields);
522
523     // Exercise get().
524     $this->assertEquals($expected, $mock_base->get($field_name));
525   }
526
527   /**
528    * Data provider for testGetFields().
529    *
530    * @returns array
531    *   - Expected output from getFields().
532    *   - $include_computed value to pass to getFields().
533    *   - Value to mock from all field definitions for isComputed().
534    *   - Array of field names to return from mocked getFieldDefinitions(). A
535    *     Drupal\Core\Field\FieldDefinitionInterface object will be mocked for
536    *     each name.
537    */
538   public function providerGetFields() {
539     return [
540       [[], FALSE, FALSE, []],
541       [['field' => 'field', 'field2' => 'field2'], TRUE, FALSE, ['field', 'field2']],
542       [['field3' => 'field3'], TRUE, TRUE, ['field3']],
543       [[], FALSE, TRUE, ['field4']],
544     ];
545   }
546
547   /**
548    * @covers ::getFields
549    * @dataProvider providerGetFields
550    */
551   public function testGetFields($expected, $include_computed, $is_computed, $field_definitions) {
552     // Mock ContentEntityBase.
553     $mock_base = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
554       ->disableOriginalConstructor()
555       ->setMethods(['getFieldDefinitions', 'get'])
556       ->getMockForAbstractClass();
557
558     // Mock field definition objects for each element of $field_definitions.
559     $mocked_field_definitions = [];
560     foreach ($field_definitions as $name) {
561       $mock_definition = $this->getMockBuilder('Drupal\Core\Field\FieldDefinitionInterface')
562         ->setMethods(['isComputed'])
563         ->getMockForAbstractClass();
564       // Set expectations for isComputed(). isComputed() gets called whenever
565       // $include_computed is FALSE, but not otherwise. It returns the value of
566       // $is_computed.
567       $mock_definition->expects($this->exactly(
568         $include_computed ? 0 : 1
569         ))
570         ->method('isComputed')
571         ->willReturn($is_computed);
572       $mocked_field_definitions[$name] = $mock_definition;
573     }
574
575     // Set up expectations for getFieldDefinitions().
576     $mock_base->expects($this->once())
577       ->method('getFieldDefinitions')
578       ->willReturn($mocked_field_definitions);
579
580     // How many time will we call get()? Since we are rigging all defined fields
581     // to be computed based on $is_computed, then if $include_computed is FALSE,
582     // get() will never be called.
583     $get_count = 0;
584     if ($include_computed) {
585       $get_count = count($field_definitions);
586     }
587
588     // Set up expectations for get(). It simply returns the name passed in.
589     $mock_base->expects($this->exactly($get_count))
590       ->method('get')
591       ->willReturnArgument(0);
592
593     // Exercise getFields().
594     $this->assertArrayEquals(
595       $expected,
596       $mock_base->getFields($include_computed)
597     );
598   }
599
600   /**
601    * @covers ::set
602    */
603   public function testSet() {
604     // Exercise set(), check if it returns $this
605     $this->assertSame(
606       $this->entity,
607       $this->entity->set('id', 0)
608     );
609   }
610
611 }