3 namespace Drupal\Tests\migrate_drupal_ui\Functional;
5 use Drupal\Core\Database\Database;
6 use Drupal\migrate\Plugin\MigrateIdMapInterface;
7 use Drupal\migrate_drupal\MigrationConfigurationTrait;
8 use Drupal\Tests\BrowserTestBase;
9 use Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait;
10 use Drupal\Tests\WebAssert;
13 * Provides a base class for testing migration upgrades in the UI.
15 abstract class MigrateUpgradeTestBase extends BrowserTestBase {
17 use MigrationConfigurationTrait;
18 use CreateTestContentEntitiesTrait;
21 * Use the Standard profile to test help implementations of many core modules.
25 protected $profile = 'standard';
28 * The source database connection.
30 * @var \Drupal\Core\Database\Connection
32 protected $sourceDatabase;
37 protected function setUp() {
39 $this->createMigrationConnection();
40 $this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui');
42 // Log in as user 1. Migrations in the UI can only be performed as user 1.
43 $this->drupalLogin($this->rootUser);
47 * Loads a database fixture into the source database connection.
50 * Path to the dump file.
52 protected function loadFixture($path) {
53 $default_db = Database::getConnection()->getKey();
54 Database::setActiveConnection($this->sourceDatabase->getKey());
56 if (substr($path, -3) == '.gz') {
57 $path = 'compress.zlib://' . $path;
61 Database::setActiveConnection($default_db);
65 * Changes the database connection to the prefixed one.
67 * @todo Remove when we don't use global. https://www.drupal.org/node/2552791
69 protected function createMigrationConnection() {
70 $connection_info = Database::getConnectionInfo('default')['default'];
71 if ($connection_info['driver'] === 'sqlite') {
72 // Create database file in the test site's public file directory so that
73 // \Drupal\simpletest\TestBase::restoreEnvironment() will delete this once
74 // the test is complete.
75 $file = $this->publicFilesDirectory . '/' . $this->testId . '-migrate.db.sqlite';
77 $connection_info['database'] = $file;
78 $connection_info['prefix'] = '';
81 $prefix = is_array($connection_info['prefix']) ? $connection_info['prefix']['default'] : $connection_info['prefix'];
82 // Simpletest uses fixed length prefixes. Create a new prefix for the
83 // source database. Adding to the end of the prefix ensures that
84 // \Drupal\simpletest\TestBase::restoreEnvironment() will remove the
86 $connection_info['prefix'] = $prefix . '0';
89 Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info);
95 protected function tearDown() {
96 Database::removeConnection('migrate_drupal_ui');
101 * Tests the displayed upgrade paths.
103 * @param \Drupal\Tests\WebAssert $session
104 * The web-assert session.
105 * @param array $available_paths
106 * An array of modules that will be upgraded.
107 * @param array $missing_paths
108 * An array of modules that will not be upgraded.
110 protected function assertUpgradePaths(WebAssert $session, array $available_paths, array $missing_paths) {
111 // Test the available migration paths.
112 foreach ($available_paths as $available) {
113 $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']");
114 $session->elementNotExists('xpath', "//span[contains(@class, 'error') and text() = '$available']");
117 // Test the missing migration paths.
118 foreach ($missing_paths as $missing) {
119 $session->elementExists('xpath', "//span[contains(@class, 'error') and text() = '$missing']");
120 $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']");
123 // Test the total count of missing and available paths.
124 $session->elementsCount('xpath', "//span[contains(@class, 'upgrade-analysis-report__status-icon--error')]", count($missing_paths));
125 $session->elementsCount('xpath', "//span[contains(@class, 'upgrade-analysis-report__status-icon--checked')]", count($available_paths));
129 * Gets the source base path for the concrete test.
132 * The source base path.
134 abstract protected function getSourceBasePath();
137 * Gets the expected number of entities per entity type after migration.
140 * An array of expected counts keyed by entity type ID.
142 abstract protected function getEntityCounts();
145 * Gets the available upgrade paths.
148 * An array of available upgrade paths.
150 abstract protected function getAvailablePaths();
153 * Gets the missing upgrade paths.
156 * An array of missing upgrade paths.
158 abstract protected function getMissingPaths();
161 * Gets expected number of entities per entity after incremental migration.
164 * An array of expected counts keyed by entity type ID.
166 abstract protected function getEntityCountsIncremental();
169 * Helper method to assert the text on the 'Upgrade analysis report' page.
171 * @param \Drupal\Tests\WebAssert $session
172 * The current session.
173 * @param array $all_available
174 * Array of modules that will be upgraded.
175 * @param array $all_missing
176 * Array of modules that will not be upgraded.
178 protected function assertReviewPage(WebAssert $session, array $all_available, array $all_missing) {
179 $this->assertText('What will be upgraded?');
181 // Ensure there are no errors about the missing modules from the test module.
182 $session->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.'));
183 $session->pageTextNotContains(t('Source module not found for migration_provider_test.'));
184 $session->pageTextNotContains(t('Destination module not found for migration_provider_test'));
185 // Ensure there are no errors about any other missing migration providers.
186 $session->pageTextNotContains(t('module not found'));
188 // Test the available migration paths.
189 foreach ($all_available as $available) {
190 $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']");
191 $session->elementNotExists('xpath', "//span[contains(@class, 'error') and text() = '$available']");
194 // Test the missing migration paths.
195 foreach ($all_missing as $missing) {
196 $session->elementExists('xpath', "//span[contains(@class, 'error') and text() = '$missing']");
197 $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']");
202 * Helper method that asserts text on the ID conflict form.
204 * @param \Drupal\Tests\WebAssert $session
205 * The current session.
207 * The current session.
209 protected function assertIdConflict(WebAssert $session) {
210 $session->pageTextContains('WARNING: Content may be overwritten on your new site.');
211 $session->pageTextContains('There is conflicting content of these types:');
212 $session->pageTextContains('custom blocks');
213 $session->pageTextContains('custom menu links');
214 $session->pageTextContains('files');
215 $session->pageTextContains('taxonomy terms');
216 $session->pageTextContains('users');
217 $session->pageTextContains('comments');
218 $session->pageTextContains('content item revisions');
219 $session->pageTextContains('content items');
220 $session->pageTextContains('There is translated content of these types:');
224 * Checks that migrations have been performed successfully.
226 * @param array $expected_counts
227 * The expected counts of each entity type.
228 * @param int $version
229 * The Drupal version.
231 protected function assertMigrationResults(array $expected_counts, $version) {
232 // Have to reset all the statics after migration to ensure entities are
235 foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type) {
236 $real_count = (int) \Drupal::entityQuery($entity_type)->count()->execute();
237 $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0;
238 $this->assertSame($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count.");
241 $plugin_manager = \Drupal::service('plugin.manager.migration');
242 /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */
243 $all_migrations = $plugin_manager->createInstancesByTag('Drupal ' . $version);
244 foreach ($all_migrations as $migration) {
245 $id_map = $migration->getIdMap();
246 foreach ($id_map as $source_id => $map) {
247 // Convert $source_id into a keyless array so that
248 // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as
250 $source_id_values = array_values(unserialize($source_id));
251 $row = $id_map->getRowBySource($source_id_values);
252 $destination = serialize($id_map->currentDestination());
253 $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status'];
254 // A completed migration should have maps with
255 // MigrateIdMapInterface::STATUS_IGNORED or
256 // MigrateIdMapInterface::STATUS_IMPORTED.
257 $this->assertNotSame(MigrateIdMapInterface::STATUS_FAILED, $row['source_row_status'], $message);
258 $this->assertNotSame(MigrateIdMapInterface::STATUS_NEEDS_UPDATE, $row['source_row_status'], $message);