contrib modules updates

This commit is contained in:
2019-02-27 10:39:59 +01:00
parent 04a4b8895d
commit e3cf889820
579 changed files with 18343 additions and 4076 deletions
@@ -5,8 +5,8 @@ description: 'Provides basic revert and update functionality for other modules'
dependencies:
- drupal:config
# Information added by Drupal.org packaging script on 2017-12-05
version: '8.x-1.5'
# Information added by Drupal.org packaging script on 2018-12-17
version: '8.x-1.6'
core: '8.x'
project: 'config_update'
datestamp: 1512514387
datestamp: 1545090489
@@ -285,8 +285,13 @@ function drush_config_update_ui_config_revert($name) {
// The revert command needs the type and the unprefixed name.
$type = $lister->getTypeNameByConfigName($name);
// The lister gives NULL if simple configuration, but the reverter expects
// 'system.simple' so we convert it.
if ($type === NULL) {
$type = 'system.simple';
}
$shortname = $name;
if ($type && $type != 'system.simple') {
if ($type != 'system.simple') {
$definition = $manager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
if (strpos($name, $prefix) === 0) {
@@ -7,8 +7,8 @@ dependencies:
- config_update:config_update
- drupal:config
# Information added by Drupal.org packaging script on 2017-12-05
version: '8.x-1.5'
# Information added by Drupal.org packaging script on 2018-12-17
version: '8.x-1.6'
core: '8.x'
project: 'config_update'
datestamp: 1512514387
datestamp: 1545090489
@@ -35,3 +35,20 @@ function config_update_ui_help($route_name, RouteMatchInterface $route_match) {
return '<p>' . t('Most configuration is organized into a hierarchy of settings; at a minimum, it is a one-level hierarchy where each setting has a name and a value, and the hierarchy comes in when some of the settings have multiple components.') . '</p><p>' . t('Configuration items are normalized and formatted before computing differences. The normalization step alphabetizes the components at each level of the hierarchy, and removes a few components whose differences should be ignored, such as the UUID. The formatting step shows the full hierarchy of each configuration value with :: separators for the hierarchy levels, and a : separator between the lowest-level setting name and the value, so that in a line-by-line diff you can always see which values are actually different. Green lines with + signs have been added, and yellow lines with - signs have been removed.') . '</p><p>' . t('Note that differences are considering the base configuration, without overrides from your settings.php file, or translations.') . '</p>';
}
}
/**
* Implements hook_menu_links_discovered_alter().
*/
function config_update_ui_menu_links_discovered_alter(&$links) {
if (\Drupal::moduleHandler()->moduleExists('admin_toolbar_tools')) {
// Add the Updates report link to the Tools menu, but only if the
// Admin Toolbar Extra Tools module is present.
$links['admin_toolbar_tools.config.update'] = [
'title' => t('Updates report'),
'route_name' => 'config_update_ui.report',
'menu_name' => 'admin',
'parent' => 'config.sync',
'weight' => 3,
];
}
}
@@ -137,7 +137,7 @@ class ConfigDeleteConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->delete($this->type, $this->name);
drupal_set_message($this->t('The configuration was deleted.'));
$this->messenger()->addMessage($this->t('The configuration %item has been deleted.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -139,7 +139,7 @@ class ConfigImportConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->import($this->type, $this->name);
drupal_set_message($this->t('The configuration was imported from its source.'));
$this->messenger()->addMessage($this->t('The configuration %item has been imported from its source.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -139,7 +139,7 @@ class ConfigRevertConfirmForm extends ConfirmFormBase {
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->configRevert->revert($this->type, $this->name);
drupal_set_message($this->t('The configuration was reverted to its source.'));
$this->messenger()->addMessage($this->t('The configuration %item has been reverted to its source.', ['%item' => $this->name]));
$form_state->setRedirectUrl($this->getCancelUrl());
}
@@ -1,15 +1,15 @@
<?php
namespace Drupal\config_update_ui\Tests;
namespace Drupal\Tests\config_update_ui\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Verify config reports, reverts, and diffs with profile overrides.
*
* @group config
* @group config_update
*/
class ConfigProfileOverridesTest extends WebTestBase {
class ConfigProfileOverridesTest extends BrowserTestBase {
/**
* Use the Standard profile, so that there are profile config overrides.
@@ -43,7 +43,7 @@ class ConfigProfileOverridesTest extends WebTestBase {
parent::setUp();
// Create user and log in.
$this->adminUser = $this->drupalCreateUser([
$this->adminUser = $this->createUser([
'access administration pages',
'administer themes',
'view config updates report',
@@ -68,7 +68,8 @@ class ConfigProfileOverridesTest extends WebTestBase {
// that system.theme is not shown (it should not be missing, or added,
// or overridden).
$this->drupalGet('admin/config/development/configuration/report/type/system.simple');
$this->assertNoRaw('system.theme');
$session = $this->assertSession();
$session->responseNotContains('system.theme');
// Go to the Appearance page and change the theme to whatever is currently
// disabled. Return to the report and verify that system.theme is there,
@@ -76,22 +77,25 @@ class ConfigProfileOverridesTest extends WebTestBase {
$this->drupalGet('admin/appearance');
$this->clickLink('Install and set as default');
$this->drupalGet('admin/config/development/configuration/report/type/system.simple');
$this->assertText('system.theme');
$session = $this->assertSession();
$session->pageTextContains('system.theme');
// Look at the differences for system.theme and verify it's against
// the standard profile version, not default version. The line for
// default should show bartik as the source; if it's against the system
// version, the word bartik would not be there.
$this->drupalGet('admin/config/development/configuration/report/diff/system.simple/system.theme');
$this->assertText('bartik');
$session = $this->assertSession();
$session->pageTextContains('bartik');
// Revert and verify that it reverted to the profile version, not the
// system module version.
$this->drupalGet('admin/config/development/configuration/report/revert/system.simple/system.theme');
$this->drupalPostForm(NULL, [], 'Revert');
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.theme');
$this->assertText('admin: seven');
$this->assertText('default: bartik');
$session = $this->assertSession();
$session->pageTextContains('admin: seven');
$session->pageTextContains('default: bartik');
}
}
@@ -1,15 +1,15 @@
<?php
namespace Drupal\config_update_ui\Tests;
namespace Drupal\Tests\config_update_ui\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Verify the config revert report and its links.
*
* @group config
* @group config_update
*/
class ConfigUpdateTest extends WebTestBase {
class ConfigUpdateTest extends BrowserTestBase {
/**
* Modules to enable.
@@ -46,7 +46,7 @@ class ConfigUpdateTest extends WebTestBase {
parent::setUp();
// Create user and log in.
$this->adminUser = $this->drupalCreateUser([
$this->adminUser = $this->createUser([
'access administration pages',
'administer search',
'view config updates report',
@@ -60,8 +60,8 @@ class ConfigUpdateTest extends WebTestBase {
$this->drupalLogin($this->adminUser);
// Make sure local tasks and page title are showing.
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
$this->placeBlock('local_tasks_block');
$this->placeBlock('page_title_block');
// Load the Drush include file so that its functions can be tested, plus
// the Drush testing include file.
@@ -103,13 +103,15 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Testing profile', [], [], [], $inactive, ['added']);
// The locale.settings line should show that the Testing profile is the
// provider.
$this->assertText('Testing profile');
$session = $this->assertSession();
$session->pageTextContains('Testing profile');
$this->assertDrushReports('profile', '', [], [], [], array_keys($inactive));
// Verify that the user search page cannot be imported (because it already
// exists).
$this->drupalGet('admin/config/development/configuration/report/import/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Delete the user search page from the search UI and verify report for
// both the search page config type and user module.
@@ -121,7 +123,8 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Search page', [], [], [], $inactive);
// The search.page.user_search line should show that the User module is the
// provider.
$this->assertText('User module');
$session = $this->assertSession();
$session->pageTextContains('User module');
$this->assertDrushReports('type', 'search_page', [], [], [], array_keys($inactive));
$this->drupalGet('admin/config/development/configuration/report/module/user');
@@ -138,17 +141,20 @@ class ConfigUpdateTest extends WebTestBase {
// Verify that the user search page cannot be reverted (because it does
// not already exist).
$this->drupalGet('admin/config/development/configuration/report/revert/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Verify that the delete URL doesn't work either.
$this->drupalGet('admin/config/development/configuration/report/delete/search_page/user_search');
$this->assertResponse(404);
$session = $this->assertSession();
$session->statusCodeEquals(404);
// Use the import link to get it back. Do this from the search page
// report to make sure we are importing the right config.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Import from source');
$this->drupalPostForm(NULL, [], 'Import');
$this->assertText('The configuration was imported');
$session = $this->assertSession();
$session->pageTextContains('has been imported');
$this->assertNoReport();
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
@@ -156,8 +162,9 @@ class ConfigUpdateTest extends WebTestBase {
// Verify that after import, there is no config hash generated.
$this->drupalGet('admin/config/development/configuration/single/export/search_page/user_search');
$this->assertText('id: user_search');
$this->assertNoText('default_config_hash:');
$session = $this->assertSession();
$session->pageTextContains('id: user_search');
$session->pageTextNotContains('default_config_hash:');
// Test importing again, this time using the Drush import command.
$this->drupalGet('admin/config/search/pages');
@@ -184,10 +191,11 @@ class ConfigUpdateTest extends WebTestBase {
// Test the show differences link.
$this->clickLink('Show differences');
$this->assertText('Content');
$this->assertText('New label');
$this->assertText('node');
$this->assertText('new_path');
$session = $this->assertSession();
$session->pageTextContains('Content');
$session->pageTextContains('New label');
$session->pageTextContains('node');
$session->pageTextContains('new_path');
// Test the show differences Drush command.
$output = drush_config_update_ui_config_diff('search.page.node_search');
@@ -203,36 +211,39 @@ class ConfigUpdateTest extends WebTestBase {
// Test the export link.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Export');
$this->assertText('Here is your configuration:');
$this->assertText('id: node_search');
$this->assertText('New label');
$this->assertText('path: new_path');
$this->assertText('search.page.node_search.yml');
$session = $this->assertSession();
$session->pageTextContains('Here is your configuration:');
$session->pageTextContains('id: node_search');
$session->pageTextContains('New label');
$session->pageTextContains('path: new_path');
$session->pageTextContains('search.page.node_search.yml');
// Grab the uuid and hash lines for the next test.
$text = $this->getTextContent();
// Grab the uuid and hash lines from the exported config for the next test.
$text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
$matches = [];
preg_match('|^.*uuid:.*$|m', $text, $matches);
$uuid_line = $matches[0];
$uuid_line = trim($matches[0]);
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
$hash_line = trim($matches[0]);
// Test reverting.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Revert to source');
$this->assertText('Are you sure you want to revert');
$this->assertText('Search page');
$this->assertText('node_search');
$this->assertText('Customizations will be lost. This action cannot be undone');
$session = $this->assertSession();
$session->pageTextContains('Are you sure you want to revert');
$session->pageTextContains('Search page');
$session->pageTextContains('node_search');
$session->pageTextContains('Customizations will be lost. This action cannot be undone');
$this->drupalPostForm(NULL, [], 'Revert');
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->assertReport('Search page', [], [], [], []);
// Verify that the uuid and hash keys were retained in the revert.
$this->drupalGet('admin/config/development/configuration/single/export/search_page/node_search');
$this->assertText('id: node_search');
$this->assertText($uuid_line);
$this->assertText($hash_line);
$session = $this->assertSession();
$session->pageTextContains('id: node_search');
$session->pageTextContains($uuid_line);
$session->pageTextContains($hash_line);
// Test reverting again, this time using Drush single revert command.
$this->drupalGet('admin/config/search/pages');
@@ -278,19 +289,22 @@ class ConfigUpdateTest extends WebTestBase {
// Test the export link.
$this->clickLink('Export');
$this->assertText('Here is your configuration:');
$this->assertText('id: test');
$this->assertText('label: test');
$this->assertText('path: test');
$this->assertText('search.page.test.yml');
$session = $this->assertSession();
$session->pageTextContains('Here is your configuration:');
$session->pageTextContains('id: test');
$session->pageTextContains('label: test');
$session->pageTextContains('path: test');
$session->pageTextContains('search.page.test.yml');
// Test the delete link.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
$this->clickLink('Delete');
$this->assertText('Are you sure');
$this->assertText('cannot be undone');
$session = $this->assertSession();
$session->pageTextContains('Are you sure');
$session->pageTextContains('cannot be undone');
$this->drupalPostForm(NULL, [], 'Delete');
$this->assertText('The configuration was deleted');
$session = $this->assertSession();
$session->pageTextContains('has been deleted');
// And verify the report again.
$this->drupalGet('admin/config/development/configuration/report/type/search_page');
@@ -306,18 +320,20 @@ class ConfigUpdateTest extends WebTestBase {
$this->assertReport('Search module', [], [], $changed, [], ['added']);
$this->clickLink('Show differences');
$this->assertText('Config difference for Simple configuration search.settings');
$this->assertText('index::minimum_word_size');
$this->assertText('4');
$session = $this->assertSession();
$session->pageTextContains('Config difference for Simple configuration search.settings');
$session->pageTextContains('index::minimum_word_size');
$session->pageTextContains('4');
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Export');
$this->assertText('minimum_word_size: 4');
$session = $this->assertSession();
$session->pageTextContains('minimum_word_size: 4');
// Grab the hash line for the next test.
$text = $this->getTextContent();
$text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
$matches = [];
preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
$hash_line = $matches[0];
$hash_line = trim($matches[0]);
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->clickLink('Revert to source');
@@ -325,7 +341,8 @@ class ConfigUpdateTest extends WebTestBase {
// Verify that the hash was retained in the revert.
$this->drupalGet('admin/config/development/configuration/single/export/system.simple/search.settings');
$this->assertText($hash_line);
$session = $this->assertSession();
$session->pageTextContains($hash_line);
$this->drupalGet('admin/config/development/configuration/report/module/search');
$this->assertReport('Search module', [], [], [], [], ['added']);
@@ -338,6 +355,10 @@ class ConfigUpdateTest extends WebTestBase {
$changed = ['filter.format.plain_text' => 'New label'];
$this->drupalGet('admin/config/development/configuration/report/type/filter_format');
$this->assertReport('Text format', [], [], $changed, []);
// Verify that we can revert non-entity configuration in Drush. Issue:
// https://www.drupal.org/project/config_update/issues/2935395
drush_config_update_ui_config_revert('system.date');
}
/**
@@ -359,62 +380,63 @@ class ConfigUpdateTest extends WebTestBase {
* Array of report sections to skip checking.
*/
protected function assertReport($title, array $missing, array $added, array $changed, array $inactive, array $skip = []) {
$this->assertText('Configuration updates report for ' . $title);
$this->assertText('Generate new report');
$session = $this->assertSession();
$session->pageTextContains('Configuration updates report for ' . $title);
$session->pageTextContains('Generate new report');
if (!in_array('missing', $skip)) {
$this->assertText('Missing configuration items');
$session->pageTextContains('Missing configuration items');
if (count($missing)) {
foreach ($missing as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all provided configuration items are in your active configuration.');
$session->pageTextNotContains('None: all provided configuration items are in your active configuration.');
}
else {
$this->assertText('None: all provided configuration items are in your active configuration.');
$session->pageTextContains('None: all provided configuration items are in your active configuration.');
}
}
if (!in_array('inactive', $skip)) {
$this->assertText('Inactive optional items');
$session->pageTextContains('Inactive optional items');
if (count($inactive)) {
foreach ($inactive as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all optional configuration items are in your active configuration.');
$session->pageTextNotContains('None: all optional configuration items are in your active configuration.');
}
else {
$this->assertText('None: all optional configuration items are in your active configuration.');
$session->pageTextContains('None: all optional configuration items are in your active configuration.');
}
}
if (!in_array('added', $skip)) {
$this->assertText('Added configuration items');
$session->pageTextContains('Added configuration items');
if (count($added)) {
foreach ($added as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
$session->pageTextNotContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
}
else {
$this->assertText('None: all active configuration items of this type were provided by modules, themes, or install profile.');
$session->pageTextContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
}
}
if (!in_array('changed', $skip)) {
$this->assertText('Changed configuration items');
$session->pageTextContains('Changed configuration items');
if (count($changed)) {
foreach ($changed as $name => $label) {
$this->assertText($name);
$this->assertText($label);
$session->pageTextContains($name);
$session->pageTextContains($label);
}
$this->assertNoText('None: no active configuration items differ from their current provided versions.');
$session->pageTextNotContains('None: no active configuration items differ from their current provided versions.');
}
else {
$this->assertText('None: no active configuration items differ from their current provided versions.');
$session->pageTextContains('None: no active configuration items differ from their current provided versions.');
}
}
}
@@ -440,7 +462,7 @@ class ConfigUpdateTest extends WebTestBase {
protected function assertDrushReports($type, $name, array $missing, array $added, array $changed, array $inactive, array $skip = []) {
if (!in_array('missing', $skip)) {
$output = drush_config_update_ui_config_missing_report($type, $name);
$this->assertEqual(count($output), count($missing), 'Drush missing report has correct number of items');
$this->assertEquals(count($output), count($missing), 'Drush missing report has correct number of items');
if (count($missing)) {
foreach ($missing as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush missing report");
@@ -450,7 +472,7 @@ class ConfigUpdateTest extends WebTestBase {
if (!in_array('added', $skip) && $type == 'type') {
$output = drush_config_update_ui_config_added_report($name);
$this->assertEqual(count($output), count($added), 'Drush added report has correct number of items');
$this->assertEquals(count($output), count($added), 'Drush added report has correct number of items');
if (count($added)) {
foreach ($added as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush added report");
@@ -460,7 +482,7 @@ class ConfigUpdateTest extends WebTestBase {
if (!in_array('changed', $skip)) {
$output = drush_config_update_ui_config_different_report($type, $name);
$this->assertEqual(count($output), count($changed), 'Drush changed report has correct number of items');
$this->assertEquals(count($output), count($changed), 'Drush changed report has correct number of items');
if (count($changed)) {
foreach ($changed as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush changed report");
@@ -470,7 +492,7 @@ class ConfigUpdateTest extends WebTestBase {
if (!in_array('inactive', $skip)) {
$output = drush_config_update_ui_config_inactive_report($type, $name);
$this->assertEqual(count($output), count($inactive), 'Drush inactive report has correct number of items');
$this->assertEquals(count($output), count($inactive), 'Drush inactive report has correct number of items');
if (count($inactive)) {
foreach ($inactive as $item) {
$this->assertTrue(in_array($item, $output), "Item $item is in the Drush inactive report");
@@ -485,37 +507,38 @@ class ConfigUpdateTest extends WebTestBase {
* Assumes you are already on the report form page.
*/
protected function assertNoReport() {
$this->assertText('Report type');
$this->assertText('Full report');
$this->assertText('Single configuration type');
$this->assertText('Single module');
$this->assertText('Single theme');
$this->assertText('Installation profile');
$this->assertText('Updates report');
$this->assertNoText('Missing configuration items');
$this->assertNoText('Added configuration items');
$this->assertNoText('Changed configuration items');
$this->assertNoText('Unchanged configuration items');
$session = $this->assertSession();
$session->pageTextContains('Report type');
$session->pageTextContains('Full report');
$session->pageTextContains('Single configuration type');
$session->pageTextContains('Single module');
$session->pageTextContains('Single theme');
$session->pageTextContains('Installation profile');
$session->pageTextContains('Updates report');
$session->pageTextNotContains('Missing configuration items');
$session->pageTextNotContains('Added configuration items');
$session->pageTextNotContains('Changed configuration items');
$session->pageTextNotContains('Unchanged configuration items');
// Verify that certain report links are shown or not shown. For extensions,
// only extensions that have configuration should be shown.
// Modules.
$this->assertLink('Search');
$this->assertLink('Field');
$this->assertNoLink('Configuration Update Base');
$this->assertNoLink('Configuration Update Reports');
$session->linkExists('Search');
$session->linkExists('Field');
$session->linkNotExists('Configuration Update Base');
$session->linkNotExists('Configuration Update Reports');
// Themes.
$this->assertNoLink('Stark');
$this->assertNoLink('Classy');
$session->linkNotExists('Stark');
$session->linkNotExists('Classy');
// Profiles.
$this->assertLink('Testing');
$session->linkExists('Testing');
// Configuration types.
$this->assertLink('Everything');
$this->assertLink('Simple configuration');
$this->assertLink('Search page');
$session->linkExists('Everything');
$session->linkExists('Simple configuration');
$session->linkExists('Search page');
}
}
@@ -0,0 +1,37 @@
# This is the DrupalCI testbot build file for Configuration Update Manager.
# Learn to make one for your own drupal.org project:
# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
build:
assessment:
validate_codebase:
phplint:
phpcs:
# phpcs will use core's specified version of Coder.
sniff-all-files: true
halt-on-fail: true
testing:
# run_tests task is executed several times in order of performance speeds.
# halt-on-fail can be set on the run_tests tasks in order to fail fast.
# suppress-deprecations is false in order to be alerted to usages of
# deprecated code.
run_tests.phpunit:
types: 'PHPUnit-Unit'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.kernel:
types: 'PHPUnit-Kernel'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.functional:
types: 'PHPUnit-Functional'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
run_tests.javascript:
concurrency: 15
types: 'PHPUnit-FunctionalJavascript'
testgroups: '--all'
suppress-deprecations: false
halt-on-fail: false
@@ -5,6 +5,7 @@ namespace Drupal\config_update;
use Drupal\Component\Diff\Diff;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Serialization\Yaml;
/**
* Provides methods related to config differences.
@@ -14,7 +15,7 @@ class ConfigDiffer implements ConfigDiffInterface {
use StringTranslationTrait;
/**
* List of elements to ignore when comparing config.
* List of elements to ignore on top level when comparing config.
*
* @var string[]
*
@@ -46,7 +47,7 @@ class ConfigDiffer implements ConfigDiffInterface {
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation
* String translation service.
* @param string[] $ignore
* Config components to ignore.
* Config components to ignore at the top level.
* @param string $hierarchy_prefix
* Prefix to use in diffs for array hierarchy.
* @param string $value_prefix
@@ -62,9 +63,9 @@ class ConfigDiffer implements ConfigDiffInterface {
/**
* Normalizes config for comparison.
*
* Recursively removes elements in the ignore list from configuration,
* as well as empty array values, and sorts at each level by array key, so
* that config from different storage can be compared meaningfully.
* Removes elements in the ignore list from the top level of configuration,
* and at each level of the array, removes empty arrays and sorts by array
* key, so that config from different storage can be compared meaningfully.
*
* @param array|null $config
* Configuration array to normalize.
@@ -80,27 +81,40 @@ class ConfigDiffer implements ConfigDiffInterface {
return [];
}
// Remove "ignore" elements.
// Remove "ignore" elements, only at the top level.
foreach ($this->ignore as $element) {
unset($config[$element]);
}
// Recursively normalize remaining elements, if they are arrays.
foreach ($config as $key => $value) {
// Recursively normalize and return.
return $this->normalizeArray($config);
}
/**
* Recursively sorts an array by key, and removes empty arrays.
*
* @param array $array
* An array to normalize.
*
* @return array
* An array that is sorted by key, at each level of the array, with empty
* arrays removed.
*/
protected function normalizeArray(array $array) {
foreach ($array as $key => $value) {
if (is_array($value)) {
$new = $this->normalize($value);
$new = $this->normalizeArray($value);
if (count($new)) {
$config[$key] = $new;
$array[$key] = $new;
}
else {
unset($config[$key]);
unset($array[$key]);
}
}
}
// Sort and return.
ksort($config);
return $config;
ksort($array);
return $array;
}
/**
@@ -157,11 +171,8 @@ class ConfigDiffer implements ConfigDiffInterface {
$lines[] = $line;
}
}
elseif (is_null($value)) {
$lines[] = $section_prefix . $this->valuePrefix . $this->t('(NULL)');
}
else {
$lines[] = $section_prefix . $this->valuePrefix . $value;
$lines[] = $section_prefix . $this->valuePrefix . Yaml::encode($value);
}
}
@@ -89,7 +89,7 @@ class ConfigLister implements ConfigListInterface {
// Calculate and return the list.
foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) {
if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
if ($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface')) {
$this->definitions[$entity_type] = $definition;
$prefix = $definition->getConfigPrefix();
$this->typesByPrefix[$prefix] = $entity_type;
@@ -72,7 +72,7 @@ interface ConfigRevertInterface {
* The name of the config item, without the prefix.
*
* @return array
* The configuration value.
* The configuration value, or FALSE if it is not found.
*/
public function getFromActive($type, $name);
@@ -87,11 +87,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function import($type, $name) {
// Read the config from the file.
// Read the config from the file. Note: Do not call getFromExtension() here
// because we need $full_name below.
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
$value = FALSE;
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
if (!$value) {
return FALSE;
@@ -118,36 +122,44 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function revert($type, $name) {
// Read the config from the file.
// Read the config from the file. Note: Do not call getFromExtension() here
// because we need $full_name below.
$value = FALSE;
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
if (!$value) {
return FALSE;
}
// Make sure the configuration exists currently in active storage.
if (!$this->activeConfigStorage->read($full_name)) {
return FALSE;
}
// Load the current config and replace the value, retaining the config
// hash (which is part of the _core config key's value).
if ($type == 'system.simple') {
// Load the current config and replace the value, retaining the config
// hash (which is part of the _core config key's value).
$config = $this->configFactory->getEditable($full_name);
$core = $config->get('_core');
$config->setData($value);
$config->set('_core', $core);
$config->save();
$config
->setData($value)
->set('_core', $core)
->save();
}
else {
// Load the current config entity and replace the value. Note that
// the uuid and _core/hash values are retained for entity-based config,
// since updateFromStorageRecord() only updates values that are part of
// the passed-in array, and doesn't remove or alter other values.
$definition = $this->entityManager->getDefinition($type);
$id_key = $definition->getKey('id');
$id = $value[$id_key];
$entity_storage = $this->entityManager->getStorage($type);
$entity = $entity_storage->load($id);
$core = $entity->get('_core');
$entity = $entity_storage->updateFromStorageRecord($entity, $value);
$entity->set('_core', $core);
$entity->save();
}
@@ -162,11 +174,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* {@inheritdoc}
*/
public function delete($type, $name) {
$config = FALSE;
$full_name = $this->getFullName($type, $name);
if (!$full_name) {
return FALSE;
if ($full_name) {
// Make sure the configuration exists currently in active storage.
if (!$this->activeConfigStorage->read($full_name)) {
return FALSE;
}
$config = $this->configFactory->getEditable($full_name);
}
$config = $this->configFactory->getEditable($full_name);
if (!$config) {
return FALSE;
}
@@ -183,17 +199,23 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
*/
public function getFromActive($type, $name) {
$full_name = $this->getFullName($type, $name);
return $this->activeConfigStorage->read($full_name);
if ($full_name) {
return $this->activeConfigStorage->read($full_name);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function getFromExtension($type, $name) {
$value = FALSE;
$full_name = $this->getFullName($type, $name);
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
if ($full_name) {
$value = $this->extensionConfigStorage->read($full_name);
if (!$value) {
$value = $this->extensionOptionalConfigStorage->read($full_name);
}
}
return $value;
}
@@ -207,7 +229,7 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
* The config name, without prefix.
*
* @return string
* The config item's full name.
* The config item's full name, or FALSE if there is an error.
*/
protected function getFullName($type, $name) {
if ($type == 'system.simple' || !$type) {
@@ -215,8 +237,13 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface {
}
$definition = $this->entityManager->getDefinition($type);
$prefix = $definition->getConfigPrefix() . '.';
return $prefix . $name;
if ($definition) {
$prefix = $definition->getConfigPrefix() . '.';
return $prefix . $name;
}
else {
return FALSE;
}
}
}
@@ -3,7 +3,6 @@
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigDiffer;
use Drupal\Tests\UnitTestCase;
/**
* Tests the \Drupal\config_update\ConfigDiffer class.
@@ -12,14 +11,7 @@ use Drupal\Tests\UnitTestCase;
*
* @coversDefaultClass \Drupal\config_update\ConfigDiffer
*/
class ConfigDifferTest extends UnitTestCase {
/**
* The mock translation object.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $stringTranslation;
class ConfigDifferTest extends ConfigUpdateUnitTestBase {
/**
* The config differ to test.
@@ -32,12 +24,7 @@ class ConfigDifferTest extends UnitTestCase {
* {@inheritdoc}
*/
protected function setUp() {
$this->stringTranslation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock();
$this->stringTranslation
->method('t')
->will($this->returnArgument(0));
$this->configDiffer = new ConfigDiffer($this->stringTranslation);
$this->configDiffer = new ConfigDiffer($this->getTranslationMock());
}
/**
@@ -59,13 +46,14 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
];
return [
[$base, $base, TRUE],
// Add _core, omit uuid. Should match.
// Add _core, omit uuid at top level. Should match, as both are removed
// in normalization process.
[
$base,
[
@@ -75,27 +63,30 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
TRUE,
],
// Change order. Should match.
// Change order in top and deep level. Should match.
[
$base,
[
'a' => 'a',
'uuid' => 'bar',
'b' => 0,
'a' => 'a',
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
'd' => TRUE,
],
],
TRUE,
],
// Change order and add _core in deeper level. Should match.
// Add _core in deeper level. Should not match, as this is removed
// only at the top level during normalization.
[
$base,
[
@@ -103,12 +94,31 @@ class ConfigDifferTest extends UnitTestCase {
'a' => 'a',
'b' => 0,
'c' => [
'e' => FALSE,
'_core' => 'foo',
'_core' => 'do-not-use-this-key',
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
TRUE,
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.
@@ -120,6 +130,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
],
FALSE,
@@ -135,6 +146,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => FALSE,
'empty' => [],
],
'f' => 'f',
],
@@ -152,6 +164,24 @@ class ConfigDifferTest extends UnitTestCase {
'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,
@@ -168,6 +198,7 @@ class ConfigDifferTest extends UnitTestCase {
'c' => [
'd' => TRUE,
'e' => 'e',
'empty' => [],
],
],
FALSE,
@@ -184,10 +215,29 @@ class ConfigDifferTest extends UnitTestCase {
'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,
],
];
}
@@ -201,6 +251,7 @@ class ConfigDifferTest extends UnitTestCase {
'id_to_remove' => 'test.remove.id',
'type' => 'old_type',
'true_value' => TRUE,
'null_value' => NULL,
'nested_array' => [
'flat_array' => [
'value2',
@@ -216,6 +267,7 @@ class ConfigDifferTest extends UnitTestCase {
'id' => 'test.config.id',
'type' => 'new_type',
'true_value' => FALSE,
'null_value' => FALSE,
'nested_array' => [
'flat_array' => [
'value2',
@@ -284,12 +336,14 @@ class ConfigDifferTest extends UnitTestCase {
'orig' => [
'nested_array::flat_array::1 : value1',
'nested_array::flat_array::2 : value3',
'true_value : 1',
'null_value : null',
'true_value : true',
'type : old_type',
],
'closing' => [
'nested_array::flat_array::1 : value3',
'true_value : ',
'null_value : false',
'true_value : false',
'type : new_type',
],
],
@@ -2,169 +2,73 @@
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigLister;
use Drupal\Tests\UnitTestCase;
/**
* Tests the \Drupal\config_update\ConfigLister class.
* Tests the \Drupal\config_update\ConfigListerWithProviders class.
*
* The methods from \Drupal\config_update\ConfigLister are also tested.
*
* @group config_update
*
* @coversDefaultClass \Drupal\config_update\ConfigLister
* @coversDefaultClass \Drupal\config_update\ConfigListerWithProviders
*/
class ConfigListerTest extends UnitTestCase {
class ConfigListerTest extends ConfigUpdateUnitTestBase {
/**
* The config lister to test.
*
* @var \Drupal\config_update\ConfigLister
* @var \Drupal\config_update\ConfigListerWithProviders
*/
protected $configLister;
/**
* The mocked entity definition information.
* List of configuration by provider in the mocks.
*
* @var string[]
* This is an array whose keys are provider names, and whose values are
* each an array containing the provider type, the name of one config item
* mocked to be in config/install, and one in config/optional.
*
* @var array
*/
protected $entityDefinitionInformation;
protected $configProviderList = [
'foo_module' => ['module', 'foo.barbaz.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() {
$this->configLister = new ConfigLister($this->getEntityManagerMock(), $this->getConfigStorageMock('active'), $this->getConfigStorageMock('extension'), $this->getConfigStorageMock('optional'));
$lister = $this->getMockBuilder('Drupal\config_update\ConfigListerWithProviders')
->setConstructorArgs([
$this->getEntityManagerMock(),
$this->getConfigStorageMock('active'),
$this->getConfigStorageMock('extension'),
$this->getConfigStorageMock('optional'),
$this->getModuleHandlerMock(),
$this->getThemeHandlerMock(),
])
->setMethods(['listProvidedItems', 'getProfileName'])
->getMock();
$lister->method('getProfileName')
->willReturn('standard');
$map = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// 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')
->will($this->returnValueMap($map));
$this->configLister = $lister;
}
/**
* Creates a mock entity manager for the test.
*/
protected function getEntityManagerMock() {
// Make a list of fake entity definitions. Make sure they are not sorted,
// to test that the methods sort them. Also make sure there are a couple
// with prefixes that are subsets of each other.
$this->entityDefinitionInformation = [
['prefix' => 'foo.bar', 'type' => 'foo'],
['prefix' => 'foo.barbaz', 'type' => 'bar'],
['prefix' => 'baz.foo', 'type' => 'baz'],
];
$definitions = [];
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('isSubclassOf')
->willReturn(TRUE);
$def->getConfigPrefix();
$definitions[$info['type']] = $def;
}
$manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock();
$manager
->method('getDefinitions')
->willReturn($definitions);
return($manager);
}
/**
* Creates a mock config storage object for the test.
*
* @param string $type
* Type of storage object to return: 'active', 'extension', or 'optional'.
*/
protected function getConfigStorageMock($type) {
if ($type == 'active') {
$storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock();
// The only use of the read() method on active storage is
// with the core.extension config, to get the profile name.
$storage
->method('read')
->willReturn(['profile' => 'standard']);
$map = [
['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',
],
],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
elseif ($type == 'extension') {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$storage
->method('getComponentNames')
->willReturn([
'foo.bar.one' => 'ignored',
'foo.bar.two' => 'ignored',
'foo.bar.seven' => 'ignored',
'foo.barnot.three' => 'ignored',
'something.else' => 'ignored',
]);
$map = [
['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']],
['baz.foo'], [],
['',
[
'foo.bar.one',
'foo.bar.two',
'foo.bar.seven',
'foo.barbaz.four',
'foo.barnot.three',
'something.else',
],
],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
else {
$storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock();
$storage
->method('getComponentNames')
->willReturn([
'foo.barbaz.four' => 'ignored',
]);
$map = [
['foo.bar'], [],
['foo.barbaz', ['foo.barbaz.four']],
['', ['foo.barbaz.four']],
];
$storage
->method('listAll')
->will($this->returnValueMap($map));
}
return $storage;
}
/**
* @covers \Drupal\config_update\ConfigLister::listConfig
* @covers \Drupal\config_update\ConfigListerWithProviders::listConfig
* @dataProvider listConfigProvider
*/
public function testListConfig($a, $b, $expected) {
@@ -176,12 +80,8 @@ class ConfigListerTest extends UnitTestCase {
*/
public function listConfigProvider() {
return [
// Arguments are $list_type, $name.
// We cannot really test the extension types here, because they rely
// on the going out to the file system to find out what config objects
// are there. This is too complex to mock. It is tested in the tests for
// the report output in the config_update_ui module tests. Anyway, we
// can test the other types.
// Arguments are $list_type, $name, and return value is that list of
// configuration in active, extension, and optional storage.
['type', 'system.all',
[
[
@@ -220,11 +120,59 @@ class ConfigListerTest extends UnitTestCase {
],
],
['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'],
['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\ConfigLister::getType
* @covers \Drupal\config_update\ConfigListerWithProviders::getType
*/
public function testGetType() {
$return = $this->configLister->getType('not_in_list');
@@ -237,7 +185,7 @@ class ConfigListerTest extends UnitTestCase {
}
/**
* @covers \Drupal\config_update\ConfigLister::getTypeByPrefix
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeByPrefix
*/
public function testGetTypeByPrefix() {
$return = $this->configLister->getTypeByPrefix('not_in_list');
@@ -250,7 +198,7 @@ class ConfigListerTest extends UnitTestCase {
}
/**
* @covers \Drupal\config_update\ConfigLister::getTypeNameByConfigName
* @covers \Drupal\config_update\ConfigListerWithProviders::getTypeNameByConfigName
*/
public function testGetTypeNameByConfigName() {
$return = $this->configLister->getTypeNameByConfigName('not_in_list');
@@ -262,4 +210,84 @@ class ConfigListerTest extends UnitTestCase {
}
}
/**
* @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 ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Expected needs: key is item name, value is [type, provider name].
$expected[$info[1]] = [$info[0], $provider];
$expected[$info[2]] = [$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 function getConfigProviderProvider() {
$values = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// Values needs: [item, [type, provider name]].
$values[] = [$info[1], [$info[0], $provider]];
$values[] = [$info[2], [$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 function providerHasConfigProvider() {
$values = [];
foreach ($this->configProviderList as $provider => $info) {
// Info has: [type, install storage item, optional storage item].
// 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;
}
}
@@ -0,0 +1,356 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\config_update\ConfigReverter;
use Drupal\config_update\ConfigRevertInterface;
use Drupal\config_update\ConfigDeleteInterface;
/**
* 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() {
$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($result, $expected);
if ($result) {
// Verify that the config is correct after import, and logging worked.
$this->assertEquals($this->configStorage[$config_name], $config_after);
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::IMPORT);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* 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'],
],
[
'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($result, $expected);
if ($result) {
// Verify that the config is correct after revert, and logging worked.
$this->assertEquals($this->configStorage[$config_name], $config_after);
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::REVERT);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* 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().
[
'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) {
// Clear dispatch log.
$this->dispatchedEvents = [];
$save_config = $this->configStorage;
// Call the deleteer and test the Boolean result.
$result = $this->configReverter->delete($type, $name);
$this->assertEquals($result, $expected);
if ($result) {
// Verify that the config is missing after delete, and logging worked.
$this->assertTrue(!isset($this->configStorage[$config_name]));
$this->assertEquals(count($this->dispatchedEvents), 1);
$this->assertEquals($this->dispatchedEvents[0][0], ConfigDeleteInterface::DELETE);
}
else {
// Verify that the config didn't change and no events were logged.
$this->assertEquals($this->configStorage, $save_config);
$this->assertEquals(count($this->dispatchedEvents), 0);
}
}
/**
* Data provider for self:testDelete().
*/
public function deleteProvider() {
return [
// Elements: type, name, config name, return value.
[
'system.simple',
'in.extension',
'in.extension',
TRUE,
],
[
'foo',
'one',
'foo.bar.one',
TRUE,
],
[
'unknown',
'in.extension',
FALSE,
FALSE,
],
[
'system.simple',
'missing2',
'missing2',
FALSE,
],
];
}
}
@@ -0,0 +1,547 @@
<?php
namespace Drupal\Tests\config_update\Unit;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\EventDispatcher\Event;
/**
* 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')
->will($this->returnValueMap($map));
$manager
->method('getStorage')
->will($this->returnCallback([$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']],
];
$storage
->method('read')
->will($this->returnValueMap($map));
$storage
->method('listAll')
->will($this->returnValueMap($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')
->will($this->returnValueMap($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']],
['missing2', FALSE],
];
$storage
->method('read')
->will($this->returnValueMap($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')
->will($this->returnValueMap($this->configStorageOptionalInfo));
$map = [
['in.optional', ['in.optional' => 'optional']],
['in.both', ['in.both' => 'optional']],
['missing2', FALSE],
];
$storage
->method('read')
->will($this->returnValueMap($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')
->will($this->returnCallback([$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')
->will($this->returnCallback([$this, 'mockDispatch']));
return $event;
}
/**
* Mocks event dispatch.
*
* For \Symfony\Component\EventDispatcher\EventDispatchInterface::dispatch().
*/
public function mockDispatch($name, Event $event = NULL) {
$this->dispatchedEvents[] = [$name, $event];
}
/**
* 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')
->will($this->returnCallback([$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 isset($this->value[$key]) ? $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 = isset($this->value['_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;
}
}