Merge "Remove incorrect timezone conversion from date parameters"
[lhc/web/wiklou.git] / tests / phpunit / includes / Storage / DerivedPageDataUpdaterTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Storage;
4
5 use CommentStoreComment;
6 use Content;
7 use LinksUpdate;
8 use MediaWiki\MediaWikiServices;
9 use MediaWiki\Storage\DerivedPageDataUpdater;
10 use MediaWiki\Storage\MutableRevisionRecord;
11 use MediaWiki\Storage\MutableRevisionSlots;
12 use MediaWiki\Storage\RevisionRecord;
13 use MediaWiki\Storage\RevisionSlotsUpdate;
14 use MediaWiki\Storage\SlotRecord;
15 use MediaWikiTestCase;
16 use Title;
17 use User;
18 use Wikimedia\TestingAccessWrapper;
19 use WikiPage;
20 use WikitextContent;
21
22 /**
23 * @group Database
24 *
25 * @covers MediaWiki\Storage\DerivedPageDataUpdater
26 */
27 class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
28
29 /**
30 * @param string $title
31 *
32 * @return Title
33 */
34 private function getTitle( $title ) {
35 return Title::makeTitleSafe( $this->getDefaultWikitextNS(), $title );
36 }
37
38 /**
39 * @param string|Title $title
40 *
41 * @return WikiPage
42 */
43 private function getPage( $title ) {
44 $title = ( $title instanceof Title ) ? $title : $this->getTitle( $title );
45
46 return WikiPage::factory( $title );
47 }
48
49 /**
50 * @param string|Title|WikiPage $page
51 *
52 * @return DerivedPageDataUpdater
53 */
54 private function getDerivedPageDataUpdater( $page, RevisionRecord $rec = null ) {
55 if ( is_string( $page ) || $page instanceof Title ) {
56 $page = $this->getPage( $page );
57 }
58
59 $page = TestingAccessWrapper::newFromObject( $page );
60 return $page->getDerivedDataUpdater( null, $rec );
61 }
62
63 /**
64 * Creates a revision in the database.
65 *
66 * @param WikiPage $page
67 * @param $summary
68 * @param null|string|Content $content
69 *
70 * @return RevisionRecord|null
71 */
72 private function createRevision( WikiPage $page, $summary, $content = null ) {
73 $user = $this->getTestUser()->getUser();
74 $comment = CommentStoreComment::newUnsavedComment( $summary );
75
76 if ( !$content instanceof Content ) {
77 $content = new WikitextContent( $content ?? $summary );
78 }
79
80 $this->getDerivedPageDataUpdater( $page ); // flush cached instance before.
81
82 $updater = $page->newPageUpdater( $user );
83 $updater->setContent( 'main', $content );
84 $rev = $updater->saveRevision( $comment );
85
86 $this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
87 return $rev;
88 }
89
90 // TODO: test setArticleCountMethod() and isCountable();
91 // TODO: test isRedirect() and wasRedirect()
92
93 /**
94 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOptions()
95 */
96 public function testGetCanonicalParserOptions() {
97 $user = $this->getTestUser()->getUser();
98 $page = $this->getPage( __METHOD__ );
99
100 $parentRev = $this->createRevision( $page, 'first' );
101
102 $mainContent = new WikitextContent( 'Lorem ipsum' );
103
104 $update = new RevisionSlotsUpdate();
105 $update->modifyContent( 'main', $mainContent );
106 $updater = $this->getDerivedPageDataUpdater( $page );
107 $updater->prepareContent( $user, $update, false );
108
109 $options1 = $updater->getCanonicalParserOptions();
110 $this->assertSame( MediaWikiServices::getInstance()->getContentLanguage(),
111 $options1->getUserLangObj() );
112
113 $speculativeId = call_user_func( $options1->getSpeculativeRevIdCallback(), $page->getTitle() );
114 $this->assertSame( $parentRev->getId() + 1, $speculativeId );
115
116 $rev = $this->makeRevision(
117 $page->getTitle(),
118 $update,
119 $user,
120 $parentRev->getId() + 7,
121 $parentRev->getId()
122 );
123 $updater->prepareUpdate( $rev );
124
125 $options2 = $updater->getCanonicalParserOptions();
126 $this->assertNotSame( $options1, $options2 );
127
128 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
129 $this->assertSame( $rev->getId(), $currentRev->getId() );
130 }
131
132 /**
133 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::grabCurrentRevision()
134 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
135 */
136 public function testGrabCurrentRevision() {
137 $page = $this->getPage( __METHOD__ );
138
139 $updater0 = $this->getDerivedPageDataUpdater( $page );
140 $this->assertNull( $updater0->grabCurrentRevision() );
141 $this->assertFalse( $updater0->pageExisted() );
142
143 $rev1 = $this->createRevision( $page, 'first' );
144 $updater1 = $this->getDerivedPageDataUpdater( $page );
145 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
146 $this->assertFalse( $updater0->pageExisted() );
147 $this->assertTrue( $updater1->pageExisted() );
148
149 $rev2 = $this->createRevision( $page, 'second' );
150 $updater2 = $this->getDerivedPageDataUpdater( $page );
151 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
152 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
153 }
154
155 /**
156 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
157 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isContentPrepared()
158 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
159 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
160 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
161 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
162 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
163 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
164 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
165 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
166 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
167 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
168 */
169 public function testPrepareContent() {
170 $user = $this->getTestUser()->getUser();
171 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
172
173 $this->assertFalse( $updater->isContentPrepared() );
174
175 // TODO: test stash
176 // TODO: MCR: Test multiple slots. Test slot removal.
177 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
178 $auxContent = new WikitextContent( 'inherited ~~~ content' );
179 $auxSlot = SlotRecord::newSaved(
180 10, 7, 'tt:7',
181 SlotRecord::newUnsaved( 'aux', $auxContent )
182 );
183
184 $update = new RevisionSlotsUpdate();
185 $update->modifyContent( 'main', $mainContent );
186 $update->modifySlot( SlotRecord::newInherited( $auxSlot ) );
187 // TODO: MCR: test removing slots!
188
189 $updater->prepareContent( $user, $update, false );
190
191 // second be ok to call again with the same params
192 $updater->prepareContent( $user, $update, false );
193
194 $this->assertNull( $updater->grabCurrentRevision() );
195 $this->assertTrue( $updater->isContentPrepared() );
196 $this->assertFalse( $updater->isUpdatePrepared() );
197 $this->assertFalse( $updater->pageExisted() );
198 $this->assertTrue( $updater->isCreation() );
199 $this->assertTrue( $updater->isChange() );
200 $this->assertTrue( $updater->isContentPublic() );
201
202 $this->assertEquals( [ 'main', 'aux' ], $updater->getSlots()->getSlotRoles() );
203 $this->assertEquals( [ 'main' ], array_keys( $updater->getSlots()->getOriginalSlots() ) );
204 $this->assertEquals( [ 'aux' ], array_keys( $updater->getSlots()->getInheritedSlots() ) );
205 $this->assertEquals( [ 'main', 'aux' ], $updater->getModifiedSlotRoles() );
206 $this->assertEquals( [ 'main', 'aux' ], $updater->getTouchedSlotRoles() );
207
208 $mainSlot = $updater->getRawSlot( 'main' );
209 $this->assertInstanceOf( SlotRecord::class, $mainSlot );
210 $this->assertNotContains( '~~~', $mainSlot->getContent()->serialize(), 'PST should apply.' );
211 $this->assertContains( $user->getName(), $mainSlot->getContent()->serialize() );
212
213 $auxSlot = $updater->getRawSlot( 'aux' );
214 $this->assertInstanceOf( SlotRecord::class, $auxSlot );
215 $this->assertContains( '~~~', $auxSlot->getContent()->serialize(), 'No PST should apply.' );
216
217 $mainOutput = $updater->getCanonicalParserOutput();
218 $this->assertContains( 'first', $mainOutput->getText() );
219 $this->assertContains( '<a ', $mainOutput->getText() );
220 $this->assertNotEmpty( $mainOutput->getLinks() );
221
222 $canonicalOutput = $updater->getCanonicalParserOutput();
223 $this->assertContains( 'first', $canonicalOutput->getText() );
224 $this->assertContains( '<a ', $canonicalOutput->getText() );
225 $this->assertNotEmpty( $canonicalOutput->getLinks() );
226 }
227
228 /**
229 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
230 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
231 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
232 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
233 */
234 public function testPrepareContentInherit() {
235 $user = $this->getTestUser()->getUser();
236 $page = $this->getPage( __METHOD__ );
237
238 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
239 $mainContent2 = new WikitextContent( 'second' );
240
241 $this->createRevision( $page, 'first', $mainContent1 );
242
243 $update = new RevisionSlotsUpdate();
244 $update->modifyContent( 'main', $mainContent1 );
245 $updater1 = $this->getDerivedPageDataUpdater( $page );
246 $updater1->prepareContent( $user, $update, false );
247
248 $this->assertNotNull( $updater1->grabCurrentRevision() );
249 $this->assertTrue( $updater1->isContentPrepared() );
250 $this->assertTrue( $updater1->pageExisted() );
251 $this->assertFalse( $updater1->isCreation() );
252 $this->assertFalse( $updater1->isChange() );
253
254 // TODO: MCR: test inheritance from parent
255 $update = new RevisionSlotsUpdate();
256 $update->modifyContent( 'main', $mainContent2 );
257 $updater2 = $this->getDerivedPageDataUpdater( $page );
258 $updater2->prepareContent( $user, $update, false );
259
260 $this->assertFalse( $updater2->isCreation() );
261 $this->assertTrue( $updater2->isChange() );
262 }
263
264 // TODO: test failure of prepareContent() when called again...
265 // - with different user
266 // - with different update
267 // - after calling prepareUpdate()
268
269 /**
270 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
271 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isUpdatePrepared()
272 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
273 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
274 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
275 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
276 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
277 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
278 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
279 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
280 */
281 public function testPrepareUpdate() {
282 $page = $this->getPage( __METHOD__ );
283
284 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
285 $rev1 = $this->createRevision( $page, 'first', $mainContent1 );
286 $updater1 = $this->getDerivedPageDataUpdater( $page, $rev1 );
287
288 $options = []; // TODO: test *all* the options...
289 $updater1->prepareUpdate( $rev1, $options );
290
291 $this->assertTrue( $updater1->isUpdatePrepared() );
292 $this->assertTrue( $updater1->isContentPrepared() );
293 $this->assertTrue( $updater1->isCreation() );
294 $this->assertTrue( $updater1->isChange() );
295 $this->assertTrue( $updater1->isContentPublic() );
296
297 $this->assertEquals( [ 'main' ], $updater1->getSlots()->getSlotRoles() );
298 $this->assertEquals( [ 'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
299 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
300 $this->assertEquals( [ 'main' ], $updater1->getModifiedSlotRoles() );
301 $this->assertEquals( [ 'main' ], $updater1->getTouchedSlotRoles() );
302
303 // TODO: MCR: test multiple slots, test slot removal!
304
305 $this->assertInstanceOf( SlotRecord::class, $updater1->getRawSlot( 'main' ) );
306 $this->assertNotContains( '~~~~', $updater1->getRawContent( 'main' )->serialize() );
307
308 $mainOutput = $updater1->getCanonicalParserOutput();
309 $this->assertContains( 'first', $mainOutput->getText() );
310 $this->assertContains( '<a ', $mainOutput->getText() );
311 $this->assertNotEmpty( $mainOutput->getLinks() );
312
313 $canonicalOutput = $updater1->getCanonicalParserOutput();
314 $this->assertContains( 'first', $canonicalOutput->getText() );
315 $this->assertContains( '<a ', $canonicalOutput->getText() );
316 $this->assertNotEmpty( $canonicalOutput->getLinks() );
317
318 $mainContent2 = new WikitextContent( 'second' );
319 $rev2 = $this->createRevision( $page, 'second', $mainContent2 );
320 $updater2 = $this->getDerivedPageDataUpdater( $page, $rev2 );
321
322 $options = []; // TODO: test *all* the options...
323 $updater2->prepareUpdate( $rev2, $options );
324
325 $this->assertFalse( $updater2->isCreation() );
326 $this->assertTrue( $updater2->isChange() );
327
328 $canonicalOutput = $updater2->getCanonicalParserOutput();
329 $this->assertContains( 'second', $canonicalOutput->getText() );
330 }
331
332 /**
333 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
334 */
335 public function testPrepareUpdateReusesParserOutput() {
336 $user = $this->getTestUser()->getUser();
337 $page = $this->getPage( __METHOD__ );
338
339 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
340
341 $update = new RevisionSlotsUpdate();
342 $update->modifyContent( 'main', $mainContent1 );
343 $updater = $this->getDerivedPageDataUpdater( $page );
344 $updater->prepareContent( $user, $update, false );
345
346 $mainOutput = $updater->getSlotParserOutput( 'main' );
347 $canonicalOutput = $updater->getCanonicalParserOutput();
348
349 $rev = $this->createRevision( $page, 'first', $mainContent1 );
350
351 $options = []; // TODO: test *all* the options...
352 $updater->prepareUpdate( $rev, $options );
353
354 $this->assertTrue( $updater->isUpdatePrepared() );
355 $this->assertTrue( $updater->isContentPrepared() );
356
357 $this->assertSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
358 $this->assertSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
359 }
360
361 /**
362 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
363 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
364 */
365 public function testPrepareUpdateOutputReset() {
366 $user = $this->getTestUser()->getUser();
367 $page = $this->getPage( __METHOD__ );
368
369 $mainContent1 = new WikitextContent( 'first --{{REVISIONID}}--' );
370
371 $update = new RevisionSlotsUpdate();
372 $update->modifyContent( 'main', $mainContent1 );
373 $updater = $this->getDerivedPageDataUpdater( $page );
374 $updater->prepareContent( $user, $update, false );
375
376 $mainOutput = $updater->getSlotParserOutput( 'main' );
377 $canonicalOutput = $updater->getCanonicalParserOutput();
378
379 // prevent optimization on matching speculative ID
380 $mainOutput->setSpeculativeRevIdUsed( 0 );
381 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
382
383 $rev = $this->createRevision( $page, 'first', $mainContent1 );
384
385 $options = []; // TODO: test *all* the options...
386 $updater->prepareUpdate( $rev, $options );
387
388 $this->assertTrue( $updater->isUpdatePrepared() );
389 $this->assertTrue( $updater->isContentPrepared() );
390
391 // ParserOutput objects should have been flushed.
392 $this->assertNotSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
393 $this->assertNotSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
394
395 $html = $updater->getCanonicalParserOutput()->getText();
396 $this->assertContains( '--' . $rev->getId() . '--', $html );
397
398 // TODO: MCR: ensure that when the main slot uses {{REVISIONID}} but another slot is
399 // updated, the main slot is still re-rendered!
400 }
401
402 // TODO: test failure of prepareUpdate() when called again with a different revision
403 // TODO: test failure of prepareUpdate() on inconsistency with prepareContent.
404
405 /**
406 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
407 */
408 public function testGetPreparedEditAfterPrepareContent() {
409 $user = $this->getTestUser()->getUser();
410
411 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
412 $update = new RevisionSlotsUpdate();
413 $update->modifyContent( 'main', $mainContent );
414
415 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
416 $updater->prepareContent( $user, $update, false );
417
418 $canonicalOutput = $updater->getCanonicalParserOutput();
419
420 $preparedEdit = $updater->getPreparedEdit();
421 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
422 $this->assertSame( $canonicalOutput, $preparedEdit->output );
423 $this->assertSame( $mainContent, $preparedEdit->newContent );
424 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent );
425 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
426 $this->assertSame( null, $preparedEdit->revid );
427 }
428
429 /**
430 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
431 */
432 public function testGetPreparedEditAfterPrepareUpdate() {
433 $page = $this->getPage( __METHOD__ );
434
435 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
436 $update = new MutableRevisionSlots();
437 $update->setContent( 'main', $mainContent );
438
439 $rev = $this->createRevision( $page, __METHOD__ );
440
441 $updater = $this->getDerivedPageDataUpdater( $page );
442 $updater->prepareUpdate( $rev );
443
444 $canonicalOutput = $updater->getCanonicalParserOutput();
445
446 $preparedEdit = $updater->getPreparedEdit();
447 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
448 $this->assertSame( $canonicalOutput, $preparedEdit->output );
449 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent );
450 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
451 $this->assertSame( $rev->getId(), $preparedEdit->revid );
452 }
453
454 public function testGetSecondaryDataUpdatesAfterPrepareContent() {
455 $user = $this->getTestUser()->getUser();
456 $page = $this->getPage( __METHOD__ );
457 $this->createRevision( $page, __METHOD__ );
458
459 $mainContent1 = new WikitextContent( 'first' );
460
461 $update = new RevisionSlotsUpdate();
462 $update->modifyContent( 'main', $mainContent1 );
463 $updater = $this->getDerivedPageDataUpdater( $page );
464 $updater->prepareContent( $user, $update, false );
465
466 $dataUpdates = $updater->getSecondaryDataUpdates();
467
468 // TODO: MCR: assert updates from all slots!
469 $this->assertNotEmpty( $dataUpdates );
470
471 $linksUpdates = array_filter( $dataUpdates, function ( $du ) {
472 return $du instanceof LinksUpdate;
473 } );
474 $this->assertCount( 1, $linksUpdates );
475 }
476
477 /**
478 * Creates a dummy revision object without touching the database.
479 *
480 * @param Title $title
481 * @param RevisionSlotsUpdate $update
482 * @param User $user
483 * @param string $comment
484 * @param int $id
485 * @param int $parentId
486 *
487 * @return MutableRevisionRecord
488 */
489 private function makeRevision(
490 Title $title,
491 RevisionSlotsUpdate $update,
492 User $user,
493 $comment,
494 $id,
495 $parentId = 0
496 ) {
497 $rev = new MutableRevisionRecord( $title );
498
499 $rev->applyUpdate( $update );
500 $rev->setUser( $user );
501 $rev->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
502 $rev->setId( $id );
503 $rev->setPageId( $title->getArticleID() );
504 $rev->setParentId( $parentId );
505
506 return $rev;
507 }
508
509 public function provideIsReusableFor() {
510 $title = Title::makeTitleSafe( NS_MAIN, __METHOD__ );
511
512 $user1 = User::newFromName( 'Alice' );
513 $user2 = User::newFromName( 'Bob' );
514
515 $content1 = new WikitextContent( 'one' );
516 $content2 = new WikitextContent( 'two' );
517
518 $update1 = new RevisionSlotsUpdate();
519 $update1->modifyContent( 'main', $content1 );
520
521 $update1b = new RevisionSlotsUpdate();
522 $update1b->modifyContent( 'xyz', $content1 );
523
524 $update2 = new RevisionSlotsUpdate();
525 $update2->modifyContent( 'main', $content2 );
526
527 $rev1 = $this->makeRevision( $title, $update1, $user1, 'rev1', 11 );
528 $rev1b = $this->makeRevision( $title, $update1b, $user1, 'rev1', 11 );
529
530 $rev2 = $this->makeRevision( $title, $update2, $user1, 'rev2', 12 );
531 $rev2x = $this->makeRevision( $title, $update2, $user2, 'rev2', 12 );
532 $rev2y = $this->makeRevision( $title, $update2, $user1, 'rev2', 122 );
533
534 yield 'any' => [
535 '$prepUser' => null,
536 '$prepRevision' => null,
537 '$prepUpdate' => null,
538 '$forUser' => null,
539 '$forRevision' => null,
540 '$forUpdate' => null,
541 '$forParent' => null,
542 '$isReusable' => true,
543 ];
544 yield 'for any' => [
545 '$prepUser' => $user1,
546 '$prepRevision' => $rev1,
547 '$prepUpdate' => $update1,
548 '$forUser' => null,
549 '$forRevision' => null,
550 '$forUpdate' => null,
551 '$forParent' => null,
552 '$isReusable' => true,
553 ];
554 yield 'unprepared' => [
555 '$prepUser' => null,
556 '$prepRevision' => null,
557 '$prepUpdate' => null,
558 '$forUser' => $user1,
559 '$forRevision' => $rev1,
560 '$forUpdate' => $update1,
561 '$forParent' => 0,
562 '$isReusable' => true,
563 ];
564 yield 'match prepareContent' => [
565 '$prepUser' => $user1,
566 '$prepRevision' => null,
567 '$prepUpdate' => $update1,
568 '$forUser' => $user1,
569 '$forRevision' => null,
570 '$forUpdate' => $update1,
571 '$forParent' => 0,
572 '$isReusable' => true,
573 ];
574 yield 'match prepareUpdate' => [
575 '$prepUser' => null,
576 '$prepRevision' => $rev1,
577 '$prepUpdate' => null,
578 '$forUser' => $user1,
579 '$forRevision' => $rev1,
580 '$forUpdate' => null,
581 '$forParent' => 0,
582 '$isReusable' => true,
583 ];
584 yield 'match all' => [
585 '$prepUser' => $user1,
586 '$prepRevision' => $rev1,
587 '$prepUpdate' => $update1,
588 '$forUser' => $user1,
589 '$forRevision' => $rev1,
590 '$forUpdate' => $update1,
591 '$forParent' => 0,
592 '$isReusable' => true,
593 ];
594 yield 'mismatch prepareContent update' => [
595 '$prepUser' => $user1,
596 '$prepRevision' => null,
597 '$prepUpdate' => $update1,
598 '$forUser' => $user1,
599 '$forRevision' => null,
600 '$forUpdate' => $update1b,
601 '$forParent' => 0,
602 '$isReusable' => false,
603 ];
604 yield 'mismatch prepareContent user' => [
605 '$prepUser' => $user1,
606 '$prepRevision' => null,
607 '$prepUpdate' => $update1,
608 '$forUser' => $user2,
609 '$forRevision' => null,
610 '$forUpdate' => $update1,
611 '$forParent' => 0,
612 '$isReusable' => false,
613 ];
614 yield 'mismatch prepareContent parent' => [
615 '$prepUser' => $user1,
616 '$prepRevision' => null,
617 '$prepUpdate' => $update1,
618 '$forUser' => $user1,
619 '$forRevision' => null,
620 '$forUpdate' => $update1,
621 '$forParent' => 7,
622 '$isReusable' => false,
623 ];
624 yield 'mismatch prepareUpdate revision update' => [
625 '$prepUser' => null,
626 '$prepRevision' => $rev1,
627 '$prepUpdate' => null,
628 '$forUser' => null,
629 '$forRevision' => $rev1b,
630 '$forUpdate' => null,
631 '$forParent' => 0,
632 '$isReusable' => false,
633 ];
634 yield 'mismatch prepareUpdate revision user' => [
635 '$prepUser' => null,
636 '$prepRevision' => $rev2,
637 '$prepUpdate' => null,
638 '$forUser' => null,
639 '$forRevision' => $rev2x,
640 '$forUpdate' => null,
641 '$forParent' => 0,
642 '$isReusable' => false,
643 ];
644 yield 'mismatch prepareUpdate revision id' => [
645 '$prepUser' => null,
646 '$prepRevision' => $rev2,
647 '$prepUpdate' => null,
648 '$forUser' => null,
649 '$forRevision' => $rev2y,
650 '$forUpdate' => null,
651 '$forParent' => 0,
652 '$isReusable' => false,
653 ];
654 }
655
656 /**
657 * @dataProvider provideIsReusableFor
658 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
659 *
660 * @param User|null $prepUser
661 * @param RevisionRecord|null $prepRevision
662 * @param RevisionSlotsUpdate|null $prepUpdate
663 * @param User|null $forUser
664 * @param RevisionRecord|null $forRevision
665 * @param RevisionSlotsUpdate|null $forUpdate
666 * @param int|null $forParent
667 * @param bool $isReusable
668 */
669 public function testIsReusableFor(
670 User $prepUser = null,
671 RevisionRecord $prepRevision = null,
672 RevisionSlotsUpdate $prepUpdate = null,
673 User $forUser = null,
674 RevisionRecord $forRevision = null,
675 RevisionSlotsUpdate $forUpdate = null,
676 $forParent = null,
677 $isReusable = null
678 ) {
679 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
680
681 if ( $prepUpdate ) {
682 $updater->prepareContent( $prepUser, $prepUpdate, false );
683 }
684
685 if ( $prepRevision ) {
686 $updater->prepareUpdate( $prepRevision );
687 }
688
689 $this->assertSame(
690 $isReusable,
691 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
692 );
693 }
694
695 /**
696 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
697 */
698 public function testDoUpdates() {
699 $page = $this->getPage( __METHOD__ );
700
701 $mainContent1 = new WikitextContent( 'first [[main]]' );
702 $rev = $this->createRevision( $page, 'first', $mainContent1 );
703 $pageId = $page->getId();
704 $oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
705
706 $updater = $this->getDerivedPageDataUpdater( $page, $rev );
707 $updater->setArticleCountMethod( 'link' );
708
709 $options = []; // TODO: test *all* the options...
710 $updater->prepareUpdate( $rev, $options );
711
712 $updater->doUpdates();
713
714 // links table update
715 $linkCount = $this->db->selectRowCount( 'pagelinks', '*', [ 'pl_from' => $pageId ] );
716 $this->assertSame( 1, $linkCount );
717
718 $pageLinksRow = $this->db->selectRow( 'pagelinks', '*', [ 'pl_from' => $pageId ] );
719 $this->assertInternalType( 'object', $pageLinksRow );
720 $this->assertSame( 'Main', $pageLinksRow->pl_title );
721
722 // parser cache update
723 $pcache = MediaWikiServices::getInstance()->getParserCache();
724 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions() );
725 $this->assertInternalType( 'object', $cached );
726 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
727
728 // site stats
729 $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
730 $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages );
731 $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
732 $this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles );
733
734 // TODO: MCR: test data updates for additional slots!
735 // TODO: test update for edit without page creation
736 // TODO: test message cache purge
737 // TODO: test module cache purge
738 // TODO: test CDN purge
739 // TODO: test newtalk update
740 // TODO: test search update
741 // TODO: test site stats good_articles while turning the page into (or back from) a redir.
742 // TODO: test category membership update (with setRcWatchCategoryMembership())
743 }
744
745 }