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