Version 1
[yaffs-website] / web / core / tests / Drupal / KernelTests / Core / Config / ConfigDependencyTest.php
diff --git a/web/core/tests/Drupal/KernelTests/Core/Config/ConfigDependencyTest.php b/web/core/tests/Drupal/KernelTests/Core/Config/ConfigDependencyTest.php
new file mode 100644 (file)
index 0000000..79b01cb
--- /dev/null
@@ -0,0 +1,638 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Config;
+
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+
+/**
+ * Tests for configuration dependencies.
+ *
+ * @coversDefaultClass \Drupal\Core\Config\ConfigManager
+ *
+ * @group config
+ */
+class ConfigDependencyTest extends EntityKernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * The entity_test module is enabled to provide content entity types.
+   *
+   * @var array
+   */
+  public static $modules = ['config_test', 'entity_test', 'user'];
+
+  /**
+   * Tests that calculating dependencies for system module.
+   */
+  public function testNonEntity() {
+    $this->installConfig(['system']);
+    $config_manager = \Drupal::service('config.manager');
+    $dependents = $config_manager->findConfigEntityDependents('module', ['system']);
+    $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.');
+    // Ensure that calling
+    // \Drupal\Core\Config\ConfigManager::findConfigEntityDependentsAsEntities()
+    // does not try to load system.site as an entity.
+    $config_manager->findConfigEntityDependentsAsEntities('module', ['system']);
+  }
+
+  /**
+   * Tests creating dependencies on configuration entities.
+   */
+  public function testDependencyManagement() {
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+    $storage = $this->container->get('entity.manager')->getStorage('config_test');
+    // Test dependencies between modules.
+    $entity1 = $storage->create(
+      [
+        'id' => 'entity1',
+        'dependencies' => [
+          'enforced' => [
+            'module' => ['node']
+          ]
+        ]
+      ]
+    );
+    $entity1->save();
+
+    $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
+    $dependents = $config_manager->findConfigEntityDependents('module', ['config_test']);
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the config_test module.');
+    $dependents = $config_manager->findConfigEntityDependents('module', ['views']);
+    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Views module.');
+    // Ensure that the provider of the config entity is not actually written to
+    // the dependencies array.
+    $raw_config = $this->config('config_test.dynamic.entity1');
+    $root_module_dependencies = $raw_config->get('dependencies.module');
+    $this->assertTrue(empty($root_module_dependencies), 'Node module is not written to the root dependencies array as it is enforced.');
+
+    // Create additional entities to test dependencies on config entities.
+    $entity2 = $storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]]);
+    $entity2->save();
+    $entity3 = $storage->create(['id' => 'entity3', 'dependencies' => ['enforced' => ['config' => [$entity2->getConfigDependencyName()]]]]);
+    $entity3->save();
+    $entity4 = $storage->create(['id' => 'entity4', 'dependencies' => ['enforced' => ['config' => [$entity3->getConfigDependencyName()]]]]);
+    $entity4->save();
+
+    // Test getting $entity1's dependencies as configuration dependency objects.
+    $dependents = $config_manager->findConfigEntityDependents('config', [$entity1->getConfigDependencyName()]);
+    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on itself.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
+
+    // Test getting $entity2's dependencies as entities.
+    $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity2->getConfigDependencyName()]);
+    $dependent_ids = $this->getDependentIds($dependents);
+    $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on config_test.dynamic.entity1.');
+    $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on itself.');
+    $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity2.');
+    $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity2.');
+
+    // Test getting node module's dependencies as configuration dependency
+    // objects.
+    $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 has a dependency on the Node module.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the Node module.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
+
+    // Test getting node module's dependencies as configuration dependency
+    // objects after making $entity3 also dependent on node module but $entity1
+    // no longer depend on node module.
+    $entity1->setEnforcedDependencies([])->save();
+    $entity3->setEnforcedDependencies(['module' => ['node'], 'config' => [$entity2->getConfigDependencyName()]])->save();
+    $dependents = $config_manager->findConfigEntityDependents('module', ['node']);
+    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the Node module.');
+    $this->assertFalse(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 does not have a dependency on the Node module.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the Node module.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the Node module.');
+
+    // Test dependency on a content entity.
+    $entity_test = EntityTest::create([
+      'name' => $this->randomString(),
+      'type' => 'entity_test',
+    ]);
+    $entity_test->save();
+    $entity2->setEnforcedDependencies(['config' => [$entity1->getConfigDependencyName()], 'content' => [$entity_test->getConfigDependencyName()]])->save();;
+    $dependents = $config_manager->findConfigEntityDependents('content', [$entity_test->getConfigDependencyName()]);
+    $this->assertFalse(isset($dependents['config_test.dynamic.entity1']), 'config_test.dynamic.entity1 does not have a dependency on the content entity.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity2']), 'config_test.dynamic.entity2 has a dependency on the content entity.');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity3']), 'config_test.dynamic.entity3 has a dependency on the content entity (via entity2).');
+    $this->assertTrue(isset($dependents['config_test.dynamic.entity4']), 'config_test.dynamic.entity4 has a dependency on the content entity (via entity3).');
+
+    // Create a configuration entity of a different type with the same ID as one
+    // of the entities already created.
+    $alt_storage = $this->container->get('entity.manager')->getStorage('config_query_test');
+    $alt_storage->create(['id' => 'entity1', 'dependencies' => ['enforced' => ['config' => [$entity1->getConfigDependencyName()]]]])->save();
+    $alt_storage->create(['id' => 'entity2', 'dependencies' => ['enforced' => ['module' => ['views']]]])->save();
+
+    $dependents = $config_manager->findConfigEntityDependentsAsEntities('config', [$entity1->getConfigDependencyName()]);
+    $dependent_ids = $this->getDependentIds($dependents);
+    $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on itself.');
+    $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test.dynamic.entity1.');
+    $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test.dynamic.entity1.');
+    $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test.dynamic.entity1.');
+    $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_query_test.dynamic.entity1 has a dependency on config_test.dynamic.entity1.');
+    $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.');
+
+    $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['node', 'views']);
+    $dependent_ids = $this->getDependentIds($dependents);
+    $this->assertFalse(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 does not have a dependency on Views or Node.');
+    $this->assertFalse(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 does not have a dependency on Views or Node.');
+    $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on Views or Node.');
+    $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on Views or Node.');
+    $this->assertFalse(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 does not have a dependency on Views or Node.');
+    $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on Views or Node.');
+
+    $dependents = $config_manager->findConfigEntityDependentsAsEntities('module', ['config_test']);
+    $dependent_ids = $this->getDependentIds($dependents);
+    $this->assertTrue(in_array('config_test:entity1', $dependent_ids), 'config_test.dynamic.entity1 has a dependency on config_test module.');
+    $this->assertTrue(in_array('config_test:entity2', $dependent_ids), 'config_test.dynamic.entity2 has a dependency on config_test module.');
+    $this->assertTrue(in_array('config_test:entity3', $dependent_ids), 'config_test.dynamic.entity3 has a dependency on config_test module.');
+    $this->assertTrue(in_array('config_test:entity4', $dependent_ids), 'config_test.dynamic.entity4 has a dependency on config_test module.');
+    $this->assertTrue(in_array('config_query_test:entity1', $dependent_ids), 'config_test.query.entity1 has a dependency on config_test module.');
+    $this->assertTrue(in_array('config_query_test:entity2', $dependent_ids), 'config_test.query.entity2 has a dependency on config_test module.');
+
+    // Test the ability to find missing content dependencies.
+    $missing_dependencies = $config_manager->findMissingContentDependencies();
+    $this->assertEqual([], $missing_dependencies);
+
+    $expected = [$entity_test->uuid() => [
+      'entity_type' => 'entity_test',
+      'bundle' => $entity_test->bundle(),
+      'uuid' => $entity_test->uuid(),
+    ]];
+    // Delete the content entity so that is it now missing.
+    $entity_test->delete();
+    $missing_dependencies = $config_manager->findMissingContentDependencies();
+    $this->assertEqual($expected, $missing_dependencies);
+
+    // Add a fake missing dependency to ensure multiple missing dependencies
+    // work.
+    $entity1->setEnforcedDependencies(['content' => [$entity_test->getConfigDependencyName(), 'entity_test:bundle:uuid']])->save();;
+    $expected['uuid'] = [
+      'entity_type' => 'entity_test',
+      'bundle' => 'bundle',
+      'uuid' => 'uuid',
+    ];
+    $missing_dependencies = $config_manager->findMissingContentDependencies();
+    $this->assertEqual($expected, $missing_dependencies);
+  }
+
+  /**
+   * Tests ConfigManager::uninstall() and config entity dependency management.
+   */
+  public function testConfigEntityUninstall() {
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
+    $storage = $this->container->get('entity.manager')
+      ->getStorage('config_test');
+    // Test dependencies between modules.
+    $entity1 = $storage->create(
+      [
+        'id' => 'entity1',
+        'dependencies' => [
+          'enforced' => [
+            'module' => ['node', 'config_test']
+          ],
+        ],
+      ]
+    );
+    $entity1->save();
+    $entity2 = $storage->create(
+      [
+        'id' => 'entity2',
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity1->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity2->save();
+    // Perform a module rebuild so we can know where the node module is located
+    // and uninstall it.
+    // @todo Remove as part of https://www.drupal.org/node/2186491
+    system_rebuild_module_data();
+    // Test that doing a config uninstall of the node module deletes entity2
+    // since it is dependent on entity1 which is dependent on the node module.
+    $config_manager->uninstall('module', 'node');
+    $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
+    $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
+  }
+
+  /**
+   * Data provider for self::testConfigEntityUninstallComplex().
+   */
+  public function providerConfigEntityUninstallComplex() {
+    // Ensure that alphabetical order has no influence on dependency fixing and
+    // removal.
+    return [
+      [['a', 'b', 'c', 'd']],
+      [['d', 'c', 'b', 'a']],
+      [['c', 'd', 'a', 'b']],
+    ];
+  }
+
+  /**
+   * Tests complex configuration entity dependency handling during uninstall.
+   *
+   * Configuration entities can be deleted or updated during module uninstall
+   * because they have dependencies on the module.
+   *
+   * @param array $entity_id_suffixes
+   *   The suffixes to add to the 4 entities created by the test.
+   *
+   * @dataProvider providerConfigEntityUninstallComplex
+   */
+  public function testConfigEntityUninstallComplex(array $entity_id_suffixes) {
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
+    $storage = $this->container->get('entity.manager')
+      ->getStorage('config_test');
+    // Entity 1 will be deleted because it depends on node.
+    $entity_1 = $storage->create(
+      [
+        'id' => 'entity_' . $entity_id_suffixes[0],
+        'dependencies' => [
+          'enforced' => [
+            'module' => ['node', 'config_test']
+          ],
+        ],
+      ]
+    );
+    $entity_1->save();
+
+    // Entity 2 has a dependency on entity 1 but it can be fixed because
+    // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
+    // dependency before config entities are deleted.
+    $entity_2 = $storage->create(
+      [
+        'id' => 'entity_' . $entity_id_suffixes[1],
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity_1->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity_2->save();
+
+    // Entity 3 will be unchanged because it is dependent on entity 2 which can
+    // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
+    // not be called for this entity.
+    $entity_3 = $storage->create(
+      [
+        'id' => 'entity_' . $entity_id_suffixes[2],
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity_2->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity_3->save();
+
+    // Entity 4's config dependency will be fixed but it will still be deleted
+    // because it also depends on the node module.
+    $entity_4 = $storage->create(
+      [
+        'id' => 'entity_' . $entity_id_suffixes[3],
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity_1->getConfigDependencyName()],
+            'module' => ['node', 'config_test']
+          ],
+        ],
+      ]
+    );
+    $entity_4->save();
+
+    // Set a more complicated test where dependencies will be fixed.
+    \Drupal::state()->set('config_test.fix_dependencies', [$entity_1->getConfigDependencyName()]);
+    \Drupal::state()->set('config_test.on_dependency_removal_called', []);
+
+    // Do a dry run using
+    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
+    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
+    $this->assertEqual($entity_1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
+    $this->assertEqual($entity_2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
+    $this->assertEqual($entity_3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
+    $this->assertEqual($entity_4->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 4 will be deleted.');
+
+    $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
+    $this->assertFalse(in_array($entity_3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
+    $this->assertIdentical([$entity_1->id(), $entity_4->id(), $entity_2->id()], $called, 'The most dependent entites have ConfigEntityInterface::onDependencyRemoval() called first.');
+
+    // Perform a module rebuild so we can know where the node module is located
+    // and uninstall it.
+    // @todo Remove as part of https://www.drupal.org/node/2186491
+    system_rebuild_module_data();
+    // Perform the uninstall.
+    $config_manager->uninstall('module', 'node');
+
+    // Test that expected actions have been performed.
+    $this->assertFalse($storage->load($entity_1->id()), 'Entity 1 deleted');
+    $entity_2 = $storage->load($entity_2->id());
+    $this->assertTrue($entity_2, 'Entity 2 not deleted');
+    $this->assertEqual($entity_2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on entity 1.');
+    $entity_3 = $storage->load($entity_3->id());
+    $this->assertTrue($entity_3, 'Entity 3 not deleted');
+    $this->assertEqual($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
+    $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
+  }
+
+  /**
+   * @covers ::uninstall
+   * @covers ::getConfigEntitiesToChangeOnDependencyRemoval
+   */
+  public function testConfigEntityUninstallThirdParty() {
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
+    $storage = $this->container->get('entity_type.manager')
+      ->getStorage('config_test');
+    // Entity 1 will be fixed because it only has a dependency via third-party
+    // settings, which are fixable.
+    $entity_1 = $storage->create([
+      'id' => 'entity_1',
+      'dependencies' => [
+        'enforced' => [
+          'module' => ['config_test'],
+        ],
+      ],
+      'third_party_settings' => [
+        'node' => [
+          'foo' => 'bar',
+        ],
+      ],
+    ]);
+    $entity_1->save();
+
+    // Entity 2 has a dependency on entity 1.
+    $entity_2 = $storage->create([
+      'id' => 'entity_2',
+      'dependencies' => [
+        'enforced' => [
+          'config' => [$entity_1->getConfigDependencyName()],
+        ],
+      ],
+      'third_party_settings' => [
+        'node' => [
+          'foo' => 'bar',
+        ],
+      ],
+    ]);
+    $entity_2->save();
+
+    // Entity 3 will be unchanged because it is dependent on entity 2 which can
+    // be fixed. The ConfigEntityInterface::onDependencyRemoval() method will
+    // not be called for this entity.
+    $entity_3 = $storage->create([
+      'id' => 'entity_3',
+      'dependencies' => [
+        'enforced' => [
+          'config' => [$entity_2->getConfigDependencyName()],
+        ],
+      ],
+    ]);
+    $entity_3->save();
+
+    // Entity 4's config dependency will be fixed but it will still be deleted
+    // because it also depends on the node module.
+    $entity_4 = $storage->create([
+      'id' => 'entity_4',
+      'dependencies' => [
+        'enforced' => [
+          'config' => [$entity_1->getConfigDependencyName()],
+          'module' => ['node', 'config_test'],
+        ],
+      ],
+    ]);
+    $entity_4->save();
+
+    \Drupal::state()->set('config_test.fix_dependencies', []);
+    \Drupal::state()->set('config_test.on_dependency_removal_called', []);
+
+    // Do a dry run using
+    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
+    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('module', ['node']);
+    $config_entity_ids = [
+      'update' => [],
+      'delete' => [],
+      'unchanged' => [],
+    ];
+    foreach ($config_entities as $type => $config_entities_by_type) {
+      foreach ($config_entities_by_type as $config_entity) {
+        $config_entity_ids[$type][] = $config_entity->id();
+      }
+    }
+    $expected = [
+      'update' => [$entity_1->id(), $entity_2->id()],
+      'delete' => [$entity_4->id()],
+      'unchanged' => [$entity_3->id()],
+    ];
+    $this->assertSame($expected, $config_entity_ids);
+
+    $called = \Drupal::state()->get('config_test.on_dependency_removal_called', []);
+    $this->assertFalse(in_array($entity_3->id(), $called), 'ConfigEntityInterface::onDependencyRemoval() is not called for entity 3.');
+    $this->assertSame([$entity_1->id(), $entity_4->id(), $entity_2->id()], $called, 'The most dependent entities have ConfigEntityInterface::onDependencyRemoval() called first.');
+
+    // Perform a module rebuild so we can know where the node module is located
+    // and uninstall it.
+    // @todo Remove as part of https://www.drupal.org/node/2186491
+    system_rebuild_module_data();
+    // Perform the uninstall.
+    $config_manager->uninstall('module', 'node');
+
+    // Test that expected actions have been performed.
+    $entity_1 = $storage->load($entity_1->id());
+    $this->assertTrue($entity_1, 'Entity 1 not deleted');
+    $this->assertSame($entity_1->getThirdPartySettings('node'), [], 'Entity 1 third party settings updated.');
+    $entity_2 = $storage->load($entity_2->id());
+    $this->assertTrue($entity_2, 'Entity 2 not deleted');
+    $this->assertSame($entity_2->getThirdPartySettings('node'), [], 'Entity 2 third party settings updated.');
+    $this->assertSame($entity_2->calculateDependencies()->getDependencies()['config'], [$entity_1->getConfigDependencyName()], 'Entity 2 still depends on entity 1.');
+    $entity_3 = $storage->load($entity_3->id());
+    $this->assertTrue($entity_3, 'Entity 3 not deleted');
+    $this->assertSame($entity_3->calculateDependencies()->getDependencies()['config'], [$entity_2->getConfigDependencyName()], 'Entity 3 still depends on entity 2.');
+    $this->assertFalse($storage->load($entity_4->id()), 'Entity 4 deleted');
+  }
+
+  /**
+   * Tests deleting a configuration entity and dependency management.
+   */
+  public function testConfigEntityDelete() {
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
+    $storage = $this->container->get('entity.manager')->getStorage('config_test');
+    // Test dependencies between configuration entities.
+    $entity1 = $storage->create(
+      [
+        'id' => 'entity1'
+      ]
+    );
+    $entity1->save();
+    $entity2 = $storage->create(
+      [
+        'id' => 'entity2',
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity1->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity2->save();
+
+    // Do a dry run using
+    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
+    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
+    $this->assertEqual($entity2->uuid(), reset($config_entities['delete'])->uuid(), 'Entity 2 will be deleted.');
+    $this->assertTrue(empty($config_entities['update']), 'No dependent configuration entities will be updated.');
+    $this->assertTrue(empty($config_entities['unchanged']), 'No dependent configuration entities will be unchanged.');
+
+    // Test that doing a delete of entity1 deletes entity2 since it is dependent
+    // on entity1.
+    $entity1->delete();
+    $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
+    $this->assertFalse($storage->load('entity2'), 'Entity 2 deleted');
+
+    // Set a more complicated test where dependencies will be fixed.
+    \Drupal::state()->set('config_test.fix_dependencies', [$entity1->getConfigDependencyName()]);
+
+    // Entity1 will be deleted by the test.
+    $entity1 = $storage->create(
+      [
+        'id' => 'entity1',
+      ]
+    );
+    $entity1->save();
+
+    // Entity2 has a dependency on Entity1 but it can be fixed because
+    // \Drupal\config_test\Entity::onDependencyRemoval() will remove the
+    // dependency before config entities are deleted.
+    $entity2 = $storage->create(
+      [
+        'id' => 'entity2',
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity1->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity2->save();
+
+    // Entity3 will be unchanged because it is dependent on Entity2 which can
+    // be fixed.
+    $entity3 = $storage->create(
+      [
+        'id' => 'entity3',
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity2->getConfigDependencyName()],
+          ],
+        ],
+      ]
+    );
+    $entity3->save();
+
+    // Do a dry run using
+    // \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval().
+    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('config', [$entity1->getConfigDependencyName()]);
+    $this->assertTrue(empty($config_entities['delete']), 'No dependent configuration entities will be deleted.');
+    $this->assertEqual($entity2->uuid(), reset($config_entities['update'])->uuid(), 'Entity 2 will be updated.');
+    $this->assertEqual($entity3->uuid(), reset($config_entities['unchanged'])->uuid(), 'Entity 3 is not changed.');
+
+    // Perform the uninstall.
+    $entity1->delete();
+
+    // Test that expected actions have been performed.
+    $this->assertFalse($storage->load('entity1'), 'Entity 1 deleted');
+    $entity2 = $storage->load('entity2');
+    $this->assertTrue($entity2, 'Entity 2 not deleted');
+    $this->assertEqual($entity2->calculateDependencies()->getDependencies()['config'], [], 'Entity 2 dependencies updated to remove dependency on Entity1.');
+    $entity3 = $storage->load('entity3');
+    $this->assertTrue($entity3, 'Entity 3 not deleted');
+    $this->assertEqual($entity3->calculateDependencies()->getDependencies()['config'], [$entity2->getConfigDependencyName()], 'Entity 3 still depends on Entity 2.');
+  }
+
+  /**
+   * Tests getConfigEntitiesToChangeOnDependencyRemoval() with content entities.
+   *
+   * At the moment there is no runtime code that calculates configuration
+   * dependencies on content entity delete because this calculation is expensive
+   * and all content dependencies are soft. This test ensures that the code
+   * works for content entities.
+   *
+   * @see \Drupal\Core\Config\ConfigManager::getConfigEntitiesToChangeOnDependencyRemoval()
+   */
+  public function testContentEntityDelete() {
+    $this->installEntitySchema('entity_test');
+    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+    $config_manager = \Drupal::service('config.manager');
+
+    $content_entity = EntityTest::create();
+    $content_entity->save();
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $storage */
+    $storage = $this->container->get('entity.manager')->getStorage('config_test');
+    $entity1 = $storage->create(
+      [
+        'id' => 'entity1',
+        'dependencies' => [
+          'enforced' => [
+            'content' => [$content_entity->getConfigDependencyName()]
+          ],
+        ],
+      ]
+    );
+    $entity1->save();
+    $entity2 = $storage->create(
+      [
+        'id' => 'entity2',
+        'dependencies' => [
+          'enforced' => [
+            'config' => [$entity1->getConfigDependencyName()]
+          ],
+        ],
+      ]
+    );
+    $entity2->save();
+
+    // Create a configuration entity that is not in the dependency chain.
+    $entity3 = $storage->create(['id' => 'entity3']);
+    $entity3->save();
+
+    $config_entities = $config_manager->getConfigEntitiesToChangeOnDependencyRemoval('content', [$content_entity->getConfigDependencyName()]);
+    $this->assertEqual($entity1->uuid(), $config_entities['delete'][1]->uuid(), 'Entity 1 will be deleted.');
+    $this->assertEqual($entity2->uuid(), $config_entities['delete'][0]->uuid(), 'Entity 2 will be deleted.');
+    $this->assertTrue(empty($config_entities['update']), 'No dependencies of the content entity will be updated.');
+    $this->assertTrue(empty($config_entities['unchanged']), 'No dependencies of the content entity will be unchanged.');
+  }
+
+  /**
+   * Gets a list of identifiers from an array of configuration entities.
+   *
+   * @param \Drupal\Core\Config\Entity\ConfigEntityInterface[] $dependents
+   *   An array of configuration entities.
+   *
+   * @return array
+   *   An array with values of entity_type_id:ID
+   */
+  protected function getDependentIds(array $dependents) {
+    $dependent_ids = [];
+    foreach ($dependents as $dependent) {
+      $dependent_ids[] = $dependent->getEntityTypeId() . ':' . $dependent->id();
+    }
+    return $dependent_ids;
+  }
+
+}