ConfigUpdateTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. <?php
  2. namespace Drupal\Tests\config_update_ui\Functional;
  3. use Drupal\Tests\BrowserTestBase;
  4. /**
  5. * Verify the config revert report and its links.
  6. *
  7. * @group config_update
  8. */
  9. class ConfigUpdateTest extends BrowserTestBase {
  10. /**
  11. * Modules to enable.
  12. *
  13. * Use the Search module because it has two included config items in its
  14. * config/install, assuming node and user are also enabled.
  15. *
  16. * @var array
  17. */
  18. public static $modules = [
  19. 'config',
  20. 'config_update',
  21. 'config_update_ui',
  22. 'search',
  23. 'node',
  24. 'user',
  25. 'block',
  26. 'text',
  27. 'field',
  28. 'filter',
  29. ];
  30. /**
  31. * The admin user that will be created.
  32. *
  33. * @var object
  34. */
  35. protected $adminUser;
  36. /**
  37. * {@inheritdoc}
  38. */
  39. protected function setUp() {
  40. parent::setUp();
  41. // Create user and log in.
  42. $this->adminUser = $this->createUser([
  43. 'access administration pages',
  44. 'administer search',
  45. 'view config updates report',
  46. 'synchronize configuration',
  47. 'export configuration',
  48. 'import configuration',
  49. 'revert configuration',
  50. 'delete configuration',
  51. 'administer filters',
  52. ]);
  53. $this->drupalLogin($this->adminUser);
  54. // Make sure local tasks and page title are showing.
  55. $this->placeBlock('local_tasks_block');
  56. $this->placeBlock('page_title_block');
  57. // Load the Drush include file so that its functions can be tested, plus
  58. // the Drush testing include file.
  59. module_load_include('inc', 'config_update_ui', 'config_update_ui.drush_testing');
  60. module_load_include('inc', 'config_update_ui', 'config_update_ui.drush');
  61. }
  62. /**
  63. * Tests the config report and its linked pages.
  64. */
  65. public function testConfigReport() {
  66. // Test links to report page.
  67. $this->drupalGet('admin/config/development/configuration');
  68. $this->clickLink('Updates report');
  69. $this->assertNoReport();
  70. // Verify the Drush list types command.
  71. $output = implode("\n", drush_config_update_ui_config_list_types());
  72. $this->assertTrue(strpos($output, 'search_page') !== FALSE);
  73. $this->assertTrue(strpos($output, 'node_type') !== FALSE);
  74. $this->assertTrue(strpos($output, 'user_role') !== FALSE);
  75. $this->assertTrue(strpos($output, 'block') !== FALSE);
  76. // Verify some empty reports.
  77. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  78. $this->assertReport('Search page', [], [], [], []);
  79. $this->assertDrushReports('type', 'search_page', [], [], [], []);
  80. // Module, theme, and profile reports have no 'added' section.
  81. $this->drupalGet('admin/config/development/configuration/report/module/search');
  82. $this->assertReport('Search module', [], [], [], [], ['added']);
  83. $this->assertDrushReports('module', 'search', [], [], [], []);
  84. $this->drupalGet('admin/config/development/configuration/report/theme/classy');
  85. $this->assertReport('Classy theme', [], [], [], [], ['added']);
  86. $this->assertDrushReports('theme', 'classy', [], [], [], []);
  87. $inactive = ['locale.settings' => 'Simple configuration'];
  88. $this->drupalGet('admin/config/development/configuration/report/profile');
  89. $this->assertReport('Testing profile', [], [], [], $inactive, ['added']);
  90. // The locale.settings line should show that the Testing profile is the
  91. // provider.
  92. $session = $this->assertSession();
  93. $session->pageTextContains('Testing profile');
  94. $this->assertDrushReports('profile', '', [], [], [], array_keys($inactive));
  95. // Verify that the user search page cannot be imported (because it already
  96. // exists).
  97. $this->drupalGet('admin/config/development/configuration/report/import/search_page/user_search');
  98. $session = $this->assertSession();
  99. $session->statusCodeEquals(404);
  100. // Delete the user search page from the search UI and verify report for
  101. // both the search page config type and user module.
  102. $this->drupalGet('admin/config/search/pages');
  103. $this->clickLink('Delete');
  104. $this->drupalPostForm(NULL, [], 'Delete');
  105. $inactive = ['search.page.user_search' => 'Users'];
  106. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  107. $this->assertReport('Search page', [], [], [], $inactive);
  108. // The search.page.user_search line should show that the User module is the
  109. // provider.
  110. $session = $this->assertSession();
  111. $session->pageTextContains('User module');
  112. $this->assertDrushReports('type', 'search_page', [], [], [], array_keys($inactive));
  113. $this->drupalGet('admin/config/development/configuration/report/module/user');
  114. $this->assertReport('User module', [], [], [], $inactive, ['added', 'changed']);
  115. $this->assertDrushReports('module', 'user', [], [], [],
  116. [
  117. 'rdf.mapping.user.user',
  118. 'search.page.user_search',
  119. 'views.view.user_admin_people',
  120. 'views.view.who_s_new',
  121. 'views.view.who_s_online',
  122. ], ['changed']);
  123. // Verify that the user search page cannot be reverted (because it does
  124. // not already exist).
  125. $this->drupalGet('admin/config/development/configuration/report/revert/search_page/user_search');
  126. $session = $this->assertSession();
  127. $session->statusCodeEquals(404);
  128. // Verify that the delete URL doesn't work either.
  129. $this->drupalGet('admin/config/development/configuration/report/delete/search_page/user_search');
  130. $session = $this->assertSession();
  131. $session->statusCodeEquals(404);
  132. // Use the import link to get it back. Do this from the search page
  133. // report to make sure we are importing the right config.
  134. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  135. $this->clickLink('Import from source');
  136. $this->drupalPostForm(NULL, [], 'Import');
  137. $session = $this->assertSession();
  138. $session->pageTextContains('has been imported');
  139. $this->assertNoReport();
  140. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  141. $this->assertReport('Search page', [], [], [], []);
  142. $this->assertDrushReports('type', 'search_page', [], [], [], []);
  143. // Verify that after import, there is no config hash generated.
  144. $this->drupalGet('admin/config/development/configuration/single/export/search_page/user_search');
  145. $session = $this->assertSession();
  146. $session->pageTextContains('id: user_search');
  147. $session->pageTextNotContains('default_config_hash:');
  148. // Test importing again, this time using the Drush import command.
  149. $this->drupalGet('admin/config/search/pages');
  150. $this->clickLink('Delete');
  151. $this->drupalPostForm(NULL, [], 'Delete');
  152. $inactive = ['search.page.user_search' => 'Users'];
  153. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  154. $this->assertReport('Search page', [], [], [], $inactive);
  155. drush_config_update_ui_config_import_missing('search.page.user_search');
  156. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  157. $this->assertReport('Search page', [], [], [], []);
  158. // Edit the node search page from the search UI and verify report.
  159. $this->drupalGet('admin/config/search/pages');
  160. $this->clickLink('Edit');
  161. $this->drupalPostForm(NULL, [
  162. 'label' => 'New label',
  163. 'path' => 'new_path',
  164. ], 'Save search page');
  165. $changed = ['search.page.node_search' => 'New label'];
  166. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  167. $this->assertReport('Search page', [], [], $changed, []);
  168. $this->assertDrushReports('type', 'search_page', [], [], array_keys($changed), []);
  169. // Test the show differences link.
  170. $this->clickLink('Show differences');
  171. $session = $this->assertSession();
  172. $session->pageTextContains('Content');
  173. $session->pageTextContains('New label');
  174. $session->pageTextContains('node');
  175. $session->pageTextContains('new_path');
  176. // Test the show differences Drush command.
  177. $output = drush_config_update_ui_config_diff('search.page.node_search');
  178. $this->assertTrue(strpos($output, 'Content') !== FALSE);
  179. $this->assertTrue(strpos($output, 'New label') !== FALSE);
  180. $this->assertTrue(strpos($output, 'node') !== FALSE);
  181. $this->assertTrue(strpos($output, 'new_path') !== FALSE);
  182. // Test the Back link.
  183. $this->clickLink("Back to 'Updates report' page.");
  184. $this->assertNoReport();
  185. // Test the export link.
  186. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  187. $this->clickLink('Export');
  188. $session = $this->assertSession();
  189. $session->pageTextContains('Here is your configuration:');
  190. $session->pageTextContains('id: node_search');
  191. $session->pageTextContains('New label');
  192. $session->pageTextContains('path: new_path');
  193. $session->pageTextContains('search.page.node_search.yml');
  194. // Grab the uuid and hash lines from the exported config for the next test.
  195. $text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
  196. $matches = [];
  197. preg_match('|^.*uuid:.*$|m', $text, $matches);
  198. $uuid_line = trim($matches[0]);
  199. preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
  200. $hash_line = trim($matches[0]);
  201. // Test reverting.
  202. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  203. $this->clickLink('Revert to source');
  204. $session = $this->assertSession();
  205. $session->pageTextContains('Are you sure you want to revert');
  206. $session->pageTextContains('Search page');
  207. $session->pageTextContains('node_search');
  208. $session->pageTextContains('Customizations will be lost. This action cannot be undone');
  209. $this->drupalPostForm(NULL, [], 'Revert');
  210. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  211. $this->assertReport('Search page', [], [], [], []);
  212. // Verify that the uuid and hash keys were retained in the revert.
  213. $this->drupalGet('admin/config/development/configuration/single/export/search_page/node_search');
  214. $session = $this->assertSession();
  215. $session->pageTextContains('id: node_search');
  216. $session->pageTextContains($uuid_line);
  217. $session->pageTextContains($hash_line);
  218. // Test reverting again, this time using Drush single revert command.
  219. $this->drupalGet('admin/config/search/pages');
  220. $this->clickLink('Edit');
  221. $this->drupalPostForm(NULL, [
  222. 'label' => 'New label',
  223. 'path' => 'new_path',
  224. ], 'Save search page');
  225. $changed = ['search.page.node_search' => 'New label'];
  226. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  227. $this->assertReport('Search page', [], [], $changed, []);
  228. drush_config_update_ui_config_revert('search.page.node_search');
  229. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  230. $this->assertReport('Search page', [], [], [], []);
  231. // Test reverting again, this time using Drush multiple revert command.
  232. $this->drupalGet('admin/config/search/pages');
  233. $this->clickLink('Edit');
  234. $this->drupalPostForm(NULL, [
  235. 'label' => 'New label',
  236. 'path' => 'new_path',
  237. ], 'Save search page');
  238. $changed = ['search.page.node_search' => 'New label'];
  239. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  240. $this->assertReport('Search page', [], [], $changed, []);
  241. drush_config_update_ui_config_revert_multiple('type', 'search_page');
  242. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  243. $this->assertReport('Search page', [], [], [], []);
  244. // Add a new search page from the search UI and verify report.
  245. $this->drupalPostForm('admin/config/search/pages', [
  246. 'search_type' => 'node_search',
  247. ], 'Add search page');
  248. $this->drupalPostForm(NULL, [
  249. 'label' => 'test',
  250. 'id' => 'test',
  251. 'path' => 'test',
  252. ], 'Save');
  253. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  254. $added = ['search.page.test' => 'test'];
  255. $this->assertReport('Search page', [], $added, [], []);
  256. $this->assertDrushReports('type', 'search_page', [], array_keys($added), [], []);
  257. // Test the export link.
  258. $this->clickLink('Export');
  259. $session = $this->assertSession();
  260. $session->pageTextContains('Here is your configuration:');
  261. $session->pageTextContains('id: test');
  262. $session->pageTextContains('label: test');
  263. $session->pageTextContains('path: test');
  264. $session->pageTextContains('search.page.test.yml');
  265. // Test the delete link.
  266. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  267. $this->clickLink('Delete');
  268. $session = $this->assertSession();
  269. $session->pageTextContains('Are you sure');
  270. $session->pageTextContains('cannot be undone');
  271. $this->drupalPostForm(NULL, [], 'Delete');
  272. $session = $this->assertSession();
  273. $session->pageTextContains('has been deleted');
  274. // And verify the report again.
  275. $this->drupalGet('admin/config/development/configuration/report/type/search_page');
  276. $this->assertReport('Search page', [], [], [], []);
  277. // Change the search module config and verify the actions work for
  278. // simple config.
  279. $this->drupalPostForm('admin/config/search/pages', [
  280. 'minimum_word_size' => 4,
  281. ], 'Save configuration');
  282. $changed = ['search.settings' => 'search.settings'];
  283. $this->drupalGet('admin/config/development/configuration/report/module/search');
  284. $this->assertReport('Search module', [], [], $changed, [], ['added']);
  285. $this->clickLink('Show differences');
  286. $session = $this->assertSession();
  287. $session->pageTextContains('Config difference for Simple configuration search.settings');
  288. $session->pageTextContains('index::minimum_word_size');
  289. $session->pageTextContains('4');
  290. $this->drupalGet('admin/config/development/configuration/report/module/search');
  291. $this->clickLink('Export');
  292. $session = $this->assertSession();
  293. $session->pageTextContains('minimum_word_size: 4');
  294. // Grab the hash line for the next test.
  295. $text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml());
  296. $matches = [];
  297. preg_match('|^.*default_config_hash:.*$|m', $text, $matches);
  298. $hash_line = trim($matches[0]);
  299. $this->drupalGet('admin/config/development/configuration/report/module/search');
  300. $this->clickLink('Revert to source');
  301. $this->drupalPostForm(NULL, [], 'Revert');
  302. // Verify that the hash was retained in the revert.
  303. $this->drupalGet('admin/config/development/configuration/single/export/system.simple/search.settings');
  304. $session = $this->assertSession();
  305. $session->pageTextContains($hash_line);
  306. $this->drupalGet('admin/config/development/configuration/report/module/search');
  307. $this->assertReport('Search module', [], [], [], [], ['added']);
  308. // Edit the plain_text filter from the filter UI and verify report.
  309. // The filter_format config type uses a label key other than 'label'.
  310. $this->drupalPostForm('admin/config/content/formats/manage/plain_text', [
  311. 'name' => 'New label',
  312. ], 'Save configuration');
  313. $changed = ['filter.format.plain_text' => 'New label'];
  314. $this->drupalGet('admin/config/development/configuration/report/type/filter_format');
  315. $this->assertReport('Text format', [], [], $changed, []);
  316. // Verify that we can revert non-entity configuration in Drush. Issue:
  317. // https://www.drupal.org/project/config_update/issues/2935395
  318. drush_config_update_ui_config_revert('system.date');
  319. }
  320. /**
  321. * Asserts that the report page has the correct content.
  322. *
  323. * Assumes you are already on the report page.
  324. *
  325. * @param string $title
  326. * Report title to check for.
  327. * @param string[] $missing
  328. * Array of items that should be listed as missing, name => label.
  329. * @param string[] $added
  330. * Array of items that should be listed as added, name => label.
  331. * @param string[] $changed
  332. * Array of items that should be listed as changed, name => label.
  333. * @param string[] $inactive
  334. * Array of items that should be listed as inactive, name => label.
  335. * @param string[] $skip
  336. * Array of report sections to skip checking.
  337. */
  338. protected function assertReport($title, array $missing, array $added, array $changed, array $inactive, array $skip = []) {
  339. $session = $this->assertSession();
  340. $session->pageTextContains('Configuration updates report for ' . $title);
  341. $session->pageTextContains('Generate new report');
  342. if (!in_array('missing', $skip)) {
  343. $session->pageTextContains('Missing configuration items');
  344. if (count($missing)) {
  345. foreach ($missing as $name => $label) {
  346. $session->pageTextContains($name);
  347. $session->pageTextContains($label);
  348. }
  349. $session->pageTextNotContains('None: all provided configuration items are in your active configuration.');
  350. }
  351. else {
  352. $session->pageTextContains('None: all provided configuration items are in your active configuration.');
  353. }
  354. }
  355. if (!in_array('inactive', $skip)) {
  356. $session->pageTextContains('Inactive optional items');
  357. if (count($inactive)) {
  358. foreach ($inactive as $name => $label) {
  359. $session->pageTextContains($name);
  360. $session->pageTextContains($label);
  361. }
  362. $session->pageTextNotContains('None: all optional configuration items are in your active configuration.');
  363. }
  364. else {
  365. $session->pageTextContains('None: all optional configuration items are in your active configuration.');
  366. }
  367. }
  368. if (!in_array('added', $skip)) {
  369. $session->pageTextContains('Added configuration items');
  370. if (count($added)) {
  371. foreach ($added as $name => $label) {
  372. $session->pageTextContains($name);
  373. $session->pageTextContains($label);
  374. }
  375. $session->pageTextNotContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
  376. }
  377. else {
  378. $session->pageTextContains('None: all active configuration items of this type were provided by modules, themes, or install profile.');
  379. }
  380. }
  381. if (!in_array('changed', $skip)) {
  382. $session->pageTextContains('Changed configuration items');
  383. if (count($changed)) {
  384. foreach ($changed as $name => $label) {
  385. $session->pageTextContains($name);
  386. $session->pageTextContains($label);
  387. }
  388. $session->pageTextNotContains('None: no active configuration items differ from their current provided versions.');
  389. }
  390. else {
  391. $session->pageTextContains('None: no active configuration items differ from their current provided versions.');
  392. }
  393. }
  394. }
  395. /**
  396. * Asserts that the Drush reports have the correct content.
  397. *
  398. * @param string $type
  399. * Type of report to run (type, module, theme, etc.).
  400. * @param string $name
  401. * Name of that type to run (e.g., module machine name).
  402. * @param string[] $missing
  403. * Array of config items that should be listed as missing.
  404. * @param string[] $added
  405. * Array of config items that should be listed as added.
  406. * @param string[] $changed
  407. * Array of config items that should be listed as changed.
  408. * @param string[] $inactive
  409. * Array of config items that should be listed as inactive.
  410. * @param string[] $skip
  411. * Array of report sections to skip checking.
  412. */
  413. protected function assertDrushReports($type, $name, array $missing, array $added, array $changed, array $inactive, array $skip = []) {
  414. if (!in_array('missing', $skip)) {
  415. $output = drush_config_update_ui_config_missing_report($type, $name);
  416. $this->assertEquals(count($output), count($missing), 'Drush missing report has correct number of items');
  417. if (count($missing)) {
  418. foreach ($missing as $item) {
  419. $this->assertTrue(in_array($item, $output), "Item $item is in the Drush missing report");
  420. }
  421. }
  422. }
  423. if (!in_array('added', $skip) && $type == 'type') {
  424. $output = drush_config_update_ui_config_added_report($name);
  425. $this->assertEquals(count($output), count($added), 'Drush added report has correct number of items');
  426. if (count($added)) {
  427. foreach ($added as $item) {
  428. $this->assertTrue(in_array($item, $output), "Item $item is in the Drush added report");
  429. }
  430. }
  431. }
  432. if (!in_array('changed', $skip)) {
  433. $output = drush_config_update_ui_config_different_report($type, $name);
  434. $this->assertEquals(count($output), count($changed), 'Drush changed report has correct number of items');
  435. if (count($changed)) {
  436. foreach ($changed as $item) {
  437. $this->assertTrue(in_array($item, $output), "Item $item is in the Drush changed report");
  438. }
  439. }
  440. }
  441. if (!in_array('inactive', $skip)) {
  442. $output = drush_config_update_ui_config_inactive_report($type, $name);
  443. $this->assertEquals(count($output), count($inactive), 'Drush inactive report has correct number of items');
  444. if (count($inactive)) {
  445. foreach ($inactive as $item) {
  446. $this->assertTrue(in_array($item, $output), "Item $item is in the Drush inactive report");
  447. }
  448. }
  449. }
  450. }
  451. /**
  452. * Asserts that the report is not shown.
  453. *
  454. * Assumes you are already on the report form page.
  455. */
  456. protected function assertNoReport() {
  457. $session = $this->assertSession();
  458. $session->pageTextContains('Report type');
  459. $session->pageTextContains('Full report');
  460. $session->pageTextContains('Single configuration type');
  461. $session->pageTextContains('Single module');
  462. $session->pageTextContains('Single theme');
  463. $session->pageTextContains('Installation profile');
  464. $session->pageTextContains('Updates report');
  465. $session->pageTextNotContains('Missing configuration items');
  466. $session->pageTextNotContains('Added configuration items');
  467. $session->pageTextNotContains('Changed configuration items');
  468. $session->pageTextNotContains('Unchanged configuration items');
  469. // Verify that certain report links are shown or not shown. For extensions,
  470. // only extensions that have configuration should be shown.
  471. // Modules.
  472. $session->linkExists('Search');
  473. $session->linkExists('Field');
  474. $session->linkNotExists('Configuration Update Base');
  475. $session->linkNotExists('Configuration Update Reports');
  476. // Themes.
  477. $session->linkNotExists('Stark');
  478. $session->linkNotExists('Classy');
  479. // Profiles.
  480. $session->linkExists('Testing');
  481. // Configuration types.
  482. $session->linkExists('Everything');
  483. $session->linkExists('Simple configuration');
  484. $session->linkExists('Search page');
  485. }
  486. }