19 use PHPUnit\Framework\MockObject\MockObject;
24 use Wikimedia\TestingAccessWrapper;
36 MWTimestamp::setFakeTime(
false );
67 if ( is_string( $page ) || $page instanceof
Title ) {
68 $page = $this->
getPage( $page );
71 $page = TestingAccessWrapper::newFromObject( $page );
72 return $page->getDerivedDataUpdater(
null, $rec );
106 $this->fail(
$updater->getStatus()->getWikiText() );
121 $page = $this->
getPage( __METHOD__ );
132 $options1 =
$updater->getCanonicalParserOptions();
134 $options1->getUserLangObj() );
136 $speculativeId = $options1->getSpeculativeRevId();
137 $this->assertSame( $parentRev->getId() + 1, $speculativeId );
143 $parentRev->getId() + 7,
148 $options2 =
$updater->getCanonicalParserOptions();
150 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
151 $this->assertSame(
$rev->getId(), $currentRev->getId() );
159 $page = $this->
getPage( __METHOD__ );
162 $this->assertNull( $updater0->grabCurrentRevision() );
163 $this->assertFalse( $updater0->pageExisted() );
167 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
168 $this->assertFalse( $updater0->pageExisted() );
169 $this->assertTrue( $updater1->pageExisted() );
173 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
174 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
197 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
200 $this->assertFalse(
$updater->isContentPrepared() );
216 $updater->prepareContent( $sysop, $update,
false );
219 $updater->prepareContent( $sysop, $update,
false );
221 $this->assertNull(
$updater->grabCurrentRevision() );
222 $this->assertTrue(
$updater->isContentPrepared() );
223 $this->assertFalse(
$updater->isUpdatePrepared() );
224 $this->assertFalse(
$updater->pageExisted() );
225 $this->assertTrue(
$updater->isCreation() );
226 $this->assertTrue(
$updater->isChange() );
227 $this->assertFalse(
$updater->isContentDeleted() );
229 $this->assertNotNull(
$updater->getRevision() );
230 $this->assertNotNull(
$updater->getRenderedRevision() );
232 $this->assertEquals( [
'main',
'aux' ],
$updater->getSlots()->getSlotRoles() );
233 $this->assertEquals( [
'main' ], array_keys(
$updater->getSlots()->getOriginalSlots() ) );
234 $this->assertEquals( [
'aux' ], array_keys(
$updater->getSlots()->getInheritedSlots() ) );
235 $this->assertEquals( [
'main',
'aux' ],
$updater->getModifiedSlotRoles() );
236 $this->assertEquals( [
'main',
'aux' ],
$updater->getTouchedSlotRoles() );
240 $this->assertNotContains(
'~~~', $mainSlot->getContent()->serialize(),
'PST should apply.' );
241 $this->assertContains( $sysop->getName(), $mainSlot->getContent()->serialize() );
243 $auxSlot =
$updater->getRawSlot(
'aux' );
245 $this->assertContains(
'~~~', $auxSlot->getContent()->serialize(),
'No PST should apply.' );
247 $mainOutput =
$updater->getCanonicalParserOutput();
248 $this->assertContains(
'first', $mainOutput->getText() );
249 $this->assertContains(
'<a ', $mainOutput->getText() );
250 $this->assertNotEmpty( $mainOutput->getLinks() );
252 $canonicalOutput =
$updater->getCanonicalParserOutput();
253 $this->assertContains(
'first', $canonicalOutput->getText() );
254 $this->assertContains(
'<a ', $canonicalOutput->getText() );
255 $this->assertContains(
'inherited ', $canonicalOutput->getText() );
256 $this->assertNotEmpty( $canonicalOutput->getLinks() );
266 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
267 $page = $this->
getPage( __METHOD__ );
269 $mainContent1 =
new WikitextContent(
'first [[main]] ({{REVISIONUSER}}) #~~~#' );
270 $mainContent2 =
new WikitextContent(
'second ({{subst:REVISIONUSER}}) #~~~#' );
274 $userName =
$rev->getUser()->getName();
275 $sysopName = $sysop->getName();
280 $updater1->prepareContent( $sysop, $update,
false );
282 $this->assertNotNull( $updater1->grabCurrentRevision() );
283 $this->assertTrue( $updater1->isContentPrepared() );
284 $this->assertTrue( $updater1->pageExisted() );
285 $this->assertFalse( $updater1->isCreation() );
286 $this->assertFalse( $updater1->isChange() );
288 $this->assertNotNull( $updater1->getRevision() );
289 $this->assertNotNull( $updater1->getRenderedRevision() );
292 $html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
293 $this->assertNotContains( $sysopName,
$html,
'{{REVISIONUSER}}' );
294 $this->assertNotContains(
'{{REVISIONUSER}}',
$html,
'{{REVISIONUSER}}' );
295 $this->assertNotContains(
'~~~',
$html,
'signature ~~~' );
296 $this->assertContains(
'(' . $userName .
')',
$html,
'{{REVISIONUSER}}' );
297 $this->assertContains(
'>' . $userName .
'<',
$html,
'signature ~~~' );
303 $updater2->prepareContent( $sysop, $update,
false );
306 $pstText = $updater2->getSlots()->getContent(
SlotRecord::MAIN )->serialize();
307 $this->assertNotContains(
'{{subst:REVISIONUSER}}', $pstText,
'{{subst:REVISIONUSER}}' );
308 $this->assertNotContains(
'~~~', $pstText,
'signature ~~~' );
309 $this->assertContains(
'(' . $sysopName .
')', $pstText,
'{{subst:REVISIONUSER}}' );
310 $this->assertContains(
':' . $sysopName .
'|', $pstText,
'signature ~~~' );
312 $this->assertFalse( $updater2->isCreation() );
313 $this->assertTrue( $updater2->isChange() );
334 $page = $this->
getPage( __METHOD__ );
341 $updater1->prepareUpdate( $rev1,
$options );
343 $this->assertTrue( $updater1->isUpdatePrepared() );
344 $this->assertTrue( $updater1->isContentPrepared() );
345 $this->assertTrue( $updater1->isCreation() );
346 $this->assertTrue( $updater1->isChange() );
347 $this->assertFalse( $updater1->isContentDeleted() );
349 $this->assertNotNull( $updater1->getRevision() );
350 $this->assertNotNull( $updater1->getRenderedRevision() );
352 $this->assertEquals( [
'main' ], $updater1->getSlots()->getSlotRoles() );
353 $this->assertEquals( [
'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
354 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
355 $this->assertEquals( [
'main' ], $updater1->getModifiedSlotRoles() );
356 $this->assertEquals( [
'main' ], $updater1->getTouchedSlotRoles() );
361 $this->assertNotContains(
'~~~~', $updater1->getRawContent(
SlotRecord::MAIN )->serialize() );
363 $mainOutput = $updater1->getCanonicalParserOutput();
364 $this->assertContains(
'first', $mainOutput->getText() );
365 $this->assertContains(
'<a ', $mainOutput->getText() );
366 $this->assertNotEmpty( $mainOutput->getLinks() );
368 $canonicalOutput = $updater1->getCanonicalParserOutput();
369 $this->assertContains(
'first', $canonicalOutput->getText() );
370 $this->assertContains(
'<a ', $canonicalOutput->getText() );
371 $this->assertNotEmpty( $canonicalOutput->getLinks() );
378 $updater2->prepareUpdate( $rev2,
$options );
380 $this->assertFalse( $updater2->isCreation() );
381 $this->assertTrue( $updater2->isChange() );
383 $canonicalOutput = $updater2->getCanonicalParserOutput();
384 $this->assertContains(
'second', $canonicalOutput->getText() );
392 $page = $this->
getPage( __METHOD__ );
402 $canonicalOutput =
$updater->getCanonicalParserOutput();
409 $this->assertTrue(
$updater->isUpdatePrepared() );
410 $this->assertTrue(
$updater->isContentPrepared() );
413 $this->assertSame( $canonicalOutput,
$updater->getCanonicalParserOutput() );
422 $page = $this->
getPage( __METHOD__ );
432 $canonicalOutput =
$updater->getCanonicalParserOutput();
435 $mainOutput->setSpeculativeRevIdUsed( 0 );
436 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
443 $this->assertTrue(
$updater->isUpdatePrepared() );
444 $this->assertTrue(
$updater->isContentPrepared() );
448 $this->assertNotSame( $canonicalOutput,
$updater->getCanonicalParserOutput() );
451 $this->assertContains(
'--' .
$rev->getId() .
'--',
$html );
473 $canonicalOutput =
$updater->getCanonicalParserOutput();
475 $preparedEdit =
$updater->getPreparedEdit();
476 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
477 $this->assertSame( $canonicalOutput, $preparedEdit->output );
478 $this->assertSame( $mainContent, $preparedEdit->newContent );
480 $this->assertSame(
$updater->getCanonicalParserOptions(), $preparedEdit->popts );
481 $this->assertSame(
null, $preparedEdit->revid );
488 $clock = MWTimestamp::convert( TS_UNIX,
'20100101000000' );
489 MWTimestamp::setFakeTime(
function ()
use ( &$clock ) {
493 $page = $this->
getPage( __METHOD__ );
504 $canonicalOutput =
$updater->getCanonicalParserOutput();
506 $preparedEdit =
$updater->getPreparedEdit();
507 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
508 $this->assertSame( $canonicalOutput, $preparedEdit->output );
510 $this->assertSame(
$updater->getCanonicalParserOptions(), $preparedEdit->popts );
511 $this->assertSame(
$rev->getId(), $preparedEdit->revid );
516 $page = $this->
getPage( __METHOD__ );
526 $dataUpdates =
$updater->getSecondaryDataUpdates();
528 $this->assertNotEmpty( $dataUpdates );
530 $linksUpdates = array_filter( $dataUpdates,
function ( $du ) {
533 $this->assertCount( 1, $linksUpdates );
544 ->setConstructorArgs( [
$name ] )
546 [
'getSecondaryDataUpdates',
'getDeletionUpdates',
'unserializeContent' ]
551 $dataUpdate->_name =
"$name data update";
554 $deletionUpdate->_name =
"$name deletion update";
556 $handler->method(
'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
557 $handler->method(
'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
558 $handler->method(
'unserializeContent' )->willReturnCallback(
565 'wgContentHandlers', [
584 ->setConstructorArgs( [ $text ] )
585 ->setMethods( [
'getModel',
'getContentHandler' ] )
596 $this->markTestSkipped(
'Slot removal cannot happen with MCR being enabled' );
613 $page = $this->
getPage( __METHOD__ );
617 [
'main' => $mainContent1,
'aux' => $auxContent1 ]
622 $update->removeSlot(
'aux' );
624 $page = $this->
getPage( __METHOD__ );
628 $dataUpdates =
$updater->getSecondaryDataUpdates();
630 $this->assertNotEmpty( $dataUpdates );
632 $updateNames = array_map(
function ( $du ) {
633 return isset( $du->_name ) ? $du->_name : get_class( $du );
637 $this->assertContains(
'A1 deletion update', $updateNames );
638 $this->assertContains(
'M2 data update', $updateNames );
639 $this->assertNotContains(
'M1 data update', $updateNames );
664 $rev->applyUpdate( $update );
668 $rev->setParentId( $parentId );
683 ->disableOriginalConstructor()
685 $mock->expects( $this->
any() )
686 ->method(
'getDBkey' )
687 ->will( $this->returnValue( __CLASS__ ) );
688 $mock->expects( $this->
any() )
689 ->method(
'getArticleID' )
690 ->will( $this->returnValue( $id ) );
708 $update1b->modifyContent(
'xyz', $content1 );
722 '$prepRevision' =>
null,
723 '$prepUpdate' =>
null,
725 '$forRevision' =>
null,
726 '$forUpdate' =>
null,
727 '$forParent' =>
null,
728 '$isReusable' =>
true,
731 '$prepUser' => $user1,
732 '$prepRevision' => $rev1,
733 '$prepUpdate' => $update1,
735 '$forRevision' =>
null,
736 '$forUpdate' =>
null,
737 '$forParent' =>
null,
738 '$isReusable' =>
true,
740 yield
'unprepared' => [
742 '$prepRevision' =>
null,
743 '$prepUpdate' =>
null,
744 '$forUser' => $user1,
745 '$forRevision' => $rev1,
746 '$forUpdate' => $update1,
748 '$isReusable' =>
true,
750 yield
'match prepareContent' => [
751 '$prepUser' => $user1,
752 '$prepRevision' =>
null,
753 '$prepUpdate' => $update1,
754 '$forUser' => $user1,
755 '$forRevision' =>
null,
756 '$forUpdate' => $update1,
758 '$isReusable' =>
true,
760 yield
'match prepareUpdate' => [
762 '$prepRevision' => $rev1,
763 '$prepUpdate' =>
null,
764 '$forUser' => $user1,
765 '$forRevision' => $rev1,
766 '$forUpdate' =>
null,
768 '$isReusable' =>
true,
770 yield
'match all' => [
771 '$prepUser' => $user1,
772 '$prepRevision' => $rev1,
773 '$prepUpdate' => $update1,
774 '$forUser' => $user1,
775 '$forRevision' => $rev1,
776 '$forUpdate' => $update1,
778 '$isReusable' =>
true,
780 yield
'mismatch prepareContent update' => [
781 '$prepUser' => $user1,
782 '$prepRevision' =>
null,
783 '$prepUpdate' => $update1,
784 '$forUser' => $user1,
785 '$forRevision' =>
null,
786 '$forUpdate' => $update1b,
788 '$isReusable' =>
false,
790 yield
'mismatch prepareContent user' => [
791 '$prepUser' => $user1,
792 '$prepRevision' =>
null,
793 '$prepUpdate' => $update1,
794 '$forUser' => $user2,
795 '$forRevision' =>
null,
796 '$forUpdate' => $update1,
798 '$isReusable' =>
false,
800 yield
'mismatch prepareContent parent' => [
801 '$prepUser' => $user1,
802 '$prepRevision' =>
null,
803 '$prepUpdate' => $update1,
804 '$forUser' => $user1,
805 '$forRevision' =>
null,
806 '$forUpdate' => $update1,
808 '$isReusable' =>
false,
810 yield
'mismatch prepareUpdate revision update' => [
812 '$prepRevision' => $rev1,
813 '$prepUpdate' =>
null,
815 '$forRevision' => $rev1b,
816 '$forUpdate' =>
null,
818 '$isReusable' =>
false,
820 yield
'mismatch prepareUpdate revision id' => [
822 '$prepRevision' => $rev2,
823 '$prepUpdate' =>
null,
825 '$forRevision' => $rev2y,
826 '$forUpdate' =>
null,
828 '$isReusable' =>
false,
846 User $prepUser =
null,
849 User $forUser =
null,
858 $updater->prepareContent( $prepUser, $prepUpdate,
false );
861 if ( $prepRevision ) {
862 $updater->prepareUpdate( $prepRevision );
867 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
877 $page = $this->
getPage( __METHOD__ );
891 $pageId = $page->getId();
893 $oldStats = $this->db->selectRow(
'site_stats',
'*',
'1=1' );
894 $this->db->delete(
'pagelinks',
'*' );
897 $pcache->deleteOptionsKey( $page );
900 $updater->setArticleCountMethod(
'link' );
908 $pageLinks = $this->db->select(
911 [
'pl_from' => $pageId ],
913 [
'ORDER BY' =>
'pl_namespace, pl_title' ]
916 $pageLinksRow = $pageLinks->fetchObject();
917 $this->assertInternalType(
'object', $pageLinksRow );
918 $this->assertSame(
'Main', $pageLinksRow->pl_title );
921 $pageLinksRow = $pageLinks->fetchObject();
922 $this->assertInternalType(
'object', $pageLinksRow );
923 $this->assertSame(
'Nix', $pageLinksRow->pl_title );
927 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions() );
928 $this->assertInternalType(
'object', $cached );
929 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
932 $stats = $this->db->selectRow(
'site_stats',
'*',
'1=1' );
933 $this->assertSame( $oldStats->ss_total_pages + 1, (
int)$stats->ss_total_pages );
934 $this->assertSame( $oldStats->ss_total_edits + 1, (
int)$stats->ss_total_edits );
935 $this->assertSame( $oldStats->ss_good_articles + 1, (
int)$stats->ss_good_articles );
959 $page = $this->
getPage( __METHOD__ );
965 $update->modifyContent(
'main',
new WikitextContent(
'first [[Main]]' ) );
973 $pcache->deleteOptionsKey( $page );
976 $rev->setTimestamp(
'20100101000000' );
977 $rev->setParentId( $page->getLatest() );
987 $page->setTimestamp(
$rev->getTimestamp() );
991 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions(),
true );
992 $this->assertInternalType(
'object', $cached );
993 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
995 $this->assertSame(
$rev->getTimestamp(), $cached->getCacheTime() );
996 $this->assertSame(
$rev->getId(), $cached->getCacheRevisionId() );
999 $pcache->deleteOptionsKey( $page );
1006 $page->setTimestamp(
$rev->getTimestamp() );
1010 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions(),
true );
1011 $this->assertInternalType(
'object', $cached );
1012 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
1014 $this->assertGreaterThan(
$rev->getTimestamp(), $cached->getCacheTime() );
1015 $this->assertSame(
$rev->getId(), $cached->getCacheRevisionId() );