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 );
118 $page = $this->
getPage( __METHOD__ );
129 $options1 =
$updater->getCanonicalParserOptions();
131 $options1->getUserLangObj() );
133 $speculativeId = $options1->getSpeculativeRevId();
134 $this->assertSame( $parentRev->getId() + 1, $speculativeId );
140 $parentRev->getId() + 7,
145 $options2 =
$updater->getCanonicalParserOptions();
147 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
148 $this->assertSame(
$rev->getId(), $currentRev->getId() );
156 $page = $this->
getPage( __METHOD__ );
159 $this->assertNull( $updater0->grabCurrentRevision() );
160 $this->assertFalse( $updater0->pageExisted() );
164 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
165 $this->assertFalse( $updater0->pageExisted() );
166 $this->assertTrue( $updater1->pageExisted() );
170 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
171 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
189 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
192 $this->assertFalse(
$updater->isContentPrepared() );
208 $updater->prepareContent( $sysop, $update,
false );
211 $updater->prepareContent( $sysop, $update,
false );
213 $this->assertNull(
$updater->grabCurrentRevision() );
214 $this->assertTrue(
$updater->isContentPrepared() );
215 $this->assertFalse(
$updater->isUpdatePrepared() );
216 $this->assertFalse(
$updater->pageExisted() );
217 $this->assertTrue(
$updater->isCreation() );
218 $this->assertTrue(
$updater->isChange() );
219 $this->assertFalse(
$updater->isContentDeleted() );
221 $this->assertNotNull(
$updater->getRevision() );
222 $this->assertNotNull(
$updater->getRenderedRevision() );
224 $this->assertEquals( [
'main',
'aux' ],
$updater->getSlots()->getSlotRoles() );
225 $this->assertEquals( [
'main' ], array_keys(
$updater->getSlots()->getOriginalSlots() ) );
226 $this->assertEquals( [
'aux' ], array_keys(
$updater->getSlots()->getInheritedSlots() ) );
227 $this->assertEquals( [
'main',
'aux' ],
$updater->getModifiedSlotRoles() );
228 $this->assertEquals( [
'main',
'aux' ],
$updater->getTouchedSlotRoles() );
232 $this->assertNotContains(
'~~~', $mainSlot->getContent()->serialize(),
'PST should apply.' );
233 $this->assertContains( $sysop->getName(), $mainSlot->getContent()->serialize() );
235 $auxSlot =
$updater->getRawSlot(
'aux' );
237 $this->assertContains(
'~~~', $auxSlot->getContent()->serialize(),
'No PST should apply.' );
239 $mainOutput =
$updater->getCanonicalParserOutput();
240 $this->assertContains(
'first', $mainOutput->getText() );
241 $this->assertContains(
'<a ', $mainOutput->getText() );
242 $this->assertNotEmpty( $mainOutput->getLinks() );
244 $canonicalOutput =
$updater->getCanonicalParserOutput();
245 $this->assertContains(
'first', $canonicalOutput->getText() );
246 $this->assertContains(
'<a ', $canonicalOutput->getText() );
247 $this->assertContains(
'inherited ', $canonicalOutput->getText() );
248 $this->assertNotEmpty( $canonicalOutput->getLinks() );
258 $sysop = $this->
getTestUser( [
'sysop' ] )->getUser();
259 $page = $this->
getPage( __METHOD__ );
261 $mainContent1 =
new WikitextContent(
'first [[main]] ({{REVISIONUSER}}) #~~~#' );
262 $mainContent2 =
new WikitextContent(
'second ({{subst:REVISIONUSER}}) #~~~#' );
266 $userName =
$rev->getUser()->getName();
267 $sysopName = $sysop->getName();
272 $updater1->prepareContent( $sysop, $update,
false );
274 $this->assertNotNull( $updater1->grabCurrentRevision() );
275 $this->assertTrue( $updater1->isContentPrepared() );
276 $this->assertTrue( $updater1->pageExisted() );
277 $this->assertFalse( $updater1->isCreation() );
278 $this->assertFalse( $updater1->isChange() );
280 $this->assertNotNull( $updater1->getRevision() );
281 $this->assertNotNull( $updater1->getRenderedRevision() );
284 $html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
285 $this->assertNotContains( $sysopName,
$html,
'{{REVISIONUSER}}' );
286 $this->assertNotContains(
'{{REVISIONUSER}}',
$html,
'{{REVISIONUSER}}' );
287 $this->assertNotContains(
'~~~',
$html,
'signature ~~~' );
288 $this->assertContains(
'(' . $userName .
')',
$html,
'{{REVISIONUSER}}' );
289 $this->assertContains(
'>' . $userName .
'<',
$html,
'signature ~~~' );
295 $updater2->prepareContent( $sysop, $update,
false );
298 $pstText = $updater2->getSlots()->getContent(
SlotRecord::MAIN )->serialize();
299 $this->assertNotContains(
'{{subst:REVISIONUSER}}', $pstText,
'{{subst:REVISIONUSER}}' );
300 $this->assertNotContains(
'~~~', $pstText,
'signature ~~~' );
301 $this->assertContains(
'(' . $sysopName .
')', $pstText,
'{{subst:REVISIONUSER}}' );
302 $this->assertContains(
':' . $sysopName .
'|', $pstText,
'signature ~~~' );
304 $this->assertFalse( $updater2->isCreation() );
305 $this->assertTrue( $updater2->isChange() );
326 $page = $this->
getPage( __METHOD__ );
333 $updater1->prepareUpdate( $rev1,
$options );
335 $this->assertTrue( $updater1->isUpdatePrepared() );
336 $this->assertTrue( $updater1->isContentPrepared() );
337 $this->assertTrue( $updater1->isCreation() );
338 $this->assertTrue( $updater1->isChange() );
339 $this->assertFalse( $updater1->isContentDeleted() );
341 $this->assertNotNull( $updater1->getRevision() );
342 $this->assertNotNull( $updater1->getRenderedRevision() );
344 $this->assertEquals( [
'main' ], $updater1->getSlots()->getSlotRoles() );
345 $this->assertEquals( [
'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
346 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
347 $this->assertEquals( [
'main' ], $updater1->getModifiedSlotRoles() );
348 $this->assertEquals( [
'main' ], $updater1->getTouchedSlotRoles() );
353 $this->assertNotContains(
'~~~~', $updater1->getRawContent(
SlotRecord::MAIN )->serialize() );
355 $mainOutput = $updater1->getCanonicalParserOutput();
356 $this->assertContains(
'first', $mainOutput->getText() );
357 $this->assertContains(
'<a ', $mainOutput->getText() );
358 $this->assertNotEmpty( $mainOutput->getLinks() );
360 $canonicalOutput = $updater1->getCanonicalParserOutput();
361 $this->assertContains(
'first', $canonicalOutput->getText() );
362 $this->assertContains(
'<a ', $canonicalOutput->getText() );
363 $this->assertNotEmpty( $canonicalOutput->getLinks() );
370 $updater2->prepareUpdate( $rev2,
$options );
372 $this->assertFalse( $updater2->isCreation() );
373 $this->assertTrue( $updater2->isChange() );
375 $canonicalOutput = $updater2->getCanonicalParserOutput();
376 $this->assertContains(
'second', $canonicalOutput->getText() );
384 $page = $this->
getPage( __METHOD__ );
394 $canonicalOutput =
$updater->getCanonicalParserOutput();
401 $this->assertTrue(
$updater->isUpdatePrepared() );
402 $this->assertTrue(
$updater->isContentPrepared() );
405 $this->assertSame( $canonicalOutput,
$updater->getCanonicalParserOutput() );
414 $page = $this->
getPage( __METHOD__ );
424 $canonicalOutput =
$updater->getCanonicalParserOutput();
427 $mainOutput->setSpeculativeRevIdUsed( 0 );
428 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
435 $this->assertTrue(
$updater->isUpdatePrepared() );
436 $this->assertTrue(
$updater->isContentPrepared() );
440 $this->assertNotSame( $canonicalOutput,
$updater->getCanonicalParserOutput() );
443 $this->assertContains(
'--' .
$rev->getId() .
'--',
$html );
465 $canonicalOutput =
$updater->getCanonicalParserOutput();
467 $preparedEdit =
$updater->getPreparedEdit();
468 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
469 $this->assertSame( $canonicalOutput, $preparedEdit->output );
470 $this->assertSame( $mainContent, $preparedEdit->newContent );
472 $this->assertSame(
$updater->getCanonicalParserOptions(), $preparedEdit->popts );
473 $this->assertSame(
null, $preparedEdit->revid );
480 $clock = MWTimestamp::convert( TS_UNIX,
'20100101000000' );
481 MWTimestamp::setFakeTime(
function ()
use ( &$clock ) {
485 $page = $this->
getPage( __METHOD__ );
496 $canonicalOutput =
$updater->getCanonicalParserOutput();
498 $preparedEdit =
$updater->getPreparedEdit();
499 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
500 $this->assertSame( $canonicalOutput, $preparedEdit->output );
502 $this->assertSame(
$updater->getCanonicalParserOptions(), $preparedEdit->popts );
503 $this->assertSame(
$rev->getId(), $preparedEdit->revid );
508 $page = $this->
getPage( __METHOD__ );
518 $dataUpdates =
$updater->getSecondaryDataUpdates();
520 $this->assertNotEmpty( $dataUpdates );
522 $linksUpdates = array_filter( $dataUpdates,
function ( $du ) {
525 $this->assertCount( 1, $linksUpdates );
536 ->setConstructorArgs( [
$name ] )
538 [
'getSecondaryDataUpdates',
'getDeletionUpdates',
'unserializeContent' ]
543 $dataUpdate->_name =
"$name data update";
546 $deletionUpdate->_name =
"$name deletion update";
548 $handler->method(
'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
549 $handler->method(
'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
550 $handler->method(
'unserializeContent' )->willReturnCallback(
557 'wgContentHandlers', [
576 ->setConstructorArgs( [ $text ] )
577 ->setMethods( [
'getModel',
'getContentHandler' ] )
590 $this->markTestSkipped(
'Slot removal cannot happen with MCR being enabled' );
602 $page = $this->
getPage( __METHOD__ );
606 [
'main' => $mainContent1,
'aux' => $auxContent1 ]
611 $update->removeSlot(
'aux' );
613 $page = $this->
getPage( __METHOD__ );
617 $dataUpdates =
$updater->getSecondaryDataUpdates();
619 $this->assertNotEmpty( $dataUpdates );
621 $updateNames = array_map(
function ( $du ) {
622 return isset( $du->_name ) ? $du->_name : get_class( $du );
626 $this->assertContains(
'A1 deletion update', $updateNames );
627 $this->assertContains(
'M2 data update', $updateNames );
628 $this->assertNotContains(
'M1 data update', $updateNames );
653 $rev->applyUpdate( $update );
657 $rev->setParentId( $parentId );
672 ->disableOriginalConstructor()
674 $mock->expects( $this->
any() )
675 ->method(
'getDBkey' )
676 ->will( $this->returnValue( __CLASS__ ) );
677 $mock->expects( $this->
any() )
678 ->method(
'getArticleID' )
679 ->will( $this->returnValue( $id ) );
697 $update1b->modifyContent(
'xyz', $content1 );
711 '$prepRevision' =>
null,
712 '$prepUpdate' =>
null,
714 '$forRevision' =>
null,
715 '$forUpdate' =>
null,
716 '$forParent' =>
null,
717 '$isReusable' =>
true,
720 '$prepUser' => $user1,
721 '$prepRevision' => $rev1,
722 '$prepUpdate' => $update1,
724 '$forRevision' =>
null,
725 '$forUpdate' =>
null,
726 '$forParent' =>
null,
727 '$isReusable' =>
true,
729 yield
'unprepared' => [
731 '$prepRevision' =>
null,
732 '$prepUpdate' =>
null,
733 '$forUser' => $user1,
734 '$forRevision' => $rev1,
735 '$forUpdate' => $update1,
737 '$isReusable' =>
true,
739 yield
'match prepareContent' => [
740 '$prepUser' => $user1,
741 '$prepRevision' =>
null,
742 '$prepUpdate' => $update1,
743 '$forUser' => $user1,
744 '$forRevision' =>
null,
745 '$forUpdate' => $update1,
747 '$isReusable' =>
true,
749 yield
'match prepareUpdate' => [
751 '$prepRevision' => $rev1,
752 '$prepUpdate' =>
null,
753 '$forUser' => $user1,
754 '$forRevision' => $rev1,
755 '$forUpdate' =>
null,
757 '$isReusable' =>
true,
759 yield
'match all' => [
760 '$prepUser' => $user1,
761 '$prepRevision' => $rev1,
762 '$prepUpdate' => $update1,
763 '$forUser' => $user1,
764 '$forRevision' => $rev1,
765 '$forUpdate' => $update1,
767 '$isReusable' =>
true,
769 yield
'mismatch prepareContent update' => [
770 '$prepUser' => $user1,
771 '$prepRevision' =>
null,
772 '$prepUpdate' => $update1,
773 '$forUser' => $user1,
774 '$forRevision' =>
null,
775 '$forUpdate' => $update1b,
777 '$isReusable' =>
false,
779 yield
'mismatch prepareContent user' => [
780 '$prepUser' => $user1,
781 '$prepRevision' =>
null,
782 '$prepUpdate' => $update1,
783 '$forUser' => $user2,
784 '$forRevision' =>
null,
785 '$forUpdate' => $update1,
787 '$isReusable' =>
false,
789 yield
'mismatch prepareContent parent' => [
790 '$prepUser' => $user1,
791 '$prepRevision' =>
null,
792 '$prepUpdate' => $update1,
793 '$forUser' => $user1,
794 '$forRevision' =>
null,
795 '$forUpdate' => $update1,
797 '$isReusable' =>
false,
799 yield
'mismatch prepareUpdate revision update' => [
801 '$prepRevision' => $rev1,
802 '$prepUpdate' =>
null,
804 '$forRevision' => $rev1b,
805 '$forUpdate' =>
null,
807 '$isReusable' =>
false,
809 yield
'mismatch prepareUpdate revision user' => [
811 '$prepRevision' => $rev2,
812 '$prepUpdate' =>
null,
814 '$forRevision' => $rev2x,
815 '$forUpdate' =>
null,
817 '$isReusable' =>
false,
819 yield
'mismatch prepareUpdate revision id' => [
821 '$prepRevision' => $rev2,
822 '$prepUpdate' =>
null,
824 '$forRevision' => $rev2y,
825 '$forUpdate' =>
null,
827 '$isReusable' =>
false,
845 User $prepUser =
null,
848 User $forUser =
null,
857 $updater->prepareContent( $prepUser, $prepUpdate,
false );
860 if ( $prepRevision ) {
861 $updater->prepareUpdate( $prepRevision );
866 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
876 $page = $this->
getPage( __METHOD__ );
885 $pageId = $page->getId();
887 $oldStats = $this->db->selectRow(
'site_stats',
'*',
'1=1' );
888 $this->db->delete(
'pagelinks',
'*' );
891 $pcache->deleteOptionsKey( $page );
894 $updater->setArticleCountMethod(
'link' );
902 $pageLinks = $this->db->select(
905 [
'pl_from' => $pageId ],
907 [
'ORDER BY' =>
'pl_namespace, pl_title' ]
910 $pageLinksRow = $pageLinks->fetchObject();
911 $this->assertInternalType(
'object', $pageLinksRow );
912 $this->assertSame(
'Main', $pageLinksRow->pl_title );
915 $pageLinksRow = $pageLinks->fetchObject();
916 $this->assertInternalType(
'object', $pageLinksRow );
917 $this->assertSame(
'Nix', $pageLinksRow->pl_title );
921 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions() );
922 $this->assertInternalType(
'object', $cached );
923 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
926 $stats = $this->db->selectRow(
'site_stats',
'*',
'1=1' );
927 $this->assertSame( $oldStats->ss_total_pages + 1, (
int)$stats->ss_total_pages );
928 $this->assertSame( $oldStats->ss_total_edits + 1, (
int)$stats->ss_total_edits );
929 $this->assertSame( $oldStats->ss_good_articles + 1, (
int)$stats->ss_good_articles );
946 $page = $this->
getPage( __METHOD__ );
952 $update->modifyContent(
'main',
new WikitextContent(
'first [[Main]]' ) );
960 $pcache->deleteOptionsKey( $page );
963 $rev->setTimestamp(
'20100101000000' );
964 $rev->setParentId( $page->getLatest() );
974 $page->setTimestamp(
$rev->getTimestamp() );
978 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions(),
true );
979 $this->assertInternalType(
'object', $cached );
980 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
982 $this->assertSame(
$rev->getTimestamp(), $cached->getCacheTime() );
983 $this->assertSame(
$rev->getId(), $cached->getCacheRevisionId() );
986 $pcache->deleteOptionsKey( $page );
993 $page->setTimestamp(
$rev->getTimestamp() );
997 $cached = $pcache->get( $page,
$updater->getCanonicalParserOptions(),
true );
998 $this->assertInternalType(
'object', $cached );
999 $this->assertSame(
$updater->getCanonicalParserOutput(), $cached );
1001 $this->assertGreaterThan(
$rev->getTimestamp(), $cached->getCacheTime() );
1002 $this->assertSame(
$rev->getId(), $cached->getCacheRevisionId() );