3c88db7f9e535b835a84c65689141720b556088f
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Entity / Sql / SqlContentEntityStorageTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTest.
6  */
7
8 namespace Drupal\Tests\Core\Entity\Sql;
9
10 use Drupal\Core\Cache\CacheBackendInterface;
11 use Drupal\Core\Cache\MemoryCache\MemoryCache;
12 use Drupal\Core\Entity\EntityFieldManagerInterface;
13 use Drupal\Core\Entity\EntityInterface;
14 use Drupal\Core\Entity\EntityManager;
15 use Drupal\Core\Entity\EntityStorageInterface;
16 use Drupal\Core\Entity\EntityTypeManagerInterface;
17 use Drupal\Core\Entity\Query\QueryFactoryInterface;
18 use Drupal\Core\Entity\Sql\DefaultTableMapping;
19 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
20 use Drupal\Core\Language\Language;
21 use Drupal\Tests\UnitTestCase;
22 use Symfony\Component\DependencyInjection\ContainerBuilder;
23
24 /**
25  * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage
26  * @group Entity
27  */
28 class SqlContentEntityStorageTest extends UnitTestCase {
29
30   /**
31    * The content entity database storage used in this test.
32    *
33    * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
34    */
35   protected $entityStorage;
36
37   /**
38    * The mocked entity type used in this test.
39    *
40    * @var \Drupal\Core\Entity\ContentEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
41    */
42   protected $entityType;
43
44   /**
45    * An array of field definitions used for this test, keyed by field name.
46    *
47    * @var \Drupal\Core\Field\BaseFieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[]
48    */
49   protected $fieldDefinitions = [];
50
51   /**
52    * The mocked entity manager used in this test.
53    *
54    * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
55    */
56   protected $entityManager;
57
58   /**
59    * The mocked entity type manager used in this test.
60    *
61    * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
62    */
63   protected $entityTypeManager;
64
65   /**
66    * The mocked entity field manager used in this test.
67    *
68    * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject
69    */
70   protected $entityFieldManager;
71
72   /**
73    * The entity type ID.
74    *
75    * @var string
76    */
77   protected $entityTypeId = 'entity_test';
78
79   /**
80    * The dependency injection container.
81    *
82    * @var \Symfony\Component\DependencyInjection\ContainerBuilder
83    */
84   protected $container;
85
86   /**
87    * The module handler.
88    *
89    * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
90    */
91   protected $moduleHandler;
92
93   /**
94    * The cache backend to use.
95    *
96    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
97    */
98   protected $cache;
99
100   /**
101    * The language manager.
102    *
103    * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
104    */
105   protected $languageManager;
106
107   /**
108    * The database connection to use.
109    *
110    * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
111    */
112   protected $connection;
113
114   /**
115    * {@inheritdoc}
116    */
117   protected function setUp() {
118     $this->entityType = $this->getMock('Drupal\Core\Entity\ContentEntityTypeInterface');
119     $this->entityType->expects($this->any())
120       ->method('id')
121       ->will($this->returnValue($this->entityTypeId));
122
123     $this->container = new ContainerBuilder();
124     \Drupal::setContainer($this->container);
125
126     $this->entityManager = new EntityManager();
127     // Inject the container into entity.manager so it can defer to
128     // entity_type.manager and other services.
129     $this->entityManager->setContainer($this->container);
130     $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
131     $this->entityFieldManager = $this->getMock(EntityFieldManagerInterface::class);
132     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
133     $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
134     $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
135     $this->languageManager->expects($this->any())
136       ->method('getDefaultLanguage')
137       ->will($this->returnValue(new Language(['langcode' => 'en'])));
138     $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
139       ->disableOriginalConstructor()
140       ->getMock();
141
142     $this->container->set('entity.manager', $this->entityManager);
143     $this->container->set('entity_type.manager', $this->entityTypeManager);
144     $this->container->set('entity_field.manager', $this->entityFieldManager);
145   }
146
147   /**
148    * Tests SqlContentEntityStorage::getBaseTable().
149    *
150    * @param string $base_table
151    *   The base table to be returned by the mocked entity type.
152    * @param string $expected
153    *   The expected return value of
154    *   SqlContentEntityStorage::getBaseTable().
155    *
156    * @covers ::__construct
157    * @covers ::getBaseTable
158    *
159    * @dataProvider providerTestGetBaseTable
160    */
161   public function testGetBaseTable($base_table, $expected) {
162     $this->entityType->expects($this->once())
163       ->method('getBaseTable')
164       ->willReturn($base_table);
165
166     $this->setUpEntityStorage();
167
168     $this->assertSame($expected, $this->entityStorage->getBaseTable());
169   }
170
171   /**
172    * Provides test data for testGetBaseTable().
173    *
174    * @return array[]
175    *   An nested array where each inner array has the base table to be returned
176    *   by the mocked entity type as the first value and the expected return
177    *   value of SqlContentEntityStorage::getBaseTable() as the second
178    *   value.
179    */
180   public function providerTestGetBaseTable() {
181     return [
182       // Test that the entity type's base table is used, if provided.
183       ['entity_test', 'entity_test'],
184       // Test that the storage falls back to the entity type ID.
185       [NULL, 'entity_test'],
186     ];
187   }
188
189   /**
190    * Tests SqlContentEntityStorage::getRevisionTable().
191    *
192    * @param string $revision_table
193    *   The revision table to be returned by the mocked entity type.
194    * @param string $expected
195    *   The expected return value of
196    *   SqlContentEntityStorage::getRevisionTable().
197    *
198    * @covers ::__construct
199    * @covers ::getRevisionTable
200    *
201    * @dataProvider providerTestGetRevisionTable
202    */
203   public function testGetRevisionTable($revision_table, $expected) {
204     $this->entityType->expects($this->any())
205       ->method('isRevisionable')
206       ->will($this->returnValue(TRUE));
207     $this->entityType->expects($this->once())
208       ->method('getRevisionTable')
209       ->will($this->returnValue($revision_table));
210     $this->entityType->expects($this->any())
211       ->method('getRevisionMetadataKeys')
212       ->willReturn([]);
213
214     $this->setUpEntityStorage();
215
216     $this->assertSame($expected, $this->entityStorage->getRevisionTable());
217   }
218
219   /**
220    * Provides test data for testGetRevisionTable().
221    *
222    * @return array[]
223    *   An nested array where each inner array has the revision table to be
224    *   returned by the mocked entity type as the first value and the expected
225    *   return value of SqlContentEntityStorage::getRevisionTable() as the
226    *   second value.
227    */
228   public function providerTestGetRevisionTable() {
229     return [
230       // Test that the entity type's revision table is used, if provided.
231       ['entity_test_revision', 'entity_test_revision'],
232       // Test that the storage falls back to the entity type ID with a
233       // '_revision' suffix.
234       [NULL, 'entity_test_revision'],
235     ];
236   }
237
238   /**
239    * Tests SqlContentEntityStorage::getDataTable().
240    *
241    * @covers ::__construct
242    * @covers ::getDataTable
243    */
244   public function testGetDataTable() {
245     $this->entityType->expects($this->any())
246       ->method('isTranslatable')
247       ->will($this->returnValue(TRUE));
248     $this->entityType->expects($this->exactly(1))
249       ->method('getDataTable')
250       ->will($this->returnValue('entity_test_field_data'));
251     $this->entityType->expects($this->any())
252       ->method('getRevisionMetadataKeys')
253       ->willReturn([]);
254
255     $this->setUpEntityStorage();
256
257     $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
258   }
259
260   /**
261    * Tests SqlContentEntityStorage::getRevisionDataTable().
262    *
263    * @param string $revision_data_table
264    *   The revision data table to be returned by the mocked entity type.
265    * @param string $expected
266    *   The expected return value of
267    *   SqlContentEntityStorage::getRevisionDataTable().
268    *
269    * @covers ::__construct
270    * @covers ::getRevisionDataTable
271    *
272    * @dataProvider providerTestGetRevisionDataTable
273    */
274   public function testGetRevisionDataTable($revision_data_table, $expected) {
275     $this->entityType->expects($this->any())
276       ->method('isRevisionable')
277       ->will($this->returnValue(TRUE));
278     $this->entityType->expects($this->any())
279       ->method('isTranslatable')
280       ->will($this->returnValue(TRUE));
281     $this->entityType->expects($this->exactly(1))
282       ->method('getDataTable')
283       ->will($this->returnValue('entity_test_field_data'));
284     $this->entityType->expects($this->once())
285       ->method('getRevisionDataTable')
286       ->will($this->returnValue($revision_data_table));
287     $this->entityType->expects($this->any())
288       ->method('getRevisionMetadataKeys')
289       ->willReturn([]);
290
291     $this->setUpEntityStorage();
292
293     $actual = $this->entityStorage->getRevisionDataTable();
294     $this->assertSame($expected, $actual);
295   }
296
297   /**
298    * Provides test data for testGetRevisionDataTable().
299    *
300    * @return array[]
301    *   An nested array where each inner array has the revision data table to be
302    *   returned by the mocked entity type as the first value and the expected
303    *   return value of SqlContentEntityStorage::getRevisionDataTable() as
304    *   the second value.
305    */
306   public function providerTestGetRevisionDataTable() {
307     return [
308       // Test that the entity type's revision data table is used, if provided.
309       ['entity_test_field_revision', 'entity_test_field_revision'],
310       // Test that the storage falls back to the entity type ID with a
311       // '_field_revision' suffix.
312       [NULL, 'entity_test_field_revision'],
313     ];
314   }
315
316   /**
317    * Tests that setting a new table mapping also updates the table names.
318    *
319    * @covers ::setTableMapping
320    */
321   public function testSetTableMapping() {
322     $this->entityType->expects($this->any())
323       ->method('isRevisionable')
324       ->will($this->returnValue(FALSE));
325     $this->entityType->expects($this->any())
326       ->method('isTranslatable')
327       ->will($this->returnValue(FALSE));
328     $this->entityType->expects($this->any())
329       ->method('getRevisionMetadataKeys')
330       ->willReturn([]);
331
332     $this->setUpEntityStorage();
333
334     $this->assertSame('entity_test', $this->entityStorage->getBaseTable());
335     $this->assertNull($this->entityStorage->getRevisionTable());
336     $this->assertNull($this->entityStorage->getDataTable());
337     $this->assertNull($this->entityStorage->getRevisionDataTable());
338
339     // Change the entity type definition and instantiate a new table mapping
340     // with it.
341     $updated_entity_type = $this->createMock('Drupal\Core\Entity\ContentEntityTypeInterface');
342     $updated_entity_type->expects($this->any())
343       ->method('id')
344       ->will($this->returnValue($this->entityTypeId));
345     $updated_entity_type->expects($this->any())
346       ->method('isRevisionable')
347       ->will($this->returnValue(TRUE));
348     $updated_entity_type->expects($this->any())
349       ->method('isTranslatable')
350       ->will($this->returnValue(TRUE));
351
352     $table_mapping = new DefaultTableMapping($updated_entity_type, []);
353     $this->entityStorage->setTableMapping($table_mapping);
354
355     $this->assertSame('entity_test', $this->entityStorage->getBaseTable());
356     $this->assertSame('entity_test_revision', $this->entityStorage->getRevisionTable());
357     $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
358     $this->assertSame('entity_test_field_revision', $this->entityStorage->getRevisionDataTable());
359   }
360
361   /**
362    * Tests ContentEntityDatabaseStorage::onEntityTypeCreate().
363    *
364    * @covers ::__construct
365    * @covers ::onEntityTypeCreate
366    * @covers ::getTableMapping
367    */
368   public function testOnEntityTypeCreate() {
369     $columns = [
370       'value' => [
371         'type' => 'int',
372       ],
373     ];
374
375     $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
376     $this->fieldDefinitions['id']->expects($this->any())
377       ->method('getColumns')
378       ->will($this->returnValue($columns));
379     $this->fieldDefinitions['id']->expects($this->once())
380       ->method('getSchema')
381       ->will($this->returnValue(['columns' => $columns]));
382
383     $this->entityType->expects($this->once())
384       ->method('getKeys')
385       ->will($this->returnValue(['id' => 'id']));
386     $this->entityType->expects($this->any())
387       ->method('hasKey')
388       ->will($this->returnValueMap([
389         // SqlContentEntityStorageSchema::initializeBaseTable()
390         ['revision', FALSE],
391         // SqlContentEntityStorageSchema::processBaseTable()
392         ['id', TRUE],
393       ]));
394     $this->entityType->expects($this->any())
395       ->method('getKey')
396       ->will($this->returnValueMap([
397         // EntityStorageBase::__construct()
398         ['id', 'id'],
399         // ContentEntityStorageBase::__construct()
400         ['uuid', NULL],
401         ['bundle', NULL],
402         // SqlContentEntityStorageSchema::initializeBaseTable()
403         ['id' => 'id'],
404         // SqlContentEntityStorageSchema::processBaseTable()
405         ['id' => 'id'],
406       ]));
407
408     $this->setUpEntityStorage();
409
410     $expected = [
411       'description' => 'The base table for entity_test entities.',
412       'fields' => [
413         'id' => [
414           'type' => 'serial',
415           'not null' => TRUE,
416         ],
417       ],
418       'primary key' => ['id'],
419       'unique keys' => [],
420       'indexes' => [],
421       'foreign keys' => [],
422     ];
423
424     $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
425       ->disableOriginalConstructor()
426       ->getMock();
427     $schema_handler->expects($this->any())
428       ->method('createTable')
429       ->with($this->equalTo('entity_test'), $this->equalTo($expected));
430
431     $this->connection->expects($this->once())
432       ->method('schema')
433       ->will($this->returnValue($schema_handler));
434
435     $storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
436       ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
437       ->setMethods(['getStorageSchema'])
438       ->getMock();
439
440     $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
441     $schema_handler = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
442       ->setConstructorArgs([$this->entityManager, $this->entityType, $storage, $this->connection])
443       ->setMethods(['installedStorageSchema', 'createSharedTableSchema'])
444       ->getMock();
445     $schema_handler
446       ->expects($this->any())
447       ->method('installedStorageSchema')
448       ->will($this->returnValue($key_value));
449
450     $storage
451       ->expects($this->any())
452       ->method('getStorageSchema')
453       ->will($this->returnValue($schema_handler));
454
455     $storage->onEntityTypeCreate($this->entityType);
456   }
457
458   /**
459    * Tests getTableMapping() with an empty entity type.
460    *
461    * @covers ::__construct
462    * @covers ::getTableMapping
463    */
464   public function testGetTableMappingEmpty() {
465     $this->setUpEntityStorage();
466
467     $mapping = $this->entityStorage->getTableMapping();
468     $this->assertSame(['entity_test'], $mapping->getTableNames());
469     $this->assertSame([], $mapping->getFieldNames('entity_test'));
470     $this->assertSame([], $mapping->getExtraColumns('entity_test'));
471   }
472
473   /**
474    * Tests getTableMapping() with a simple entity type.
475    *
476    * @param string[] $entity_keys
477    *   A map of entity keys to use for the mocked entity type.
478    *
479    * @covers ::__construct
480    * @covers ::getTableMapping
481    *
482    * @dataProvider providerTestGetTableMappingSimple()
483    */
484   public function testGetTableMappingSimple(array $entity_keys) {
485     $this->entityType->expects($this->any())
486       ->method('getKey')
487       ->will($this->returnValueMap([
488         ['id', $entity_keys['id']],
489         ['uuid', $entity_keys['uuid']],
490         ['bundle', $entity_keys['bundle']],
491       ]));
492
493     $this->setUpEntityStorage();
494
495     $mapping = $this->entityStorage->getTableMapping();
496
497     $this->assertEquals(['entity_test'], $mapping->getTableNames());
498
499     $expected = array_values(array_filter($entity_keys));
500     $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
501
502     $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
503   }
504
505   /**
506    * Tests getTableMapping() with a simple entity type with some base fields.
507    *
508    * @param string[] $entity_keys
509    *   A map of entity keys to use for the mocked entity type.
510    *
511    * @covers ::__construct
512    * @covers ::getTableMapping
513    *
514    * @dataProvider providerTestGetTableMappingSimple()
515    */
516   public function testGetTableMappingSimpleWithFields(array $entity_keys) {
517     $base_field_names = ['title', 'description', 'owner'];
518     $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
519     $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
520     $this->setUpEntityStorage();
521
522     $mapping = $this->entityStorage->getTableMapping();
523     $this->assertEquals(['entity_test'], $mapping->getTableNames());
524     $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
525     $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
526   }
527
528   /**
529    * Provides test data for testGetTableMappingSimple().
530    *
531    * @return array[]
532    *   A nested array, where each inner array has a single value being a  map of
533    *   entity keys to use for the mocked entity type.
534    */
535   public function providerTestGetTableMappingSimple() {
536     return [
537       [['id' => 'test_id', 'bundle' => NULL, 'uuid' => NULL]],
538       [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => NULL]],
539       [['id' => 'test_id', 'bundle' => NULL, 'uuid' => 'test_uuid']],
540       [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => 'test_uuid']],
541     ];
542   }
543
544   /**
545    * Tests getTableMapping() with a base field that requires a dedicated table.
546    *
547    * @covers ::__construct
548    * @covers ::getTableMapping
549    */
550   public function testGetTableMappingSimpleWithDedicatedStorageFields() {
551     $base_field_names = ['multi_valued_base_field'];
552
553     // Set up one entity key in order to have a base table.
554     $this->fieldDefinitions = $this->mockFieldDefinitions(['test_id']);
555
556     // Set up the multi-valued base field.
557     $this->fieldDefinitions += $this->mockFieldDefinitions($base_field_names, [
558       'hasCustomStorage' => FALSE,
559       'isMultiple' => TRUE,
560       'getTargetEntityTypeId' => 'entity_test',
561     ]);
562
563     $this->setUpEntityStorage();
564
565     $mapping = $this->entityStorage->getTableMapping();
566     $this->assertEquals(['entity_test', 'entity_test__multi_valued_base_field'], $mapping->getTableNames());
567     $this->assertEquals($base_field_names, $mapping->getFieldNames('entity_test__multi_valued_base_field'));
568
569     $extra_columns = [
570       'bundle',
571       'deleted',
572       'entity_id',
573       'revision_id',
574       'langcode',
575       'delta',
576     ];
577     $this->assertEquals($extra_columns, $mapping->getExtraColumns('entity_test__multi_valued_base_field'));
578   }
579
580   /**
581    * Tests getTableMapping() with a revisionable, non-translatable entity type.
582    *
583    * @param string[] $entity_keys
584    *   A map of entity keys to use for the mocked entity type.
585    *
586    * @covers ::__construct
587    * @covers ::getTableMapping
588    *
589    * @dataProvider providerTestGetTableMappingSimple()
590    */
591   public function testGetTableMappingRevisionable(array $entity_keys) {
592     // This allows to re-use the data provider.
593     $entity_keys = [
594       'id' => $entity_keys['id'],
595       'revision' => 'test_revision',
596       'bundle' => $entity_keys['bundle'],
597       'uuid' => $entity_keys['uuid'],
598     ];
599
600     $this->entityType->expects($this->exactly(4))
601       ->method('isRevisionable')
602       ->will($this->returnValue(TRUE));
603     $this->entityType->expects($this->any())
604       ->method('getKey')
605       ->will($this->returnValueMap([
606         ['id', $entity_keys['id']],
607         ['uuid', $entity_keys['uuid']],
608         ['bundle', $entity_keys['bundle']],
609         ['revision', $entity_keys['revision']],
610       ]));
611     $this->entityType->expects($this->any())
612       ->method('getRevisionMetadataKeys')
613       ->will($this->returnValue([]));
614
615     $this->setUpEntityStorage();
616
617     $mapping = $this->entityStorage->getTableMapping();
618
619     $expected = ['entity_test', 'entity_test_revision'];
620     $this->assertEquals($expected, $mapping->getTableNames());
621
622     $expected = array_values(array_filter($entity_keys));
623     $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
624     $expected = [$entity_keys['id'], $entity_keys['revision']];
625     $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
626
627     $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
628     $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
629   }
630
631   /**
632    * Tests getTableMapping() with a revisionable entity type with fields.
633    *
634    * @param string[] $entity_keys
635    *   A map of entity keys to use for the mocked entity type.
636    *
637    * @covers ::__construct
638    * @covers ::getTableMapping
639    *
640    * @dataProvider providerTestGetTableMappingSimple()
641    */
642   public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
643     // This allows to re-use the data provider.
644     $entity_keys = [
645       'id' => $entity_keys['id'],
646       'revision' => 'test_revision',
647       'bundle' => $entity_keys['bundle'],
648       'uuid' => $entity_keys['uuid'],
649     ];
650
651     // PHPUnit does not allow for multiple data providers.
652     $test_cases = [
653       [],
654       ['revision_created' => 'revision_timestamp'],
655       ['revision_user' => 'revision_uid'],
656       ['revision_log_message' => 'revision_log'],
657       ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
658       ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
659       ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
660       ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
661     ];
662     foreach ($test_cases as $revision_metadata_field_names) {
663       $this->setUp();
664
665       $base_field_names = ['title'];
666       $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
667       $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
668
669       $revisionable_field_names = ['description', 'owner'];
670       $field_names = array_merge($field_names, $revisionable_field_names);
671       $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
672
673       $this->entityType->expects($this->exactly(4))
674         ->method('isRevisionable')
675         ->will($this->returnValue(TRUE));
676       $this->entityType->expects($this->any())
677         ->method('getKey')
678         ->will($this->returnValueMap([
679           ['id', $entity_keys['id']],
680           ['uuid', $entity_keys['uuid']],
681           ['bundle', $entity_keys['bundle']],
682           ['revision', $entity_keys['revision']],
683         ]));
684
685       $this->entityType->expects($this->any())
686         ->method('getRevisionMetadataKeys')
687         ->will($this->returnValue($revision_metadata_field_names));
688
689       $this->setUpEntityStorage();
690
691       $mapping = $this->entityStorage->getTableMapping();
692
693       $expected = ['entity_test', 'entity_test_revision'];
694       $this->assertEquals($expected, $mapping->getTableNames());
695
696       $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
697       $expected = array_merge(
698         [$entity_keys['id'], $entity_keys['revision']],
699         $revisionable_field_names,
700         array_values($revision_metadata_field_names)
701       );
702       $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
703
704       $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
705       $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
706     }
707   }
708
709   /**
710    * Tests getTableMapping() with a non-revisionable, translatable entity type.
711    *
712    * @param string[] $entity_keys
713    *   A map of entity keys to use for the mocked entity type.
714    *
715    * @covers ::__construct
716    * @covers ::getTableMapping
717    *
718    * @dataProvider providerTestGetTableMappingSimple()
719    */
720   public function testGetTableMappingTranslatable(array $entity_keys) {
721     // This allows to re-use the data provider.
722     $entity_keys['langcode'] = 'langcode';
723
724     $this->entityType->expects($this->atLeastOnce())
725       ->method('isTranslatable')
726       ->will($this->returnValue(TRUE));
727     $this->entityType->expects($this->atLeastOnce())
728       ->method('getDataTable')
729       ->will($this->returnValue('entity_test_field_data'));
730     $this->entityType->expects($this->any())
731       ->method('getKey')
732       ->will($this->returnValueMap([
733         ['id', $entity_keys['id']],
734         ['uuid', $entity_keys['uuid']],
735         ['bundle', $entity_keys['bundle']],
736         ['langcode', $entity_keys['langcode']],
737       ]));
738
739     $this->setUpEntityStorage();
740
741     $mapping = $this->entityStorage->getTableMapping();
742
743     $expected = ['entity_test', 'entity_test_field_data'];
744     $this->assertEquals($expected, $mapping->getTableNames());
745
746     $expected = array_values(array_filter($entity_keys));
747     $actual = $mapping->getFieldNames('entity_test');
748     $this->assertEquals($expected, $actual);
749     // The UUID is not stored on the data table.
750     $expected = array_values(array_filter([
751       $entity_keys['id'],
752       $entity_keys['bundle'],
753       $entity_keys['langcode'],
754     ]));
755     $actual = $mapping->getFieldNames('entity_test_field_data');
756     $this->assertEquals($expected, $actual);
757
758     $expected = [];
759     $actual = $mapping->getExtraColumns('entity_test');
760     $this->assertEquals($expected, $actual);
761     $actual = $mapping->getExtraColumns('entity_test_field_data');
762     $this->assertEquals($expected, $actual);
763   }
764
765   /**
766    * Tests getTableMapping() with a translatable entity type with fields.
767    *
768    * @param string[] $entity_keys
769    *   A map of entity keys to use for the mocked entity type.
770    *
771    * @covers ::__construct
772    * @covers ::getTableMapping
773    *
774    * @dataProvider providerTestGetTableMappingSimple()
775    */
776   public function testGetTableMappingTranslatableWithFields(array $entity_keys) {
777     // This allows to re-use the data provider.
778     $entity_keys['langcode'] = 'langcode';
779
780     $base_field_names = ['title', 'description', 'owner'];
781     $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
782     $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
783
784     $this->entityType->expects($this->atLeastOnce())
785       ->method('isTranslatable')
786       ->will($this->returnValue(TRUE));
787     $this->entityType->expects($this->atLeastOnce())
788       ->method('getDataTable')
789       ->will($this->returnValue('entity_test_field_data'));
790     $this->entityType->expects($this->any())
791       ->method('getKey')
792       ->will($this->returnValueMap([
793         ['id', $entity_keys['id']],
794         ['uuid', $entity_keys['uuid']],
795         ['bundle', $entity_keys['bundle']],
796         ['langcode', $entity_keys['langcode']],
797       ]));
798
799     $this->setUpEntityStorage();
800
801     $mapping = $this->entityStorage->getTableMapping();
802
803     $expected = ['entity_test', 'entity_test_field_data'];
804     $this->assertEquals($expected, $mapping->getTableNames());
805
806     $expected = array_values(array_filter($entity_keys));
807     $actual = $mapping->getFieldNames('entity_test');
808     $this->assertEquals($expected, $actual);
809     // The UUID is not stored on the data table.
810     $expected = array_merge(array_filter([
811       $entity_keys['id'],
812       $entity_keys['bundle'],
813       $entity_keys['langcode'],
814     ]), $base_field_names);
815     $actual = $mapping->getFieldNames('entity_test_field_data');
816     $this->assertEquals($expected, $actual);
817
818     $expected = [];
819     $actual = $mapping->getExtraColumns('entity_test');
820     $this->assertEquals($expected, $actual);
821     $actual = $mapping->getExtraColumns('entity_test_field_data');
822     $this->assertEquals($expected, $actual);
823   }
824
825   /**
826    * Tests getTableMapping() with a revisionable, translatable entity type.
827    *
828    * @param string[] $entity_keys
829    *   A map of entity keys to use for the mocked entity type.
830    *
831    * @covers ::__construct
832    * @covers ::getTableMapping
833    *
834    * @dataProvider providerTestGetTableMappingSimple()
835    */
836   public function testGetTableMappingRevisionableTranslatable(array $entity_keys) {
837     // This allows to re-use the data provider.
838     $entity_keys = [
839       'id' => $entity_keys['id'],
840       'revision' => 'test_revision',
841       'bundle' => $entity_keys['bundle'],
842       'uuid' => $entity_keys['uuid'],
843       'langcode' => 'langcode',
844     ];
845     $revision_metadata_keys = [
846       'revision_created' => 'revision_timestamp',
847       'revision_user' => 'revision_uid',
848       'revision_log_message' => 'revision_log',
849     ];
850
851     $this->entityType->expects($this->atLeastOnce())
852       ->method('isRevisionable')
853       ->will($this->returnValue(TRUE));
854     $this->entityType->expects($this->atLeastOnce())
855       ->method('isTranslatable')
856       ->will($this->returnValue(TRUE));
857     $this->entityType->expects($this->atLeastOnce())
858       ->method('getDataTable')
859       ->will($this->returnValue('entity_test_field_data'));
860     $this->entityType->expects($this->any())
861       ->method('getKey')
862       ->will($this->returnValueMap([
863         ['id', $entity_keys['id']],
864         ['uuid', $entity_keys['uuid']],
865         ['bundle', $entity_keys['bundle']],
866         ['revision', $entity_keys['revision']],
867         ['langcode', $entity_keys['langcode']],
868       ]));
869     $this->entityType->expects($this->any())
870       ->method('getRevisionMetadataKeys')
871       ->will($this->returnValue($revision_metadata_keys));
872
873     $this->setUpEntityStorage();
874
875     $mapping = $this->entityStorage->getTableMapping();
876
877     $expected = [
878       'entity_test',
879       'entity_test_field_data',
880       'entity_test_revision',
881       'entity_test_field_revision',
882     ];
883     $this->assertEquals($expected, $mapping->getTableNames());
884
885     // The default language code is stored on the base table.
886     $expected = array_values(array_filter([
887       $entity_keys['id'],
888       $entity_keys['revision'],
889       $entity_keys['bundle'],
890       $entity_keys['uuid'],
891       $entity_keys['langcode'],
892     ]));
893     $actual = $mapping->getFieldNames('entity_test');
894     $this->assertEquals($expected, $actual);
895     // The revision table on the other hand does not store the bundle and the
896     // UUID.
897     $expected = array_values(array_filter([
898       $entity_keys['id'],
899       $entity_keys['revision'],
900       $entity_keys['langcode'],
901     ]));
902     $expected = array_merge($expected, array_values($revision_metadata_keys));
903     $actual = $mapping->getFieldNames('entity_test_revision');
904     $this->assertEquals($expected, $actual);
905     // The UUID is not stored on the data table.
906     $expected = array_values(array_filter([
907       $entity_keys['id'],
908       $entity_keys['revision'],
909       $entity_keys['bundle'],
910       $entity_keys['langcode'],
911     ]));
912     $actual = $mapping->getFieldNames('entity_test_field_data');
913     $this->assertEquals($expected, $actual);
914     // The data revision also does not store the bundle.
915     $expected = array_values(array_filter([
916       $entity_keys['id'],
917       $entity_keys['revision'],
918       $entity_keys['langcode'],
919     ]));
920     $actual = $mapping->getFieldNames('entity_test_field_revision');
921     $this->assertEquals($expected, $actual);
922
923     $expected = [];
924     $actual = $mapping->getExtraColumns('entity_test');
925     $this->assertEquals($expected, $actual);
926     $actual = $mapping->getExtraColumns('entity_test_revision');
927     $this->assertEquals($expected, $actual);
928     $actual = $mapping->getExtraColumns('entity_test_field_data');
929     $this->assertEquals($expected, $actual);
930     $actual = $mapping->getExtraColumns('entity_test_field_revision');
931     $this->assertEquals($expected, $actual);
932   }
933
934   /**
935    * Tests getTableMapping() with a complex entity type with fields.
936    *
937    * @param string[] $entity_keys
938    *   A map of entity keys to use for the mocked entity type.
939    *
940    * @covers ::__construct
941    * @covers ::getTableMapping
942    *
943    * @dataProvider providerTestGetTableMappingSimple()
944    */
945   public function testGetTableMappingRevisionableTranslatableWithFields(array $entity_keys) {
946     // This allows to re-use the data provider.
947     $entity_keys = [
948       'id' => $entity_keys['id'],
949       'revision' => 'test_revision',
950       'bundle' => $entity_keys['bundle'],
951       'uuid' => $entity_keys['uuid'],
952       'langcode' => 'langcode',
953     ];
954
955     // PHPUnit does not allow for multiple data providers.
956     $test_cases = [
957       [],
958       ['revision_created' => 'revision_timestamp'],
959       ['revision_user' => 'revision_uid'],
960       ['revision_log_message' => 'revision_log'],
961       ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
962       ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
963       ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
964       ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
965     ];
966     foreach ($test_cases as $revision_metadata_field_names) {
967       $this->setUp();
968
969       $base_field_names = ['title'];
970       $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
971       $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
972
973       $revisionable_field_names = ['description', 'owner'];
974       $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
975
976       $this->entityType->expects($this->atLeastOnce())
977         ->method('isRevisionable')
978         ->will($this->returnValue(TRUE));
979       $this->entityType->expects($this->atLeastOnce())
980         ->method('isTranslatable')
981         ->will($this->returnValue(TRUE));
982       $this->entityType->expects($this->atLeastOnce())
983         ->method('getDataTable')
984         ->will($this->returnValue('entity_test_field_data'));
985       $this->entityType->expects($this->any())
986         ->method('getKey')
987         ->will($this->returnValueMap([
988           ['id', $entity_keys['id']],
989           ['uuid', $entity_keys['uuid']],
990           ['bundle', $entity_keys['bundle']],
991           ['revision', $entity_keys['revision']],
992           ['langcode', $entity_keys['langcode']],
993         ]));
994       $this->entityType->expects($this->any())
995         ->method('getRevisionMetadataKeys')
996         ->will($this->returnValue($revision_metadata_field_names));
997
998       $this->setUpEntityStorage();
999
1000       $mapping = $this->entityStorage->getTableMapping();
1001
1002       $expected = [
1003         'entity_test',
1004         'entity_test_field_data',
1005         'entity_test_revision',
1006         'entity_test_field_revision',
1007       ];
1008       $this->assertEquals($expected, $mapping->getTableNames());
1009
1010       $expected = [
1011         'entity_test',
1012         'entity_test_field_data',
1013         'entity_test_revision',
1014         'entity_test_field_revision',
1015       ];
1016       $this->assertEquals($expected, $mapping->getTableNames());
1017
1018       // The default language code is not stored on the base table.
1019       $expected = array_values(array_filter([
1020         $entity_keys['id'],
1021         $entity_keys['revision'],
1022         $entity_keys['bundle'],
1023         $entity_keys['uuid'],
1024         $entity_keys['langcode'],
1025       ]));
1026       $actual = $mapping->getFieldNames('entity_test');
1027       $this->assertEquals($expected, $actual);
1028       // The revision table on the other hand does not store the bundle and the
1029       // UUID.
1030       $expected = array_merge(array_filter([
1031         $entity_keys['id'],
1032         $entity_keys['revision'],
1033         $entity_keys['langcode'],
1034       ]), array_values($revision_metadata_field_names));
1035       $actual = $mapping->getFieldNames('entity_test_revision');
1036       $this->assertEquals($expected, $actual);
1037       // The UUID is not stored on the data table.
1038       $expected = array_merge(array_filter([
1039         $entity_keys['id'],
1040         $entity_keys['revision'],
1041         $entity_keys['bundle'],
1042         $entity_keys['langcode'],
1043       ]), $base_field_names, $revisionable_field_names);
1044       $actual = $mapping->getFieldNames('entity_test_field_data');
1045       $this->assertEquals($expected, $actual);
1046       // The data revision also does not store the bundle.
1047       $expected = array_merge(array_filter([
1048         $entity_keys['id'],
1049         $entity_keys['revision'],
1050         $entity_keys['langcode'],
1051       ]), $revisionable_field_names);
1052       $actual = $mapping->getFieldNames('entity_test_field_revision');
1053       $this->assertEquals($expected, $actual);
1054
1055       $expected = [];
1056       $actual = $mapping->getExtraColumns('entity_test');
1057       $this->assertEquals($expected, $actual);
1058       $actual = $mapping->getExtraColumns('entity_test_revision');
1059       $this->assertEquals($expected, $actual);
1060       $actual = $mapping->getExtraColumns('entity_test_field_data');
1061       $this->assertEquals($expected, $actual);
1062       $actual = $mapping->getExtraColumns('entity_test_field_revision');
1063       $this->assertEquals($expected, $actual);
1064     }
1065   }
1066
1067   /**
1068    * @covers ::create
1069    */
1070   public function testCreate() {
1071     $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
1072
1073     $language = new Language(['id' => 'en']);
1074     $language_manager->expects($this->any())
1075       ->method('getCurrentLanguage')
1076       ->will($this->returnValue($language));
1077
1078     $this->container->set('language_manager', $language_manager);
1079     $this->container->set('module_handler', $this->moduleHandler);
1080
1081     $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
1082       ->disableOriginalConstructor()
1083       ->setMethods(['id'])
1084       ->getMockForAbstractClass();
1085
1086     $this->entityType->expects($this->atLeastOnce())
1087       ->method('id')
1088       ->will($this->returnValue($this->entityTypeId));
1089     $this->entityType->expects($this->atLeastOnce())
1090       ->method('getClass')
1091       ->will($this->returnValue(get_class($entity)));
1092     $this->entityType->expects($this->atLeastOnce())
1093       ->method('getKeys')
1094       ->will($this->returnValue(['id' => 'id']));
1095
1096     // ContentEntityStorageBase iterates over the entity which calls this method
1097     // internally in ContentEntityBase::getProperties().
1098     $this->entityFieldManager->expects($this->once())
1099       ->method('getFieldDefinitions')
1100       ->will($this->returnValue([]));
1101
1102     $this->entityType->expects($this->atLeastOnce())
1103       ->method('isRevisionable')
1104       ->will($this->returnValue(FALSE));
1105     $this->entityTypeManager->expects($this->atLeastOnce())
1106       ->method('getDefinition')
1107       ->with($this->entityType->id())
1108       ->will($this->returnValue($this->entityType));
1109
1110     $this->setUpEntityStorage();
1111
1112     $entity = $this->entityStorage->create();
1113     $entity->expects($this->atLeastOnce())
1114       ->method('id')
1115       ->will($this->returnValue('foo'));
1116
1117     $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
1118     $this->assertSame('foo', $entity->id());
1119     $this->assertTrue($entity->isNew());
1120   }
1121
1122   /**
1123    * Returns a set of mock field definitions for the given names.
1124    *
1125    * @param array $field_names
1126    *   An array of field names.
1127    * @param array $methods
1128    *   (optional) An associative array of mock method return values keyed by
1129    *   method name.
1130    *
1131    * @return \Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
1132    *   An array of mock base field definitions.
1133    */
1134   protected function mockFieldDefinitions(array $field_names, $methods = []) {
1135     $field_definitions = [];
1136     $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1137
1138     // Assign common method return values.
1139     $methods += [
1140       'isBaseField' => TRUE,
1141     ];
1142     foreach ($methods as $method => $result) {
1143       $definition
1144         ->expects($this->any())
1145         ->method($method)
1146         ->will($this->returnValue($result));
1147     }
1148
1149     // Assign field names to mock definitions.
1150     foreach ($field_names as $field_name) {
1151       $field_definitions[$field_name] = clone $definition;
1152       $field_definitions[$field_name]
1153         ->expects($this->any())
1154         ->method('getName')
1155         ->will($this->returnValue($field_name));
1156     }
1157
1158     return $field_definitions;
1159   }
1160
1161   /**
1162    * Sets up the content entity database storage.
1163    */
1164   protected function setUpEntityStorage() {
1165     $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1166       ->disableOriginalConstructor()
1167       ->getMock();
1168
1169     $this->entityTypeManager->expects($this->any())
1170       ->method('getDefinition')
1171       ->will($this->returnValue($this->entityType));
1172
1173     $this->entityFieldManager->expects($this->any())
1174       ->method('getFieldStorageDefinitions')
1175       ->will($this->returnValue($this->fieldDefinitions));
1176
1177     $this->entityFieldManager->expects($this->any())
1178       ->method('getBaseFieldDefinitions')
1179       ->will($this->returnValue($this->fieldDefinitions));
1180
1181     $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache());
1182   }
1183
1184   /**
1185    * @covers ::doLoadMultiple
1186    * @covers ::buildCacheId
1187    * @covers ::getFromPersistentCache
1188    */
1189   public function testLoadMultiplePersistentCached() {
1190     $this->setUpModuleHandlerNoImplementations();
1191
1192     $key = 'values:' . $this->entityTypeId . ':1';
1193     $id = 1;
1194     $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1195       ->getMockForAbstractClass();
1196     $entity->expects($this->any())
1197       ->method('id')
1198       ->will($this->returnValue($id));
1199
1200     $this->entityType->expects($this->atLeastOnce())
1201       ->method('isPersistentlyCacheable')
1202       ->will($this->returnValue(TRUE));
1203     $this->entityType->expects($this->atLeastOnce())
1204       ->method('id')
1205       ->will($this->returnValue($this->entityTypeId));
1206     $this->entityType->expects($this->atLeastOnce())
1207       ->method('getClass')
1208       ->will($this->returnValue(get_class($entity)));
1209
1210     $this->cache->expects($this->once())
1211       ->method('getMultiple')
1212       ->with([$key])
1213       ->will($this->returnValue([$key => (object) ['data' => $entity]]));
1214     $this->cache->expects($this->never())
1215       ->method('set');
1216
1217     $this->setUpEntityStorage();
1218     $entities = $this->entityStorage->loadMultiple([$id]);
1219     $this->assertEquals($entity, $entities[$id]);
1220   }
1221
1222   /**
1223    * @covers ::doLoadMultiple
1224    * @covers ::buildCacheId
1225    * @covers ::getFromPersistentCache
1226    * @covers ::setPersistentCache
1227    */
1228   public function testLoadMultipleNoPersistentCache() {
1229     $this->setUpModuleHandlerNoImplementations();
1230
1231     $id = 1;
1232     $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1233       ->getMockForAbstractClass();
1234     $entity->expects($this->any())
1235       ->method('id')
1236       ->will($this->returnValue($id));
1237
1238     $this->entityType->expects($this->any())
1239       ->method('isPersistentlyCacheable')
1240       ->will($this->returnValue(FALSE));
1241     $this->entityType->expects($this->atLeastOnce())
1242       ->method('id')
1243       ->will($this->returnValue($this->entityTypeId));
1244     $this->entityType->expects($this->atLeastOnce())
1245       ->method('getClass')
1246       ->will($this->returnValue(get_class($entity)));
1247
1248     // There should be no calls to the cache backend for an entity type without
1249     // persistent caching.
1250     $this->cache->expects($this->never())
1251       ->method('getMultiple');
1252     $this->cache->expects($this->never())
1253       ->method('set');
1254
1255     $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1256       ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
1257       ->setMethods(['getFromStorage', 'invokeStorageLoadHook', 'initTableLayout'])
1258       ->getMock();
1259     $entity_storage->method('invokeStorageLoadHook')
1260       ->willReturn(NULL);
1261     $entity_storage->method('initTableLayout')
1262       ->willReturn(NULL);
1263     $entity_storage->expects($this->once())
1264       ->method('getFromStorage')
1265       ->with([$id])
1266       ->will($this->returnValue([$id => $entity]));
1267
1268     $entities = $entity_storage->loadMultiple([$id]);
1269     $this->assertEquals($entity, $entities[$id]);
1270   }
1271
1272   /**
1273    * @covers ::doLoadMultiple
1274    * @covers ::buildCacheId
1275    * @covers ::getFromPersistentCache
1276    * @covers ::setPersistentCache
1277    */
1278   public function testLoadMultiplePersistentCacheMiss() {
1279     $this->setUpModuleHandlerNoImplementations();
1280
1281     $id = 1;
1282     $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1283       ->getMockForAbstractClass();
1284     $entity->expects($this->any())
1285       ->method('id')
1286       ->will($this->returnValue($id));
1287
1288     $this->entityType->expects($this->any())
1289       ->method('isPersistentlyCacheable')
1290       ->will($this->returnValue(TRUE));
1291     $this->entityType->expects($this->atLeastOnce())
1292       ->method('id')
1293       ->will($this->returnValue($this->entityTypeId));
1294     $this->entityType->expects($this->atLeastOnce())
1295       ->method('getClass')
1296       ->will($this->returnValue(get_class($entity)));
1297
1298     // In case of a cache miss, the entity is loaded from the storage and then
1299     // set in the cache.
1300     $key = 'values:' . $this->entityTypeId . ':1';
1301     $this->cache->expects($this->once())
1302       ->method('getMultiple')
1303       ->with([$key])
1304       ->will($this->returnValue([]));
1305     $this->cache->expects($this->once())
1306       ->method('set')
1307       ->with($key, $entity, CacheBackendInterface::CACHE_PERMANENT, [$this->entityTypeId . '_values', 'entity_field_info']);
1308
1309     $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1310       ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
1311       ->setMethods(['getFromStorage', 'invokeStorageLoadHook', 'initTableLayout'])
1312       ->getMock();
1313     $entity_storage->method('invokeStorageLoadHook')
1314       ->willReturn(NULL);
1315     $entity_storage->method('initTableLayout')
1316       ->willReturn(NULL);
1317     $entity_storage->expects($this->once())
1318       ->method('getFromStorage')
1319       ->with([$id])
1320       ->will($this->returnValue([$id => $entity]));
1321
1322     $entities = $entity_storage->loadMultiple([$id]);
1323     $this->assertEquals($entity, $entities[$id]);
1324   }
1325
1326   /**
1327    * @covers ::hasData
1328    */
1329   public function testHasData() {
1330     $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
1331     $query->expects(($this->once()))
1332       ->method('accessCheck')
1333       ->with(FALSE)
1334       ->willReturn($query);
1335     $query->expects(($this->once()))
1336       ->method('range')
1337       ->with(0, 1)
1338       ->willReturn($query);
1339     $query->expects(($this->once()))
1340       ->method('execute')
1341       ->willReturn([5]);
1342
1343     $factory = $this->getMock(QueryFactoryInterface::class);
1344     $factory->expects($this->once())
1345       ->method('get')
1346       ->with($this->entityType, 'AND')
1347       ->willReturn($query);
1348
1349     $this->container->set('entity.query.sql', $factory);
1350
1351     $database = $this->getMockBuilder('Drupal\Core\Database\Connection')
1352       ->disableOriginalConstructor()
1353       ->getMock();
1354
1355     $this->entityTypeManager->expects($this->any())
1356       ->method('getDefinition')
1357       ->will($this->returnValue($this->entityType));
1358
1359     $this->entityFieldManager->expects($this->any())
1360       ->method('getFieldStorageDefinitions')
1361       ->will($this->returnValue($this->fieldDefinitions));
1362
1363     $this->entityFieldManager->expects($this->any())
1364       ->method('getBaseFieldDefinitions')
1365       ->will($this->returnValue($this->fieldDefinitions));
1366
1367     $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache());
1368
1369     $result = $this->entityStorage->hasData();
1370
1371     $this->assertTrue($result, 'hasData returned TRUE');
1372   }
1373
1374   /**
1375    * Tests entity ID sanitization.
1376    */
1377   public function testCleanIds() {
1378     $valid_ids = [
1379       -1,
1380       0,
1381       1,
1382       '-1',
1383       '0',
1384       '1',
1385       0123,
1386       -0x1A,
1387       0x1AFC,
1388       -0b111,
1389       0b101,
1390       '0123',
1391       '00123',
1392       '000123',
1393       '-0123',
1394       '-00123',
1395       '-000123',
1396       -10.0,
1397       -1.0,
1398       0.0,
1399       1.0,
1400       10.0,
1401       -10.00,
1402       -1.00,
1403       0.00,
1404       1.00,
1405       10.00,
1406     ];
1407
1408     $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
1409     $this->fieldDefinitions['id']->expects($this->any())
1410       ->method('getType')
1411       ->will($this->returnValue('integer'));
1412
1413     $this->setUpEntityStorage();
1414
1415     $this->entityType->expects($this->any())
1416       ->method('getKey')
1417       ->will($this->returnValueMap(
1418         [['id', 'id']]
1419       ));
1420
1421     $method = new \ReflectionMethod($this->entityStorage, 'cleanIds');
1422     $method->setAccessible(TRUE);
1423     $this->assertEquals($valid_ids, $method->invoke($this->entityStorage, $valid_ids));
1424
1425     $invalid_ids = [
1426       '--1',
1427       '-0x1A',
1428       '0x1AFC',
1429       '-0b111',
1430       '0b101',
1431       'a',
1432       FALSE,
1433       TRUE,
1434       NULL,
1435       '32acb',
1436       123.123,
1437       123.678,
1438     ];
1439     $this->assertEquals([], $method->invoke($this->entityStorage, $invalid_ids));
1440
1441   }
1442
1443   /**
1444    * Sets up the module handler with no implementations.
1445    */
1446   protected function setUpModuleHandlerNoImplementations() {
1447     $this->moduleHandler->expects($this->any())
1448       ->method('getImplementations')
1449       ->will($this->returnValueMap([
1450         ['entity_load', []],
1451         [$this->entityTypeId . '_load', []],
1452       ]));
1453
1454     $this->container->set('module_handler', $this->moduleHandler);
1455   }
1456
1457 }
1458
1459 /**
1460  * Provides an entity with dummy implementations of static methods, because
1461  * those cannot be mocked.
1462  */
1463 abstract class SqlContentEntityStorageTestEntityInterface implements EntityInterface {
1464
1465   /**
1466    * {@inheritdoc}
1467    */
1468   public static function postLoad(EntityStorageInterface $storage, array &$entities) {
1469   }
1470
1471 }