These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Wikibase\Client\Tests\Integration\Changes; |
||
4 | |||
5 | use ArrayIterator; |
||
6 | use InvalidArgumentException; |
||
7 | use MediaWiki\MediaWikiServices; |
||
8 | use MediaWikiIntegrationTestCase; |
||
9 | use Psr\Log\NullLogger; |
||
10 | use SiteLookup; |
||
11 | use Title; |
||
12 | use TitleFactory; |
||
13 | use Wikibase\Client\Changes\AffectedPagesFinder; |
||
14 | use Wikibase\Client\Changes\ChangeHandler; |
||
15 | use Wikibase\Client\Changes\ChangeRunCoalescer; |
||
16 | use Wikibase\Client\Changes\PageUpdater; |
||
17 | use Wikibase\Client\Usage\EntityUsage; |
||
18 | use Wikibase\Client\Usage\PageEntityUsages; |
||
19 | use Wikibase\Client\Usage\UsageLookup; |
||
20 | use Wikibase\DataModel\Entity\Item; |
||
21 | use Wikibase\DataModel\Entity\ItemId; |
||
22 | use Wikibase\Lib\Changes\Change; |
||
23 | use Wikibase\Lib\Changes\EntityChange; |
||
24 | use Wikibase\Lib\Store\SiteLinkLookup; |
||
25 | use Wikibase\Lib\Tests\Changes\TestChanges; |
||
26 | use Wikibase\Lib\Tests\MockRepository; |
||
27 | |||
28 | /** |
||
29 | * @covers \Wikibase\Client\Changes\ChangeHandler |
||
30 | * |
||
31 | * @group Wikibase |
||
32 | * @group WikibaseClient |
||
33 | * @group WikibaseChange |
||
34 | * |
||
35 | * @group Database |
||
36 | * |
||
37 | * @license GPL-2.0-or-later |
||
38 | * @author Daniel Kinzler |
||
39 | * @author Jeroen De Dauw < [email protected] > |
||
40 | */ |
||
41 | class ChangeHandlerTest extends MediaWikiIntegrationTestCase { |
||
42 | |||
43 | private function getAffectedPagesFinder( UsageLookup $usageLookup, TitleFactory $titleFactory ) { |
||
44 | // @todo: mock the finder directly |
||
45 | return new AffectedPagesFinder( |
||
46 | $usageLookup, |
||
47 | $titleFactory, |
||
48 | MediaWikiServices::getInstance()->getLinkBatchFactory(), |
||
49 | 'enwiki', |
||
50 | null, |
||
51 | false |
||
52 | ); |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * @return ChangeRunCoalescer |
||
57 | */ |
||
58 | private function getChangeRunCoalescer() { |
||
59 | $transformer = $this->getMockBuilder( ChangeRunCoalescer::class ) |
||
60 | ->disableOriginalConstructor() |
||
61 | ->getMock(); |
||
62 | |||
63 | $transformer->expects( $this->any() ) |
||
64 | ->method( 'transformChangeList' ) |
||
65 | ->will( $this->returnArgument( 0 ) ); |
||
66 | |||
67 | return $transformer; |
||
68 | } |
||
69 | |||
70 | private function getChangeHandler( |
||
71 | array $pageNamesPerItemId = [], |
||
72 | PageUpdater $updater = null |
||
73 | ) { |
||
74 | $siteLinkLookup = $this->getSiteLinkLookup( $pageNamesPerItemId ); |
||
75 | $usageLookup = $this->getUsageLookup( $siteLinkLookup ); |
||
76 | $titleFactory = $this->getTitleFactory( $pageNamesPerItemId ); |
||
77 | $affectedPagesFinder = $this->getAffectedPagesFinder( $usageLookup, $titleFactory ); |
||
78 | |||
79 | $handler = new ChangeHandler( |
||
80 | $affectedPagesFinder, |
||
81 | $titleFactory, |
||
82 | $updater ?: new MockPageUpdater(), |
||
83 | $this->getChangeRunCoalescer(), |
||
84 | $this->createMock( SiteLookup::class ), |
||
85 | new NullLogger(), |
||
86 | true |
||
87 | ); |
||
88 | |||
89 | return $handler; |
||
90 | } |
||
91 | |||
92 | /** |
||
93 | * @param array $pageNamesPerItemId |
||
94 | * |
||
95 | * @return SiteLinkLookup |
||
96 | */ |
||
97 | private function getSiteLinkLookup( array $pageNamesPerItemId ) { |
||
98 | $repo = new MockRepository(); |
||
99 | |||
100 | // entity 1, revision 11 |
||
101 | $entity1 = new Item( new ItemId( 'Q1' ) ); |
||
102 | $entity1->setLabel( 'en', 'one' ); |
||
103 | $repo->putEntity( $entity1, 11 ); |
||
104 | |||
105 | // entity 1, revision 12 |
||
106 | $entity1->setLabel( 'de', 'eins' ); |
||
107 | $repo->putEntity( $entity1, 12 ); |
||
108 | |||
109 | // entity 1, revision 13 |
||
110 | $entity1->setLabel( 'it', 'uno' ); |
||
111 | $repo->putEntity( $entity1, 13 ); |
||
112 | |||
113 | // entity 1, revision 1111 |
||
114 | $entity1->setDescription( 'en', 'the first' ); |
||
115 | $repo->putEntity( $entity1, 1111 ); |
||
116 | |||
117 | // entity 2, revision 21 |
||
118 | $entity1 = new Item( new ItemId( 'Q2' ) ); |
||
119 | $entity1->setLabel( 'en', 'two' ); |
||
120 | $repo->putEntity( $entity1, 21 ); |
||
121 | |||
122 | // entity 2, revision 22 |
||
123 | $entity1->setLabel( 'de', 'zwei' ); |
||
124 | $repo->putEntity( $entity1, 22 ); |
||
125 | |||
126 | // entity 2, revision 23 |
||
127 | $entity1->setLabel( 'it', 'due' ); |
||
128 | $repo->putEntity( $entity1, 23 ); |
||
129 | |||
130 | // entity 2, revision 1211 |
||
131 | $entity1->setDescription( 'en', 'the second' ); |
||
132 | $repo->putEntity( $entity1, 1211 ); |
||
133 | |||
134 | $this->updateMockRepository( $repo, $pageNamesPerItemId ); |
||
135 | |||
136 | return $repo; |
||
137 | } |
||
138 | |||
139 | public function provideHandleChanges() { |
||
140 | $empty = new Item( new ItemId( 'Q55668877' ) ); |
||
141 | |||
142 | $changeFactory = TestChanges::getEntityChangeFactory(); |
||
143 | $itemCreation = $changeFactory->newFromUpdate( EntityChange::ADD, null, $empty ); |
||
144 | $itemDeletion = $changeFactory->newFromUpdate( EntityChange::REMOVE, $empty, null ); |
||
145 | |||
146 | $itemCreation->setField( 'time', '20130101010101' ); |
||
147 | $itemDeletion->setField( 'time', '20130102020202' ); |
||
148 | |||
149 | return [ |
||
150 | [], |
||
151 | [ $itemCreation ], |
||
152 | [ $itemDeletion ], |
||
153 | [ $itemCreation, $itemDeletion ], |
||
154 | ]; |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * @dataProvider provideHandleChanges |
||
159 | */ |
||
160 | public function testHandleChanges( ...$changes ) { |
||
161 | $spy = (object)[ |
||
162 | 'handleChangeCallCount' => 0, |
||
163 | 'handleChangesCallCount' => 0, |
||
164 | ]; |
||
165 | |||
166 | $testHooks = [ |
||
167 | 'WikibaseHandleChange' => [ function( Change $change ) use ( $spy ) { |
||
168 | $spy->handleChangeCallCount++; |
||
169 | return true; |
||
170 | } ], |
||
171 | 'WikibaseHandleChanges' => [ function( array $changes ) use ( $spy ) { |
||
172 | $spy->handleChangesCallCount++; |
||
173 | return true; |
||
174 | } ] |
||
175 | ]; |
||
176 | |||
177 | $this->mergeMwGlobalArrayValue( 'wgHooks', $testHooks ); |
||
178 | |||
179 | $changeHandler = $this->getChangeHandler(); |
||
180 | $changeHandler->handleChanges( $changes ); |
||
181 | |||
182 | $this->assertSame( count( $changes ), $spy->handleChangeCallCount ); |
||
183 | $this->assertSame( 1, $spy->handleChangesCallCount ); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * Returns a map of fake local page IDs to the corresponding local page names. |
||
188 | * The fake page IDs are the IDs of the items that have a sitelink to the |
||
189 | * respective page on the local wiki: |
||
190 | * |
||
191 | * Example: If Q100 has a link enwiki => 'Emmy', |
||
192 | * then 100 => 'Emmy' will be in the map returned by this method. |
||
193 | * |
||
194 | * @param array[] $pageNamesPerItemId Assoc array mapping entity IDs to lists of sitelinks. |
||
195 | * |
||
196 | * @return string[] |
||
197 | */ |
||
198 | private function getFakePageIdMap( array $pageNamesPerItemId ) { |
||
199 | $titlesByPageId = []; |
||
200 | $siteId = 'enwiki'; |
||
201 | |||
202 | foreach ( $pageNamesPerItemId as $idString => $pageNames ) { |
||
203 | $itemId = new ItemId( $idString ); |
||
204 | |||
205 | // If $links[0] is set, it's considered a link to the local wiki. |
||
206 | // The index 0 is effectively an alias for $siteId; |
||
207 | if ( isset( $pageNames[0] ) ) { |
||
208 | $pageNames[$siteId] = $pageNames[0]; |
||
209 | } |
||
210 | |||
211 | if ( isset( $pageNames[$siteId] ) ) { |
||
212 | $pageId = $itemId->getNumericId(); |
||
213 | $titlesByPageId[$pageId] = $pageNames[$siteId]; |
||
214 | } |
||
215 | } |
||
216 | |||
217 | return $titlesByPageId; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Title factory, using spoofed local page ids that correspond to the ids of items linked to |
||
222 | * the respective page (see getUsageLookup). |
||
223 | * |
||
224 | * @param array[] $pageNamesPerItemId Assoc array mapping entity IDs to lists of sitelinks. |
||
225 | * |
||
226 | * @return TitleFactory |
||
227 | */ |
||
228 | private function getTitleFactory( array $pageNamesPerItemId ) { |
||
229 | $titlesById = $this->getFakePageIdMap( $pageNamesPerItemId ); |
||
230 | $pageIdsByTitle = array_flip( $titlesById ); |
||
231 | |||
232 | $titleFactory = $this->createMock( TitleFactory::class ); |
||
233 | |||
234 | $titleFactory->method( 'newFromIDs' ) |
||
235 | ->willReturnCallback( function ( array $ids ) use ( $titlesById ) { |
||
236 | $titles = []; |
||
237 | foreach ( $ids as $id ) { |
||
238 | if ( isset( $titlesById[$id] ) ) { |
||
239 | $title = Title::newFromText( $titlesById[$id] ); |
||
240 | $title->resetArticleID( $id ); |
||
241 | $titles[] = $title; |
||
242 | } else { |
||
243 | throw new InvalidArgumentException( 'Unknown ID: ' . $id ); |
||
244 | } |
||
245 | } |
||
246 | return $titles; |
||
247 | } ); |
||
248 | |||
249 | $titleFactory->expects( $this->any() ) |
||
250 | ->method( 'newFromText' ) |
||
251 | ->will( $this->returnCallback( function( $text, $defaultNs = \NS_MAIN ) use ( $pageIdsByTitle ) { |
||
252 | $title = Title::newFromText( $text, $defaultNs ); |
||
253 | |||
254 | if ( !$title ) { |
||
255 | return $title; |
||
256 | } |
||
257 | |||
258 | if ( isset( $pageIdsByTitle[$text] ) ) { |
||
259 | $title->resetArticleID( $pageIdsByTitle[$text] ); |
||
260 | } else { |
||
261 | throw new InvalidArgumentException( 'Unknown title text: ' . $text ); |
||
262 | } |
||
263 | |||
264 | return $title; |
||
265 | } ) ); |
||
266 | |||
267 | return $titleFactory; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Returns a usage lookup based on $siteLinklookup. |
||
272 | * Local page IDs are spoofed using the numeric item ID as the local page ID. |
||
273 | * |
||
274 | * @param SiteLinkLookup $siteLinkLookup |
||
275 | * |
||
276 | * @return UsageLookup |
||
277 | */ |
||
278 | private function getUsageLookup( SiteLinkLookup $siteLinkLookup ) { |
||
279 | $usageLookup = $this->createMock( UsageLookup::class ); |
||
280 | $usageLookup->expects( $this->any() ) |
||
281 | ->method( 'getPagesUsing' ) |
||
282 | ->will( $this->returnCallback( |
||
283 | function( $ids, $aspects ) use ( $siteLinkLookup ) { |
||
284 | $pages = []; |
||
285 | |||
286 | foreach ( $ids as $id ) { |
||
287 | if ( !( $id instanceof ItemId ) ) { |
||
288 | continue; |
||
289 | } |
||
290 | |||
291 | $links = $siteLinkLookup->getSiteLinksForItem( $id ); |
||
292 | foreach ( $links as $link ) { |
||
293 | if ( $link->getSiteId() === 'enwiki' ) { |
||
294 | // we use the numeric item id as the fake page id of the local page! |
||
295 | $usedAspects = array_intersect( |
||
296 | [ EntityUsage::SITELINK_USAGE, EntityUsage::LABEL_USAGE . '.en' ], |
||
297 | $aspects |
||
298 | ); |
||
299 | if ( !$usedAspects ) { |
||
0 ignored issues
–
show
|
|||
300 | continue; |
||
301 | } |
||
302 | $usages = []; |
||
303 | foreach ( $usedAspects as $aspect ) { |
||
304 | $usages[] = new EntityUsage( |
||
305 | $id, |
||
306 | EntityUsage::splitAspectKey( $aspect )[0], |
||
307 | EntityUsage::splitAspectKey( $aspect )[1] |
||
308 | ); |
||
309 | } |
||
310 | $pages[] = new PageEntityUsages( |
||
311 | $id->getNumericId(), |
||
312 | $usages |
||
313 | ); |
||
314 | } |
||
315 | } |
||
316 | } |
||
317 | |||
318 | return new ArrayIterator( $pages ); |
||
319 | } ) ); |
||
320 | |||
321 | return $usageLookup; |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * @param MockRepository $mockRepository |
||
326 | * @param array $pageNamesPerItemId Associative array of item id string => either Item object |
||
327 | * or array of site id => page name. |
||
328 | */ |
||
329 | private function updateMockRepository( MockRepository $mockRepository, array $pageNamesPerItemId ) { |
||
330 | foreach ( $pageNamesPerItemId as $idString => $pageNames ) { |
||
331 | if ( is_array( $pageNames ) ) { |
||
332 | $item = new Item( new ItemId( $idString ) ); |
||
333 | |||
334 | foreach ( $pageNames as $siteId => $pageName ) { |
||
335 | if ( !is_string( $siteId ) ) { |
||
336 | $siteId = 'enwiki'; |
||
337 | } |
||
338 | |||
339 | $item->getSiteLinkList()->addNewSiteLink( $siteId, $pageName ); |
||
340 | } |
||
341 | } else { |
||
342 | $item = $pageNames; |
||
343 | } |
||
344 | |||
345 | $mockRepository->putEntity( $item ); |
||
346 | } |
||
347 | } |
||
348 | |||
349 | public function provideHandleChange() { |
||
350 | $changes = TestChanges::getChanges(); |
||
351 | $userEmmy2 = Title::newFromText( 'User:Emmy2' )->getPrefixedText(); |
||
352 | |||
353 | $empty = [ |
||
354 | 'scheduleRefreshLinks' => [], |
||
355 | 'purgeWebCache' => [], |
||
356 | 'injectRCRecord' => [], |
||
357 | ]; |
||
358 | |||
359 | $emmy2PurgeParser = [ |
||
360 | 'scheduleRefreshLinks' => [ 'Emmy2' => true ], |
||
361 | 'purgeWebCache' => [ 'Emmy2' => true ], |
||
362 | 'injectRCRecord' => [ 'Emmy2' => true ], |
||
363 | ]; |
||
364 | |||
365 | $userEmmy2PurgeParser = [ |
||
366 | 'scheduleRefreshLinks' => [ $userEmmy2 => true ], |
||
367 | 'purgeWebCache' => [ $userEmmy2 => true ], |
||
368 | 'injectRCRecord' => [ $userEmmy2 => true ], |
||
369 | ]; |
||
370 | |||
371 | $emmyUpdateLinks = [ |
||
372 | 'scheduleRefreshLinks' => [ 'Emmy' => true ], |
||
373 | 'purgeWebCache' => [ 'Emmy' => true ], |
||
374 | 'injectRCRecord' => [ 'Emmy' => true ], |
||
375 | ]; |
||
376 | |||
377 | $emmy2UpdateLinks = [ |
||
378 | 'scheduleRefreshLinks' => [ 'Emmy2' => true ], |
||
379 | 'purgeWebCache' => [ 'Emmy2' => true ], |
||
380 | 'injectRCRecord' => [ 'Emmy2' => true ], |
||
381 | ]; |
||
382 | |||
383 | $emmy2UpdateAll = [ |
||
384 | 'scheduleRefreshLinks' => [ 'Emmy2' => true ], |
||
385 | 'purgeWebCache' => [ 'Emmy2' => true ], |
||
386 | 'injectRCRecord' => [ 'Emmy2' => true ], |
||
387 | ]; |
||
388 | |||
389 | return [ |
||
390 | [ // #0 |
||
391 | $changes['property-creation'], |
||
392 | [ 'Q100' => [] ], |
||
393 | $empty |
||
394 | ], |
||
395 | [ // #1 |
||
396 | $changes['property-deletion'], |
||
397 | [ 'Q100' => [] ], |
||
398 | $empty |
||
399 | ], |
||
400 | [ // #2 |
||
401 | $changes['property-set-label'], |
||
402 | [ 'Q100' => [] ], |
||
403 | $empty |
||
404 | ], |
||
405 | |||
406 | [ // #3 |
||
407 | $changes['item-creation'], |
||
408 | [ 'Q100' => [] ], |
||
409 | $empty |
||
410 | ], |
||
411 | [ // #4 |
||
412 | $changes['item-deletion'], |
||
413 | [ 'Q100' => [] ], |
||
414 | $empty |
||
415 | ], |
||
416 | [ // #5 |
||
417 | $changes['item-deletion-linked'], |
||
418 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
419 | $emmy2UpdateAll |
||
420 | ], |
||
421 | |||
422 | [ // #6 |
||
423 | $changes['set-de-label'], |
||
424 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
425 | $empty, // For the dummy page, only label and sitelink usage is defined. |
||
426 | ], |
||
427 | [ // #7 |
||
428 | $changes['set-en-label'], |
||
429 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
430 | $emmy2PurgeParser |
||
431 | ], |
||
432 | [ // #8 |
||
433 | $changes['set-en-label'], |
||
434 | [ 'Q100' => [ 'enwiki' => 'User:Emmy2' ] ], // user namespace |
||
435 | $userEmmy2PurgeParser |
||
436 | ], |
||
437 | [ // #9 |
||
438 | $changes['set-en-aliases'], |
||
439 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
440 | $empty, // For the dummy page, only label and sitelink usage is defined. |
||
441 | ], |
||
442 | |||
443 | [ // #10 |
||
444 | $changes['add-claim'], |
||
445 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
446 | $empty // statements are ignored |
||
447 | ], |
||
448 | [ // #11 |
||
449 | $changes['remove-claim'], |
||
450 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
451 | $empty // statements are ignored |
||
452 | ], |
||
453 | |||
454 | [ // #12 |
||
455 | $changes['set-dewiki-sitelink'], |
||
456 | [ 'Q100' => [] ], |
||
457 | $empty // not yet linked |
||
458 | ], |
||
459 | [ // #13 |
||
460 | $changes['set-enwiki-sitelink'], |
||
461 | [ 'Q100' => [ 'enwiki' => 'Emmy' ] ], |
||
462 | $emmyUpdateLinks |
||
463 | ], |
||
464 | |||
465 | [ // #14 |
||
466 | $changes['change-dewiki-sitelink'], |
||
467 | [ 'Q100' => [ 'enwiki' => 'Emmy' ] ], |
||
468 | $emmyUpdateLinks |
||
469 | ], |
||
470 | [ // #15 |
||
471 | $changes['change-enwiki-sitelink'], |
||
472 | [ 'Q100' => [ 'enwiki' => 'Emmy' ], 'Q200' => [ 'enwiki' => 'Emmy2' ] ], |
||
473 | [ |
||
474 | 'scheduleRefreshLinks' => [ 'Emmy' => true, 'Emmy2' => true ], |
||
475 | 'purgeWebCache' => [ 'Emmy' => true, 'Emmy2' => true ], |
||
476 | 'injectRCRecord' => [ 'Emmy' => true, 'Emmy2' => true ], |
||
477 | ] |
||
478 | ], |
||
479 | [ // #16 |
||
480 | $changes['change-enwiki-sitelink-badges'], |
||
481 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
482 | $emmy2UpdateLinks |
||
483 | ], |
||
484 | |||
485 | [ // #17 |
||
486 | $changes['remove-dewiki-sitelink'], |
||
487 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
488 | $emmy2UpdateLinks |
||
489 | ], |
||
490 | [ // #18 |
||
491 | $changes['remove-enwiki-sitelink'], |
||
492 | [ 'Q100' => [ 'enwiki' => 'Emmy2' ] ], |
||
493 | $emmy2UpdateLinks |
||
494 | ], |
||
495 | ]; |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * @dataProvider provideHandleChange |
||
500 | */ |
||
501 | public function testHandleChange( EntityChange $change, array $pageNamesPerItemId, array $expected ) { |
||
502 | $updater = new MockPageUpdater(); |
||
503 | $handler = $this->getChangeHandler( $pageNamesPerItemId, $updater ); |
||
504 | |||
505 | $handler->handleChange( $change ); |
||
506 | $updates = $updater->getUpdates(); |
||
507 | |||
508 | $this->assertSameSize( $expected, $updates ); |
||
509 | |||
510 | foreach ( $expected as $k => $exp ) { |
||
511 | $up = $updates[$k]; |
||
512 | $this->assertSame( array_keys( $exp ), array_keys( $up ), $k ); |
||
513 | } |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * @param int|null $id |
||
518 | * @param string $type |
||
519 | * @param string $objectId |
||
520 | * @param array $info |
||
521 | * |
||
522 | * @return EntityChange |
||
523 | */ |
||
524 | private function newChange( $id, $type, $objectId, $info = [] ) { |
||
525 | $fields = [ |
||
526 | 'id' => $id, |
||
527 | 'time' => '20121212121212', |
||
528 | 'type' => $type, |
||
529 | 'objectid' => $objectId, |
||
530 | 'info' => $info, |
||
531 | ]; |
||
532 | |||
533 | return new EntityChange( $fields ); |
||
534 | } |
||
535 | |||
536 | public function provideHandleChange_rootJobParams() { |
||
537 | $ids = [ 18, 19, 17 ]; // note: provide these out of order, to check canonical sorting! |
||
538 | $regularChange = $this->newChange( 17, 'x~y', 'Q100', [] ); |
||
539 | $coalescedChange = $this->newChange( 0, 'x~y', 'Q100', [ 'change-ids' => $ids ] ); |
||
540 | $strangeChange = $this->newChange( 0, 'x~y', 'Q100', [ 'kittens' => 13 ] ); |
||
541 | |||
542 | $q100 = new ItemId( 'Q100' ); |
||
543 | $usages = [ // note: provide these out of order, to check canonical sorting! |
||
544 | 102 => new PageEntityUsages( 102, [ new EntityUsage( $q100, 'X' ) ] ), |
||
545 | 101 => new PageEntityUsages( 101, [ new EntityUsage( $q100, 'X' ) ] ), |
||
546 | ]; |
||
547 | |||
548 | $titleBatchHash = 'f0b873699a63c858667e54cd071f7d9209faeda1'; |
||
549 | $strangeHash = 'cadbb4899603593164f06a4754f597fdcb2c07b4'; |
||
550 | |||
551 | $regularRootJobParams = [ |
||
552 | 'purgeWebCache' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
553 | 'scheduleRefreshLinks' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
554 | 'injectRCRecord' => [ 'rootJobSignature' => "title-batch:$titleBatchHash&change-id:17" ], |
||
555 | ]; |
||
556 | |||
557 | $coalescedRootJobParams = [ |
||
558 | 'purgeWebCache' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
559 | 'scheduleRefreshLinks' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
560 | 'injectRCRecord' => [ 'rootJobSignature' => "title-batch:$titleBatchHash&change-batch:17,18,19" ], |
||
561 | ]; |
||
562 | |||
563 | $strangeRootJobParams = [ |
||
564 | 'purgeWebCache' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
565 | 'scheduleRefreshLinks' => [ 'rootJobSignature' => "title-batch:$titleBatchHash" ], |
||
566 | 'injectRCRecord' => [ 'rootJobSignature' => "title-batch:$titleBatchHash&change-hash:$strangeHash" ], |
||
567 | ]; |
||
568 | |||
569 | return [ |
||
570 | [ $regularChange, $usages, $regularRootJobParams ], |
||
571 | [ $coalescedChange, $usages, $coalescedRootJobParams ], |
||
572 | [ $strangeChange, $usages, $strangeRootJobParams ], |
||
573 | ]; |
||
574 | } |
||
575 | |||
576 | /** |
||
577 | * @dataProvider provideHandleChange_rootJobParams |
||
578 | */ |
||
579 | public function testHandleChange_rootJobParams( |
||
580 | EntityChange $change, |
||
581 | array $usages, |
||
582 | array $expectedRootJobParams |
||
583 | ) { |
||
584 | $updater = new MockPageUpdater(); |
||
585 | |||
586 | $affectedPagesFinder = $this->getMockBuilder( AffectedPagesFinder::class ) |
||
587 | ->disableOriginalConstructor() |
||
588 | ->getMock(); |
||
589 | $affectedPagesFinder->expects( $this->any() ) |
||
590 | ->method( 'getAffectedUsagesByPage' ) |
||
591 | ->will( $this->returnValue( $usages ) ); |
||
592 | |||
593 | $titleFactory = $this->getMockBuilder( TitleFactory::class ) |
||
594 | ->disableOriginalConstructor() |
||
595 | ->getMock(); |
||
596 | $titleFactory->method( 'newFromIDs' ) |
||
597 | ->willReturnCallback( function ( array $ids ) { |
||
598 | // NOTE: the fake title construction influences the expected hash values |
||
599 | // defined in provideHandleChange_rootJobParams! |
||
600 | $titles = []; |
||
601 | foreach ( $ids as $id ) { |
||
602 | $title = Title::makeTitle( NS_MAIN, 'Page_No_' . $id ); |
||
603 | $title->resetArticleID( $id ); |
||
604 | $titles[] = $title; |
||
605 | } |
||
606 | return $titles; |
||
607 | } ); |
||
608 | |||
609 | $handler = new ChangeHandler( |
||
610 | $affectedPagesFinder, |
||
611 | $titleFactory, |
||
612 | $updater, |
||
613 | $this->getChangeRunCoalescer(), |
||
614 | $this->createMock( SiteLookup::class ), |
||
615 | new NullLogger() |
||
616 | ); |
||
617 | |||
618 | $inputRootJobParams = [ 'rootJobTimestamp' => '20171122040506' ]; |
||
619 | |||
620 | $handler->handleChange( $change, $inputRootJobParams ); |
||
621 | $actualRootJobParams = $updater->getRootJobParams(); |
||
622 | |||
623 | $this->assertSameSize( $expectedRootJobParams, $actualRootJobParams ); |
||
624 | |||
625 | foreach ( $expectedRootJobParams as $k => $exp ) { |
||
626 | $act = $actualRootJobParams[$k]; |
||
627 | if ( $k !== 'scheduleRefreshLinks' ) { |
||
628 | $this->assertSame( '20171122040506', $act['rootJobTimestamp'], "$k/rootJobTimestamp" ); |
||
629 | } |
||
630 | $this->assertSame( $exp['rootJobSignature'], $act['rootJobSignature'], "$k/rootJobSignature" ); |
||
631 | } |
||
632 | } |
||
633 | |||
634 | } |
||
635 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.