4 * @group ContentHandler
9 class RevisionIntegrationTest
extends MediaWikiTestCase
{
12 * @var WikiPage $testPage
16 public function __construct( $name = null, array $data = [], $dataName = '' ) {
17 parent
::__construct( $name, $data, $dataName );
19 $this->tablesUsed
= array_merge( $this->tablesUsed
,
42 protected function setUp() {
47 $this->mergeMwGlobalArrayValue(
51 12313 => 'Dummy_talk',
55 $this->mergeMwGlobalArrayValue(
56 'wgNamespaceContentModels',
58 12312 => DummyContentForTesting
::MODEL_ID
,
62 $this->mergeMwGlobalArrayValue(
65 DummyContentForTesting
::MODEL_ID
=> 'DummyContentHandlerForTesting',
66 RevisionTestModifyableContent
::MODEL_ID
=> 'RevisionTestModifyableContentHandler',
70 MWNamespace
::clearCaches();
71 // Reset namespace cache
72 $wgContLang->resetNamespaces();
73 if ( !$this->testPage
) {
74 $this->testPage
= WikiPage
::factory( Title
::newFromText( 'UTPage' ) );
78 protected function tearDown() {
83 MWNamespace
::clearCaches();
84 // Reset namespace cache
85 $wgContLang->resetNamespaces();
88 private function makeRevisionWithProps( $props = null ) {
89 if ( $props === null ) {
93 if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
94 $props['text'] = 'Lorem Ipsum';
97 if ( !isset( $props['comment'] ) ) {
98 $props['comment'] = 'just a test';
101 if ( !isset( $props['page'] ) ) {
102 $props['page'] = $this->testPage
->getId();
105 $rev = new Revision( $props );
107 $dbw = wfGetDB( DB_MASTER
);
108 $rev->insertOn( $dbw );
114 * @param string $titleString
115 * @param string $text
116 * @param string|null $model
120 private function createPage( $titleString, $text, $model = null ) {
121 if ( !preg_match( '/:/', $titleString ) &&
122 ( $model === null ||
$model === CONTENT_MODEL_WIKITEXT
)
124 $ns = $this->getDefaultWikitextNS();
125 $titleString = MWNamespace
::getCanonicalName( $ns ) . ':' . $titleString;
128 $title = Title
::newFromText( $titleString );
129 $wikipage = new WikiPage( $title );
131 // Delete the article if it already exists
132 if ( $wikipage->exists() ) {
133 $wikipage->doDeleteArticle( "done" );
136 $content = ContentHandler
::makeContent( $text, $title, $model );
137 $wikipage->doEditContent( $content, __METHOD__
, EDIT_NEW
);
142 private function assertRevEquals( Revision
$orig, Revision
$rev = null ) {
143 $this->assertNotNull( $rev, 'missing revision' );
145 $this->assertEquals( $orig->getId(), $rev->getId() );
146 $this->assertEquals( $orig->getPage(), $rev->getPage() );
147 $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
148 $this->assertEquals( $orig->getUser(), $rev->getUser() );
149 $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
150 $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
151 $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
155 * @covers Revision::__construct
157 public function testConstructFromRow() {
158 $latestRevisionId = $this->testPage
->getLatest();
159 $latestRevision = $this->testPage
->getRevision();
161 $dbr = wfGetDB( DB_REPLICA
);
164 Revision
::selectFields(),
165 [ 'rev_id' => $latestRevisionId ]
167 $this->assertTrue( is_object( $res ), 'query failed' );
169 $row = $res->fetchObject();
172 $this->assertRevEquals( $latestRevision, new Revision( $row ) );
176 * @covers Revision::newFromTitle
178 public function testNewFromTitle_withoutId() {
179 $latestRevId = $this->testPage
->getLatest();
181 $rev = Revision
::newFromTitle( $this->testPage
->getTitle() );
183 $this->assertTrue( $this->testPage
->getTitle()->equals( $rev->getTitle() ) );
184 $this->assertEquals( $latestRevId, $rev->getId() );
188 * @covers Revision::newFromTitle
190 public function testNewFromTitle_withId() {
191 $latestRevId = $this->testPage
->getLatest();
193 $rev = Revision
::newFromTitle( $this->testPage
->getTitle(), $latestRevId );
195 $this->assertTrue( $this->testPage
->getTitle()->equals( $rev->getTitle() ) );
196 $this->assertEquals( $latestRevId, $rev->getId() );
200 * @covers Revision::newFromTitle
202 public function testNewFromTitle_withBadId() {
203 $latestRevId = $this->testPage
->getLatest();
205 $rev = Revision
::newFromTitle( $this->testPage
->getTitle(), $latestRevId +
1 );
207 $this->assertNull( $rev );
211 * @covers Revision::newFromRow
213 public function testNewFromRow() {
214 $orig = $this->makeRevisionWithProps();
216 $dbr = wfGetDB( DB_REPLICA
);
217 $res = $dbr->select( 'revision', Revision
::selectFields(), [ 'rev_id' => $orig->getId() ] );
218 $this->assertTrue( is_object( $res ), 'query failed' );
220 $row = $res->fetchObject();
223 $rev = Revision
::newFromRow( $row );
225 $this->assertRevEquals( $orig, $rev );
229 * @covers Revision::newFromArchiveRow
231 public function testNewFromArchiveRow() {
232 $page = $this->createPage(
233 'RevisionStorageTest_testNewFromArchiveRow',
235 CONTENT_MODEL_WIKITEXT
237 $orig = $page->getRevision();
238 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
240 $dbr = wfGetDB( DB_REPLICA
);
242 'archive', Revision
::selectArchiveFields(), [ 'ar_rev_id' => $orig->getId() ]
244 $this->assertTrue( is_object( $res ), 'query failed' );
246 $row = $res->fetchObject();
249 $rev = Revision
::newFromArchiveRow( $row );
251 $this->assertRevEquals( $orig, $rev );
255 * @covers Revision::newFromId
257 public function testNewFromId() {
258 $orig = $this->testPage
->getRevision();
259 $rev = Revision
::newFromId( $orig->getId() );
260 $this->assertRevEquals( $orig, $rev );
264 * @covers Revision::fetchRevision
266 public function testFetchRevision() {
267 // Hidden process cache assertion below
268 $this->testPage
->getRevision()->getId();
270 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
271 $id = $this->testPage
->getRevision()->getId();
273 $res = Revision
::fetchRevision( $this->testPage
->getTitle() );
275 # note: order is unspecified
277 while ( ( $row = $res->fetchObject() ) ) {
278 $rows[$row->rev_id
] = $row;
281 $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' );
282 $this->assertArrayHasKey( $id, $rows, 'missing revision with id ' . $id );
286 * @covers Revision::selectFields
288 public function testSelectFields() {
289 global $wgContentHandlerUseDB;
291 $fields = Revision
::selectFields();
293 $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
294 $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
296 in_array( 'rev_timestamp', $fields ),
297 'missing rev_timestamp in list of fields'
299 $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
301 if ( $wgContentHandlerUseDB ) {
302 $this->assertTrue( in_array( 'rev_content_model', $fields ),
303 'missing rev_content_model in list of fields' );
304 $this->assertTrue( in_array( 'rev_content_format', $fields ),
305 'missing rev_content_format in list of fields' );
310 * @covers Revision::getPage
312 public function testGetPage() {
313 $page = $this->testPage
;
315 $orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
316 $rev = Revision
::newFromId( $orig->getId() );
318 $this->assertEquals( $page->getId(), $rev->getPage() );
322 * @covers Revision::isCurrent
324 public function testIsCurrent() {
325 $rev1 = $this->testPage
->getRevision();
327 # @todo find out if this should be true
328 # $this->assertTrue( $rev1->isCurrent() );
330 $rev1x = Revision
::newFromId( $rev1->getId() );
331 $this->assertTrue( $rev1x->isCurrent() );
333 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
334 $rev2 = $this->testPage
->getRevision();
336 # @todo find out if this should be true
337 # $this->assertTrue( $rev2->isCurrent() );
339 $rev1x = Revision
::newFromId( $rev1->getId() );
340 $this->assertFalse( $rev1x->isCurrent() );
342 $rev2x = Revision
::newFromId( $rev2->getId() );
343 $this->assertTrue( $rev2x->isCurrent() );
347 * @covers Revision::getPrevious
349 public function testGetPrevious() {
350 $oldestRevision = $this->testPage
->getOldestRevision();
351 $latestRevision = $this->testPage
->getLatest();
353 $this->assertNull( $oldestRevision->getPrevious() );
355 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
356 $newRevision = $this->testPage
->getRevision();
358 $this->assertNotNull( $newRevision->getPrevious() );
359 $this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
363 * @covers Revision::getNext
365 public function testGetNext() {
366 $rev1 = $this->testPage
->getRevision();
368 $this->assertNull( $rev1->getNext() );
370 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
371 $rev2 = $this->testPage
->getRevision();
373 $this->assertNotNull( $rev1->getNext() );
374 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
378 * @covers Revision::newNullRevision
380 public function testNewNullRevision() {
381 $this->testPage
->doEditContent( new WikitextContent( __METHOD__
), __METHOD__
);
382 $orig = $this->testPage
->getRevision();
384 $dbw = wfGetDB( DB_MASTER
);
385 $rev = Revision
::newNullRevision( $dbw, $this->testPage
->getId(), 'a null revision', false );
387 $this->assertNotEquals( $orig->getId(), $rev->getId(),
388 'new null revision should have a different id from the original revision' );
389 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
390 'new null revision should have the same text id as the original revision' );
391 $this->assertEquals( __METHOD__
, $rev->getContent()->getNativeData() );
395 * @covers Revision::insertOn
397 public function testInsertOn() {
398 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
400 $orig = $this->makeRevisionWithProps( [
404 // Make sure the revision was copied to ip_changes
405 $dbr = wfGetDB( DB_REPLICA
);
406 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
407 $row = $res->fetchObject();
409 $this->assertEquals( IP
::toHex( $ip ), $row->ipc_hex
);
410 $this->assertEquals( $orig->getTimestamp(), $row->ipc_rev_timestamp
);
413 public static function provideUserWasLastToEdit() {
414 yield
'actually the last edit' => [ 3, true ];
415 yield
'not the current edit, but still by this user' => [ 2, true ];
416 yield
'edit by another user' => [ 1, false ];
417 yield
'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
421 * @dataProvider provideUserWasLastToEdit
423 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
424 $userA = User
::newFromName( "RevisionStorageTest_userA" );
425 $userB = User
::newFromName( "RevisionStorageTest_userB" );
427 if ( $userA->getId() === 0 ) {
428 $userA = User
::createNew( $userA->getName() );
431 if ( $userB->getId() === 0 ) {
432 $userB = User
::createNew( $userB->getName() );
435 $ns = $this->getDefaultWikitextNS();
437 $dbw = wfGetDB( DB_MASTER
);
440 // create revisions -----------------------------
441 $page = WikiPage
::factory( Title
::newFromText(
442 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
443 $page->insertOn( $dbw );
445 $revisions[0] = new Revision( [
446 'page' => $page->getId(),
447 // we need the title to determine the page's default content model
448 'title' => $page->getTitle(),
449 'timestamp' => '20120101000000',
450 'user' => $userA->getId(),
452 'content_model' => CONTENT_MODEL_WIKITEXT
,
453 'summary' => 'edit zero'
455 $revisions[0]->insertOn( $dbw );
457 $revisions[1] = new Revision( [
458 'page' => $page->getId(),
459 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
460 'title' => $page->getTitle(),
461 'timestamp' => '20120101000100',
462 'user' => $userA->getId(),
464 'content_model' => CONTENT_MODEL_WIKITEXT
,
465 'summary' => 'edit one'
467 $revisions[1]->insertOn( $dbw );
469 $revisions[2] = new Revision( [
470 'page' => $page->getId(),
471 'title' => $page->getTitle(),
472 'timestamp' => '20120101000200',
473 'user' => $userB->getId(),
475 'content_model' => CONTENT_MODEL_WIKITEXT
,
476 'summary' => 'edit two'
478 $revisions[2]->insertOn( $dbw );
480 $revisions[3] = new Revision( [
481 'page' => $page->getId(),
482 'title' => $page->getTitle(),
483 'timestamp' => '20120101000300',
484 'user' => $userA->getId(),
486 'content_model' => CONTENT_MODEL_WIKITEXT
,
487 'summary' => 'edit three'
489 $revisions[3]->insertOn( $dbw );
491 $revisions[4] = new Revision( [
492 'page' => $page->getId(),
493 'title' => $page->getTitle(),
494 'timestamp' => '20120101000200',
495 'user' => $userA->getId(),
497 'content_model' => CONTENT_MODEL_WIKITEXT
,
498 'summary' => 'edit four'
500 $revisions[4]->insertOn( $dbw );
502 // test it ---------------------------------
503 $since = $revisions[$sinceIdx]->getTimestamp();
505 $wasLast = Revision
::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
507 $this->assertEquals( $expectedLast, $wasLast );
511 * @param string $text
512 * @param string $title
513 * @param string $model
514 * @param string $format
518 private function newTestRevision( $text, $title = "Test",
519 $model = CONTENT_MODEL_WIKITEXT
, $format = null
521 if ( is_string( $title ) ) {
522 $title = Title
::newFromText( $title );
525 $content = ContentHandler
::makeContent( $text, $title, $model, $format );
533 'content' => $content,
534 'length' => $content->getSize(),
535 'comment' => "testing",
536 'minor_edit' => false,
538 'content_format' => $format,
545 public function provideGetContentModel() {
546 // NOTE: we expect the help namespace to always contain wikitext
548 [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT
],
549 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS
],
550 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting
::MODEL_ID
],
555 * @dataProvider provideGetContentModel
556 * @covers Revision::getContentModel
558 public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
559 $rev = $this->newTestRevision( $text, $title, $model, $format );
561 $this->assertEquals( $expectedModel, $rev->getContentModel() );
564 public function provideGetContentFormat() {
565 // NOTE: we expect the help namespace to always contain wikitext
567 [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT
],
568 [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS
, null, CONTENT_FORMAT_CSS
],
569 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS
],
570 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting
::MODEL_ID
],
575 * @dataProvider provideGetContentFormat
576 * @covers Revision::getContentFormat
578 public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
579 $rev = $this->newTestRevision( $text, $title, $model, $format );
581 $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
584 public function provideGetContentHandler() {
585 // NOTE: we expect the help namespace to always contain wikitext
587 [ 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ],
588 [ 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ],
589 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ],
594 * @dataProvider provideGetContentHandler
595 * @covers Revision::getContentHandler
597 public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
598 $rev = $this->newTestRevision( $text, $title, $model, $format );
600 $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
603 public function provideGetContent() {
604 // NOTE: we expect the help namespace to always contain wikitext
606 [ 'hello world', 'Help:Hello', null, null, Revision
::FOR_PUBLIC
, 'hello world' ],
608 serialize( 'hello world' ),
610 DummyContentForTesting
::MODEL_ID
,
612 Revision
::FOR_PUBLIC
,
613 serialize( 'hello world' )
616 serialize( 'hello world' ),
620 Revision
::FOR_PUBLIC
,
621 serialize( 'hello world' )
627 * @dataProvider provideGetContent
628 * @covers Revision::getContent
630 public function testGetContent( $text, $title, $model, $format,
631 $audience, $expectedSerialization
633 $rev = $this->newTestRevision( $text, $title, $model, $format );
634 $content = $rev->getContent( $audience );
637 $expectedSerialization,
638 is_null( $content ) ?
null : $content->serialize( $format )
643 * @covers Revision::getContent
645 public function testGetContent_failure() {
646 $rev = new Revision( [
647 'page' => $this->testPage
->getId(),
648 'content_model' => $this->testPage
->getContentModel(),
649 'text_id' => 123456789, // not in the test DB
652 $this->assertNull( $rev->getContent(),
653 "getContent() should return null if the revision's text blob could not be loaded." );
655 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
656 $this->assertNull( $rev->getContent(),
657 "getContent() should return null if the revision's text blob could not be loaded." );
660 public function provideGetSize() {
662 [ "hello world.", CONTENT_MODEL_WIKITEXT
, 12 ],
663 [ serialize( "hello world." ), DummyContentForTesting
::MODEL_ID
, 12 ],
668 * @covers Revision::getSize
669 * @dataProvider provideGetSize
671 public function testGetSize( $text, $model, $expected_size ) {
672 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
673 $this->assertEquals( $expected_size, $rev->getSize() );
676 public function provideGetSha1() {
678 [ "hello world.", CONTENT_MODEL_WIKITEXT
, Revision
::base36Sha1( "hello world." ) ],
680 serialize( "hello world." ),
681 DummyContentForTesting
::MODEL_ID
,
682 Revision
::base36Sha1( serialize( "hello world." ) )
688 * @covers Revision::getSha1
689 * @dataProvider provideGetSha1
691 public function testGetSha1( $text, $model, $expected_hash ) {
692 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
693 $this->assertEquals( $expected_hash, $rev->getSha1() );
697 * Tests whether $rev->getContent() returns a clone when needed.
699 * @covers Revision::getContent
701 public function testGetContentClone() {
702 $content = new RevisionTestModifyableContent( "foo" );
708 'title' => Title
::newFromText( "testGetContentClone_dummy" ),
710 'content' => $content,
711 'length' => $content->getSize(),
712 'comment' => "testing",
713 'minor_edit' => false,
717 /** @var RevisionTestModifyableContent $content */
718 $content = $rev->getContent( Revision
::RAW
);
719 $content->setText( "bar" );
721 /** @var RevisionTestModifyableContent $content2 */
722 $content2 = $rev->getContent( Revision
::RAW
);
723 // content is mutable, expect clone
724 $this->assertNotSame( $content, $content2, "expected a clone" );
725 // clone should contain the original text
726 $this->assertEquals( "foo", $content2->getText() );
728 $content2->setText( "bla bla" );
729 // clones should be independent
730 $this->assertEquals( "bar", $content->getText() );
734 * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
735 * @covers Revision::getContent
737 public function testGetContentUncloned() {
738 $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT
);
739 $content = $rev->getContent( Revision
::RAW
);
740 $content2 = $rev->getContent( Revision
::RAW
);
742 // for immutable content like wikitext, this should be the same object
743 $this->assertSame( $content, $content2 );