clean install

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

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Tests\BrowserTestBase;
use Drush\TestTraits\DrushTestTrait;
/**
* Test that batch import runs correctly in drush command.
*
* @group migrate_tools
*/
final class DrushBatchImportTest extends BrowserTestBase {
use DrushTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_tools_test',
'migrate_tools',
'migrate_plus',
'taxonomy',
'text',
'system',
'user',
];
/**
* Tests that a batch import run from a custom drush command succeeds.
*/
public function testBatchImportInDrushComand(): void {
$this->drush('migrate:batch-import-fruit');
$migration = \Drupal::service('plugin.manager.migration')->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$this->assertSame(3, $id_map->importedCount());
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Tests\BrowserTestBase;
use Drush\TestTraits\DrushTestTrait;
/**
* Execute drush on fully functional website using source generators.
*
* @group migrate_tools
*/
final class DrushCommandsGeneratorTest extends BrowserTestBase {
use DrushTestTrait;
/**
* The source CSV data.
*
* @var string
*/
private string $sourceData;
/**
* {@inheritdoc}
*/
protected static $modules = [
'csv_source_test',
'migrate',
'migrate_plus',
'migrate_source_csv',
'migrate_tools',
'taxonomy',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Setup the file system so we create the source CSV.
$this->container->get('stream_wrapper_manager')->registerWrapper('public', PublicStream::class, StreamWrapperInterface::NORMAL);
$fs = \Drupal::service('file_system');
$fs->mkdir('public://sites/default/files', NULL, TRUE);
// The source data for this test.
$this->sourceData = <<<'EOD'
vid,name,description,hierarchy,weight
tags,Tags,Use tags to group articles,0,0
forums,Sujet de discussion,Forum navigation vocabulary,1,0
test_vocabulary,Test Vocabulary,This is the vocabulary description,1,0
genre,Genre,Genre description,1,0
EOD;
// Write the data to the filepath given in the test migration.
file_put_contents('public://test.csv', $this->sourceData);
}
/**
* Tests synced import.
*/
public function testSyncImport(): void {
$this->drush('mim', ['csv_source_test']);
$this->assertStringContainsString('1/4', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/4\/4[^\n]+\[notice\][^\n]+Processed 4 items \(4 created, 0 updated, 0 failed, 0 ignored\) - done with \'csv_source_test\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('5/5', $this->getErrorOutput());
$vocabulary = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('genre');
$this->assertEquals('Genre', $vocabulary->label());
$this->assertEquals(4, \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->getQuery()->accessCheck(TRUE)->count()->execute());
// Remove one vocab and replace with another.
$this->sourceData = str_replace('genre,Genre,Genre description,1,0', 'fruit,Fruit,Fruit description,1,0', $this->sourceData);
file_put_contents('public://test.csv', $this->sourceData);
// Execute sync migration.
$this->drush('mim', ['csv_source_test'], ['sync' => NULL, 'update' => NULL]);
$this->assertMatchesRegularExpression('/1\/4[^\n]+25%[^\n]+\[notice\][^\n]+Rolled back 1 item - done with \'csv_source_test\'/', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/4\/4[^\n]+100%/', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/5\/5[^\n]+100%[^\n]+\[notice\][^\n]+Processed 4 items \(1 created, 3 updated, 0 failed, 0 ignored\) - done with \'csv_source_test\'/', $this->getErrorOutput());
// Flush cache so recently deleted vocabulary actually goes away.
drupal_flush_all_caches();
$this->assertEquals(4, \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->getQuery()->accessCheck(TRUE)->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('genre'));
// Remove one vocab and replace with another (reverse previous change).
$this->sourceData = str_replace('fruit,Fruit,Fruit description,1,0', 'genre,Genre,Genre description,1,0', $this->sourceData);
file_put_contents('public://test.csv', $this->sourceData);
// Execute sync migration without update enforced.
$this->drush('mim', ['csv_source_test'], ['sync' => NULL]);
$this->assertStringContainsString('1/4', $this->getErrorOutput());
$this->assertStringContainsString('25%', $this->getErrorOutput());
$this->assertStringContainsString('Rolled back 1 item - done with \'csv_source_test\'', $this->getErrorOutput());
$this->assertStringNotContainsString('3 updated', $this->getErrorOutput());
$this->assertStringContainsString('Processed 1 item (1 created, 0 updated, 0 failed, 0 ignored) - done with \'csv_source_test\'', $this->getErrorOutput());
// Flush cache so recently deleted vocabulary actually goes away.
drupal_flush_all_caches();
$this->assertEquals(4, \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->getQuery()->accessCheck()->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load('fruit'));
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */
$id_map = $this->container->get('plugin.manager.migration')->createInstance('csv_source_test')->getIdMap();
$this->assertCount(4, $id_map);
}
}

View File

@@ -0,0 +1,225 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Tests\BrowserTestBase;
use Drush\TestTraits\DrushTestTrait;
/**
* Execute drush on fully functional website.
*
* @group migrate_tools
*/
final class DrushCommandsTest extends BrowserTestBase {
use DrushTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_tools_test',
'migrate_tools',
'migrate_plus',
'taxonomy',
'text',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests migrate:import with feedback.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function testFeedback(): void {
$this->drush('mim', ['fruit_terms'], ['feedback' => 2]);
$this->assertMatchesRegularExpression('/1\/3[^\n]+\[notice\][^\n]+Processed 2 items \(2 created, 0 updated, 0 failed, 0 ignored\) - continuing with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/3\/3[^\n]+\[notice\][^\n]+Processed 1 item \(1 created, 0 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 4 items', $this->getErrorOutput());
}
/**
* Tests migrate:import with limit.
*/
public function testLimit(): void {
$this->drush('mim', ['fruit_terms'], ['limit' => 2]);
$this->assertMatchesRegularExpression('/\[notice\][^\n]+Processed 2 items \(2 created, 0 updated, 0 failed, 0 ignored\)/', $this->getErrorOutput());
$this->assertStringContainsString('done with \'fruit_terms\'', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 3 items', $this->getErrorOutput());
}
/**
* Test that migrations continue after a failure if the option is set.
*/
public function testContinueOnFailure(): void {
// Option not set, fruit_terms should not run.
$this->drush('mim', ['invalid_plugin,fruit_terms'], [], NULL, NULL, 1);
$this->assertStringNotContainsString("done with 'fruit_terms'", $this->getErrorOutput());
// Option set, fruit_terms should run.
$this->drush('mim', ['invalid_plugin,fruit_terms'], ['continue-on-failure' => NULL]);
$this->assertStringContainsString("done with 'fruit_terms'", $this->getErrorOutput());
// Option not set, fruit_terms should not run.
$this->drush('mr', ['invalid_plugin,fruit_terms'], [], NULL, NULL, 1);
$this->assertStringNotContainsString("done with 'fruit_terms'", $this->getErrorOutput());
// Option set, fruit_terms should run.
$this->drush('mr', ['invalid_plugin,fruit_terms'], ['continue-on-failure' => NULL]);
$this->assertStringContainsString("done with 'fruit_terms'", $this->getErrorOutput());
// Option not set, fruit_terms should not display.
$this->drush('ms', ['invalid_plugin,fruit_terms'], ['format' => 'json'], NULL, NULL, 1);
// This demonstrates we surface the exception but not as an error.
$this->assertStringNotContainsString('[error] The "does_not_exist" plugin does not exist', $this->getErrorOutput());
$this->assertStringContainsString('The "does_not_exist" plugin does not exist', $this->getErrorOutput());
$this->assertStringNotContainsString('fruit_terms Idle 3', $this->getOutput());
// Option set, fruit_terms should display.
$this->drush('ms', ['invalid_plugin,fruit_terms'], ['continue-on-failure' => NULL]);
$this->assertMatchesRegularExpression('/\[error\][^\n]+The "does_not_exist" plugin does not exist/', $this->getErrorOutput());
$this->assertStringContainsString('fruit_terms Idle 3', $this->getOutput());
}
/**
* Tests many of the migrate drush commands.
*/
public function testDrush(): void {
$this->drush('ms', [], [], NULL, NULL, 1);
$this->assertStringContainsString('The "does_not_exist" plugin does not exist.', $this->getErrorOutput());
$this->container->get('config.factory')->getEditable('migrate_plus.migration.invalid_plugin')->delete();
// Flush cache so the recently removed invalid migration is cleared.
drupal_flush_all_caches();
$this->drush('ms', [], ['format' => 'json']);
$expected = [
[
'group' => 'Default (default)',
'id' => 'fruit_terms',
'imported' => 0,
'status' => 'Idle',
'total' => 3,
'unprocessed' => 3,
'message_count' => 0,
'last_imported' => '',
],
[
'group' => 'Default (default)',
'id' => 'source_exception',
'imported' => 0,
'status' => 'Idle',
'total' => 0,
'unprocessed' => 0,
'message_count' => 0,
'last_imported' => '',
],
];
$this->assertEquals($expected, $this->getOutputFromJSON());
$this->drush('mim', ['fruit_terms']);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/3\/3[^\n]+\[notice\][^\n]+Processed 3 items \(3 created, 0 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 4 items', $this->getErrorOutput());
$this->drush('mim', ['fruit_terms'], [
'update' => NULL,
'force' => NULL,
'execute-dependencies' => NULL,
]);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/3\/3[^\n]+\[notice\][^\n]+Processed 3 items \(0 created, 3 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 4 items', $this->getErrorOutput());
$this->drush('mrs', ['fruit_terms']);
$this->assertStringContainsString('Migration fruit_terms is already Idle', $this->getErrorOutput());
$this->drush('mfs', ['fruit_terms'], ['format' => 'json']);
$expected = [
[
'machine_name' => 'name',
'description' => 'name',
],
];
$this->assertEquals($expected, $this->getOutputFromJSON());
$this->drush('mr', ['fruit_terms']);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/3\/3[^\n]+\[notice\][^\n]+Rolled back 3 items - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 4 items', $this->getErrorOutput());
$this->drush('migrate:stop', ['fruit_terms']);
$this->assertMatchesRegularExpression('/warning\][^\n]+Migration fruit_terms is idle/', $this->getErrorOutput());
$this->drush('mim', ['fruit_terms'], ['skip-progress-bar' => NULL]);
$this->assertMatchesRegularExpression('/\[notice\][^\n]+Processed 3 items \(3 created, 0 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->drush('mr', ['fruit_terms'], ['skip-progress-bar' => NULL]);
$this->assertMatchesRegularExpression('/\[notice\][^\n]+Rolled back 3 items - done with \'fruit_terms\'/', $this->getErrorOutput());
}
/**
* Fully test migrate messages.
*/
public function testMessages(): void {
$this->drush('mim', ['fruit_terms']);
$this->drush('mmsg', ['fruit_terms']);
$this->assertMatchesRegularExpression('/\[notice\][^\n]+No messages for this migration/', $this->getErrorOutput());
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */
$id_map = $this->container->get('plugin.manager.migration')->createInstance('fruit_terms')->getIdMap();
$id_map->saveMessage(['name' => 'Apple'], 'You picked a bad one.');
$this->drush('mmsg', ['fruit_terms'], ['format' => 'json']);
$expected = [
[
'level' => 'Error',
'message' => 'You picked a bad one.',
'source_ids' => 'Apple',
'destination_ids' => '1',
],
];
$this->assertEquals($expected, $this->getOutputFromJSON());
$this->drush('mmsg', ['fruit_terms'], ['format' => 'csv']);
$expected = <<<EOT
"Source ID(s)","Destination ID(s)",Level,Message
Apple,1,Error,"You picked a bad one."
EOT;
$this->assertEquals($expected, $this->getOutput());
}
/**
* Tests synced import with and without update enforced.
*/
public function testSyncImport(): void {
$this->drush('mim', ['fruit_terms']);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertStringContainsString('3/3', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/3\/3[^\n]+\[notice\][^\n]+Processed 3 items \(3 created, 0 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 4 items', $this->getErrorOutput());
$term = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2);
$this->assertEquals('Banana', $term->label());
$this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute());
$source = $this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->get('source');
unset($source['data_rows'][1]);
$source['data_rows'][] = ['name' => 'Grape'];
$this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save();
// Flush cache so the recently changed migration can be refreshed.
drupal_flush_all_caches();
$this->drush('mim', ['fruit_terms'], ['sync' => NULL, 'update' => NULL]);
$this->assertStringContainsString('1/3', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/4\/4[^\n]+\[notice\][^\n]+Processed 3 items \(1 created, 2 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('Processed 5 items', $this->getErrorOutput());
$this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(2));
unset($source['data_rows'][2]);
$source['data_rows'][] = ['name' => 'Pear'];
$this->container->get('config.factory')->getEditable('migrate_plus.migration.fruit_terms')->set('source', $source)->save();
// Flush cache so the recently changed migration can be refreshed.
drupal_flush_all_caches();
$this->drush('mim', ['fruit_terms'], ['sync' => NULL]);
$this->assertMatchesRegularExpression('/1\/3[^\n]+\[notice\][^\n]+Rolled back 1 item - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertMatchesRegularExpression('/\[notice\][^\n]+Processed 1 item \(1 created, 0 updated, 0 failed, 0 ignored\) - done with \'fruit_terms\'/', $this->getErrorOutput());
$this->assertStringNotContainsString('2 updated', $this->getErrorOutput());
$this->assertStringNotContainsString('5', $this->getErrorOutput());
$this->assertEquals(3, \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()->accessCheck(TRUE)->count()->execute());
$this->assertEmpty(\Drupal::entityTypeManager()->getStorage('taxonomy_term')->load(3));
/** @var \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map */
$id_map = $this->container->get('plugin.manager.migration')->createInstance('fruit_terms')->getIdMap();
$this->assertCount(3, $id_map);
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Tests\BrowserTestBase;
/**
* Execution form test.
*
* @group migrate_tools
*/
final class MigrateExecutionFormTest extends BrowserTestBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'filter',
'field',
'node',
'text',
'taxonomy',
'migrate',
'migrate_plus',
'migrate_tools',
'migrate_tools_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
private VocabularyInterface $vocabulary;
private QueryInterface $vocabularyQuery;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->vocabulary = $this->createVocabulary([
'vid' => 'fruit',
'name' => 'Fruit',
]);
$this->vocabularyQuery = $this->container->get('entity_type.manager')
->getStorage('taxonomy_term')
->getQuery()
->accessCheck(TRUE);
// Log in as user 1. Migrations in the UI can only be performed as user 1.
$this->drupalLogin($this->rootUser);
}
/**
* Tests execution of import and rollback of a migration.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testExecution(): void {
$group = 'default';
$migration = 'fruit_terms';
$urlPath = "/admin/structure/migrate/manage/{$group}/migrations/{$migration}/execute";
$real_count = $this->vocabularyQuery->count()->execute();
$expected_count = 0;
$this->assertEquals($expected_count, $real_count);
$this->drupalGet($urlPath);
$this->assertSession()->responseContains('Choose an operation to run');
$edit = [
'operation' => 'import',
];
$this->drupalGet($urlPath);
$this->submitForm($edit, 'Execute');
$real_count = $this->vocabularyQuery->count()->execute();
$expected_count = 3;
$this->assertEquals($expected_count, $real_count);
$edit = [
'operation' => 'rollback',
];
$this->drupalGet($urlPath);
$this->submitForm($edit, 'Execute');
$real_count = $this->vocabularyQuery->count()->execute();
$expected_count = 0;
$this->assertEquals($expected_count, $real_count);
$edit = [
'operation' => 'import',
];
$this->drupalGet($urlPath);
$this->submitForm($edit, 'Execute');
$real_count = $this->vocabularyQuery->count()->execute();
$expected_count = 3;
$this->assertEquals($expected_count, $real_count);
}
/**
* Creates a custom vocabulary based on default settings.
*
* @param array $values
* An array of settings to change from the defaults.
* Example: 'vid' => 'foo'.
*
* @return \Drupal\taxonomy\VocabularyInterface
* Created vocabulary.
*/
protected function createVocabulary(array $values = []): VocabularyInterface {
// Find a non-existent random vocabulary name.
if (!isset($values['vid'])) {
do {
$id = strtolower($this->randomMachineName(8));
} while (Vocabulary::load($id));
}
else {
$id = $values['vid'];
}
$values += [
'id' => $id,
'name' => $id,
];
$vocabulary = Vocabulary::create($values);
$status = $vocabulary->save();
$this->assertSame($status, SAVED_NEW);
return $vocabulary;
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Execution form test.
*
* @group migrate_tools
*/
final class MigrateListBuilderTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'user',
'migrate',
'migrate_plus',
'migrate_tools',
'migrate_tools_test',
];
/**
* {@inheritdoc}
*/
protected $profile = 'testing';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Log in as user 1. Migrations in the UI can only be performed as user 1.
$this->drupalLogin($this->rootUser);
}
/**
* Test migrate UI list page with default migrations.
*/
public function testMigrateListBuilderDefault(): void {
// List migrations from default group.
$this->drupalGet('/admin/structure/migrate/manage/default/migrations');
$this->assertSession()->statusCodeEquals(200);
}
/**
* Test migrate UI list page with disabled migrations.
*/
public function testMigrateListBuilderDisabled(): void {
// List migrations containing disabled migrations.
$this->drupalGet('/admin/structure/migrate/manage/disabled/migrations');
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Functional;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Tests\BrowserTestBase;
/**
* Test the URL column alias edit form.
*
* @group migrate_tools
*/
final class SourceUrlFormTest extends BrowserTestBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate',
'migrate_plus',
'migrate_tools',
'url_source_test',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The migration group for the test migration.
*
* @var string
*/
private string $group;
/**
* The test migration id.
*
* @var string
*/
private string $migration;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Log in as user 1. Migrations in the UI can only be performed as user 1.
$this->drupalLogin($this->rootUser);
// Select the group and migration to test.
$this->group = 'url_test';
$this->migration = 'url_404_source_test';
}
/**
* Tests the form ensure graceful 404 handling.
*
* @throws \Behat\Mink\Exception\ExpectationException
*/
public function testSourceUrl404Form(): void {
// Assert the test migration is listed.
$this->drupalGet("/admin/structure/migrate/manage/{$this->group}/migrations");
$session = $this->assertSession();
$session->responseContains('Test 404 URLs in the UI');
}
}

View File

@@ -0,0 +1,319 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Kernel {
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate_tools\Drush\Commands\MigrateToolsCommands;
use Drupal\migrate_tools\MigrateTools;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
use Drush\Log\DrushLoggerManager;
use Psr\Log\LoggerInterface;
/**
* Tests for the Drush 9 commands.
*
* @group migrate_tools
*/
final class DrushTest extends MigrateTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_tools_test',
'migrate_tools',
'migrate_plus',
'taxonomy',
'text',
'system',
'user',
];
/**
* Base options array for import.
*
* @var array
*/
private array $importBaseOptions = [
'all' => NULL,
'group' => NULL,
'tag' => NULL,
'limit' => NULL,
'feedback' => NULL,
'idlist' => NULL,
'idlist-delimiter' => MigrateTools::DEFAULT_ID_LIST_DELIMITER,
'update' => NULL,
'force' => NULL,
'execute-dependencies' => NULL,
'skip-progress-bar' => FALSE,
'continue-on-failure' => FALSE,
'sync' => FALSE,
];
private ?MigrateToolsCommands $commands = NULL;
private MigrationPluginManagerInterface $migrationPluginManager;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->installConfig('migrate_plus');
$this->installConfig('migrate_tools_test');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
$this->installSchema('user', ['users_data']);
$this->installSchema('migrate_tools', ['migrate_tools_sync_source_ids']);
$this->migrationPluginManager = $this->container->get('plugin.manager.migration');
// Handle Drush 10 vs Drush 11 differences.
$logger_class = class_exists(DrushLoggerManager::class) ? DrushLoggerManager::class : LoggerInterface::class;
$this->logger = $this->prophesize($logger_class)->reveal();
$this->commands = new MigrateToolsCommands(
$this->migrationPluginManager,
$this->container->get('date.formatter'),
$this->container->get('entity_type.manager'),
$this->container->get('keyvalue'));
$this->commands->setLogger($this->logger);
}
/**
* Tests drush ms.
*/
public function testStatus(): void {
$this->executeMigration('fruit_terms');
$result = $this->commands->status('fruit_terms', [
'group' => NULL,
'tag' => NULL,
'names-only' => FALSE,
]);
$rows = $result->getArrayCopy();
$this->assertCount(1, $rows);
$row = reset($rows);
$this->assertSame('fruit_terms', $row['id']);
$this->assertSame(3, $row['total']);
$this->assertSame(3, $row['imported']);
$this->assertSame('Idle', $row['status']);
// Migrate status should not display migrate_drupal migrations if no
// source database is defined.
\Drupal::service('module_installer')->uninstall([
'migrate_tools_test',
]);
$this->enableModules(['migrate_drupal']);
$this->migrationPluginManager->clearCachedDefinitions();
$rows = $this->commands->status();
$this->assertEmpty($rows);
}
/**
* Tests that a failing status throws an exception (i.e. exit code).
*/
public function testFailingStatusThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('The "does_not_exist" plugin does not exist.');
$this->commands->status('invalid_plugin');
}
/**
* Tests drush mim.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function testImport(): void {
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$this->commands->import('fruit_terms', array_merge($this->importBaseOptions, ['idlist' => 'Apple']));
$this->assertSame(1, $id_map->importedCount());
$this->commands->import('fruit_terms', $this->importBaseOptions);
$this->assertSame(3, $id_map->importedCount());
$this->commands->import('fruit_terms', array_merge($this->importBaseOptions, [
'idlist' => 'Apple',
'update' => TRUE,
]));
$this->assertCount(0, $id_map->getRowsNeedingUpdate(100));
}
/**
* Tests that a failing import throws an exception (i.e. exit code).
*/
public function testFailingImportThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('source_exception migration failed.');
$this->commands->import('source_exception', $this->importBaseOptions);
}
/**
* Tests drush mmsg.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function testMessages(): void {
$this->executeMigration('fruit_terms');
$message = $this->getRandomGenerator()->string(16);
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$id_map->saveMessage(['name' => 'Apple'], $message);
/** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */
$result = $this->commands->messages('fruit_terms', [
'csv' => FALSE,
'idlist' => NULL,
'idlist-delimiter' => MigrateTools::DEFAULT_ID_LIST_DELIMITER,
]);
$rows = $result->getArrayCopy();
$this->assertSame($message, $rows[0]['message']);
}
/**
* Tests that a failing messages throws an exception (i.e. exit code).
*/
public function testFailingMessagesThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Migration does_not_exist does not exist');
$this->commands->messages('does_not_exist', [
'csv' => FALSE,
'idlist' => NULL,
'idlist-delimiter' => MigrateTools::DEFAULT_ID_LIST_DELIMITER,
]);
}
/**
* Tests drush mr.
*/
public function testRollback(): void {
$this->executeMigration('fruit_terms');
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('fruit_terms');
$id_map = $migration->getIdMap();
$this->assertSame(3, $id_map->importedCount());
$this->commands->rollback('fruit_terms', $this->importBaseOptions);
$this->assertSame(0, $id_map->importedCount());
}
/**
* Tests that a failing rollback throws an exception (i.e. exit code).
*/
public function testFailingRollbackThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('source_exception migration failed');
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('source_exception');
$migration->setStatus(MigrationInterface::STATUS_IMPORTING);
$this->commands->rollback('source_exception', $this->importBaseOptions);
}
/**
* Tests drush mrs.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function testReset(): void {
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('fruit_terms');
$migration->setStatus(MigrationInterface::STATUS_IMPORTING);
$status = $this->commands->status('fruit_terms', [
'group' => NULL,
'tag' => NULL,
'names-only' => FALSE,
])->getArrayCopy()[0]['status'];
$this->assertSame('Importing', $status);
$this->commands->resetStatus('fruit_terms');
$this->assertSame(MigrationInterface::STATUS_IDLE, $migration->getStatus());
}
/**
* Tests that a failing reset status throws an exception (i.e. exit code).
*/
public function testFailingResetStatusThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Migration does_not_exist does not exist');
$this->commands->resetStatus('does_not_exist');
}
/**
* Tests drush mst.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
public function testStop(): void {
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$migration = $this->migrationPluginManager->createInstance('fruit_terms');
$migration->setStatus(MigrationInterface::STATUS_IMPORTING);
$this->commands->stop('fruit_terms');
$this->assertSame(MigrationInterface::STATUS_STOPPING, $migration->getStatus());
}
/**
* Tests that a failing stop throws an exception (i.e. exit code).
*/
public function testFailingStopThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Migration does_not_exist does not exist');
$this->commands->stop('does_not_exist');
}
/**
* Tests drush mfs.
*/
public function testFieldsSource(): void {
/** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */
$result = $this->commands->fieldsSource('fruit_terms');
$rows = $result->getArrayCopy();
$this->assertCount(1, $rows);
$this->assertSame('name', $rows[0]['machine_name']);
$this->assertSame('name', $rows[0]['description']);
}
/**
* Tests that a failing fields source throws an exception (i.e. exit code).
*/
public function testFailingFieldsSourceThrowsException(): void {
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Migration does_not_exist does not exist');
$this->commands->fieldsSource('does_not_exist');
}
}
}
namespace {
if (!function_exists('dt')) {
/**
* Stub for dt().
*
* @param string $message
* The text.
* @param array $replace
* The replacement values.
*
* The text.
*/
function dt($message, array $replace = []): string {
return strtr($message, $replace);
}
}
if (!function_exists('drush_op')) {
/**
* Stub for drush_op.
*
* @param callable $callable
* The function to call.
*/
function drush_op(callable $callable) {
$args = func_get_args();
array_shift($args);
return call_user_func_array($callable, $args);
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Kernel;
use Drupal\migrate_tools\MigrateExecutable;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Tests imports.
*
* @group migrate_tools
*/
final class MigrateImportTest extends MigrateTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'field',
'system',
'taxonomy',
'text',
'user',
'system',
];
protected $collectMessages = TRUE;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('taxonomy_vocabulary');
$this->installEntitySchema('taxonomy_term');
$this->installConfig(['taxonomy']);
}
/**
* Tests rolling back configuration and content entities.
*/
public function testImport(): void {
// We use vocabularies to demonstrate importing and rolling back
// configuration entities.
$vocabulary_data_rows = [
['id' => '1', 'name' => 'categories', 'weight' => '2'],
['id' => '2', 'name' => 'tags', 'weight' => '1'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'vocabularies',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $vocabulary_data_rows,
'ids' => $ids,
],
'process' => [
'vid' => 'id',
'name' => 'name',
'weight' => 'weight',
],
'destination' => ['plugin' => 'entity:taxonomy_vocabulary'],
];
/** @var \Drupal\migrate\Plugin\MigrationInterface $vocabulary_migration */
$vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$vocabulary_id_map = $vocabulary_migration->getIdMap();
// Test id list import.
$executable = new MigrateExecutable($vocabulary_migration, $this, ['idlist' => 2]);
$executable->import();
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = Vocabulary::load(1);
$this->assertEmpty($vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => 1]);
$this->assertEmpty($map_row);
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = Vocabulary::load(2);
$this->assertInstanceOf(VocabularyInterface::class, $vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => 2]);
$this->assertEquals($map_row['destid1'], $vocabulary->id());
}
}

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Kernel;
use Drupal\migrate_tools\MigrateExecutable;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\VocabularyInterface;
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
/**
* Tests rolling back of imports.
*
* @group migrate_tools
*/
final class MigrateRollbackTest extends MigrateTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'field',
'system',
'taxonomy',
'text',
'user',
];
protected $collectMessages = TRUE;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('taxonomy_vocabulary');
$this->installEntitySchema('taxonomy_term');
$this->installConfig(['taxonomy']);
}
/**
* Tests rolling back configuration and content entities.
*/
public function testRollback(): void {
// We use vocabularies to demonstrate importing and rolling back
// configuration entities.
$vocabulary_data_rows = [
['id' => '1', 'name' => 'categories', 'weight' => '2'],
['id' => '2', 'name' => 'tags', 'weight' => '1'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'vocabularies',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $vocabulary_data_rows,
'ids' => $ids,
],
'process' => [
'vid' => 'id',
'name' => 'name',
'weight' => 'weight',
],
'destination' => ['plugin' => 'entity:taxonomy_vocabulary'],
];
/** @var \Drupal\migrate\Plugin\MigrationInterface $vocabulary_migration */
$vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$vocabulary_id_map = $vocabulary_migration->getIdMap();
// Import and validate vocabulary config entities were created.
$executable = new MigrateExecutable($vocabulary_migration, $this, []);
$executable->import();
foreach ($vocabulary_data_rows as $row) {
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = Vocabulary::load($row['id']);
$this->assertInstanceOf(VocabularyInterface::class, $vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]);
$this->assertEquals($map_row['destid1'], $vocabulary->id());
}
// Test id list rollback.
$rollback_executable = new MigrateExecutable($vocabulary_migration, $this, ['idlist' => 1]);
$rollback_executable->rollback();
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = Vocabulary::load(1);
$this->assertEmpty($vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => 1]);
$this->assertEmpty($map_row);
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = Vocabulary::load(2);
$this->assertInstanceOf(VocabularyInterface::class, $vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => 2]);
$this->assertEquals($map_row['destid1'], $vocabulary->id());
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Tests merging shared configuration.
*
* @group migrate_tools
*/
final class MigrateSharedConfigTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'migrate_tools',
'migrate_shared_config_test',
'migrate',
];
/**
* Tests including shared configuration.
*/
public function testInclude(): void {
$plugin_manager = $this->container->get('plugin.manager.migration');
// Validate a single include with not conflicts.
$migration = $plugin_manager->createInstance('test_stub_migration');
$this->assertInstanceOf(MigrationInterface::class, $migration);
$expected_source_configuration = [
'batch_size' => 2,
'plugin' => 'embedded_data',
'data_rows' => [
['label' => 'foo'],
['label' => 'bar'],
['label' => 'baz'],
],
'ids' => ['label' => ['type' => 'string']],
];
$this->assertEquals($expected_source_configuration, $migration->getSourceConfiguration());
// Validate multiple includes.
$migration = $plugin_manager->createInstance('test_stub_multiple_includes_migration');
$this->assertInstanceOf(MigrationInterface::class, $migration);
$expected_destination_configuration = [
'batch_size' => 2,
'plugin' => 'entity:entity_test',
'my_single_file_default_configuration' => 'value',
];
$this->assertEquals($expected_source_configuration, $migration->getSourceConfiguration());
$this->assertEquals($expected_destination_configuration, $migration->getDestinationConfiguration());
// Validate with conflicts.
$migration = $plugin_manager->createInstance('test_stub_conflicts_migration');
$this->assertInstanceOf(MigrationInterface::class, $migration);
$expected_source_configuration['batch_size'] = 1000;
$this->assertEquals($expected_source_configuration, $migration->getSourceConfiguration());
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types = 1);
namespace Drupal\Tests\migrate_tools\Unit;
use Drupal\migrate_tools\MigrateTools;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate_tools\MigrateTools
* @group migrate_tools
*/
final class MigrateToolsTest extends UnitTestCase {
/**
* @covers ::buildIdList
*
* @dataProvider dataProviderIdList
*/
public function testBuildIdList(array $options, array $expected): void {
$results = MigrateTools::buildIdList($options);
$this->assertEquals($results, $expected);
}
/**
* Data provider for testBuildIdList.
*/
public static function dataProviderIdList(): array {
$cases = [];
$cases[] = [
'options' => [],
'expected' => [],
];
$cases['single id'] = [
'options' => [
'idlist' => 123,
],
'expected' => [[123]],
];
$cases['multiple ids'] = [
'options' => [
'idlist' => '123, 456',
],
'expected' => [
[123], [456],
],
];
$cases['default delimiter, composite key'] = [
'options' => [
'idlist' => '123:456',
],
'expected' => [
[123, 456],
],
];
$cases['special delimiter, single'] = [
'options' => [
'idlist' => '123:456',
'idlist-delimiter' => '~',
],
'expected' => [
['123:456'],
],
];
$cases['special delimiter, multiple'] = [
'options' => [
'idlist' => '123:456~987:654',
'idlist-delimiter' => '~',
],
'expected' => [
['123:456', '987:654'],
],
];
$cases['space delimiter, multiple'] = [
'options' => [
'idlist' => '123:456 987:654',
'idlist-delimiter' => ' ',
],
'expected' => [
['123:456', '987:654'],
],
];
return $cases;
}
}