clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigDiffer;
/**
* Tests the \Drupal\config_update\ConfigDiffer class.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigDiffer
*/
class ConfigDifferTest extends ConfigUpdateUnitTestBase {
/**
* The config differ to test.
*
* @var \Drupal\config_update\ConfigDiffer
*/
protected $configDiffer;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->configDiffer = new ConfigDiffer($this->getTranslationMock());
}
/**
* @covers \Drupal\config_update\ConfigDiffer::same
* @dataProvider sameProvider
*/
public function testSame($a, $b, $expected) {
$this->assertEquals($expected, $this->configDiffer->same($a, $b));
}
/**
* Data provider for self:testSame().
*/
public function sameProvider() {
$base = [
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
];
return [
[$base, $base, TRUE],
// Add _core, omit uuid at top level. Should match, as both are removed
// in normalization process.
[
$base,
[
'_core' => 'foo',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
TRUE,
],
// Change order in top and deep level. Should match.
[
$base,
[
'uuid' => 'bar',
'b' => 0,
'a' => 'a',
'c' => [
'e' => FALSE,
'empty' => [],
'd' => TRUE,
],
],
TRUE,
],
// Add _core in deeper level. Should not match, as this is removed
// only at the top level during normalization.
[
$base,
[
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'_core' => 'do-not-use-this-key',
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// Add uuid in deeper level. Should not match, as this is removed
// only at the top level during normalization.
[
$base,
[
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
'uuid' => 'important',
'empty' => [],
],
],
FALSE,
],
// Omit a component. Should not match.
[
$base,
[
'uuid' => 'bar',
'a' => 'a',
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// Add a component. Should not match.
[
$base,
[
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
'f' => 'f',
],
FALSE,
],
// 0 should not match a string.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => 'b',
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// 0 should not match NULL.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => NULL,
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// FALSE should not match a string.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => 'e',
'empty' => [],
],
],
FALSE,
],
// TRUE should not match a string.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => 'd',
'e' => FALSE,
'empty' => [],
],
],
FALSE,
],
// Add an empty array at top, and remove at lower level. Should still
// match.
[
$base,
[
'_core' => 'foo',
'uuid' => 'bar',
'a' => 'a',
'b' => 0,
'c' => [
'd' => TRUE,
'e' => FALSE,
],
'empty_two' => [],
],
TRUE,
],
];
}
/**
* @covers \Drupal\config_update\ConfigDiffer::diff
*/
public function testDiff() {
$configOne = [
'uuid' => '1234-5678-90',
'id' => 'test.config.id',
'id_to_remove' => 'test.remove.id',
'type' => 'old_type',
'true_value' => TRUE,
'null_value' => NULL,
'nested_array' => [
'flat_array' => [
'value2',
'value1',
'value3',
],
'custom_key' => 'value',
],
];
$configTwo = [
'uuid' => '09-8765-4321',
'id' => 'test.config.id',
'type' => 'new_type',
'true_value' => FALSE,
'null_value' => FALSE,
'nested_array' => [
'flat_array' => [
'value2',
'value3',
],
'custom_key' => 'value',
'custom_key_2' => 'value2',
],
];
$edits = $this->configDiffer->diff($configOne, $configTwo)->getEdits();
$expectedEdits = [
[
'copy' => [
'orig' => [
'id : test.config.id',
],
'closing' => [
'id : test.config.id',
],
],
],
[
'delete' => [
'orig' => [
'id_to_remove : test.remove.id',
],
'closing' => FALSE,
],
],
[
'copy' => [
'orig' => [
'nested_array',
'nested_array::custom_key : value',
],
'closing' => [
'nested_array',
'nested_array::custom_key : value',
],
],
],
[
'add' => [
'orig' => FALSE,
'closing' => [
'nested_array::custom_key_2 : value2',
],
],
],
[
'copy' => [
'orig' => [
'nested_array::flat_array',
'nested_array::flat_array::0 : value2',
],
'closing' => [
'nested_array::flat_array',
'nested_array::flat_array::0 : value2',
],
],
],
[
'change' => [
'orig' => [
'nested_array::flat_array::1 : value1',
'nested_array::flat_array::2 : value3',
'null_value : null',
'true_value : true',
'type : old_type',
],
'closing' => [
'nested_array::flat_array::1 : value3',
'null_value : false',
'true_value : false',
'type : new_type',
],
],
],
];
$this->assertEquals(count($expectedEdits), count($edits));
/** @var \Drupal\Component\Diff\Engine\DiffOp $diffOp */
foreach ($edits as $index => $diffOp) {
$this->assertEquals($expectedEdits[$index][$diffOp->type]['orig'], $diffOp->orig);
$this->assertEquals($expectedEdits[$index][$diffOp->type]['closing'], $diffOp->closing);
}
}
}

