cf18e2bb9c5ef5afc82f230d6bd4b445b44ca239
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Entity / Sql / SqlContentEntityStorageSchemaTest.php
1 <?php
2
3 namespace Drupal\Tests\Core\Entity\Sql;
4
5 use Drupal\Core\Entity\ContentEntityType;
6 use Drupal\Core\Entity\ContentEntityTypeInterface;
7 use Drupal\Core\Entity\Sql\DefaultTableMapping;
8 use Drupal\Tests\UnitTestCase;
9
10 /**
11  * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
12  * @group Entity
13  */
14 class SqlContentEntityStorageSchemaTest extends UnitTestCase {
15
16   /**
17    * The mocked DB schema handler.
18    *
19    * @var \Drupal\Core\Database\Schema|\PHPUnit_Framework_MockObject_MockObject
20    */
21   protected $dbSchemaHandler;
22
23   /**
24    * The mocked entity manager used in this test.
25    *
26    * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
27    */
28   protected $entityManager;
29
30   /**
31    * The mocked entity type used in this test.
32    *
33    * @var \Drupal\Core\Entity\ContentEntityTypeInterface
34    */
35   protected $entityType;
36
37   /**
38    * The mocked SQL storage used in this test.
39    *
40    * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
41    */
42   protected $storage;
43
44   /**
45    * The mocked field definitions used in this test.
46    *
47    * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
48    */
49   protected $storageDefinitions;
50
51   /**
52    * The storage schema handler used in this test.
53    *
54    * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema|\PHPUnit_Framework_MockObject_MockObject
55    */
56   protected $storageSchema;
57
58   /**
59    * {@inheritdoc}
60    */
61   protected function setUp() {
62     $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
63     $this->storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
64       ->disableOriginalConstructor()
65       ->getMock();
66
67     $this->storage->expects($this->any())
68       ->method('getBaseTable')
69       ->will($this->returnValue('entity_test'));
70
71     // Add an ID field. This also acts as a test for a simple, single-column
72     // field.
73     $this->setUpStorageDefinition('id', [
74       'columns' => [
75         'value' => [
76           'type' => 'int',
77         ],
78       ],
79     ]);
80   }
81
82   /**
83    * Tests the schema for non-revisionable, non-translatable entities.
84    *
85    * @covers ::__construct
86    * @covers ::getEntitySchemaTables
87    * @covers ::initializeBaseTable
88    * @covers ::addTableDefaults
89    * @covers ::getEntityIndexName
90    * @covers ::getFieldIndexes
91    * @covers ::getFieldUniqueKeys
92    * @covers ::getFieldForeignKeys
93    * @covers ::getFieldSchemaData
94    * @covers ::processBaseTable
95    * @covers ::processIdentifierSchema
96    */
97   public function testGetSchemaBase() {
98     $this->entityType = new ContentEntityType([
99       'id' => 'entity_test',
100       'entity_keys' => ['id' => 'id'],
101     ]);
102
103     // Add a field with a 'length' constraint.
104     $this->setUpStorageDefinition('name', [
105       'columns' => [
106         'value' => [
107           'type' => 'varchar',
108           'length' => 255,
109         ],
110       ],
111     ]);
112     // Add a multi-column field.
113     $this->setUpStorageDefinition('description', [
114       'columns' => [
115         'value' => [
116           'type' => 'text',
117         ],
118         'format' => [
119           'type' => 'varchar',
120         ],
121       ],
122     ]);
123     // Add a field with a unique key.
124     $this->setUpStorageDefinition('uuid', [
125       'columns' => [
126         'value' => [
127           'type' => 'varchar',
128           'length' => 128,
129         ],
130       ],
131       'unique keys' => [
132         'value' => ['value'],
133       ],
134     ]);
135     // Add a field with a unique key, specified as column name and length.
136     $this->setUpStorageDefinition('hash', [
137       'columns' => [
138         'value' => [
139           'type' => 'varchar',
140           'length' => 20,
141         ],
142       ],
143       'unique keys' => [
144         'value' => [['value', 10]],
145       ],
146     ]);
147     // Add a field with a multi-column unique key.
148     $this->setUpStorageDefinition('email', [
149       'columns' => [
150         'username' => [
151           'type' => 'varchar',
152         ],
153         'hostname' => [
154           'type' => 'varchar',
155         ],
156         'domain' => [
157           'type' => 'varchar',
158         ],
159       ],
160       'unique keys' => [
161         'email' => ['username', 'hostname', ['domain', 3]],
162       ],
163     ]);
164     // Add a field with an index.
165     $this->setUpStorageDefinition('owner', [
166       'columns' => [
167         'target_id' => [
168           'type' => 'int',
169         ],
170       ],
171       'indexes' => [
172         'target_id' => ['target_id'],
173       ],
174     ]);
175     // Add a field with an index, specified as column name and length.
176     $this->setUpStorageDefinition('translator', [
177       'columns' => [
178         'target_id' => [
179           'type' => 'int',
180         ],
181       ],
182       'indexes' => [
183         'target_id' => [['target_id', 10]],
184       ],
185     ]);
186     // Add a field with a multi-column index.
187     $this->setUpStorageDefinition('location', [
188       'columns' => [
189         'country' => [
190           'type' => 'varchar',
191         ],
192         'state' => [
193           'type' => 'varchar',
194         ],
195         'city' => [
196           'type' => 'varchar',
197         ],
198       ],
199       'indexes' => [
200         'country_state_city' => ['country', 'state', ['city', 10]],
201       ],
202     ]);
203     // Add a field with a foreign key.
204     $this->setUpStorageDefinition('editor', [
205       'columns' => [
206         'target_id' => [
207           'type' => 'int',
208         ],
209       ],
210       'foreign keys' => [
211         'user_id' => [
212           'table' => 'users',
213           'columns' => ['target_id' => 'uid'],
214         ],
215       ],
216     ]);
217     // Add a multi-column field with a foreign key.
218     $this->setUpStorageDefinition('editor_revision', [
219       'columns' => [
220         'target_id' => [
221           'type' => 'int',
222         ],
223         'target_revision_id' => [
224           'type' => 'int',
225         ],
226       ],
227       'foreign keys' => [
228         'user_id' => [
229           'table' => 'users',
230           'columns' => ['target_id' => 'uid'],
231         ],
232       ],
233     ]);
234     // Add a field with a really long index.
235     $this->setUpStorageDefinition('long_index_name', [
236       'columns' => [
237         'long_index_name' => [
238           'type' => 'int',
239         ],
240       ],
241       'indexes' => [
242         'long_index_name_really_long_long_name' => [['long_index_name', 10]],
243       ],
244     ]);
245
246     $expected = [
247       'entity_test' => [
248         'description' => 'The base table for entity_test entities.',
249         'fields' => [
250           'id' => [
251             'type' => 'serial',
252             'not null' => TRUE,
253           ],
254           'name' => [
255             'type' => 'varchar',
256             'length' => 255,
257             'not null' => FALSE,
258           ],
259           'description__value' => [
260             'type' => 'text',
261             'not null' => FALSE,
262           ],
263           'description__format' => [
264             'type' => 'varchar',
265             'not null' => FALSE,
266           ],
267           'uuid' => [
268             'type' => 'varchar',
269             'length' => 128,
270             'not null' => FALSE,
271           ],
272           'hash' => [
273             'type' => 'varchar',
274             'length' => 20,
275             'not null' => FALSE,
276           ],
277           'email__username' => [
278             'type' => 'varchar',
279             'not null' => FALSE,
280           ],
281           'email__hostname' => [
282             'type' => 'varchar',
283             'not null' => FALSE,
284           ],
285           'email__domain' => [
286             'type' => 'varchar',
287             'not null' => FALSE,
288           ],
289           'owner' => [
290             'type' => 'int',
291             'not null' => FALSE,
292           ],
293           'translator' => [
294             'type' => 'int',
295             'not null' => FALSE,
296           ],
297           'location__country' => [
298             'type' => 'varchar',
299             'not null' => FALSE,
300           ],
301           'location__state' => [
302             'type' => 'varchar',
303             'not null' => FALSE,
304           ],
305           'location__city' => [
306             'type' => 'varchar',
307             'not null' => FALSE,
308           ],
309           'editor' => [
310             'type' => 'int',
311             'not null' => FALSE,
312           ],
313           'editor_revision__target_id' => [
314             'type' => 'int',
315             'not null' => FALSE,
316           ],
317           'editor_revision__target_revision_id' => [
318             'type' => 'int',
319             'not null' => FALSE,
320           ],
321           'long_index_name' => [
322             'type' => 'int',
323             'not null' => FALSE,
324           ],
325         ],
326         'primary key' => ['id'],
327         'unique keys' => [
328           'entity_test_field__uuid__value' => ['uuid'],
329           'entity_test_field__hash__value' => [['hash', 10]],
330           'entity_test_field__email__email' => [
331             'email__username',
332             'email__hostname',
333             ['email__domain', 3],
334           ],
335         ],
336         'indexes' => [
337           'entity_test_field__owner__target_id' => ['owner'],
338           'entity_test_field__translator__target_id' => [
339             ['translator', 10],
340           ],
341           'entity_test_field__location__country_state_city' => [
342             'location__country',
343             'location__state',
344             ['location__city', 10],
345           ],
346           'entity_test__b588603cb9' => [
347             ['long_index_name', 10],
348           ],
349
350         ],
351         'foreign keys' => [
352           'entity_test_field__editor__user_id' => [
353             'table' => 'users',
354             'columns' => ['editor' => 'uid'],
355           ],
356           'entity_test_field__editor_revision__user_id' => [
357             'table' => 'users',
358             'columns' => ['editor_revision__target_id' => 'uid'],
359           ],
360         ],
361       ],
362     ];
363
364     $this->setUpStorageSchema($expected);
365
366     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
367     $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
368     $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
369
370     $this->storage->expects($this->any())
371       ->method('getTableMapping')
372       ->will($this->returnValue($table_mapping));
373     $this->storage->expects($this->any())
374       ->method('getCustomTableMapping')
375       ->will($this->returnValue($table_mapping));
376
377     $this->assertNull(
378       $this->storageSchema->onEntityTypeCreate($this->entityType)
379     );
380   }
381
382   /**
383    * Tests the schema for revisionable, non-translatable entities.
384    *
385    * @covers ::__construct
386    * @covers ::getEntitySchemaTables
387    * @covers ::initializeBaseTable
388    * @covers ::initializeRevisionTable
389    * @covers ::addTableDefaults
390    * @covers ::getEntityIndexName
391    * @covers ::processRevisionTable
392    * @covers ::processIdentifierSchema
393    */
394   public function testGetSchemaRevisionable() {
395     $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
396       ->setConstructorArgs([
397         [
398           'id' => 'entity_test',
399           'entity_keys' => [
400             'id' => 'id',
401             'revision' => 'revision_id',
402           ],
403         ],
404       ])
405       ->setMethods(['getRevisionMetadataKeys'])
406       ->getMock();
407
408     $this->entityType->expects($this->any())
409       ->method('getRevisionMetadataKeys')
410       ->will($this->returnValue([]));
411
412     $this->storage->expects($this->exactly(2))
413       ->method('getRevisionTable')
414       ->will($this->returnValue('entity_test_revision'));
415
416     $this->setUpStorageDefinition('revision_id', [
417       'columns' => [
418         'value' => [
419           'type' => 'int',
420         ],
421       ],
422     ]);
423
424     $expected = [
425       'entity_test' => [
426         'description' => 'The base table for entity_test entities.',
427         'fields' => [
428           'id' => [
429             'type' => 'serial',
430             'not null' => TRUE,
431           ],
432           'revision_id' => [
433             'type' => 'int',
434             'not null' => FALSE,
435           ],
436         ],
437         'primary key' => ['id'],
438         'unique keys' => [
439           'entity_test__revision_id' => ['revision_id'],
440         ],
441         'indexes' => [],
442         'foreign keys' => [
443           'entity_test__revision' => [
444             'table' => 'entity_test_revision',
445             'columns' => ['revision_id' => 'revision_id'],
446           ],
447         ],
448       ],
449       'entity_test_revision' => [
450         'description' => 'The revision table for entity_test entities.',
451         'fields' => [
452           'id' => [
453             'type' => 'int',
454             'not null' => TRUE,
455           ],
456           'revision_id' => [
457             'type' => 'serial',
458             'not null' => TRUE,
459           ],
460         ],
461         'primary key' => ['revision_id'],
462         'unique keys' => [],
463         'indexes' => [
464           'entity_test__id' => ['id'],
465         ],
466         'foreign keys' => [
467           'entity_test__revisioned' => [
468             'table' => 'entity_test',
469             'columns' => ['id' => 'id'],
470           ],
471         ],
472       ],
473     ];
474
475     $this->setUpStorageSchema($expected);
476
477     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
478     $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
479     $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
480
481     $this->storage->expects($this->any())
482       ->method('getTableMapping')
483       ->will($this->returnValue($table_mapping));
484     $this->storage->expects($this->any())
485       ->method('getCustomTableMapping')
486       ->will($this->returnValue($table_mapping));
487
488     $this->storageSchema->onEntityTypeCreate($this->entityType);
489   }
490
491   /**
492    * Tests the schema for non-revisionable, translatable entities.
493    *
494    * @covers ::__construct
495    * @covers ::getEntitySchemaTables
496    * @covers ::initializeDataTable
497    * @covers ::addTableDefaults
498    * @covers ::getEntityIndexName
499    * @covers ::processDataTable
500    */
501   public function testGetSchemaTranslatable() {
502     $this->entityType = new ContentEntityType([
503       'id' => 'entity_test',
504       'entity_keys' => [
505         'id' => 'id',
506         'langcode' => 'langcode',
507       ],
508     ]);
509
510     $this->storage->expects($this->any())
511       ->method('getDataTable')
512       ->will($this->returnValue('entity_test_field_data'));
513
514     $this->setUpStorageDefinition('langcode', [
515       'columns' => [
516         'value' => [
517           'type' => 'varchar',
518         ],
519       ],
520     ]);
521
522     $this->setUpStorageDefinition('default_langcode', [
523       'columns' => [
524         'value' => [
525           'type' => 'int',
526           'size' => 'tiny',
527         ],
528       ],
529     ]);
530
531     $expected = [
532       'entity_test' => [
533         'description' => 'The base table for entity_test entities.',
534         'fields' => [
535           'id' => [
536             'type' => 'serial',
537             'not null' => TRUE,
538           ],
539           'langcode' => [
540             'type' => 'varchar',
541             'not null' => TRUE,
542           ],
543         ],
544         'primary key' => ['id'],
545         'unique keys' => [],
546         'indexes' => [],
547         'foreign keys' => [],
548       ],
549       'entity_test_field_data' => [
550         'description' => 'The data table for entity_test entities.',
551         'fields' => [
552           'id' => [
553             'type' => 'int',
554             'not null' => TRUE,
555           ],
556           'langcode' => [
557             'type' => 'varchar',
558             'not null' => TRUE,
559           ],
560           'default_langcode' => [
561             'type' => 'int',
562             'size' => 'tiny',
563             'not null' => TRUE,
564           ],
565         ],
566         'primary key' => ['id', 'langcode'],
567         'unique keys' => [],
568         'indexes' => [
569           'entity_test__id__default_langcode__langcode' => [
570             0 => 'id',
571             1 => 'default_langcode',
572             2 => 'langcode',
573           ],
574         ],
575         'foreign keys' => [
576           'entity_test' => [
577             'table' => 'entity_test',
578             'columns' => ['id' => 'id'],
579           ],
580         ],
581       ],
582     ];
583
584     $this->setUpStorageSchema($expected);
585
586     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
587     $non_data_fields = array_keys($this->storageDefinitions);
588     unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
589     $table_mapping->setFieldNames('entity_test', $non_data_fields);
590     $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
591
592     $this->storage->expects($this->any())
593       ->method('getTableMapping')
594       ->will($this->returnValue($table_mapping));
595     $this->storage->expects($this->any())
596       ->method('getCustomTableMapping')
597       ->will($this->returnValue($table_mapping));
598
599     $this->assertNull(
600       $this->storageSchema->onEntityTypeCreate($this->entityType)
601     );
602   }
603
604   /**
605    * Tests the schema for revisionable, translatable entities.
606    *
607    * @covers ::__construct
608    * @covers ::getEntitySchemaTables
609    * @covers ::initializeDataTable
610    * @covers ::addTableDefaults
611    * @covers ::getEntityIndexName
612    * @covers ::initializeRevisionDataTable
613    * @covers ::processRevisionDataTable
614    */
615   public function testGetSchemaRevisionableTranslatable() {
616     $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
617       ->setConstructorArgs([
618         [
619           'id' => 'entity_test',
620           'entity_keys' => [
621             'id' => 'id',
622             'revision' => 'revision_id',
623             'langcode' => 'langcode',
624           ],
625         ],
626       ])
627       ->setMethods(['getRevisionMetadataKeys'])
628       ->getMock();
629
630     $this->entityType->expects($this->any())
631       ->method('getRevisionMetadataKeys')
632       ->will($this->returnValue([]));
633
634     $this->storage->expects($this->exactly(3))
635       ->method('getRevisionTable')
636       ->will($this->returnValue('entity_test_revision'));
637     $this->storage->expects($this->once())
638       ->method('getDataTable')
639       ->will($this->returnValue('entity_test_field_data'));
640     $this->storage->expects($this->once())
641       ->method('getRevisionDataTable')
642       ->will($this->returnValue('entity_test_revision_field_data'));
643
644     $this->setUpStorageDefinition('revision_id', [
645       'columns' => [
646         'value' => [
647           'type' => 'int',
648         ],
649       ],
650     ]);
651     $this->setUpStorageDefinition('langcode', [
652       'columns' => [
653         'value' => [
654           'type' => 'varchar',
655         ],
656       ],
657     ]);
658     $this->setUpStorageDefinition('default_langcode', [
659       'columns' => [
660         'value' => [
661           'type' => 'int',
662           'size' => 'tiny',
663         ],
664       ],
665     ]);
666
667     $expected = [
668       'entity_test' => [
669         'description' => 'The base table for entity_test entities.',
670         'fields' => [
671           'id' => [
672             'type' => 'serial',
673             'not null' => TRUE,
674           ],
675           'revision_id' => [
676             'type' => 'int',
677             'not null' => FALSE,
678           ],
679           'langcode' => [
680             'type' => 'varchar',
681             'not null' => TRUE,
682           ],
683         ],
684         'primary key' => ['id'],
685         'unique keys' => [
686           'entity_test__revision_id' => ['revision_id'],
687         ],
688         'indexes' => [],
689         'foreign keys' => [
690           'entity_test__revision' => [
691             'table' => 'entity_test_revision',
692             'columns' => ['revision_id' => 'revision_id'],
693           ],
694         ],
695       ],
696       'entity_test_revision' => [
697         'description' => 'The revision table for entity_test entities.',
698         'fields' => [
699           'id' => [
700             'type' => 'int',
701             'not null' => TRUE,
702           ],
703           'revision_id' => [
704             'type' => 'serial',
705             'not null' => TRUE,
706           ],
707           'langcode' => [
708             'type' => 'varchar',
709             'not null' => TRUE,
710           ],
711         ],
712         'primary key' => ['revision_id'],
713         'unique keys' => [],
714         'indexes' => [
715           'entity_test__id' => ['id'],
716         ],
717         'foreign keys' => [
718           'entity_test__revisioned' => [
719             'table' => 'entity_test',
720             'columns' => ['id' => 'id'],
721           ],
722         ],
723       ],
724       'entity_test_field_data' => [
725         'description' => 'The data table for entity_test entities.',
726         'fields' => [
727           'id' => [
728             'type' => 'int',
729             'not null' => TRUE,
730           ],
731           'revision_id' => [
732             'type' => 'int',
733             'not null' => TRUE,
734           ],
735           'langcode' => [
736             'type' => 'varchar',
737             'not null' => TRUE,
738           ],
739           'default_langcode' => [
740             'type' => 'int',
741             'size' => 'tiny',
742             'not null' => TRUE,
743           ],
744         ],
745         'primary key' => ['id', 'langcode'],
746         'unique keys' => [],
747         'indexes' => [
748           'entity_test__revision_id' => ['revision_id'],
749           'entity_test__id__default_langcode__langcode' => [
750             0 => 'id',
751             1 => 'default_langcode',
752             2 => 'langcode',
753           ],
754         ],
755         'foreign keys' => [
756           'entity_test' => [
757             'table' => 'entity_test',
758             'columns' => ['id' => 'id'],
759           ],
760         ],
761       ],
762       'entity_test_revision_field_data' => [
763         'description' => 'The revision data table for entity_test entities.',
764         'fields' => [
765           'id' => [
766             'type' => 'int',
767             'not null' => TRUE,
768           ],
769           'revision_id' => [
770             'type' => 'int',
771             'not null' => TRUE,
772           ],
773           'langcode' => [
774             'type' => 'varchar',
775             'not null' => TRUE,
776           ],
777           'default_langcode' => [
778             'type' => 'int',
779             'size' => 'tiny',
780             'not null' => TRUE,
781           ],
782         ],
783         'primary key' => ['revision_id', 'langcode'],
784         'unique keys' => [],
785         'indexes' => [
786           'entity_test__id__default_langcode__langcode' => [
787             0 => 'id',
788             1 => 'default_langcode',
789             2 => 'langcode',
790           ],
791         ],
792         'foreign keys' => [
793           'entity_test' => [
794             'table' => 'entity_test',
795             'columns' => ['id' => 'id'],
796           ],
797           'entity_test__revision' => [
798             'table' => 'entity_test_revision',
799             'columns' => ['revision_id' => 'revision_id'],
800           ],
801         ],
802       ],
803     ];
804
805     $this->setUpStorageSchema($expected);
806
807     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
808     $non_data_fields = array_keys($this->storageDefinitions);
809     unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
810     $table_mapping->setFieldNames('entity_test', $non_data_fields);
811     $table_mapping->setFieldNames('entity_test_revision', $non_data_fields);
812     $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
813     $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions));
814
815     $this->storage->expects($this->any())
816       ->method('getTableMapping')
817       ->will($this->returnValue($table_mapping));
818     $this->storage->expects($this->any())
819       ->method('getCustomTableMapping')
820       ->will($this->returnValue($table_mapping));
821
822     $this->storageSchema->onEntityTypeCreate($this->entityType);
823   }
824
825   /**
826    * Tests the schema for a field dedicated table.
827    *
828    * @covers ::onFieldStorageDefinitionCreate
829    * @covers ::getDedicatedTableSchema
830    * @covers ::createDedicatedTableSchema
831    */
832   public function testDedicatedTableSchema() {
833     $entity_type_id = 'entity_test';
834     $this->entityType = new ContentEntityType([
835       'id' => 'entity_test',
836       'entity_keys' => ['id' => 'id'],
837     ]);
838
839     // Setup a field having a dedicated schema.
840     $field_name = $this->getRandomGenerator()->name();
841     $this->setUpStorageDefinition($field_name, [
842       'columns' => [
843         'shape' => [
844           'type' => 'varchar',
845           'length' => 32,
846           'not null' => FALSE,
847         ],
848         'color' => [
849           'type' => 'varchar',
850           'length' => 32,
851           'not null' => FALSE,
852         ],
853         'area' => [
854           'type' => 'int',
855           'unsigned' => TRUE,
856           'not null' => TRUE,
857         ],
858         'depth' => [
859           'type' => 'int',
860           'unsigned' => TRUE,
861           'not null' => TRUE,
862         ],
863       ],
864       'foreign keys' => [
865         'color' => [
866           'table' => 'color',
867           'columns' => [
868             'color' => 'id',
869           ],
870         ],
871       ],
872       'unique keys' => [
873         'area' => ['area'],
874         'shape' => [['shape', 10]],
875       ],
876       'indexes' => [
877         'depth' => ['depth'],
878         'color' => [['color', 3]],
879       ],
880     ]);
881
882     $field_storage = $this->storageDefinitions[$field_name];
883     $field_storage
884       ->expects($this->any())
885       ->method('getType')
886       ->will($this->returnValue('shape'));
887     $field_storage
888       ->expects($this->any())
889       ->method('getTargetEntityTypeId')
890       ->will($this->returnValue($entity_type_id));
891     $field_storage
892       ->expects($this->any())
893       ->method('isMultiple')
894       ->will($this->returnValue(TRUE));
895
896     $this->storageDefinitions['id']
897       ->expects($this->any())
898       ->method('getType')
899       ->will($this->returnValue('integer'));
900
901     $expected = [
902       $entity_type_id . '__' . $field_name => [
903         'description' => "Data storage for $entity_type_id field $field_name.",
904         'fields' => [
905           'bundle' => [
906             'type' => 'varchar_ascii',
907             'length' => 128,
908             'not null' => TRUE,
909             'default' => '',
910             'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
911           ],
912           'deleted' => [
913             'type' => 'int',
914             'size' => 'tiny',
915             'not null' => TRUE,
916             'default' => 0,
917             'description' => 'A boolean indicating whether this data item has been deleted',
918           ],
919           'entity_id' => [
920             'type' => 'int',
921             'unsigned' => TRUE,
922             'not null' => TRUE,
923             'description' => 'The entity id this data is attached to',
924           ],
925           'revision_id' => [
926             'type' => 'int',
927             'unsigned' => TRUE,
928             'not null' => TRUE,
929             'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
930           ],
931           'langcode' => [
932             'type' => 'varchar_ascii',
933             'length' => 32,
934             'not null' => TRUE,
935             'default' => '',
936             'description' => 'The language code for this data item.',
937           ],
938           'delta' => [
939             'type' => 'int',
940             'unsigned' => TRUE,
941             'not null' => TRUE,
942             'description' => 'The sequence number for this data item, used for multi-value fields',
943           ],
944           $field_name . '_shape' => [
945             'type' => 'varchar',
946             'length' => 32,
947             'not null' => FALSE,
948           ],
949           $field_name . '_color' => [
950             'type' => 'varchar',
951             'length' => 32,
952             'not null' => FALSE,
953           ],
954           $field_name . '_area' => [
955             'type' => 'int',
956             'unsigned' => TRUE,
957             'not null' => TRUE,
958           ],
959           $field_name . '_depth' => [
960             'type' => 'int',
961             'unsigned' => TRUE,
962             'not null' => TRUE,
963           ],
964         ],
965         'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
966         'indexes' => [
967           'bundle' => ['bundle'],
968           'revision_id' => ['revision_id'],
969           $field_name . '_depth' => [$field_name . '_depth'],
970           $field_name . '_color' => [[$field_name . '_color', 3]],
971         ],
972         'unique keys' => [
973            $field_name . '_area' => [$field_name . '_area'],
974            $field_name . '_shape' => [[$field_name . '_shape', 10]],
975         ],
976         'foreign keys' => [
977           $field_name . '_color' => [
978             'table' => 'color',
979             'columns' => [
980               $field_name . '_color' => 'id',
981             ],
982           ],
983         ],
984       ],
985     ];
986
987     $this->setUpStorageSchema($expected);
988
989     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
990     $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
991     $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
992
993     $this->storage->expects($this->any())
994       ->method('getTableMapping')
995       ->will($this->returnValue($table_mapping));
996
997     $this->assertNull(
998       $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
999     );
1000   }
1001
1002   /**
1003    * Tests the schema for a field dedicated table for an entity with a string identifier.
1004    *
1005    * @covers ::onFieldStorageDefinitionCreate
1006    * @covers ::getDedicatedTableSchema
1007    * @covers ::createDedicatedTableSchema
1008    */
1009   public function testDedicatedTableSchemaForEntityWithStringIdentifier() {
1010     $entity_type_id = 'entity_test';
1011     $this->entityType = new ContentEntityType([
1012       'id' => 'entity_test',
1013       'entity_keys' => ['id' => 'id'],
1014     ]);
1015
1016     // Setup a field having a dedicated schema.
1017     $field_name = $this->getRandomGenerator()->name();
1018     $this->setUpStorageDefinition($field_name, [
1019       'columns' => [
1020         'shape' => [
1021           'type' => 'varchar',
1022           'length' => 32,
1023           'not null' => FALSE,
1024         ],
1025         'color' => [
1026           'type' => 'varchar',
1027           'length' => 32,
1028           'not null' => FALSE,
1029         ],
1030       ],
1031       'foreign keys' => [
1032         'color' => [
1033           'table' => 'color',
1034           'columns' => [
1035             'color' => 'id',
1036           ],
1037         ],
1038       ],
1039       'unique keys' => [],
1040       'indexes' => [],
1041     ]);
1042
1043     $field_storage = $this->storageDefinitions[$field_name];
1044     $field_storage
1045       ->expects($this->any())
1046       ->method('getType')
1047       ->will($this->returnValue('shape'));
1048     $field_storage
1049       ->expects($this->any())
1050       ->method('getTargetEntityTypeId')
1051       ->will($this->returnValue($entity_type_id));
1052     $field_storage
1053       ->expects($this->any())
1054       ->method('isMultiple')
1055       ->will($this->returnValue(TRUE));
1056
1057     $this->storageDefinitions['id']
1058       ->expects($this->any())
1059       ->method('getType')
1060       ->will($this->returnValue('string'));
1061
1062     $expected = [
1063       $entity_type_id . '__' . $field_name => [
1064         'description' => "Data storage for $entity_type_id field $field_name.",
1065         'fields' => [
1066           'bundle' => [
1067             'type' => 'varchar_ascii',
1068             'length' => 128,
1069             'not null' => TRUE,
1070             'default' => '',
1071             'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
1072           ],
1073           'deleted' => [
1074             'type' => 'int',
1075             'size' => 'tiny',
1076             'not null' => TRUE,
1077             'default' => 0,
1078             'description' => 'A boolean indicating whether this data item has been deleted',
1079           ],
1080           'entity_id' => [
1081             'type' => 'varchar_ascii',
1082             'length' => 128,
1083             'not null' => TRUE,
1084             'description' => 'The entity id this data is attached to',
1085           ],
1086           'revision_id' => [
1087             'type' => 'varchar_ascii',
1088             'length' => 128,
1089             'not null' => TRUE,
1090             'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
1091           ],
1092           'langcode' => [
1093             'type' => 'varchar_ascii',
1094             'length' => 32,
1095             'not null' => TRUE,
1096             'default' => '',
1097             'description' => 'The language code for this data item.',
1098           ],
1099           'delta' => [
1100             'type' => 'int',
1101             'unsigned' => TRUE,
1102             'not null' => TRUE,
1103             'description' => 'The sequence number for this data item, used for multi-value fields',
1104           ],
1105           $field_name . '_shape' => [
1106             'type' => 'varchar',
1107             'length' => 32,
1108             'not null' => FALSE,
1109           ],
1110           $field_name . '_color' => [
1111             'type' => 'varchar',
1112             'length' => 32,
1113             'not null' => FALSE,
1114           ],
1115         ],
1116         'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
1117         'indexes' => [
1118           'bundle' => ['bundle'],
1119           'revision_id' => ['revision_id'],
1120         ],
1121         'foreign keys' => [
1122           $field_name . '_color' => [
1123             'table' => 'color',
1124             'columns' => [
1125               $field_name . '_color' => 'id',
1126             ],
1127           ],
1128         ],
1129       ],
1130     ];
1131
1132     $this->setUpStorageSchema($expected);
1133
1134     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1135     $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
1136     $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
1137
1138     $this->storage->expects($this->any())
1139       ->method('getTableMapping')
1140       ->will($this->returnValue($table_mapping));
1141
1142     $this->assertNull(
1143       $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
1144     );
1145   }
1146
1147   public function providerTestRequiresEntityDataMigration() {
1148     $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1149     $updated_entity_type_definition->expects($this->any())
1150       ->method('getStorageClass')
1151       // A class that exists, *any* class.
1152       ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1153     $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1154     $original_entity_type_definition->expects($this->any())
1155       ->method('getStorageClass')
1156       // A class that exists, *any* class.
1157       ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1158     $original_entity_type_definition_other_nonexisting = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1159     $original_entity_type_definition_other_nonexisting->expects($this->any())
1160       ->method('getStorageClass')
1161       ->willReturn('bar');
1162     $original_entity_type_definition_other_existing = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1163     $original_entity_type_definition_other_existing->expects($this->any())
1164       ->method('getStorageClass')
1165       // A class that exists, *any* class.
1166       ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1167
1168     return [
1169       // Case 1: same storage class, ::hasData() === TRUE.
1170       [$updated_entity_type_definition, $original_entity_type_definition, TRUE, TRUE, TRUE],
1171       // Case 2: same storage class, ::hasData() === FALSE.
1172       [$updated_entity_type_definition, $original_entity_type_definition, FALSE, TRUE, FALSE],
1173       // Case 3: different storage class, original storage class does not exist.
1174       [$updated_entity_type_definition, $original_entity_type_definition_other_nonexisting, NULL, TRUE, TRUE],
1175       // Case 4: different storage class, original storage class exists,
1176       // ::hasData() === TRUE.
1177       [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, TRUE, TRUE],
1178       // Case 5: different storage class, original storage class exists,
1179       // ::hasData() === FALSE.
1180       [$updated_entity_type_definition, $original_entity_type_definition_other_existing, FALSE, TRUE, FALSE],
1181       // Case 6: same storage class, ::hasData() === TRUE, no structure changes.
1182       [$updated_entity_type_definition, $original_entity_type_definition, TRUE, FALSE, FALSE],
1183       // Case 7: different storage class, original storage class exists,
1184       // ::hasData() === TRUE, no structure changes.
1185       [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, FALSE, FALSE],
1186     ];
1187   }
1188
1189   /**
1190    * @covers ::requiresEntityDataMigration
1191    *
1192    * @dataProvider providerTestRequiresEntityDataMigration
1193    */
1194   public function testRequiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition, $original_storage_has_data, $shared_table_structure_changed, $migration_required) {
1195     $this->entityType = new ContentEntityType([
1196       'id' => 'entity_test',
1197       'entity_keys' => ['id' => 'id'],
1198     ]);
1199
1200     $original_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1201       ->disableOriginalConstructor()
1202       ->getMock();
1203
1204     $original_storage->expects($this->exactly(is_null($original_storage_has_data) || !$shared_table_structure_changed ? 0 : 1))
1205       ->method('hasData')
1206       ->willReturn($original_storage_has_data);
1207
1208     // Assert hasData() is never called on the new storage definition.
1209     $this->storage->expects($this->never())
1210       ->method('hasData');
1211
1212     $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1213       ->disableOriginalConstructor()
1214       ->getMock();
1215
1216     $this->entityManager->expects($this->any())
1217       ->method('createHandlerInstance')
1218       ->willReturn($original_storage);
1219
1220     $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1221       ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1222       ->setMethods(['installedStorageSchema', 'hasSharedTableStructureChange'])
1223       ->getMock();
1224
1225     $this->storageSchema->expects($this->any())
1226       ->method('hasSharedTableStructureChange')
1227       ->with($updated_entity_type_definition, $original_entity_type_definition)
1228       ->willReturn($shared_table_structure_changed);
1229
1230     $this->assertEquals($migration_required, $this->storageSchema->requiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition));
1231   }
1232
1233   /**
1234    * Data provider for ::testRequiresEntityStorageSchemaChanges().
1235    */
1236   public function providerTestRequiresEntityStorageSchemaChanges() {
1237
1238     $cases = [];
1239
1240     $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1241     $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1242
1243     $updated_entity_type_definition->expects($this->any())
1244       ->method('id')
1245       ->willReturn('entity_test');
1246     $updated_entity_type_definition->expects($this->any())
1247       ->method('getKey')
1248       ->willReturn('id');
1249     $original_entity_type_definition->expects($this->any())
1250       ->method('id')
1251       ->willReturn('entity_test');
1252     $original_entity_type_definition->expects($this->any())
1253       ->method('getKey')
1254       ->willReturn('id');
1255
1256     // Storage class changes should not impact this at all, and should not be
1257     // checked.
1258     $updated = clone $updated_entity_type_definition;
1259     $original = clone $original_entity_type_definition;
1260     $updated->expects($this->never())
1261       ->method('getStorageClass');
1262     $original->expects($this->never())
1263       ->method('getStorageClass');
1264
1265     // Case 1: No shared table changes should not require change.
1266     $cases[] = [$updated, $original, FALSE, FALSE, FALSE];
1267
1268     // Case 2: A change in the entity schema should result in required changes.
1269     $cases[] = [$updated, $original, TRUE, TRUE, FALSE];
1270
1271     // Case 3: Has shared table changes should result in required changes.
1272     $cases[] = [$updated, $original, TRUE, FALSE, TRUE];
1273
1274     // Case 4: Changing translation should result in required changes.
1275     $updated = clone $updated_entity_type_definition;
1276     $updated->expects($this->once())
1277       ->method('isTranslatable')
1278       ->willReturn(FALSE);
1279     $original = clone $original_entity_type_definition;
1280     $original->expects($this->once())
1281       ->method('isTranslatable')
1282       ->willReturn(TRUE);
1283     $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1284
1285     // Case 5: Changing revisionable should result in required changes.
1286     $updated = clone $updated_entity_type_definition;
1287     $updated->expects($this->once())
1288       ->method('isRevisionable')
1289       ->willReturn(FALSE);
1290     $original = clone $original_entity_type_definition;
1291     $original->expects($this->once())
1292       ->method('isRevisionable')
1293       ->willReturn(TRUE);
1294     $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1295
1296     return $cases;
1297   }
1298
1299   /**
1300    * @covers ::requiresEntityStorageSchemaChanges
1301    *
1302    * @dataProvider providerTestRequiresEntityStorageSchemaChanges
1303    */
1304   public function testRequiresEntityStorageSchemaChanges(ContentEntityTypeInterface $updated, ContentEntityTypeInterface $original, $requires_change, $change_schema, $change_shared_table) {
1305
1306     $this->entityType = new ContentEntityType([
1307       'id' => 'entity_test',
1308       'entity_keys' => ['id' => 'id'],
1309     ]);
1310
1311     $this->setUpStorageSchema();
1312     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1313     $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1314     $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1315     $this->storage->expects($this->any())
1316       ->method('getTableMapping')
1317       ->will($this->returnValue($table_mapping));
1318     $this->storage->expects($this->any())
1319       ->method('getCustomTableMapping')
1320       ->will($this->returnValue($table_mapping));
1321
1322     // Setup storage schema.
1323     if ($change_schema) {
1324       $this->storageSchema->expects($this->once())
1325         ->method('loadEntitySchemaData')
1326         ->willReturn([]);
1327     }
1328     else {
1329       $expected = [
1330         'entity_test' => [
1331           'primary key' => ['id'],
1332         ],
1333       ];
1334       $this->storageSchema->expects($this->any())
1335         ->method('loadEntitySchemaData')
1336         ->willReturn($expected);
1337     }
1338
1339     if ($change_shared_table) {
1340       $this->storageSchema->expects($this->once())
1341         ->method('hasSharedTableNameChanges')
1342         ->willReturn(TRUE);
1343     }
1344
1345     $this->assertEquals($requires_change, $this->storageSchema->requiresEntityStorageSchemaChanges($updated, $original));
1346   }
1347
1348   /**
1349    * Sets up the storage schema object to test.
1350    *
1351    * This uses the field definitions set in $this->storageDefinitions.
1352    *
1353    * @param array $expected
1354    *   (optional) An associative array describing the expected entity schema to
1355    *   be created. Defaults to expecting nothing.
1356    */
1357   protected function setUpStorageSchema(array $expected = []) {
1358     $this->entityManager->expects($this->any())
1359       ->method('getDefinition')
1360       ->with($this->entityType->id())
1361       ->will($this->returnValue($this->entityType));
1362
1363     $this->entityManager->expects($this->any())
1364       ->method('getFieldStorageDefinitions')
1365       ->with($this->entityType->id())
1366       ->will($this->returnValue($this->storageDefinitions));
1367
1368     $this->dbSchemaHandler = $this->getMockBuilder('Drupal\Core\Database\Schema')
1369       ->disableOriginalConstructor()
1370       ->getMock();
1371
1372     if ($expected) {
1373       $invocation_count = 0;
1374       $expected_table_names = array_keys($expected);
1375       $expected_table_schemas = array_values($expected);
1376
1377       $this->dbSchemaHandler->expects($this->any())
1378         ->method('createTable')
1379         ->with(
1380           $this->callback(function ($table_name) use (&$invocation_count, $expected_table_names) {
1381             return $expected_table_names[$invocation_count] == $table_name;
1382           }),
1383           $this->callback(function ($table_schema) use (&$invocation_count, $expected_table_schemas) {
1384             return $expected_table_schemas[$invocation_count] == $table_schema;
1385           })
1386         )
1387         ->will($this->returnCallback(function () use (&$invocation_count) {
1388           $invocation_count++;
1389         }));
1390     }
1391
1392     $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1393       ->disableOriginalConstructor()
1394       ->getMock();
1395     $connection->expects($this->any())
1396       ->method('schema')
1397       ->will($this->returnValue($this->dbSchemaHandler));
1398
1399     $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
1400     $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1401       ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1402       ->setMethods(['installedStorageSchema', 'loadEntitySchemaData', 'hasSharedTableNameChanges', 'isTableEmpty'])
1403       ->getMock();
1404     $this->storageSchema
1405       ->expects($this->any())
1406       ->method('installedStorageSchema')
1407       ->will($this->returnValue($key_value));
1408     $this->storageSchema
1409       ->expects($this->any())
1410       ->method('isTableEmpty')
1411       ->willReturn(FALSE);
1412   }
1413
1414   /**
1415    * Sets up a field definition.
1416    *
1417    * @param string $field_name
1418    *   The field name.
1419    * @param array $schema
1420    *   The schema array of the field definition, as returned from
1421    *   FieldStorageDefinitionInterface::getSchema().
1422    */
1423   public function setUpStorageDefinition($field_name, array $schema) {
1424     $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1425     $this->storageDefinitions[$field_name]->expects($this->any())
1426       ->method('isBaseField')
1427       ->will($this->returnValue(TRUE));
1428     // getName() is called once for each table.
1429     $this->storageDefinitions[$field_name]->expects($this->any())
1430       ->method('getName')
1431       ->will($this->returnValue($field_name));
1432     // getSchema() is called once for each table.
1433     $this->storageDefinitions[$field_name]->expects($this->any())
1434       ->method('getSchema')
1435       ->will($this->returnValue($schema));
1436     $this->storageDefinitions[$field_name]->expects($this->any())
1437       ->method('getColumns')
1438       ->will($this->returnValue($schema['columns']));
1439     // Add property definitions.
1440     if (!empty($schema['columns'])) {
1441       $property_definitions = [];
1442       foreach ($schema['columns'] as $column => $info) {
1443         $property_definitions[$column] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
1444         $property_definitions[$column]->expects($this->any())
1445           ->method('isRequired')
1446           ->will($this->returnValue(!empty($info['not null'])));
1447       }
1448       $this->storageDefinitions[$field_name]->expects($this->any())
1449         ->method('getPropertyDefinitions')
1450         ->will($this->returnValue($property_definitions));
1451     }
1452   }
1453
1454   /**
1455    * ::onEntityTypeUpdate
1456    */
1457   public function testonEntityTypeUpdateWithNewIndex() {
1458     $this->entityType = $original_entity_type = new ContentEntityType([
1459       'id' => 'entity_test',
1460       'entity_keys' => ['id' => 'id'],
1461     ]);
1462
1463     // Add a field with a really long index.
1464     $this->setUpStorageDefinition('long_index_name', [
1465       'columns' => [
1466         'long_index_name' => [
1467           'type' => 'int',
1468         ],
1469       ],
1470       'indexes' => [
1471         'long_index_name_really_long_long_name' => [['long_index_name', 10]],
1472       ],
1473     ]);
1474
1475     $expected = [
1476       'entity_test' => [
1477         'description' => 'The base table for entity_test entities.',
1478         'fields' => [
1479           'id' => [
1480             'type' => 'serial',
1481             'not null' => TRUE,
1482           ],
1483           'long_index_name' => [
1484             'type' => 'int',
1485             'not null' => FALSE,
1486           ],
1487         ],
1488         'indexes' => [
1489           'entity_test__b588603cb9' => [
1490             ['long_index_name', 10],
1491           ],
1492         ],
1493       ],
1494     ];
1495
1496     $this->setUpStorageSchema($expected);
1497
1498     $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1499     $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1500     $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1501
1502     $this->storage->expects($this->any())
1503       ->method('getTableMapping')
1504       ->will($this->returnValue($table_mapping));
1505     $this->storage->expects($this->any())
1506       ->method('getCustomTableMapping')
1507       ->will($this->returnValue($table_mapping));
1508
1509     $this->storageSchema->expects($this->any())
1510       ->method('loadEntitySchemaData')
1511       ->willReturn([
1512         'entity_test' => [
1513           'indexes' => [
1514             // A changed index definition.
1515             'entity_test__b588603cb9' => ['longer_index_name'],
1516             // An index that has been removed.
1517             'entity_test__removed_field' => ['removed_field'],
1518           ],
1519         ],
1520       ]);
1521
1522     // The original indexes should be dropped before the new one is added.
1523     $this->dbSchemaHandler->expects($this->at(0))
1524       ->method('dropIndex')
1525       ->with('entity_test', 'entity_test__b588603cb9');
1526     $this->dbSchemaHandler->expects($this->at(1))
1527       ->method('dropIndex')
1528       ->with('entity_test', 'entity_test__removed_field');
1529
1530     $this->dbSchemaHandler->expects($this->atLeastOnce())
1531       ->method('fieldExists')
1532       ->willReturn(TRUE);
1533     $this->dbSchemaHandler->expects($this->atLeastOnce())
1534       ->method('addIndex')
1535       ->with('entity_test', 'entity_test__b588603cb9', [['long_index_name', 10]], $this->callback(function ($actual_value) use ($expected) {
1536         $this->assertEquals($expected['entity_test']['indexes'], $actual_value['indexes']);
1537         $this->assertEquals($expected['entity_test']['fields'], $actual_value['fields']);
1538         // If the parameters don't match, the assertions above will throw an
1539         // exception.
1540         return TRUE;
1541       }));
1542
1543     $this->assertNull(
1544       $this->storageSchema->onEntityTypeUpdate($this->entityType, $original_entity_type)
1545     );
1546   }
1547
1548 }