comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 /**
4 * @file
5 * Contains \Drupal\Tests\migrate\Unit\MigrateSourceTest.
6 */
7
8 namespace Drupal\Tests\migrate\Unit;
9
10 use Drupal\Core\Cache\CacheBackendInterface;
11 use Drupal\Core\DependencyInjection\ContainerBuilder;
12 use Drupal\Core\Extension\ModuleHandlerInterface;
13 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
14 use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
15 use Drupal\migrate\MigrateException;
16 use Drupal\migrate\MigrateExecutable;
17 use Drupal\migrate\MigrateSkipRowException;
18 use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
19 use Drupal\migrate\Plugin\MigrateIdMapInterface;
20 use Drupal\migrate\Row;
21
22 /**
23 * @coversDefaultClass \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
24 * @group migrate
25 */
26 class MigrateSourceTest extends MigrateTestCase {
27
28 /**
29 * Override the migration config.
30 *
31 * @var array
32 */
33 protected $defaultMigrationConfiguration = [
34 'id' => 'test_migration',
35 'source' => [],
36 ];
37
38 /**
39 * Test row data.
40 *
41 * @var array
42 */
43 protected $row = ['test_sourceid1' => '1', 'timestamp' => 500];
44
45 /**
46 * Test source ids.
47 *
48 * @var array
49 */
50 protected $sourceIds = ['test_sourceid1' => 'test_sourceid1'];
51
52 /**
53 * The migration entity.
54 *
55 * @var \Drupal\migrate\Plugin\MigrationInterface
56 */
57 protected $migration;
58
59 /**
60 * The migrate executable.
61 *
62 * @var \Drupal\migrate\MigrateExecutable
63 */
64 protected $executable;
65
66 /**
67 * Gets the source plugin to test.
68 *
69 * @param array $configuration
70 * (optional) The source configuration. Defaults to an empty array.
71 * @param array $migrate_config
72 * (optional) The migration configuration to be used in
73 * parent::getMigration(). Defaults to an empty array.
74 * @param int $status
75 * (optional) The default status for the new rows to be imported. Defaults
76 * to MigrateIdMapInterface::STATUS_NEEDS_UPDATE.
77 *
78 * @return \Drupal\migrate\Plugin\MigrateSourceInterface
79 * A mocked source plugin.
80 */
81 protected function getSource($configuration = [], $migrate_config = [], $status = MigrateIdMapInterface::STATUS_NEEDS_UPDATE, $high_water_value = NULL) {
82 $container = new ContainerBuilder();
83 \Drupal::setContainer($container);
84
85 $key_value = $this->getMock(KeyValueStoreInterface::class);
86
87 $key_value_factory = $this->getMock(KeyValueFactoryInterface::class);
88 $key_value_factory
89 ->method('get')
90 ->with('migrate:high_water')
91 ->willReturn($key_value);
92 $container->set('keyvalue', $key_value_factory);
93
94 $container->set('cache.migrate', $this->getMock(CacheBackendInterface::class));
95
96 $this->migrationConfiguration = $this->defaultMigrationConfiguration + $migrate_config;
97 $this->migration = parent::getMigration();
98 $this->executable = $this->getMigrateExecutable($this->migration);
99
100 // Update the idMap for Source so the default is that the row has already
101 // been imported. This allows us to use the highwater mark to decide on the
102 // outcome of whether we choose to import the row.
103 $id_map_array = ['original_hash' => '', 'hash' => '', 'source_row_status' => $status];
104 $this->idMap
105 ->expects($this->any())
106 ->method('getRowBySource')
107 ->willReturn($id_map_array);
108
109 $constructor_args = [$configuration, 'd6_action', [], $this->migration];
110 $methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'prepareRow', 'initializeIterator'];
111 $source_plugin = $this->getMock(SourcePluginBase::class, $methods, $constructor_args);
112
113 $source_plugin
114 ->method('fields')
115 ->willReturn([]);
116 $source_plugin
117 ->method('getIds')
118 ->willReturn([]);
119 $source_plugin
120 ->method('__toString')
121 ->willReturn('');
122 $source_plugin
123 ->method('prepareRow')
124 ->willReturn(empty($migrate_config['prepare_row_false']));
125
126 $rows = [$this->row];
127 if (isset($configuration['high_water_property']) && isset($high_water_value)) {
128 $property = $configuration['high_water_property']['name'];
129 $rows = array_filter($rows, function (array $row) use ($property, $high_water_value) {
130 return $row[$property] >= $high_water_value;
131 });
132 }
133 $iterator = new \ArrayIterator($rows);
134
135 $source_plugin
136 ->method('initializeIterator')
137 ->willReturn($iterator);
138
139 $module_handler = $this->getMock(ModuleHandlerInterface::class);
140 $source_plugin
141 ->method('getModuleHandler')
142 ->willReturn($module_handler);
143
144 $this->migration
145 ->method('getSourcePlugin')
146 ->willReturn($source_plugin);
147
148 return $source_plugin;
149 }
150
151 /**
152 * @covers ::__construct
153 */
154 public function testHighwaterTrackChangesIncompatible() {
155 $source_config = ['track_changes' => TRUE, 'high_water_property' => ['name' => 'something']];
156 $this->setExpectedException(MigrateException::class);
157 $this->getSource($source_config);
158 }
159
160 /**
161 * Test that the source count is correct.
162 *
163 * @covers ::count
164 */
165 public function testCount() {
166 // Mock the cache to validate set() receives appropriate arguments.
167 $container = new ContainerBuilder();
168 $cache = $this->getMock(CacheBackendInterface::class);
169 $cache->expects($this->any())->method('set')
170 ->with($this->isType('string'), $this->isType('int'), $this->isType('int'));
171 $container->set('cache.migrate', $cache);
172 \Drupal::setContainer($container);
173
174 // Test that the basic count works.
175 $source = $this->getSource();
176 $this->assertEquals(1, $source->count());
177
178 // Test caching the count works.
179 $source = $this->getSource(['cache_counts' => TRUE]);
180 $this->assertEquals(1, $source->count());
181
182 // Test the skip argument.
183 $source = $this->getSource(['skip_count' => TRUE]);
184 $this->assertEquals(-1, $source->count());
185
186 $this->migrationConfiguration['id'] = 'test_migration';
187 $migration = $this->getMigration();
188 $source = new StubSourceGeneratorPlugin([], '', [], $migration);
189
190 // Test the skipCount property's default value.
191 $this->assertEquals(-1, $source->count());
192
193 // Test the count value using a generator.
194 $source = new StubSourceGeneratorPlugin(['skip_count' => FALSE], '', [], $migration);
195 $this->assertEquals(3, $source->count());
196 }
197
198 /**
199 * Test that the key can be set for the count cache.
200 *
201 * @covers ::count
202 */
203 public function testCountCacheKey() {
204 // Mock the cache to validate set() receives appropriate arguments.
205 $container = new ContainerBuilder();
206 $cache = $this->getMock(CacheBackendInterface::class);
207 $cache->expects($this->any())->method('set')
208 ->with('test_key', $this->isType('int'), $this->isType('int'));
209 $container->set('cache.migrate', $cache);
210 \Drupal::setContainer($container);
211
212 // Test caching the count with a configured key works.
213 $source = $this->getSource(['cache_counts' => TRUE, 'cache_key' => 'test_key']);
214 $this->assertEquals(1, $source->count());
215 }
216
217 /**
218 * Test that we don't get a row if prepareRow() is false.
219 */
220 public function testPrepareRowFalse() {
221 $source = $this->getSource([], ['prepare_row_false' => TRUE]);
222
223 $source->rewind();
224 $this->assertNull($source->current(), 'No row is available when prepareRow() is false.');
225 }
226
227 /**
228 * Test that $row->needsUpdate() works as expected.
229 */
230 public function testNextNeedsUpdate() {
231 $source = $this->getSource();
232
233 // $row->needsUpdate() === TRUE so we get a row.
234 $source->rewind();
235 $this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), '$row->needsUpdate() is TRUE so we got a row.');
236
237 // Test that we don't get a row when the incoming row is marked as imported.
238 $source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
239 $source->rewind();
240 $this->assertNull($source->current(), 'Row was already imported, should be NULL');
241 }
242
243 /**
244 * Test that an outdated highwater mark does not cause a row to be imported.
245 */
246 public function testOutdatedHighwater() {
247 $configuration = [
248 'high_water_property' => [
249 'name' => 'timestamp',
250 ],
251 ];
252 $source = $this->getSource($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $this->row['timestamp'] + 1);
253
254 // The current highwater mark is now higher than the row timestamp so no row
255 // is expected.
256 $source->rewind();
257 $this->assertNull($source->current(), 'Original highwater mark is higher than incoming row timestamp.');
258 }
259
260 /**
261 * Test that a highwater mark newer than our saved one imports a row.
262 *
263 * @throws \Exception
264 */
265 public function testNewHighwater() {
266 $configuration = [
267 'high_water_property' => [
268 'name' => 'timestamp',
269 ],
270 ];
271 // Set a highwater property field for source. Now we should have a row
272 // because the row timestamp is greater than the current highwater mark.
273 $source = $this->getSource($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $this->row['timestamp'] - 1);
274
275 $source->rewind();
276 $this->assertInstanceOf(Row::class, $source->current(), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
277 }
278
279 /**
280 * Test basic row preparation.
281 *
282 * @covers ::prepareRow
283 */
284 public function testPrepareRow() {
285 $this->migrationConfiguration['id'] = 'test_migration';
286
287 // Get a new migration with an id.
288 $migration = $this->getMigration();
289 $source = new StubSourcePlugin([], '', [], $migration);
290 $row = new Row();
291
292 $module_handler = $this->prophesize(ModuleHandlerInterface::class);
293 $module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
294 ->willReturn([TRUE, TRUE])
295 ->shouldBeCalled();
296 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
297 ->willReturn([TRUE, TRUE])
298 ->shouldBeCalled();
299 $source->setModuleHandler($module_handler->reveal());
300
301 // Ensure we don't log this to the mapping table.
302 $this->idMap->expects($this->never())
303 ->method('saveIdMapping');
304
305 $this->assertTrue($source->prepareRow($row));
306
307 // Track_changes...
308 $source = new StubSourcePlugin(['track_changes' => TRUE], '', [], $migration);
309 $row2 = $this->prophesize(Row::class);
310 $row2->rehash()
311 ->shouldBeCalled();
312 $module_handler->invokeAll('migrate_prepare_row', [$row2, $source, $migration])
313 ->willReturn([TRUE, TRUE])
314 ->shouldBeCalled();
315 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row2, $source, $migration])
316 ->willReturn([TRUE, TRUE])
317 ->shouldBeCalled();
318 $source->setModuleHandler($module_handler->reveal());
319 $this->assertTrue($source->prepareRow($row2->reveal()));
320 }
321
322 /**
323 * Test that global prepare hooks can skip rows.
324 *
325 * @covers ::prepareRow
326 */
327 public function testPrepareRowGlobalPrepareSkip() {
328 $this->migrationConfiguration['id'] = 'test_migration';
329
330 $migration = $this->getMigration();
331 $source = new StubSourcePlugin([], '', [], $migration);
332 $row = new Row();
333
334 $module_handler = $this->prophesize(ModuleHandlerInterface::class);
335 // Return a failure from a prepare row hook.
336 $module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
337 ->willReturn([TRUE, FALSE, TRUE])
338 ->shouldBeCalled();
339 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
340 ->willReturn([TRUE, TRUE])
341 ->shouldBeCalled();
342 $source->setModuleHandler($module_handler->reveal());
343
344 $this->idMap->expects($this->once())
345 ->method('saveIdMapping')
346 ->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
347
348 $this->assertFalse($source->prepareRow($row));
349 }
350
351 /**
352 * Test that migrate specific prepare hooks can skip rows.
353 *
354 * @covers ::prepareRow
355 */
356 public function testPrepareRowMigratePrepareSkip() {
357 $this->migrationConfiguration['id'] = 'test_migration';
358
359 $migration = $this->getMigration();
360 $source = new StubSourcePlugin([], '', [], $migration);
361 $row = new Row();
362
363 $module_handler = $this->prophesize(ModuleHandlerInterface::class);
364 // Return a failure from a prepare row hook.
365 $module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
366 ->willReturn([TRUE, TRUE])
367 ->shouldBeCalled();
368 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
369 ->willReturn([TRUE, FALSE, TRUE])
370 ->shouldBeCalled();
371 $source->setModuleHandler($module_handler->reveal());
372
373 $this->idMap->expects($this->once())
374 ->method('saveIdMapping')
375 ->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
376
377 $this->assertFalse($source->prepareRow($row));
378 }
379
380 /**
381 * Test that a skip exception during prepare hooks correctly skips.
382 *
383 * @covers ::prepareRow
384 */
385 public function testPrepareRowPrepareException() {
386 $this->migrationConfiguration['id'] = 'test_migration';
387
388 $migration = $this->getMigration();
389 $source = new StubSourcePlugin([], '', [], $migration);
390 $row = new Row();
391
392 $module_handler = $this->prophesize(ModuleHandlerInterface::class);
393 // Return a failure from a prepare row hook.
394 $module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
395 ->willReturn([TRUE, TRUE])
396 ->shouldBeCalled();
397 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
398 ->willThrow(new MigrateSkipRowException())
399 ->shouldBeCalled();
400 $source->setModuleHandler($module_handler->reveal());
401
402 // This will only be called on the first prepare because the second
403 // explicitly avoids it.
404 $this->idMap->expects($this->once())
405 ->method('saveIdMapping')
406 ->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
407 $this->assertFalse($source->prepareRow($row));
408
409 // Throw an exception the second time that avoids mapping.
410 $e = new MigrateSkipRowException('', FALSE);
411 $module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
412 ->willThrow($e)
413 ->shouldBeCalled();
414 $this->assertFalse($source->prepareRow($row));
415 }
416
417 /**
418 * Test that cacheCounts, skipCount, trackChanges preserve their default
419 * values.
420 */
421 public function testDefaultPropertiesValues() {
422 $this->migrationConfiguration['id'] = 'test_migration';
423 $migration = $this->getMigration();
424 $source = new StubSourceGeneratorPlugin([], '', [], $migration);
425
426 // Test the default value of the skipCount Value;
427 $this->assertTrue($source->getSkipCount());
428 $this->assertTrue($source->getCacheCounts());
429 $this->assertTrue($source->getTrackChanges());
430 }
431
432 /**
433 * Gets a mock executable for the test.
434 *
435 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
436 * The migration entity.
437 *
438 * @return \Drupal\migrate\MigrateExecutable
439 * The migrate executable.
440 */
441 protected function getMigrateExecutable($migration) {
442 /** @var \Drupal\migrate\MigrateMessageInterface $message */
443 $message = $this->getMock('Drupal\migrate\MigrateMessageInterface');
444 /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher */
445 $event_dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
446 return new MigrateExecutable($migration, $message, $event_dispatcher);
447 }
448
449 }
450
451 /**
452 * Stubbed source plugin for testing base class implementations.
453 */
454 class StubSourcePlugin extends SourcePluginBase {
455
456 /**
457 * Helper for setting internal module handler implementation.
458 *
459 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
460 * The module handler.
461 */
462 public function setModuleHandler(ModuleHandlerInterface $module_handler) {
463 $this->moduleHandler = $module_handler;
464 }
465
466 /**
467 * {@inheritdoc}
468 */
469 public function fields() {
470 return [];
471 }
472
473 /**
474 * {@inheritdoc}
475 */
476 public function __toString() {
477 return '';
478 }
479
480 /**
481 * {@inheritdoc}
482 */
483 public function getIds() {
484 return [];
485 }
486
487 /**
488 * {@inheritdoc}
489 */
490 protected function initializeIterator() {
491 return [];
492 }
493
494 }
495
496 /**
497 * Stubbed source plugin with a generator as iterator. Also it overwrites the
498 * $skipCount, $cacheCounts and $trackChanges properties.
499 */
500 class StubSourceGeneratorPlugin extends StubSourcePlugin {
501
502 /**
503 * {@inheritdoc}
504 */
505 protected $skipCount = TRUE;
506
507 /**
508 * {@inheritdoc}
509 */
510 protected $cacheCounts = TRUE;
511
512 /**
513 * {@inheritdoc}
514 */
515 protected $trackChanges = TRUE;
516
517 /**
518 * Return the skipCount value.
519 */
520 public function getSkipCount() {
521 return $this->skipCount;
522 }
523
524 /**
525 * Return the cacheCounts value.
526 */
527 public function getCacheCounts() {
528 return $this->cacheCounts;
529 }
530
531 /**
532 * Return the trackChanges value.
533 */
534 public function getTrackChanges() {
535 return $this->trackChanges;
536 }
537
538 /**
539 * {@inheritdoc}
540 */
541 protected function initializeIterator() {
542 $data = [
543 ['title' => 'foo'],
544 ['title' => 'bar'],
545 ['title' => 'iggy'],
546 ];
547 foreach ($data as $row) {
548 yield $row;
549 }
550 }
551
552 }