2 use MediaWiki\MediaWikiServices
;
3 use MediaWiki\Storage\RevisionStore
;
4 use MediaWiki\Storage\IncompleteRevisionException
;
5 use MediaWiki\Storage\RevisionRecord
;
8 * RevisionDbTestBase contains test cases for the Revision class that have Database interactions.
13 abstract class RevisionDbTestBase
extends MediaWikiTestCase
{
16 * @var WikiPage $testPage
20 public function __construct( $name = null, array $data = [], $dataName = '' ) {
21 parent
::__construct( $name, $data, $dataName );
23 $this->tablesUsed
= array_merge( $this->tablesUsed
,
46 protected function setUp() {
51 $this->mergeMwGlobalArrayValue(
55 12313 => 'Dummy_talk',
59 $this->mergeMwGlobalArrayValue(
60 'wgNamespaceContentModels',
62 12312 => DummyContentForTesting
::MODEL_ID
,
66 $this->mergeMwGlobalArrayValue(
69 DummyContentForTesting
::MODEL_ID
=> 'DummyContentHandlerForTesting',
70 RevisionTestModifyableContent
::MODEL_ID
=> 'RevisionTestModifyableContentHandler',
74 $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
76 MWNamespace
::clearCaches();
77 // Reset namespace cache
78 $wgContLang->resetNamespaces();
80 if ( !$this->testPage
) {
82 * We have to create a new page for each subclass as the page creation may result
83 * in different DB fields being filled based on configuration.
85 $this->testPage
= $this->createPage( __CLASS__
, __CLASS__
);
89 protected function tearDown() {
94 MWNamespace
::clearCaches();
95 // Reset namespace cache
96 $wgContLang->resetNamespaces();
99 abstract protected function getContentHandlerUseDB();
101 private function makeRevisionWithProps( $props = null ) {
102 if ( $props === null ) {
106 if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
107 $props['text'] = 'Lorem Ipsum';
110 if ( !isset( $props['user_text'] ) ) {
111 $user = $this->getTestUser()->getUser();
112 $props['user_text'] = $user->getName();
113 $props['user'] = $user->getId();
116 if ( !isset( $props['user'] ) ) {
120 if ( !isset( $props['comment'] ) ) {
121 $props['comment'] = 'just a test';
124 if ( !isset( $props['page'] ) ) {
125 $props['page'] = $this->testPage
->getId();
128 if ( !isset( $props['content_model'] ) ) {
129 $props['content_model'] = CONTENT_MODEL_WIKITEXT
;
132 $rev = new Revision( $props );
134 $dbw = wfGetDB( DB_MASTER
);
135 $rev->insertOn( $dbw );
141 * @param string $titleString
142 * @param string $text
143 * @param string|null $model
147 private function createPage( $titleString, $text, $model = null ) {
148 if ( !preg_match( '/:/', $titleString ) &&
149 ( $model === null ||
$model === CONTENT_MODEL_WIKITEXT
)
151 $ns = $this->getDefaultWikitextNS();
152 $titleString = MWNamespace
::getCanonicalName( $ns ) . ':' . $titleString;
155 $title = Title
::newFromText( $titleString );
156 $wikipage = new WikiPage( $title );
158 // Delete the article if it already exists
159 if ( $wikipage->exists() ) {
160 $wikipage->doDeleteArticle( "done" );
163 $content = ContentHandler
::makeContent( $text, $title, $model );
164 $wikipage->doEditContent( $content, __METHOD__
, EDIT_NEW
);
169 private function assertRevEquals( Revision
$orig, Revision
$rev = null ) {
170 $this->assertNotNull( $rev, 'missing revision' );
172 $this->assertEquals( $orig->getId(), $rev->getId() );
173 $this->assertEquals( $orig->getPage(), $rev->getPage() );
174 $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
175 $this->assertEquals( $orig->getUser(), $rev->getUser() );
176 $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
177 $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
178 $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
182 * @covers Revision::getRecentChange
184 public function testGetRecentChange() {
185 $rev = $this->testPage
->getRevision();
186 $recentChange = $rev->getRecentChange();
188 // Make sure various attributes look right / the correct entry has been retrieved.
189 $this->assertEquals( $rev->getTimestamp(), $recentChange->getAttribute( 'rc_timestamp' ) );
191 $rev->getTitle()->getNamespace(),
192 $recentChange->getAttribute( 'rc_namespace' )
195 $rev->getTitle()->getDBkey(),
196 $recentChange->getAttribute( 'rc_title' )
198 $this->assertEquals( $rev->getUser(), $recentChange->getAttribute( 'rc_user' ) );
199 $this->assertEquals( $rev->getUserText(), $recentChange->getAttribute( 'rc_user_text' ) );
200 $this->assertEquals( $rev->getComment(), $recentChange->getAttribute( 'rc_comment' ) );
201 $this->assertEquals( $rev->getPage(), $recentChange->getAttribute( 'rc_cur_id' ) );
202 $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
206 * @covers Revision::insertOn
208 public function testInsertOn_success() {
209 $parentId = $this->testPage
->getLatest();
211 // If an ExternalStore is set don't use it.
212 $this->setMwGlobals( 'wgDefaultExternalStore', false );
214 $rev = new Revision( [
215 'page' => $this->testPage
->getId(),
216 'title' => $this->testPage
->getTitle(),
217 'text' => 'Revision Text',
218 'comment' => 'Revision comment',
221 $revId = $rev->insertOn( wfGetDB( DB_MASTER
) );
223 $this->assertInternalType( 'integer', $revId );
224 $this->assertSame( $revId, $rev->getId() );
226 // getTextId() must be an int!
227 $this->assertInternalType( 'integer', $rev->getTextId() );
229 $mainSlot = $rev->getRevisionRecord()->getSlot( 'main', RevisionRecord
::RAW
);
231 // we currently only support storage in the text table
232 $textId = MediaWikiServices
::getInstance()
234 ->getTextIdFromAddress( $mainSlot->getAddress() );
238 [ 'old_id', 'old_text' ],
240 [ [ strval( $textId ), 'Revision Text' ] ]
254 "rev_id = {$rev->getId()}",
256 strval( $rev->getId() ),
257 strval( $this->testPage
->getId() ),
263 's0ngbdoxagreuf2vjtuxzwdz64n29xm',
269 * @covers Revision::insertOn
271 public function testInsertOn_exceptionOnNoPage() {
272 // If an ExternalStore is set don't use it.
273 $this->setMwGlobals( 'wgDefaultExternalStore', false );
274 $this->setExpectedException(
275 IncompleteRevisionException
::class,
276 "rev_page field must not be 0!"
279 $title = Title
::newFromText( 'Nonexistant-' . __METHOD__
);
280 $rev = new Revision( [], 0, $title );
282 $rev->insertOn( wfGetDB( DB_MASTER
) );
286 * @covers Revision::newFromTitle
288 public function testNewFromTitle_withoutId() {
289 $latestRevId = $this->testPage
->getLatest();
291 $rev = Revision
::newFromTitle( $this->testPage
->getTitle() );
293 $this->assertTrue( $this->testPage
->getTitle()->equals( $rev->getTitle() ) );
294 $this->assertEquals( $latestRevId, $rev->getId() );
298 * @covers Revision::newFromTitle
300 public function testNewFromTitle_withId() {
301 $latestRevId = $this->testPage
->getLatest();
303 $rev = Revision
::newFromTitle( $this->testPage
->getTitle(), $latestRevId );
305 $this->assertTrue( $this->testPage
->getTitle()->equals( $rev->getTitle() ) );
306 $this->assertEquals( $latestRevId, $rev->getId() );
310 * @covers Revision::newFromTitle
312 public function testNewFromTitle_withBadId() {
313 $latestRevId = $this->testPage
->getLatest();
315 $rev = Revision
::newFromTitle( $this->testPage
->getTitle(), $latestRevId +
1 );
317 $this->assertNull( $rev );
321 * @covers Revision::newFromRow
323 public function testNewFromRow() {
324 $orig = $this->makeRevisionWithProps();
326 $dbr = wfGetDB( DB_REPLICA
);
327 $revQuery = Revision
::getQueryInfo();
328 $res = $dbr->select( $revQuery['tables'], $revQuery['fields'], [ 'rev_id' => $orig->getId() ],
329 __METHOD__
, [], $revQuery['joins'] );
330 $this->assertTrue( is_object( $res ), 'query failed' );
332 $row = $res->fetchObject();
335 $rev = Revision
::newFromRow( $row );
337 $this->assertRevEquals( $orig, $rev );
340 public function provideNewFromArchiveRow() {
348 return $f +
[ 'ar_namespace', 'ar_title' ];
353 unset( $f['ar_text_id'] );
359 unset( $f['ar_page_id'] );
365 unset( $f['ar_parent_id'] );
371 unset( $f['ar_rev_id'] );
377 unset( $f['ar_sha1'] );
384 * @dataProvider provideNewFromArchiveRow
385 * @covers Revision::newFromArchiveRow
387 public function testNewFromArchiveRow( $selectModifier ) {
388 $services = MediaWikiServices
::getInstance();
390 $store = new RevisionStore(
391 $services->getDBLoadBalancer(),
392 $services->getService( '_SqlBlobStore' ),
393 $services->getMainWANObjectCache(),
394 $services->getCommentStore(),
395 $services->getActorMigration()
398 $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
399 $this->setService( 'RevisionStore', $store );
401 $page = $this->createPage(
402 'RevisionStorageTest_testNewFromArchiveRow',
404 CONTENT_MODEL_WIKITEXT
406 $orig = $page->getRevision();
407 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
409 $dbr = wfGetDB( DB_REPLICA
);
410 $arQuery = Revision
::getArchiveQueryInfo();
411 $arQuery['fields'] = $selectModifier( $arQuery['fields'] );
413 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
414 __METHOD__
, [], $arQuery['joins']
416 $this->assertTrue( is_object( $res ), 'query failed' );
418 $row = $res->fetchObject();
421 // MCR migration note: $row is now required to contain ar_title and ar_namespace.
422 // Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
423 $rev = Revision
::newFromArchiveRow( $row );
425 $this->assertRevEquals( $orig, $rev );
429 * @covers Revision::newFromArchiveRow
431 public function testNewFromArchiveRowOverrides() {
432 $page = $this->createPage(
433 'RevisionStorageTest_testNewFromArchiveRow',
435 CONTENT_MODEL_WIKITEXT
437 $orig = $page->getRevision();
438 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
440 $dbr = wfGetDB( DB_REPLICA
);
441 $arQuery = Revision
::getArchiveQueryInfo();
443 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
444 __METHOD__
, [], $arQuery['joins']
446 $this->assertTrue( is_object( $res ), 'query failed' );
448 $row = $res->fetchObject();
451 $rev = Revision
::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
453 $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
454 $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
458 * @covers Revision::newFromId
460 public function testNewFromId() {
461 $orig = $this->testPage
->getRevision();
462 $rev = Revision
::newFromId( $orig->getId() );
463 $this->assertRevEquals( $orig, $rev );
467 * @covers Revision::newFromPageId
469 public function testNewFromPageId() {
470 $rev = Revision
::newFromPageId( $this->testPage
->getId() );
471 $this->assertRevEquals(
472 $this->testPage
->getRevision(),
478 * @covers Revision::newFromPageId
480 public function testNewFromPageIdWithLatestId() {
481 $rev = Revision
::newFromPageId(
482 $this->testPage
->getId(),
483 $this->testPage
->getLatest()
485 $this->assertRevEquals(
486 $this->testPage
->getRevision(),
492 * @covers Revision::newFromPageId
494 public function testNewFromPageIdWithNotLatestId() {
495 $content = new WikitextContent( __METHOD__
);
496 $this->testPage
->doEditContent( $content, __METHOD__
);
497 $rev = Revision
::newFromPageId(
498 $this->testPage
->getId(),
499 $this->testPage
->getRevision()->getPrevious()->getId()
501 $this->assertRevEquals(
502 $this->testPage
->getRevision()->getPrevious(),
508 * @covers Revision::fetchRevision
510 public function testFetchRevision() {
511 // Hidden process cache assertion below
512 $this->testPage
->getRevision()->getId();
514 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
515 $id = $this->testPage
->getRevision()->getId();
517 $this->hideDeprecated( 'Revision::fetchRevision' );
518 $res = Revision
::fetchRevision( $this->testPage
->getTitle() );
520 # note: order is unspecified
522 while ( ( $row = $res->fetchObject() ) ) {
523 $rows[$row->rev_id
] = $row;
526 $this->assertEmpty( $rows, 'expected empty set' );
530 * @covers Revision::getPage
532 public function testGetPage() {
533 $page = $this->testPage
;
535 $orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
536 $rev = Revision
::newFromId( $orig->getId() );
538 $this->assertEquals( $page->getId(), $rev->getPage() );
542 * @covers Revision::isCurrent
544 public function testIsCurrent() {
545 $rev1 = $this->testPage
->getRevision();
547 # @todo find out if this should be true
548 # $this->assertTrue( $rev1->isCurrent() );
550 $rev1x = Revision
::newFromId( $rev1->getId() );
551 $this->assertTrue( $rev1x->isCurrent() );
553 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
554 $rev2 = $this->testPage
->getRevision();
556 # @todo find out if this should be true
557 # $this->assertTrue( $rev2->isCurrent() );
559 $rev1x = Revision
::newFromId( $rev1->getId() );
560 $this->assertFalse( $rev1x->isCurrent() );
562 $rev2x = Revision
::newFromId( $rev2->getId() );
563 $this->assertTrue( $rev2x->isCurrent() );
567 * @covers Revision::getPrevious
569 public function testGetPrevious() {
570 $oldestRevision = $this->testPage
->getOldestRevision();
571 $latestRevision = $this->testPage
->getLatest();
573 $this->assertNull( $oldestRevision->getPrevious() );
575 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
576 $newRevision = $this->testPage
->getRevision();
578 $this->assertNotNull( $newRevision->getPrevious() );
579 $this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
583 * @covers Revision::getNext
585 public function testGetNext() {
586 $rev1 = $this->testPage
->getRevision();
588 $this->assertNull( $rev1->getNext() );
590 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
591 $rev2 = $this->testPage
->getRevision();
593 $this->assertNotNull( $rev1->getNext() );
594 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
598 * @covers Revision::newNullRevision
600 public function testNewNullRevision() {
601 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
602 $orig = $this->testPage
->getRevision();
604 $dbw = wfGetDB( DB_MASTER
);
605 $rev = Revision
::newNullRevision( $dbw, $this->testPage
->getId(), 'a null revision', false );
607 $this->assertNotEquals( $orig->getId(), $rev->getId(),
608 'new null revision should have a different id from the original revision' );
609 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
610 'new null revision should have the same text id as the original revision' );
611 $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
612 'new null revision should have the same SHA1 as the original revision' );
613 $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
614 'new null revision should have the same content as the original revision' );
615 $this->assertEquals( __METHOD__
, $rev->getContent()->getNativeData() );
619 * @covers Revision::newNullRevision
621 public function testNewNullRevision_badPage() {
622 $dbw = wfGetDB( DB_MASTER
);
623 $rev = Revision
::newNullRevision( $dbw, -1, 'a null revision', false );
625 $this->assertNull( $rev );
629 * @covers Revision::insertOn
631 public function testInsertOn() {
632 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
634 $orig = $this->makeRevisionWithProps( [
638 // Make sure the revision was copied to ip_changes
639 $dbr = wfGetDB( DB_REPLICA
);
640 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
641 $row = $res->fetchObject();
643 $this->assertEquals( IP
::toHex( $ip ), $row->ipc_hex
);
645 $orig->getTimestamp(),
646 wfTimestamp( TS_MW
, $row->ipc_rev_timestamp
)
650 public static function provideUserWasLastToEdit() {
651 yield
'actually the last edit' => [ 3, true ];
652 yield
'not the current edit, but still by this user' => [ 2, true ];
653 yield
'edit by another user' => [ 1, false ];
654 yield
'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
658 * @covers Revision::userWasLastToEdit
659 * @dataProvider provideUserWasLastToEdit
661 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
662 $userA = User
::newFromName( "RevisionStorageTest_userA" );
663 $userB = User
::newFromName( "RevisionStorageTest_userB" );
665 if ( $userA->getId() === 0 ) {
666 $userA = User
::createNew( $userA->getName() );
669 if ( $userB->getId() === 0 ) {
670 $userB = User
::createNew( $userB->getName() );
673 $ns = $this->getDefaultWikitextNS();
675 $dbw = wfGetDB( DB_MASTER
);
678 // create revisions -----------------------------
679 $page = WikiPage
::factory( Title
::newFromText(
680 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
681 $page->insertOn( $dbw );
683 $revisions[0] = new Revision( [
684 'page' => $page->getId(),
685 // we need the title to determine the page's default content model
686 'title' => $page->getTitle(),
687 'timestamp' => '20120101000000',
688 'user' => $userA->getId(),
690 'content_model' => CONTENT_MODEL_WIKITEXT
,
691 'comment' => 'edit zero'
693 $revisions[0]->insertOn( $dbw );
695 $revisions[1] = new Revision( [
696 'page' => $page->getId(),
697 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
698 'title' => $page->getTitle(),
699 'timestamp' => '20120101000100',
700 'user' => $userA->getId(),
702 'content_model' => CONTENT_MODEL_WIKITEXT
,
703 'comment' => 'edit one'
705 $revisions[1]->insertOn( $dbw );
707 $revisions[2] = new Revision( [
708 'page' => $page->getId(),
709 'title' => $page->getTitle(),
710 'timestamp' => '20120101000200',
711 'user' => $userB->getId(),
713 'content_model' => CONTENT_MODEL_WIKITEXT
,
714 'comment' => 'edit two'
716 $revisions[2]->insertOn( $dbw );
718 $revisions[3] = new Revision( [
719 'page' => $page->getId(),
720 'title' => $page->getTitle(),
721 'timestamp' => '20120101000300',
722 'user' => $userA->getId(),
724 'content_model' => CONTENT_MODEL_WIKITEXT
,
725 'comment' => 'edit three'
727 $revisions[3]->insertOn( $dbw );
729 $revisions[4] = new Revision( [
730 'page' => $page->getId(),
731 'title' => $page->getTitle(),
732 'timestamp' => '20120101000200',
733 'user' => $userA->getId(),
735 'content_model' => CONTENT_MODEL_WIKITEXT
,
736 'comment' => 'edit four'
738 $revisions[4]->insertOn( $dbw );
740 // test it ---------------------------------
741 $since = $revisions[$sinceIdx]->getTimestamp();
743 $revQuery = Revision
::getQueryInfo();
744 $allRows = iterator_to_array( $dbw->select(
746 [ 'rev_id', 'rev_timestamp', 'rev_user' => $revQuery['fields']['rev_user'] ],
748 'rev_page' => $page->getId(),
749 //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
752 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
756 $wasLast = Revision
::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
758 $this->assertEquals( $expectedLast, $wasLast );
762 * @param string $text
763 * @param string $title
764 * @param string $model
765 * @param string $format
769 private function newTestRevision( $text, $title = "Test",
770 $model = CONTENT_MODEL_WIKITEXT
, $format = null
772 if ( is_string( $title ) ) {
773 $title = Title
::newFromText( $title );
776 $content = ContentHandler
::makeContent( $text, $title, $model, $format );
784 'content' => $content,
785 'length' => $content->getSize(),
786 'comment' => "testing",
787 'minor_edit' => false,
789 'content_format' => $format,
796 public function provideGetContentModel() {
797 // NOTE: we expect the help namespace to always contain wikitext
799 [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT
],
800 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS
],
801 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting
::MODEL_ID
],
806 * @dataProvider provideGetContentModel
807 * @covers Revision::getContentModel
809 public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
810 $rev = $this->newTestRevision( $text, $title, $model, $format );
812 $this->assertEquals( $expectedModel, $rev->getContentModel() );
815 public function provideGetContentFormat() {
816 // NOTE: we expect the help namespace to always contain wikitext
818 [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT
],
819 [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS
, null, CONTENT_FORMAT_CSS
],
820 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS
],
821 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting
::MODEL_ID
],
826 * @dataProvider provideGetContentFormat
827 * @covers Revision::getContentFormat
829 public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
830 $rev = $this->newTestRevision( $text, $title, $model, $format );
832 $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
835 public function provideGetContentHandler() {
836 // NOTE: we expect the help namespace to always contain wikitext
838 [ 'hello world', 'Help:Hello', null, null, WikitextContentHandler
::class ],
839 [ 'hello world', 'User:hello/there.css', null, null, CssContentHandler
::class ],
840 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentHandlerForTesting
::class ],
845 * @dataProvider provideGetContentHandler
846 * @covers Revision::getContentHandler
848 public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
849 $rev = $this->newTestRevision( $text, $title, $model, $format );
851 $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
854 public function provideGetContent() {
855 // NOTE: we expect the help namespace to always contain wikitext
857 [ 'hello world', 'Help:Hello', null, null, Revision
::FOR_PUBLIC
, 'hello world' ],
859 serialize( 'hello world' ),
861 DummyContentForTesting
::MODEL_ID
,
863 Revision
::FOR_PUBLIC
,
864 serialize( 'hello world' )
867 serialize( 'hello world' ),
871 Revision
::FOR_PUBLIC
,
872 serialize( 'hello world' )
878 * @dataProvider provideGetContent
879 * @covers Revision::getContent
881 public function testGetContent( $text, $title, $model, $format,
882 $audience, $expectedSerialization
884 $rev = $this->newTestRevision( $text, $title, $model, $format );
885 $content = $rev->getContent( $audience );
888 $expectedSerialization,
889 is_null( $content ) ?
null : $content->serialize( $format )
894 * @covers Revision::getContent
896 public function testGetContent_failure() {
897 $rev = new Revision( [
898 'page' => $this->testPage
->getId(),
899 'content_model' => $this->testPage
->getContentModel(),
900 'text_id' => 123456789, // not in the test DB
903 Wikimedia\
suppressWarnings(); // bad text_id will trigger a warning.
905 $this->assertNull( $rev->getContent(),
906 "getContent() should return null if the revision's text blob could not be loaded." );
908 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
909 $this->assertNull( $rev->getContent(),
910 "getContent() should return null if the revision's text blob could not be loaded." );
912 Wikimedia\restoreWarnings
();
915 public function provideGetSize() {
917 [ "hello world.", CONTENT_MODEL_WIKITEXT
, 12 ],
918 [ serialize( "hello world." ), DummyContentForTesting
::MODEL_ID
, 12 ],
923 * @covers Revision::getSize
924 * @dataProvider provideGetSize
926 public function testGetSize( $text, $model, $expected_size ) {
927 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
928 $this->assertEquals( $expected_size, $rev->getSize() );
931 public function provideGetSha1() {
933 [ "hello world.", CONTENT_MODEL_WIKITEXT
, Revision
::base36Sha1( "hello world." ) ],
935 serialize( "hello world." ),
936 DummyContentForTesting
::MODEL_ID
,
937 Revision
::base36Sha1( serialize( "hello world." ) )
943 * @covers Revision::getSha1
944 * @dataProvider provideGetSha1
946 public function testGetSha1( $text, $model, $expected_hash ) {
947 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
948 $this->assertEquals( $expected_hash, $rev->getSha1() );
952 * Tests whether $rev->getContent() returns a clone when needed.
954 * @covers Revision::getContent
956 public function testGetContentClone() {
957 $content = new RevisionTestModifyableContent( "foo" );
963 'title' => Title
::newFromText( "testGetContentClone_dummy" ),
965 'content' => $content,
966 'length' => $content->getSize(),
967 'comment' => "testing",
968 'minor_edit' => false,
972 /** @var RevisionTestModifyableContent $content */
973 $content = $rev->getContent( Revision
::RAW
);
974 $content->setText( "bar" );
976 /** @var RevisionTestModifyableContent $content2 */
977 $content2 = $rev->getContent( Revision
::RAW
);
978 // content is mutable, expect clone
979 $this->assertNotSame( $content, $content2, "expected a clone" );
980 // clone should contain the original text
981 $this->assertEquals( "foo", $content2->getText() );
983 $content2->setText( "bla bla" );
984 // clones should be independent
985 $this->assertEquals( "bar", $content->getText() );
989 * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
990 * @covers Revision::getContent
992 public function testGetContentUncloned() {
993 $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT
);
994 $content = $rev->getContent( Revision
::RAW
);
995 $content2 = $rev->getContent( Revision
::RAW
);
997 // for immutable content like wikitext, this should be the same object
998 $this->assertSame( $content, $content2 );
1002 * @covers Revision::loadFromId
1004 public function testLoadFromId() {
1005 $rev = $this->testPage
->getRevision();
1006 $this->hideDeprecated( 'Revision::loadFromId' );
1007 $this->assertRevEquals(
1009 Revision
::loadFromId( wfGetDB( DB_MASTER
), $rev->getId() )
1014 * @covers Revision::loadFromPageId
1016 public function testLoadFromPageId() {
1017 $this->assertRevEquals(
1018 $this->testPage
->getRevision(),
1019 Revision
::loadFromPageId( wfGetDB( DB_MASTER
), $this->testPage
->getId() )
1024 * @covers Revision::loadFromPageId
1026 public function testLoadFromPageIdWithLatestRevId() {
1027 $this->assertRevEquals(
1028 $this->testPage
->getRevision(),
1029 Revision
::loadFromPageId(
1030 wfGetDB( DB_MASTER
),
1031 $this->testPage
->getId(),
1032 $this->testPage
->getLatest()
1038 * @covers Revision::loadFromPageId
1040 public function testLoadFromPageIdWithNotLatestRevId() {
1041 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
1042 $this->assertRevEquals(
1043 $this->testPage
->getRevision()->getPrevious(),
1044 Revision
::loadFromPageId(
1045 wfGetDB( DB_MASTER
),
1046 $this->testPage
->getId(),
1047 $this->testPage
->getRevision()->getPrevious()->getId()
1053 * @covers Revision::loadFromTitle
1055 public function testLoadFromTitle() {
1056 $this->assertRevEquals(
1057 $this->testPage
->getRevision(),
1058 Revision
::loadFromTitle( wfGetDB( DB_MASTER
), $this->testPage
->getTitle() )
1063 * @covers Revision::loadFromTitle
1065 public function testLoadFromTitleWithLatestRevId() {
1066 $this->assertRevEquals(
1067 $this->testPage
->getRevision(),
1068 Revision
::loadFromTitle(
1069 wfGetDB( DB_MASTER
),
1070 $this->testPage
->getTitle(),
1071 $this->testPage
->getLatest()
1077 * @covers Revision::loadFromTitle
1079 public function testLoadFromTitleWithNotLatestRevId() {
1080 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
1081 $this->assertRevEquals(
1082 $this->testPage
->getRevision()->getPrevious(),
1083 Revision
::loadFromTitle(
1084 wfGetDB( DB_MASTER
),
1085 $this->testPage
->getTitle(),
1086 $this->testPage
->getRevision()->getPrevious()->getId()
1092 * @covers Revision::loadFromTimestamp()
1094 public function testLoadFromTimestamp() {
1095 $this->assertRevEquals(
1096 $this->testPage
->getRevision(),
1097 Revision
::loadFromTimestamp(
1098 wfGetDB( DB_MASTER
),
1099 $this->testPage
->getTitle(),
1100 $this->testPage
->getRevision()->getTimestamp()
1106 * @covers Revision::getParentLengths
1108 public function testGetParentLengths_noRevIds() {
1111 Revision
::getParentLengths(
1112 wfGetDB( DB_MASTER
),
1119 * @covers Revision::getParentLengths
1121 public function testGetParentLengths_oneRevId() {
1122 $text = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1123 $textLength = strlen( $text );
1125 $this->testPage
->doEditContent( new WikitextContent( $text ), __METHOD__
);
1126 $rev[1] = $this->testPage
->getLatest();
1129 [ $rev[1] => $textLength ],
1130 Revision
::getParentLengths(
1131 wfGetDB( DB_MASTER
),
1138 * @covers Revision::getParentLengths
1140 public function testGetParentLengths_multipleRevIds() {
1141 $textOne = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1142 $textOneLength = strlen( $textOne );
1143 $textTwo = '831jr091jr092121j09rj1';
1144 $textTwoLength = strlen( $textTwo );
1146 $this->testPage
->doEditContent( new WikitextContent( $textOne ), __METHOD__
);
1147 $rev[1] = $this->testPage
->getLatest();
1148 $this->testPage
->doEditContent( new WikitextContent( $textTwo ), __METHOD__
);
1149 $rev[2] = $this->testPage
->getLatest();
1152 [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
1153 Revision
::getParentLengths(
1154 wfGetDB( DB_MASTER
),
1155 [ $rev[1], $rev[2] ]
1161 * @covers Revision::getTitle
1163 public function testGetTitle_fromExistingRevision() {
1165 $this->testPage
->getTitle()->equals(
1166 $this->testPage
->getRevision()->getTitle()
1172 * @covers Revision::getTitle
1174 public function testGetTitle_fromRevisionWhichWillLoadTheTitle() {
1175 $rev = new Revision( [ 'id' => $this->testPage
->getLatest() ] );
1177 $this->testPage
->getTitle()->equals(
1184 * @covers Revision::isMinor
1186 public function testIsMinor_true() {
1187 // Use a sysop to ensure we can mark edits as minor
1188 $sysop = $this->getTestSysop()->getUser();
1190 $this->testPage
->doEditContent(
1191 new WikitextContent( __METHOD__
),
1197 $rev = $this->testPage
->getRevision();
1199 $this->assertSame( true, $rev->isMinor() );
1203 * @covers Revision::isMinor
1205 public function testIsMinor_false() {
1206 $this->testPage
->doEditContent(
1207 new WikitextContent( __METHOD__
),
1211 $rev = $this->testPage
->getRevision();
1213 $this->assertSame( false, $rev->isMinor() );
1217 * @covers Revision::getTimestamp
1219 public function testGetTimestamp() {
1220 $testTimestamp = wfTimestampNow();
1222 $this->testPage
->doEditContent(
1223 new WikitextContent( __METHOD__
),
1226 $rev = $this->testPage
->getRevision();
1228 $this->assertInternalType( 'string', $rev->getTimestamp() );
1229 $this->assertTrue( strlen( $rev->getTimestamp() ) == strlen( 'YYYYMMDDHHMMSS' ) );
1230 $this->assertContains( substr( $testTimestamp, 0, 10 ), $rev->getTimestamp() );
1234 * @covers Revision::getUser
1235 * @covers Revision::getUserText
1237 public function testGetUserAndText() {
1238 $sysop = $this->getTestSysop()->getUser();
1240 $this->testPage
->doEditContent(
1241 new WikitextContent( __METHOD__
),
1247 $rev = $this->testPage
->getRevision();
1249 $this->assertSame( $sysop->getId(), $rev->getUser() );
1250 $this->assertSame( $sysop->getName(), $rev->getUserText() );
1254 * @covers Revision::isDeleted
1256 public function testIsDeleted_nothingDeleted() {
1257 $rev = $this->testPage
->getRevision();
1259 $this->assertSame( false, $rev->isDeleted( Revision
::DELETED_TEXT
) );
1260 $this->assertSame( false, $rev->isDeleted( Revision
::DELETED_COMMENT
) );
1261 $this->assertSame( false, $rev->isDeleted( Revision
::DELETED_RESTRICTED
) );
1262 $this->assertSame( false, $rev->isDeleted( Revision
::DELETED_USER
) );
1266 * @covers Revision::getVisibility
1268 public function testGetVisibility_nothingDeleted() {
1269 $rev = $this->testPage
->getRevision();
1271 $this->assertSame( 0, $rev->getVisibility() );
1275 * @covers Revision::getComment
1277 public function testGetComment_notDeleted() {
1278 $expectedSummary = 'goatlicious summary';
1280 $this->testPage
->doEditContent(
1281 new WikitextContent( __METHOD__
),
1284 $rev = $this->testPage
->getRevision();
1286 $this->assertSame( $expectedSummary, $rev->getComment() );
1290 * @covers Revision::isUnpatrolled
1292 public function testIsUnpatrolled_returnsRecentChangesId() {
1293 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
1294 $rev = $this->testPage
->getRevision();
1296 $this->assertGreaterThan( 0, $rev->isUnpatrolled() );
1297 $this->assertSame( $rev->getRecentChange()->getAttribute( 'rc_id' ), $rev->isUnpatrolled() );
1301 * @covers Revision::isUnpatrolled
1303 public function testIsUnpatrolled_returnsZeroIfPatrolled() {
1304 // This assumes that sysops are auto patrolled
1305 $sysop = $this->getTestSysop()->getUser();
1306 $this->testPage
->doEditContent(
1307 new WikitextContent( __METHOD__
),
1313 $rev = $this->testPage
->getRevision();
1315 $this->assertSame( 0, $rev->isUnpatrolled() );
1319 * This is a simple blanket test for all simple content getters and is methods to provide some
1320 * coverage before the split of Revision into multiple classes for MCR work.
1321 * @covers Revision::getContent
1322 * @covers Revision::getSerializedData
1323 * @covers Revision::getContentModel
1324 * @covers Revision::getContentFormat
1325 * @covers Revision::getContentHandler
1327 public function testSimpleContentGetters() {
1328 $expectedText = 'testSimpleContentGetters in Revision. Goats love MCR...';
1329 $expectedSummary = 'goatlicious testSimpleContentGetters summary';
1331 $this->testPage
->doEditContent(
1332 new WikitextContent( $expectedText ),
1335 $rev = $this->testPage
->getRevision();
1337 $this->assertSame( $expectedText, $rev->getContent()->getNativeData() );
1338 $this->assertSame( $expectedText, $rev->getSerializedData() );
1339 $this->assertSame( $this->testPage
->getContentModel(), $rev->getContentModel() );
1340 $this->assertSame( $this->testPage
->getContent()->getDefaultFormat(), $rev->getContentFormat() );
1341 $this->assertSame( $this->testPage
->getContentHandler(), $rev->getContentHandler() );
1345 * @covers Revision::newKnownCurrent
1347 public function testNewKnownCurrent() {
1348 // Setup the services
1349 $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
1350 $this->setService( 'MainWANObjectCache', $cache );
1351 $db = wfGetDB( DB_MASTER
);
1353 // Get a fresh revision to use during testing
1354 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
1355 $rev = $this->testPage
->getRevision();
1357 // Clear any previous cache for the revision during creation
1358 $key = $cache->makeGlobalKey( 'revision-row-1.29',
1363 $cache->delete( $key, WANObjectCache
::HOLDOFF_NONE
);
1364 $this->assertFalse( $cache->get( $key ) );
1366 // Get the new revision and make sure it is in the cache and correct
1367 $newRev = Revision
::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
1368 $this->assertRevEquals( $rev, $newRev );
1370 $cachedRow = $cache->get( $key );
1371 $this->assertNotFalse( $cachedRow );
1372 $this->assertEquals( $rev->getId(), $cachedRow->rev_id
);
1375 public function testNewKnownCurrent_withPageId() {
1376 $db = wfGetDB( DB_MASTER
);
1378 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
1379 $rev = $this->testPage
->getRevision();
1381 $pageId = $this->testPage
->getId();
1383 $newRev = Revision
::newKnownCurrent( $db, $pageId, $rev->getId() );
1384 $this->assertRevEquals( $rev, $newRev );
1387 public function testNewKnownCurrent_returnsFalseWhenTitleDoesntExist() {
1388 $db = wfGetDB( DB_MASTER
);
1390 $this->assertFalse( Revision
::newKnownCurrent( $db, 0 ) );
1393 public function provideUserCanBitfield() {
1394 yield
[ 0, 0, [], null, true ];
1395 // Bitfields match, user has no permissions
1396 yield
[ Revision
::DELETED_TEXT
, Revision
::DELETED_TEXT
, [], null, false ];
1397 yield
[ Revision
::DELETED_COMMENT
, Revision
::DELETED_COMMENT
, [], null, false ];
1398 yield
[ Revision
::DELETED_USER
, Revision
::DELETED_USER
, [], null, false ];
1399 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [], null, false ];
1400 // Bitfields match, user (admin) does have permissions
1401 yield
[ Revision
::DELETED_TEXT
, Revision
::DELETED_TEXT
, [ 'sysop' ], null, true ];
1402 yield
[ Revision
::DELETED_COMMENT
, Revision
::DELETED_COMMENT
, [ 'sysop' ], null, true ];
1403 yield
[ Revision
::DELETED_USER
, Revision
::DELETED_USER
, [ 'sysop' ], null, true ];
1404 // Bitfields match, user (admin) does not have permissions
1405 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [ 'sysop' ], null, false ];
1406 // Bitfields match, user (oversight) does have permissions
1407 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [ 'oversight' ], null, true ];
1408 // Check permissions using the title
1410 Revision
::DELETED_TEXT
,
1411 Revision
::DELETED_TEXT
,
1413 Title
::newFromText( __METHOD__
),
1417 Revision
::DELETED_TEXT
,
1418 Revision
::DELETED_TEXT
,
1420 Title
::newFromText( __METHOD__
),
1426 * @dataProvider provideUserCanBitfield
1427 * @covers Revision::userCanBitfield
1429 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
1430 $this->setMwGlobals(
1431 'wgGroupPermissions',
1434 'deletedtext' => true,
1435 'deletedhistory' => true,
1438 'viewsuppressed' => true,
1439 'suppressrevision' => true,
1443 $user = $this->getTestUser( $userGroups )->getUser();
1447 Revision
::userCanBitfield( $bitField, $field, $user, $title )
1450 // Fallback to $wgUser
1451 $this->setMwGlobals(
1457 Revision
::userCanBitfield( $bitField, $field, null, $title )
1461 public function provideUserCan() {
1462 yield
[ 0, 0, [], true ];
1463 // Bitfields match, user has no permissions
1464 yield
[ Revision
::DELETED_TEXT
, Revision
::DELETED_TEXT
, [], false ];
1465 yield
[ Revision
::DELETED_COMMENT
, Revision
::DELETED_COMMENT
, [], false ];
1466 yield
[ Revision
::DELETED_USER
, Revision
::DELETED_USER
, [], false ];
1467 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [], false ];
1468 // Bitfields match, user (admin) does have permissions
1469 yield
[ Revision
::DELETED_TEXT
, Revision
::DELETED_TEXT
, [ 'sysop' ], true ];
1470 yield
[ Revision
::DELETED_COMMENT
, Revision
::DELETED_COMMENT
, [ 'sysop' ], true ];
1471 yield
[ Revision
::DELETED_USER
, Revision
::DELETED_USER
, [ 'sysop' ], true ];
1472 // Bitfields match, user (admin) does not have permissions
1473 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [ 'sysop' ], false ];
1474 // Bitfields match, user (oversight) does have permissions
1475 yield
[ Revision
::DELETED_RESTRICTED
, Revision
::DELETED_RESTRICTED
, [ 'oversight' ], true ];
1479 * @dataProvider provideUserCan
1480 * @covers Revision::userCan
1482 public function testUserCan( $bitField, $field, $userGroups, $expected ) {
1483 $this->setMwGlobals(
1484 'wgGroupPermissions',
1487 'deletedtext' => true,
1488 'deletedhistory' => true,
1491 'viewsuppressed' => true,
1492 'suppressrevision' => true,
1496 $user = $this->getTestUser( $userGroups )->getUser();
1497 $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage
->getTitle() );
1501 $revision->userCan( $field, $user )