View File

@@ -0,0 +1,304 @@
<?php
namespace Drupal\Tests\config_update\Unit;
/**
* Tests the \Drupal\config_update\ConfigListerWithProviders class.
*
* The methods from \Drupal\config_update\ConfigLister are also tested.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigListerWithProviders
*/
class ConfigListerTest extends ConfigUpdateUnitTestBase {
/**
* The config lister to test.
*
* @var \Drupal\config_update\ConfigListerWithProviders
*/
protected $configLister;
/**
* List of configuration by provider in the mocks.
*
* This is an array whose keys are provider names, and whose values are
* each an array containing the provider type, an array of config items
* mocked to be in config/install, and the same for config/optional. In
* all cases, the first item in the array of config items should be tested
* to be provided by that provider, and any others should not be there.
*
* @var array
*/
protected static $configProviderList = [
'foo_module' => [
'module',
['foo.barbaz.one', 'baz.bar.one'],
['foo.barbaz.two'],
],
'foo_theme' => ['theme', ['foo.bar.one'], ['foo.bar.two']],
'standard' => ['profile', ['baz.bar.one'], ['baz.bar.two']],
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$lister = $this->getMockBuilder('Drupal\config_update\ConfigListerWithProviders')
->setConstructorArgs([
$this->getEntityManagerMock(),
$this->getConfigStorageMock('active'),
$this->getConfigStorageMock('extension'),
$this->getConfigStorageMock('optional'),
$this->getModuleHandlerMock(),
$this->getThemeHandlerMock(),
])
->onlyMethods(['listProvidedItems', 'getProfileName'])
->getMock();
$lister->method('getProfileName')
->willReturn('standard');
$map = [];
foreach (self::$configProviderList as $provider => $info) {
// Info has: [type, install storage items, optional storage items].
// Map needs: [type, provider name, isOptional, [config items]].
$map[] = [$info[0], $provider, FALSE, $info[1]];
$map[] = [$info[0], $provider, TRUE, $info[2]];
}
$lister->method('listProvidedItems')
->willReturnMap($map);
$this->configLister = $lister;
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::listConfig
* @dataProvider listConfigProvider
*/
public function testListConfig($a, $b, $expected) {
$this->assertEquals($expected, $this->configLister->listConfig($a, $b));
}
/**
* Data provider for self:testListConfig().
*/
public function listConfigProvider() {
return [
// Arguments are $list_type, $name, and return value is that list of
// configuration in active, extension, and optional storage.
['type', 'system.all',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.seven',
'foo.barbaz.four',
'foo.barnot.three',
'something.else',
],
['foo.barbaz.four'],
],
],
['type', 'system.simple',
[
['something.else', 'another.one'],
['foo.barnot.three', 'something.else'],
[],
],
],
['type', 'foo',
[
['foo.bar.one', 'foo.bar.two', 'foo.bar.three'],
['foo.bar.one', 'foo.bar.two', 'foo.bar.seven'],
[],
],
],
['type', 'unknown.type', [[], [], []]],
['profile', 'dummy',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['baz.bar.one'],
['baz.bar.two'],
],
],
['module', 'foo_module',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['foo.barbaz.one', 'baz.bar.one'],
['foo.barbaz.two'],
],
],
['theme', 'foo_theme',
[
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
['foo.bar.one'],
['foo.bar.two'],
],
],
];
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::getType
*/
public function testGetType() {
$return = $this->configLister->getType('not_in_list');
$this->assertNull($return);
foreach ($this->entityDefinitionInformation as $info) {
$return = $this->configLister->getType($info['type']);
$this->assertEquals($return->getConfigPrefix(), $info['prefix']);
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeByPrefix
*/
public function testGetTypeByPrefix() {
$return = $this->configLister->getTypeByPrefix('not_in_list');
$this->assertNull($return);
foreach ($this->entityDefinitionInformation as $info) {
$return = $this->configLister->getTypeByPrefix($info['prefix']);
$this->assertEquals($return->getConfigPrefix(), $info['prefix']);
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeNameByConfigName
*/
public function testGetTypeNameByConfigName() {
$return = $this->configLister->getTypeNameByConfigName('not_in_list');
$this->assertNull($return);
foreach ($this->entityDefinitionInformation as $info) {
$return = $this->configLister->getTypeNameByConfigName($info['prefix'] . '.something');
$this->assertEquals($return, $info['type']);
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::listTypes
*/
public function testListTypes() {
$return = $this->configLister->listTypes();
// Should return an array in sorted order, of just the config entities
// that $this->getEntityManagerMock() set up.
$expected = ['bar' => 'foo.barbaz', 'baz' => 'baz.foo', 'foo' => 'foo.bar'];
$this->assertEquals(array_keys($return), array_keys($expected));
foreach ($return as $key => $definition) {
$this->assertTrue($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface'));
$this->assertEquals($definition->getConfigPrefix(), $expected[$key]);
}
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::listProviders
*/
public function testListProviders() {
// This method's return value is not sorted in any particular way.
$return = $this->configLister->listProviders();
$expected = [];
foreach (self::$configProviderList as $provider => $info) {
// Info has: [type, install storage items, optional storage items], with
// only the first item in each list that should be present in
// listProviders().
// Expected needs: key is item name, value is [type, provider name].
$expected[$info[1][0]] = [$info[0], $provider];
$expected[$info[2][0]] = [$info[0], $provider];
}
ksort($return);
ksort($expected);
$this->assertEquals($return, $expected);
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::getConfigProvider
* @dataProvider getConfigProviderProvider
*/
public function testGetConfigProvider($a, $expected) {
$this->assertEquals($expected, $this->configLister->getConfigProvider($a));
}
/**
* Data provider for self:testGetConfigProvider().
*/
public static function getConfigProviderProvider(): array {
$values = [];
foreach (self::$configProviderList as $provider => $info) {
// Info has: [type, install storage items, optional storage items], with
// the first item in each list that should be OK to test with
// getConfigProvider().
// Values needs: [item, [type, provider name]].
$values[] = [$info[1][0], [$info[0], $provider]];
$values[] = [$info[2][0], [$info[0], $provider]];
}
$values[] = ['not.a.config.item', NULL];
return $values;
}
/**
* @covers \Drupal\config_update\ConfigListerWithProviders::providerHasConfig
* @dataProvider providerHasConfigProvider
*/
public function testProviderHasConfig($a, $b, $expected) {
$this->assertEquals($expected, $this->configLister->providerHasConfig($a, $b));
}
/**
* Data provider for self:testProviderHasConfig().
*/
public static function providerHasConfigProvider(): array {
$values = [];
foreach (self::$configProviderList as $provider => $info) {
// Info has: [type, install storage items, optional storage items].
// Values needs: [type, provider name, TRUE] for valid providers,
// change the last to FALSE for invalid providers.
$values[] = [$info[0], $provider, TRUE];
$values[] = [$info[0], $provider . '_suffix', FALSE];
}
$values[] = ['invalid_type', 'foo_module', FALSE];
return $values;
}
}

View File

@@ -0,0 +1,415 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigDeleteInterface;
use Drupal\config_update\ConfigReverter;
use Drupal\config_update\ConfigRevertInterface;
/**
* Tests the \Drupal\config_update\ConfigReverter class.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigReverter
*/
class ConfigReverterTest extends ConfigUpdateUnitTestBase {
/**
* The config reverter to test.
*
* @var \Drupal\config_update\ConfigReverter
*/
protected $configReverter;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->configReverter = new ConfigReverter(
$this->getEntityManagerMock(),
$this->getConfigStorageMock('active'),
$this->getConfigStorageMock('extension'),
$this->getConfigStorageMock('optional'),
$this->getConfigFactoryMock(),
$this->getEventDispatcherMock());
}
/**
* @covers \Drupal\config_update\ConfigReverter::getFromActive
* @dataProvider getFromActiveProvider
*/
public function testGetFromActive($a, $b, $expected) {
$this->assertEquals($expected, $this->configReverter->getFromActive($a, $b));
}
/**
* Data provider for self:testGetFromActive().
*/
public function getFromActiveProvider() {
return [
// Arguments are $type, $name, and return value is the config.
// Some config items that are already prefixed.
['', 'foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']],
['system.simple', 'foo.bar.one',
['foo.bar.one' => 'active', 'id' => 'one'],
],
// Config item with a defined entity definition prefix. Entity type 'foo'
// has prefix 'foo.bar'.
['foo', 'one', ['foo.bar.one' => 'active', 'id' => 'one']],
// Unknown type. This should not generate a call into the config read,
// so should not return the known value.
['unknown', 'foo.bar.one', FALSE],
// Missing configuration. Config mock is configured to return FALSE for
// this particular config name.
['system.simple', 'missing', FALSE],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::getFromExtension
* @dataProvider getFromExtensionProvider
*/
public function testGetFromExtension($a, $b, $expected) {
$this->assertEquals($expected, $this->configReverter->getFromExtension($a, $b));
}
/**
* Data provider for self:testGetFromExtension().
*/
public function getFromExtensionProvider() {
return [
// Arguments are $type, $name, and return value is the config.
// Some config items that are already prefixed, and exist in the mock
// extension storage.
['', 'in.extension', ['in.extension' => 'extension']],
['system.simple', 'in.extension', ['in.extension' => 'extension']],
// Config item with a defined entity definition prefix. Entity type 'foo'
// has prefix 'foo.bar'.
['foo', 'one', ['foo.bar.one' => 'extension', 'id' => 'one']],
// One that exists in both extension and optional storage.
['system.simple', 'in.both', ['in.both' => 'extension']],
// One that exists only in optional storage.
['system.simple', 'in.optional', ['in.optional' => 'optional']],
// Unknown type. This should not generate a call into the config read,
// so should not return the known value.
['unknown', 'in.extension', FALSE],
// Missing configuration. Storage mock is configured to return FALSE for
// this particular config name.
['system.simple', 'missing2', FALSE],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::import
* @dataProvider importProvider
*/
public function testImport($type, $name, $config_name, $expected, $config_before, $config_after) {
// Clear dispatch log and set pre-config.
$this->dispatchedEvents = [];
if ($config_name) {
$this->configStorage[$config_name] = $config_before;
}
$save_config = $this->configStorage;
// Call the importer and test the Boolean result.
$result = $this->configReverter->import($type, $name);
$this->assertEquals($expected, $result);
if ($result) {
// Verify that the config is correct after import, and logging worked.
$this->assertEquals($config_after, $this->configStorage[$config_name]);
$this->assertCount(2, $this->dispatchedEvents);
$this->assertEquals(ConfigRevertInterface::PRE_IMPORT, $this->dispatchedEvents[0][0]);
$this->assertEquals(ConfigRevertInterface::IMPORT, $this->dispatchedEvents[1][0]);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($save_config, $this->configStorage);
$this->assertCount(0, $this->dispatchedEvents);
}
}
/**
* Data provider for self:testImport().
*/
public function importProvider() {
return [
// Elements: type, name, config name, return value,
// config to set up before, config expected after. See also
// getFromExtensionProvider().
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
['in.extension' => 'before'],
['in.extension' => 'extension', '_core' => 'core_for_in.extension'],
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
['foo.bar.one' => 'before', 'id' => 'one'],
[
'foo.bar.one' => 'extension',
'id' => 'one',
'_core' => 'core_for_foo.bar.one',
],
],
[
'system.simple',
'in.both',
'in.both',
TRUE,
['in.both' => 'before'],
['in.both' => 'extension', '_core' => 'core_for_in.both'],
],
[
'system.simple',
'in.optional',
'in.optional',
TRUE,
['in.optional' => 'before'],
['in.optional' => 'optional', '_core' => 'core_for_in.optional'],
],
// Will be altered if the extension config exists.
[
'system.simple',
'in.extension.pre_import',
'in.extension.pre_import',
TRUE,
['prop' => 'unaltered_value'],
[
'prop' => 'altered_value',
'new_prop' => 'new_value',
'_core' => 'core_for_in.extension.pre_import',
],
],
// Will not be altered if the extension config doesn't exist.
[
'system.simple',
'missing2,pre_import',
'missing2.pre_import',
FALSE,
FALSE,
FALSE,
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
FALSE,
FALSE,
],
[
'system.simple',
'missing2',
'missing2',
FALSE,
FALSE,
FALSE,
],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::revert
* @dataProvider revertProvider
*/
public function testRevert($type, $name, $config_name, $expected, $config_before, $config_after) {
// Clear dispatch log and set pre-config.
$this->dispatchedEvents = [];
if ($config_name) {
$this->configStorage[$config_name] = $config_before;
}
$save_config = $this->configStorage;
// Call the reverter and test the Boolean result.
$result = $this->configReverter->revert($type, $name);
$this->assertEquals($expected, $result);
if ($result) {
// Verify that the config is correct after revert, and logging worked.
$this->assertEquals($config_after, $this->configStorage[$config_name]);
$this->assertCount(2, $this->dispatchedEvents);
$this->assertEquals(ConfigRevertInterface::PRE_REVERT, $this->dispatchedEvents[0][0]);
$this->assertEquals(ConfigRevertInterface::REVERT, $this->dispatchedEvents[1][0]);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($save_config, $this->configStorage);
$this->assertCount(0, $this->dispatchedEvents);
}
}
/**
* Data provider for self:testRevert().
*/
public function revertProvider() {
return [
// Elements: type, name, config name, return value,
// config to set up before, config expected after. See also
// getFromExtensionProvider().
// The active config's 'prop' property will not be reverted.
[
'system.simple',
'in.extension.pre_revert',
'in.extension.pre_revert',
TRUE,
['prop' => 'unaltered_value'],
[
'prop' => 'active.pre_revert_value',
'new_prop' => 'new_value',
'_core' => 'core_for_in.extension.pre_revert',
],
],
// The active config's 'prop' property will not be reverted.
[
'foo',
'pre_revert',
'foo.bar.pre_revert',
TRUE,
['foo.bar.pre_revert' => 'active', 'id' => 'one'],
[
'foo.bar.pre_revert' => 'extension',
'id' => 'pre_revert',
'prop' => 'active.pre_revert_value',
'new_prop' => 'new_value',
'_core' => 'core_for_foo.bar.pre_revert',
],
],
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
['in.extension' => 'active'],
['in.extension' => 'extension', '_core' => 'core_for_in.extension'],
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
['foo.bar.one' => 'active', 'id' => 'one'],
[
'foo.bar.one' => 'extension',
'id' => 'one',
'_core' => 'core_for_foo.bar.one',
],
],
[
'system.simple',
'in.both',
'in.both',
TRUE,
['in.both' => 'active'],
['in.both' => 'extension', '_core' => 'core_for_in.both'],
],
[
'system.simple',
'in.optional',
'in.optional',
TRUE,
['in.optional' => 'active'],
['in.optional' => 'optional', '_core' => 'core_for_in.optional'],
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
FALSE,
FALSE,
],
// Missing from extension storage.
[
'system.simple',
'missing2',
'missing2',
FALSE,
FALSE,
FALSE,
],
// Present in extension storage but missing from active storage.
[
'system.simple',
'another',
'another',
FALSE,
FALSE,
FALSE,
],
];
}
/**
* @covers \Drupal\config_update\ConfigReverter::delete
* @dataProvider deleteProvider
*/
public function testDelete($type, $name, $config_name, $expected, $config_before = NULL) {
// Clear dispatch log.
$this->dispatchedEvents = [];
if ($config_name && $config_before) {
$this->configStorage[$config_name] = $config_before;
}
$save_config = $this->configStorage;
// Call the configReverter delete method and test the Boolean result.
$result = $this->configReverter->delete($type, $name);
$this->assertEquals($expected, $result);
if ($result) {
// Verify that the config is missing after delete, and logging worked.
$this->assertNotTrue(isset($this->configStorage[$config_name]));
$this->assertCount(2, $this->dispatchedEvents);
$this->assertEquals(ConfigDeleteInterface::PRE_DELETE, $this->dispatchedEvents[0][0]);
$this->assertEquals(ConfigDeleteInterface::DELETE, $this->dispatchedEvents[1][0]);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($save_config, $this->configStorage);
$this->assertCount(0, $this->dispatchedEvents);
}
}
/**
* Data provider for self:testDelete().
*/
public function deleteProvider() {
return [
// Elements: type, name, config name, return value,
// config to set up before (optional).
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
['foo.bar.one' => 'before', 'id' => 'one'],
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
],
[
'system.simple',
'missing2',
'missing2',
FALSE,
],
];
}
}

View File

@@ -0,0 +1,614 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\Component\EventDispatcher\Event;
use Drupal\config_update\ConfigPreRevertEvent;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
/**
* Base class for unit testing in Config Update Manager.
*
* This class provides some mock classes for unit testing.
*/
abstract class ConfigUpdateUnitTestBase extends UnitTestCase {
/**
* The mocked entity definition information.
*
* They are not sorted, to test that the methods sort them. Also there are a
* couple with prefixes that are subsets of each other.
*
* @var string[]
*
* @see ConfigUpdateUnitTestBase::getEntityManagerMock().
*/
protected $entityDefinitionInformation = [
['prefix' => 'foo.bar', 'type' => 'foo'],
['prefix' => 'foo.barbaz', 'type' => 'bar'],
['prefix' => 'baz.foo', 'type' => 'baz'],
];
/**
* Creates a mock entity manager for the test.
*
* @see ConfigUpdateUnitTestBase::entityDefinitionInformation
*/
protected function getEntityManagerMock() {
$definitions = [];
$map = [];
foreach ($this->entityDefinitionInformation as $info) {
$def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock();
$def
->expects($this->any())
->method('getConfigPrefix')
->willReturn($info['prefix']);
$def
->expects($this->any())
->method('entityClassImplements')
->willReturn(TRUE);
$def
->method('getKey')
->willReturn('id');
$def->getConfigPrefix();
$definitions[$info['type']] = $def;
$map[] = [$info['type'], FALSE, $def];
$map[] = [$info['type'], TRUE, $def];
}
// Add in a content entity definition, which shouldn't be recognized by the
// config lister class.
$def = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityTypeInterface')->getMock();
$def
->expects($this->any())
->method('entityClassImplements')
->willReturn(FALSE);
$definitions['content_entity'] = $def;
$manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock();
$manager
->method('getDefinitions')
->willReturn($definitions);
$manager
->method('getDefinition')
->willReturnMap($map);
$manager
->method('getStorage')
->willReturnCallback([$this, 'mockGetStorage']);
return $manager;
}
/**
* Mocks the getStorage() method for the entity manager.
*/
public function mockGetStorage($entity_type) {
// Figure out the config prefix for this entity type.
$prefix = '';
foreach ($this->entityDefinitionInformation as $info) {
if ($info['type'] == $entity_type) {
$prefix = $info['prefix'];
}
}
// This is used in ConfigReverter::import(). Although it is supposed to
// be entity storage, we'll use our mock config object instead.
return new MockConfig('', $prefix, $this);
}
/**
* Array of active configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageActiveInfo = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']],
['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']],
['baz.foo', []],
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.three',
'foo.barbaz.four',
'foo.barbaz.five',
'foo.barbaz.six',
'something.else',
'another.one',
],
],
];
/**
* Array of extension configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageExtensionInfo = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']],
['baz.foo', []],
// This next item is assumed to be element 2 of the array. If not, you
// will need to change ConfigUpdateUnitTestBase::getConfigStorageMock().
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.seven',
'foo.barbaz.four',
'foo.barnot.three',
'something.else',
],
],
];
/**
* Array of optional configuration information for mocking.
*
* Array structure: Each element is an array whose first element is a
* provider name, and second is an array of config items it provides.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getConfigStorageMock()
*/
protected $configStorageOptionalInfo = [
['foo.bar', []],
['foo.barbaz', ['foo.barbaz.four']],
// This next item is assumed to be element 2 of the array. If not, you
// will need to change ConfigUpdateUnitTestBase::getConfigStorageMock().
['', ['foo.barbaz.four']],
];
/**
* Creates a mock config storage object for the test.
*
* @param string $type
* Type of storage object to return: 'active', 'extension', or 'optional'.
* In active storage, the read() method is mocked to assume you are reading
* core.extension to get the profile name, so it returns that information.
* For extension and optional storage, the getComponentNames() method is
* mocked, and for all storages, the listAll() method is mocked.
*
* @see ConfigUpdateUnitTestBase::configStorageActiveInfo
* @see ConfigUpdateUnitTestBase::configStorageExtensionInfo
* @see ConfigUpdateUnitTestBase::configStorageOptionalInfo
*/
protected function getConfigStorageMock($type) {
if ($type == 'active') {
$storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock();
// Various tests assume various values of configuration that need to be
// read from active storage.
$map = [
['core.extension', ['profile' => 'standard']],
['foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']],
['missing', FALSE],
['in.extension',
['in.extension' => 'active', '_core' => 'core_for_in.extension'],
],
['in.both', ['in.both' => 'active']],
['in.optional', ['in.optional' => 'active']],
['in.extension.pre_revert',
['prop' => 'active.pre_revert_value', '_core' => 'core_for_in.extension'],
],
['foo.bar.pre_revert',
[
'foo.bar.pre_revert' => 'active',
'id' => 'pre_revert',
'prop' => 'active.pre_revert_value',
],
],
];
$storage
->method('read')
->willReturnMap($map);
$storage
->method('listAll')
->willReturnMap($this->configStorageActiveInfo);
}
elseif ($type == 'extension') {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$value = [];
foreach ($this->configStorageExtensionInfo[2][1] as $item) {
$value[$item] = 'ignored';
}
$storage
->method('getComponentNames')
->willReturn($value);
$storage
->method('listAll')
->willReturnMap($this->configStorageExtensionInfo);
$map = [
['in.extension', ['in.extension' => 'extension']],
['in.both', ['in.both' => 'extension']],
['in.optional', FALSE],
['foo.bar.one', ['foo.bar.one' => 'extension', 'id' => 'one']],
['another', ['another' => 'extension', 'id' => 'one']],
['in.extension.pre_import', ['prop' => 'extension.pre_import_value']],
['in.extension.pre_revert', ['prop' => 'extension.pre_revert_value']],
['foo.bar.pre_revert',
[
'foo.bar.pre_revert' => 'extension',
'id' => 'pre_revert',
'prop' => 'extension.pre_revert_value',
],
],
['missing2', FALSE],
['missing2.pre_import', FALSE],
];
$storage
->method('read')
->willReturnMap($map);
}
else {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$value = [];
foreach ($this->configStorageOptionalInfo[2][1] as $item) {
$value[$item] = 'ignored';
}
$storage
->method('getComponentNames')
->willReturn($value);
$storage
->method('listAll')
->willReturnMap($this->configStorageOptionalInfo);
$map = [
['in.optional', ['in.optional' => 'optional']],
['in.both', ['in.both' => 'optional']],
['missing2', FALSE],
];
$storage
->method('read')
->willReturnMap($map);
}
return $storage;
}
/**
* Creates a mock module handler for the test.
*/
protected function getModuleHandlerMock() {
$manager = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')->getMock();
$manager->method('getModuleList')
->willReturn(['foo_module' => '', 'standard' => '']);
return $manager;
}
/**
* Creates a mock theme handler for the test.
*/
protected function getThemeHandlerMock() {
$manager = $this->getMockBuilder('Drupal\Core\Extension\ThemeHandlerInterface')->getMock();
$manager->method('listInfo')
->willReturn(['foo_theme' => '']);
return $manager;
}
/**
* Creates a mock string translation class for the test.
*/
protected function getTranslationMock() {
$translation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock();
$translation
->method('translateString')
->willReturnCallback([$this, 'mockTranslate']);
return $translation;
}
/**
* Mocks the translateString() method for the string translation mock object.
*
* @param \Drupal\Core\StringTranslation\TranslatableMarkup $input
* Object to translate.
*
* @return string
* The untranslated string from $input.
*/
public function mockTranslate(TranslatableMarkup $input) {
return $input->getUntranslatedString();
}
/**
* List of mock-dispatched events.
*
* Each element of the array is the call parameters to dispatchEvent() in
* the mocked dispatch class: name and event instance.
*
* @var array
*
* @see ConfigUpdateUnitTestBase::getEventDispatcherMock()
*/
protected $dispatchedEvents = [];
/**
* Mocks the event dispatcher service.
*
* Stores dispatched events in ConfigUpdateUnitTestBase::dispatchedEvents.
*/
protected function getEventDispatcherMock() {
$event = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$event
->method('dispatch')
->willReturnCallback([$this, 'mockDispatch']);
return $event;
}
/**
* Mocks event dispatch.
*
* @see \Symfony\Component\EventDispatcher\EventDispatcherInterface::dispatch()
*/
public function mockDispatch(Event $event, $name = NULL) {
$this->dispatchedEvents[] = [$name, $event];
if ($event instanceof ConfigPreRevertEvent) {
$this->handlePreRevertDispatch($event);
}
return $event;
}
/**
* Handle the pre-revert events.
*
* @param \Drupal\config_update\ConfigPreRevertEvent $event
* The dispatched event.
*/
protected function handlePreRevertDispatch(ConfigPreRevertEvent $event) {
$name = $event->getName();
// Only modify configurations with the pre_import or pre_revert names.
$import = strpos($name, 'pre_import') !== FALSE;
$revert = strpos($name, 'pre_revert') !== FALSE;
if (!$import && !$revert) {
return;
}
$active = $event->getActive();
$value = $event->getValue();
// Always add the new property.
$value['new_prop'] = 'new_value';
// Alter the original property.
if (isset($value['prop'])) {
if ($active) {
// A revert operation is being done, read properties from it.
if (isset($active['prop'])) {
// Don't override the original property.
$value['prop'] = $active['prop'];
}
else {
$value['prop'] = 'altered_active_value';
}
}
else {
$value['prop'] = 'altered_value';
}
}
// Store the modified value.
$event->setValue($value);
}
/**
* Mock config storage for the mock config factory.
*
* This is actually managed by the MockConfig class in this file.
*
* @var array
*/
protected $configStorage = [];
/**
* Gets the value of the mocked config storage.
*/
public function getConfigStorage() {
return $this->configStorage;
}
/**
* Sets the value of the mocked config storage.
*/
public function setConfigStorage($values) {
$this->configStorage = $values;
}
/**
* Creates a mock config factory class for the test.
*/
protected function getConfigFactoryMock() {
$config = $this->getMockBuilder('Drupal\Core\Config\ConfigFactoryInterface')->getMock();
$config
->method('getEditable')
->willReturnCallback([$this, 'mockGetEditable']);
return $config;
}
/**
* Mocks the getEditable() method for the mock config factory.
*
* @param string $name
* Name of the config object to get an editable object for.
*
* @return MockConfig
* Editable mock config object.
*/
public function mockGetEditable($name) {
return new MockConfig($name, '', $this);
}
}
/**
* Mock class for mutable configuration, config entity, and entity storage.
*/
class MockConfig {
/**
* Name of the config.
*
* @var string
*/
protected $name = '';
/**
* Prefix for the entity type being mocked, for entity storage mocking.
*
* @var string
*/
protected $entityPrefix = '';
/**
* Test class this comes from.
*
* @var \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase
*/
protected $test;
/**
* Current value of the configuration.
*
* @var array
*/
protected $value = '';
/**
* Constructs a mock config object.
*
* @param string $name
* Name of the config that is being mocked. Can be blank.
* @param string $entity_prefix
* Prefix for the entity type that is being mocked. Often blank.
* @param \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase $test
* Test class this comes from.
*/
public function __construct($name, $entity_prefix, ConfigUpdateUnitTestBase $test) {
$this->name = $name;
$this->entityPrefix = $entity_prefix;
$this->test = $test;
$storage = $test->getConfigStorage();
if ($name && isset($storage[$name])) {
$value = $storage[$name];
$value['is_new'] = FALSE;
}
else {
$value['is_new'] = TRUE;
}
$value['_core'] = 'core_for_' . $name;
$this->value = $value;
}
/**
* Gets a component of the configuration value.
*/
public function get($key) {
return $this->value[$key] ?? NULL;
}
/**
* Sets a component of the configuration value.
*/
public function set($key, $value) {
$this->value[$key] = $value;
return $this;
}
/**
* Sets the entire configuration value.
*/
public function setData($value) {
// Retain the _core key.
$core = $this->value['_core'] ?? '';
$this->value = $value;
if ($core) {
$this->value['_core'] = $core;
}
return $this;
}
/**
* Saves the configuration.
*/
public function save() {
$config = $this->test->getConfigStorage();
$config[$this->name] = $this->value;
$this->test->setConfigStorage($config);
return $this;
}
/**
* Deletes the configuration.
*/
public function delete() {
$config = $this->test->getConfigStorage();
unset($config[$this->name]);
$this->test->setConfigStorage($config);
return $this;
}
/**
* Mocks the createFromStorageRecord() method from entity storage.
*/
public function createFromStorageRecord($values) {
if (!$this->entityPrefix) {
return NULL;
}
// This is supposed to return an entity, but the only method we need is
// save(), so instead set up and return this object.
$this->name = $this->entityPrefix . '.' . $values['id'];
$this->value = $values;
$this->value['_core'] = 'core_for_' . $this->name;
return $this;
}
/**
* Mocks the updateFromStorageRecord() method from entity storage.
*/
public function updateFromStorageRecord($object, $values) {
return $object->createFromStorageRecord($values);
}
/**
* Mocks the load() method for entity storage.
*/
public function load($id) {
$full_name = $this->entityPrefix . '.' . $id;
$configs = $this->test->getConfigStorage();
if (isset($configs[$full_name])) {
$this->value = $configs[$full_name];
$this->name = $full_name;
$this->value['_core'] = 'core_for_' . $full_name;
return $this;
}
return NULL;
}
}