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