Mercurial > hg > isophonics-drupal-site
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 } |