1<?php 2 3namespace Drupal\Tests\migrate_drupal_ui\Functional; 4 5use Drupal\Core\Database\Database; 6use Drupal\migrate\Plugin\MigrateIdMapInterface; 7use Drupal\migrate_drupal\MigrationConfigurationTrait; 8use Drupal\Tests\BrowserTestBase; 9use Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait; 10use Drupal\Tests\WebAssert; 11 12/** 13 * Provides a base class for testing migration upgrades in the UI. 14 */ 15abstract class MigrateUpgradeTestBase extends BrowserTestBase { 16 17 use MigrationConfigurationTrait; 18 use CreateTestContentEntitiesTrait; 19 20 /** 21 * Use the Standard profile to test help implementations of many core modules. 22 * 23 * @var string 24 */ 25 protected $profile = 'standard'; 26 27 /** 28 * The source database connection. 29 * 30 * @var \Drupal\Core\Database\Connection 31 */ 32 protected $sourceDatabase; 33 34 /** 35 * {@inheritdoc} 36 */ 37 protected function setUp() { 38 parent::setUp(); 39 $this->createMigrationConnection(); 40 $this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui'); 41 42 // Log in as user 1. Migrations in the UI can only be performed as user 1. 43 $this->drupalLogin($this->rootUser); 44 } 45 46 /** 47 * Loads a database fixture into the source database connection. 48 * 49 * @param string $path 50 * Path to the dump file. 51 */ 52 protected function loadFixture($path) { 53 $default_db = Database::getConnection()->getKey(); 54 Database::setActiveConnection($this->sourceDatabase->getKey()); 55 56 if (substr($path, -3) == '.gz') { 57 $path = 'compress.zlib://' . $path; 58 } 59 require $path; 60 61 Database::setActiveConnection($default_db); 62 } 63 64 /** 65 * Changes the database connection to the prefixed one. 66 * 67 * @todo Remove when we don't use global. https://www.drupal.org/node/2552791 68 */ 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'; 76 touch($file); 77 $connection_info['database'] = $file; 78 $connection_info['prefix'] = ''; 79 } 80 else { 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 85 // additional tables. 86 $connection_info['prefix'] = $prefix . '0'; 87 } 88 89 Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info); 90 } 91 92 /** 93 * {@inheritdoc} 94 */ 95 protected function tearDown() { 96 Database::removeConnection('migrate_drupal_ui'); 97 parent::tearDown(); 98 } 99 100 /** 101 * Tests the displayed upgrade paths. 102 * 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. 109 */ 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']"); 115 } 116 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']"); 121 } 122 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)); 126 } 127 128 /** 129 * Gets the source base path for the concrete test. 130 * 131 * @return string 132 * The source base path. 133 */ 134 abstract protected function getSourceBasePath(); 135 136 /** 137 * Gets the expected number of entities per entity type after migration. 138 * 139 * @return int[] 140 * An array of expected counts keyed by entity type ID. 141 */ 142 abstract protected function getEntityCounts(); 143 144 /** 145 * Gets the available upgrade paths. 146 * 147 * @return string[] 148 * An array of available upgrade paths. 149 */ 150 abstract protected function getAvailablePaths(); 151 152 /** 153 * Gets the missing upgrade paths. 154 * 155 * @return string[] 156 * An array of missing upgrade paths. 157 */ 158 abstract protected function getMissingPaths(); 159 160 /** 161 * Gets expected number of entities per entity after incremental migration. 162 * 163 * @return int[] 164 * An array of expected counts keyed by entity type ID. 165 */ 166 abstract protected function getEntityCountsIncremental(); 167 168 /** 169 * Helper method to assert the text on the 'Upgrade analysis report' page. 170 * 171 * @param \Drupal\Tests\WebAssert $session 172 * The web-assert session. 173 * @param array $available_paths 174 * An array of modules that will be upgraded. 175 * @param array $missing_paths 176 * An array of modules that will not be upgraded. 177 */ 178 protected function assertReviewPage(WebAssert $session, array $available_paths, array $missing_paths) { 179 $this->assertText('What will be upgraded?'); 180 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')); 187 188 $this->assertUpgradePaths($session, $available_paths, $missing_paths); 189 } 190 191 /** 192 * Helper method that asserts text on the ID conflict form. 193 * 194 * @param \Drupal\Tests\WebAssert $session 195 * The current session. 196 * @param array $entity_types 197 * An array of entity types 198 */ 199 protected function assertIdConflict(WebAssert $session, array $entity_types) { 200 /** @var \Drupal\ $entity_type_manager */ 201 $entity_type_manager = \Drupal::service('entity_type.manager'); 202 203 $session->pageTextContains('WARNING: Content may be overwritten on your new site.'); 204 $session->pageTextContains('There is conflicting content of these types:'); 205 $this->assertNotEmpty($entity_types, 'No entity types provided to \Drupal\Tests\migrate_drupal_ui\Functional\MigrateUpgradeTestBase::assertIdConflict()'); 206 foreach ($entity_types as $entity_type) { 207 $label = $entity_type_manager->getDefinition($entity_type)->getPluralLabel(); 208 $session->pageTextContains($label); 209 } 210 $session->pageTextContainsOnce('content items'); 211 $session->pageTextContains('There is translated content of these types:'); 212 } 213 214 /** 215 * Checks that migrations have been performed successfully. 216 * 217 * @param array $expected_counts 218 * The expected counts of each entity type. 219 * @param int $version 220 * The Drupal version. 221 */ 222 protected function assertMigrationResults(array $expected_counts, $version) { 223 // Have to reset all the statics after migration to ensure entities are 224 // loadable. 225 $this->resetAll(); 226 // Check that the expected number of entities is the same as the actual 227 // number of entities. 228 $entity_definitions = array_keys(\Drupal::entityTypeManager()->getDefinitions()); 229 $expected_count_keys = array_keys($expected_counts); 230 sort($entity_definitions); 231 sort($expected_count_keys); 232 $this->assertSame($expected_count_keys, $entity_definitions); 233 234 // Assert the correct number of entities exist. 235 foreach ($entity_definitions as $entity_type) { 236 $real_count = (int) \Drupal::entityQuery($entity_type)->count()->execute(); 237 $expected_count = $expected_counts[$entity_type]; 238 $this->assertSame($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count."); 239 } 240 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 249 // expected. 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); 259 } 260 } 261 } 262 263} 264