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