3 namespace MediaWiki\Tests\Storage
;
5 use CommentStoreComment
;
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
;
18 use Wikimedia\TestingAccessWrapper
;
25 * @covers \MediaWiki\Storage\DerivedPageDataUpdater
27 class DerivedPageDataUpdaterTest
extends MediaWikiTestCase
{
30 * @param string $title
34 private function getTitle( $title ) {
35 return Title
::makeTitleSafe( $this->getDefaultWikitextNS(), $title );
39 * @param string|Title $title
43 private function getPage( $title ) {
44 $title = ( $title instanceof Title
) ?
$title : $this->getTitle( $title );
46 return WikiPage
::factory( $title );
50 * @param string|Title|WikiPage $page
52 * @return DerivedPageDataUpdater
54 private function getDerivedPageDataUpdater( $page, RevisionRecord
$rec = null ) {
55 if ( is_string( $page ) ||
$page instanceof Title
) {
56 $page = $this->getPage( $page );
59 $page = TestingAccessWrapper
::newFromObject( $page );
60 return $page->getDerivedDataUpdater( null, $rec );
64 * Creates a revision in the database.
66 * @param WikiPage $page
68 * @param null|string|Content $content
70 * @return RevisionRecord|null
72 private function createRevision( WikiPage
$page, $summary, $content = null ) {
73 $user = $this->getTestUser()->getUser();
74 $comment = CommentStoreComment
::newUnsavedComment( $summary );
76 if ( $content === null ||
is_string( $content ) ) {
77 $content = new WikitextContent( $content ??
$summary );
80 if ( !is_array( $content ) ) {
81 $content = [ 'main' => $content ];
84 $this->getDerivedPageDataUpdater( $page ); // flush cached instance before.
86 $updater = $page->newPageUpdater( $user );
88 foreach ( $content as $role => $c ) {
89 $updater->setContent( $role, $c );
92 $rev = $updater->saveRevision( $comment );
94 $this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
98 // TODO: test setArticleCountMethod() and isCountable();
99 // TODO: test isRedirect() and wasRedirect()
102 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOptions()
104 public function testGetCanonicalParserOptions() {
105 $user = $this->getTestUser()->getUser();
106 $page = $this->getPage( __METHOD__
);
108 $parentRev = $this->createRevision( $page, 'first' );
110 $mainContent = new WikitextContent( 'Lorem ipsum' );
112 $update = new RevisionSlotsUpdate();
113 $update->modifyContent( 'main', $mainContent );
114 $updater = $this->getDerivedPageDataUpdater( $page );
115 $updater->prepareContent( $user, $update, false );
117 $options1 = $updater->getCanonicalParserOptions();
118 $this->assertSame( MediaWikiServices
::getInstance()->getContentLanguage(),
119 $options1->getUserLangObj() );
121 $speculativeId = $options1->getSpeculativeRevId();
122 $this->assertSame( $parentRev->getId() +
1, $speculativeId );
124 $rev = $this->makeRevision(
128 $parentRev->getId() +
7,
131 $updater->prepareUpdate( $rev );
133 $options2 = $updater->getCanonicalParserOptions();
135 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
136 $this->assertSame( $rev->getId(), $currentRev->getId() );
140 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::grabCurrentRevision()
141 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
143 public function testGrabCurrentRevision() {
144 $page = $this->getPage( __METHOD__
);
146 $updater0 = $this->getDerivedPageDataUpdater( $page );
147 $this->assertNull( $updater0->grabCurrentRevision() );
148 $this->assertFalse( $updater0->pageExisted() );
150 $rev1 = $this->createRevision( $page, 'first' );
151 $updater1 = $this->getDerivedPageDataUpdater( $page );
152 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
153 $this->assertFalse( $updater0->pageExisted() );
154 $this->assertTrue( $updater1->pageExisted() );
156 $rev2 = $this->createRevision( $page, 'second' );
157 $updater2 = $this->getDerivedPageDataUpdater( $page );
158 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
159 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
163 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
164 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isContentPrepared()
165 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
166 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
167 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
168 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
169 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
170 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
171 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
172 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
173 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
174 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
176 public function testPrepareContent() {
177 $sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
178 $updater = $this->getDerivedPageDataUpdater( __METHOD__
);
180 $this->assertFalse( $updater->isContentPrepared() );
183 // TODO: MCR: Test multiple slots. Test slot removal.
184 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
185 $auxContent = new WikitextContent( 'inherited ~~~ content' );
186 $auxSlot = SlotRecord
::newSaved(
188 SlotRecord
::newUnsaved( 'aux', $auxContent )
191 $update = new RevisionSlotsUpdate();
192 $update->modifyContent( 'main', $mainContent );
193 $update->modifySlot( SlotRecord
::newInherited( $auxSlot ) );
194 // TODO: MCR: test removing slots!
196 $updater->prepareContent( $sysop, $update, false );
198 // second be ok to call again with the same params
199 $updater->prepareContent( $sysop, $update, false );
201 $this->assertNull( $updater->grabCurrentRevision() );
202 $this->assertTrue( $updater->isContentPrepared() );
203 $this->assertFalse( $updater->isUpdatePrepared() );
204 $this->assertFalse( $updater->pageExisted() );
205 $this->assertTrue( $updater->isCreation() );
206 $this->assertTrue( $updater->isChange() );
207 $this->assertFalse( $updater->isContentDeleted() );
209 $this->assertNotNull( $updater->getRevision() );
210 $this->assertNotNull( $updater->getRenderedRevision() );
212 $this->assertEquals( [ 'main', 'aux' ], $updater->getSlots()->getSlotRoles() );
213 $this->assertEquals( [ 'main' ], array_keys( $updater->getSlots()->getOriginalSlots() ) );
214 $this->assertEquals( [ 'aux' ], array_keys( $updater->getSlots()->getInheritedSlots() ) );
215 $this->assertEquals( [ 'main', 'aux' ], $updater->getModifiedSlotRoles() );
216 $this->assertEquals( [ 'main', 'aux' ], $updater->getTouchedSlotRoles() );
218 $mainSlot = $updater->getRawSlot( 'main' );
219 $this->assertInstanceOf( SlotRecord
::class, $mainSlot );
220 $this->assertNotContains( '~~~', $mainSlot->getContent()->serialize(), 'PST should apply.' );
221 $this->assertContains( $sysop->getName(), $mainSlot->getContent()->serialize() );
223 $auxSlot = $updater->getRawSlot( 'aux' );
224 $this->assertInstanceOf( SlotRecord
::class, $auxSlot );
225 $this->assertContains( '~~~', $auxSlot->getContent()->serialize(), 'No PST should apply.' );
227 $mainOutput = $updater->getCanonicalParserOutput();
228 $this->assertContains( 'first', $mainOutput->getText() );
229 $this->assertContains( '<a ', $mainOutput->getText() );
230 $this->assertNotEmpty( $mainOutput->getLinks() );
232 $canonicalOutput = $updater->getCanonicalParserOutput();
233 $this->assertContains( 'first', $canonicalOutput->getText() );
234 $this->assertContains( '<a ', $canonicalOutput->getText() );
235 $this->assertContains( 'inherited ', $canonicalOutput->getText() );
236 $this->assertNotEmpty( $canonicalOutput->getLinks() );
240 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
241 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
242 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
243 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
245 public function testPrepareContentInherit() {
246 $sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
247 $page = $this->getPage( __METHOD__
);
249 $mainContent1 = new WikitextContent( 'first [[main]] ({{REVISIONUSER}}) ~~~' );
250 $mainContent2 = new WikitextContent( 'second' );
252 $rev = $this->createRevision( $page, 'first', $mainContent1 );
253 $mainContent1 = $rev->getContent( 'main' ); // get post-pst content
255 $update = new RevisionSlotsUpdate();
256 $update->modifyContent( 'main', $mainContent1 );
257 $updater1 = $this->getDerivedPageDataUpdater( $page );
258 $updater1->prepareContent( $sysop, $update, false );
260 $this->assertNotNull( $updater1->grabCurrentRevision() );
261 $this->assertTrue( $updater1->isContentPrepared() );
262 $this->assertTrue( $updater1->pageExisted() );
263 $this->assertFalse( $updater1->isCreation() );
264 $this->assertFalse( $updater1->isChange() );
266 $this->assertNotNull( $updater1->getRevision() );
267 $this->assertNotNull( $updater1->getRenderedRevision() );
269 // parser-output for null-edit uses the original author's name
270 $html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
271 $this->assertNotContains( $sysop->getName(), $html, '{{REVISIONUSER}}' );
272 $this->assertNotContains( '{{REVISIONUSER}}', $html, '{{REVISIONUSER}}' );
273 $this->assertContains( '(' . $rev->getUser()->getName() . ')', $html, '{{REVISIONUSER}}' );
275 // TODO: MCR: test inheritance from parent
276 $update = new RevisionSlotsUpdate();
277 $update->modifyContent( 'main', $mainContent2 );
278 $updater2 = $this->getDerivedPageDataUpdater( $page );
279 $updater2->prepareContent( $sysop, $update, false );
281 $this->assertFalse( $updater2->isCreation() );
282 $this->assertTrue( $updater2->isChange() );
285 // TODO: test failure of prepareContent() when called again...
286 // - with different user
287 // - with different update
288 // - after calling prepareUpdate()
291 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
292 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isUpdatePrepared()
293 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
294 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
295 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
296 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
297 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
298 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
299 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
300 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
302 public function testPrepareUpdate() {
303 $page = $this->getPage( __METHOD__
);
305 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
306 $rev1 = $this->createRevision( $page, 'first', $mainContent1 );
307 $updater1 = $this->getDerivedPageDataUpdater( $page, $rev1 );
309 $options = []; // TODO: test *all* the options...
310 $updater1->prepareUpdate( $rev1, $options );
312 $this->assertTrue( $updater1->isUpdatePrepared() );
313 $this->assertTrue( $updater1->isContentPrepared() );
314 $this->assertTrue( $updater1->isCreation() );
315 $this->assertTrue( $updater1->isChange() );
316 $this->assertFalse( $updater1->isContentDeleted() );
318 $this->assertNotNull( $updater1->getRevision() );
319 $this->assertNotNull( $updater1->getRenderedRevision() );
321 $this->assertEquals( [ 'main' ], $updater1->getSlots()->getSlotRoles() );
322 $this->assertEquals( [ 'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
323 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
324 $this->assertEquals( [ 'main' ], $updater1->getModifiedSlotRoles() );
325 $this->assertEquals( [ 'main' ], $updater1->getTouchedSlotRoles() );
327 // TODO: MCR: test multiple slots, test slot removal!
329 $this->assertInstanceOf( SlotRecord
::class, $updater1->getRawSlot( 'main' ) );
330 $this->assertNotContains( '~~~~', $updater1->getRawContent( 'main' )->serialize() );
332 $mainOutput = $updater1->getCanonicalParserOutput();
333 $this->assertContains( 'first', $mainOutput->getText() );
334 $this->assertContains( '<a ', $mainOutput->getText() );
335 $this->assertNotEmpty( $mainOutput->getLinks() );
337 $canonicalOutput = $updater1->getCanonicalParserOutput();
338 $this->assertContains( 'first', $canonicalOutput->getText() );
339 $this->assertContains( '<a ', $canonicalOutput->getText() );
340 $this->assertNotEmpty( $canonicalOutput->getLinks() );
342 $mainContent2 = new WikitextContent( 'second' );
343 $rev2 = $this->createRevision( $page, 'second', $mainContent2 );
344 $updater2 = $this->getDerivedPageDataUpdater( $page, $rev2 );
346 $options = []; // TODO: test *all* the options...
347 $updater2->prepareUpdate( $rev2, $options );
349 $this->assertFalse( $updater2->isCreation() );
350 $this->assertTrue( $updater2->isChange() );
352 $canonicalOutput = $updater2->getCanonicalParserOutput();
353 $this->assertContains( 'second', $canonicalOutput->getText() );
357 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
359 public function testPrepareUpdateReusesParserOutput() {
360 $user = $this->getTestUser()->getUser();
361 $page = $this->getPage( __METHOD__
);
363 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
365 $update = new RevisionSlotsUpdate();
366 $update->modifyContent( 'main', $mainContent1 );
367 $updater = $this->getDerivedPageDataUpdater( $page );
368 $updater->prepareContent( $user, $update, false );
370 $mainOutput = $updater->getSlotParserOutput( 'main' );
371 $canonicalOutput = $updater->getCanonicalParserOutput();
373 $rev = $this->createRevision( $page, 'first', $mainContent1 );
375 $options = []; // TODO: test *all* the options...
376 $updater->prepareUpdate( $rev, $options );
378 $this->assertTrue( $updater->isUpdatePrepared() );
379 $this->assertTrue( $updater->isContentPrepared() );
381 $this->assertSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
382 $this->assertSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
386 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
387 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
389 public function testPrepareUpdateOutputReset() {
390 $user = $this->getTestUser()->getUser();
391 $page = $this->getPage( __METHOD__
);
393 $mainContent1 = new WikitextContent( 'first --{{REVISIONID}}--' );
395 $update = new RevisionSlotsUpdate();
396 $update->modifyContent( 'main', $mainContent1 );
397 $updater = $this->getDerivedPageDataUpdater( $page );
398 $updater->prepareContent( $user, $update, false );
400 $mainOutput = $updater->getSlotParserOutput( 'main' );
401 $canonicalOutput = $updater->getCanonicalParserOutput();
403 // prevent optimization on matching speculative ID
404 $mainOutput->setSpeculativeRevIdUsed( 0 );
405 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
407 $rev = $this->createRevision( $page, 'first', $mainContent1 );
409 $options = []; // TODO: test *all* the options...
410 $updater->prepareUpdate( $rev, $options );
412 $this->assertTrue( $updater->isUpdatePrepared() );
413 $this->assertTrue( $updater->isContentPrepared() );
415 // ParserOutput objects should have been flushed.
416 $this->assertNotSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
417 $this->assertNotSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
419 $html = $updater->getCanonicalParserOutput()->getText();
420 $this->assertContains( '--' . $rev->getId() . '--', $html );
422 // TODO: MCR: ensure that when the main slot uses {{REVISIONID}} but another slot is
423 // updated, the main slot is still re-rendered!
426 // TODO: test failure of prepareUpdate() when called again with a different revision
427 // TODO: test failure of prepareUpdate() on inconsistency with prepareContent.
430 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
432 public function testGetPreparedEditAfterPrepareContent() {
433 $user = $this->getTestUser()->getUser();
435 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
436 $update = new RevisionSlotsUpdate();
437 $update->modifyContent( 'main', $mainContent );
439 $updater = $this->getDerivedPageDataUpdater( __METHOD__
);
440 $updater->prepareContent( $user, $update, false );
442 $canonicalOutput = $updater->getCanonicalParserOutput();
444 $preparedEdit = $updater->getPreparedEdit();
445 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp
);
446 $this->assertSame( $canonicalOutput, $preparedEdit->output
);
447 $this->assertSame( $mainContent, $preparedEdit->newContent
);
448 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent
);
449 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts
);
450 $this->assertSame( null, $preparedEdit->revid
);
454 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
456 public function testGetPreparedEditAfterPrepareUpdate() {
457 $page = $this->getPage( __METHOD__
);
459 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
460 $update = new MutableRevisionSlots();
461 $update->setContent( 'main', $mainContent );
463 $rev = $this->createRevision( $page, __METHOD__
);
465 $updater = $this->getDerivedPageDataUpdater( $page );
466 $updater->prepareUpdate( $rev );
468 $canonicalOutput = $updater->getCanonicalParserOutput();
470 $preparedEdit = $updater->getPreparedEdit();
471 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp
);
472 $this->assertSame( $canonicalOutput, $preparedEdit->output
);
473 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent
);
474 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts
);
475 $this->assertSame( $rev->getId(), $preparedEdit->revid
);
478 public function testGetSecondaryDataUpdatesAfterPrepareContent() {
479 $user = $this->getTestUser()->getUser();
480 $page = $this->getPage( __METHOD__
);
481 $this->createRevision( $page, __METHOD__
);
483 $mainContent1 = new WikitextContent( 'first' );
485 $update = new RevisionSlotsUpdate();
486 $update->modifyContent( 'main', $mainContent1 );
487 $updater = $this->getDerivedPageDataUpdater( $page );
488 $updater->prepareContent( $user, $update, false );
490 $dataUpdates = $updater->getSecondaryDataUpdates();
492 // TODO: MCR: assert updates from all slots!
493 $this->assertNotEmpty( $dataUpdates );
495 $linksUpdates = array_filter( $dataUpdates, function ( $du ) {
496 return $du instanceof LinksUpdate
;
498 $this->assertCount( 1, $linksUpdates );
502 * Creates a dummy revision object without touching the database.
504 * @param Title $title
505 * @param RevisionSlotsUpdate $update
507 * @param string $comment
509 * @param int $parentId
511 * @return MutableRevisionRecord
513 private function makeRevision(
515 RevisionSlotsUpdate
$update,
521 $rev = new MutableRevisionRecord( $title );
523 $rev->applyUpdate( $update );
524 $rev->setUser( $user );
525 $rev->setComment( CommentStoreComment
::newUnsavedComment( $comment ) );
527 $rev->setPageId( $title->getArticleID() );
528 $rev->setParentId( $parentId );
537 private function getMockTitle( $id = 23 ) {
538 $mock = $this->getMockBuilder( Title
::class )
539 ->disableOriginalConstructor()
541 $mock->expects( $this->any() )
542 ->method( 'getDBkey' )
543 ->will( $this->returnValue( __CLASS__
) );
544 $mock->expects( $this->any() )
545 ->method( 'getArticleID' )
546 ->will( $this->returnValue( $id ) );
551 public function provideIsReusableFor() {
552 $title = $this->getMockTitle();
554 $user1 = User
::newFromName( 'Alice' );
555 $user2 = User
::newFromName( 'Bob' );
557 $content1 = new WikitextContent( 'one' );
558 $content2 = new WikitextContent( 'two' );
560 $update1 = new RevisionSlotsUpdate();
561 $update1->modifyContent( 'main', $content1 );
563 $update1b = new RevisionSlotsUpdate();
564 $update1b->modifyContent( 'xyz', $content1 );
566 $update2 = new RevisionSlotsUpdate();
567 $update2->modifyContent( 'main', $content2 );
569 $rev1 = $this->makeRevision( $title, $update1, $user1, 'rev1', 11 );
570 $rev1b = $this->makeRevision( $title, $update1b, $user1, 'rev1', 11 );
572 $rev2 = $this->makeRevision( $title, $update2, $user1, 'rev2', 12 );
573 $rev2x = $this->makeRevision( $title, $update2, $user2, 'rev2', 12 );
574 $rev2y = $this->makeRevision( $title, $update2, $user1, 'rev2', 122 );
578 '$prepRevision' => null,
579 '$prepUpdate' => null,
581 '$forRevision' => null,
582 '$forUpdate' => null,
583 '$forParent' => null,
584 '$isReusable' => true,
587 '$prepUser' => $user1,
588 '$prepRevision' => $rev1,
589 '$prepUpdate' => $update1,
591 '$forRevision' => null,
592 '$forUpdate' => null,
593 '$forParent' => null,
594 '$isReusable' => true,
596 yield
'unprepared' => [
598 '$prepRevision' => null,
599 '$prepUpdate' => null,
600 '$forUser' => $user1,
601 '$forRevision' => $rev1,
602 '$forUpdate' => $update1,
604 '$isReusable' => true,
606 yield
'match prepareContent' => [
607 '$prepUser' => $user1,
608 '$prepRevision' => null,
609 '$prepUpdate' => $update1,
610 '$forUser' => $user1,
611 '$forRevision' => null,
612 '$forUpdate' => $update1,
614 '$isReusable' => true,
616 yield
'match prepareUpdate' => [
618 '$prepRevision' => $rev1,
619 '$prepUpdate' => null,
620 '$forUser' => $user1,
621 '$forRevision' => $rev1,
622 '$forUpdate' => null,
624 '$isReusable' => true,
626 yield
'match all' => [
627 '$prepUser' => $user1,
628 '$prepRevision' => $rev1,
629 '$prepUpdate' => $update1,
630 '$forUser' => $user1,
631 '$forRevision' => $rev1,
632 '$forUpdate' => $update1,
634 '$isReusable' => true,
636 yield
'mismatch prepareContent update' => [
637 '$prepUser' => $user1,
638 '$prepRevision' => null,
639 '$prepUpdate' => $update1,
640 '$forUser' => $user1,
641 '$forRevision' => null,
642 '$forUpdate' => $update1b,
644 '$isReusable' => false,
646 yield
'mismatch prepareContent user' => [
647 '$prepUser' => $user1,
648 '$prepRevision' => null,
649 '$prepUpdate' => $update1,
650 '$forUser' => $user2,
651 '$forRevision' => null,
652 '$forUpdate' => $update1,
654 '$isReusable' => false,
656 yield
'mismatch prepareContent parent' => [
657 '$prepUser' => $user1,
658 '$prepRevision' => null,
659 '$prepUpdate' => $update1,
660 '$forUser' => $user1,
661 '$forRevision' => null,
662 '$forUpdate' => $update1,
664 '$isReusable' => false,
666 yield
'mismatch prepareUpdate revision update' => [
668 '$prepRevision' => $rev1,
669 '$prepUpdate' => null,
671 '$forRevision' => $rev1b,
672 '$forUpdate' => null,
674 '$isReusable' => false,
676 yield
'mismatch prepareUpdate revision user' => [
678 '$prepRevision' => $rev2,
679 '$prepUpdate' => null,
681 '$forRevision' => $rev2x,
682 '$forUpdate' => null,
684 '$isReusable' => false,
686 yield
'mismatch prepareUpdate revision id' => [
688 '$prepRevision' => $rev2,
689 '$prepUpdate' => null,
691 '$forRevision' => $rev2y,
692 '$forUpdate' => null,
694 '$isReusable' => false,
699 * @dataProvider provideIsReusableFor
700 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
702 * @param User|null $prepUser
703 * @param RevisionRecord|null $prepRevision
704 * @param RevisionSlotsUpdate|null $prepUpdate
705 * @param User|null $forUser
706 * @param RevisionRecord|null $forRevision
707 * @param RevisionSlotsUpdate|null $forUpdate
708 * @param int|null $forParent
709 * @param bool $isReusable
711 public function testIsReusableFor(
712 User
$prepUser = null,
713 RevisionRecord
$prepRevision = null,
714 RevisionSlotsUpdate
$prepUpdate = null,
715 User
$forUser = null,
716 RevisionRecord
$forRevision = null,
717 RevisionSlotsUpdate
$forUpdate = null,
721 $updater = $this->getDerivedPageDataUpdater( __METHOD__
);
724 $updater->prepareContent( $prepUser, $prepUpdate, false );
727 if ( $prepRevision ) {
728 $updater->prepareUpdate( $prepRevision );
733 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
738 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
739 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
740 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
742 public function testDoUpdates() {
743 $page = $this->getPage( __METHOD__
);
745 $content = [ 'main' => new WikitextContent( 'first [[main]]' ) ];
747 if ( $this->hasMultiSlotSupport() ) {
748 $content['aux'] = new WikitextContent( 'Aux [[Nix]]' );
751 $rev = $this->createRevision( $page, 'first', $content );
752 $pageId = $page->getId();
754 $oldStats = $this->db
->selectRow( 'site_stats', '*', '1=1' );
755 $this->db
->delete( 'pagelinks', '*' );
757 $pcache = MediaWikiServices
::getInstance()->getParserCache();
758 $pcache->deleteOptionsKey( $page );
760 $updater = $this->getDerivedPageDataUpdater( $page, $rev );
761 $updater->setArticleCountMethod( 'link' );
763 $options = []; // TODO: test *all* the options...
764 $updater->prepareUpdate( $rev, $options );
766 $updater->doUpdates();
768 // links table update
769 $pageLinks = $this->db
->select(
772 [ 'pl_from' => $pageId ],
774 [ 'ORDER BY' => 'pl_namespace, pl_title' ]
777 $pageLinksRow = $pageLinks->fetchObject();
778 $this->assertInternalType( 'object', $pageLinksRow );
779 $this->assertSame( 'Main', $pageLinksRow->pl_title
);
781 if ( $this->hasMultiSlotSupport() ) {
782 $pageLinksRow = $pageLinks->fetchObject();
783 $this->assertInternalType( 'object', $pageLinksRow );
784 $this->assertSame( 'Nix', $pageLinksRow->pl_title
);
787 // parser cache update
788 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions() );
789 $this->assertInternalType( 'object', $cached );
790 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
793 $stats = $this->db
->selectRow( 'site_stats', '*', '1=1' );
794 $this->assertSame( $oldStats->ss_total_pages +
1, (int)$stats->ss_total_pages
);
795 $this->assertSame( $oldStats->ss_total_edits +
1, (int)$stats->ss_total_edits
);
796 $this->assertSame( $oldStats->ss_good_articles +
1, (int)$stats->ss_good_articles
);
798 // TODO: MCR: test data updates for additional slots!
799 // TODO: test update for edit without page creation
800 // TODO: test message cache purge
801 // TODO: test module cache purge
802 // TODO: test CDN purge
803 // TODO: test newtalk update
804 // TODO: test search update
805 // TODO: test site stats good_articles while turning the page into (or back from) a redir.
806 // TODO: test category membership update (with setRcWatchCategoryMembership())
809 private function hasMultiSlotSupport() {
810 global $wgMultiContentRevisionSchemaMigrationStage;
812 return ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW
)
813 && ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW
);