Mercurial > hg > isophonics-drupal-site
diff core/modules/migrate/tests/src/Unit/MigrateSourceTest.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/migrate/tests/src/Unit/MigrateSourceTest.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,552 @@ +<?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\Core\KeyValueStore\KeyValueFactoryInterface; +use Drupal\Core\KeyValueStore\KeyValueStoreInterface; +use Drupal\migrate\MigrateException; +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, $high_water_value = NULL) { + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + + $key_value = $this->getMock(KeyValueStoreInterface::class); + + $key_value_factory = $this->getMock(KeyValueFactoryInterface::class); + $key_value_factory + ->method('get') + ->with('migrate:high_water') + ->willReturn($key_value); + $container->set('keyvalue', $key_value_factory); + + $container->set('cache.migrate', $this->getMock(CacheBackendInterface::class)); + + $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', 'prepareRow', 'initializeIterator']; + $source_plugin = $this->getMock(SourcePluginBase::class, $methods, $constructor_args); + + $source_plugin + ->method('fields') + ->willReturn([]); + $source_plugin + ->method('getIds') + ->willReturn([]); + $source_plugin + ->method('__toString') + ->willReturn(''); + $source_plugin + ->method('prepareRow') + ->willReturn(empty($migrate_config['prepare_row_false'])); + + $rows = [$this->row]; + if (isset($configuration['high_water_property']) && isset($high_water_value)) { + $property = $configuration['high_water_property']['name']; + $rows = array_filter($rows, function (array $row) use ($property, $high_water_value) { + return $row[$property] >= $high_water_value; + }); + } + $iterator = new \ArrayIterator($rows); + + $source_plugin + ->method('initializeIterator') + ->willReturn($iterator); + + $module_handler = $this->getMock(ModuleHandlerInterface::class); + $source_plugin + ->method('getModuleHandler') + ->willReturn($module_handler); + + $this->migration + ->method('getSourcePlugin') + ->willReturn($source_plugin); + + return $source_plugin; + } + + /** + * @covers ::__construct + */ + public function testHighwaterTrackChangesIncompatible() { + $source_config = ['track_changes' => TRUE, 'high_water_property' => ['name' => 'something']]; + $this->setExpectedException(MigrateException::class); + $this->getSource($source_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()); + + $this->migrationConfiguration['id'] = 'test_migration'; + $migration = $this->getMigration(); + $source = new StubSourceGeneratorPlugin([], '', [], $migration); + + // Test the skipCount property's default value. + $this->assertEquals(-1, $source->count()); + + // Test the count value using a generator. + $source = new StubSourceGeneratorPlugin(['skip_count' => FALSE], '', [], $migration); + $this->assertEquals(3, $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() { + $configuration = [ + 'high_water_property' => [ + 'name' => 'timestamp', + ], + ]; + $source = $this->getSource($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $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() { + $configuration = [ + 'high_water_property' => [ + 'name' => 'timestamp', + ], + ]; + // 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($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $this->row['timestamp'] - 1); + + $source->rewind(); + $this->assertInstanceOf(Row::class, $source->current(), '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)); + } + + /** + * Test that cacheCounts, skipCount, trackChanges preserve their default + * values. + */ + public function testDefaultPropertiesValues() { + $this->migrationConfiguration['id'] = 'test_migration'; + $migration = $this->getMigration(); + $source = new StubSourceGeneratorPlugin([], '', [], $migration); + + // Test the default value of the skipCount Value; + $this->assertTrue($source->getSkipCount()); + $this->assertTrue($source->getCacheCounts()); + $this->assertTrue($source->getTrackChanges()); + } + + /** + * 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 []; + } + +} + +/** + * Stubbed source plugin with a generator as iterator. Also it overwrites the + * $skipCount, $cacheCounts and $trackChanges properties. + */ +class StubSourceGeneratorPlugin extends StubSourcePlugin { + + /** + * {@inheritdoc} + */ + protected $skipCount = TRUE; + + /** + * {@inheritdoc} + */ + protected $cacheCounts = TRUE; + + /** + * {@inheritdoc} + */ + protected $trackChanges = TRUE; + + /** + * Return the skipCount value. + */ + public function getSkipCount() { + return $this->skipCount; + } + + /** + * Return the cacheCounts value. + */ + public function getCacheCounts() { + return $this->cacheCounts; + } + + /** + * Return the trackChanges value. + */ + public function getTrackChanges() { + return $this->trackChanges; + } + + /** + * {@inheritdoc} + */ + protected function initializeIterator() { + $data = [ + ['title' => 'foo'], + ['title' => 'bar'], + ['title' => 'iggy'], + ]; + foreach ($data as $row) { + yield $row; + } + } + +}