ConfigSchemaTest.php 23.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
<?php

/**
 * @file
 * Contains \Drupal\config\Tests\ConfigSchemaTest.
 */

namespace Drupal\config\Tests;

10
11
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
12
use Drupal\Core\Config\Schema\ConfigSchemaAlterException;
13
14
use Drupal\Core\TypedData\Type\IntegerInterface;
use Drupal\Core\TypedData\Type\StringInterface;
15
use Drupal\simpletest\KernelTestBase;
16
17
18

/**
 * Tests schema for configuration objects.
19
20
 *
 * @group config
21
 */
22
class ConfigSchemaTest extends KernelTestBase {
23
24
25
26
27
28

  /**
   * Modules to enable.
   *
   * @var array
   */
29
  public static $modules = array('system', 'language', 'locale', 'field', 'image', 'config_test', 'config_schema_test');
30

31
32
33
  /**
   * {@inheritdoc}
   */
34
  protected function setUp() {
35
    parent::setUp();
36
    $this->installConfig(array('system', 'image', 'config_schema_test'));
37
38
39
40
41
42
  }

  /**
   * Tests the basic metadata retrieval layer.
   */
  function testSchemaMapping() {
43
    // Nonexistent configuration key will have Undefined as metadata.
44
45
    $this->assertIdentical(FALSE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.no_such_key'));
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.no_such_key');
46
    $expected = array();
47
48
    $expected['label'] = 'Undefined';
    $expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
49
50
    $expected['type'] = 'undefined';
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
51
52
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for nonexistent configuration.');

53
    // Configuration file without schema will return Undefined as well.
54
55
    $this->assertIdentical(FALSE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.noschema'));
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.noschema');
56
57
58
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with no schema.');

    // Configuration file with only some schema.
59
60
    $this->assertIdentical(TRUE, \Drupal::service('config.typed')->hasConfigSchema('config_schema_test.someschema'));
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema');
61
62
63
64
65
    $expected = array();
    $expected['label'] = 'Schema test data';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
    $expected['mapping']['testitem'] = array('label' => 'Test item');
    $expected['mapping']['testlist'] = array('label' => 'Test list');
66
67
    $expected['type'] = 'config_schema_test.someschema';
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
68
69
70
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for configuration with only some schema.');

    // Check type detection on elements with undefined types.
71
    $config = \Drupal::service('config.typed')->get('config_schema_test.someschema');
72
    $definition = $config->get('testitem')->getDataDefinition()->toArray();
73
74
    $expected = array();
    $expected['label'] = 'Test item';
75
    $expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
76
    $expected['type'] = 'undefined';
77
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
78
    $this->assertEqual($definition, $expected, 'Automatic type detected for a scalar is undefined.');
79
    $definition = $config->get('testlist')->getDataDefinition()->toArray();
80
81
    $expected = array();
    $expected['label'] = 'Test list';
82
    $expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
83
    $expected['type'] = 'undefined';
84
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
85
    $this->assertEqual($definition, $expected, 'Automatic type detected for a list is undefined.');
86
    $definition = $config->get('testnoschema')->getDataDefinition()->toArray();
87
88
89
90
91
92
    $expected = array();
    $expected['label'] = 'Undefined';
    $expected['class'] = '\Drupal\Core\Config\Schema\Undefined';
    $expected['type'] = 'undefined';
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
    $this->assertEqual($definition, $expected, 'Automatic type detected for an undefined integer is undefined.');
93

94
    // Simple case, straight metadata.
95
    $definition = \Drupal::service('config.typed')->getDefinition('system.maintenance');
96
97
98
99
100
101
102
    $expected = array();
    $expected['label'] = 'Maintenance mode';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
    $expected['mapping']['message'] = array(
      'label' =>  'Message to display when in maintenance mode',
      'type' => 'text',
    );
103
104
105
106
    $expected['mapping']['langcode'] = array(
      'label' => 'Default language',
      'type' => 'string',
    );
107
108
    $expected['type'] = 'system.maintenance';
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
109
110
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');

111
112
113
114
115
    // Mixed schema with ignore elements.
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.ignore');
    $expected = array();
    $expected['label'] = 'Ignore test';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
116
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    $expected['mapping']['label'] = array(
      'label' =>  'Label',
      'type' => 'label',
    );
    $expected['mapping']['irrelevant'] = array(
      'label' => 'Irrelevant',
      'type' => 'ignore',
    );
    $expected['mapping']['indescribable'] = array(
      'label' => 'Indescribable',
      'type' => 'ignore',
    );
    $expected['mapping']['weight'] = array(
      'label' => 'Weight',
      'type' => 'integer',
    );
133
134
    $expected['type'] = 'config_schema_test.ignore';

135
136
137
    $this->assertEqual($definition, $expected);

    // The ignore elements themselves.
138
    $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('irrelevant')->getDataDefinition()->toArray();
139
140
141
142
    $expected = array();
    $expected['type'] = 'ignore';
    $expected['label'] = 'Irrelevant';
    $expected['class'] = '\Drupal\Core\Config\Schema\Ignore';
143
    $expected['definition_class'] = '\Drupal\Core\TypedData\DataDefinition';
144
    $this->assertEqual($definition, $expected);
145
    $definition = \Drupal::service('config.typed')->get('config_schema_test.ignore')->get('indescribable')->getDataDefinition()->toArray();
146
147
148
    $expected['label'] = 'Indescribable';
    $this->assertEqual($definition, $expected);

149
    // More complex case, generic type. Metadata for image style.
150
    $definition = \Drupal::service('config.typed')->getDefinition('image.style.large');
151
152
153
    $expected = array();
    $expected['label'] = 'Image style';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
154
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
155
    $expected['mapping']['name']['type'] = 'string';
156
    $expected['mapping']['uuid']['type'] = 'string';
157
158
159
160
161
162
163
164
    $expected['mapping']['uuid']['label'] = 'UUID';
    $expected['mapping']['langcode']['type'] = 'string';
    $expected['mapping']['langcode']['label'] = 'Default language';
    $expected['mapping']['status']['type'] = 'boolean';
    $expected['mapping']['status']['label'] = 'Status';
    $expected['mapping']['dependencies']['type'] = 'config_dependencies';
    $expected['mapping']['dependencies']['label'] = 'Dependencies';
    $expected['mapping']['name']['type'] = 'string';
165
166
    $expected['mapping']['label']['type'] = 'label';
    $expected['mapping']['label']['label'] = 'Label';
167
    $expected['mapping']['effects']['type'] = 'sequence';
168
169
170
171
172
    $expected['mapping']['effects']['sequence']['type'] = 'mapping';
    $expected['mapping']['effects']['sequence']['mapping']['id']['type'] = 'string';
    $expected['mapping']['effects']['sequence']['mapping']['data']['type'] = 'image.effect.[%parent.id]';
    $expected['mapping']['effects']['sequence']['mapping']['weight']['type'] = 'integer';
    $expected['mapping']['effects']['sequence']['mapping']['uuid']['type'] = 'string';
173
174
    $expected['mapping']['third_party_settings']['type'] = 'sequence';
    $expected['mapping']['third_party_settings']['label'] = 'Third party settings';
175
    $expected['mapping']['third_party_settings']['sequence']['type'] = '[%parent.%parent.%type].third_party.[%key]';
176
    $expected['type'] = 'image.style.*';
177

178
    $this->assertEqual($definition, $expected);
179
180

    // More complex, type based on a complex one.
181
    $definition = \Drupal::service('config.typed')->getDefinition('image.effect.image_scale');
182
183
184
185
    // This should be the schema for image.effect.image_scale.
    $expected = array();
    $expected['label'] = 'Image scale';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
186
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
187
188
189
190
191
192
    $expected['mapping']['width']['type'] = 'integer';
    $expected['mapping']['width']['label'] = 'Width';
    $expected['mapping']['height']['type'] = 'integer';
    $expected['mapping']['height']['label'] = 'Height';
    $expected['mapping']['upscale']['type'] = 'boolean';
    $expected['mapping']['upscale']['label'] = 'Upscale';
193
194
    $expected['type'] = 'image.effect.image_scale';

195
196
197
198

    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.effect.image_scale');

    // Most complex case, get metadata for actual configuration element.
199
    $effects = \Drupal::service('config.typed')->get('image.style.medium')->get('effects');
200
    $definition = $effects->get('bddf0d06-42f9-4c75-a700-a33cafa25ea0')->get('data')->getDataDefinition()->toArray();
201
202
203
204
    // This should be the schema for image.effect.image_scale, reuse previous one.
    $expected['type'] =  'image.effect.image_scale';

    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for the first effect of image.style.medium');
205

206
207
208
209
210
211
212
213
214
215
216
217
218
    $test = \Drupal::service('config.typed')->get('config_test.dynamic.third_party')->get('third_party_settings.config_schema_test');
    $definition = $test->getDataDefinition()->toArray();
    $expected = array();
    $expected['type'] = 'config_test.dynamic.*.third_party.config_schema_test';
    $expected['label'] = 'Mapping';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
    $expected['mapping'] = [
      'integer' => ['type' => 'integer'],
      'string' => ['type' => 'string'],
    ];
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_test.dynamic.third_party:third_party_settings.config_schema_test');

219
    // More complex, several level deep test.
220
221
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema.somemodule.section_one.subsection');
    // This should be the schema of config_schema_test.someschema.somemodule.*.*.
222
223
224
    $expected = array();
    $expected['label'] = 'Schema multiple filesytem marker test';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
225
226
227
228
    $expected['mapping']['testid']['type'] = 'string';
    $expected['mapping']['testid']['label'] = 'ID';
    $expected['mapping']['testdescription']['type'] = 'text';
    $expected['mapping']['testdescription']['label'] = 'Description';
229
230
    $expected['type'] = 'config_schema_test.someschema.somemodule.*.*';
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
231

232
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_one.subsection');
233

234
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.someschema.somemodule.section_two.subsection');
235
    // The other file should have the same schema.
236
    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.someschema.somemodule.section_two.subsection');
237
  }
238

239
240
241
242
  /**
   * Tests metadata retrieval with several levels of %parent indirection.
   */
  function testSchemaMappingWithParents() {
243
    $config_data = \Drupal::service('config.typed')->get('config_schema_test.someschema.with_parents');
244
245
246

    // Test fetching parent one level up.
    $entry = $config_data->get('one_level');
247
    $definition = $entry->get('testitem')->getDataDefinition()->toArray();
248
    $expected = array(
249
      'type' => 'config_schema_test.someschema.with_parents.key_1',
250
251
      'label' => 'Test item nested one level',
      'class' => '\Drupal\Core\TypedData\Plugin\DataType\String',
252
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
253
254
255
256
257
    );
    $this->assertEqual($definition, $expected);

    // Test fetching parent two levels up.
    $entry = $config_data->get('two_levels');
258
    $definition = $entry->get('wrapper')->get('testitem')->getDataDefinition()->toArray();
259
    $expected = array(
260
      'type' => 'config_schema_test.someschema.with_parents.key_2',
261
262
      'label' => 'Test item nested two levels',
      'class' => '\Drupal\Core\TypedData\Plugin\DataType\String',
263
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
264
265
266
267
268
    );
    $this->assertEqual($definition, $expected);

    // Test fetching parent three levels up.
    $entry = $config_data->get('three_levels');
269
    $definition = $entry->get('wrapper_1')->get('wrapper_2')->get('testitem')->getDataDefinition()->toArray();
270
    $expected = array(
271
      'type' => 'config_schema_test.someschema.with_parents.key_3',
272
273
      'label' => 'Test item nested three levels',
      'class' => '\Drupal\Core\TypedData\Plugin\DataType\String',
274
      'definition_class' => '\Drupal\Core\TypedData\DataDefinition',
275
276
    );
    $this->assertEqual($definition, $expected);
277
278
279
280
281
282
283
  }

  /**
   * Tests metadata applied to configuration objects.
   */
  function testSchemaData() {
    // Try some simple properties.
284
    $meta = \Drupal::service('config.typed')->get('system.site');
285
    $property = $meta->get('name');
286
    $this->assertTrue($property instanceof StringInterface, 'Got the right wrapper fo the site name property.');
287
    $this->assertEqual($property->getValue(), 'Drupal', 'Got the right string value for site name data.');
288
    $definition = $property->getDataDefinition();
289
    $this->assertTrue($definition['translatable'], 'Got the right translatability setting for site name data.');
290
291

    $property = $meta->get('page')->get('front');
292
    $this->assertTrue($property instanceof StringInterface, 'Got the right wrapper fo the page.front property.');
293
    $this->assertEqual($property->getValue(), 'user/login', 'Got the right value for page.front data.');
294
    $definition = $property->getDataDefinition();
295
    $this->assertTrue(empty($definition['translatable']), 'Got the right translatability setting for page.front data.');
296
297

    // Check nested array of properties.
298
    $list = $meta->get('page')->getElements();
299
300
    $this->assertEqual(count($list), 3, 'Got a list with the right number of properties for site page data');
    $this->assertTrue(isset($list['front']) && isset($list['403']) && isset($list['404']), 'Got a list with the right properties for site page data.');
301
    $this->assertEqual($list['front']->getValue(), 'user/login', 'Got the right value for page.front data from the list.');
302

303
304
    // And test some TypedConfigInterface methods.
    $properties = $list;
305
    $this->assertTrue(count($properties) == 3 && $properties['front'] == $list['front'], 'Got the right properties for site page.');
306
    $values = $meta->get('page')->toArray();
307
    $this->assertTrue(count($values) == 3 && $values['front'] == 'user/login', 'Got the right property values for site page.');
308
309

    // Now let's try something more complex, with nested objects.
310
    $wrapper = \Drupal::service('config.typed')->get('image.style.large');
311
    $effects = $wrapper->get('effects');
312
    $this->assertTrue(count($effects->toArray()) == 1, 'Got an array with effects for image.style.large data');
313
    $uuid = key($effects->getValue());
314
315
316
317
    $effect = $effects->get($uuid)->getElements();
    $this->assertTrue(!$effect['data']->isEmpty() && $effect['id']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
    $this->assertTrue($effect['data']->get('width') instanceof IntegerInterface, 'Got the right type for the scale effect width.');
    $this->assertEqual($effect['data']->get('width')->getValue(), 480, 'Got the right value for the scale effect width.' );
318
319
  }

320
321
322
323
324
325
326
327
328
329
330
  /**
   * Test configuration value data type enforcement using schemas.
   */
  public function testConfigSaveWithSchema() {
    $untyped_values = array(
      'string' => 1,
      'empty_string' => '',
      'null_string' => NULL,
      'integer' => '100',
      'null_integer' => '',
      'boolean' => 1,
331
      // If the config schema doesn't have a type it shouldn't be casted.
332
333
334
335
336
337
338
      'no_type' => 1,
      'mapping' => array(
        'string' => 1
      ),
      'float' => '3.14',
      'null_float' => '',
      'sequence' => array (1, 0, 1),
339
      'sequence_bc' => array(1, 0, 1),
340
341
342
      // Not in schema and therefore should be left untouched.
      'not_present_in_schema' => TRUE,
      // Test a custom type.
343
344
      'config_schema_test_integer' => '1',
      'config_schema_test_integer_empty_string' => '',
345
346
347
348
349
350
351
352
353
354
    );
    $untyped_to_typed = $untyped_values;

    $typed_values = array(
      'string' => '1',
      'empty_string' => '',
      'null_string' => NULL,
      'integer' => 100,
      'null_integer' => NULL,
      'boolean' => TRUE,
355
      'no_type' => 1,
356
357
358
359
360
361
      'mapping' => array(
        'string' => '1'
      ),
      'float' => 3.14,
      'null_float' => NULL,
      'sequence' => array (TRUE, FALSE, TRUE),
362
      'sequence_bc' => array(TRUE, FALSE, TRUE),
363
      'not_present_in_schema' => TRUE,
364
365
      'config_schema_test_integer' => 1,
      'config_schema_test_integer_empty_string' => NULL,
366
367
368
    );

    // Save config which has a schema that enforces types.
369
    $this->config('config_schema_test.schema_data_types')
370
371
      ->setData($untyped_to_typed)
      ->save();
372
    $this->assertIdentical($this->config('config_schema_test.schema_data_types')->get(), $typed_values);
373
374

    // Save config which does not have a schema that enforces types.
375
    $this->config('config_schema_test.no_schema_data_types')
376
377
      ->setData($untyped_values)
      ->save();
378
    $this->assertIdentical($this->config('config_schema_test.no_schema_data_types')->get(), $untyped_values);
379
380
381
382
383
384
385

    // Ensure that configuration objects with keys marked as ignored are not
    // changed when saved. The 'config_schema_test.ignore' will have been saved
    // during the installation of configuration in the setUp method.
    $extension_path = drupal_get_path('module', 'config_schema_test');
    $install_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY);
    $original_data = $install_storage->read('config_schema_test.ignore');
386
    $this->assertIdentical($this->config('config_schema_test.ignore')->get(), $original_data);
387
388
389
390
391
392
393
394
395
396
397
  }

  /**
   * Tests fallback to a greedy wildcard.
   */
  function testSchemaFallback() {
    $definition = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something');
    // This should be the schema of config_schema_test.wildcard_fallback.*.
    $expected = array();
    $expected['label'] = 'Schema wildcard fallback test';
    $expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
398
    $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
399
400
401
402
    $expected['mapping']['testid']['type'] = 'string';
    $expected['mapping']['testid']['label'] = 'ID';
    $expected['mapping']['testdescription']['type'] = 'text';
    $expected['mapping']['testdescription']['label'] = 'Description';
403
    $expected['type'] = 'config_schema_test.wildcard_fallback.*';
404
405
406
407
408
409
410

    $this->assertEqual($definition, $expected, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');

    $definition2 = \Drupal::service('config.typed')->getDefinition('config_schema_test.wildcard_fallback.something.something');
    // This should be the schema of config_schema_test.wildcard_fallback.* as
    //well.
    $this->assertIdentical($definition, $definition2);
411
412
  }

413
414
415
416
417
418
  /**
   * Tests use of colons in schema type determination.
   *
   * @see \Drupal\Core\Config\TypedConfigManager::getFallbackName()
   */
  function testColonsInSchemaTypeDetermination() {
419
    $tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('tests')->getElements();
420
421
422
423
424
425
426
427
428
429
430
431
    $definition = $tests[0]->getDataDefinition()->toArray();
    $this->assertEqual($definition['type'], 'test.plugin_types.boolean');

    $definition = $tests[1]->getDataDefinition()->toArray();
    $this->assertEqual($definition['type'], 'test.plugin_types.boolean:*');

    $definition = $tests[2]->getDataDefinition()->toArray();
    $this->assertEqual($definition['type'], 'test.plugin_types.*');

    $definition = $tests[3]->getDataDefinition()->toArray();
    $this->assertEqual($definition['type'], 'test.plugin_types.*');

432
433
    $tests = \Drupal::service('config.typed')->get('config_schema_test.plugin_types')->get('test_with_parents')->getElements();
    $definition = $tests[0]->get('settings')->getDataDefinition()->toArray();
434
435
    $this->assertEqual($definition['type'], 'test_with_parents.plugin_types.boolean');

436
    $definition = $tests[1]->get('settings')->getDataDefinition()->toArray();
437
438
    $this->assertEqual($definition['type'], 'test_with_parents.plugin_types.boolean:*');

439
    $definition = $tests[2]->get('settings')->getDataDefinition()->toArray();
440
441
    $this->assertEqual($definition['type'], 'test_with_parents.plugin_types.*');

442
    $definition = $tests[3]->get('settings')->getDataDefinition()->toArray();
443
444
445
    $this->assertEqual($definition['type'], 'test_with_parents.plugin_types.*');
  }

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
  /**
   * Tests hook_config_schema_info_alter().
   */
  public function testConfigSchemaInfoAlter() {
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config */
    $typed_config = \Drupal::service('config.typed');
    $typed_config->clearCachedDefinitions();

    // Ensure that keys can not be added or removed by
    // hook_config_schema_info_alter().
    \Drupal::state()->set('config_schema_test_exception_remove', TRUE);
    $message = 'Expected ConfigSchemaAlterException thrown.';
    try {
      $typed_config->getDefinitions();
      $this->fail($message);
    }
    catch (ConfigSchemaAlterException $e) {
      $this->pass($message);
      $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has removed (config_schema_test.hook) schema definitions');
    }

    \Drupal::state()->set('config_schema_test_exception_add', TRUE);
    $message = 'Expected ConfigSchemaAlterException thrown.';
    try {
      $typed_config->getDefinitions();
      $this->fail($message);
    }
    catch (ConfigSchemaAlterException $e) {
      $this->pass($message);
      $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) and removed (config_schema_test.hook) schema definitions');
    }

    \Drupal::state()->set('config_schema_test_exception_remove', FALSE);
    $message = 'Expected ConfigSchemaAlterException thrown.';
    try {
      $typed_config->getDefinitions();
      $this->fail($message);
    }
    catch (ConfigSchemaAlterException $e) {
      $this->pass($message);
      $this->assertEqual($e->getMessage(), 'Invoking hook_config_schema_info_alter() has added (config_schema_test.hook_added_defintion) schema definitions');
    }

    // Tests that hook_config_schema_info_alter() can add additional metadata to
    // existing configuration schema.
    \Drupal::state()->set('config_schema_test_exception_add', FALSE);
    $definitions = $typed_config->getDefinitions();
    $this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info');
  }

496
}