a9cd9f19022b42aefbb4786d075ede2f5a0bccde
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Config / ConfigDependencyTest.php
1 <?php
2
3 namespace Drupal\KernelTests\Core\Config;
4
5 use Drupal\entity_test\Entity\EntityTest;
6 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
7
8 /**
9  * Tests for configuration dependencies.
10  *
11  * @coversDefaultClass \Drupal\Core\Config\ConfigManager
12  *
13  * @group config
14  */
15 class ConfigDependencyTest extends EntityKernelTestBase {
16
17   /**
18    * Modules to enable.
19    *
20    * The entity_test module is enabled to provide content entity types.
21    *
22    * @var array
23    */
24   public static $modules = ['config_test', 'entity_test', 'user'];
25
26   /**
27    * Tests that calculating dependencies for system module.
28    */
29   public function testNonEntity() {
30     $this->installConfig(['system']);
31     $config_manager = \Drupal::service('config.manager');
32     $dependents = $config_manager->findConfigEntityDependents('module', ['system']);
33     $this->assertTrue(isset($dependents['system.site']), 'Simple configuration system.site has a UUID key even though it is not a configuration entity and therefore is found when looking for dependencies of the System module.');
34     // Ensure that calling
35     // \Drupal\Core\Config\ConfigManager::findConfigEntityDependentsAsEntities()
36     // does not try to load system.site as an entity.
37     $config_manager->findConfigEntityDependentsAsEntities('module', ['system']);
38   }
39
40   /**
41    * Tests creating dependencies on configuration entities.
42    */
43   public function testDependencyManagement() {
44     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
45     $config_manager = \Drupal::service('config.manager');
46     $storage = $this->container->get('entity.manager')->getStorage('config_test');
47     // Test dependencies between modules.
48     $entity1 = $storage->create(
49       [
50         'id' => 'entity1',
51         'dependencies' => [
52           'enforced' => [
53             'module' => ['node']
54           ]
55         ]
56       ]
57     );
58     $entity1->save();
59
60     $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
61     $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
62     $dependents = $config_manager->findConfigEntityDependents('module', ['config_test']);
63     $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the config_test module.');
64     $dependents = $config_manager->findConfigEntityDependents('module', ['views']);
65     $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Views module.');
66     // Ensure that the provider of the config entity is not actually written to
67     // the dependencies array.
68     $raw_config = $this->config('config_test.dynamic.entity1');
69     $root_module_dependencies = $raw_config->get('dependencies.module');
70     $this->assertTrue(empty($root_module_dependencies), 'Node module is not written to the root dependencies array as it is enforced.');
71
72     // Create additional entities to test dependencies on config entities.
73     $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
74     $entity2->save();
75     $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity2->getConfigDependencyName()]]]]);
76     $entity3->save();
77     $entity4 = $storage->create(['id' => 'entity4', 'dependencies' => ['enforced' => ['config' => [$entity3->getConfigDependencyName()]]]]);
78     $entity4->save();
79
80     // Test getting $entity1's dependencies as configuration dependency objects.
81     $dependents = $config_manager->findConfigEntityDependents('config', [$entity1->getConfigDependencyName()]);
82     $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on itself.');
83     $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
84     $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
85     $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
86
87     // Test getting $entity2's dependencies as entities.
88     $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity2->getConfigDependencyName()]);
89     $dependent_ids = $this->getDependentIds($dependents);
90     $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on config_test.dynamic.entity1.');
91     $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on itself.');
92     $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity2.');
93     $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity2.');
94
95     // Test getting node module's dependencies as configuration dependency
96     // objects.
97     $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
98     $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
99     $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the Node module.');
100     $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
101     $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
102
103     // Test getting node module's dependencies as configuration dependency
104     // objects after making $entity3 also dependent on node module but $entity1
105     // no longer depend on node module.
106     $entity1->setEnforcedDependencies([])->save();
107     $entity3->setEnforcedDependencies(['module' => ['node'], 'config' => [$entity2->getConfigDependencyName()]])->save();
108     $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
109     $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.');
110     $this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.');
111     $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
112     $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
113
114     // Test dependency on a content entity.
115     $entity_test = EntityTest::create([
116       'name' => $this->randomString(),
117       'type' => 'entity_test',
118     ]);
119     $entity_test->save();
120     $entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();;
121     $dependents = $config_manager->findConfigEntityDependents('content', [$entity_test->getConfigDependencyName()]);
122     $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the content entity.');
123     $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the content entity.');
124     $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the content entity (via entity2).');
125     $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the content entity (via entity3).');
126
127     // Create a configuration entity of a different type with the same ID as one
128     // of the entities already created.
129     $alt_storage = $this->container->get('entity.manager')->getStorage('config_query_test');
130     $alt_storage->create(['id' => 'entity1', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]])->save();
131     $alt_storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['module' => ['views']]]])->save();
132
133     $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity1->getConfigDependencyName()]);
134     $dependent_ids = $this->getDependentIds($dependents);
135     $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on itself.');
136     $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
137     $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
138     $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
139     $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.');
140     $this->assertFalse(in_array('config_query_test:entity2', $dependent_ids), 'config_query_test.dynamic.entity2 does not have a dependency on config_test.dynamic.entity1.');
141
142     $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['node', 'views']);
143     $dependent_ids = $this->getDependentIds($dependents);
144     $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on Views or Node.');
145     $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on Views or Node.');
146     $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on Views or Node.');
147     $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on Views or Node.');
148     $this->assertFalse(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 does not have a dependency on Views or Node.');
149     $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on Views or Node.');
150
151     $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['config_test']);
152     $dependent_ids = $this->getDependentIds($dependents);
153     $this->assertTrue(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 has a dependency on config_test module.');
154     $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test module.');
155     $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test module.');
156     $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test module.');
157     $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 has a dependency on config_test module.');
158     $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on config_test module.');
159
160     // Test the ability to find missing content dependencies.
161     $missing_dependencies = $config_manager->findMissingContentDependencies();
162     $this->assertEqual([], $missing_dependencies);
163
164     $expected = [
165       $entity_test->uuid() => [
166         'entity_type' => 'entity_test',
167         'bundle' => $entity_test->bundle(),
168         'uuid' => $entity_test->uuid(),
169       ],
170     ];
171     // Delete the content entity so that is it now missing.
172     $entity_test->delete();
173     $missing_dependencies = $config_manager->findMissingContentDependencies();
174     $this->assertEqual($expected, $missing_dependencies);
175
176     // Add a fake missing dependency to ensure multiple missing dependencies
177     // work.
178     $entity1->setEnforcedDependencies(['content' => [$entity_test->getConfigDependencyName(), 'entity_test:bundle:uuid']])->save();;
179     $expected['uuid'] = [
180       'entity_type' => 'entity_test',
181       'bundle' => 'bundle',
182       'uuid' => 'uuid',
183     ];
184     $missing_dependencies = $config_manager->findMissingContentDependencies();
185     $this->assertEqual($expected, $missing_dependencies);
186   }
187
188   /**
189    * Tests ConfigManager::uninstall() and config entity dependency management.
190    */
191   public function testConfigEntityUninstall() {
192     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
193     $config_manager = \Drupal::service('config.manager');
194     /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
195     $storage = $this->container->get('entity.manager')
196       ->getStorage('config_test');
197     // Test dependencies between modules.
198     $entity1 = $storage->create(
199       [
200         'id' => 'entity1',
201         'dependencies' => [
202           'enforced' => [
203             'module' => ['node', 'config_test']
204           ],
205         ],
206       ]
207     );
208     $entity1->save();
209     $entity2 = $storage->create(
210       [
211         'id' => 'entity2',
212         'dependencies' => [
213           'enforced' => [
214             'config' => [$entity1->getConfigDependencyName()],
215           ],
216         ],
217       ]
218     );
219     $entity2->save();
220     // Perform a module rebuild so we can know where the node module is located
221     // and uninstall it.
222     // @todo Remove as part of https://www.drupal.org/node/2186491
223     system_rebuild_module_data();
224     // Test that doing a config uninstall of the node module deletes entity2
225     // since it is dependent on entity1 which is dependent on the node module.
226     $config_manager->uninstall('module', 'node');
227     $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
228     $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
229   }
230
231   /**
232    * Data provider for self::testConfigEntityUninstallComplex().
233    */
234   public function providerConfigEntityUninstallComplex() {
235     // Ensure that alphabetical order has no influence on dependency fixing and
236     // removal.
237     return [
238       [['a', 'b', 'c', 'd', 'e']],
239       [['e', 'd', 'c', 'b', 'a']],
240       [['e', 'c', 'd', 'a', 'b']],
241     ];
242   }
243
244   /**
245    * Tests complex configuration entity dependency handling during uninstall.
246    *
247    * Configuration entities can be deleted or updated during module uninstall
248    * because they have dependencies on the module.
249    *
250    * @param array $entity_id_suffixes
251    *   The suffixes to add to the 4 entities created by the test.
252    *
253    * @dataProvider providerConfigEntityUninstallComplex
254    */
255   public function testConfigEntityUninstallComplex(array $entity_id_suffixes) {
256     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
257     $config_manager = \Drupal::service('config.manager');
258     /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
259     $storage = $this->container->get('entity.manager')
260       ->getStorage('config_test');
261     // Entity 1 will be deleted because it depends on node.
262     $entity_1 = $storage->create(
263       [
264         'id' => 'entity_' . $entity_id_suffixes[0],
265         'dependencies' => [
266           'enforced' => [
267             'module' => ['node', 'config_test']
268           ],
269         ],
270       ]
271     );
272     $entity_1->save();
273
274     // Entity 2 has a dependency on entity 1 but it can be fixed because
275     // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
276     // dependency before config entities are deleted.
277     $entity_2 = $storage->create(
278       [
279         'id' => 'entity_' . $entity_id_suffixes[1],
280         'dependencies' => [
281           'enforced' => [
282             'config' => [$entity_1->getConfigDependencyName()],
283           ],
284         ],
285       ]
286     );
287     $entity_2->save();
288
289     // Entity 3 will be unchanged because it is dependent on entity 2 which can
290     // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
291     // not be called for this entity.
292     $entity_3 = $storage->create(
293       [
294         'id' => 'entity_' . $entity_id_suffixes[2],
295         'dependencies' => [
296           'enforced' => [
297             'config' => [$entity_2->getConfigDependencyName()],
298           ],
299         ],
300       ]
301     );
302     $entity_3->save();
303
304     // Entity 4's config dependency will be fixed but it will still be deleted
305     // because it also depends on the node module.
306     $entity_4 = $storage->create(
307       [
308         'id' => 'entity_' . $entity_id_suffixes[3],
309         'dependencies' => [
310           'enforced' => [
311             'config' => [$entity_1->getConfigDependencyName()],
312             'module' => ['node', 'config_test']
313           ],
314         ],
315       ]
316     );
317     $entity_4->save();
318
319     // Entity 5 will be fixed because it is dependent on entity 3, which is
320     // unchanged, and entity 1 which will be fixed because
321     // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
322     // dependency.
323     $entity_5 = $storage->create(
324       [
325         'id' => 'entity_' . $entity_id_suffixes[4],
326         'dependencies' => [
327           'enforced' => [
328             'config' => [
329               $entity_1->getConfigDependencyName(),
330               $entity_3->getConfigDependencyName(),
331             ],
332           ],
333         ],
334       ]
335     );
336     $entity_5->save();
337
338     // Set a more complicated test where dependencies will be fixed.
339     \Drupal::state()->set('config_test.fix_dependencies', [$entity_1->getConfigDependencyName()]);
340     \Drupal::state()->set('config_test.on_dependency_removal_called', []);
341
342     // Do a dry run using
343     // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
344     $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
345
346     // Assert that \Drupal\config_test\Entity\ConfigTest::onDependencyRemoval()
347     // is called as expected and with the correct dependencies.
348     $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
349     $this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
350     $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id(), $entity_5->id()], array_keys($called), 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
351     $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
352     $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_2->id()]);
353     $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
354     $this->assertSame(['config' => [$entity_1->getConfigDependencyName()], 'content' => [], 'module' => [], 'theme' => []], $called[$entity_5->id()]);
355
356     $this->assertEqual($entity_1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
357     $this->assertEqual($entity_2->uuid(), $config_entities['update'][0]->uuid(), 'Entity 2 will be updated.');
358     $this->assertEqual($entity_3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
359     $this->assertEqual($entity_4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.');
360     $this->assertEqual($entity_5->uuid(), $config_entities['update'][1]->uuid(), 'Entity 5 is updated.');
361
362     // Perform a module rebuild so we can know where the node module is located
363     // and uninstall it.
364     // @todo Remove as part of https://www.drupal.org/node/2186491
365     system_rebuild_module_data();
366     // Perform the uninstall.
367     $config_manager->uninstall('module', 'node');
368
369     // Test that expected actions have been performed.
370     $this->assertFalse($storage->load($entity_1->id()), 'Entity 1 deleted');
371     $entity_2 = $storage->load($entity_2->id());
372     $this->assertTrue($entity_2, 'Entity 2 not deleted');
373     $this->assertEqual($entity_2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on entity 1.');
374     $entity_3 = $storage->load($entity_3->id());
375     $this->assertTrue($entity_3, 'Entity 3 not deleted');
376     $this->assertEqual($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
377     $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
378   }
379
380   /**
381    * @covers ::uninstall
382    * @covers ::getConfigEntitiesToChangeOnDependencyRemoval
383    */
384   public function testConfigEntityUninstallThirdParty() {
385     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
386     $config_manager = \Drupal::service('config.manager');
387     /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
388     $storage = $this->container->get('entity_type.manager')
389       ->getStorage('config_test');
390     // Entity 1 will be fixed because it only has a dependency via third-party
391     // settings, which are fixable.
392     $entity_1 = $storage->create([
393       'id' => 'entity_1',
394       'dependencies' => [
395         'enforced' => [
396           'module' => ['config_test'],
397         ],
398       ],
399       'third_party_settings' => [
400         'node' => [
401           'foo' => 'bar',
402         ],
403       ],
404     ]);
405     $entity_1->save();
406
407     // Entity 2 has a dependency on entity 1.
408     $entity_2 = $storage->create([
409       'id' => 'entity_2',
410       'dependencies' => [
411         'enforced' => [
412           'config' => [$entity_1->getConfigDependencyName()],
413         ],
414       ],
415       'third_party_settings' => [
416         'node' => [
417           'foo' => 'bar',
418         ],
419       ],
420     ]);
421     $entity_2->save();
422
423     // Entity 3 will be unchanged because it is dependent on entity 2 which can
424     // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
425     // not be called for this entity.
426     $entity_3 = $storage->create([
427       'id' => 'entity_3',
428       'dependencies' => [
429         'enforced' => [
430           'config' => [$entity_2->getConfigDependencyName()],
431         ],
432       ],
433     ]);
434     $entity_3->save();
435
436     // Entity 4's config dependency will be fixed but it will still be deleted
437     // because it also depends on the node module.
438     $entity_4 = $storage->create([
439       'id' => 'entity_4',
440       'dependencies' => [
441         'enforced' => [
442           'config' => [$entity_1->getConfigDependencyName()],
443           'module' => ['node', 'config_test'],
444         ],
445       ],
446     ]);
447     $entity_4->save();
448
449     \Drupal::state()->set('config_test.fix_dependencies', []);
450     \Drupal::state()->set('config_test.on_dependency_removal_called', []);
451
452     // Do a dry run using
453     // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
454     $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
455     $config_entity_ids = [
456       'update' => [],
457       'delete' => [],
458       'unchanged' => [],
459     ];
460     foreach ($config_entities as $type => $config_entities_by_type) {
461       foreach ($config_entities_by_type as $config_entity) {
462         $config_entity_ids[$type][] = $config_entity->id();
463       }
464     }
465     $expected = [
466       'update' => [$entity_1->id(), $entity_2->id()],
467       'delete' => [$entity_4->id()],
468       'unchanged' => [$entity_3->id()],
469     ];
470     $this->assertSame($expected, $config_entity_ids);
471
472     $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
473     $this->assertArrayNotHasKey($entity_3->id(), $called, 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
474     $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], array_keys($called), 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
475     $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_1->id()]);
476     $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_2->id()]);
477     $this->assertSame(['config' => [], 'content' => [], 'module' => ['node'], 'theme' => []], $called[$entity_4->id()]);
478
479     // Perform a module rebuild so we can know where the node module is located
480     // and uninstall it.
481     // @todo Remove as part of https://www.drupal.org/node/2186491
482     system_rebuild_module_data();
483     // Perform the uninstall.
484     $config_manager->uninstall('module', 'node');
485
486     // Test that expected actions have been performed.
487     $entity_1 = $storage->load($entity_1->id());
488     $this->assertTrue($entity_1, 'Entity 1 not deleted');
489     $this->assertSame($entity_1->getThirdPartySettings('node'), [], 'Entity 1 third party settings updated.');
490     $entity_2 = $storage->load($entity_2->id());
491     $this->assertTrue($entity_2, 'Entity 2 not deleted');
492     $this->assertSame($entity_2->getThirdPartySettings('node'), [], 'Entity 2 third party settings updated.');
493     $this->assertSame($entity_2->calculateDependencies()->getDependencies()['config'], [$entity_1->getConfigDependencyName()], 'Entity 2 still depends on entity 1.');
494     $entity_3 = $storage->load($entity_3->id());
495     $this->assertTrue($entity_3, 'Entity 3 not deleted');
496     $this->assertSame($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
497     $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
498   }
499
500   /**
501    * Tests deleting a configuration entity and dependency management.
502    */
503   public function testConfigEntityDelete() {
504     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
505     $config_manager = \Drupal::service('config.manager');
506     /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
507     $storage = $this->container->get('entity.manager')->getStorage('config_test');
508     // Test dependencies between configuration entities.
509     $entity1 = $storage->create(
510       [
511         'id' => 'entity1'
512       ]
513     );
514     $entity1->save();
515     $entity2 = $storage->create(
516       [
517         'id' => 'entity2',
518         'dependencies' => [
519           'enforced' => [
520             'config' => [$entity1->getConfigDependencyName()],
521           ],
522         ],
523       ]
524     );
525     $entity2->save();
526
527     // Do a dry run using
528     // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
529     $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
530     $this->assertEqual($entity2->uuid(), reset($config_entities['delete'])->uuid(), 'Entity 2 will be deleted.');
531     $this->assertTrue(empty($config_entities['update']), 'No dependent configuration entities will be updated.');
532     $this->assertTrue(empty($config_entities['unchanged']), 'No dependent configuration entities will be unchanged.');
533
534     // Test that doing a delete of entity1 deletes entity2 since it is dependent
535     // on entity1.
536     $entity1->delete();
537     $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
538     $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
539
540     // Set a more complicated test where dependencies will be fixed.
541     \Drupal::state()->set('config_test.fix_dependencies', [$entity1->getConfigDependencyName()]);
542
543     // Entity1 will be deleted by the test.
544     $entity1 = $storage->create(
545       [
546         'id' => 'entity1',
547       ]
548     );
549     $entity1->save();
550
551     // Entity2 has a dependency on Entity1 but it can be fixed because
552     // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
553     // dependency before config entities are deleted.
554     $entity2 = $storage->create(
555       [
556         'id' => 'entity2',
557         'dependencies' => [
558           'enforced' => [
559             'config' => [$entity1->getConfigDependencyName()],
560           ],
561         ],
562       ]
563     );
564     $entity2->save();
565
566     // Entity3 will be unchanged because it is dependent on Entity2 which can
567     // be fixed.
568     $entity3 = $storage->create(
569       [
570         'id' => 'entity3',
571         'dependencies' => [
572           'enforced' => [
573             'config' => [$entity2->getConfigDependencyName()],
574           ],
575         ],
576       ]
577     );
578     $entity3->save();
579
580     // Do a dry run using
581     // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
582     $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
583     $this->assertTrue(empty($config_entities['delete']), 'No dependent configuration entities will be deleted.');
584     $this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
585     $this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
586
587     // Perform the uninstall.
588     $entity1->delete();
589
590     // Test that expected actions have been performed.
591     $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
592     $entity2 = $storage->load('entity2');
593     $this->assertTrue($entity2, 'Entity 2 not deleted');
594     $this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on Entity1.');
595     $entity3 = $storage->load('entity3');
596     $this->assertTrue($entity3, 'Entity 3 not deleted');
597     $this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
598   }
599
600   /**
601    * Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities.
602    *
603    * At the moment there is no runtime code that calculates configuration
604    * dependencies on content entity delete because this calculation is expensive
605    * and all content dependencies are soft. This test ensures that the code
606    * works for content entities.
607    *
608    * @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
609    */
610   public function testContentEntityDelete() {
611     $this->installEntitySchema('entity_test');
612     /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
613     $config_manager = \Drupal::service('config.manager');
614
615     $content_entity = EntityTest::create();
616     $content_entity->save();
617     /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
618     $storage = $this->container->get('entity.manager')->getStorage('config_test');
619     $entity1 = $storage->create(
620       [
621         'id' => 'entity1',
622         'dependencies' => [
623           'enforced' => [
624             'content' => [$content_entity->getConfigDependencyName()]
625           ],
626         ],
627       ]
628     );
629     $entity1->save();
630     $entity2 = $storage->create(
631       [
632         'id' => 'entity2',
633         'dependencies' => [
634           'enforced' => [
635             'config' => [$entity1->getConfigDependencyName()]
636           ],
637         ],
638       ]
639     );
640     $entity2->save();
641
642     // Create a configuration entity that is not in the dependency chain.
643     $entity3 = $storage->create(['id' => 'entity3']);
644     $entity3->save();
645
646     $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
647     $this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
648     $this->assertEqual($entity2->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 2 will be deleted.');
649     $this->assertTrue(empty($config_entities['update']), 'No dependencies of the content entity will be updated.');
650     $this->assertTrue(empty($config_entities['unchanged']), 'No dependencies of the content entity will be unchanged.');
651   }
652
653   /**
654    * Gets a list of identifiers from an array of configuration entities.
655    *
656    * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents
657    *   An array of configuration entities.
658    *
659    * @return array
660    *   An array with values of entity_type_id:ID
661    */
662   protected function getDependentIds(array $dependents) {
663     $dependent_ids = [];
664     foreach ($dependents as $dependent) {
665       $dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
666     }
667     return $dependent_ids;
668   }
669
670 }