3 namespace Drupal\Tests\migrate\Unit;
5 use Drupal\Core\Database\Driver\sqlite\Connection;
6 use Drupal\migrate\Plugin\MigrationInterface;
7 use Drupal\migrate\MigrateException;
8 use Drupal\migrate\Plugin\MigrateIdMapInterface;
9 use Drupal\migrate\Row;
12 * Tests the SQL ID map plugin.
16 class MigrateSqlIdMapTest extends MigrateTestCase {
19 * The migration configuration, initialized to set the ID and destination IDs.
23 protected $migrationConfiguration = [
24 'id' => 'sql_idmap_test',
32 protected $sourceIds = [
33 'source_id_property' => [
39 * The destination IDs.
43 protected $destinationIds = [
44 'destination_id_property' => [
50 * The database connection.
52 * @var \Drupal\Core\Database\Connection
59 protected function setUp() {
60 $this->database = $this->getDatabase([]);
64 * Saves a single ID mapping row in the database.
69 protected function saveMap(array $map) {
70 $table = 'migrate_map_sql_idmap_test';
72 $schema = $this->database->schema();
73 // If the table already exists, add any columns which are in the map array,
74 // but don't yet exist in the table. Yay, flexibility!
75 if ($schema->tableExists($table)) {
76 foreach (array_keys($map) as $field) {
77 if (!$schema->fieldExists($table, $field)) {
78 $schema->addField($table, $field, ['type' => 'text']);
83 $schema->createTable($table, $this->createSchemaFromRow($map));
86 $this->database->insert($table)->fields($map)->execute();
90 * Creates a test SQL ID map plugin.
92 * @return \Drupal\Tests\migrate\Unit\TestSqlIdMap
93 * A SQL ID map plugin test instance.
95 protected function getIdMap() {
96 $migration = $this->getMigration();
98 $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateSourceInterface');
101 ->willReturn($this->sourceIds);
103 ->method('getSourcePlugin')
104 ->willReturn($plugin);
106 $plugin = $this->getMock('Drupal\migrate\Plugin\MigrateDestinationInterface');
109 ->willReturn($this->destinationIds);
111 ->method('getDestinationPlugin')
112 ->willReturn($plugin);
113 $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
115 $id_map = new TestSqlIdMap($this->database, [], 'sql', [], $migration, $event_dispatcher);
118 ->willReturn($id_map);
124 * Sets defaults for SQL ID map plugin tests.
127 * An associative array with the following keys:
128 * - source_row_status
132 protected function idMapDefaults() {
134 'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED,
135 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
138 // By default, the PDO SQLite driver strongly prefers to return strings
139 // from SELECT queries. Even for columns that don't store strings. Even
140 // if the connection's STRINGIFY_FETCHES attribute is FALSE. This can cause
141 // assertSame() calls to fail, since 0 !== '0'. Casting these values to
142 // strings isn't the most elegant workaround, but it allows the assertions
144 if ($this->database->driver() == 'sqlite') {
145 $defaults['source_row_status'] = (string) $defaults['source_row_status'];
146 $defaults['rollback_action'] = (string) $defaults['rollback_action'];
152 * Tests the ID mapping method.
154 * Create two ID mappings and update the second to verify that:
155 * - saving new to empty tables work.
156 * - saving new to nonempty tables work.
159 public function testSaveIdMapping() {
161 'source_id_property' => 'source_value',
163 $row = new Row($source, ['source_id_property' => []]);
164 $id_map = $this->getIdMap();
165 $id_map->saveIdMapping($row, ['destination_id_property' => 2]);
168 'sourceid1' => 'source_value',
169 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
171 ] + $this->idMapDefaults(),
173 $this->queryResultTest($this->getIdMapContents(), $expected_result);
175 'source_id_property' => 'source_value_1',
177 $row = new Row($source, ['source_id_property' => []]);
178 $id_map->saveIdMapping($row, ['destination_id_property' => 3]);
179 $expected_result[] = [
180 'sourceid1' => 'source_value_1',
181 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
183 ] + $this->idMapDefaults();
184 $this->queryResultTest($this->getIdMapContents(), $expected_result);
185 $id_map->saveIdMapping($row, ['destination_id_property' => 4]);
186 $expected_result[1]['destid1'] = 4;
187 $this->queryResultTest($this->getIdMapContents(), $expected_result);
191 * Tests the SQL ID map set message method.
193 public function testSetMessage() {
194 $message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
195 $id_map = $this->getIdMap();
196 $id_map->setMessage($message);
197 $this->assertAttributeEquals($message, 'message', $id_map);
201 * Tests the clear messages method.
203 public function testClearMessages() {
204 $message = 'Hello world.';
205 $expected_results = [0, 1, 2, 3];
206 $id_map = $this->getIdMap();
208 // Insert 4 message for later delete.
209 foreach ($expected_results as $key => $expected_result) {
210 $id_map->saveMessage(['source_id_property' => $key], $message);
213 // Truncate and check that 4 messages were deleted.
214 $this->assertSame($id_map->messageCount(), 4);
215 $id_map->clearMessages();
216 $count = $id_map->messageCount();
217 $this->assertSame($count, 0);
221 * Tests the getRowsNeedingUpdate method for rows that need an update.
223 public function testGetRowsNeedingUpdate() {
224 $id_map = $this->getIdMap();
226 MigrateIdMapInterface::STATUS_IMPORTED,
227 MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
228 MigrateIdMapInterface::STATUS_IGNORED,
229 MigrateIdMapInterface::STATUS_FAILED,
231 // Create a mapping row for each STATUS constant.
232 foreach ($row_statuses as $status) {
233 $source = ['source_id_property' => 'source_value_' . $status];
234 $row = new Row($source, ['source_id_property' => []]);
235 $destination = ['destination_id_property' => 'destination_value_' . $status];
236 $id_map->saveIdMapping($row, $destination, $status);
237 $expected_results[] = [
238 'sourceid1' => 'source_value_' . $status,
239 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
240 'destid1' => 'destination_value_' . $status,
241 'source_row_status' => $status,
242 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
245 // Assert zero rows need an update.
246 if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
247 $rows_needing_update = $id_map->getRowsNeedingUpdate(1);
248 $this->assertCount(0, $rows_needing_update);
251 // Assert that test values exist.
252 $this->queryResultTest($this->getIdMapContents(), $expected_results);
254 // Assert a single row needs an update.
255 $row_needing_update = $id_map->getRowsNeedingUpdate(1);
256 $this->assertCount(1, $row_needing_update);
258 // Assert the row matches its original source.
259 $source_id = $expected_results[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]['sourceid1'];
260 $test_row = $id_map->getRowBySource(['source_id_property' => $source_id]);
261 // $row_needing_update is an array of objects returned from the database,
262 // but $test_row is an array, so the cast is necessary.
263 $this->assertSame($test_row, (array) $row_needing_update[0]);
265 // Add additional row that needs an update.
266 $source = ['source_id_property' => 'source_value_multiple'];
267 $row = new Row($source, ['source_id_property' => []]);
268 $destination = ['destination_id_property' => 'destination_value_multiple'];
269 $id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
271 // Assert multiple rows need an update.
272 $rows_needing_update = $id_map->getRowsNeedingUpdate(2);
273 $this->assertCount(2, $rows_needing_update);
277 * Tests the SQL ID map message count method by counting and saving messages.
279 public function testMessageCount() {
280 $message = 'Hello world.';
281 $expected_results = [0, 1, 2, 3];
282 $id_map = $this->getIdMap();
284 // Test count message multiple times starting from 0.
285 foreach ($expected_results as $key => $expected_result) {
286 $count = $id_map->messageCount();
287 $this->assertSame($expected_result, $count);
288 $id_map->saveMessage(['source_id_property' => $key], $message);
293 * Tests the SQL ID map save message method.
295 public function testMessageSave() {
296 $message = 'Hello world.';
298 1 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR],
299 2 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING],
300 3 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE],
301 4 => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL],
303 $expected_results = [
304 '7ad742edb7e866caa78ced1e4455d2e9cbd8adb2074e7c323d21b4e67732e755' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_ERROR],
305 '2d3ec2b0c547e819346e6ae03f881fd9f5c978ff3cbe29dfb807d40735e53703' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_WARNING],
306 '12a042f72cad9a2a8c7715df0c7695d762975f0687d87f5d480725dae1432a6f' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_NOTICE],
307 'd9d1fd27a2447ace48f47a2e9ff649673f67b446d9381a7963c949fc083f8791' => ['message' => $message, 'level' => MigrationInterface::MESSAGE_INFORMATIONAL],
309 $id_map = $this->getIdMap();
311 foreach ($original_values as $key => $original_value) {
312 $id_map->saveMessage(['source_id_property' => $key], $message, $original_value['level']);
315 foreach ($id_map->getMessageIterator() as $message_row) {
316 $key = $message_row->source_ids_hash;
317 $this->assertEquals($expected_results[$key]['message'], $message_row->message);
318 $this->assertEquals($expected_results[$key]['level'], $message_row->level);
321 // Insert with default level.
322 $message_default = 'Hello world default.';
323 $id_map->saveMessage(['source_id_property' => 5], $message_default);
324 $messages = $id_map->getMessageIterator(['source_id_property' => 5]);
326 foreach ($messages as $key => $message_row) {
328 $this->assertEquals($message_default, $message_row->message);
329 $this->assertEquals(MigrationInterface::MESSAGE_ERROR, $message_row->level);
331 $this->assertEquals($count, 1);
333 // Retrieve messages with a specific level.
334 $messages = $id_map->getMessageIterator([], MigrationInterface::MESSAGE_WARNING);
336 foreach ($messages as $key => $message_row) {
338 $this->assertEquals(MigrationInterface::MESSAGE_WARNING, $message_row->level);
340 $this->assertEquals($count, 1);
344 * Tests the getRowBySource method.
346 public function testGetRowBySource() {
347 $this->getDatabase([]);
349 'sourceid1' => 'source_id_value_1',
350 'sourceid2' => 'source_id_value_2',
351 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']),
352 'destid1' => 'destination_id_value_1',
353 ] + $this->idMapDefaults();
354 $this->saveMap($row);
356 'sourceid1' => 'source_id_value_3',
357 'sourceid2' => 'source_id_value_4',
358 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4']),
359 'destid1' => 'destination_id_value_2',
360 ] + $this->idMapDefaults();
361 $this->saveMap($row);
362 $source_id_values = ['source_id_property' => $row['sourceid1'], 'sourceid2' => $row['sourceid2']];
363 $id_map = $this->getIdMap();
364 $result_row = $id_map->getRowBySource($source_id_values);
365 $this->assertSame($row, $result_row);
366 $source_id_values = ['source_id_property' => 'missing_value_1', 'sourceid2' => 'missing_value_2'];
367 $result_row = $id_map->getRowBySource($source_id_values);
368 $this->assertFalse($result_row);
372 * Data provider for testLookupDestinationIdMapping().
374 * Scenarios to test (for both hits and misses) are:
375 * - Single-value source ID to single-value destination ID.
376 * - Multi-value source ID to multi-value destination ID.
377 * - Single-value source ID to multi-value destination ID.
378 * - Multi-value source ID to single-value destination ID.
381 * An array of data values.
383 public function lookupDestinationIdMappingDataProvider() {
393 * Performs destination ID test on source and destination fields.
395 * @param int $num_source_fields
396 * Number of source fields to test.
397 * @param int $num_destination_fields
398 * Number of destination fields to test.
400 * @dataProvider lookupDestinationIdMappingDataProvider
402 public function testLookupDestinationIdMapping($num_source_fields, $num_destination_fields) {
403 // Adjust the migration configuration according to the number of source and
404 // destination fields.
405 $this->sourceIds = [];
406 $this->destinationIds = [];
407 $source_id_values = [];
408 $nonexistent_id_values = [];
409 $row = $this->idMapDefaults();
410 for ($i = 1; $i <= $num_source_fields; $i++) {
411 $row["sourceid$i"] = "source_id_value_$i";
412 $source_id_values[] = "source_id_value_$i";
413 $nonexistent_id_values[] = "nonexistent_source_id_value_$i";
414 $this->sourceIds["source_id_property_$i"] = [];
416 $expected_result = [];
417 for ($i = 1; $i <= $num_destination_fields; $i++) {
418 $row["destid$i"] = "destination_id_value_$i";
419 $expected_result[] = "destination_id_value_$i";
420 $this->destinationIds["destination_id_property_$i"] = [];
422 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_id_values);
423 $this->saveMap($row);
424 $id_map = $this->getIdMap();
425 // Test for a valid hit.
426 $destination_id = $id_map->lookupDestinationId($source_id_values);
427 $this->assertSame($expected_result, $destination_id);
429 $destination_id = $id_map->lookupDestinationId($nonexistent_id_values);
430 $this->assertSame(0, count($destination_id));
434 * Setup a database with the given rows.
436 * @param array $source_keys
437 * The source keys for the ID map table.
438 * @param array $dest_keys
439 * The destination keys for the ID map table.
441 * An array of source and destination value arrays for the ID map table.
443 * @return \Drupal\Tests\migrate\Unit\TestSqlIdMap
444 * An ID map instance for testing.
446 protected function setupRows($source_keys, $dest_keys, $rows) {
447 $this->database = $this->getDatabase([]);
448 $this->sourceIds = array_fill_keys($source_keys, []);
449 $this->destinationIds = array_fill_keys($dest_keys, []);
452 foreach (array_keys($source_keys) as $i) {
453 $db_keys[] = 'sourceid' . ($i + 1);
455 foreach (array_keys($dest_keys) as $i) {
456 $db_keys[] = 'destid' . ($i + 1);
458 foreach ($rows as $row) {
459 $values = array_combine($db_keys, $row);
460 $source_values = array_slice($row, 0, count($source_keys));
461 $values['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_values);
462 $this->saveMap($values);
465 return $this->getIdMap();
469 * Tests lookupDestinationIds().
471 public function testLookupDestinationIds() {
472 // Simple map with one source and one destination ID.
473 $id_map = $this->setupRows(['nid'], ['nid'], [
479 // Lookup nothing, gives nothing.
480 $this->assertEquals([], $id_map->lookupDestinationIds([]));
481 // Lookup by complete non-associative list.
482 $this->assertEquals([[101]], $id_map->lookupDestinationIds([1]));
483 $this->assertEquals([[102]], $id_map->lookupDestinationIds([2]));
484 $this->assertEquals([], $id_map->lookupDestinationIds([99]));
485 // Lookup by complete associative list.
486 $this->assertEquals([[101]], $id_map->lookupDestinationIds(['nid' => 1]));
487 $this->assertEquals([[102]], $id_map->lookupDestinationIds(['nid' => 2]));
488 $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99]));
490 // Map with multiple source and destination IDs.
491 $id_map = $this->setupRows(['nid', 'language'], ['nid', 'langcode'], [
492 [1, 'en', 101, 'en'],
493 [1, 'fr', 101, 'fr'],
494 [1, 'de', 101, 'de'],
495 [2, 'en', 102, 'en'],
498 // Lookup nothing, gives nothing.
499 $this->assertEquals([], $id_map->lookupDestinationIds([]));
500 // Lookup by complete non-associative list.
501 $this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en']));
502 $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds([1, 'fr']));
503 $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2, 'en']));
504 $this->assertEquals([], $id_map->lookupDestinationIds([2, 'fr']));
505 $this->assertEquals([], $id_map->lookupDestinationIds([99, 'en']));
506 // Lookup by complete associative list.
507 $this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'en']));
508 $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'fr']));
509 $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'en']));
510 $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'fr']));
511 $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99, 'language' => 'en']));
512 // Lookup by partial non-associative list.
513 $this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds([1]));
514 $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2]));
515 $this->assertEquals([], $id_map->lookupDestinationIds([99]));
516 // Lookup by partial associative list.
517 $this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds(['nid' => 1]));
518 $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2]));
519 $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99]));
520 // Out-of-order partial associative list.
521 $this->assertEquals([[101, 'en'], [102, 'en']], $id_map->lookupDestinationIds(['language' => 'en']));
522 $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['language' => 'fr']));
523 $this->assertEquals([], $id_map->lookupDestinationIds(['language' => 'zh']));
526 $id_map->lookupDestinationIds([1, 2, 3]);
527 $this->fail('Too many source IDs should throw');
529 catch (MigrateException $e) {
530 $this->assertEquals("Extra unknown items in source IDs", $e->getMessage());
533 $id_map->lookupDestinationIds(['nid' => 1, 'aaa' => '2']);
534 $this->fail('Unknown source ID key should throw');
536 catch (MigrateException $e) {
537 $this->assertEquals("Extra unknown items in source IDs", $e->getMessage());
540 // Verify that we are looking up by source_id_hash when all source IDs are
542 $id_map->getDatabase()->update($id_map->mapTableName())
543 ->condition('sourceid1', 1)
544 ->condition('sourceid2', 'en')
545 ->fields([TestSqlIdMap::SOURCE_IDS_HASH => uniqid()])
547 $this->assertNotEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en']));
551 * Tests the getRowByDestination method.
553 public function testGetRowByDestination() {
555 'sourceid1' => 'source_id_value_1',
556 'sourceid2' => 'source_id_value_2',
557 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']),
558 'destid1' => 'destination_id_value_1',
559 ] + $this->idMapDefaults();
560 $this->saveMap($row);
562 'sourceid1' => 'source_id_value_3',
563 'sourceid2' => 'source_id_value_4',
564 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3']),
565 'destid1' => 'destination_id_value_2',
566 ] + $this->idMapDefaults();
567 $this->saveMap($row);
568 $dest_id_values = ['destination_id_property' => $row['destid1']];
569 $id_map = $this->getIdMap();
570 $result_row = $id_map->getRowByDestination($dest_id_values);
571 $this->assertSame($row, $result_row);
572 // This value does not exist.
573 $dest_id_values = ['destination_id_property' => 'invalid_destination_id_property'];
574 $id_map = $this->getIdMap();
575 $result_row = $id_map->getRowByDestination($dest_id_values);
576 $this->assertFalse($result_row);
580 * Data provider for testLookupSourceIDMapping().
582 * Scenarios to test (for both hits and misses) are:
583 * - Single-value destination ID to single-value source ID.
584 * - Multi-value destination ID to multi-value source ID.
585 * - Single-value destination ID to multi-value source ID.
586 * - Multi-value destination ID to single-value source ID.
589 * An array of data values.
591 public function lookupSourceIDMappingDataProvider() {
601 * Performs the source ID test on source and destination fields.
603 * @param int $num_source_fields
604 * Number of source fields to test.
605 * @param int $num_destination_fields
606 * Number of destination fields to test.
608 * @dataProvider lookupSourceIDMappingDataProvider
610 public function testLookupSourceIDMapping($num_source_fields, $num_destination_fields) {
611 // Adjust the migration configuration according to the number of source and
612 // destination fields.
613 $this->sourceIds = [];
614 $this->destinationIds = [];
615 $row = $this->idMapDefaults();
616 $source_ids_values = [];
617 $expected_result = [];
618 for ($i = 1; $i <= $num_source_fields; $i++) {
619 $row["sourceid$i"] = "source_id_value_$i";
620 $source_ids_values = [$row["sourceid$i"]];
621 $expected_result["source_id_property_$i"] = "source_id_value_$i";
622 $this->sourceIds["source_id_property_$i"] = [];
624 $destination_id_values = [];
625 $nonexistent_id_values = [];
626 for ($i = 1; $i <= $num_destination_fields; $i++) {
627 $row["destid$i"] = "destination_id_value_$i";
628 $destination_id_values["destination_id_property_$i"] = "destination_id_value_$i";
629 $nonexistent_id_values["destination_id_property_$i"] = "nonexistent_destination_id_value_$i";
630 $this->destinationIds["destination_id_property_$i"] = [];
632 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash($source_ids_values);
633 $this->saveMap($row);
634 $id_map = $this->getIdMap();
635 // Test for a valid hit.
636 $source_id = $id_map->lookupSourceID($destination_id_values);
637 $this->assertSame($expected_result, $source_id);
639 $source_id = $id_map->lookupSourceID($nonexistent_id_values);
640 $this->assertSame(0, count($source_id));
644 * Tests currentDestination() and currentSource().
646 public function testCurrentDestinationAndSource() {
647 // Simple map with one source and one destination ID.
648 $id_map = $this->setupRows(['nid'], ['nid'], [
652 // Mock a failed row by setting the destination ID to NULL.
656 // The rows are ordered by destination ID so the failed row should be first.
658 $this->assertEquals([], $id_map->currentDestination());
659 $this->assertEquals(['nid' => 4], $id_map->currentSource());
661 $this->assertEquals(['nid' => 101], $id_map->currentDestination());
662 $this->assertEquals(['nid' => 1], $id_map->currentSource());
664 $this->assertEquals(['nid' => 102], $id_map->currentDestination());
665 $this->assertEquals(['nid' => 2], $id_map->currentSource());
667 $this->assertEquals(['nid' => 103], $id_map->currentDestination());
668 $this->assertEquals(['nid' => 3], $id_map->currentSource());
673 * Tests the imported count method.
675 * Scenarios to test for:
678 * - Multiple imports.
680 public function testImportedCount() {
681 $id_map = $this->getIdMap();
682 // Add a single failed row and assert zero imported rows.
683 $source = ['source_id_property' => 'source_value_failed'];
684 $row = new Row($source, ['source_id_property' => []]);
685 $destination = ['destination_id_property' => 'destination_value_failed'];
686 $id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_FAILED);
687 $this->assertSame(0, $id_map->importedCount());
689 // Add an imported row and assert single count.
690 $source = ['source_id_property' => 'source_value_imported'];
691 $row = new Row($source, ['source_id_property' => []]);
692 $destination = ['destination_id_property' => 'destination_value_imported'];
693 $id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_IMPORTED);
694 $this->assertSame(1, $id_map->importedCount());
696 // Add a row needing update and assert multiple imported rows.
697 $source = ['source_id_property' => 'source_value_update'];
698 $row = new Row($source, ['source_id_property' => []]);
699 $destination = ['destination_id_property' => 'destination_value_update'];
700 $id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
701 $this->assertSame(2, $id_map->importedCount());
705 * Tests the number of processed source rows.
707 * Scenarios to test for:
708 * - No processed rows.
709 * - One processed row.
710 * - Multiple processed rows.
712 public function testProcessedCount() {
713 $id_map = $this->getIdMap();
714 // Assert zero rows have been processed before adding rows.
715 $this->assertSame(0, $id_map->processedCount());
717 MigrateIdMapInterface::STATUS_IMPORTED,
718 MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
719 MigrateIdMapInterface::STATUS_IGNORED,
720 MigrateIdMapInterface::STATUS_FAILED,
722 // Create a mapping row for each STATUS constant.
723 foreach ($row_statuses as $status) {
724 $source = ['source_id_property' => 'source_value_' . $status];
725 $row = new Row($source, ['source_id_property' => []]);
726 $destination = ['destination_id_property' => 'destination_value_' . $status];
727 $id_map->saveIdMapping($row, $destination, $status);
728 if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
729 // Assert a single row has been processed.
730 $this->assertSame(1, $id_map->processedCount());
733 // Assert multiple rows have been processed.
734 $this->assertSame(count($row_statuses), $id_map->processedCount());
738 * Data provider for testUpdateCount().
740 * Scenarios to test for:
743 * - Multiple updates.
746 * An array of data values.
748 public function updateCountDataProvider() {
757 * Performs the update count test with a given number of update rows.
759 * @param int $num_update_rows
760 * The number of update rows to test.
762 * @dataProvider updateCountDataProvider
764 public function testUpdateCount($num_update_rows) {
765 for ($i = 0; $i < 5; $i++) {
766 $row = $this->idMapDefaults();
767 $row['sourceid1'] = "source_id_value_$i";
768 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
769 $row['destid1'] = "destination_id_value_$i";
770 $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
771 $this->saveMap($row);
773 for (; $i < 5 + $num_update_rows; $i++) {
774 $row = $this->idMapDefaults();
775 $row['sourceid1'] = "source_id_value_$i";
776 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
777 $row['destid1'] = "destination_id_value_$i";
778 $row['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
779 $this->saveMap($row);
781 $id_map = $this->getIdMap();
782 $this->assertSame($num_update_rows, $id_map->updateCount());
786 * Data provider for testErrorCount().
788 * Scenarios to test for:
794 * An array of data values.
796 public function errorCountDataProvider() {
805 * Performs error count test with a given number of error rows.
807 * @param int $num_error_rows
808 * Number of error rows to test.
810 * @dataProvider errorCountDataProvider
812 public function testErrorCount($num_error_rows) {
813 for ($i = 0; $i < 5; $i++) {
814 $row = $this->idMapDefaults();
815 $row['sourceid1'] = "source_id_value_$i";
816 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
817 $row['destid1'] = "destination_id_value_$i";
818 $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
819 $this->saveMap($row);
821 for (; $i < 5 + $num_error_rows; $i++) {
822 $row = $this->idMapDefaults();
823 $row['sourceid1'] = "source_id_value_$i";
824 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
825 $row['destid1'] = "destination_id_value_$i";
826 $row['source_row_status'] = MigrateIdMapInterface::STATUS_FAILED;
827 $this->saveMap($row);
830 $this->assertSame($num_error_rows, $this->getIdMap()->errorCount());
834 * Tests setting a row source_row_status to STATUS_NEEDS_UPDATE.
836 public function testSetUpdate() {
837 $id_map = $this->getIdMap();
839 MigrateIdMapInterface::STATUS_IMPORTED,
840 MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
841 MigrateIdMapInterface::STATUS_IGNORED,
842 MigrateIdMapInterface::STATUS_FAILED,
844 // Create a mapping row for each STATUS constant.
845 foreach ($row_statuses as $status) {
846 $source = ['source_id_property' => 'source_value_' . $status];
847 $row = new Row($source, ['source_id_property' => []]);
848 $destination = ['destination_id_property' => 'destination_value_' . $status];
849 $id_map->saveIdMapping($row, $destination, $status);
850 $expected_results[] = [
851 'sourceid1' => 'source_value_' . $status,
852 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash($source),
853 'destid1' => 'destination_value_' . $status,
854 'source_row_status' => $status,
855 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
859 // Assert that test values exist.
860 $this->queryResultTest($this->getIdMapContents(), $expected_results);
861 // Mark each row as STATUS_NEEDS_UPDATE.
862 foreach ($row_statuses as $status) {
863 $id_map->setUpdate(['source_id_property' => 'source_value_' . $status]);
865 // Update expected results.
866 foreach ($expected_results as $key => $value) {
867 $expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
869 // Assert that updated expected values match.
870 $this->queryResultTest($this->getIdMapContents(), $expected_results);
871 // Assert an exception is thrown when source identifiers are not provided.
873 $id_map->setUpdate([]);
874 $this->assertFalse(FALSE, 'MigrateException not thrown, when source identifiers were provided to update.');
876 catch (MigrateException $e) {
877 $this->assertTrue(TRUE, "MigrateException thrown, when source identifiers were not provided to update.");
882 * Tests prepareUpdate().
884 public function testPrepareUpdate() {
885 $id_map = $this->getIdMap();
887 MigrateIdMapInterface::STATUS_IMPORTED,
888 MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
889 MigrateIdMapInterface::STATUS_IGNORED,
890 MigrateIdMapInterface::STATUS_FAILED,
893 // Create a mapping row for each STATUS constant.
894 foreach ($row_statuses as $status) {
895 $source = ['source_id_property' => 'source_value_' . $status];
896 $row = new Row($source, ['source_id_property' => []]);
897 $destination = ['destination_id_property' => 'destination_value_' . $status];
898 $id_map->saveIdMapping($row, $destination, $status);
899 $expected_results[] = [
900 'sourceid1' => 'source_value_' . $status,
901 'destid1' => 'destination_value_' . $status,
902 'source_row_status' => $status,
903 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE,
908 // Assert that test values exist.
909 $this->queryResultTest($this->getIdMapContents(), $expected_results);
911 // Mark all rows as STATUS_NEEDS_UPDATE.
912 $id_map->prepareUpdate();
914 // Update expected results.
915 foreach ($expected_results as $key => $value) {
916 $expected_results[$key]['source_row_status'] = MigrateIdMapInterface::STATUS_NEEDS_UPDATE;
918 // Assert that updated expected values match.
919 $this->queryResultTest($this->getIdMapContents(), $expected_results);
923 * Tests the destroy method.
925 * Scenarios to test for:
930 public function testDestroy() {
931 $id_map = $this->getIdMap();
932 // Initialize the ID map.
933 $id_map->getDatabase();
934 $map_table_name = $id_map->mapTableName();
935 $message_table_name = $id_map->messageTableName();
936 $row = new Row(['source_id_property' => 'source_value'], ['source_id_property' => []]);
937 $id_map->saveIdMapping($row, ['destination_id_property' => 2]);
938 $id_map->saveMessage(['source_id_property' => 'source_value'], 'A message');
939 $this->assertTrue($this->database->schema()->tableExists($map_table_name),
940 "$map_table_name exists");
941 $this->assertTrue($this->database->schema()->tableExists($message_table_name),
942 "$message_table_name exists");
944 $this->assertFalse($this->database->schema()->tableExists($map_table_name),
945 "$map_table_name does not exist");
946 $this->assertFalse($this->database->schema()->tableExists($message_table_name),
947 "$message_table_name does not exist");
951 * Tests the getQualifiedMapTable method with a prefixed database.
953 public function testGetQualifiedMapTablePrefix() {
954 $connection_options = [
955 'database' => ':memory:',
956 'prefix' => 'prefix',
958 $pdo = Connection::open($connection_options);
959 $this->database = new Connection($pdo, $connection_options);
960 $qualified_map_table = $this->getIdMap()->getQualifiedMapTableName();
961 // The SQLite driver is a special flower. It will prefix tables with
962 // PREFIX.TABLE, instead of the standard PREFIXTABLE.
963 // @see \Drupal\Core\Database\Driver\sqlite\Connection::__construct()
964 $this->assertEquals('prefix.migrate_map_sql_idmap_test', $qualified_map_table);
968 * Tests all the iterator methods in one swing.
970 * The iterator methods are:
977 public function testIterators() {
978 for ($i = 0; $i < 3; $i++) {
979 $row = $this->idMapDefaults();
980 $row['sourceid1'] = "source_id_value_$i";
981 $row['source_ids_hash'] = $this->getIdMap()->getSourceIDsHash(['source_id_property' => $row['sourceid1']]);
982 $row['destid1'] = "destination_id_value_$i";
983 $row['source_row_status'] = MigrateIdMapInterface::STATUS_IMPORTED;
984 $expected_results[serialize(['sourceid1' => $row['sourceid1']])] = ['destid1' => $row['destid1']];
985 $this->saveMap($row);
988 $this->assertSame(iterator_to_array($this->getIdMap()), $expected_results);
992 * Retrieves the contents of an ID map.
995 * The contents of an ID map.
997 private function getIdMapContents() {
998 $result = $this->database
999 ->select('migrate_map_sql_idmap_test', 't')
1003 // The return value needs to be countable, or it will fail certain
1004 // assertions. iterator_to_array() will not suffice because it won't
1005 // respect the PDO fetch mode, if specified.
1007 foreach ($result as $row) {
1008 $contents[] = (array) $row;