caore d8 files

This commit is contained in:
Bachir Soussi Chiadmi
2016-09-06 12:44:15 +02:00
commit 58f46e4e8f
12831 changed files with 1451591 additions and 0 deletions
@@ -0,0 +1,11 @@
name: 'Migrate events test'
type: module
package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
core: '8.x'
project: 'drupal'
datestamp: 1470233791
@@ -0,0 +1,39 @@
<?php
namespace Drupal\migrate_events_test\Plugin\migrate\destination;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\DestinationBase;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "dummy",
* requirements_met = true
* )
*/
class DummyDestination extends DestinationBase {
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['value']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
return ['value' => 'Dummy value'];
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
return ['value' => $row->getDestinationProperty('value')];
}
}
@@ -0,0 +1,14 @@
name: 'Migration external translated test'
type: module
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- node
- migrate
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
core: '8.x'
project: 'drupal'
datestamp: 1470233791
@@ -0,0 +1,19 @@
id: external_translated_test_node
label: External translated content
source:
plugin: migrate_external_translated_test
default_lang: true
constants:
type: external_test
process:
type: constants/type
title: title
langcode:
plugin: static_map
source: lang
map:
English: en
French: fr
Spanish: es
destination:
plugin: entity:node
@@ -0,0 +1,27 @@
id: external_translated_test_node_translation
label: External translated content translations
source:
plugin: migrate_external_translated_test
default_lang: false
constants:
type: external_test
process:
nid:
plugin: migration
source: name
migration: external_translated_test_node
type: constants/type
title: title
langcode:
plugin: static_map
source: lang
map:
English: en
French: fr
Spanish: es
destination:
plugin: entity:node
translations: true
migration_dependencies:
required:
- external_translated_test_node
@@ -0,0 +1,77 @@
<?php
namespace Drupal\migrate_external_translated_test\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
/**
* A simple migrate source for our tests.
*
* @MigrateSource(
* id = "migrate_external_translated_test"
* )
*/
class MigrateExternalTranslatedTestSource extends SourcePluginBase {
/**
* The data to import.
*
* @var array
*/
protected $import = [
['name' => 'cat', 'title' => 'Cat', 'lang' => 'English'],
['name' => 'cat', 'title' => 'Chat', 'lang' => 'French'],
['name' => 'cat', 'title' => 'Gato', 'lang' => 'Spanish'],
['name' => 'dog', 'title' => 'Dog', 'lang' => 'English'],
['name' => 'dog', 'title' => 'Chien', 'lang' => 'French'],
['name' => 'monkey', 'title' => 'Monkey', 'lang' => 'English'],
];
/**
* {@inheritdoc}
*/
public function fields() {
return [
'name' => $this->t('Unique name'),
'title' => $this->t('Title'),
'lang' => $this->t('Language'),
];
}
/**
* {@inheritdoc}
*/
public function __toString() {
return '';
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['name']['type'] = 'string';
if (!$this->configuration['default_lang']) {
$ids['lang']['type'] = 'string';
}
return $ids;
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$data = [];
// Keep the rows with the right languages.
$want_default = $this->configuration['default_lang'];
foreach ($this->import as $row) {
$is_english = $row['lang'] == 'English';
if ($want_default == $is_english) {
$data[] = $row;
}
}
return new \ArrayIterator($data);
}
}
@@ -0,0 +1,12 @@
name: 'Migrate module prepareRow tests'
type: module
description: 'Support module for source plugin prepareRow testing.'
package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2016-08-03
version: '8.1.8'
core: '8.x'
project: 'drupal'
datestamp: 1470233791
@@ -0,0 +1,26 @@
<?php
/**
* @file
* Test module for testing the migration source plugin prepareRow() exception
* handling.
*/
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Row;
/**
* Implements hook_migrate_prepare_row().
*/
function migrate_prepare_row_test_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
// Test both options for save_to_map.
$data = $row->getSourceProperty('data');
if ($data == 'skip_and_record') {
throw new MigrateSkipRowException('', TRUE);
}
elseif ($data == 'skip_and_dont_record') {
throw new MigrateSkipRowException('', FALSE);
}
}
@@ -0,0 +1,155 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\MigrateExecutable;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests setting of bundles on content entity migrations.
*
* @group migrate
*/
class MigrateBundleTest extends MigrateTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['taxonomy', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('taxonomy_vocabulary');
$this->installEntitySchema('taxonomy_term');
$this->installConfig(['taxonomy']);
// Set up two vocabularies (taxonomy bundles).
Vocabulary::create(['vid' => 'tags', 'name' => 'Tags']);
Vocabulary::create(['vid' => 'categories', 'name' => 'Categories']);
}
/**
* Tests setting the bundle in the destination.
*/
public function testDestinationBundle() {
$term_data_rows = [
['id' => 1, 'name' => 'Category 1'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'terms',
'migration_tags' => ['Bundle test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $term_data_rows,
'ids' => $ids,
],
'process' => [
'tid' => 'id',
'name' => 'name',
],
'destination' => [
'plugin' => 'entity:taxonomy_term',
'default_bundle' => 'categories',
],
'migration_dependencies' => [],
];
$term_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
// Import and validate the term entity was created with the correct bundle.
$term_executable = new MigrateExecutable($term_migration, $this);
$term_executable->import();
/** @var Term $term */
$term = Term::load(1);
$this->assertEquals($term->bundle(), 'categories');
}
/**
* Tests setting the bundle in the process pipeline.
*/
public function testProcessBundle() {
$term_data_rows = [
['id' => 1, 'vocab' => 'categories', 'name' => 'Category 1'],
['id' => 2, 'vocab' => 'tags', 'name' => 'Tag 1'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'terms',
'migration_tags' => ['Bundle test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $term_data_rows,
'ids' => $ids,
],
'process' => [
'tid' => 'id',
'vid' => 'vocab',
'name' => 'name',
],
'destination' => [
'plugin' => 'entity:taxonomy_term',
],
'migration_dependencies' => [],
];
$term_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
// Import and validate the term entities were created with the correct bundle.
$term_executable = new MigrateExecutable($term_migration, $this);
$term_executable->import();
/** @var Term $term */
$term = Term::load(1);
$this->assertEquals($term->bundle(), 'categories');
$term = Term::load(2);
$this->assertEquals($term->bundle(), 'tags');
}
/**
* Tests setting bundles both in process and destination.
*/
public function testMixedBundles() {
$term_data_rows = [
['id' => 1, 'vocab' => 'categories', 'name' => 'Category 1'],
['id' => 2, 'name' => 'Tag 1'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'terms',
'migration_tags' => ['Bundle test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $term_data_rows,
'ids' => $ids,
],
'process' => [
'tid' => 'id',
'vid' => 'vocab',
'name' => 'name',
],
'destination' => [
'plugin' => 'entity:taxonomy_term',
// When no vocab is provided, the destination bundle is applied.
'default_bundle' => 'tags',
],
'migration_dependencies' => [],
];
$term_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
// Import and validate the term entities were created with the correct bundle.
$term_executable = new MigrateExecutable($term_migration, $this);
$term_executable->import();
/** @var Term $term */
$term = Term::load(1);
$this->assertEquals($term->bundle(), 'categories');
$term = Term::load(2);
$this->assertEquals($term->bundle(), 'tags');
}
}
@@ -0,0 +1,22 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Allows tests to alter dumps after they have loaded.
*
* @see \Drupal\migrate_drupal\Tests\d6\MigrateFileTest
*/
interface MigrateDumpAlterInterface {
/**
* Allows tests to alter dumps after they have loaded.
*
* @param \Drupal\KernelTests\KernelTestBase $test
* The test that is being run.
*/
public static function migrateDumpAlter(KernelTestBase $test);
}
@@ -0,0 +1,69 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the EmbeddedDataSource plugin.
*
* @group migrate
*/
class MigrateEmbeddedDataTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate'];
/**
* Tests the embedded_data source plugin.
*/
public function testEmbeddedData() {
$data_rows = [
['key' => '1', 'field1' => 'f1value1', 'field2' => 'f2value1'],
['key' => '2', 'field1' => 'f1value2', 'field2' => 'f2value2'],
];
$ids = ['key' => ['type' => 'integer']];
$definition = [
'migration_tags' => ['Embedded data test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $data_rows,
'ids' => $ids,
],
'process' => [],
'destination' => ['plugin' => 'null'],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$source = $migration->getSourcePlugin();
// Validate the plugin returns the source data that was provided.
$results = [];
/** @var \Drupal\migrate\Row $row */
foreach ($source as $row) {
$data_row = $row->getSource();
// The "data" row returned by getSource() also includes all source
// configuration - we remove it so we see only the data itself.
unset($data_row['plugin']);
unset($data_row['data_rows']);
unset($data_row['ids']);
$results[] = $data_row;
}
$this->assertIdentical($results, $data_rows);
// Validate the public APIs.
$this->assertIdentical($source->count(), count($data_rows));
$this->assertIdentical($source->getIds(), $ids);
$expected_fields = [
'key' => 'key',
'field1' => 'field1',
'field2' => 'field2',
];
$this->assertIdentical($source->fields(), $expected_fields);
}
}
@@ -0,0 +1,159 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Tests the EntityContentBase destination.
*
* @group migrate
*/
class MigrateEntityContentBaseTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate', 'user', 'language', 'entity_test'];
/**
* The storage for entity_test_mul.
*
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
*/
protected $storage;
/**
* A content migrate destination.
*
* @var \Drupal\migrate\Plugin\MigrateDestinationInterface
*/
protected $destination;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mul');
ConfigurableLanguage::createFromLangcode('en')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
$this->storage = $this->container->get('entity.manager')->getStorage('entity_test_mul');
}
/**
* Check the existing translations of an entity.
*
* @param int $id
* The entity ID.
* @param string $default
* The expected default translation language code.
* @param string[] $others
* The expected other translation language codes.
*/
protected function assertTranslations($id, $default, $others = []) {
$entity = $this->storage->load($id);
$this->assertTrue($entity, "Entity exists");
$this->assertEquals($default, $entity->language()->getId(), "Entity default translation");
$translations = array_keys($entity->getTranslationLanguages(FALSE));
sort($others);
sort($translations);
$this->assertEquals($others, $translations, "Entity translations");
}
/**
* Create the destination plugin to test.
*
* @param array $configuration
* The plugin configuration.
*/
protected function createDestination(array $configuration) {
$this->destination = new EntityContentBase(
$configuration,
'fake_plugin_id',
[],
$this->getMock(MigrationInterface::class),
$this->storage,
[],
$this->container->get('entity.manager'),
$this->container->get('plugin.manager.field.field_type')
);
}
/**
* Test importing and rolling back translated entities.
*/
public function testTranslated() {
// Create a destination.
$this->createDestination(['translations' => TRUE]);
// Create some pre-existing entities.
$this->storage->create(['id' => 1, 'langcode' => 'en'])->save();
$this->storage->create(['id' => 2, 'langcode' => 'fr'])->save();
$translated = $this->storage->create(['id' => 3, 'langcode' => 'en']);
$translated->save();
$translated->addTranslation('fr')->save();
// Pre-assert that things are as expected.
$this->assertTranslations(1, 'en');
$this->assertTranslations(2, 'fr');
$this->assertTranslations(3, 'en', ['fr']);
$this->assertFalse($this->storage->load(4));
$destination_rows = [
// Existing default translation.
['id' => 1, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE],
// New translation.
['id' => 2, 'langcode' => 'en', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE],
// Existing non-default translation.
['id' => 3, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_PRESERVE],
// Brand new row.
['id' => 4, 'langcode' => 'fr', 'action' => MigrateIdMapInterface::ROLLBACK_DELETE],
];
$rollback_actions = [];
// Import some rows.
foreach ($destination_rows as $idx => $destination_row) {
$row = new Row([], []);
foreach ($destination_row as $key => $value) {
$row->setDestinationProperty($key, $value);
}
$this->destination->import($row);
// Check that the rollback action is correct, and save it.
$this->assertEquals($destination_row['action'], $this->destination->rollbackAction());
$rollback_actions[$idx] = $this->destination->rollbackAction();
}
$this->assertTranslations(1, 'en');
$this->assertTranslations(2, 'fr', ['en']);
$this->assertTranslations(3, 'en', ['fr']);
$this->assertTranslations(4, 'fr');
// Rollback the rows.
foreach ($destination_rows as $idx => $destination_row) {
if ($rollback_actions[$idx] == MigrateIdMapInterface::ROLLBACK_DELETE) {
$this->destination->rollback($destination_row);
}
}
// No change, update of existing translation.
$this->assertTranslations(1, 'en');
// Remove added translation.
$this->assertTranslations(2, 'fr');
// No change, update of existing translation.
$this->assertTranslations(3, 'en', ['fr']);
// No change, can't remove default translation.
$this->assertTranslations(4, 'fr');
}
}
@@ -0,0 +1,218 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\Event\MigrateImportEvent;
use Drupal\migrate\Event\MigrateMapDeleteEvent;
use Drupal\migrate\Event\MigrateMapSaveEvent;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\Event\MigratePreRowSaveEvent;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\MigrateExecutable;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests events fired on migrations.
*
* @group migrate
*/
class MigrateEventsTest extends KernelTestBase {
/**
* State service for recording information received by event listeners.
*
* @var \Drupal\Core\State\State
*/
protected $state;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate', 'migrate_events_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->state = \Drupal::state();
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::MAP_SAVE,
array($this, 'mapSaveEventRecorder'));
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::MAP_DELETE,
array($this, 'mapDeleteEventRecorder'));
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::PRE_IMPORT,
array($this, 'preImportEventRecorder'));
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_IMPORT,
array($this, 'postImportEventRecorder'));
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::PRE_ROW_SAVE,
array($this, 'preRowSaveEventRecorder'));
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_ROW_SAVE,
array($this, 'postRowSaveEventRecorder'));
}
/**
* Tests migration events.
*/
public function testMigrateEvents() {
// Run a simple little migration, which should trigger one of each event
// other than map_delete.
$definition = [
'migration_tags' => ['Event test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['data' => 'dummy value'],
],
'ids' => [
'data' => ['type' => 'string'],
],
],
'process' => ['value' => 'data'],
'destination' => ['plugin' => 'dummy'],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
// As the import runs, events will be dispatched, recording the received
// information in state.
$executable->import();
// Validate from the recorded state that the events were received.
$event = $this->state->get('migrate_events_test.pre_import_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::PRE_IMPORT);
$this->assertIdentical($event['migration']->id(), $migration->id());
$event = $this->state->get('migrate_events_test.post_import_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::POST_IMPORT);
$this->assertIdentical($event['migration']->id(), $migration->id());
$event = $this->state->get('migrate_events_test.map_save_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::MAP_SAVE);
// Validating the last row processed.
$this->assertIdentical($event['fields']['sourceid1'], 'dummy value');
$this->assertIdentical($event['fields']['destid1'], 'dummy value');
$this->assertIdentical($event['fields']['source_row_status'], 0);
$event = $this->state->get('migrate_events_test.map_delete_event', []);
$this->assertIdentical($event, []);
$event = $this->state->get('migrate_events_test.pre_row_save_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::PRE_ROW_SAVE);
$this->assertIdentical($event['migration']->id(), $migration->id());
// Validating the last row processed.
$this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value');
$event = $this->state->get('migrate_events_test.post_row_save_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::POST_ROW_SAVE);
$this->assertIdentical($event['migration']->id(), $migration->id());
// Validating the last row processed.
$this->assertIdentical($event['row']->getSourceProperty('data'), 'dummy value');
$this->assertIdentical($event['destination_id_values']['value'], 'dummy value');
// Generate a map delete event.
$migration->getIdMap()->delete(['data' => 'dummy value']);
$event = $this->state->get('migrate_events_test.map_delete_event', []);
$this->assertIdentical($event['event_name'], MigrateEvents::MAP_DELETE);
$this->assertIdentical($event['source_id'], ['data' => 'dummy value']);
}
/**
* Reacts to map save event.
*
* @param \Drupal\Migrate\Event\MigrateMapSaveEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function mapSaveEventRecorder(MigrateMapSaveEvent $event, $name) {
$this->state->set('migrate_events_test.map_save_event', array(
'event_name' => $name,
'map' => $event->getMap(),
'fields' => $event->getFields(),
));
}
/**
* Reacts to map delete event.
*
* @param \Drupal\Migrate\Event\MigrateMapDeleteEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function mapDeleteEventRecorder(MigrateMapDeleteEvent $event, $name) {
$this->state->set('migrate_events_test.map_delete_event', array(
'event_name' => $name,
'map' => $event->getMap(),
'source_id' => $event->getSourceId(),
));
}
/**
* Reacts to pre-import event.
*
* @param \Drupal\Migrate\Event\MigrateImportEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function preImportEventRecorder(MigrateImportEvent $event, $name) {
$this->state->set('migrate_events_test.pre_import_event', array(
'event_name' => $name,
'migration' => $event->getMigration(),
));
}
/**
* Reacts to post-import event.
*
* @param \Drupal\Migrate\Event\MigrateImportEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function postImportEventRecorder(MigrateImportEvent $event, $name) {
$this->state->set('migrate_events_test.post_import_event', array(
'event_name' => $name,
'migration' => $event->getMigration(),
));
}
/**
* Reacts to pre-row-save event.
*
* @param \Drupal\Migrate\Event\MigratePreRowSaveEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function preRowSaveEventRecorder(MigratePreRowSaveEvent $event, $name) {
$this->state->set('migrate_events_test.pre_row_save_event', array(
'event_name' => $name,
'migration' => $event->getMigration(),
'row' => $event->getRow(),
));
}
/**
* Reacts to post-row-save event.
*
* @param \Drupal\Migrate\Event\MigratePostRowSaveEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function postRowSaveEventRecorder(MigratePostRowSaveEvent $event, $name) {
$this->state->set('migrate_events_test.post_row_save_event', array(
'event_name' => $name,
'migration' => $event->getMigration(),
'row' => $event->getRow(),
'destination_id_values' => $event->getDestinationIdValues(),
));
}
}
@@ -0,0 +1,92 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\MigrateExecutable;
use Drupal\node\Entity\NodeType;
/**
* Tests migrating non-Drupal translated content.
*
* Ensure it's possible to migrate in translations, even if there's no nid or
* tnid property on the source.
*
* @group migrate
*/
class MigrateExternalTranslatedTest extends MigrateTestBase {
/**
* {@inheritdoc}
*
* @todo: Remove migrate_drupal when https://www.drupal.org/node/2560795 is
* fixed.
*/
public static $modules = ['system', 'user', 'language', 'node', 'field', 'migrate_drupal', 'migrate_external_translated_test'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->installSchema('system', ['sequences']);
$this->installSchema('node', array('node_access'));
$this->installEntitySchema('user');
$this->installEntitySchema('node');
// Create some languages.
ConfigurableLanguage::createFromLangcode('en')->save();
ConfigurableLanguage::createFromLangcode('fr')->save();
ConfigurableLanguage::createFromLangcode('es')->save();
// Create a content type.
NodeType::create([
'type' => 'external_test',
'name' => 'Test node type',
])->save();
}
/**
* Test importing and rolling back our data.
*/
public function testMigrations() {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->container->get('entity.manager')->getStorage('node');
$this->assertEquals(0, count($storage->loadMultiple()));
// Run the migrations.
$migration_ids = ['external_translated_test_node', 'external_translated_test_node_translation'];
$this->executeMigrations($migration_ids);
$this->assertEquals(3, count($storage->loadMultiple()));
$node = $storage->load(1);
$this->assertEquals('en', $node->language()->getId());
$this->assertEquals('Cat', $node->title->value);
$this->assertEquals('Chat', $node->getTranslation('fr')->title->value);
$this->assertEquals('Gato', $node->getTranslation('es')->title->value);
$node = $storage->load(2);
$this->assertEquals('en', $node->language()->getId());
$this->assertEquals('Dog', $node->title->value);
$this->assertEquals('Chien', $node->getTranslation('fr')->title->value);
$this->assertFalse($node->hasTranslation('es'), "No spanish translation for node 2");
$node = $storage->load(3);
$this->assertEquals('en', $node->language()->getId());
$this->assertEquals('Monkey', $node->title->value);
$this->assertFalse($node->hasTranslation('fr'), "No french translation for node 3");
$this->assertFalse($node->hasTranslation('es'), "No spanish translation for node 3");
$this->assertNull($storage->load(4), "No node 4 migrated");
// Roll back the migrations.
foreach ($migration_ids as $migration_id) {
$migration = $this->getMigration($migration_id);
$executable = new MigrateExecutable($migration, $this);
$executable->rollback();
}
$this->assertEquals(0, count($storage->loadMultiple()));
}
}
@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\MigrateExecutable;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests interruptions triggered during migrations.
*
* @group migrate
*/
class MigrateInterruptionTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate', 'migrate_events_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::POST_ROW_SAVE,
array($this, 'postRowSaveEventRecorder'));
}
/**
* Tests migration interruptions.
*/
public function testMigrateEvents() {
// Run a simple little migration, which should trigger one of each event
// other than map_delete.
$definition = [
'migration_tags' => ['Interruption test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['data' => 'dummy value'],
['data' => 'dummy value2'],
],
'ids' => [
'data' => ['type' => 'string'],
],
],
'process' => ['value' => 'data'],
'destination' => ['plugin' => 'dummy'],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
// When the import runs, the first row imported will trigger an
// interruption.
$result = $executable->import();
$this->assertEqual($result, MigrationInterface::RESULT_INCOMPLETE);
// The status should have been reset to IDLE.
$this->assertEqual($migration->getStatus(), MigrationInterface::STATUS_IDLE);
}
/**
* Reacts to post-row-save event.
*
* @param \Drupal\Migrate\Event\MigratePostRowSaveEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function postRowSaveEventRecorder(MigratePostRowSaveEvent $event, $name) {
$event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\Event\MigrateIdMapMessageEvent;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessageInterface;
/**
* Tests whether idmap messages are sent to message interface when requested.
*
* @group migrate
*/
class MigrateMessageTest extends KernelTestBase implements MigrateMessageInterface {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate', 'system'];
/**
* Migration to run.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* Messages accumulated during the migration run.
*
* @var array
*/
protected $messages = [];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['system']);
// A simple migration, which will generate a message to the ID map because
// the concat plugin throws an exception if its source is not an array.
$definition = [
'migration_tags' => ['Message test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['name' => 'source_message', 'value' => 'a message'],
],
'ids' => [
'name' => ['type' => 'string'],
],
],
'process' => [
'message' => [
'plugin' => 'concat',
'source' => 'value',
],
],
'destination' => [
'plugin' => 'config',
'config_name' => 'system.maintenance',
],
];
$this->migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
}
/**
* Tests migration interruptions.
*/
public function testMessagesNotTeed() {
// We don't ask for messages to be teed, so don't expect any.
$executable = new MigrateExecutable($this->migration, $this);
$executable->import();
$this->assertIdentical(count($this->messages), 0);
}
/**
* Tests migration interruptions.
*/
public function testMessagesTeed() {
// Ask to receive any messages sent to the idmap.
\Drupal::service('event_dispatcher')->addListener(MigrateEvents::IDMAP_MESSAGE,
array($this, 'mapMessageRecorder'));
$executable = new MigrateExecutable($this->migration, $this);
$executable->import();
$this->assertIdentical(count($this->messages), 1);
$this->assertIdentical(reset($this->messages), "source_message: 'a message' is not an array");
}
/**
* Reacts to map message event.
*
* @param \Drupal\Migrate\Event\MigrateIdMapMessageEvent $event
* The migration event.
* @param string $name
* The event name.
*/
public function mapMessageRecorder(MigrateIdMapMessageEvent $event, $name) {
if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE ||
$event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
$type = 'status';
}
else {
$type = 'error';
}
$source_id_string = implode(',', $event->getSourceIdValues());
$this->display($source_id_string . ': ' . $event->getMessage(), $type);
}
/**
* {@inheritdoc}
*/
public function display($message, $type = 'status') {
$this->messages[] = $message;
}
}
@@ -0,0 +1,180 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
/**
* Tests rolling back of imports.
*
* @group migrate
*/
class MigrateRollbackTest extends MigrateTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['field', 'taxonomy', 'text'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('taxonomy_vocabulary');
$this->installEntitySchema('taxonomy_term');
$this->installConfig(['taxonomy']);
}
/**
* Tests rolling back configuration and content entities.
*/
public function testRollback() {
// 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'],
];
$vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$vocabulary_id_map = $vocabulary_migration->getIdMap();
$this->assertTrue($vocabulary_migration->getDestinationPlugin()->supportsRollback());
// Import and validate vocabulary config entities were created.
$vocabulary_executable = new MigrateExecutable($vocabulary_migration, $this);
$vocabulary_executable->import();
foreach ($vocabulary_data_rows as $row) {
/** @var Vocabulary $vocabulary */
$vocabulary = Vocabulary::load($row['id']);
$this->assertTrue($vocabulary);
$map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]);
$this->assertNotNull($map_row['destid1']);
}
// We use taxonomy terms to demonstrate importing and rolling back content
// entities.
$term_data_rows = [
['id' => '1', 'vocab' => '1', 'name' => 'music'],
['id' => '2', 'vocab' => '2', 'name' => 'Bach'],
['id' => '3', 'vocab' => '2', 'name' => 'Beethoven'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'terms',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $term_data_rows,
'ids' => $ids,
],
'process' => [
'tid' => 'id',
'vid' => 'vocab',
'name' => 'name',
],
'destination' => ['plugin' => 'entity:taxonomy_term'],
'migration_dependencies' => ['required' => ['vocabularies']],
];
$term_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$term_id_map = $term_migration->getIdMap();
$this->assertTrue($term_migration->getDestinationPlugin()->supportsRollback());
// Pre-create a term, to make sure it isn't deleted on rollback.
$preserved_term_ids[] = 1;
$new_term = Term::create(['tid' => 1, 'vid' => 1, 'name' => 'music']);
$new_term->save();
// Import and validate term entities were created.
$term_executable = new MigrateExecutable($term_migration, $this);
$term_executable->import();
// Also explicitly mark one row to be preserved on rollback.
$preserved_term_ids[] = 2;
$map_row = $term_id_map->getRowBySource(['id' => 2]);
$dummy_row = new Row(['id' => 2], $ids);
$term_id_map->saveIdMapping($dummy_row, [$map_row['destid1']],
$map_row['source_row_status'], MigrateIdMapInterface::ROLLBACK_PRESERVE);
foreach ($term_data_rows as $row) {
/** @var Term $term */
$term = Term::load($row['id']);
$this->assertTrue($term);
$map_row = $term_id_map->getRowBySource(['id' => $row['id']]);
$this->assertNotNull($map_row['destid1']);
}
// Rollback and verify the entities are gone.
$term_executable->rollback();
foreach ($term_data_rows as $row) {
$term = Term::load($row['id']);
if (in_array($row['id'], $preserved_term_ids)) {
$this->assertNotNull($term);
}
else {
$this->assertNull($term);
}
$map_row = $term_id_map->getRowBySource(['id' => $row['id']]);
$this->assertFalse($map_row);
}
$vocabulary_executable->rollback();
foreach ($vocabulary_data_rows as $row) {
$term = Vocabulary::load($row['id']);
$this->assertNull($term);
$map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]);
$this->assertFalse($map_row);
}
// Test that simple configuration is not rollbackable.
$term_setting_rows = [
['id' => 1, 'override_selector' => '0', 'terms_per_page_admin' => '10'],
];
$ids = ['id' => ['type' => 'integer']];
$definition = [
'id' => 'taxonomy_settings',
'migration_tags' => ['Import and rollback test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => $term_setting_rows,
'ids' => $ids,
],
'process' => [
'override_selector' => 'override_selector',
'terms_per_page_admin' => 'terms_per_page_admin',
],
'destination' => [
'plugin' => 'config',
'config_name' => 'taxonomy.settings',
],
'migration_dependencies' => ['required' => ['vocabularies']],
];
$settings_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$this->assertFalse($settings_migration->getDestinationPlugin()->supportsRollback());
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Tests row skips triggered during hook_migrate_prepare_row().
*
* @group migrate
*/
class MigrateSkipRowTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['migrate', 'migrate_prepare_row_test'];
/**
* Tests migration interruptions.
*/
public function testPrepareRowSkip() {
// Run a simple little migration with two data rows which should be skipped
// in different ways.
$definition = [
'migration_tags' => ['prepare_row test'],
'source' => [
'plugin' => 'embedded_data',
'data_rows' => [
['id' => '1', 'data' => 'skip_and_record'],
['id' => '2', 'data' => 'skip_and_dont_record'],
],
'ids' => [
'id' => ['type' => 'string'],
],
],
'process' => ['value' => 'data'],
'destination' => [
'plugin' => 'config',
'config_name' => 'migrate_test.settings',
],
'load' => ['plugin' => 'null'],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$result = $executable->import();
$this->assertEqual($result, MigrationInterface::RESULT_COMPLETED);
$id_map_plugin = $migration->getIdMap();
// The first row is recorded in the map as ignored.
$map_row = $id_map_plugin->getRowBySource(['id' => 1]);
$this->assertEqual(MigrateIdMapInterface::STATUS_IGNORED, $map_row['source_row_status']);
// The second row is not recorded in the map.
$map_row = $id_map_plugin->getRowBySource(['id' => 2]);
$this->assertFalse($map_row);
}
}
@@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Tests migration status tracking.
*
* @group migrate
*/
class MigrateStatusTest extends MigrateTestBase {
/**
* Tests different connection types.
*/
public function testStatus() {
// Create a minimally valid migration.
$definition = [
'id' => 'migration_status_test',
'migration_tags' => ['Testing'],
'source' => ['plugin' => 'empty'],
'destination' => [
'plugin' => 'config',
'config_name' => 'migrate_test.settings',
],
'process' => ['foo' => 'bar'],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
// Default status is idle.
$status = $migration->getStatus();
$this->assertIdentical($status, MigrationInterface::STATUS_IDLE);
// Test setting and retrieving all known status values.
$status_list = array(
MigrationInterface::STATUS_IDLE,
MigrationInterface::STATUS_IMPORTING,
MigrationInterface::STATUS_ROLLING_BACK,
MigrationInterface::STATUS_STOPPING,
MigrationInterface::STATUS_DISABLED,
);
foreach ($status_list as $status) {
$migration->setStatus($status);
$this->assertIdentical($migration->getStatus(), $status);
}
}
}
@@ -0,0 +1,237 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\Core\Database\Database;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
/**
* Creates abstract base class for migration tests.
*/
abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageInterface {
/**
* TRUE to collect messages instead of displaying them.
*
* @var bool
*/
protected $collectMessages = FALSE;
/**
* A two dimensional array of messages.
*
* The first key is the type of message, the second is just numeric. Values
* are the messages.
*
* @var array
*/
protected $migrateMessages;
/**
* The primary migration being tested.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* The source database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $sourceDatabase;
public static $modules = array('migrate');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->createMigrationConnection();
$this->sourceDatabase = Database::getConnection('default', 'migrate');
}
/**
* Changes the database connection to the prefixed one.
*
* @todo Remove when we don't use global. https://www.drupal.org/node/2552791
*/
private function createMigrationConnection() {
// If the backup already exists, something went terribly wrong.
// This case is possible, because database connection info is a static
// global state construct on the Database class, which at least persists
// for all test methods executed in one PHP process.
if (Database::getConnectionInfo('simpletest_original_migrate')) {
throw new \RuntimeException("Bad Database connection state: 'simpletest_original_migrate' connection key already exists. Broken test?");
}
// Clone the current connection and replace the current prefix.
$connection_info = Database::getConnectionInfo('migrate');
if ($connection_info) {
Database::renameConnection('migrate', 'simpletest_original_migrate');
}
$connection_info = Database::getConnectionInfo('default');
foreach ($connection_info as $target => $value) {
$prefix = is_array($value['prefix']) ? $value['prefix']['default'] : $value['prefix'];
// Simpletest uses 7 character prefixes at most so this can't cause
// collisions.
$connection_info[$target]['prefix']['default'] = $prefix . '0';
// Add the original simpletest prefix so SQLite can attach its database.
// @see \Drupal\Core\Database\Driver\sqlite\Connection::init()
$connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];
}
Database::addConnectionInfo('migrate', 'default', $connection_info['default']);
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
$this->cleanupMigrateConnection();
parent::tearDown();
$this->databaseDumpFiles = [];
$this->collectMessages = FALSE;
unset($this->migration, $this->migrateMessages);
}
/**
* Cleans up the test migrate connection.
*
* @todo Remove when we don't use global. https://www.drupal.org/node/2552791
*/
private function cleanupMigrateConnection() {
Database::removeConnection('migrate');
$original_connection_info = Database::getConnectionInfo('simpletest_original_migrate');
if ($original_connection_info) {
Database::renameConnection('simpletest_original_migrate', 'migrate');
}
}
/**
* Prepare any dependent migrations.
*
* @param array $id_mappings
* A list of ID mappings keyed by migration IDs. Each ID mapping is a list
* of two arrays, the first are source IDs and the second are destination
* IDs.
*/
protected function prepareMigrations(array $id_mappings) {
$manager = $this->container->get('plugin.manager.migration');
foreach ($id_mappings as $migration_id => $data) {
foreach ($manager->createInstances($migration_id) as $migration) {
$id_map = $migration->getIdMap();
$id_map->setMessage($this);
$source_ids = $migration->getSourcePlugin()->getIds();
foreach ($data as $id_mapping) {
$row = new Row(array_combine(array_keys($source_ids), $id_mapping[0]), $source_ids);
$id_map->saveIdMapping($row, $id_mapping[1]);
}
}
}
}
/**
* Executes a single migration.
*
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to execute, or its ID.
*/
protected function executeMigration($migration) {
if (is_string($migration)) {
$this->migration = $this->getMigration($migration);
}
else {
$this->migration = $migration;
}
if ($this instanceof MigrateDumpAlterInterface) {
static::migrateDumpAlter($this);
}
(new MigrateExecutable($this->migration, $this))->import();
}
/**
* Executes a set of migrations in dependency order.
*
* @param string[] $ids
* Array of migration IDs, in any order.
*/
protected function executeMigrations(array $ids) {
$manager = $this->container->get('plugin.manager.migration');
array_walk($ids, function ($id) use ($manager) {
// This is possibly a base plugin ID and we want to run all derivatives.
$instances = $manager->createInstances($id);
array_walk($instances, [$this, 'executeMigration']);
});
}
/**
* {@inheritdoc}
*/
public function display($message, $type = 'status') {
if ($this->collectMessages) {
$this->migrateMessages[$type][] = $message;
}
else {
$this->assert($type == 'status', $message, 'migrate');
}
}
/**
* Start collecting messages and erase previous messages.
*/
public function startCollectingMessages() {
$this->collectMessages = TRUE;
$this->migrateMessages = array();
}
/**
* Stop collecting messages.
*/
public function stopCollectingMessages() {
$this->collectMessages = FALSE;
}
/**
* Records a failure in the map table of a specific migration.
*
* This is done in order to test scenarios which require a failed row.
*
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity, or its ID.
* @param array $row
* The raw source row which "failed".
* @param int $status
* (optional) The failure status. Should be one of the
* MigrateIdMapInterface::STATUS_* constants. Defaults to
* MigrateIdMapInterface::STATUS_FAILED.
*/
protected function mockFailure($migration, array $row, $status = MigrateIdMapInterface::STATUS_FAILED) {
if (is_string($migration)) {
$migration = $this->getMigration($migration);
}
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
$destination = array_map(function() { return NULL; }, $migration->getDestinationPlugin()->getIds());
$row = new Row($row, $migration->getSourcePlugin()->getIds());
$migration->getIdMap()->saveIdMapping($row, $destination, $status);
}
/**
* Gets the migration plugin.
*
* @param $plugin_id
* The plugin ID of the migration to get.
*
* @return \Drupal\migrate\Plugin\Migration
* The migration plugin.
*/
protected function getMigration($plugin_id) {
return $this->container->get('plugin.manager.migration')->createInstance($plugin_id);
}
}
@@ -0,0 +1,45 @@
<?php
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the migration plugin.
*
* @group migrate
*
* @coversDefaultClass \Drupal\migrate\Plugin\Migration
*/
class MigrationTest extends KernelTestBase {
/**
* Enable field because we are using one of its source plugins.
*
* @var array
*/
public static $modules = ['migrate', 'field'];
/**
* Tests Migration::set().
*
* @covers ::set
*/
public function testSetInvalidation() {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([
'source' => ['plugin' => 'empty'],
'destination' => ['plugin' => 'entity:entity_view_mode'],
]);
$this->assertEqual('empty', $migration->getSourcePlugin()->getPluginId());
$this->assertEqual('entity:entity_view_mode', $migration->getDestinationPlugin()->getPluginId());
// Test the source plugin is invalidated.
$migration->set('source', ['plugin' => 'd6_field']);
$this->assertEqual('d6_field', $migration->getSourcePlugin()->getPluginId());
// Test the destination plugin is invalidated.
$migration->set('destination', ['plugin' => 'null']);
$this->assertEqual('null', $migration->getDestinationPlugin()->getPluginId());
}
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\migrate\Kernel\Plugin;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the migration plugin.
*
* @coversDefaultClass \Drupal\migrate\Plugin\Migration
* @group migrate
*/
class MigrationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['migrate'];
/**
* Tests Migration::getProcessPlugins()
*
* @covers ::getProcessPlugins
*/
public function testGetProcessPlugins() {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([]);
$this->assertEquals([], $migration->getProcessPlugins([]));
}
/**
* Tests Migration::getMigrationDependencies()
*
* @covers ::getMigrationDependencies
*/
public function testGetMigrationDependencies() {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([
'migration_dependencies' => NULL
]);
$this->assertNotEmpty($migration->getMigrationDependencies(), 'Migration dependencies is not empty');
}
/**
* Tests Migration::getDestinationIds()
*
* @covers ::getDestinationIds
*/
public function testGetDestinationIds() {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration(['destinationIds' => ['foo' => 'bar']]);
$destination_ids = $migration->getDestinationIds();
$this->assertNotEmpty($destination_ids, 'Destination ids are not empty');
$this->assertEquals(['foo' => 'bar'], $destination_ids, 'Destination ids match the expected values.');
}
/**
* Tests Migration::getTrackLastImported()
*
* @covers ::getTrackLastImported
* @covers ::isTrackLastImported
*/
public function testGetTrackLastImported() {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([]);
$migration->setTrackLastImported(TRUE);
$this->assertEquals(TRUE, $migration->getTrackLastImported());
$this->assertEquals(TRUE, $migration->isTrackLastImported());
}
}
@@ -0,0 +1,138 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Kernel\SqlBaseTest.
*/
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\Plugin\migrate\source\TestSqlBase;
use Drupal\Core\Database\Database;
/**
* Tests the functionality of SqlBase.
*
* @group migrate
*/
class SqlBaseTest extends MigrateTestBase {
/**
* Tests different connection types.
*/
public function testConnectionTypes() {
$sql_base = new TestSqlBase();
// Check the default values.
$sql_base->setConfiguration([]);
$this->assertIdentical($sql_base->getDatabase()->getTarget(), 'default');
$this->assertIdentical($sql_base->getDatabase()->getKey(), 'migrate');
$target = 'test_db_target';
$key = 'test_migrate_connection';
$config = array('target' => $target, 'key' => $key);
$sql_base->setConfiguration($config);
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
// Validate we have injected our custom key and target.
$this->assertIdentical($sql_base->getDatabase()->getTarget(), $target);
$this->assertIdentical($sql_base->getDatabase()->getKey(), $key);
// Now test we can have SqlBase create the connection from an info array.
$sql_base = new TestSqlBase();
$target = 'test_db_target2';
$key = 'test_migrate_connection2';
$database = Database::getConnectionInfo('default')['default'];
$config = array('target' => $target, 'key' => $key, 'database' => $database);
$sql_base->setConfiguration($config);
// Call getDatabase() to get the connection defined.
$sql_base->getDatabase();
// Validate the connection has been created with the right values.
$this->assertIdentical(Database::getConnectionInfo($key)[$target], $database);
// Now, test this all works when using state to store db info.
$target = 'test_state_db_target';
$key = 'test_state_migrate_connection';
$config = ['target' => $target, 'key' => $key];
$database_state_key = 'migrate_sql_base_test';
\Drupal::state()->set($database_state_key, $config);
$sql_base->setConfiguration(['database_state_key' => $database_state_key]);
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
// Validate we have injected our custom key and target.
$this->assertIdentical($sql_base->getDatabase()->getTarget(), $target);
$this->assertIdentical($sql_base->getDatabase()->getKey(), $key);
// Now test we can have SqlBase create the connection from an info array.
$sql_base = new TestSqlBase();
$target = 'test_state_db_target2';
$key = 'test_state_migrate_connection2';
$database = Database::getConnectionInfo('default')['default'];
$config = ['target' => $target, 'key' => $key, 'database' => $database];
$database_state_key = 'migrate_sql_base_test2';
\Drupal::state()->set($database_state_key, $config);
$sql_base->setConfiguration(['database_state_key' => $database_state_key]);
// Call getDatabase() to get the connection defined.
$sql_base->getDatabase();
// Validate the connection has been created with the right values.
$this->assertIdentical(Database::getConnectionInfo($key)[$target], $database);
}
}
namespace Drupal\migrate\Plugin\migrate\source;
/**
* A dummy source to help with testing SqlBase.
*
* @package Drupal\migrate\Plugin\migrate\source
*/
class TestSqlBase extends SqlBase {
/**
* Overrides the constructor so we can create one easily.
*/
public function __construct() {
$this->state = \Drupal::state();
}
/**
* Gets the database without caching it.
*/
public function getDatabase() {
$this->database = NULL;
return parent::getDatabase();
}
/**
* Allows us to set the configuration from a test.
*
* @param array $config
* The config array.
*/
public function setConfiguration($config) {
$this->configuration = $config;
}
/**
* {@inheritdoc}
*/
public function getIds() {}
/**
* {@inheritdoc}
*/
public function fields() {}
/**
* {@inheritdoc}
*/
public function query() {}
}
@@ -0,0 +1,51 @@
<?php
namespace Drupal\Tests\migrate\Unit\Exception;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Exception\RequirementsException
* @group migration
*/
class RequirementsExceptionTest extends UnitTestCase {
protected $missingRequirements = ['random_jackson_pivot', '51_Eridani_b'];
/**
* @covers ::getRequirements
*/
public function testGetRequirements() {
$exception = new RequirementsException('Missing requirements ', ['requirements' => $this->missingRequirements]);
$this->assertArrayEquals(['requirements' => $this->missingRequirements], $exception->getRequirements());
}
/**
* @covers ::getRequirementsString
* @dataProvider getRequirementsProvider
*/
public function testGetExceptionString($expected, $message, $requirements) {
$exception = new RequirementsException($message, $requirements);
$this->assertEquals($expected, $exception->getRequirementsString());
}
/**
* Provides a list of requirements to test.
*/
public function getRequirementsProvider() {
return array(
array(
'requirements: random_jackson_pivot.',
'Single Requirement',
array('requirements' => $this->missingRequirements[0]),
),
array(
'requirements: random_jackson_pivot. requirements: 51_Eridani_b.',
'Multiple Requirements',
array('requirements' => $this->missingRequirements),
),
);
}
}
@@ -0,0 +1,118 @@
<?php
namespace Drupal\Tests\migrate\Unit;
/**
* Tests the \Drupal\migrate\MigrateExecutable::memoryExceeded() method.
*
* @group migrate
*/
class MigrateExecutableMemoryExceededTest extends MigrateTestCase {
/**
* The mocked migration entity.
*
* @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $migration;
/**
* The mocked migrate message.
*
* @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $message;
/**
* The tested migrate executable.
*
* @var \Drupal\Tests\migrate\Unit\TestMigrateExecutable
*/
protected $executable;
/**
* The migration configuration, initialized to set the ID to test.
*
* @var array
*/
protected $migrationConfiguration = array(
'id' => 'test',
);
/**
* The php.ini memory_limit value.
*
* @var int
*/
protected $memoryLimit = 10000000;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migration = $this->getMigration();
$this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
$this->executable = new TestMigrateExecutable($this->migration, $this->message);
$this->executable->setStringTranslation($this->getStringTranslationStub());
}
/**
* Runs the actual test.
*
* @param string $message
* The second message to assert.
* @param bool $memory_exceeded
* Whether to test the memory exceeded case.
* @param int|null $memory_usage_first
* (optional) The first memory usage value. Defaults to NULL.
* @param int|null $memory_usage_second
* (optional) The fake amount of memory usage reported after memory reclaim.
* Defaults to NULL.
* @param int|null $memory_limit
* (optional) The memory limit. Defaults to NULL.
*/
protected function runMemoryExceededTest($message, $memory_exceeded, $memory_usage_first = NULL, $memory_usage_second = NULL, $memory_limit = NULL) {
$this->executable->setMemoryLimit($memory_limit ?: $this->memoryLimit);
$this->executable->setMemoryUsage($memory_usage_first ?: $this->memoryLimit, $memory_usage_second ?: $this->memoryLimit);
$this->executable->setMemoryThreshold(0.85);
if ($message) {
$this->executable->message->expects($this->at(0))
->method('display')
->with($this->stringContains('reclaiming memory'));
$this->executable->message->expects($this->at(1))
->method('display')
->with($this->stringContains($message));
}
else {
$this->executable->message->expects($this->never())
->method($this->anything());
}
$result = $this->executable->memoryExceeded();
$this->assertEquals($memory_exceeded, $result);
}
/**
* Tests memoryExceeded method when a new batch is needed.
*/
public function testMemoryExceededNewBatch() {
// First case try reset and then start new batch.
$this->runMemoryExceededTest('starting new batch', TRUE);
}
/**
* Tests memoryExceeded method when enough is cleared.
*/
public function testMemoryExceededClearedEnough() {
$this->runMemoryExceededTest('reclaimed enough', FALSE, $this->memoryLimit, $this->memoryLimit * 0.75);
}
/**
* Tests memoryExceeded when memory usage is not exceeded.
*/
public function testMemoryNotExceeded() {
$this->runMemoryExceededTest('', FALSE, floor($this->memoryLimit * 0.85) - 1);
}
}
@@ -0,0 +1,449 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Component\Utility\Html;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
/**
* @coversDefaultClass \Drupal\migrate\MigrateExecutable
* @group migrate
*/
class MigrateExecutableTest extends MigrateTestCase {
/**
* The mocked migration entity.
*
* @var \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $migration;
/**
* The mocked migrate message.
*
* @var \Drupal\migrate\MigrateMessageInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $message;
/**
* The tested migrate executable.
*
* @var \Drupal\Tests\migrate\Unit\TestMigrateExecutable
*/
protected $executable;
/**
* The migration's configuration values.
*
* @var array
*/
protected $migrationConfiguration = array(
'id' => 'test',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migration = $this->getMigration();
$this->message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->executable = new TestMigrateExecutable($this->migration, $this->message, $event_dispatcher);
$this->executable->setStringTranslation($this->getStringTranslationStub());
}
/**
* Tests an import with an incomplete rewinding.
*/
public function testImportWithFailingRewind() {
$exception_message = $this->getRandomGenerator()->string();
$source = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$source->expects($this->once())
->method('rewind')
->will($this->throwException(new \Exception($exception_message)));
$this->migration->expects($this->any())
->method('getSourcePlugin')
->will($this->returnValue($source));
// Ensure that a message with the proper message was added.
$this->message->expects($this->once())
->method('display')
->with("Migration failed with source plugin exception: " . Html::escape($exception_message));
$result = $this->executable->import();
$this->assertEquals(MigrationInterface::RESULT_FAILED, $result);
}
/**
* Tests the import method with a valid row.
*/
public function testImportWithValidRow() {
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->will($this->returnValue(array('id' => 'test')));
$this->idMap->expects($this->once())
->method('lookupDestinationId')
->with(array('id' => 'test'))
->will($this->returnValue(array('test')));
$source->expects($this->once())
->method('current')
->will($this->returnValue($row));
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->will($this->returnValue(array()));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->once())
->method('import')
->with($row, array('test'))
->will($this->returnValue(array('id' => 'test')));
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->will($this->returnValue($destination));
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the import method with a valid row.
*/
public function testImportWithValidRowWithoutDestinationId() {
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->will($this->returnValue(array('id' => 'test')));
$this->idMap->expects($this->once())
->method('lookupDestinationId')
->with(array('id' => 'test'))
->will($this->returnValue(array('test')));
$source->expects($this->once())
->method('current')
->will($this->returnValue($row));
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->will($this->returnValue(array()));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->once())
->method('import')
->with($row, array('test'))
->will($this->returnValue(TRUE));
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->will($this->returnValue($destination));
$this->idMap->expects($this->never())
->method('saveIdMapping');
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the import method with a valid row.
*/
public function testImportWithValidRowNoDestinationValues() {
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->will($this->returnValue(array('id' => 'test')));
$source->expects($this->once())
->method('current')
->will($this->returnValue($row));
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->will($this->returnValue(array()));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->once())
->method('import')
->with($row, array('test'))
->will($this->returnValue(array()));
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->will($this->returnValue($destination));
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
$this->idMap->expects($this->once())
->method('messageCount')
->will($this->returnValue(0));
$this->idMap->expects($this->once())
->method('saveMessage');
$this->idMap->expects($this->once())
->method('lookupDestinationId')
->with(array('id' => 'test'))
->will($this->returnValue(array('test')));
$this->message->expects($this->once())
->method('display')
->with('New object was not saved, no error provided');
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the import method with a thrown MigrateException.
*
* The MigrationException in this case is being thrown from the destination.
*/
public function testImportWithValidRowWithDestinationMigrateException() {
$exception_message = $this->getRandomGenerator()->string();
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->will($this->returnValue(array('id' => 'test')));
$source->expects($this->once())
->method('current')
->will($this->returnValue($row));
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->will($this->returnValue(array()));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->once())
->method('import')
->with($row, array('test'))
->will($this->throwException(new MigrateException($exception_message)));
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->will($this->returnValue($destination));
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
$this->idMap->expects($this->once())
->method('saveMessage');
$this->idMap->expects($this->once())
->method('lookupDestinationId')
->with(array('id' => 'test'))
->will($this->returnValue(array('test')));
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the import method with a thrown MigrateException.
*
* The MigrationException in this case is being thrown from a process plugin.
*/
public function testImportWithValidRowWithProcesMigrateException() {
$exception_message = $this->getRandomGenerator()->string();
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->willReturn(array('id' => 'test'));
$source->expects($this->once())
->method('current')
->willReturn($row);
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->willThrowException(new MigrateException($exception_message));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->never())
->method('import');
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->willReturn($destination);
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
$this->idMap->expects($this->once())
->method('saveMessage');
$this->idMap->expects($this->never())
->method('lookupDestinationId');
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the import method with a regular Exception being thrown.
*/
public function testImportWithValidRowWithException() {
$exception_message = $this->getRandomGenerator()->string();
$source = $this->getMockSource();
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('getSourceIdValues')
->will($this->returnValue(array('id' => 'test')));
$source->expects($this->once())
->method('current')
->will($this->returnValue($row));
$this->executable->setSource($source);
$this->migration->expects($this->once())
->method('getProcessPlugins')
->will($this->returnValue(array()));
$destination = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$destination->expects($this->once())
->method('import')
->with($row, array('test'))
->will($this->throwException(new \Exception($exception_message)));
$this->migration->expects($this->once())
->method('getDestinationPlugin')
->will($this->returnValue($destination));
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, array(), MigrateIdMapInterface::STATUS_FAILED, NULL);
$this->idMap->expects($this->once())
->method('saveMessage');
$this->idMap->expects($this->once())
->method('lookupDestinationId')
->with(array('id' => 'test'))
->will($this->returnValue(array('test')));
$this->message->expects($this->once())
->method('display')
->with($exception_message);
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
}
/**
* Tests the processRow method.
*/
public function testProcessRow() {
$expected = array(
'test' => 'test destination',
'test1' => 'test1 destination'
);
foreach ($expected as $key => $value) {
$plugins[$key][0] = $this->getMock('Drupal\migrate\Plugin\MigrateProcessInterface');
$plugins[$key][0]->expects($this->once())
->method('getPluginDefinition')
->will($this->returnValue(array()));
$plugins[$key][0]->expects($this->once())
->method('transform')
->will($this->returnValue($value));
}
$this->migration->expects($this->once())
->method('getProcessPlugins')
->with(NULL)
->will($this->returnValue($plugins));
$row = new Row(array(), array());
$this->executable->processRow($row);
foreach ($expected as $key => $value) {
$this->assertSame($row->getDestinationProperty($key), $value);
}
$this->assertSame(count($row->getDestination()), count($expected));
}
/**
* Tests the processRow method with an empty pipeline.
*/
public function testProcessRowEmptyPipeline() {
$this->migration->expects($this->once())
->method('getProcessPlugins')
->with(NULL)
->will($this->returnValue(array('test' => array())));
$row = new Row(array(), array());
$this->executable->processRow($row);
$this->assertSame($row->getDestination(), array());
}
/**
* Returns a mock migration source instance.
*
* @return \Drupal\migrate\Plugin\MigrateSourceInterface|\PHPUnit_Framework_MockObject_MockObject
* The mocked migration source.
*/
protected function getMockSource() {
$iterator = $this->getMock('\Iterator');
$class = 'Drupal\migrate\Plugin\migrate\source\SourcePluginBase';
$source = $this->getMockBuilder($class)
->disableOriginalConstructor()
->setMethods(get_class_methods($class))
->getMockForAbstractClass();
$source->expects($this->once())
->method('rewind')
->will($this->returnValue(TRUE));
$source->expects($this->any())
->method('initializeIterator')
->will($this->returnValue([]));
$source->expects($this->any())
->method('valid')
->will($this->onConsecutiveCalls(TRUE, FALSE));
return $source;
}
}
@@ -0,0 +1,452 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\MigrateSourceTest.
*/
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
* @group migrate
*/
class MigrateSourceTest extends MigrateTestCase {
/**
* Override the migration config.
*
* @var array
*/
protected $defaultMigrationConfiguration = [
'id' => 'test_migration',
'source' => [],
];
/**
* Test row data.
*
* @var array
*/
protected $row = ['test_sourceid1' => '1', 'timestamp' => 500];
/**
* Test source ids.
*
* @var array
*/
protected $sourceIds = ['test_sourceid1' => 'test_sourceid1'];
/**
* The migration entity.
*
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* The migrate executable.
*
* @var \Drupal\migrate\MigrateExecutable
*/
protected $executable;
/**
* Gets the source plugin to test.
*
* @param array $configuration
* (optional) The source configuration. Defaults to an empty array.
* @param array $migrate_config
* (optional) The migration configuration to be used in
* parent::getMigration(). Defaults to an empty array.
* @param int $status
* (optional) The default status for the new rows to be imported. Defaults
* to MigrateIdMapInterface::STATUS_NEEDS_UPDATE.
*
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
* A mocked source plugin.
*/
protected function getSource($configuration = [], $migrate_config = [], $status = MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
$this->migrationConfiguration = $this->defaultMigrationConfiguration + $migrate_config;
$this->migration = parent::getMigration();
$this->executable = $this->getMigrateExecutable($this->migration);
// Update the idMap for Source so the default is that the row has already
// been imported. This allows us to use the highwater mark to decide on the
// outcome of whether we choose to import the row.
$id_map_array = ['original_hash' => '', 'hash' => '', 'source_row_status' => $status];
$this->idMap
->expects($this->any())
->method('getRowBySource')
->willReturn($id_map_array);
$constructor_args = [$configuration, 'd6_action', [], $this->migration];
$methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'getIterator', 'prepareRow', 'initializeIterator', 'calculateDependencies'];
$source_plugin = $this->getMock('\Drupal\migrate\Plugin\migrate\source\SourcePluginBase', $methods, $constructor_args);
$source_plugin
->expects($this->any())
->method('fields')
->willReturn([]);
$source_plugin
->expects($this->any())
->method('getIds')
->willReturn([]);
$source_plugin
->expects($this->any())
->method('__toString')
->willReturn('');
$source_plugin
->expects($this->any())
->method('prepareRow')
->willReturn(empty($migrate_config['prepare_row_false']));
$source_plugin
->expects($this->any())
->method('initializeIterator')
->willReturn([]);
$iterator = new \ArrayIterator([$this->row]);
$source_plugin
->expects($this->any())
->method('getIterator')
->willReturn($iterator);
$module_handler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
$source_plugin
->expects($this->any())
->method('getModuleHandler')
->willReturn($module_handler);
$this->migration
->expects($this->any())
->method('getSourcePlugin')
->willReturn($source_plugin);
return $this->migration->getSourcePlugin();
}
/**
* @covers ::__construct
* @expectedException \Drupal\migrate\MigrateException
*/
public function testHighwaterTrackChangesIncompatible() {
$source_config = ['track_changes' => TRUE];
$migration_config = ['highWaterProperty' => ['name' => 'something']];
$this->getSource($source_config, $migration_config);
}
/**
* Test that the source count is correct.
*
* @covers ::count
*/
public function testCount() {
// Mock the cache to validate set() receives appropriate arguments.
$container = new ContainerBuilder();
$cache = $this->getMock(CacheBackendInterface::class);
$cache->expects($this->any())->method('set')
->with($this->isType('string'), $this->isType('int'), $this->isType('int'));
$container->set('cache.migrate', $cache);
\Drupal::setContainer($container);
// Test that the basic count works.
$source = $this->getSource();
$this->assertEquals(1, $source->count());
// Test caching the count works.
$source = $this->getSource(['cache_counts' => TRUE]);
$this->assertEquals(1, $source->count());
// Test the skip argument.
$source = $this->getSource(['skip_count' => TRUE]);
$this->assertEquals(-1, $source->count());
}
/**
* Test that the key can be set for the count cache.
*
* @covers ::count
*/
public function testCountCacheKey() {
// Mock the cache to validate set() receives appropriate arguments.
$container = new ContainerBuilder();
$cache = $this->getMock(CacheBackendInterface::class);
$cache->expects($this->any())->method('set')
->with('test_key', $this->isType('int'), $this->isType('int'));
$container->set('cache.migrate', $cache);
\Drupal::setContainer($container);
// Test caching the count with a configured key works.
$source = $this->getSource(['cache_counts' => TRUE, 'cache_key' => 'test_key']);
$this->assertEquals(1, $source->count());
}
/**
* Test that we don't get a row if prepareRow() is false.
*/
public function testPrepareRowFalse() {
$source = $this->getSource([], ['prepare_row_false' => TRUE]);
$source->rewind();
$this->assertNull($source->current(), 'No row is available when prepareRow() is false.');
}
/**
* Test that $row->needsUpdate() works as expected.
*/
public function testNextNeedsUpdate() {
$source = $this->getSource();
// $row->needsUpdate() === TRUE so we get a row.
$source->rewind();
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), '$row->needsUpdate() is TRUE so we got a row.');
// Test that we don't get a row when the incoming row is marked as imported.
$source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
$source->rewind();
$this->assertNull($source->current(), 'Row was already imported, should be NULL');
}
/**
* Test that an outdated highwater mark does not cause a row to be imported.
*/
public function testOutdatedHighwater() {
$source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
// Set the originalHighwater to something higher than our timestamp.
$this->migration
->expects($this->any())
->method('getHighwater')
->willReturn($this->row['timestamp'] + 1);
// The current highwater mark is now higher than the row timestamp so no row
// is expected.
$source->rewind();
$this->assertNull($source->current(), 'Original highwater mark is higher than incoming row timestamp.');
}
/**
* Test that a highwater mark newer than our saved one imports a row.
*
* @throws \Exception
*/
public function testNewHighwater() {
// Set a highwater property field for source. Now we should have a row
// because the row timestamp is greater than the current highwater mark.
$source = $this->getSource([], ['highWaterProperty' => ['name' => 'timestamp']], MigrateIdMapInterface::STATUS_IMPORTED);
$source->rewind();
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
}
/**
* Test basic row preparation.
*
* @covers ::prepareRow
*/
public function testPrepareRow() {
$this->migrationConfiguration['id'] = 'test_migration';
// Get a new migration with an id.
$migration = $this->getMigration();
$source = new StubSourcePlugin([], '', [], $migration);
$row = new Row([], []);
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$source->setModuleHandler($module_handler->reveal());
// Ensure we don't log this to the mapping table.
$this->idMap->expects($this->never())
->method('saveIdMapping');
$this->assertTrue($source->prepareRow($row));
// Track_changes...
$source = new StubSourcePlugin(['track_changes' => TRUE], '', [], $migration);
$row2 = $this->prophesize(Row::class);
$row2->rehash()
->shouldBeCalled();
$module_handler->invokeAll('migrate_prepare_row', [$row2, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row2, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$source->setModuleHandler($module_handler->reveal());
$this->assertTrue($source->prepareRow($row2->reveal()));
}
/**
* Test that global prepare hooks can skip rows.
*
* @covers ::prepareRow
*/
public function testPrepareRowGlobalPrepareSkip() {
$this->migrationConfiguration['id'] = 'test_migration';
$migration = $this->getMigration();
$source = new StubSourcePlugin([], '', [], $migration);
$row = new Row([], []);
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
// Return a failure from a prepare row hook.
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, FALSE, TRUE])
->shouldBeCalled();
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$source->setModuleHandler($module_handler->reveal());
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
$this->assertFalse($source->prepareRow($row));
}
/**
* Test that migrate specific prepare hooks can skip rows.
*
* @covers ::prepareRow
*/
public function testPrepareRowMigratePrepareSkip() {
$this->migrationConfiguration['id'] = 'test_migration';
$migration = $this->getMigration();
$source = new StubSourcePlugin([], '', [], $migration);
$row = new Row([], []);
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
// Return a failure from a prepare row hook.
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, FALSE, TRUE])
->shouldBeCalled();
$source->setModuleHandler($module_handler->reveal());
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
$this->assertFalse($source->prepareRow($row));
}
/**
* Test that a skip exception during prepare hooks correctly skips.
*
* @covers ::prepareRow
*/
public function testPrepareRowPrepareException() {
$this->migrationConfiguration['id'] = 'test_migration';
$migration = $this->getMigration();
$source = new StubSourcePlugin([], '', [], $migration);
$row = new Row([], []);
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
// Return a failure from a prepare row hook.
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
->willReturn([TRUE, TRUE])
->shouldBeCalled();
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
->willThrow(new MigrateSkipRowException())
->shouldBeCalled();
$source->setModuleHandler($module_handler->reveal());
// This will only be called on the first prepare because the second
// explicitly avoids it.
$this->idMap->expects($this->once())
->method('saveIdMapping')
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
$this->assertFalse($source->prepareRow($row));
// Throw an exception the second time that avoids mapping.
$e = new MigrateSkipRowException('', FALSE);
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
->willThrow($e)
->shouldBeCalled();
$this->assertFalse($source->prepareRow($row));
}
/**
* Gets a mock executable for the test.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
*
* @return \Drupal\migrate\MigrateExecutable
* The migrate executable.
*/
protected function getMigrateExecutable($migration) {
/** @var \Drupal\migrate\MigrateMessageInterface $message */
$message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
/** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
return new MigrateExecutable($migration, $message, $event_dispatcher);
}
}
/**
* Stubbed source plugin for testing base class implementations.
*/
class StubSourcePlugin extends SourcePluginBase {
/**
* Helper for setting internal module handler implementation.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public function fields() {
return [];
}
/**
* {@inheritdoc}
*/
public function __toString() {
return '';
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [];
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
return [];
}
}
@@ -0,0 +1,229 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Tests the SQL ID map plugin ensureTables() method.
*
* @group migrate
*/
class MigrateSqlIdMapEnsureTablesTest extends MigrateTestCase {
/**
* The migration configuration, initialized to set the ID and destination IDs.
*
* @var array
*/
protected $migrationConfiguration = array(
'id' => 'sql_idmap_test',
);
/**
* Tests the ensureTables method when the tables do not exist.
*/
public function testEnsureTablesNotExist() {
$fields['source_ids_hash'] = Array(
'type' => 'varchar',
'length' => 64,
'not null' => 1,
'description' => 'Hash of source ids. Used as primary key'
);
$fields['sourceid1'] = array(
'type' => 'int',
'not null' => TRUE,
);
$fields['destid1'] = array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
);
$fields['source_row_status'] = array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => MigrateIdMapInterface::STATUS_IMPORTED,
'description' => 'Indicates current status of the source row',
);
$fields['rollback_action'] = array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => MigrateIdMapInterface::ROLLBACK_DELETE,
'description' => 'Flag indicating what to do for this item on rollback',
);
$fields['last_imported'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'UNIX timestamp of the last time this row was imported',
);
$fields['hash'] = array(
'type' => 'varchar',
'length' => '64',
'not null' => FALSE,
'description' => 'Hash of source row data, for detecting changes',
);
$map_table_schema = array(
'description' => 'Mappings from source identifier value(s) to destination identifier value(s).',
'fields' => $fields,
'primary key' => array('source_ids_hash'),
);
$schema = $this->getMockBuilder('Drupal\Core\Database\Schema')
->disableOriginalConstructor()
->getMock();
$schema->expects($this->at(0))
->method('tableExists')
->with('migrate_map_sql_idmap_test')
->will($this->returnValue(FALSE));
$schema->expects($this->at(1))
->method('createTable')
->with('migrate_map_sql_idmap_test', $map_table_schema);
// Now do the message table.
$fields = array();
$fields['msgid'] = array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
);
$fields['source_ids_hash'] = Array(
'type' => 'varchar',
'length' => 64,
'not null' => 1,
'description' => 'Hash of source ids. Used as primary key'
);
$fields['level'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 1,
);
$fields['message'] = array(
'type' => 'text',
'size' => 'medium',
'not null' => TRUE,
);
$table_schema = array(
'description' => 'Messages generated during a migration process',
'fields' => $fields,
'primary key' => array('msgid'),
);
$schema->expects($this->at(2))
->method('tableExists')
->with('migrate_message_sql_idmap_test')
->will($this->returnValue(FALSE));
$schema->expects($this->at(3))
->method('createTable')
->with('migrate_message_sql_idmap_test', $table_schema);
$schema->expects($this->any())
->method($this->anything());
$this->runEnsureTablesTest($schema);
}
/**
* Tests the ensureTables method when the tables exist.
*/
public function testEnsureTablesExist() {
$schema = $this->getMockBuilder('Drupal\Core\Database\Schema')
->disableOriginalConstructor()
->getMock();
$schema->expects($this->at(0))
->method('tableExists')
->with('migrate_map_sql_idmap_test')
->will($this->returnValue(TRUE));
$schema->expects($this->at(1))
->method('fieldExists')
->with('migrate_map_sql_idmap_test', 'rollback_action')
->will($this->returnValue(FALSE));
$field_schema = array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'Flag indicating what to do for this item on rollback',
);
$schema->expects($this->at(2))
->method('addField')
->with('migrate_map_sql_idmap_test', 'rollback_action', $field_schema);
$schema->expects($this->at(3))
->method('fieldExists')
->with('migrate_map_sql_idmap_test', 'hash')
->will($this->returnValue(FALSE));
$field_schema = array(
'type' => 'varchar',
'length' => '64',
'not null' => FALSE,
'description' => 'Hash of source row data, for detecting changes',
);
$schema->expects($this->at(4))
->method('addField')
->with('migrate_map_sql_idmap_test', 'hash', $field_schema);
$schema->expects($this->at(5))
->method('fieldExists')
->with('migrate_map_sql_idmap_test', 'source_ids_hash')
->will($this->returnValue(FALSE));
$field_schema = array(
'type' => 'varchar',
'length' => '64',
'not null' => TRUE,
'description' => 'Hash of source ids. Used as primary key',
);
$schema->expects($this->at(6))
->method('addField')
->with('migrate_map_sql_idmap_test', 'source_ids_hash', $field_schema);
$schema->expects($this->exactly(7))
->method($this->anything());
$this->runEnsureTablesTest($schema);
}
/**
* Actually run the test.
*
* @param array $schema
* The mock schema object with expectations set. The Sql constructor calls
* ensureTables() which in turn calls this object and the expectations on
* it are the actual test and there are no additional asserts added.
*/
protected function runEnsureTablesTest($schema) {
$database = $this->getMockBuilder('Drupal\Core\Database\Connection')
->disableOriginalConstructor()
->getMock();
$database->expects($this->any())
->method('schema')
->willReturn($schema);
$migration = $this->getMigration();
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$plugin->expects($this->any())
->method('getIds')
->willReturn(array(
'source_id_property' => array(
'type' => 'integer',
),
));
$migration->expects($this->any())
->method('getSourcePlugin')
->willReturn($plugin);
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$plugin->expects($this->any())
->method('getIds')
->willReturn(array(
'destination_id_property' => array(
'type' => 'string',
),
));
$migration->expects($this->any())
->method('getDestinationPlugin')
->willReturn($plugin);
/** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$map = new TestSqlIdMap($database, array(), 'sql', array(), $migration, $event_dispatcher);
$map->getDatabase();
}
}
@@ -0,0 +1,984 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\Database\Driver\sqlite\Connection;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
/**
* Tests the SQL ID map plugin.
*
* @group migrate
*/
class MigrateSqlIdMapTest extends MigrateTestCase {
/**
* The migration configuration, initialized to set the ID and destination IDs.
*
* @var array
*/
protected $migrationConfiguration = [
'id' => 'sql_idmap_test',
];
/**
* The source IDs.
*
* @var array
*/
protected $sourceIds = [
'source_id_property' => [
'type' => 'string',
],
];
/**
* The destination IDs.
*
* @var array
*/
protected $destinationIds = [
'destination_id_property' => [
'type' => 'string',
],
];
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->database = $this->getDatabase([]);
}
/**
* Saves a single ID mapping row in the database.
*
* @param array $map
* The row to save.
*/
protected function saveMap(array $map) {
$table = 'migrate_map_sql_idmap_test';
$schema = $this->database->schema();
// If the table already exists, add any columns which are in the map array,
// but don't yet exist in the table. Yay, flexibility!
if ($schema->tableExists($table)) {
foreach (array_keys($map) as $field) {
if (!$schema->fieldExists($table, $field)) {
$schema->addField($table, $field, ['type' => 'text']);
}
}
}
else {
$schema->createTable($table, $this->createSchemaFromRow($map));
}
$this->database->insert($table)->fields($map)->execute();
}
/**
* Creates a test SQL ID map plugin.
*
* @return \Drupal\Tests\migrate\Unit\TestSqlIdMap
* A SQL ID map plugin test instance.
*/
protected function getIdMap() {
$migration = $this->getMigration();
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$plugin
->method('getIds')
->willReturn($this->sourceIds);
$migration
->method('getSourcePlugin')
->willReturn($plugin);
$plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$plugin
->method('getIds')
->willReturn($this->destinationIds);
$migration
->method('getDestinationPlugin')
->willReturn($plugin);
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration, $event_dispatcher);
$migration
->method('getIdMap')
->willReturn($id_map);
return $id_map;
}
/**
* Sets defaults for SQL ID map plugin tests.
*
* @return array
* An associative array with the following keys:
* - source_row_status
* - rollback_action
* - hash
*/
protected function idMapDefaults() {
$defaults = array(
'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED,
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
'hash' => '',
);
// By default, the PDO SQLite driver strongly prefers to return strings
// from SELECT queries. Even for columns that don't store strings. Even
// if the connection's STRINGIFY_FETCHES attribute is FALSE. This can cause
// assertSame() calls to fail, since 0 !== '0'. Casting these values to
// strings isn't the most elegant workaround, but it allows the assertions
// to pass properly.
if ($this->database->driver() == 'sqlite') {
$defaults['source_row_status'] = (string) $defaults['source_row_status'];
$defaults['rollback_action'] = (string) $defaults['rollback_action'];
}
return $defaults;
}
/**
* Tests the ID mapping method.
*
* Create two ID mappings and update the second to verify that:
* - saving new to empty tables work.
* - saving new to nonempty tables work.
* - updating work.
*/
public function testSaveIdMapping() {
$source = array(
'source_id_property' => 'source_value',
);
$row = new Row($source, ['source_id_property' => []]);
$id_map = $this->getIdMap();
$id_map->saveIdMapping($row, ['destination_id_property' => 2]);
$expected_result = [
[
'sourceid1' => 'source_value',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
'destid1' => 2,
] + $this->idMapDefaults(),
];
$this->queryResultTest($this->getIdMapContents(), $expected_result);
$source = [
'source_id_property' => 'source_value_1',
];
$row = new Row($source, ['source_id_property' => []]);
$id_map->saveIdMapping($row, ['destination_id_property' => 3]);
$expected_result[] = [
'sourceid1' => 'source_value_1',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
'destid1' => 3,
] + $this->idMapDefaults();
$this->queryResultTest($this->getIdMapContents(), $expected_result);
$id_map->saveIdMapping($row, ['destination_id_property' => 4]);
$expected_result[1]['destid1'] = 4;
$this->queryResultTest($this->getIdMapContents(), $expected_result);
}
/**
* Tests the SQL ID map set message method.
*/
public function testSetMessage() {
$message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
$id_map = $this->getIdMap();
$id_map->setMessage($message);
$this->assertAttributeEquals($message, 'message', $id_map);
}
/**
* Tests the clear messages method.
*/
public function testClearMessages() {
$message = 'Hello world.';
$expected_results = [0, 1, 2, 3];
$id_map = $this->getIdMap();
// Insert 4 message for later delete.
foreach ($expected_results as $key => $expected_result) {
$id_map->saveMessage(['source_id_property' => $key], $message);
}
// Truncate and check that 4 messages were deleted.
$this->assertEquals($id_map->messageCount(), 4);
$id_map->clearMessages();
$count = $id_map->messageCount();
$this->assertEquals($count, 0);
}
/**
* Tests the getRowsNeedingUpdate method for rows that need an update.
*/
public function testGetRowsNeedingUpdate() {
$id_map = $this->getIdMap();
$row_statuses = [
MigrateIdMapInterface::STATUS_IMPORTED,
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
MigrateIdMapInterface::STATUS_IGNORED,
MigrateIdMapInterface::STATUS_FAILED,
];
// Create a mapping row for each STATUS constant.
foreach ($row_statuses as $status) {
$source = ['source_id_property' => 'source_value_' . $status];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_' . $status];
$id_map->saveIdMapping($row, $destination, $status);
$expected_results[] = [
'sourceid1' => 'source_value_' . $status,
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
'destid1' => 'destination_value_' . $status,
'source_row_status' => $status,
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
'hash' => '',
];
// Assert zero rows need an update.
if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
$rows_needing_update = $id_map->getRowsNeedingUpdate(1);
$this->assertCount(0, $rows_needing_update);
}
}
// Assert that test values exist.
$this->queryResultTest($this->getIdMapContents(), $expected_results);
// Assert a single row needs an update.
$row_needing_update = $id_map->getRowsNeedingUpdate(1);
$this->assertCount(1, $row_needing_update);
// Assert the row matches its original source.
$source_id = $expected_results[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]['sourceid1'];
$test_row = $id_map->getRowBySource(['source_id_property' => $source_id]);
// $row_needing_update is an array of objects returned from the database,
// but $test_row is an array, so the cast is necessary.
$this->assertSame($test_row, (array) $row_needing_update[0]);
// Add additional row that needs an update.
$source = ['source_id_property' => 'source_value_multiple'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_multiple'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
// Assert multiple rows need an update.
$rows_needing_update = $id_map->getRowsNeedingUpdate(2);
$this->assertCount(2, $rows_needing_update);
}
/**
* Tests the SQL ID map message count method by counting and saving messages.
*/
public function testMessageCount() {
$message = 'Hello world.';
$expected_results = [0, 1, 2, 3];
$id_map = $this->getIdMap();
// Test count message multiple times starting from 0.
foreach ($expected_results as $key => $expected_result) {
$count = $id_map->messageCount();
$this->assertEquals($expected_result, $count);
$id_map->saveMessage(['source_id_property' => $key], $message);
}
}
/**
* Tests the SQL ID map save message method.
*/
public function testMessageSave() {
$message = 'Hello world.';
$original_values = [
1 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR],
2 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING],
3 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE],
4 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL],
];
$expected_results = [
'7ad742edb7e866caa78ced1e4455d2e9cbd8adb2074e7c323d21b4e67732e755' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR],
'2d3ec2b0c547e819346e6ae03f881fd9f5c978ff3cbe29dfb807d40735e53703' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING],
'12a042f72cad9a2a8c7715df0c7695d762975f0687d87f5d480725dae1432a6f' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE],
'd9d1fd27a2447ace48f47a2e9ff649673f67b446d9381a7963c949fc083f8791' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL],
];
$id_map = $this->getIdMap();
foreach ($original_values as $key => $original_value) {
$id_map->saveMessage(['source_id_property' => $key], $message, $original_value['level']);
}
foreach ($id_map->getMessageIterator() as $message_row) {
$key = $message_row->source_ids_hash;
$this->assertEquals($expected_results[$key]['message'], $message_row->message);
$this->assertEquals($expected_results[$key]['level'], $message_row->level);
}
// Insert with default level.
$message_default = 'Hello world default.';
$id_map->saveMessage(['source_id_property' => 5], $message_default);
$messages = $id_map->getMessageIterator(['source_id_property' => 5]);
$count = 0;
foreach ($messages as $key => $message_row) {
$count = 1;
$this->assertEquals($message_default, $message_row->message);
$this->assertEquals(MigrationInterface::MESSAGE_ERROR, $message_row->level);
}
$this->assertEquals($count, 1);
// Retrieve messages with a specific level.
$messages = $id_map->getMessageIterator([], MigrationInterface::MESSAGE_WARNING);
$count = 0;
foreach ($messages as $key => $message_row) {
$count = 1;
$this->assertEquals(MigrationInterface::MESSAGE_WARNING, $message_row->level);
}
$this->assertEquals($count, 1);
}
/**
* Tests the getRowBySource method.
*/
public function testGetRowBySource() {
$this->getDatabase([]);
$row = [
'sourceid1' => 'source_id_value_1',
'sourceid2' => 'source_id_value_2',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']),
'destid1' => 'destination_id_value_1',
] + $this->idMapDefaults();
$this->saveMap($row);
$row = [
'sourceid1' => 'source_id_value_3',
'sourceid2' => 'source_id_value_4',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4']),
'destid1' => 'destination_id_value_2',
] + $this->idMapDefaults();
$this->saveMap($row);
$source_id_values = ['source_id_property' => $row['sourceid1'], 'sourceid2' => $row['sourceid2']];
$id_map = $this->getIdMap();
$result_row = $id_map->getRowBySource($source_id_values);
$this->assertSame($row, $result_row);
$source_id_values = ['source_id_property' => 'missing_value_1', 'sourceid2' => 'missing_value_2'];
$result_row = $id_map->getRowBySource($source_id_values);
$this->assertFalse($result_row);
}
/**
* Data provider for testLookupDestinationIdMapping().
*
* Scenarios to test (for both hits and misses) are:
* - Single-value source ID to single-value destination ID.
* - Multi-value source ID to multi-value destination ID.
* - Single-value source ID to multi-value destination ID.
* - Multi-value source ID to single-value destination ID.
*
* @return array
* An array of data values.
*/
public function lookupDestinationIdMappingDataProvider() {
return [
[1, 1],
[2, 2],
[1, 2],
[2, 1],
];
}
/**
* Performs destination ID test on source and destination fields.
*
* @param int $num_source_fields
* Number of source fields to test.
* @param int $num_destination_fields
* Number of destination fields to test.
*
* @dataProvider lookupDestinationIdMappingDataProvider
*/
public function testLookupDestinationIdMapping($num_source_fields, $num_destination_fields) {
// Adjust the migration configuration according to the number of source and
// destination fields.
$this->sourceIds = [];
$this->destinationIds = [];
$source_id_values = [];
$nonexistent_id_values = [];
$row = $this->idMapDefaults();
for ($i = 1; $i <= $num_source_fields; $i++) {
$row["sourceid$i"] = "source_id_value_$i";
$source_id_values[] = "source_id_value_$i";
$nonexistent_id_values[] = "nonexistent_source_id_value_$i";
$this->sourceIds["source_id_property_$i"] = [];
}
$expected_result = [];
for ($i = 1; $i <= $num_destination_fields; $i++) {
$row["destid$i"] = "destination_id_value_$i";
$expected_result[] = "destination_id_value_$i";
$this->destinationIds["destination_id_property_$i"] = [];
}
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_id_values);
$this->saveMap($row);
$id_map = $this->getIdMap();
// Test for a valid hit.
$destination_id = $id_map->lookupDestinationId($source_id_values);
$this->assertSame($expected_result, $destination_id);
// Test for a miss.
$destination_id = $id_map->lookupDestinationId($nonexistent_id_values);
$this->assertSame(0, count($destination_id));
}
/**
* Setup a database with the given rows.
*
* @param array $source_keys
* The source keys for the ID map table.
* @param array $dest_keys
* The destination keys for the ID map table.
* @param array $rows
* An array of source and destination value arrays for the ID map table.
*
* @return \Drupal\Tests\migrate\Unit\TestSqlIdMap
* An ID map instance for testing.
*/
protected function setupRows($source_keys, $dest_keys, $rows) {
$this->database = $this->getDatabase([]);
$this->sourceIds = array_fill_keys($source_keys, []);
$this->destinationIds = array_fill_keys($dest_keys, []);
$db_keys = [];
foreach (array_keys($source_keys) as $i) {
$db_keys[] = 'sourceid' . ($i + 1);
}
foreach (array_keys($dest_keys) as $i) {
$db_keys[] = 'destid' . ($i + 1);
}
foreach ($rows as $row) {
$values = array_combine($db_keys, $row);
$source_values = array_slice($row, 0, count($source_keys));
$values['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_values);
$this->saveMap($values);
}
return $this->getIdMap();
}
/**
* Tests lookupDestinationIds().
*/
public function testLookupDestinationIds() {
// Simple map with one source and one destination ID.
$id_map = $this->setupRows(['nid'], ['nid'], [
[1, 101],
[2, 102],
[3, 103],
]);
// Lookup nothing, gives nothing.
$this->assertEquals([], $id_map->lookupDestinationIds([]));
// Lookup by complete non-associative list.
$this->assertEquals([[101]], $id_map->lookupDestinationIds([1]));
$this->assertEquals([[102]], $id_map->lookupDestinationIds([2]));
$this->assertEquals([], $id_map->lookupDestinationIds([99]));
// Lookup by complete associative list.
$this->assertEquals([[101]], $id_map->lookupDestinationIds(['nid' => 1]));
$this->assertEquals([[102]], $id_map->lookupDestinationIds(['nid' => 2]));
$this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99]));
// Map with multiple source and destination IDs.
$id_map = $this->setupRows(['nid', 'language'], ['nid', 'langcode'], [
[1, 'en', 101, 'en'],
[1, 'fr', 101, 'fr'],
[1, 'de', 101, 'de'],
[2, 'en', 102, 'en'],
]);
// Lookup nothing, gives nothing.
$this->assertEquals([], $id_map->lookupDestinationIds([]));
// Lookup by complete non-associative list.
$this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en']));
$this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds([1, 'fr']));
$this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2, 'en']));
$this->assertEquals([], $id_map->lookupDestinationIds([2, 'fr']));
$this->assertEquals([], $id_map->lookupDestinationIds([99, 'en']));
// Lookup by complete associative list.
$this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'en']));
$this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'fr']));
$this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'en']));
$this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'fr']));
$this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99, 'language' => 'en']));
// Lookup by partial non-associative list.
$this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds([1]));
$this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2]));
$this->assertEquals([], $id_map->lookupDestinationIds([99]));
// Lookup by partial associative list.
$this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds(['nid' => 1]));
$this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2]));
$this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99]));
// Out-of-order partial associative list.
$this->assertEquals([[101, 'en'], [102, 'en']], $id_map->lookupDestinationIds(['language' => 'en']));
$this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['language' => 'fr']));
$this->assertEquals([], $id_map->lookupDestinationIds(['language' => 'zh']));
// Error conditions.
try {
$id_map->lookupDestinationIds([1, 2, 3]);
$this->fail('Too many source IDs should throw');
}
catch (MigrateException $e) {
$this->assertEquals("Extra unknown items in source IDs", $e->getMessage());
}
try {
$id_map->lookupDestinationIds(['nid' => 1, 'aaa' => '2']);
$this->fail('Unknown source ID key should throw');
}
catch (MigrateException $e) {
$this->assertEquals("Extra unknown items in source IDs", $e->getMessage());
}
// Verify that we are looking up by source_id_hash when all source IDs are
// passed in.
$id_map->getDatabase()->update($id_map->mapTableName())
->condition('sourceid1', 1)
->condition('sourceid2', 'en')
->fields([TestSqlIdMap::SOURCE_IDS_HASH => uniqid()])
->execute();
$this->assertNotEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en']));
}
/**
* Tests the getRowByDestination method.
*/
public function testGetRowByDestination() {
$row = [
'sourceid1' => 'source_id_value_1',
'sourceid2' => 'source_id_value_2',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']),
'destid1' => 'destination_id_value_1',
] + $this->idMapDefaults();
$this->saveMap($row);
$row = [
'sourceid1' => 'source_id_value_3',
'sourceid2' => 'source_id_value_4',
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3']),
'destid1' => 'destination_id_value_2',
] + $this->idMapDefaults();
$this->saveMap($row);
$dest_id_values = ['destination_id_property' => $row['destid1']];
$id_map = $this->getIdMap();
$result_row = $id_map->getRowByDestination($dest_id_values);
$this->assertSame($row, $result_row);
// This value does not exist.
$dest_id_values = ['destination_id_property' => 'invalid_destination_id_property'];
$id_map = $this->getIdMap();
$result_row = $id_map->getRowByDestination($dest_id_values);
$this->assertFalse($result_row);
}
/**
* Data provider for testLookupSourceIDMapping().
*
* Scenarios to test (for both hits and misses) are:
* - Single-value destination ID to single-value source ID.
* - Multi-value destination ID to multi-value source ID.
* - Single-value destination ID to multi-value source ID.
* - Multi-value destination ID to single-value source ID.
*
* @return array
* An array of data values.
*/
public function lookupSourceIDMappingDataProvider() {
return [
[1, 1],
[2, 2],
[1, 2],
[2, 1],
];
}
/**
* Performs the source ID test on source and destination fields.
*
* @param int $num_source_fields
* Number of source fields to test.
* @param int $num_destination_fields
* Number of destination fields to test.
*
* @dataProvider lookupSourceIDMappingDataProvider
*/
public function testLookupSourceIDMapping($num_source_fields, $num_destination_fields) {
// Adjust the migration configuration according to the number of source and
// destination fields.
$this->sourceIds = [];
$this->destinationIds = [];
$row = $this->idMapDefaults();
$source_ids_values = [];
$expected_result = [];
for ($i = 1; $i <= $num_source_fields; $i++) {
$row["sourceid$i"] = "source_id_value_$i";
$source_ids_values = [$row["sourceid$i"]];
$expected_result["source_id_property_$i"] = "source_id_value_$i";
$this->sourceIds["source_id_property_$i"] = [];
}
$destination_id_values = [];
$nonexistent_id_values = [];
for ($i = 1; $i <= $num_destination_fields; $i++) {
$row["destid$i"] = "destination_id_value_$i";
$destination_id_values["destination_id_property_$i"] = "destination_id_value_$i";
$nonexistent_id_values["destination_id_property_$i"] = "nonexistent_destination_id_value_$i";
$this->destinationIds["destination_id_property_$i"] = [];
}
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_ids_values);
$this->saveMap($row);
$id_map = $this->getIdMap();
// Test for a valid hit.
$source_id = $id_map->lookupSourceID($destination_id_values);
$this->assertSame($expected_result, $source_id);
// Test for a miss.
$source_id = $id_map->lookupSourceID($nonexistent_id_values);
$this->assertSame(0, count($source_id));
}
/**
* Tests the imported count method.
*
* Scenarios to test for:
* - No imports.
* - One import.
* - Multiple imports.
*/
public function testImportedCount() {
$id_map = $this->getIdMap();
// Add a single failed row and assert zero imported rows.
$source = ['source_id_property' => 'source_value_failed'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_failed'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_FAILED);
$this->assertSame(0, (int) $id_map->importedCount());
// Add an imported row and assert single count.
$source = ['source_id_property' => 'source_value_imported'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_imported'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_IMPORTED);
$this->assertSame(1, (int) $id_map->importedCount());
// Add a row needing update and assert multiple imported rows.
$source = ['source_id_property' => 'source_value_update'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_update'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$this->assertSame(2, (int) $id_map->importedCount());
}
/**
* Tests the number of processed source rows.
*
* Scenarios to test for:
* - No processed rows.
* - One processed row.
* - Multiple processed rows.
*/
public function testProcessedCount() {
$id_map = $this->getIdMap();
// Assert zero rows have been processed before adding rows.
$this->assertSame(0, (int) $id_map->processedCount());
$row_statuses = [
MigrateIdMapInterface::STATUS_IMPORTED,
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
MigrateIdMapInterface::STATUS_IGNORED,
MigrateIdMapInterface::STATUS_FAILED,
];
// Create a mapping row for each STATUS constant.
foreach ($row_statuses as $status) {
$source = ['source_id_property' => 'source_value_' . $status];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_' . $status];
$id_map->saveIdMapping($row, $destination, $status);
if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
// Assert a single row has been processed.
$this->assertSame(1, (int) $id_map->processedCount());
}
}
// Assert multiple rows have been processed.
$this->assertSame(count($row_statuses), (int) $id_map->processedCount());
}
/**
* Data provider for testUpdateCount().
*
* Scenarios to test for:
* - No updates.
* - One update.
* - Multiple updates.
*
* @return array
* An array of data values.
*/
public function updateCountDataProvider() {
return [
[0],
[1],
[3],
];
}
/**
* Performs the update count test with a given number of update rows.
*
* @param int $num_update_rows
* The number of update rows to test.
*
* @dataProvider updateCountDataProvider
*/
public function testUpdateCount($num_update_rows) {
for ($i = 0; $i < 5; $i++) {
$row = $this->idMapDefaults();
$row['sourceid1'] = "source_id_value_$i";
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
$row['destid1'] = "destination_id_value_$i";
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
$this->saveMap($row);
}
for (; $i < 5 + $num_update_rows; $i++) {
$row = $this->idMapDefaults();
$row['sourceid1'] = "source_id_value_$i";
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
$row['destid1'] = "destination_id_value_$i";
$row['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
$this->saveMap($row);
}
$id_map = $this->getIdMap();
$this->assertSame($num_update_rows, (int) $id_map->updateCount());
}
/**
* Data provider for testErrorCount().
*
* Scenarios to test for:
* - No errors.
* - One error.
* - Multiple errors.
*
* @return array
* An array of data values.
*/
public function errorCountDataProvider() {
return [
[0],
[1],
[3],
];
}
/**
* Performs error count test with a given number of error rows.
*
* @param int $num_error_rows
* Number of error rows to test.
*
* @dataProvider errorCountDataProvider
*/
public function testErrorCount($num_error_rows) {
for ($i = 0; $i < 5; $i++) {
$row = $this->idMapDefaults();
$row['sourceid1'] = "source_id_value_$i";
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
$row['destid1'] = "destination_id_value_$i";
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
$this->saveMap($row);
}
for (; $i < 5 + $num_error_rows; $i++) {
$row = $this->idMapDefaults();
$row['sourceid1'] = "source_id_value_$i";
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
$row['destid1'] = "destination_id_value_$i";
$row['source_row_status'] = MigrateIdMapInterface::STATUS_FAILED;
$this->saveMap($row);
}
$this->assertSame($num_error_rows, (int) $this->getIdMap()->errorCount());
}
/**
* Tests setting a row source_row_status to STATUS_NEEDS_UPDATE.
*/
public function testSetUpdate() {
$id_map = $this->getIdMap();
$row_statuses = [
MigrateIdMapInterface::STATUS_IMPORTED,
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
MigrateIdMapInterface::STATUS_IGNORED,
MigrateIdMapInterface::STATUS_FAILED,
];
// Create a mapping row for each STATUS constant.
foreach ($row_statuses as $status) {
$source = ['source_id_property' => 'source_value_' . $status];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_' . $status];
$id_map->saveIdMapping($row, $destination, $status);
$expected_results[] = [
'sourceid1' => 'source_value_' . $status,
'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
'destid1' => 'destination_value_' . $status,
'source_row_status' => $status,
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
'hash' => '',
];
}
// Assert that test values exist.
$this->queryResultTest($this->getIdMapContents(), $expected_results);
// Mark each row as STATUS_NEEDS_UPDATE.
foreach ($row_statuses as $status) {
$id_map->setUpdate(['source_id_property' => 'source_value_' . $status]);
}
// Update expected results.
foreach ($expected_results as $key => $value) {
$expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
}
// Assert that updated expected values match.
$this->queryResultTest($this->getIdMapContents(), $expected_results);
// Assert an exception is thrown when source identifiers are not provided.
try {
$id_map->setUpdate([]);
$this->assertFalse(FALSE, 'MigrateException not thrown, when source identifiers were provided to update.');
}
catch (MigrateException $e) {
$this->assertTrue(TRUE, "MigrateException thrown, when source identifiers were not provided to update.");
}
}
/**
* Tests prepareUpdate().
*/
public function testPrepareUpdate() {
$id_map = $this->getIdMap();
$row_statuses = [
MigrateIdMapInterface::STATUS_IMPORTED,
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
MigrateIdMapInterface::STATUS_IGNORED,
MigrateIdMapInterface::STATUS_FAILED,
];
// Create a mapping row for each STATUS constant.
foreach ($row_statuses as $status) {
$source = ['source_id_property' => 'source_value_' . $status];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_' . $status];
$id_map->saveIdMapping($row, $destination, $status);
$expected_results[] = [
'sourceid1' => 'source_value_' . $status,
'destid1' => 'destination_value_' . $status,
'source_row_status' => $status,
'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
'hash' => '',
];
}
// Assert that test values exist.
$this->queryResultTest($this->getIdMapContents(), $expected_results);
// Mark all rows as STATUS_NEEDS_UPDATE.
$id_map->prepareUpdate();
// Update expected results.
foreach ($expected_results as $key => $value) {
$expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
}
// Assert that updated expected values match.
$this->queryResultTest($this->getIdMapContents(), $expected_results);
}
/**
* Tests the destroy method.
*
* Scenarios to test for:
* - No errors.
* - One error.
* - Multiple errors.
*/
public function testDestroy() {
$id_map = $this->getIdMap();
// Initialize the ID map.
$id_map->getDatabase();
$map_table_name = $id_map->mapTableName();
$message_table_name = $id_map->messageTableName();
$row = new Row(['source_id_property' => 'source_value'], ['source_id_property' => []]);
$id_map->saveIdMapping($row, ['destination_id_property' => 2]);
$id_map->saveMessage(['source_id_property' => 'source_value'], 'A message');
$this->assertTrue($this->database->schema()->tableExists($map_table_name),
"$map_table_name exists");
$this->assertTrue($this->database->schema()->tableExists($message_table_name),
"$message_table_name exists");
$id_map->destroy();
$this->assertFalse($this->database->schema()->tableExists($map_table_name),
"$map_table_name does not exist");
$this->assertFalse($this->database->schema()->tableExists($message_table_name),
"$message_table_name does not exist");
}
/**
* Tests the getQualifiedMapTable method with a prefixed database.
*/
public function testGetQualifiedMapTablePrefix() {
$connection_options = [
'database' => ':memory:',
'prefix' => 'prefix',
];
$pdo = Connection::open($connection_options);
$this->database = new Connection($pdo, $connection_options);
$qualified_map_table = $this->getIdMap()->getQualifiedMapTableName();
// The SQLite driver is a special flower. It will prefix tables with
// PREFIX.TABLE, instead of the standard PREFIXTABLE.
// @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
$this->assertEquals('prefix.migrate_map_sql_idmap_test', $qualified_map_table);
}
/**
* Tests all the iterator methods in one swing.
*
* The iterator methods are:
* - Sql::rewind()
* - Sql::next()
* - Sql::valid()
* - Sql::key()
* - Sql::current()
*/
public function testIterators() {
for ($i = 0; $i < 3; $i++) {
$row = $this->idMapDefaults();
$row['sourceid1'] = "source_id_value_$i";
$row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
$row['destid1'] = "destination_id_value_$i";
$row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
$expected_results[serialize(['sourceid1' => $row['sourceid1']])] = ['destid1' => $row['destid1']];
$this->saveMap($row);
}
$this->assertSame(iterator_to_array($this->getIdMap()), $expected_results);
}
/**
* Retrieves the contents of an ID map.
*
* @return array
* The contents of an ID map.
*/
private function getIdMapContents() {
$result = $this->database
->select('migrate_map_sql_idmap_test', 't')
->fields('t')
->execute();
// The return value needs to be countable, or it will fail certain
// assertions. iterator_to_array() will not suffice because it won't
// respect the PDO fetch mode, if specified.
$contents = [];
foreach ($result as $row) {
$contents[] = (array) $row;
}
return $contents;
}
}
@@ -0,0 +1,147 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\Database\Query\SelectInterface;
/**
* Base class for Migrate module source unit tests.
*/
abstract class MigrateSqlSourceTestCase extends MigrateTestCase {
/**
* The tested source plugin.
*
* @var \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase.
*/
protected $source;
/**
* The database contents.
*
* Database contents represents a mocked database. It should contain an
* associative array with the table name as key, and as many nested arrays as
* the number of mocked rows. Each of those faked rows must be another array
* with the column name as the key and the value as the cell.
*
* @var array
*/
protected $databaseContents = array();
/**
* The plugin class under test.
*
* The plugin system is not working during unit testing so the source plugin
* class needs to be manually specified.
*
* @var string
*/
const PLUGIN_CLASS = '';
/**
* The high water mark at the beginning of the import operation.
*
* Once the migration is run, we save a mark of the migrated sources, so the
* migration can run again and update only new sources or changed sources.
*
* @var string
*/
const ORIGINAL_HIGH_WATER = '';
/**
* Expected results after the source parsing.
*
* @var array
*/
protected $expectedResults = array();
/**
* Expected count of source rows.
*
* @var int
*/
protected $expectedCount = 0;
/**
* The source plugin instance under test.
*
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
*/
protected $plugin;
/**
* {@inheritdoc}
*/
protected function setUp() {
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$state = $this->getMock('Drupal\Core\State\StateInterface');
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$migration = $this->getMigration();
$migration->expects($this->any())
->method('getHighWater')
->will($this->returnValue(static::ORIGINAL_HIGH_WATER));
// Setup the plugin.
$plugin_class = static::PLUGIN_CLASS;
$plugin = new $plugin_class($this->migrationConfiguration['source'], $this->migrationConfiguration['source']['plugin'], array(), $migration, $state, $entity_manager);
// Do some reflection to set the database and moduleHandler.
$plugin_reflection = new \ReflectionClass($plugin);
$database_property = $plugin_reflection->getProperty('database');
$database_property->setAccessible(TRUE);
$module_handler_property = $plugin_reflection->getProperty('moduleHandler');
$module_handler_property->setAccessible(TRUE);
// Set the database and the module handler onto our plugin.
$database_property->setValue($plugin, $this->getDatabase($this->databaseContents + array('test_map' => array())));
$module_handler_property->setValue($plugin, $module_handler);
$plugin->setStringTranslation($this->getStringTranslationStub());
$migration->expects($this->any())
->method('getSourcePlugin')
->will($this->returnValue($plugin));
$this->source = $plugin;
$this->expectedCount = count($this->expectedResults);
}
/**
* Tests that the source returns the same rows as expected.
*/
public function testRetrieval() {
$this->assertInstanceOf(SelectInterface::class, $this->source->query());
$this->queryResultTest($this->source, $this->expectedResults);
}
/**
* Tests that the source returns the row count expected.
*/
public function testSourceCount() {
$count = $this->source->count();
$this->assertTrue(is_numeric($count));
$this->assertEquals($count, $this->expectedCount);
}
/**
* Tests the source defines a valid ID.
*/
public function testSourceId() {
$this->assertNotEmpty($this->source->getIds());
}
/**
* Gets the value on a row for a given key.
*
* @param \Drupal\migrate\Row $row
* The row identifier.
* @param string $key
* The key identifier.
*
* @return mixed
* The value on a row for a given key.
*/
protected function getValue($row, $key) {
return $row->getSourceProperty($key);
}
}
@@ -0,0 +1,213 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\Database\Driver\sqlite\Connection;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\UnitTestCase;
/**
* Provides setup and helper methods for Migrate module tests.
*/
abstract class MigrateTestCase extends UnitTestCase {
/**
* An array of migration configuration values.
*
* @var array
*/
protected $migrationConfiguration = [];
/**
* The migration ID map.
*
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $idMap;
/**
* Local store for mocking setStatus()/getStatus().
*
* @var \Drupal\migrate\Plugin\MigrationInterface::STATUS_*
*/
protected $migrationStatus = MigrationInterface::STATUS_IDLE;
/**
* Retrieves a mocked migration.
*
* @return \Drupal\migrate\Plugin\MigrationInterface|\PHPUnit_Framework_MockObject_MockObject
* The mocked migration.
*/
protected function getMigration() {
$this->migrationConfiguration += ['migrationClass' => 'Drupal\migrate\Plugin\Migration'];
$this->idMap = $this->getMock('Drupal\migrate\Plugin\MigrateIdMapInterface');
$this->idMap
->method('getQualifiedMapTableName')
->willReturn('test_map');
$migration = $this->getMockBuilder($this->migrationConfiguration['migrationClass'])
->disableOriginalConstructor()
->getMock();
$migration->method('checkRequirements')
->willReturn(TRUE);
$migration->method('getIdMap')
->willReturn($this->idMap);
// We need the state to be toggled throughout the test so we store the value
// on the test class and use a return callback.
$migration->expects($this->any())
->method('getStatus')
->willReturnCallback(function() {
return $this->migrationStatus;
});
$migration->expects($this->any())
->method('setStatus')
->willReturnCallback(function($status) {
$this->migrationStatus = $status;
});
$migration->method('getMigrationDependencies')
->willReturn([
'required' => [],
'optional' => [],
]);
$configuration = &$this->migrationConfiguration;
$migration->method('getHighWaterProperty')
->willReturnCallback(function () use ($configuration) {
return isset($configuration['highWaterProperty']) ? $configuration['highWaterProperty'] : '';
});
$migration->method('set')
->willReturnCallback(function ($argument, $value) use (&$configuration) {
$configuration[$argument] = $value;
});
$migration->method('id')
->willReturn($configuration['id']);
return $migration;
}
/**
* Gets an SQLite database connection object for use in tests.
*
* @param array $database_contents
* The database contents faked as an array. Each key is a table name, each
* value is a list of table rows, an associative array of field => value.
* @param array $connection_options
* (optional) Options for the database connection. Defaults to an empty
* array.
*
* @return \Drupal\Core\Database\Driver\sqlite\Connection
* The database connection.
*/
protected function getDatabase(array $database_contents, $connection_options = []) {
if (extension_loaded('pdo_sqlite')) {
$connection_options['database'] = ':memory:';
$pdo = Connection::open($connection_options);
$connection = new Connection($pdo, $connection_options);
}
else {
$this->markTestSkipped('The pdo_sqlite extension is not available.');
}
// Initialize the DIC with a fake module handler for alterable queries.
$container = new ContainerBuilder();
$container->set('module_handler', $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'));
\Drupal::setContainer($container);
// Create the tables and load them up with data, skipping empty ones.
foreach (array_filter($database_contents) as $table => $rows) {
$pilot_row = reset($rows);
$connection->schema()->createTable($table, $this->createSchemaFromRow($pilot_row));
$insert = $connection->insert($table)->fields(array_keys($pilot_row));
array_walk($rows, [$insert, 'values']);
$insert->execute();
}
return $connection;
}
/**
* Generates a table schema from a row.
*
* @param array $row
* The reference row on which to base the schema.
*
* @return array
* The Schema API-ready table schema.
*/
protected function createSchemaFromRow(array $row) {
// SQLite uses loose ("affinity") typing, so it is OK for every column to be
// a text field.
$fields = array_map(function() { return ['type' => 'text']; }, $row);
return ['fields' => $fields];
}
/**
* Tests a query.
*
* @param array|\Traversable $iter
* The countable. foreach-able actual results if a query is being run.
* @param array $expected_results
* An array of expected results.
*/
public function queryResultTest($iter, $expected_results) {
$this->assertSame(count($expected_results), count($iter), 'Number of results match');
$count = 0;
foreach ($iter as $data_row) {
$expected_row = $expected_results[$count];
$count++;
foreach ($expected_row as $key => $expected_value) {
$this->retrievalAssertHelper($expected_value, $this->getValue($data_row, $key), sprintf('Value matches for key "%s"', $key));
}
}
$this->assertSame(count($expected_results), $count);
}
/**
* Gets the value on a row for a given key.
*
* @param array $row
* The row information.
* @param string $key
* The key identifier.
*
* @return mixed
* The value on a row for a given key.
*/
protected function getValue($row, $key) {
return $row[$key];
}
/**
* Asserts tested values during test retrieval.
*
* @param mixed $expected_value
* The incoming expected value to test.
* @param mixed $actual_value
* The incoming value itself.
* @param string $message
* The tested result as a formatted string.
*/
protected function retrievalAssertHelper($expected_value, $actual_value, $message) {
if (is_array($expected_value)) {
// If the expected and actual values are empty, no need to array compare.
if (empty($expected_value && $actual_value)) {
return;
}
$this->assertArrayEquals($expected_value, $actual_value, $message);
}
else {
$this->assertSame((string) $expected_value, (string) $actual_value, $message);
}
}
}
@@ -0,0 +1,220 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Plugin\MigrationPluginManager;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\MigrationPluginManager
* @group migrate
*/
class MigrationPluginManagerTest extends UnitTestCase {
/**
* A plugin manager.
*
* @param \Drupal\migrate\Plugin\MigrationPluginManager $pluginManager
*/
protected $pluginManager;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Get a plugin manager for testing.
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$cache_backend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
$language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$this->pluginManager = new MigrationPluginManager($module_handler, $cache_backend, $language_manager);
}
/**
* Tests building dependencies for multiple migrations.
*
* @dataProvider dependencyProvider
*/
public function testDependencyBuilding($migrations_data, $result_ids) {
$migrations = [];
foreach ($migrations_data as $migration_id => $migration_data) {
$migrations[$migration_id] = new TestMigrationMock($migration_id, $migration_data['dependencies']);
}
$ordered_migrations = $this->pluginManager->buildDependencyMigration($migrations, []);
// Verify results.
$this->assertEquals($result_ids, array_keys($ordered_migrations));
foreach ($migrations_data as $migration_id => $migration_data) {
$migration = $migrations[$migration_id];
$requirements = $migration_data['result_requirements'];
if (empty($requirements)) {
$this->assertEquals([], $migration->set);
}
else {
$requirements = array_combine($requirements, $requirements);
$this->assertEquals(1, count($migration->set));
list($set_prop, $set_requirements) = reset($migration->set);
$this->assertEquals('requirements', $set_prop);
$this->assertEquals($requirements, $set_requirements);
}
}
}
/**
* Provide dependency data for testing.
*/
public function dependencyProvider() {
return [
// Just one migration, with no dependencies.
[
[
'm1' => [
'dependencies' => [],
'result_requirements' => [],
],
],
['m1'],
],
// Just one migration, with required dependencies.
[
[
'm1' => [
'dependencies' => [
'required' => ['required1', 'required2'],
],
'result_requirements' => ['required1', 'required2'],
],
],
['m1'],
],
// Just one migration, with optional dependencies.
[
[
'm1' => [
'dependencies' => [
'optional' => ['optional1'],
],
'result_requirements' => [],
],
],
['m1'],
],
// Multiple migrations.
[
[
'm1' => [
'dependencies' => [
'required' => ['required1', 'required2'],
],
'result_requirements' => ['required1', 'required2'],
],
'm2' => [
'dependencies' => [
'optional' => ['optional1'],
],
'result_requirements' => [],
],
],
['m1', 'm2'],
],
// Multiple migrations, reordered due to optional requirement.
[
[
'm1' => [
'dependencies' => [
'optional' => ['m2'],
],
'result_requirements' => [],
],
'm2' => [
'dependencies' => [
'optional' => ['optional1'],
],
'result_requirements' => [],
],
],
['m2', 'm1'],
],
// Ensure that optional requirements aren't turned into required ones,
// if the last migration has no optional deps.
[
[
'm1' => [
'dependencies' => [
'optional' => ['m2'],
],
'result_requirements' => [],
],
'm2' => [
'dependencies' => [],
'result_requirements' => [],
],
],
['m2', 'm1'],
],
];
}
}
/**
* A mock migration plugin.
*
* Why are we using a custom class here?
*
* 1. The function buildDependencyMigration() calls $migration->set(), which
* is not actually in MigrationInterface.
*
* 2. The function buildDependencyMigration() calls array_multisort on an
* array with mocks in it. PHPUnit mocks are really complex, and if PHP tries
* to compare them it will die with "Nesting level too deep".
*/
class TestMigrationMock extends Migration {
/**
* The values passed into set().
*
* @var array $set
*/
public $set = [];
/**
* TestMigrationMock constructor.
*/
public function __construct($id, $dependencies) {
// Intentionally ignore parent constructor.
$this->id = $id;
$this->dependencies = $dependencies;
}
/**
* {@inheritdoc}
*/
public function id() {
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getMigrationDependencies() {
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function set($prop, $value) {
$this->set[] = func_get_args();
}
}
@@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\MigrationStorageTest.
*/
namespace Drupal\Tests\migrate\Unit;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryFactoryInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\migrate\MigrationStorage;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\MigrationStorage
* @group migrate
*/
class MigrationStorageTest extends UnitTestCase {
/**
* The migration storage.
*
* @var \Drupal\Tests\migrate\Unit\TestMigrationStorage
*/
protected $storage;
/**
* The storage query.
*
* @var \Drupal\Core\Entity\Query\QueryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $query;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->query = $this->getMock(QueryInterface::class);
$this->query->method('condition')
->willReturnSelf();
$query_factory = $this->getMock(QueryFactoryInterface::class);
$query_factory->method('get')
->willReturn($this->query);
$this->storage = new TestMigrationStorage(
$this->getMock(EntityTypeInterface::class),
$this->getMock(ConfigFactoryInterface::class),
$this->getMock(UuidInterface::class),
$this->getMock(LanguageManagerInterface::class),
$query_factory
);
}
/**
* Tests getVariantIds() when variants exist.
*
* @covers ::getVariantIds
*/
public function testGetVariantIdsWithVariants() {
$this->query->method('execute')
->willReturn(['d6_node__page', 'd6_node__article']);
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_user']);
$this->assertSame(['d6_node__page', 'd6_node__article', 'd6_user'], $ids);
}
/**
* Tests getVariantIds() when no variants exist.
*
* @covers ::getVariantIds
*/
public function testGetVariantIdsNoVariants() {
$this->query->method('execute')
->willReturn([]);
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_user']);
$this->assertSame(['d6_user'], $ids);
}
/**
* Tests getVariantIds().
*
* This tests getVariantIds() when no variants exist and there are no static
* (non-variant) dependencies.
*
* @covers ::getVariantIds
*/
public function testGetVariantIdsNoVariantsOrStaticDependencies() {
$this->query->method('execute')
->willReturn([]);
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_node_revision:*']);
$this->assertSame([], $ids);
}
}
/**
* Test version of \Drupal\migrate\MigrationStorage.
*
* This class exposes protected methods for testing.
*/
class TestMigrationStorage extends MigrationStorage {
/**
* {@inheritdoc}
*/
public function getVariantIds(array $ids) {
return parent::getVariantIds($ids);
}
}
@@ -0,0 +1,181 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\MigrationTest.
*/
namespace Drupal\Tests\migrate\Unit;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrateDestinationInterface;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\Migration
*
* @group Migration
*/
class MigrationTest extends UnitTestCase {
/**
* Tests checking requirements for source plugins.
*
* @covers ::checkRequirements
*
* @expectedException \Drupal\migrate\Exception\RequirementsException
* @expectedExceptionMessage Missing source requirement
*/
public function testRequirementsForSourcePlugin() {
$migration = new TestMigration();
$source_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareSourceInterface');
$source_plugin->expects($this->once())
->method('checkRequirements')
->willThrowException(new RequirementsException('Missing source requirement', ['key' => 'value']));
$destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface');
$migration->setSourcePlugin($source_plugin);
$migration->setDestinationPlugin($destination_plugin);
$migration->checkRequirements();
}
/**
* Tests checking requirements for destination plugins.
*
* @covers ::checkRequirements
*
* @expectedException \Drupal\migrate\Exception\RequirementsException
* @expectedExceptionMessage Missing destination requirement
*/
public function testRequirementsForDestinationPlugin() {
$migration = new TestMigration();
$source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$destination_plugin = $this->getMock('Drupal\Tests\migrate\Unit\RequirementsAwareDestinationInterface');
$destination_plugin->expects($this->once())
->method('checkRequirements')
->willThrowException(new RequirementsException('Missing destination requirement', ['key' => 'value']));
$migration->setSourcePlugin($source_plugin);
$migration->setDestinationPlugin($destination_plugin);
$migration->checkRequirements();
}
/**
* Tests checking requirements for destination plugins.
*
* @covers ::checkRequirements
*
* @expectedException \Drupal\migrate\Exception\RequirementsException
* @expectedExceptionMessage Missing migrations test_a, test_c
*/
public function testRequirementsForMigrations() {
$migration = new TestMigration();
// Setup source and destination plugins without any requirements.
$source_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
$destination_plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
$migration->setSourcePlugin($source_plugin);
$migration->setDestinationPlugin($destination_plugin);
$plugin_manager = $this->getMock('Drupal\migrate\Plugin\MigrationPluginManagerInterface');
$migration->setMigrationPluginManager($plugin_manager);
// We setup the requirements that test_a doesn't exist and test_c is not
// completed yet.
$migration->setRequirements(['test_a', 'test_b', 'test_c', 'test_d']);
$migration_b = $this->getMock(MigrationInterface::class);
$migration_c = $this->getMock(MigrationInterface::class);
$migration_d = $this->getMock(MigrationInterface::class);
$migration_b->expects($this->once())
->method('allRowsProcessed')
->willReturn(TRUE);
$migration_c->expects($this->once())
->method('allRowsProcessed')
->willReturn(FALSE);
$migration_d->expects($this->once())
->method('allRowsProcessed')
->willReturn(TRUE);
$plugin_manager->expects($this->once())
->method('createInstances')
->with(['test_a', 'test_b', 'test_c', 'test_d'])
->willReturn(['test_b' => $migration_b, 'test_c' => $migration_c, 'test_d' => $migration_d]);
$migration->checkRequirements();
}
}
/**
* Defines the TestMigration class.
*/
class TestMigration extends Migration {
/**
* Constructs an instance of TestMigration object.
*/
public function __construct() {
}
/**
* Sets the requirements values.
*
* @param array $requirements
* The array of requirement values.
*/
public function setRequirements(array $requirements) {
$this->requirements = $requirements;
}
/**
* Sets the source Plugin.
*
* @param \Drupal\migrate\Plugin\MigrateSourceInterface $source_plugin
* The source Plugin.
*/
public function setSourcePlugin(MigrateSourceInterface $source_plugin) {
$this->sourcePlugin = $source_plugin;
}
/**
* Sets the destination Plugin.
*
* @param \Drupal\migrate\Plugin\MigrateDestinationInterface $destination_plugin
* The destination Plugin.
*/
public function setDestinationPlugin(MigrateDestinationInterface $destination_plugin) {
$this->destinationPlugin = $destination_plugin;
}
/**
* Sets the plugin manager service.
*
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $plugin_manager
* The plugin manager service.
*/
public function setMigrationPluginManager(MigrationPluginManagerInterface $plugin_manager) {
$this->migrationPluginManager = $plugin_manager;
}
}
/**
* Defines the RequirementsAwareSourceInterface.
*/
interface RequirementsAwareSourceInterface extends MigrateSourceInterface, RequirementsInterface {}
/**
* Defines the RequirementsAwareDestinationInterface.
*/
interface RequirementsAwareDestinationInterface extends MigrateDestinationInterface, RequirementsInterface {}
@@ -0,0 +1,147 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\Plugin\migrate\destination\EntityContentBaseTest
*/
namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* Tests base entity migration destination functionality.
*
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\EntityContentBase
* @group migrate
*/
class EntityContentBaseTest extends UnitTestCase {
/**
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->migration = $this->prophesize(MigrationInterface::class);
$this->storage = $this->prophesize(EntityStorageInterface::class);
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
}
/**
* Test basic entity save.
*
* @covers ::import
*/
public function testImport() {
$bundles = [];
$destination = new EntityTestDestination([], '', [],
$this->migration->reveal(),
$this->storage->reveal(),
$bundles,
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal());
$entity = $this->prophesize(ContentEntityInterface::class);
// Assert that save is called.
$entity->save()
->shouldBeCalledTimes(1);
// Set an id for the entity
$entity->id()
->willReturn(5);
$destination->setEntity($entity->reveal());
// Ensure the id is saved entity id is returned from import.
$this->assertEquals([5], $destination->import(new Row([], [])));
// Assert that import set the rollback action.
$this->assertEquals(MigrateIdMapInterface::ROLLBACK_DELETE, $destination->rollbackAction());
}
/**
* Test row skipping when we can't get an entity to save.
*
* @covers ::import
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage Unable to get entity
*/
public function testImportEntityLoadFailure() {
$bundles = [];
$destination = new EntityTestDestination([], '', [],
$this->migration->reveal(),
$this->storage->reveal(),
$bundles,
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal());
$destination->setEntity(FALSE);
$destination->import(new Row([], []));
}
/**
* Test that translation destination fails for untranslatable entities.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage This entity type does not support translation
*/
public function testUntranslatable() {
// An entity type without a language.
$entity_type = $this->prophesize(ContentEntityType::class);
$entity_type->getKey('langcode')->willReturn('');
$entity_type->getKey('id')->willReturn('id');
$this->storage->getEntityType()->willReturn($entity_type->reveal());
$destination = new EntityTestDestination(
[ 'translations' => TRUE ],
'',
[],
$this->migration->reveal(),
$this->storage->reveal(),
[],
$this->entityManager->reveal(),
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal()
);
$destination->getIds();
}
}
/**
* Stub class for testing EntityContentBase methods.
*
* We want to test things without testing the base class implementations.
*/
class EntityTestDestination extends EntityContentBase {
private $entity = NULL;
public function setEntity($entity) {
$this->entity = $entity;
}
protected function getEntity(Row $row, array $old_destination_id_values) {
return $this->entity;
}
}
@@ -0,0 +1,245 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Row
* @group migrate
*/
class RowTest extends UnitTestCase {
/**
* The source IDs.
*
* @var array
*/
protected $testSourceIds = array(
'nid' => 'Node ID',
);
/**
* The test values.
*
* @var array
*/
protected $testValues = array(
'nid' => 1,
'title' => 'node 1',
);
/**
* The test hash.
*
* @var string
*/
protected $testHash = '85795d4cde4a2425868b812cc88052ecd14fc912e7b9b4de45780f66750e8b1e';
/**
* The test hash after changing title value to 'new title'.
*
* @var string
*/
protected $testHashMod = '9476aab0b62b3f47342cc6530441432e5612dcba7ca84115bbab5cceaca1ecb3';
/**
* Tests object creation: empty.
*/
public function testRowWithoutData() {
$row = new Row(array(), array());
$this->assertSame(array(), $row->getSource(), 'Empty row');
}
/**
* Tests object creation: basic.
*/
public function testRowWithBasicData() {
$row = new Row($this->testValues, $this->testSourceIds);
$this->assertSame($this->testValues, $row->getSource(), 'Row with data, simple id.');
}
/**
* Tests object creation: multiple source IDs.
*/
public function testRowWithMultipleSourceIds() {
$multi_source_ids = $this->testSourceIds + array('vid' => 'Node revision');
$multi_source_ids_values = $this->testValues + array('vid' => 1);
$row = new Row($multi_source_ids_values, $multi_source_ids);
$this->assertSame($multi_source_ids_values, $row->getSource(), 'Row with data, multifield id.');
}
/**
* Tests object creation: invalid values.
*
* @expectedException \Exception
*/
public function testRowWithInvalidData() {
$invalid_values = array(
'title' => 'node X',
);
$row = new Row($invalid_values, $this->testSourceIds);
}
/**
* Tests source immutability after freeze.
*
* @expectedException \Exception
*/
public function testSourceFreeze() {
$row = new Row($this->testValues, $this->testSourceIds);
$row->rehash();
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash.');
$row->setSourceProperty('title', 'new title');
$row->rehash();
$this->assertSame($this->testHashMod, $row->getHash(), 'Hash changed correctly.');
$row->freezeSource();
$row->setSourceProperty('title', 'new title');
}
/**
* Tests setting on a frozen row.
*
* @expectedException \Exception
* @expectedExceptionMessage The source is frozen and can't be changed any more
*/
public function testSetFrozenRow() {
$row = new Row($this->testValues, $this->testSourceIds);
$row->freezeSource();
$row->setSourceProperty('title', 'new title');
}
/**
* Tests hashing.
*/
public function testHashing() {
$row = new Row($this->testValues, $this->testSourceIds);
$this->assertSame('', $row->getHash(), 'No hash at creation');
$row->rehash();
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash.');
$row->rehash();
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash even doing it twice.');
// Set the map to needs update.
$test_id_map = array(
'original_hash' => '',
'hash' => '',
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
);
$row->setIdMap($test_id_map);
$this->assertTrue($row->needsUpdate());
$row->rehash();
$this->assertSame($this->testHash, $row->getHash(), 'Correct hash even if id_mpa have changed.');
$row->setSourceProperty('title', 'new title');
$row->rehash();
$this->assertSame($this->testHashMod, $row->getHash(), 'Hash changed correctly.');
// Check hash calculation algorithm.
$hash = hash('sha256', serialize($row->getSource()));
$this->assertSame($hash, $row->getHash());
// Check length of generated hash used for mapping schema.
$this->assertSame(64, strlen($row->getHash()));
// Set the map to successfully imported.
$test_id_map = array(
'original_hash' => '',
'hash' => '',
'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED,
);
$row->setIdMap($test_id_map);
$this->assertFalse($row->needsUpdate());
// Set the same hash value and ensure it was not changed.
$random = $this->randomMachineName();
$test_id_map = array(
'original_hash' => $random,
'hash' => $random,
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
);
$row->setIdMap($test_id_map);
$this->assertFalse($row->changed());
// Set different has values to ensure it is marked as changed.
$test_id_map = array(
'original_hash' => $this->randomMachineName(),
'hash' => $this->randomMachineName(),
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
);
$row->setIdMap($test_id_map);
$this->assertTrue($row->changed());
}
/**
* Tests getting/setting the ID Map.
*
* @covers ::setIdMap
* @covers ::getIdMap
*/
public function testGetSetIdMap() {
$row = new Row($this->testValues, $this->testSourceIds);
$test_id_map = array(
'original_hash' => '',
'hash' => '',
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
);
$row->setIdMap($test_id_map);
$this->assertEquals($test_id_map, $row->getIdMap());
}
/**
* Tests the source ID.
*/
public function testSourceIdValues() {
$row = new Row($this->testValues, $this->testSourceIds);
$this->assertSame(array('nid' => $this->testValues['nid']), $row->getSourceIdValues());
}
/**
* Tests getting the source property.
*
* @covers ::getSourceProperty
*/
public function testGetSourceProperty() {
$row = new Row($this->testValues, $this->testSourceIds);
$this->assertSame($this->testValues['nid'], $row->getSourceProperty('nid'));
$this->assertSame($this->testValues['title'], $row->getSourceProperty('title'));
$this->assertNull($row->getSourceProperty('non_existing'));
}
/**
* Tests setting and getting the destination.
*/
public function testDestination() {
$row = new Row($this->testValues, $this->testSourceIds);
$this->assertEmpty($row->getDestination());
$this->assertFalse($row->hasDestinationProperty('nid'));
// Set a destination.
$row->setDestinationProperty('nid', 2);
$this->assertTrue($row->hasDestinationProperty('nid'));
$this->assertEquals(array('nid' => 2), $row->getDestination());
}
/**
* Tests setting/getting multiple destination IDs.
*/
public function testMultipleDestination() {
$row = new Row($this->testValues, $this->testSourceIds);
// Set some deep nested values.
$row->setDestinationProperty('image/alt', 'alt text');
$row->setDestinationProperty('image/fid', 3);
$this->assertTrue($row->hasDestinationProperty('image'));
$this->assertFalse($row->hasDestinationProperty('alt'));
$this->assertFalse($row->hasDestinationProperty('fid'));
$destination = $row->getDestination();
$this->assertEquals('alt text', $destination['image']['alt']);
$this->assertEquals(3, $destination['image']['fid']);
$this->assertEquals('alt text', $row->getDestinationProperty('image/alt'));
$this->assertEquals(3, $row->getDestinationProperty('image/fid'));
}
}
@@ -0,0 +1,236 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\SqlBaseTest.
*/
namespace Drupal\Tests\migrate\Unit;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Drupal\Tests\UnitTestCase;
/**
* Tests the SqlBase class.
*
* @group migrate
*/
class SqlBaseTest extends UnitTestCase {
/**
* Tests that the ID map is joinable.
*
* @param bool $expected_result
* The expected result.
* @param bool $id_map_is_sql
* TRUE if we want getIdMap() to return an instance of Sql.
* @param bool $with_id_map
* TRUE if we want the ID map to have a valid map of IDs.
* @param array $source_options
* (optional) An array of connection options for the source connection.
* Defaults to an empty array.
* @param array $idmap_options
* (optional) An array of connection options for the ID map connection.
* Defaults to an empty array.
*
* @dataProvider sqlBaseTestProvider
*/
public function testMapJoinable($expected_result, $id_map_is_sql, $with_id_map, $source_options = [], $idmap_options = []) {
// Setup a connection object.
$source_connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
->disableOriginalConstructor()
->getMock();
$source_connection->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
->method('getConnectionOptions')
->willReturn($source_options);
// Setup the ID map connection.
$idmap_connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
->disableOriginalConstructor()
->getMock();
$idmap_connection->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
->method('getConnectionOptions')
->willReturn($idmap_options);
// Setup the Sql object.
$sql = $this->getMockBuilder('Drupal\migrate\Plugin\migrate\id_map\Sql')
->disableOriginalConstructor()
->getMock();
$sql->expects($id_map_is_sql && $with_id_map ? $this->once() : $this->never())
->method('getDatabase')
->willReturn($idmap_connection);
// Setup a migration entity.
$migration = $this->getMock(MigrationInterface::class);
$migration->expects($with_id_map ? $this->once() : $this->never())
->method('getIdMap')
->willReturn($id_map_is_sql ? $sql : NULL);
// Create our SqlBase test class.
$sql_base = new TestSqlBase();
$sql_base->setMigration($migration);
$sql_base->setDatabase($source_connection);
// Configure the idMap to make the check in mapJoinable() pass.
if ($with_id_map) {
$sql_base->setIds([
'uid' => ['type' => 'integer', 'alias' => 'u'],
]);
}
$this->assertEquals($expected_result, $sql_base->mapJoinable());
}
/**
* The data provider for SqlBase.
*
* @return array
* An array of data per test run.
*/
public function sqlBaseTestProvider() {
return [
// Source ids are empty so mapJoinable() is false.
[
FALSE,
FALSE,
FALSE,
],
// Still false because getIdMap() is not a subclass of Sql.
[
FALSE,
FALSE,
TRUE,
],
// Test mapJoinable() returns false when source and id connection options
// differ.
[
FALSE,
TRUE,
TRUE,
['driver' => 'mysql', 'username' => 'different_from_map', 'password' => 'different_from_map'],
['driver' => 'mysql', 'username' => 'different_from_source', 'password' => 'different_from_source'],
],
// Returns true because source and id map connection options are the same.
[
TRUE,
TRUE,
TRUE,
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
],
// Returns false because driver is sqlite and the databases are not the
// same.
[
FALSE,
TRUE,
TRUE,
['driver' => 'sqlite', 'database' => '1.sqlite', 'username' => '', 'password' => ''],
['driver' => 'sqlite', 'database' => '2.sqlite', 'username' => '', 'password' => ''],
],
// Returns false because driver is not the same.
[
FALSE,
TRUE,
TRUE,
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
['driver' => 'mysql', 'username' => 'same_value', 'password' => 'same_value'],
],
];
}
}
/**
* Creates a base source class for SQL migration testing.
*/
class TestSqlBase extends SqlBase {
/**
* The database object.
*
* @var object
*/
protected $database;
/**
* The migration IDs.
*
* @var array
*/
protected $ids;
/**
* Override the constructor so we can create one easily.
*/
public function __construct() {}
/**
* Allows us to set the database during tests.
*
* @param mixed $database
* The database mock object.
*/
public function setDatabase($database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function getDatabase() {
return $this->database;
}
/**
* Allows us to set the migration during the test.
*
* @param mixed $migration
* The migration mock.
*/
public function setMigration($migration) {
$this->migration = $migration;
}
/**
* {@inheritdoc}
*/
public function mapJoinable() {
return parent::mapJoinable();
}
/**
* {@inheritdoc}
*/
public function getIds() {
return $this->ids;
}
/**
* Allows us to set the IDs during a test.
*
* @param array $ids
* An array of identifiers.
*/
public function setIds($ids) {
$this->ids = $ids;
}
/**
* {@inheritdoc}
*/
public function fields() {}
/**
* {@inheritdoc}
*/
public function query() {}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
}
@@ -0,0 +1,132 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\migrate\MigrateExecutable;
/**
* Tests MigrateExecutable.
*/
class TestMigrateExecutable extends MigrateExecutable {
/**
* The fake memory usage in bytes.
*
* @var int
*/
protected $memoryUsage;
/**
* The cleared memory usage.
*
* @var int
*/
protected $clearedMemoryUsage;
/**
* Sets the string translation service.
*
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*/
public function setStringTranslation(TranslationInterface $string_translation) {
$this->stringTranslation = $string_translation;
}
/**
* Allows access to set protected source property.
*
* @param \Drupal\migrate\Plugin\MigrateSourceInterface $source
* The value to set.
*/
public function setSource($source) {
$this->source = $source;
}
/**
* Allows access to protected sourceIdValues property.
*
* @param array $source_id_values
* The values to set.
*/
public function setSourceIdValues($source_id_values) {
$this->sourceIdValues = $source_id_values;
}
/**
* {@inheritdoc}
*/
public function handleException(\Exception $exception, $save = TRUE) {
$message = $exception->getMessage();
if ($save) {
$this->saveMessage($message);
}
$this->message->display($message);
}
/**
* Allows access to the protected memoryExceeded method.
*
* @return bool
* The memoryExceeded value.
*/
public function memoryExceeded() {
return parent::memoryExceeded();
}
/**
* {@inheritdoc}
*/
protected function attemptMemoryReclaim() {
return $this->clearedMemoryUsage;
}
/**
* {@inheritdoc}
*/
protected function getMemoryUsage() {
return $this->memoryUsage;
}
/**
* Sets the fake memory usage.
*
* @param int $memory_usage
* The fake memory usage value.
* @param int $cleared_memory_usage
* (optional) The fake cleared memory value. Defaults to NULL.
*/
public function setMemoryUsage($memory_usage, $cleared_memory_usage = NULL) {
$this->memoryUsage = $memory_usage;
$this->clearedMemoryUsage = $cleared_memory_usage;
}
/**
* Sets the memory limit.
*
* @param int $memory_limit
* The memory limit.
*/
public function setMemoryLimit($memory_limit) {
$this->memoryLimit = $memory_limit;
}
/**
* Sets the memory threshold.
*
* @param float $threshold
* The new threshold.
*/
public function setMemoryThreshold($threshold) {
$this->memoryThreshold = $threshold;
}
/**
* {@inheritdoc}
*/
protected function formatSize($size) {
return $size;
}
}
@@ -0,0 +1,79 @@
<?php
namespace Drupal\Tests\migrate\Unit;
use Drupal\Core\Database\Connection;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\migrate\id_map\Sql;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Defines a SQL ID map for use in tests.
*/
class TestSqlIdMap extends Sql implements \Iterator {
/**
* Constructs a TestSqlIdMap object.
*
* @param \Drupal\Core\Database\Connection $database
* The database.
* @param array $configuration
* The configuration.
* @param string $plugin_id
* The plugin ID for the migration process to do.
* @param mixed $plugin_definition
* The configuration for the plugin.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to do.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher service.
*/
public function __construct(Connection $database, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) {
$this->database = $database;
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $event_dispatcher);
}
/**
* {@inheritdoc}
*/
public function getDatabase() {
return parent::getDatabase();
}
/**
* Gets the field schema.
*
* @param array $id_definition
* An array defining the field, with a key 'type'.
*
* @return array
* A field schema depending on value of key 'type'. An empty array is
* returned if 'type' is not defined.
*
* @throws \Drupal\migrate\MigrateException
*/
protected function getFieldSchema(array $id_definition) {
if (!isset($id_definition['type'])) {
return array();
}
switch ($id_definition['type']) {
case 'integer':
return array(
'type' => 'int',
'not null' => TRUE,
);
case 'string':
return array(
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
);
default:
throw new MigrateException($id_definition['type'] . ' not supported');
}
}
}
@@ -0,0 +1,118 @@
<?php
namespace Drupal\Tests\migrate\Unit\destination;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\Config;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\Config
* @group migrate
*/
class ConfigTest extends UnitTestCase {
/**
* Test the import method.
*/
public function testImport() {
$source = array(
'test' => 'x',
);
$migration = $this->getMockBuilder('Drupal\migrate\Plugin\Migration')
->disableOriginalConstructor()
->getMock();
$config = $this->getMockBuilder('Drupal\Core\Config\Config')
->disableOriginalConstructor()
->getMock();
foreach ($source as $key => $val) {
$config->expects($this->once())
->method('set')
->with($this->equalTo($key), $this->equalTo($val))
->will($this->returnValue($config));
}
$config->expects($this->once())
->method('save');
$config->expects($this->once())
->method('getName')
->willReturn('d8_config');
$config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface');
$config_factory->expects($this->once())
->method('getEditable')
->with('d8_config')
->will($this->returnValue($config));
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue(FALSE));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
$language_manager = $this->getMockBuilder('Drupal\language\ConfigurableLanguageManagerInterface')
->disableOriginalConstructor()
->getMock();
$language_manager->expects($this->never())
->method('getLanguageConfigOverride')
->with('fr', 'd8_config')
->will($this->returnValue($config));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination_id = $destination->import($row);
$this->assertEquals($destination_id, ['d8_config']);
}
/**
* Test the import method.
*/
public function testLanguageImport() {
$source = array(
'langcode' => 'mi',
);
$migration = $this->getMockBuilder(MigrationInterface::class)
->disableOriginalConstructor()
->getMock();
$config = $this->getMockBuilder('Drupal\Core\Config\Config')
->disableOriginalConstructor()
->getMock();
foreach ($source as $key => $val) {
$config->expects($this->once())
->method('set')
->with($this->equalTo($key), $this->equalTo($val))
->will($this->returnValue($config));
}
$config->expects($this->once())
->method('save');
$config->expects($this->any())
->method('getName')
->willReturn('d8_config');
$config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface');
$config_factory->expects($this->once())
->method('getEditable')
->with('d8_config')
->will($this->returnValue($config));
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue($source));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
$row->expects($this->any())
->method('getDestinationProperty')
->will($this->returnValue($source['langcode']));
$language_manager = $this->getMockBuilder('Drupal\language\ConfigurableLanguageManagerInterface')
->disableOriginalConstructor()
->getMock();
$language_manager->expects($this->any())
->method('getLanguageConfigOverride')
->with('mi', 'd8_config')
->will($this->returnValue($config));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination_id = $destination->import($row);
$this->assertEquals($destination_id, ['d8_config']);
}
}
@@ -0,0 +1,228 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\destination\EntityRevisionTest.
*/
namespace Drupal\Tests\migrate\Unit\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityRevision as RealEntityRevision;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* Tests entity revision destination.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\EntityRevision
*/
class EntityRevisionTest extends UnitTestCase {
/**
* @var \Drupal\migrate\Plugin\MigrationInterface
*/
protected $migration;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
protected function setUp() {
parent::setUp();
// Setup mocks to be used when creating a revision destination.
$this->migration = $this->prophesize(MigrationInterface::class);
$this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface');
$this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface');
$this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface');
}
/**
* Test that passed old destination values are used by default.
*
* @covers ::getEntity
*/
public function testGetEntityDestinationValues() {
$destination = $this->getEntityRevisionDestination([]);
// Return a dummy because we don't care what gets called.
$entity = $this->prophesize('\Drupal\Core\Entity\EntityInterface')
->willImplement('\Drupal\Core\Entity\RevisionableInterface');
// Assert that the first ID from the destination values is used to load the
// entity.
$this->storage->loadRevision(12)
->shouldBeCalled()
->willReturn($entity->reveal());
$row = new Row([], []);
$this->assertEquals($entity->reveal(), $destination->getEntity($row, [12, 13]));
}
/**
* Test that revision updates update.
*
* @covers ::getEntity
*/
public function testGetEntityUpdateRevision() {
$destination = $this->getEntityRevisionDestination([]);
$entity = $this->prophesize('\Drupal\Core\Entity\EntityInterface')
->willImplement('\Drupal\Core\Entity\RevisionableInterface');
$entity_type = $this->prophesize('\Drupal\Core\Entity\EntityTypeInterface');
$entity_type->getKey('id')->willReturn('nid');
$entity_type->getKey('revision')->willReturn('vid');
$this->storage->getEntityType()->willReturn($entity_type->reveal());
// Assert we load the correct revision.
$this->storage->loadRevision(2)
->shouldBeCalled()
->willReturn($entity->reveal());
// Make sure its set as an update and not the default revision.
$entity->setNewRevision(FALSE)->shouldBeCalled();
$entity->isDefaultRevision(FALSE)->shouldBeCalled();
$row = new Row(['nid' => 1, 'vid' => 2], ['nid' => 1, 'vid' => 2]);
$row->setDestinationProperty('vid', 2);
$this->assertEquals($entity->reveal(), $destination->getEntity($row, []));
}
/**
* Test that new revisions are flagged to be written as new.
*
* @covers ::getEntity
*/
public function testGetEntityNewRevision() {
$destination = $this->getEntityRevisionDestination([]);
$entity = $this->prophesize('\Drupal\Core\Entity\EntityInterface')
->willImplement('\Drupal\Core\Entity\RevisionableInterface');
$entity_type = $this->prophesize('\Drupal\Core\Entity\EntityTypeInterface');
$entity_type->getKey('id')->willReturn('nid');
$entity_type->getKey('revision')->willReturn('vid');
$this->storage->getEntityType()->willReturn($entity_type->reveal());
// Enforce is new should be disabled.
$entity->enforceIsNew(FALSE)->shouldBeCalled();
// And toggle this as new revision but not the default revision.
$entity->setNewRevision(TRUE)->shouldBeCalled();
$entity->isDefaultRevision(FALSE)->shouldBeCalled();
// Assert we load the correct revision.
$this->storage->load(1)
->shouldBeCalled()
->willReturn($entity->reveal());
$row = new Row(['nid' => 1, 'vid' => 2], ['nid' => 1, 'vid' => 2]);
$row->setDestinationProperty('nid', 1);
$this->assertEquals($entity->reveal(), $destination->getEntity($row, []));
}
/**
* Test entity load failure.
*
* @covers ::getEntity
*/
public function testGetEntityLoadFailure() {
$destination = $this->getEntityRevisionDestination([]);
$entity_type = $this->prophesize('\Drupal\Core\Entity\EntityTypeInterface');
$entity_type->getKey('id')->willReturn('nid');
$entity_type->getKey('revision')->willReturn('vid');
$this->storage->getEntityType()->willReturn($entity_type->reveal());
// Return a failed load and make sure we don't fail and we return FALSE.
$this->storage->load(1)
->shouldBeCalled()
->willReturn(FALSE);
$row = new Row(['nid' => 1, 'vid' => 2], ['nid' => 1, 'vid' => 2]);
$row->setDestinationProperty('nid', 1);
$this->assertFalse($destination->getEntity($row, []));
}
/**
* Test entity revision save.
*
* @covers ::save
*/
public function testSave() {
$entity = $this->prophesize('\Drupal\Core\Entity\ContentEntityInterface');
$entity->save()
->shouldBeCalled();
$entity->getRevisionId()
->shouldBeCalled()
->willReturn(1234);
$destination = $this->getEntityRevisionDestination();
$this->assertEquals([1234], $destination->save($entity->reveal(), []));
}
/**
* Helper method to create an entity revision destination with mock services.
*
* @see \Drupal\Tests\migrate\Unit\Destination\EntityRevision
*
* @param $configuration
* Configuration for the destination.
* @param string $plugin_id
* The plugin id.
* @param array $plugin_definition
* The plugin definition.
*
* @return \Drupal\Tests\migrate\Unit\destination\EntityRevision
* Mocked destination.
*/
protected function getEntityRevisionDestination(array $configuration = [], $plugin_id = 'entity_revision', array $plugin_definition = []) {
return new EntityRevision($configuration, $plugin_id, $plugin_definition,
$this->migration->reveal(),
$this->storage->reveal(),
[],
$this->entityManager->reveal(),
$this->fieldTypeManager->reveal()
);
}
}
/**
* Mock that exposes from internal methods for testing.
*/
class EntityRevision extends RealEntityRevision {
/**
* Allow public access for testing.
*/
public function getEntity(Row $row, array $old_destination_id_values) {
return parent::getEntity($row, $old_destination_id_values);
}
/**
* Allow public access for testing.
*/
public function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
return parent::save($entity, $old_destination_id_values);
}
/**
* Don't test method from base class.
*
* This method is from the parent and we aren't concerned with the inner
* workings of its implementation which would trickle into mock assertions. An
* empty implementation avoids this.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {}
}
@@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\destination\PerComponentEntityDisplayTest.
*/
namespace Drupal\Tests\migrate\Unit\destination;
use Drupal\migrate\Plugin\migrate\destination\ComponentEntityDisplayBase;
use Drupal\migrate\Row;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
/**
* Tests the entity display destination plugin.
*
* @group migrate
*/
class PerComponentEntityDisplayTest extends MigrateTestCase {
/**
* Tests the entity display import method.
*/
public function testImport() {
$values = array(
'entity_type' => 'entity_type_test',
'bundle' => 'bundle_test',
'view_mode' => 'view_mode_test',
'field_name' => 'field_name_test',
'options' => array('test setting'),
);
$row = new Row(array(), array());
foreach ($values as $key => $value) {
$row->setDestinationProperty($key, $value);
}
$entity = $this->getMockBuilder('Drupal\Core\Entity\Entity\EntityViewDisplay')
->disableOriginalConstructor()
->getMock();
$entity->expects($this->once())
->method('setComponent')
->with('field_name_test', array('test setting'))
->will($this->returnSelf());
$entity->expects($this->once())
->method('save')
->with();
$plugin = new TestPerComponentEntityDisplay($entity);
$this->assertSame($plugin->import($row), array('entity_type_test', 'bundle_test', 'view_mode_test', 'field_name_test'));
$this->assertSame($plugin->getTestValues(), array('entity_type_test', 'bundle_test', 'view_mode_test'));
}
}
class TestPerComponentEntityDisplay extends ComponentEntityDisplayBase {
const MODE_NAME = 'view_mode';
protected $testValues;
public function __construct($entity) {
$this->entity = $entity;
}
protected function getEntity($entity_type, $bundle, $view_mode) {
$this->testValues = func_get_args();
return $this->entity;
}
public function getTestValues() {
return $this->testValues;
}
}
@@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\destination\PerComponentEntityFormDisplayTest.
*/
namespace Drupal\Tests\migrate\Unit\destination;
use Drupal\migrate\Plugin\migrate\destination\PerComponentEntityFormDisplay;
use Drupal\migrate\Row;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
/**
* Tests the entity display destination plugin.
*
* @group migrate
*/
class PerComponentEntityFormDisplayTest extends MigrateTestCase {
/**
* Tests the entity display import method.
*/
public function testImport() {
$values = array(
'entity_type' => 'entity_type_test',
'bundle' => 'bundle_test',
'form_mode' => 'form_mode_test',
'field_name' => 'field_name_test',
'options' => array('test setting'),
);
$row = new Row(array(), array());
foreach ($values as $key => $value) {
$row->setDestinationProperty($key, $value);
}
$entity = $this->getMockBuilder('Drupal\Core\Entity\Entity\EntityFormDisplay')
->disableOriginalConstructor()
->getMock();
$entity->expects($this->once())
->method('setComponent')
->with('field_name_test', array('test setting'))
->will($this->returnSelf());
$entity->expects($this->once())
->method('save')
->with();
$plugin = new TestPerComponentEntityFormDisplay($entity);
$this->assertSame($plugin->import($row), array('entity_type_test', 'bundle_test', 'form_mode_test', 'field_name_test'));
$this->assertSame($plugin->getTestValues(), array('entity_type_test', 'bundle_test', 'form_mode_test'));
}
}
class TestPerComponentEntityFormDisplay extends PerComponentEntityFormDisplay {
const MODE_NAME = 'form_mode';
protected $testValues;
public function __construct($entity) {
$this->entity = $entity;
}
protected function getEntity($entity_type, $bundle, $form_mode) {
$this->testValues = func_get_args();
return $this->entity;
}
public function getTestValues() {
return $this->testValues;
}
}
@@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\process\CallbackTest.
*/
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Callback;
/**
* Tests the callback process plugin.
*
* @group migrate
*/
class CallbackTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->plugin = new TestCallback();
parent::setUp();
}
/**
* Test callback with a function as callable.
*/
public function testCallbackWithFunction() {
$this->plugin->setCallable('strtolower');
$value = $this->plugin->transform('FooBar', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
}
/**
* Test callback with a class method as callable.
*/
public function testCallbackWithClassMethod() {
$this->plugin->setCallable(array('\Drupal\Component\Utility\Unicode', 'strtolower'));
$value = $this->plugin->transform('FooBar', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
}
}
class TestCallback extends Callback {
public function __construct() {
}
public function setCallable($callable) {
$this->configuration['callable'] = $callable;
}
}
@@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\process\ConcatTest.
*/
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Concat;
/**
* Tests the concat process plugin.
*
* @group migrate
*/
class ConcatTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->plugin = new TestConcat();
parent::setUp();
}
/**
* Test concat works without a delimiter.
*/
public function testConcatWithoutDelimiter() {
$value = $this->plugin->transform(array('foo', 'bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
}
/**
* Test concat fails properly on non-arrays.
*
* @expectedException \Drupal\migrate\MigrateException
*/
public function testConcatWithNonArray() {
$this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Test concat works without a delimiter.
*/
public function testConcatWithDelimiter() {
$this->plugin->setDelimiter('_');
$value = $this->plugin->transform(array('foo', 'bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foo_bar');
}
}
class TestConcat extends Concat {
public function __construct() {
}
/**
* Set the delimiter.
*
* @param string $delimiter
* The new delimiter.
*/
public function setDelimiter($delimiter) {
$this->configuration['delimiter'] = $delimiter;
}
}
@@ -0,0 +1,164 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\DedupeEntity;
use Drupal\Component\Utility\Unicode;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\DedupeEntity
* @group migrate
*/
class DedupeEntityTest extends MigrateProcessTestCase {
/**
* The mock entity query.
*
* @var \Drupal\Core\Entity\Query\QueryInterface
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQuery;
/**
* The mock entity query factory.
*
* @var \Drupal\Core\Entity\Query\QueryFactory|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityQueryFactory;
/**
* The migration configuration, initialized to set the ID to test.
*
* @var array
*/
protected $migrationConfiguration = array(
'id' => 'test',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityQuery = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryInterface')
->disableOriginalConstructor()
->getMock();
$this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
->disableOriginalConstructor()
->getMock();
$this->entityQueryFactory->expects($this->any())
->method('get')
->will($this->returnValue($this->entityQuery));
parent::setUp();
}
/**
* Tests entity based deduplication based on providerTestDedupe() values.
*
* @dataProvider providerTestDedupe
*/
public function testDedupe($count, $postfix = '', $start = NULL, $length = NULL) {
$configuration = array(
'entity_type' => 'test_entity_type',
'field' => 'test_field',
);
if ($postfix) {
$configuration['postfix'] = $postfix;
}
$configuration['start'] = isset($start) ? $start : NULL;
$configuration['length'] = isset($length) ? $length : NULL;
$plugin = new DedupeEntity($configuration, 'dedupe_entity', array(), $this->getMigration(), $this->entityQueryFactory);
$this->entityQueryExpects($count);
$value = $this->randomMachineName(32);
$actual = $plugin->transform($value, $this->migrateExecutable, $this->row, 'testproperty');
$expected = Unicode::substr($value, $start, $length);
$expected .= $count ? $postfix . $count : '';
$this->assertSame($expected, $actual);
}
/**
* Tests that invalid start position throws an exception.
*/
public function testDedupeEntityInvalidStart() {
$configuration = array(
'entity_type' => 'test_entity_type',
'field' => 'test_field',
'start' => 'foobar',
);
$plugin = new DedupeEntity($configuration, 'dedupe_entity', array(), $this->getMigration(), $this->entityQueryFactory);
$this->setExpectedException('Drupal\migrate\MigrateException', 'The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.');
$plugin->transform('test_start', $this->migrateExecutable, $this->row, 'testproperty');
}
/**
* Tests that invalid length option throws an exception.
*/
public function testDedupeEntityInvalidLength() {
$configuration = array(
'entity_type' => 'test_entity_type',
'field' => 'test_field',
'length' => 'foobar',
);
$plugin = new DedupeEntity($configuration, 'dedupe_entity', array(), $this->getMigration(), $this->entityQueryFactory);
$this->setExpectedException('Drupal\migrate\MigrateException', 'The character length configuration key should be an integer. Omit this key to capture the entire string.');
$plugin->transform('test_length', $this->migrateExecutable, $this->row, 'testproperty');
}
/**
* Data provider for testDedupe().
*/
public function providerTestDedupe() {
return array(
// Tests no duplication.
array(0),
// Tests no duplication and start position.
array(0, NULL, 10),
// Tests no duplication, start position, and length.
array(0, NULL, 5, 10),
// Tests no duplication and length.
array(0, NULL, NULL, 10),
// Tests duplication.
array(3),
// Tests duplication and start position.
array(3, NULL, 10),
// Tests duplication, start position, and length.
array(3, NULL, 5, 10),
// Tests duplication and length.
array(3, NULL, NULL, 10),
// Tests no duplication and postfix.
array(0, '_'),
// Tests no duplication, postfix, and start position.
array(0, '_', 5),
// Tests no duplication, postfix, start position, and length.
array(0, '_', 5, 10),
// Tests no duplication, postfix, and length.
array(0, '_', NULL, 10),
// Tests duplication and postfix.
array(2, '_'),
// Tests duplication, postfix, and start position.
array(2, '_', 5),
// Tests duplication, postfix, start position, and length.
array(2, '_', 5, 10),
// Tests duplication, postfix, and length.
array(2, '_', NULL, 10),
);
}
/**
* Helper function to add expectations to the mock entity query object.
*
* @param int $count
* The number of deduplications to be set up.
*/
protected function entityQueryExpects($count) {
$this->entityQuery->expects($this->exactly($count + 1))
->method('condition')
->will($this->returnValue($this->entityQuery));
$this->entityQuery->expects($this->exactly($count + 1))
->method('count')
->will($this->returnValue($this->entityQuery));
$this->entityQuery->expects($this->exactly($count + 1))
->method('execute')
->will($this->returnCallback(function () use (&$count) { return $count--;}));
}
}
@@ -0,0 +1,77 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Explode;
use Drupal\migrate\Plugin\migrate\process\Concat;
/**
* Tests the Explode process plugin.
*
* @group migrate
*/
class ExplodeTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$configuration = [
'delimiter' => ',',
];
$this->plugin = new Explode($configuration, 'map', []);
parent::setUp();
}
/**
* Test explode transform process works.
*/
public function testTransform() {
$value = $this->plugin->transform('foo,bar,tik', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['foo', 'bar', 'tik']);
}
/**
* Test explode transform process works with a limit.
*/
public function testTransformLimit() {
$plugin = new Explode(['delimiter' => '_', 'limit' => 2], 'map', []);
$value = $plugin->transform('foo_bar_tik', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['foo', 'bar_tik']);
}
/**
* Test if the explode process can be chained with a handles_multiple process.
*/
public function testChainedTransform() {
$exploded = $this->plugin->transform('foo,bar,tik', $this->migrateExecutable, $this->row, 'destinationproperty');
$concat = new Concat([], 'map', []);
$concatenated = $concat->transform($exploded, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($concatenated, 'foobartik');
}
/**
* Test explode fails properly on non-strings.
*
* @expectedException \Drupal\migrate\MigrateException
*
* @expectedExceptionMessage is not a string
*/
public function testExplodeWithNonString() {
$this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Test explode fails with empty delimiter.
*
* @expectedException \Drupal\migrate\MigrateException
*
* @expectedExceptionMessage delimiter is empty
*/
public function testExplodeWithEmptyDelimiter() {
$plugin = new Explode(['delimiter' => ''], 'map', []);
$plugin->transform('foo,bar', $this->migrateExecutable, $this->row, 'destinationproperty');
}
}
@@ -0,0 +1,59 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Extract;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\Extract
* @group migrate
*/
class ExtractTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$configuration['index'] = array('foo');
$this->plugin = new Extract($configuration, 'map', array());
parent::setUp();
}
/**
* Tests successful extraction.
*/
public function testExtract() {
$value = $this->plugin->transform(array('foo' => 'bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'bar');
}
/**
* Tests invalid input.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage Input should be an array.
*/
public function testExtractFromString() {
$this->plugin->transform('bar', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests unsuccessful extraction.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage Array index missing, extraction failed.
*/
public function testExtractFail() {
$this->plugin->transform(array('bar' => 'foo'), $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests unsuccessful extraction.
*/
public function testExtractFailDefault() {
$plugin = new Extract(['index' => ['foo'], 'default' => 'test'], 'map', []);
$value = $plugin->transform(['bar' => 'foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'test', '');
}
}
@@ -0,0 +1,23 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Flatten;
/**
* Tests the flatten plugin.
*
* @group migrate
*/
class FlattenTest extends MigrateProcessTestCase {
/**
* Test that various array flatten operations work properly.
*/
public function testFlatten() {
$plugin = new Flatten(array(), 'flatten', array());
$flattened = $plugin->transform(array(1, 2, array(3, 4, array(5)), array(), array(7, 8)), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($flattened, array(1, 2, 3, 4, 5, 7, 8));
}
}
@@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate\Unit\process\GetTest.
*/
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\TestGet;
/**
* Tests the get process plugin.
*
* @group migrate
*/
class GetTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->plugin = new TestGet();
parent::setUp();
}
/**
* Tests the Get plugin when source is a string.
*/
public function testTransformSourceString() {
$this->row->expects($this->once())
->method('getSourceProperty')
->with('test')
->will($this->returnValue('source_value'));
$this->plugin->setSource('test');
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'source_value');
}
/**
* Tests the Get plugin when source is an array.
*/
public function testTransformSourceArray() {
$map = array(
'test1' => 'source_value1',
'test2' => 'source_value2',
);
$this->plugin->setSource(array('test1', 'test2'));
$this->row->expects($this->exactly(2))
->method('getSourceProperty')
->will($this->returnCallback(function ($argument) use ($map) { return $map[$argument]; } ));
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, array('source_value1', 'source_value2'));
}
/**
* Tests the Get plugin when source is a string pointing to destination.
*/
public function testTransformSourceStringAt() {
$this->row->expects($this->once())
->method('getSourceProperty')
->with('@test')
->will($this->returnValue('source_value'));
$this->plugin->setSource('@@test');
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'source_value');
}
/**
* Tests the Get plugin when source is an array pointing to destination.
*/
public function testTransformSourceArrayAt() {
$map = array(
'test1' => 'source_value1',
'@test2' => 'source_value2',
'@test3' => 'source_value3',
'test4' => 'source_value4',
);
$this->plugin->setSource(array('test1', '@@test2', '@@test3', 'test4'));
$this->row->expects($this->exactly(4))
->method('getSourceProperty')
->will($this->returnCallback(function ($argument) use ($map) { return $map[$argument]; } ));
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, array('source_value1', 'source_value2', 'source_value3', 'source_value4'));
}
/**
* Tests the Get plugin when source has integer values.
*/
public function testIntegerValues() {
$this->row->expects($this->exactly(2))
->method('getSourceProperty')
->willReturnOnConsecutiveCalls('val1', 'val2');
$this->plugin->setSource([0 => 0, 1 => 'test']);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([0 => 'val1', 1 => 'val2'], $return);
$this->plugin->setSource([FALSE]);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([NULL], $return);
$this->plugin->setSource([NULL]);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([NULL], $return);
}
}
namespace Drupal\migrate\Plugin\migrate\process;
class TestGet extends Get {
public function __construct() {
}
public function setSource($source) {
$this->configuration['source'] = $source;
}
}
@@ -0,0 +1,82 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\migrate\process\Get;
use Drupal\migrate\Plugin\migrate\process\Iterator;
use Drupal\migrate\Row;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
/**
* Tests the iterator process plugin.
*
* @group migrate
*/
class IteratorTest extends MigrateTestCase {
/**
* The iterator plugin being tested.
*
* @var \Drupal\migrate\Plugin\migrate\process\TestIterator
*/
protected $plugin;
/**
* @var array
*/
protected $migrationConfiguration = array(
'id' => 'test',
);
/**
* Tests the iterator process plugin.
*/
public function testIterator() {
$migration = $this->getMigration();
// Set up the properties for the iterator.
$configuration = array(
'process' => array(
'foo' => 'source_foo',
'id' => 'source_id',
),
'key' => '@id',
);
$plugin = new Iterator($configuration, 'iterator', array());
// Manually create the plugins. Migration::getProcessPlugins does this
// normally but the plugin system is not available.
foreach ($configuration['process'] as $destination => $source) {
$iterator_plugins[$destination][] = new Get(array('source' => $source), 'get', array());
}
$migration->expects($this->at(1))
->method('getProcessPlugins')
->will($this->returnValue($iterator_plugins));
// Set up the key plugins.
$key_plugin['key'][] = new Get(array('source' => '@id'), 'get', array());
$migration->expects($this->at(2))
->method('getProcessPlugins')
->will($this->returnValue($key_plugin));
$event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$migrate_executable = new MigrateExecutable($migration, $this->getMock('Drupal\migrate\MigrateMessageInterface'), $event_dispatcher);
// The current value of the pipeline.
$current_value = array(
array(
'source_foo' => 'test',
'source_id' => 42,
),
);
// This is not used but the interface requires it, so create an empty row.
$row = new Row(array(), array());
// After transformation, check to make sure that source_foo and source_id's
// values ended up in the proper destinations, and that the value of the
// key (@id) is the same as the destination ID (42).
$new_value = $plugin->transform($current_value, $migrate_executable, $row, 'test');
$this->assertSame(count($new_value), 1);
$this->assertSame(count($new_value[42]), 2);
$this->assertSame($new_value[42]['foo'], 'test');
$this->assertSame($new_value[42]['id'], 42);
}
}
@@ -0,0 +1,61 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\MachineName;
/**
* Tests the machine name process plugin.
*
* @group migrate
*/
class MachineNameTest extends MigrateProcessTestCase {
/**
* The mock transliteration.
*
* @var \Drupal\Component\Transliteration\TransliterationInterface
*/
protected $transliteration;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->transliteration = $this->getMockBuilder('Drupal\Component\Transliteration\TransliterationInterface')
->disableOriginalConstructor()
->getMock();
$this->row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$this->migrateExecutable = $this->getMockBuilder('Drupal\migrate\MigrateExecutable')
->disableOriginalConstructor()
->getMock();
parent::setUp();
}
/**
* Tests machine name transformation of non-alphanumeric characters.
*/
public function testMachineNames() {
// Tests the following transformations:
// - non-alphanumeric character (including spaces) -> underscore,
// - Uppercase -> lowercase,
// - Multiple consecutive underscore -> single underscore.
$human_name_ascii = 'foo2, the.bar;2*&the%baz!YEE____HaW ';
$human_name = $human_name_ascii . 'áéő';
$expected_result = 'foo2_the_bar_2_the_baz_yee_haw_aeo';
// Test for calling transliterate on mock object.
$this->transliteration
->expects($this->once())
->method('transliterate')
->with($human_name)
->will($this->returnValue($human_name_ascii . 'aeo'));
$plugin = new MachineName(array(), 'machine_name', array(), $this->transliteration);
$value = $plugin->transform($human_name, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertEquals($expected_result, $value);
}
}
@@ -0,0 +1,38 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
abstract class MigrateProcessTestCase extends MigrateTestCase {
/**
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
*/
protected $plugin;
/**
* @var \Drupal\migrate\Row
*/
protected $row;
/**
* @var \Drupal\migrate\MigrateExecutable|\PHPUnit_Framework_MockObject_MockObject
*/
protected $migrateExecutable;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$this->migrateExecutable = $this->getMockBuilder('Drupal\migrate\MigrateExecutable')
->disableOriginalConstructor()
->getMock();
parent::setUp();
}
}
@@ -0,0 +1,137 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\process\Migration;
use Drupal\migrate\Plugin\MigrateDestinationInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\Migration
* @group migrate
*/
class MigrationTest extends MigrateProcessTestCase {
/**
* @covers ::transform
*/
public function testTransformWithStubSkipping() {
$migration_plugin = $this->prophesize(MigrationInterface::class);
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
$destination_id_map = $this->prophesize(MigrateIdMapInterface::class);
$destination_migration = $this->prophesize(MigrationInterface::class);
$destination_migration->getIdMap()->willReturn($destination_id_map->reveal());
$destination_id_map->lookupDestinationId([1])->willReturn(NULL);
// Ensure the migration plugin manager returns our migration.
$migration_plugin_manager->createInstances(Argument::exact(['destination_migration']))
->willReturn(['destination_migration' => $destination_migration->reveal()]);
$configuration = [
'no_stub' => TRUE,
'migration' => 'destination_migration',
];
$migration_plugin->id()->willReturn('actual_migration');
$destination_migration->getDestinationPlugin(TRUE)->shouldNotBeCalled();
$migration = new Migration($configuration, '', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$result = $migration->transform(1, $this->migrateExecutable, $this->row, '');
$this->assertNull($result);
}
/**
* @covers ::transform
*/
public function testTransformWithStubbing() {
$migration_plugin = $this->prophesize(MigrationInterface::class);
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
$destination_id_map = $this->prophesize(MigrateIdMapInterface::class);
$destination_migration = $this->prophesize('Drupal\migrate\Plugin\Migration');
$destination_migration->getIdMap()->willReturn($destination_id_map->reveal());
$migration_plugin_manager->createInstances(['destination_migration'])
->willReturn(['destination_migration' => $destination_migration->reveal()]);
$destination_id_map->lookupDestinationId([1])->willReturn(NULL);
$destination_id_map->saveIdMapping(Argument::any(), Argument::any(), MigrateIdMapInterface::STATUS_NEEDS_UPDATE)->willReturn(NULL);
$configuration = [
'no_stub' => FALSE,
'migration' => 'destination_migration',
];
$migration_plugin->id()->willReturn('actual_migration');
$destination_migration->id()->willReturn('destination_migration');
$destination_migration->getDestinationPlugin(TRUE)->shouldBeCalled();
$destination_migration->getProcess()->willReturn([]);
$destination_migration->getSourceConfiguration()->willReturn([]);
$source_plugin = $this->prophesize(MigrateSourceInterface::class);
$source_plugin->getIds()->willReturn(['nid']);
$destination_migration->getSourcePlugin()->willReturn($source_plugin->reveal());
$destination_plugin = $this->prophesize(MigrateDestinationInterface::class);
$destination_plugin->import(Argument::any())->willReturn([2]);
$destination_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal());
$migration = new Migration($configuration, '', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$result = $migration->transform(1, $this->migrateExecutable, $this->row, '');
$this->assertEquals(2, $result);
}
/**
* Tests that processing is skipped when the input value is empty.
*
* @expectedException \Drupal\migrate\MigrateSkipProcessException
*/
public function testSkipOnEmpty() {
$migration_plugin = $this->prophesize(MigrationInterface::class);
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
$configuration = [
'migration' => 'foobaz',
];
$migration_plugin->id()->willReturn(uniqid());
$migration = new Migration($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$migration->transform(0, $this->migrateExecutable, $this->row, 'foo');
}
/**
* Tests a successful lookup.
*/
public function testSuccessfulLookup() {
$migration_plugin = $this->prophesize(MigrationInterface::class);
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
$configuration = [
'migration' => 'foobaz',
];
$migration_plugin->id()->willReturn(uniqid());
$id_map = $this->prophesize(MigrateIdMapInterface::class);
$id_map->lookupDestinationId([1])->willReturn([3]);
$migration_plugin->getIdMap()->willReturn($id_map->reveal());
$migration_plugin_manager->createInstances(['foobaz'])
->willReturn(['foobaz' => $migration_plugin->reveal()]);
$migrationStorage = $this->prophesize(EntityStorageInterface::class);
$migrationStorage
->loadMultiple(['foobaz'])
->willReturn([$migration_plugin->reveal()]);
$migration = new Migration($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$this->assertSame(3, $migration->transform(1, $this->migrateExecutable, $this->row, 'foo'));
}
}
@@ -0,0 +1,54 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\SkipOnEmpty;
/**
* Tests the skip on empty process plugin.
*
* @group migrate
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty
*/
class SkipOnEmptyTest extends MigrateProcessTestCase {
/**
* @covers ::process
* @expectedException \Drupal\migrate\MigrateSkipProcessException
*/
public function testProcessSkipsOnEmpty() {
$configuration['method'] = 'process';
(new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform('', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::process
*/
public function testProcessBypassesOnNonEmpty() {
$configuration['method'] = 'process';
$value = (new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform(' ', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ' ');
}
/**
* @covers ::row
* @expectedException \Drupal\migrate\MigrateSkipRowException
*/
public function testRowSkipsOnEmpty() {
$configuration['method'] = 'row';
(new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform('', $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* @covers ::row
*/
public function testRowBypassesOnNonEmpty() {
$configuration['method'] = 'row';
$value = (new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform(' ', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ' ');
}
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
/**
* Tests the static map process plugin.
*
* @group migrate
*/
class StaticMapTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
$configuration['map']['foo']['bar'] = 'baz';
$this->plugin = new StaticMap($configuration, 'map', array());
parent::setUp();
}
/**
* Tests map when the source is a string.
*/
public function testMapWithSourceString() {
$value = $this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, array('bar' => 'baz'));
}
/**
* Tests map when the source is a list.
*/
public function testMapWithSourceList() {
$value = $this->plugin->transform(array('foo', 'bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'baz');
}
/**
* Tests when the source is empty.
*
* @expectedException \Drupal\migrate\MigrateException
*/
public function testMapwithEmptySource() {
$this->plugin->transform(array(), $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests when the source is invalid.
*
* @expectedException \Drupal\migrate\MigrateSkipRowException
*/
public function testMapwithInvalidSource() {
$this->plugin->transform(array('bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests when the source is invalid but there's a default.
*/
public function testMapWithInvalidSourceWithADefaultValue() {
$configuration['map']['foo']['bar'] = 'baz';
$configuration['default_value'] = 'test';
$this->plugin = new StaticMap($configuration, 'map', array());
$value = $this->plugin->transform(array('bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'test');
}
/**
* Tests when the source is invalid but there's a default value of NULL.
*/
public function testMapWithInvalidSourceWithANullDefaultValue() {
$configuration['map']['foo']['bar'] = 'baz';
$configuration['default_value'] = NULL;
$this->plugin = new StaticMap($configuration, 'map', []);
$value = $this->plugin->transform(array('bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertNull($value);
}
/**
* Tests when the source is invalid and bypass is enabled.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage Setting both default_value and bypass is invalid.
*/
public function testMapWithInvalidSourceAndBypass() {
$configuration['map']['foo']['bar'] = 'baz';
$configuration['default_value'] = 'test';
$configuration['bypass'] = TRUE;
$this->plugin = new StaticMap($configuration, 'map', array());
$this->plugin->transform(array('bar'), $this->migrateExecutable, $this->row, 'destinationproperty');
}
}
@@ -0,0 +1,92 @@
<?php
namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\Substr;
/**
* Tests the substr plugin.
*
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\Substr
*
* @group migrate
*/
class SubstrTest extends MigrateProcessTestCase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
}
/**
* Tests Substr plugin based on providerTestSubstr() values.
*
* @dataProvider providerTestSubstr
*/
public function testSubstr($start = NULL, $length = NULL, $expected = NULL) {
$configuration['start'] = $start;
$configuration['length'] = $length;
$this->plugin = new Substr($configuration, 'map', []);
$value = $this->plugin->transform('Captain Janeway', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($expected, $value);
}
/**
* Data provider for testSubstr().
*/
public function providerTestSubstr() {
return [
// Tests with valid start and length values.
[0, 7, 'Captain'],
// Tests with valid start > 0 and valid length.
[6, 3, 'n J'],
// Tests with valid start < 0 and valid length.
[-7, 4, 'Jane'],
// Tests without start value and valid length value.
[NULL, 7, 'Captain'],
// Tests with valid start value and no length value.
[1, NULL, 'aptain Janeway'],
// Tests without both start and length values.
[NULL, NULL, 'Captain Janeway'],
];
}
/**
* Tests invalid input type.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage The input value must be a string.
*/
public function testSubstrFail() {
$configuration = [];
$this->plugin = new Substr($configuration, 'map', []);
$this->plugin->transform(['Captain Janeway'], $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests that the start parameter is an integer.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage The start position configuration value should be an integer. Omit this key to capture from the beginning of the string.
*/
public function testStartIsString() {
$configuration['start'] = '2';
$this->plugin = new Substr($configuration, 'map', []);
$this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
}
/**
* Tests that the length parameter is an integer.
*
* @expectedException \Drupal\migrate\MigrateException
* @expectedExceptionMessage The character length configuration value should be an integer. Omit this key to capture from the start position to the end of the string.
*/
public function testLengthIsString() {
$configuration['length'] = '1';
$this->plugin = new Substr($configuration, 'map', []);
$this->plugin->transform(['foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
}
}