3 namespace Drupal\KernelTests\Core\Config;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Component\Utility\SafeMarkup;
7 use Drupal\Core\Config\ConfigImporter;
8 use Drupal\Core\Config\ConfigImporterException;
9 use Drupal\Core\Config\StorageComparer;
10 use Drupal\KernelTests\KernelTestBase;
13 * Tests importing configuration from files into active configuration.
17 class ConfigImporterTest extends KernelTestBase {
20 * Config Importer object used for testing.
22 * @var \Drupal\Core\Config\ConfigImporter
24 protected $configImporter;
31 public static $modules = ['config_test', 'system', 'config_import_test'];
33 protected function setUp() {
36 $this->installConfig(['config_test']);
37 // Installing config_test's default configuration pollutes the global
38 // variable being used for recording hook invocations by this test already,
39 // so it has to be cleared out manually.
40 unset($GLOBALS['hook_config_test']);
42 $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.sync'));
44 // Set up the ConfigImporter object for testing.
45 $storage_comparer = new StorageComparer(
46 $this->container->get('config.storage.sync'),
47 $this->container->get('config.storage'),
48 $this->container->get('config.manager')
50 $this->configImporter = new ConfigImporter(
51 $storage_comparer->createChangelist(),
52 $this->container->get('event_dispatcher'),
53 $this->container->get('config.manager'),
54 $this->container->get('lock'),
55 $this->container->get('config.typed'),
56 $this->container->get('module_handler'),
57 $this->container->get('module_installer'),
58 $this->container->get('theme_handler'),
59 $this->container->get('string_translation')
64 * Tests omission of module APIs for bare configuration operations.
66 public function testNoImport() {
67 $dynamic_name = 'config_test.dynamic.dotted.default';
69 // Verify the default configuration values exist.
70 $config = $this->config($dynamic_name);
71 $this->assertIdentical($config->get('id'), 'dotted.default');
73 // Verify that a bare $this->config() does not involve module APIs.
74 $this->assertFalse(isset($GLOBALS['hook_config_test']));
78 * Tests that trying to import from an empty sync configuration directory
81 public function testEmptyImportFails() {
83 $this->container->get('config.storage.sync')->deleteAll();
84 $this->configImporter->reset()->import();
85 $this->fail('ConfigImporterException thrown, successfully stopping an empty import.');
87 catch (ConfigImporterException $e) {
88 $this->pass('ConfigImporterException thrown, successfully stopping an empty import.');
93 * Tests verification of site UUID before importing configuration.
95 public function testSiteUuidValidate() {
96 $sync = \Drupal::service('config.storage.sync');
97 // Create updated configuration object.
98 $config_data = $this->config('system.site')->get();
99 // Generate a new site UUID.
100 $config_data['uuid'] = \Drupal::service('uuid')->generate();
101 $sync->write('system.site', $config_data);
103 $this->configImporter->reset()->import();
104 $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to mis-matching site UUID.');
106 catch (ConfigImporterException $e) {
107 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
108 $error_log = $this->configImporter->getErrors();
109 $expected = ['Site UUID in source storage does not match the target storage.'];
110 $this->assertEqual($expected, $error_log);
115 * Tests deletion of configuration during import.
117 public function testDeleted() {
118 $dynamic_name = 'config_test.dynamic.dotted.default';
119 $storage = $this->container->get('config.storage');
120 $sync = $this->container->get('config.storage.sync');
122 // Verify the default configuration values exist.
123 $config = $this->config($dynamic_name);
124 $this->assertIdentical($config->get('id'), 'dotted.default');
126 // Delete the file from the sync directory.
127 $sync->delete($dynamic_name);
130 $this->configImporter->reset()->import();
132 // Verify the file has been removed.
133 $this->assertIdentical($storage->read($dynamic_name), FALSE);
135 $config = $this->config($dynamic_name);
136 $this->assertIdentical($config->get('id'), NULL);
138 // Verify that appropriate module API hooks have been invoked.
139 $this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
140 $this->assertFalse(isset($GLOBALS['hook_config_test']['presave']));
141 $this->assertFalse(isset($GLOBALS['hook_config_test']['insert']));
142 $this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
143 $this->assertTrue(isset($GLOBALS['hook_config_test']['predelete']));
144 $this->assertTrue(isset($GLOBALS['hook_config_test']['delete']));
146 $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
147 $logs = $this->configImporter->getErrors();
148 $this->assertEqual(count($logs), 0);
152 * Tests creation of configuration during import.
154 public function testNew() {
155 $dynamic_name = 'config_test.dynamic.new';
156 $storage = $this->container->get('config.storage');
157 $sync = $this->container->get('config.storage.sync');
159 // Verify the configuration to create does not exist yet.
160 $this->assertIdentical($storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
162 // Create new config entity.
163 $original_dynamic_data = [
164 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651',
165 'langcode' => \Drupal::languageManager()->getDefaultLanguage()->getId(),
167 'dependencies' => [],
174 'protected_property' => '',
176 $sync->write($dynamic_name, $original_dynamic_data);
178 $this->assertIdentical($sync->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
181 $this->configImporter->reset()->import();
183 // Verify the values appeared.
184 $config = $this->config($dynamic_name);
185 $this->assertIdentical($config->get('label'), $original_dynamic_data['label']);
187 // Verify that appropriate module API hooks have been invoked.
188 $this->assertFalse(isset($GLOBALS['hook_config_test']['load']));
189 $this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
190 $this->assertTrue(isset($GLOBALS['hook_config_test']['insert']));
191 $this->assertFalse(isset($GLOBALS['hook_config_test']['update']));
192 $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
193 $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
195 // Verify that hook_config_import_steps_alter() can add steps to
196 // configuration synchronization.
197 $this->assertTrue(isset($GLOBALS['hook_config_test']['config_import_steps_alter']));
199 // Verify that there is nothing more to import.
200 $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
201 $logs = $this->configImporter->getErrors();
202 $this->assertEqual(count($logs), 0);
206 * Tests that secondary writes are overwritten.
208 public function testSecondaryWritePrimaryFirst() {
209 $name_primary = 'config_test.dynamic.primary';
210 $name_secondary = 'config_test.dynamic.secondary';
211 $sync = $this->container->get('config.storage.sync');
212 $uuid = $this->container->get('uuid');
216 'label' => 'Primary',
218 'uuid' => $uuid->generate(),
220 $sync->write($name_primary, $values_primary);
221 $values_secondary = [
223 'label' => 'Secondary Sync',
225 'uuid' => $uuid->generate(),
226 // Add a dependency on primary, to ensure that is synced first.
228 'config' => [$name_primary],
231 $sync->write($name_secondary, $values_secondary);
234 $this->configImporter->reset()->import();
236 $entity_storage = \Drupal::entityManager()->getStorage('config_test');
237 $primary = $entity_storage->load('primary');
238 $this->assertEqual($primary->id(), 'primary');
239 $this->assertEqual($primary->uuid(), $values_primary['uuid']);
240 $this->assertEqual($primary->label(), $values_primary['label']);
241 $secondary = $entity_storage->load('secondary');
242 $this->assertEqual($secondary->id(), 'secondary');
243 $this->assertEqual($secondary->uuid(), $values_secondary['uuid']);
244 $this->assertEqual($secondary->label(), $values_secondary['label']);
246 $logs = $this->configImporter->getErrors();
247 $this->assertEqual(count($logs), 1);
248 $this->assertEqual($logs[0], SafeMarkup::format('Deleted and replaced configuration entity "@name"', ['@name' => $name_secondary]));
252 * Tests that secondary writes are overwritten.
254 public function testSecondaryWriteSecondaryFirst() {
255 $name_primary = 'config_test.dynamic.primary';
256 $name_secondary = 'config_test.dynamic.secondary';
257 $sync = $this->container->get('config.storage.sync');
258 $uuid = $this->container->get('uuid');
262 'label' => 'Primary',
264 'uuid' => $uuid->generate(),
265 // Add a dependency on secondary, so that is synced first.
267 'config' => [$name_secondary],
270 $sync->write($name_primary, $values_primary);
271 $values_secondary = [
273 'label' => 'Secondary Sync',
275 'uuid' => $uuid->generate(),
277 $sync->write($name_secondary, $values_secondary);
280 $this->configImporter->reset()->import();
282 $entity_storage = \Drupal::entityManager()->getStorage('config_test');
283 $primary = $entity_storage->load('primary');
284 $this->assertEqual($primary->id(), 'primary');
285 $this->assertEqual($primary->uuid(), $values_primary['uuid']);
286 $this->assertEqual($primary->label(), $values_primary['label']);
287 $secondary = $entity_storage->load('secondary');
288 $this->assertEqual($secondary->id(), 'secondary');
289 $this->assertEqual($secondary->uuid(), $values_secondary['uuid']);
290 $this->assertEqual($secondary->label(), $values_secondary['label']);
292 $logs = $this->configImporter->getErrors();
293 $this->assertEqual(count($logs), 1);
294 $this->assertEqual($logs[0], Html::escape("Unexpected error during import with operation create for $name_primary: 'config_test' entity with ID 'secondary' already exists."));
298 * Tests that secondary updates for deleted files work as expected.
300 public function testSecondaryUpdateDeletedDeleterFirst() {
301 $name_deleter = 'config_test.dynamic.deleter';
302 $name_deletee = 'config_test.dynamic.deletee';
303 $name_other = 'config_test.dynamic.other';
304 $storage = $this->container->get('config.storage');
305 $sync = $this->container->get('config.storage.sync');
306 $uuid = $this->container->get('uuid');
310 'label' => 'Deleter',
312 'uuid' => $uuid->generate(),
314 $storage->write($name_deleter, $values_deleter);
315 $values_deleter['label'] = 'Updated Deleter';
316 $sync->write($name_deleter, $values_deleter);
319 'label' => 'Deletee',
321 'uuid' => $uuid->generate(),
322 // Add a dependency on deleter, to make sure that is synced first.
324 'config' => [$name_deleter],
327 $storage->write($name_deletee, $values_deletee);
328 $values_deletee['label'] = 'Updated Deletee';
329 $sync->write($name_deletee, $values_deletee);
331 // Ensure that import will continue after the error.
336 'uuid' => $uuid->generate(),
337 // Add a dependency on deleter, to make sure that is synced first. This
338 // will also be synced after the deletee due to alphabetical ordering.
340 'config' => [$name_deleter],
343 $storage->write($name_other, $values_other);
344 $values_other['label'] = 'Updated other';
345 $sync->write($name_other, $values_other);
347 // Check update changelist order.
348 $updates = $this->configImporter->reset()->getStorageComparer()->getChangelist('update');
354 $this->assertSame($expected, $updates);
357 $this->configImporter->import();
359 $entity_storage = \Drupal::entityManager()->getStorage('config_test');
360 $deleter = $entity_storage->load('deleter');
361 $this->assertEqual($deleter->id(), 'deleter');
362 $this->assertEqual($deleter->uuid(), $values_deleter['uuid']);
363 $this->assertEqual($deleter->label(), $values_deleter['label']);
365 // The deletee was deleted in
366 // \Drupal\config_test\Entity\ConfigTest::postSave().
367 $this->assertFalse($entity_storage->load('deletee'));
369 $other = $entity_storage->load('other');
370 $this->assertEqual($other->id(), 'other');
371 $this->assertEqual($other->uuid(), $values_other['uuid']);
372 $this->assertEqual($other->label(), $values_other['label']);
374 $logs = $this->configImporter->getErrors();
375 $this->assertEqual(count($logs), 1);
376 $this->assertEqual($logs[0], SafeMarkup::format('Update target "@name" is missing.', ['@name' => $name_deletee]));
380 * Tests that secondary updates for deleted files work as expected.
382 * This test is completely hypothetical since we only support full
383 * configuration tree imports. Therefore, any configuration updates that cause
384 * secondary deletes should be reflected already in the staged configuration.
386 public function testSecondaryUpdateDeletedDeleteeFirst() {
387 $name_deleter = 'config_test.dynamic.deleter';
388 $name_deletee = 'config_test.dynamic.deletee';
389 $storage = $this->container->get('config.storage');
390 $sync = $this->container->get('config.storage.sync');
391 $uuid = $this->container->get('uuid');
395 'label' => 'Deleter',
397 'uuid' => $uuid->generate(),
398 // Add a dependency on deletee, to make sure that is synced first.
400 'config' => [$name_deletee],
403 $storage->write($name_deleter, $values_deleter);
404 $values_deleter['label'] = 'Updated Deleter';
405 $sync->write($name_deleter, $values_deleter);
408 'label' => 'Deletee',
410 'uuid' => $uuid->generate(),
412 $storage->write($name_deletee, $values_deletee);
413 $values_deletee['label'] = 'Updated Deletee';
414 $sync->write($name_deletee, $values_deletee);
417 $this->configImporter->reset()->import();
419 $entity_storage = \Drupal::entityManager()->getStorage('config_test');
420 // Both entities are deleted. ConfigTest::postSave() causes updates of the
421 // deleter entity to delete the deletee entity. Since the deleter depends on
422 // the deletee, removing the deletee causes the deleter to be removed.
423 $this->assertFalse($entity_storage->load('deleter'));
424 $this->assertFalse($entity_storage->load('deletee'));
425 $logs = $this->configImporter->getErrors();
426 $this->assertEqual(count($logs), 0);
430 * Tests that secondary deletes for deleted files work as expected.
432 public function testSecondaryDeletedDeleteeSecond() {
433 $name_deleter = 'config_test.dynamic.deleter';
434 $name_deletee = 'config_test.dynamic.deletee';
435 $storage = $this->container->get('config.storage');
437 $uuid = $this->container->get('uuid');
441 'label' => 'Deleter',
443 'uuid' => $uuid->generate(),
444 // Add a dependency on deletee, to make sure this delete is synced first.
446 'config' => [$name_deletee],
449 $storage->write($name_deleter, $values_deleter);
452 'label' => 'Deletee',
454 'uuid' => $uuid->generate(),
456 $storage->write($name_deletee, $values_deletee);
459 $this->configImporter->reset()->import();
461 $entity_storage = \Drupal::entityManager()->getStorage('config_test');
462 $this->assertFalse($entity_storage->load('deleter'));
463 $this->assertFalse($entity_storage->load('deletee'));
464 // The deletee entity does not exist as the delete worked and although the
465 // delete occurred in \Drupal\config_test\Entity\ConfigTest::postDelete()
466 // this does not matter.
467 $logs = $this->configImporter->getErrors();
468 $this->assertEqual(count($logs), 0);
472 * Tests updating of configuration during import.
474 public function testUpdated() {
475 $name = 'config_test.system';
476 $dynamic_name = 'config_test.dynamic.dotted.default';
477 $storage = $this->container->get('config.storage');
478 $sync = $this->container->get('config.storage.sync');
480 // Verify that the configuration objects to import exist.
481 $this->assertIdentical($storage->exists($name), TRUE, $name . ' found.');
482 $this->assertIdentical($storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
484 // Replace the file content of the existing configuration objects in the
486 $original_name_data = [
489 $sync->write($name, $original_name_data);
490 $original_dynamic_data = $storage->read($dynamic_name);
491 $original_dynamic_data['label'] = 'Updated';
492 $sync->write($dynamic_name, $original_dynamic_data);
494 // Verify the active configuration still returns the default values.
495 $config = $this->config($name);
496 $this->assertIdentical($config->get('foo'), 'bar');
497 $config = $this->config($dynamic_name);
498 $this->assertIdentical($config->get('label'), 'Default');
501 $this->configImporter->reset()->import();
503 // Verify the values were updated.
504 \Drupal::configFactory()->reset($name);
505 $config = $this->config($name);
506 $this->assertIdentical($config->get('foo'), 'beer');
507 $config = $this->config($dynamic_name);
508 $this->assertIdentical($config->get('label'), 'Updated');
510 // Verify that the original file content is still the same.
511 $this->assertIdentical($sync->read($name), $original_name_data);
512 $this->assertIdentical($sync->read($dynamic_name), $original_dynamic_data);
514 // Verify that appropriate module API hooks have been invoked.
515 $this->assertTrue(isset($GLOBALS['hook_config_test']['load']));
516 $this->assertTrue(isset($GLOBALS['hook_config_test']['presave']));
517 $this->assertFalse(isset($GLOBALS['hook_config_test']['insert']));
518 $this->assertTrue(isset($GLOBALS['hook_config_test']['update']));
519 $this->assertFalse(isset($GLOBALS['hook_config_test']['predelete']));
520 $this->assertFalse(isset($GLOBALS['hook_config_test']['delete']));
522 // Verify that there is nothing more to import.
523 $this->assertFalse($this->configImporter->hasUnprocessedConfigurationChanges());
524 $logs = $this->configImporter->getErrors();
525 $this->assertEqual(count($logs), 0);
529 * Tests the isInstallable method()
531 public function testIsInstallable() {
532 $config_name = 'config_test.dynamic.isinstallable';
533 $this->assertFalse($this->container->get('config.storage')->exists($config_name));
534 \Drupal::state()->set('config_test.isinstallable', TRUE);
535 $this->installConfig(['config_test']);
536 $this->assertTrue($this->container->get('config.storage')->exists($config_name));
540 * Tests dependency validation during configuration import.
542 * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
543 * @see \Drupal\Core\Config\ConfigImporter::createExtensionChangelist()
545 public function testUnmetDependency() {
546 $storage = $this->container->get('config.storage');
547 $sync = $this->container->get('config.storage.sync');
549 // Test an unknown configuration owner.
550 $sync->write('unknown.config', ['test' => 'test']);
552 // Make a config entity have unmet dependencies.
553 $config_entity_data = $sync->read('config_test.dynamic.dotted.default');
554 $config_entity_data['dependencies'] = ['module' => ['unknown']];
555 $sync->write('config_test.dynamic.dotted.module', $config_entity_data);
556 $config_entity_data['dependencies'] = ['theme' => ['unknown']];
557 $sync->write('config_test.dynamic.dotted.theme', $config_entity_data);
558 $config_entity_data['dependencies'] = ['config' => ['unknown']];
559 $sync->write('config_test.dynamic.dotted.config', $config_entity_data);
561 // Make an active config depend on something that is missing in sync.
562 // The whole configuration needs to be consistent, not only the updated one.
563 $config_entity_data['dependencies'] = [];
564 $storage->write('config_test.dynamic.dotted.deleted', $config_entity_data);
565 $config_entity_data['dependencies'] = ['config' => ['config_test.dynamic.dotted.deleted']];
566 $storage->write('config_test.dynamic.dotted.existing', $config_entity_data);
567 $sync->write('config_test.dynamic.dotted.existing', $config_entity_data);
569 $extensions = $sync->read('core.extension');
570 // Add a module and a theme that do not exist.
571 $extensions['module']['unknown_module'] = 0;
572 $extensions['theme']['unknown_theme'] = 0;
573 // Add a module and a theme that depend on uninstalled extensions.
574 $extensions['module']['book'] = 0;
575 $extensions['theme']['bartik'] = 0;
577 $sync->write('core.extension', $extensions);
579 $this->configImporter->reset()->import();
580 $this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
582 catch (ConfigImporterException $e) {
583 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
584 $error_log = $this->configImporter->getErrors();
586 'Unable to install the <em class="placeholder">unknown_module</em> module since it does not exist.',
587 'Unable to install the <em class="placeholder">Book</em> module since it requires the <em class="placeholder">Node, Text, Field, Filter, User</em> modules.',
588 'Unable to install the <em class="placeholder">unknown_theme</em> theme since it does not exist.',
589 'Unable to install the <em class="placeholder">Bartik</em> theme since it requires the <em class="placeholder">Classy</em> theme.',
590 'Configuration <em class="placeholder">config_test.dynamic.dotted.config</em> depends on the <em class="placeholder">unknown</em> configuration that will not exist after import.',
591 'Configuration <em class="placeholder">config_test.dynamic.dotted.existing</em> depends on the <em class="placeholder">config_test.dynamic.dotted.deleted</em> configuration that will not exist after import.',
592 'Configuration <em class="placeholder">config_test.dynamic.dotted.module</em> depends on the <em class="placeholder">unknown</em> module that will not be installed after import.',
593 'Configuration <em class="placeholder">config_test.dynamic.dotted.theme</em> depends on the <em class="placeholder">unknown</em> theme that will not be installed after import.',
594 'Configuration <em class="placeholder">unknown.config</em> depends on the <em class="placeholder">unknown</em> extension that will not be installed after import.',
596 foreach ($expected as $expected_message) {
597 $this->assertTrue(in_array($expected_message, $error_log), $expected_message);
601 // Make a config entity have mulitple unmet dependencies.
602 $config_entity_data = $sync->read('config_test.dynamic.dotted.default');
603 $config_entity_data['dependencies'] = ['module' => ['unknown', 'dblog']];
604 $sync->write('config_test.dynamic.dotted.module', $config_entity_data);
605 $config_entity_data['dependencies'] = ['theme' => ['unknown', 'seven']];
606 $sync->write('config_test.dynamic.dotted.theme', $config_entity_data);
607 $config_entity_data['dependencies'] = ['config' => ['unknown', 'unknown2']];
608 $sync->write('config_test.dynamic.dotted.config', $config_entity_data);
610 $this->configImporter->reset()->import();
611 $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing dependencies.');
613 catch (ConfigImporterException $e) {
614 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
615 $error_log = $this->configImporter->getErrors();
617 'Configuration <em class="placeholder">config_test.dynamic.dotted.config</em> depends on configuration (<em class="placeholder">unknown, unknown2</em>) that will not exist after import.',
618 'Configuration <em class="placeholder">config_test.dynamic.dotted.module</em> depends on modules (<em class="placeholder">unknown, Database Logging</em>) that will not be installed after import.',
619 'Configuration <em class="placeholder">config_test.dynamic.dotted.theme</em> depends on themes (<em class="placeholder">unknown, Seven</em>) that will not be installed after import.',
621 foreach ($expected as $expected_message) {
622 $this->assertTrue(in_array($expected_message, $error_log), $expected_message);
628 * Tests missing core.extension during configuration import.
630 * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
632 public function testMissingCoreExtension() {
633 $sync = $this->container->get('config.storage.sync');
634 $sync->delete('core.extension');
636 $this->configImporter->reset()->import();
637 $this->fail('ConfigImporterException not thrown, invalid import was not stopped due to missing dependencies.');
639 catch (ConfigImporterException $e) {
640 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
641 $error_log = $this->configImporter->getErrors();
642 $this->assertEqual(['The core.extension configuration does not exist.'], $error_log);
647 * Tests install profile validation during configuration import.
649 * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
651 public function testInstallProfile() {
652 $sync = $this->container->get('config.storage.sync');
654 $extensions = $sync->read('core.extension');
655 // Add an install profile.
656 $extensions['module']['standard'] = 0;
658 $sync->write('core.extension', $extensions);
660 $this->configImporter->reset()->import();
661 $this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
663 catch (ConfigImporterException $e) {
664 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
665 $error_log = $this->configImporter->getErrors();
666 // Install profiles should not even be scanned at this point.
667 $this->assertEqual(['Unable to install the <em class="placeholder">standard</em> module since it does not exist.'], $error_log);
672 * Tests install profile validation during configuration import.
674 * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
676 public function testInstallProfileMisMatch() {
677 $sync = $this->container->get('config.storage.sync');
679 $extensions = $sync->read('core.extension');
680 // Change the install profile.
681 $extensions['profile'] = 'this_will_not_work';
682 $sync->write('core.extension', $extensions);
685 $this->configImporter->reset()->import();
686 $this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
688 catch (ConfigImporterException $e) {
689 $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
690 $error_log = $this->configImporter->getErrors();
691 // Install profiles can not be changed. Note that KernelTestBase currently
692 // does not use an install profile. This situation should be impossible
693 // to get in but site's can removed the install profile setting from
694 // settings.php so the test is valid.
695 $this->assertEqual(['Cannot change the install profile from <em class="placeholder"></em> to <em class="placeholder">this_will_not_work</em> once Drupal is installed.'], $error_log);
700 * Tests config_get_config_directory().
702 public function testConfigGetConfigDirectory() {
703 global $config_directories;
704 $directory = config_get_config_directory(CONFIG_SYNC_DIRECTORY);
705 $this->assertEqual($config_directories[CONFIG_SYNC_DIRECTORY], $directory);
707 $message = 'Calling config_get_config_directory() with CONFIG_ACTIVE_DIRECTORY results in an exception.';
709 config_get_config_directory(CONFIG_ACTIVE_DIRECTORY);
710 $this->fail($message);
712 catch (\Exception $e) {
713 $this->pass($message);
718 * Tests the isSyncing flags.
720 public function testIsSyncingInHooks() {
721 $dynamic_name = 'config_test.dynamic.dotted.default';
722 $storage = $this->container->get('config.storage');
724 // Verify the default configuration values exist.
725 $config = $this->config($dynamic_name);
726 $this->assertSame('dotted.default', $config->get('id'));
728 // Delete the config so that create hooks will fire.
729 $storage->delete($dynamic_name);
730 \Drupal::state()->set('config_test.store_isSyncing', []);
731 $this->configImporter->reset()->import();
733 // The values of the syncing values should be stored in state by
734 // config_test_config_test_create().
735 $state = \Drupal::state()->get('config_test.store_isSyncing');
736 $this->assertTrue($state['global_state::create'], '\Drupal::isConfigSyncing() returns TRUE');
737 $this->assertTrue($state['entity_state::create'], 'ConfigEntity::isSyncing() returns TRUE');
738 $this->assertTrue($state['global_state::presave'], '\Drupal::isConfigSyncing() returns TRUE');
739 $this->assertTrue($state['entity_state::presave'], 'ConfigEntity::isSyncing() returns TRUE');
740 $this->assertTrue($state['global_state::insert'], '\Drupal::isConfigSyncing() returns TRUE');
741 $this->assertTrue($state['entity_state::insert'], 'ConfigEntity::isSyncing() returns TRUE');
743 // Cause a config update so update hooks will fire.
744 $config = $this->config($dynamic_name);
745 $config->set('label', 'A new name')->save();
746 \Drupal::state()->set('config_test.store_isSyncing', []);
747 $this->configImporter->reset()->import();
749 // The values of the syncing values should be stored in state by
750 // config_test_config_test_create().
751 $state = \Drupal::state()->get('config_test.store_isSyncing');
752 $this->assertTrue($state['global_state::presave'], '\Drupal::isConfigSyncing() returns TRUE');
753 $this->assertTrue($state['entity_state::presave'], 'ConfigEntity::isSyncing() returns TRUE');
754 $this->assertTrue($state['global_state::update'], '\Drupal::isConfigSyncing() returns TRUE');
755 $this->assertTrue($state['entity_state::update'], 'ConfigEntity::isSyncing() returns TRUE');
757 // Cause a config delete so delete hooks will fire.
758 $sync = $this->container->get('config.storage.sync');
759 $sync->delete($dynamic_name);
760 \Drupal::state()->set('config_test.store_isSyncing', []);
761 $this->configImporter->reset()->import();
763 // The values of the syncing values should be stored in state by
764 // config_test_config_test_create().
765 $state = \Drupal::state()->get('config_test.store_isSyncing');
766 $this->assertTrue($state['global_state::predelete'], '\Drupal::isConfigSyncing() returns TRUE');
767 $this->assertTrue($state['entity_state::predelete'], 'ConfigEntity::isSyncing() returns TRUE');
768 $this->assertTrue($state['global_state::delete'], '\Drupal::isConfigSyncing() returns TRUE');
769 $this->assertTrue($state['entity_state::delete'], 'ConfigEntity::isSyncing() returns TRUE');
773 * Tests that the isConfigSyncing flag is cleanup after an invalid step.
775 public function testInvalidStep() {
776 $this->assertFalse(\Drupal::isConfigSyncing(), 'Before an import \Drupal::isConfigSyncing() returns FALSE');
779 $this->configImporter->doSyncStep('a_non_existent_step', $context);
780 $this->fail('Expected \InvalidArgumentException thrown');
782 catch (\InvalidArgumentException $e) {
783 $this->pass('Expected \InvalidArgumentException thrown');
785 $this->assertFalse(\Drupal::isConfigSyncing(), 'After an invalid step \Drupal::isConfigSyncing() returns FALSE');
789 * Tests that the isConfigSyncing flag is set correctly during a custom step.
791 public function testCustomStep() {
792 $this->assertFalse(\Drupal::isConfigSyncing(), 'Before an import \Drupal::isConfigSyncing() returns FALSE');
794 $this->configImporter->doSyncStep([self::class, 'customStep'], $context);
795 $this->assertTrue($context['is_syncing'], 'Inside a custom step \Drupal::isConfigSyncing() returns TRUE');
796 $this->assertFalse(\Drupal::isConfigSyncing(), 'After an valid custom step \Drupal::isConfigSyncing() returns FALSE');
800 * Helper meothd to test custom config installer steps.
802 * @param array $context
804 * @param \Drupal\Core\Config\ConfigImporter $importer
805 * The config importer.
807 public static function customStep(array &$context, ConfigImporter $importer) {
808 $context['is_syncing'] = \Drupal::isConfigSyncing();