Merge "Remove parameter 'options' from hook 'SkinEditSectionLinks'"
[lhc/web/wiklou.git] / tests / phpunit / includes / RevisionDbTestBase.php
1 <?php
2 use MediaWiki\MediaWikiServices;
3 use MediaWiki\Revision\MutableRevisionRecord;
4 use MediaWiki\Revision\RevisionStore;
5 use MediaWiki\Revision\IncompleteRevisionException;
6 use MediaWiki\Revision\RevisionRecord;
7 use MediaWiki\Revision\SlotRecord;
8
9 /**
10 * RevisionDbTestBase contains test cases for the Revision class that have Database interactions.
11 *
12 * @group Database
13 * @group medium
14 */
15 abstract class RevisionDbTestBase extends MediaWikiTestCase {
16
17 /**
18 * @var WikiPage $testPage
19 */
20 private $testPage;
21
22 public function __construct( $name = null, array $data = [], $dataName = '' ) {
23 parent::__construct( $name, $data, $dataName );
24
25 $this->tablesUsed = array_merge( $this->tablesUsed,
26 [
27 'page',
28 'revision',
29 'comment',
30 'ip_changes',
31 'text',
32 'archive',
33
34 'recentchanges',
35 'logging',
36
37 'page_props',
38 'pagelinks',
39 'categorylinks',
40 'langlinks',
41 'externallinks',
42 'imagelinks',
43 'templatelinks',
44 'iwlinks'
45 ]
46 );
47 }
48
49 protected function addCoreDBData() {
50 // Blank out. This would fail with a modified schema, and we don't need it.
51 }
52
53 /**
54 * @return int
55 */
56 abstract protected function getMcrMigrationStage();
57
58 /**
59 * @return string[]
60 */
61 abstract protected function getMcrTablesToReset();
62
63 protected function setUp() {
64 $this->tablesUsed += $this->getMcrTablesToReset();
65
66 parent::setUp();
67
68 $this->mergeMwGlobalArrayValue(
69 'wgExtraNamespaces',
70 [
71 12312 => 'Dummy',
72 12313 => 'Dummy_talk',
73 ]
74 );
75
76 $this->mergeMwGlobalArrayValue(
77 'wgNamespaceContentModels',
78 [
79 12312 => DummyContentForTesting::MODEL_ID,
80 ]
81 );
82
83 $this->mergeMwGlobalArrayValue(
84 'wgContentHandlers',
85 [
86 DummyContentForTesting::MODEL_ID => 'DummyContentHandlerForTesting',
87 RevisionTestModifyableContent::MODEL_ID => 'RevisionTestModifyableContentHandler',
88 ]
89 );
90
91 $this->setMwGlobals( [
92 'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
93 'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
94 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
95 ] );
96
97 $this->overrideMwServices();
98
99 if ( !$this->testPage ) {
100 /**
101 * We have to create a new page for each subclass as the page creation may result
102 * in different DB fields being filled based on configuration.
103 */
104 $this->testPage = $this->createPage( __CLASS__, __CLASS__ );
105 }
106 }
107
108 /**
109 * @param string $model
110 * @return Title
111 */
112 protected function getMockTitle() {
113 $mock = $this->getMockBuilder( Title::class )
114 ->disableOriginalConstructor()
115 ->getMock();
116 $mock->expects( $this->any() )
117 ->method( 'getNamespace' )
118 ->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
119 $mock->expects( $this->any() )
120 ->method( 'getPrefixedText' )
121 ->will( $this->returnValue( __CLASS__ ) );
122 $mock->expects( $this->any() )
123 ->method( 'getDBkey' )
124 ->will( $this->returnValue( __CLASS__ ) );
125 $mock->expects( $this->any() )
126 ->method( 'getArticleID' )
127 ->will( $this->returnValue( 23 ) );
128
129 return $mock;
130 }
131
132 abstract protected function getContentHandlerUseDB();
133
134 private function makeRevisionWithProps( $props = null ) {
135 if ( $props === null ) {
136 $props = [];
137 }
138
139 if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) {
140 $props['text'] = 'Lorem Ipsum';
141 }
142
143 if ( !isset( $props['user_text'] ) ) {
144 $user = $this->getTestUser()->getUser();
145 $props['user_text'] = $user->getName();
146 $props['user'] = $user->getId();
147 }
148
149 if ( !isset( $props['user'] ) ) {
150 $props['user'] = 0;
151 }
152
153 if ( !isset( $props['comment'] ) ) {
154 $props['comment'] = 'just a test';
155 }
156
157 if ( !isset( $props['page'] ) ) {
158 $props['page'] = $this->testPage->getId();
159 }
160
161 if ( !isset( $props['content_model'] ) ) {
162 $props['content_model'] = CONTENT_MODEL_WIKITEXT;
163 }
164
165 $rev = new Revision( $props );
166
167 $dbw = wfGetDB( DB_MASTER );
168 $rev->insertOn( $dbw );
169
170 return $rev;
171 }
172
173 /**
174 * @param string $titleString
175 * @param string $text
176 * @param string|null $model
177 *
178 * @return WikiPage
179 */
180 private function createPage( $titleString, $text, $model = null ) {
181 if ( !preg_match( '/:/', $titleString ) &&
182 ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
183 ) {
184 $ns = $this->getDefaultWikitextNS();
185 $titleString = MediaWikiServices::getInstance()->getNamespaceInfo()->
186 getCanonicalName( $ns ) . ':' . $titleString;
187 }
188
189 $title = Title::newFromText( $titleString );
190 $wikipage = new WikiPage( $title );
191
192 // Delete the article if it already exists
193 if ( $wikipage->exists() ) {
194 $wikipage->doDeleteArticle( "done" );
195 }
196
197 $content = ContentHandler::makeContent( $text, $title, $model );
198 $wikipage->doEditContent( $content, __METHOD__, EDIT_NEW );
199
200 return $wikipage;
201 }
202
203 private function assertRevEquals( Revision $orig, Revision $rev = null ) {
204 $this->assertNotNull( $rev, 'missing revision' );
205
206 $this->assertEquals( $orig->getId(), $rev->getId() );
207 $this->assertEquals( $orig->getPage(), $rev->getPage() );
208 $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() );
209 $this->assertEquals( $orig->getUser(), $rev->getUser() );
210 $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() );
211 $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() );
212 $this->assertEquals( $orig->getSha1(), $rev->getSha1() );
213 }
214
215 /**
216 * @covers Revision::getRecentChange
217 */
218 public function testGetRecentChange() {
219 $rev = $this->testPage->getRevision();
220 $recentChange = $rev->getRecentChange();
221
222 // Make sure various attributes look right / the correct entry has been retrieved.
223 $this->assertEquals( $rev->getTimestamp(), $recentChange->getAttribute( 'rc_timestamp' ) );
224 $this->assertEquals(
225 $rev->getTitle()->getNamespace(),
226 $recentChange->getAttribute( 'rc_namespace' )
227 );
228 $this->assertEquals(
229 $rev->getTitle()->getDBkey(),
230 $recentChange->getAttribute( 'rc_title' )
231 );
232 $this->assertEquals( $rev->getUser(), $recentChange->getAttribute( 'rc_user' ) );
233 $this->assertEquals( $rev->getUserText(), $recentChange->getAttribute( 'rc_user_text' ) );
234 $this->assertEquals( $rev->getComment(), $recentChange->getAttribute( 'rc_comment' ) );
235 $this->assertEquals( $rev->getPage(), $recentChange->getAttribute( 'rc_cur_id' ) );
236 $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
237 }
238
239 /**
240 * @covers Revision::insertOn
241 */
242 public function testInsertOn_success() {
243 $parentId = $this->testPage->getLatest();
244
245 // If an ExternalStore is set don't use it.
246 $this->setMwGlobals( 'wgDefaultExternalStore', false );
247
248 $rev = new Revision( [
249 'page' => $this->testPage->getId(),
250 'title' => $this->testPage->getTitle(),
251 'text' => 'Revision Text',
252 'comment' => 'Revision comment',
253 ] );
254
255 $revId = $rev->insertOn( wfGetDB( DB_MASTER ) );
256
257 $this->assertInternalType( 'integer', $revId );
258 $this->assertSame( $revId, $rev->getId() );
259
260 // getTextId() must be an int!
261 $this->assertInternalType( 'integer', $rev->getTextId() );
262
263 $mainSlot = $rev->getRevisionRecord()->getSlot( SlotRecord::MAIN, RevisionRecord::RAW );
264
265 // we currently only support storage in the text table
266 $textId = MediaWikiServices::getInstance()
267 ->getBlobStore()
268 ->getTextIdFromAddress( $mainSlot->getAddress() );
269
270 $this->assertSelect(
271 'text',
272 [ 'old_id', 'old_text' ],
273 "old_id = $textId",
274 [ [ strval( $textId ), 'Revision Text' ] ]
275 );
276 $this->assertSelect(
277 'revision',
278 [
279 'rev_id',
280 'rev_page',
281 'rev_minor_edit',
282 'rev_deleted',
283 'rev_len',
284 'rev_parent_id',
285 'rev_sha1',
286 ],
287 "rev_id = {$rev->getId()}",
288 [ [
289 strval( $rev->getId() ),
290 strval( $this->testPage->getId() ),
291 '0',
292 '0',
293 '13',
294 strval( $parentId ),
295 's0ngbdoxagreuf2vjtuxzwdz64n29xm',
296 ] ]
297 );
298 }
299
300 public function provideInsertOn_exceptionOnIncomplete() {
301 $content = new TextContent( '' );
302 $user = User::newFromName( 'Foo' );
303
304 yield 'no parent' => [
305 [
306 'content' => $content,
307 'comment' => 'test',
308 'user' => $user,
309 ],
310 IncompleteRevisionException::class,
311 "rev_page field must not be 0!"
312 ];
313
314 yield 'no comment' => [
315 [
316 'content' => $content,
317 'page' => 7,
318 'user' => $user,
319 ],
320 IncompleteRevisionException::class,
321 "comment must not be NULL!"
322 ];
323
324 yield 'no content' => [
325 [
326 'comment' => 'test',
327 'page' => 7,
328 'user' => $user,
329 ],
330 IncompleteRevisionException::class,
331 "Uninitialized field: content_address" // XXX: message may change
332 ];
333 }
334
335 /**
336 * @dataProvider provideInsertOn_exceptionOnIncomplete
337 * @covers Revision::insertOn
338 */
339 public function testInsertOn_exceptionOnIncomplete( $array, $expException, $expMessage ) {
340 // If an ExternalStore is set don't use it.
341 $this->setMwGlobals( 'wgDefaultExternalStore', false );
342 $this->setExpectedException( $expException, $expMessage );
343
344 $title = Title::newFromText( 'Nonexistant-' . __METHOD__ );
345 $rev = new Revision( $array, 0, $title );
346
347 $rev->insertOn( wfGetDB( DB_MASTER ) );
348 }
349
350 /**
351 * @covers Revision::newFromTitle
352 */
353 public function testNewFromTitle_withoutId() {
354 $latestRevId = $this->testPage->getLatest();
355
356 $rev = Revision::newFromTitle( $this->testPage->getTitle() );
357
358 $this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
359 $this->assertEquals( $latestRevId, $rev->getId() );
360 }
361
362 /**
363 * @covers Revision::newFromTitle
364 */
365 public function testNewFromTitle_withId() {
366 $latestRevId = $this->testPage->getLatest();
367
368 $rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId );
369
370 $this->assertTrue( $this->testPage->getTitle()->equals( $rev->getTitle() ) );
371 $this->assertEquals( $latestRevId, $rev->getId() );
372 }
373
374 /**
375 * @covers Revision::newFromTitle
376 */
377 public function testNewFromTitle_withBadId() {
378 $latestRevId = $this->testPage->getLatest();
379
380 $rev = Revision::newFromTitle( $this->testPage->getTitle(), $latestRevId + 1 );
381
382 $this->assertNull( $rev );
383 }
384
385 /**
386 * @covers Revision::newFromRow
387 */
388 public function testNewFromRow() {
389 $orig = $this->makeRevisionWithProps();
390
391 $dbr = wfGetDB( DB_REPLICA );
392 $revQuery = Revision::getQueryInfo();
393 $res = $dbr->select( $revQuery['tables'], $revQuery['fields'], [ 'rev_id' => $orig->getId() ],
394 __METHOD__, [], $revQuery['joins'] );
395 $this->assertTrue( is_object( $res ), 'query failed' );
396
397 $row = $res->fetchObject();
398 $res->free();
399
400 $rev = Revision::newFromRow( $row );
401
402 $this->assertRevEquals( $orig, $rev );
403 }
404
405 public function provideNewFromArchiveRow() {
406 yield [
407 function ( $f ) {
408 return $f;
409 },
410 ];
411 yield [
412 function ( $f ) {
413 return $f + [ 'ar_namespace', 'ar_title' ];
414 },
415 ];
416 yield [
417 function ( $f ) {
418 unset( $f['ar_text_id'] );
419 return $f;
420 },
421 ];
422 yield [
423 function ( $f ) {
424 unset( $f['ar_page_id'] );
425 return $f;
426 },
427 ];
428 yield [
429 function ( $f ) {
430 unset( $f['ar_parent_id'] );
431 return $f;
432 },
433 ];
434 yield [
435 function ( $f ) {
436 unset( $f['ar_rev_id'] );
437 return $f;
438 },
439 ];
440 yield [
441 function ( $f ) {
442 unset( $f['ar_sha1'] );
443 return $f;
444 },
445 ];
446 }
447
448 /**
449 * @dataProvider provideNewFromArchiveRow
450 * @covers Revision::newFromArchiveRow
451 */
452 public function testNewFromArchiveRow( $selectModifier ) {
453 $services = MediaWikiServices::getInstance();
454
455 $store = new RevisionStore(
456 $services->getDBLoadBalancer(),
457 $services->getService( '_SqlBlobStore' ),
458 $services->getMainWANObjectCache(),
459 $services->getCommentStore(),
460 $services->getContentModelStore(),
461 $services->getSlotRoleStore(),
462 $services->getSlotRoleRegistry(),
463 $this->getMcrMigrationStage(),
464 $services->getActorMigration()
465 );
466
467 $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
468 $this->setService( 'RevisionStore', $store );
469
470 $page = $this->createPage(
471 'RevisionStorageTest_testNewFromArchiveRow',
472 'Lorem Ipsum',
473 CONTENT_MODEL_WIKITEXT
474 );
475 $orig = $page->getRevision();
476 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
477
478 $dbr = wfGetDB( DB_REPLICA );
479 $arQuery = Revision::getArchiveQueryInfo();
480 $arQuery['fields'] = $selectModifier( $arQuery['fields'] );
481 $res = $dbr->select(
482 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
483 __METHOD__, [], $arQuery['joins']
484 );
485 $this->assertTrue( is_object( $res ), 'query failed' );
486
487 $row = $res->fetchObject();
488 $res->free();
489
490 // MCR migration note: $row is now required to contain ar_title and ar_namespace.
491 // Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
492 $rev = Revision::newFromArchiveRow( $row );
493
494 $this->assertRevEquals( $orig, $rev );
495 }
496
497 /**
498 * @covers Revision::newFromArchiveRow
499 */
500 public function testNewFromArchiveRowOverrides() {
501 $page = $this->createPage(
502 'RevisionStorageTest_testNewFromArchiveRow',
503 'Lorem Ipsum',
504 CONTENT_MODEL_WIKITEXT
505 );
506 $orig = $page->getRevision();
507 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
508
509 $dbr = wfGetDB( DB_REPLICA );
510 $arQuery = Revision::getArchiveQueryInfo();
511 $res = $dbr->select(
512 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
513 __METHOD__, [], $arQuery['joins']
514 );
515 $this->assertTrue( is_object( $res ), 'query failed' );
516
517 $row = $res->fetchObject();
518 $res->free();
519
520 $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
521
522 $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
523 $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
524 }
525
526 /**
527 * @covers Revision::newFromId
528 */
529 public function testNewFromId() {
530 $orig = $this->testPage->getRevision();
531 $rev = Revision::newFromId( $orig->getId() );
532 $this->assertRevEquals( $orig, $rev );
533 }
534
535 /**
536 * @covers Revision::newFromPageId
537 */
538 public function testNewFromPageId() {
539 $rev = Revision::newFromPageId( $this->testPage->getId() );
540 $this->assertRevEquals(
541 $this->testPage->getRevision(),
542 $rev
543 );
544 }
545
546 /**
547 * @covers Revision::newFromPageId
548 */
549 public function testNewFromPageIdWithLatestId() {
550 $rev = Revision::newFromPageId(
551 $this->testPage->getId(),
552 $this->testPage->getLatest()
553 );
554 $this->assertRevEquals(
555 $this->testPage->getRevision(),
556 $rev
557 );
558 }
559
560 /**
561 * @covers Revision::newFromPageId
562 */
563 public function testNewFromPageIdWithNotLatestId() {
564 $content = new WikitextContent( __METHOD__ );
565 $this->testPage->doEditContent( $content, __METHOD__ );
566 $rev = Revision::newFromPageId(
567 $this->testPage->getId(),
568 $this->testPage->getRevision()->getPrevious()->getId()
569 );
570 $this->assertRevEquals(
571 $this->testPage->getRevision()->getPrevious(),
572 $rev
573 );
574 }
575
576 /**
577 * @covers Revision::getPage
578 */
579 public function testGetPage() {
580 $page = $this->testPage;
581
582 $orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
583 $rev = Revision::newFromId( $orig->getId() );
584
585 $this->assertEquals( $page->getId(), $rev->getPage() );
586 }
587
588 /**
589 * @covers Revision::isCurrent
590 */
591 public function testIsCurrent() {
592 $rev1 = $this->testPage->getRevision();
593
594 # @todo find out if this should be true
595 # $this->assertTrue( $rev1->isCurrent() );
596
597 $rev1x = Revision::newFromId( $rev1->getId() );
598 $this->assertTrue( $rev1x->isCurrent() );
599
600 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
601 $rev2 = $this->testPage->getRevision();
602
603 # @todo find out if this should be true
604 # $this->assertTrue( $rev2->isCurrent() );
605
606 $rev1x = Revision::newFromId( $rev1->getId() );
607 $this->assertFalse( $rev1x->isCurrent() );
608
609 $rev2x = Revision::newFromId( $rev2->getId() );
610 $this->assertTrue( $rev2x->isCurrent() );
611 }
612
613 /**
614 * @covers Revision::getPrevious
615 */
616 public function testGetPrevious() {
617 $oldestRevision = $this->testPage->getOldestRevision();
618 $latestRevision = $this->testPage->getLatest();
619
620 $this->assertNull( $oldestRevision->getPrevious() );
621
622 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
623 $newRevision = $this->testPage->getRevision();
624
625 $this->assertNotNull( $newRevision->getPrevious() );
626 $this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
627 }
628
629 /**
630 * @covers Title::getPreviousRevisionID
631 * @covers Title::getRelativeRevisionID
632 * @covers MediaWiki\Revision\RevisionStore::getPreviousRevision
633 * @covers MediaWiki\Revision\RevisionStore::getRelativeRevision
634 */
635 public function testTitleGetPreviousRevisionID() {
636 $oldestId = $this->testPage->getOldestRevision()->getId();
637 $latestId = $this->testPage->getLatest();
638
639 $title = $this->testPage->getTitle();
640
641 $this->assertFalse( $title->getPreviousRevisionID( $oldestId ) );
642
643 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
644 $newId = $this->testPage->getRevision()->getId();
645
646 $this->assertEquals( $latestId, $title->getPreviousRevisionID( $newId ) );
647 }
648
649 /**
650 * @covers Title::getPreviousRevisionID
651 * @covers Title::getRelativeRevisionID
652 */
653 public function testTitleGetPreviousRevisionID_invalid() {
654 $this->assertFalse( $this->testPage->getTitle()->getPreviousRevisionID( 123456789 ) );
655 }
656
657 /**
658 * @covers Revision::getNext
659 */
660 public function testGetNext() {
661 $rev1 = $this->testPage->getRevision();
662
663 $this->assertNull( $rev1->getNext() );
664
665 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
666 $rev2 = $this->testPage->getRevision();
667
668 $this->assertNotNull( $rev1->getNext() );
669 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
670 }
671
672 /**
673 * @covers Title::getNextRevisionID
674 * @covers Title::getRelativeRevisionID
675 * @covers MediaWiki\Revision\RevisionStore::getNextRevision
676 * @covers MediaWiki\Revision\RevisionStore::getRelativeRevision
677 */
678 public function testTitleGetNextRevisionID() {
679 $title = $this->testPage->getTitle();
680
681 $origId = $this->testPage->getLatest();
682
683 $this->assertFalse( $title->getNextRevisionID( $origId ) );
684
685 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
686 $newId = $this->testPage->getLatest();
687
688 $this->assertSame( $this->testPage->getLatest(), $title->getNextRevisionID( $origId ) );
689 }
690
691 /**
692 * @covers Title::getNextRevisionID
693 * @covers Title::getRelativeRevisionID
694 */
695 public function testTitleGetNextRevisionID_invalid() {
696 $this->assertFalse( $this->testPage->getTitle()->getNextRevisionID( 123456789 ) );
697 }
698
699 /**
700 * @covers Revision::newNullRevision
701 */
702 public function testNewNullRevision() {
703 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
704 $orig = $this->testPage->getRevision();
705
706 $dbw = wfGetDB( DB_MASTER );
707 $rev = Revision::newNullRevision( $dbw, $this->testPage->getId(), 'a null revision', false );
708
709 $this->assertNotEquals( $orig->getId(), $rev->getId(),
710 'new null revision should have a different id from the original revision' );
711 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
712 'new null revision should have the same text id as the original revision' );
713 $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
714 'new null revision should have the same SHA1 as the original revision' );
715 $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
716 'new null revision should have the same content as the original revision' );
717 $this->assertEquals( __METHOD__, $rev->getContent()->getText() );
718 }
719
720 /**
721 * @covers Revision::newNullRevision
722 */
723 public function testNewNullRevision_badPage() {
724 $dbw = wfGetDB( DB_MASTER );
725 $rev = Revision::newNullRevision( $dbw, -1, 'a null revision', false );
726
727 $this->assertNull( $rev );
728 }
729
730 /**
731 * @covers Revision::insertOn
732 */
733 public function testInsertOn() {
734 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
735
736 $orig = $this->makeRevisionWithProps( [
737 'user_text' => $ip
738 ] );
739
740 // Make sure the revision was copied to ip_changes
741 $dbr = wfGetDB( DB_REPLICA );
742 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
743 $row = $res->fetchObject();
744
745 $this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
746 $this->assertEquals(
747 $orig->getTimestamp(),
748 wfTimestamp( TS_MW, $row->ipc_rev_timestamp )
749 );
750 }
751
752 public static function provideUserWasLastToEdit() {
753 yield 'actually the last edit' => [ 3, true ];
754 yield 'not the current edit, but still by this user' => [ 2, true ];
755 yield 'edit by another user' => [ 1, false ];
756 yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
757 }
758
759 /**
760 * @covers Revision::userWasLastToEdit
761 * @dataProvider provideUserWasLastToEdit
762 */
763 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
764 $userA = User::newFromName( "RevisionStorageTest_userA" );
765 $userB = User::newFromName( "RevisionStorageTest_userB" );
766
767 if ( $userA->getId() === 0 ) {
768 $userA = User::createNew( $userA->getName() );
769 }
770
771 if ( $userB->getId() === 0 ) {
772 $userB = User::createNew( $userB->getName() );
773 }
774
775 $ns = $this->getDefaultWikitextNS();
776
777 $dbw = wfGetDB( DB_MASTER );
778 $revisions = [];
779
780 // create revisions -----------------------------
781 $page = WikiPage::factory( Title::newFromText(
782 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
783 $page->insertOn( $dbw );
784
785 $revisions[0] = new Revision( [
786 'page' => $page->getId(),
787 // we need the title to determine the page's default content model
788 'title' => $page->getTitle(),
789 'timestamp' => '20120101000000',
790 'user' => $userA->getId(),
791 'text' => 'zero',
792 'content_model' => CONTENT_MODEL_WIKITEXT,
793 'comment' => 'edit zero'
794 ] );
795 $revisions[0]->insertOn( $dbw );
796
797 $revisions[1] = new Revision( [
798 'page' => $page->getId(),
799 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
800 'title' => $page->getTitle(),
801 'timestamp' => '20120101000100',
802 'user' => $userA->getId(),
803 'text' => 'one',
804 'content_model' => CONTENT_MODEL_WIKITEXT,
805 'comment' => 'edit one'
806 ] );
807 $revisions[1]->insertOn( $dbw );
808
809 $revisions[2] = new Revision( [
810 'page' => $page->getId(),
811 'title' => $page->getTitle(),
812 'timestamp' => '20120101000200',
813 'user' => $userB->getId(),
814 'text' => 'two',
815 'content_model' => CONTENT_MODEL_WIKITEXT,
816 'comment' => 'edit two'
817 ] );
818 $revisions[2]->insertOn( $dbw );
819
820 $revisions[3] = new Revision( [
821 'page' => $page->getId(),
822 'title' => $page->getTitle(),
823 'timestamp' => '20120101000300',
824 'user' => $userA->getId(),
825 'text' => 'three',
826 'content_model' => CONTENT_MODEL_WIKITEXT,
827 'comment' => 'edit three'
828 ] );
829 $revisions[3]->insertOn( $dbw );
830
831 $revisions[4] = new Revision( [
832 'page' => $page->getId(),
833 'title' => $page->getTitle(),
834 'timestamp' => '20120101000200',
835 'user' => $userA->getId(),
836 'text' => 'zero',
837 'content_model' => CONTENT_MODEL_WIKITEXT,
838 'comment' => 'edit four'
839 ] );
840 $revisions[4]->insertOn( $dbw );
841
842 // test it ---------------------------------
843 $since = $revisions[$sinceIdx]->getTimestamp();
844
845 $revQuery = Revision::getQueryInfo();
846 $allRows = iterator_to_array( $dbw->select(
847 $revQuery['tables'],
848 [ 'rev_id', 'rev_timestamp', 'rev_user' => $revQuery['fields']['rev_user'] ],
849 [
850 'rev_page' => $page->getId(),
851 //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
852 ],
853 __METHOD__,
854 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
855 $revQuery['joins']
856 ) );
857
858 $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
859
860 $this->assertEquals( $expectedLast, $wasLast );
861 }
862
863 /**
864 * @param string $text
865 * @param string $title
866 * @param string $model
867 * @param string $format
868 *
869 * @return Revision
870 */
871 private function newTestRevision( $text, $title = "Test",
872 $model = CONTENT_MODEL_WIKITEXT, $format = null
873 ) {
874 if ( is_string( $title ) ) {
875 $title = Title::newFromText( $title );
876 }
877
878 $content = ContentHandler::makeContent( $text, $title, $model, $format );
879
880 $rev = new Revision(
881 [
882 'id' => 42,
883 'page' => 23,
884 'title' => $title,
885
886 'content' => $content,
887 'length' => $content->getSize(),
888 'comment' => "testing",
889 'minor_edit' => false,
890
891 'content_format' => $format,
892 ]
893 );
894
895 return $rev;
896 }
897
898 public function provideGetContentModel() {
899 // NOTE: we expect the help namespace to always contain wikitext
900 return [
901 [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
902 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ],
903 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
904 ];
905 }
906
907 /**
908 * @dataProvider provideGetContentModel
909 * @covers Revision::getContentModel
910 */
911 public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
912 $rev = $this->newTestRevision( $text, $title, $model, $format );
913
914 $this->assertEquals( $expectedModel, $rev->getContentModel() );
915 }
916
917 public function provideGetContentFormat() {
918 // NOTE: we expect the help namespace to always contain wikitext
919 return [
920 [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
921 [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ],
922 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ],
923 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
924 ];
925 }
926
927 /**
928 * @dataProvider provideGetContentFormat
929 * @covers Revision::getContentFormat
930 */
931 public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
932 $rev = $this->newTestRevision( $text, $title, $model, $format );
933
934 $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
935 }
936
937 public function provideGetContentHandler() {
938 // NOTE: we expect the help namespace to always contain wikitext
939 return [
940 [ 'hello world', 'Help:Hello', null, null, WikitextContentHandler::class ],
941 [ 'hello world', 'User:hello/there.css', null, null, CssContentHandler::class ],
942 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentHandlerForTesting::class ],
943 ];
944 }
945
946 /**
947 * @dataProvider provideGetContentHandler
948 * @covers Revision::getContentHandler
949 */
950 public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
951 $rev = $this->newTestRevision( $text, $title, $model, $format );
952
953 $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
954 }
955
956 public function provideGetContent() {
957 // NOTE: we expect the help namespace to always contain wikitext
958 return [
959 [ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
960 [
961 serialize( 'hello world' ),
962 'Hello',
963 DummyContentForTesting::MODEL_ID,
964 null,
965 Revision::FOR_PUBLIC,
966 serialize( 'hello world' )
967 ],
968 [
969 serialize( 'hello world' ),
970 'Dummy:Hello',
971 null,
972 null,
973 Revision::FOR_PUBLIC,
974 serialize( 'hello world' )
975 ],
976 ];
977 }
978
979 /**
980 * @dataProvider provideGetContent
981 * @covers Revision::getContent
982 */
983 public function testGetContent( $text, $title, $model, $format,
984 $audience, $expectedSerialization
985 ) {
986 $rev = $this->newTestRevision( $text, $title, $model, $format );
987 $content = $rev->getContent( $audience );
988
989 $this->assertEquals(
990 $expectedSerialization,
991 is_null( $content ) ? null : $content->serialize( $format )
992 );
993 }
994
995 /**
996 * @covers Revision::getContent
997 */
998 public function testGetContent_failure() {
999 $rev = new Revision( [
1000 'page' => $this->testPage->getId(),
1001 'content_model' => $this->testPage->getContentModel(),
1002 'id' => 123456789, // not in the test DB
1003 ] );
1004
1005 Wikimedia\suppressWarnings(); // bad text_id will trigger a warning.
1006
1007 $this->assertNull( $rev->getContent(),
1008 "getContent() should return null if the revision's text blob could not be loaded." );
1009
1010 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
1011 $this->assertNull( $rev->getContent(),
1012 "getContent() should return null if the revision's text blob could not be loaded." );
1013
1014 Wikimedia\restoreWarnings();
1015 }
1016
1017 public function provideGetSize() {
1018 return [
1019 [ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
1020 [ serialize( "hello world." ), DummyContentForTesting::MODEL_ID, 12 ],
1021 ];
1022 }
1023
1024 /**
1025 * @covers Revision::getSize
1026 * @dataProvider provideGetSize
1027 */
1028 public function testGetSize( $text, $model, $expected_size ) {
1029 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
1030 $this->assertEquals( $expected_size, $rev->getSize() );
1031 }
1032
1033 public function provideGetSha1() {
1034 return [
1035 [ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
1036 [
1037 serialize( "hello world." ),
1038 DummyContentForTesting::MODEL_ID,
1039 Revision::base36Sha1( serialize( "hello world." ) )
1040 ],
1041 ];
1042 }
1043
1044 /**
1045 * @covers Revision::getSha1
1046 * @dataProvider provideGetSha1
1047 */
1048 public function testGetSha1( $text, $model, $expected_hash ) {
1049 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
1050 $this->assertEquals( $expected_hash, $rev->getSha1() );
1051 }
1052
1053 /**
1054 * Tests whether $rev->getContent() returns a clone when needed.
1055 *
1056 * @covers Revision::getContent
1057 */
1058 public function testGetContentClone() {
1059 $content = new RevisionTestModifyableContent( "foo" );
1060
1061 $rev = new Revision(
1062 [
1063 'id' => 42,
1064 'page' => 23,
1065 'title' => Title::newFromText( "testGetContentClone_dummy" ),
1066
1067 'content' => $content,
1068 'length' => $content->getSize(),
1069 'comment' => "testing",
1070 'minor_edit' => false,
1071 ]
1072 );
1073
1074 /** @var RevisionTestModifyableContent $content */
1075 $content = $rev->getContent( Revision::RAW );
1076 $content->setText( "bar" );
1077
1078 /** @var RevisionTestModifyableContent $content2 */
1079 $content2 = $rev->getContent( Revision::RAW );
1080 // content is mutable, expect clone
1081 $this->assertNotSame( $content, $content2, "expected a clone" );
1082 // clone should contain the original text
1083 $this->assertEquals( "foo", $content2->getText() );
1084
1085 $content2->setText( "bla bla" );
1086 // clones should be independent
1087 $this->assertEquals( "bar", $content->getText() );
1088 }
1089
1090 /**
1091 * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
1092 * @covers Revision::getContent
1093 */
1094 public function testGetContentUncloned() {
1095 $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
1096 $content = $rev->getContent( Revision::RAW );
1097 $content2 = $rev->getContent( Revision::RAW );
1098
1099 // for immutable content like wikitext, this should be the same object
1100 $this->assertSame( $content, $content2 );
1101 }
1102
1103 /**
1104 * @covers Revision::loadFromId
1105 */
1106 public function testLoadFromId() {
1107 $rev = $this->testPage->getRevision();
1108 $this->hideDeprecated( 'Revision::loadFromId' );
1109 $this->assertRevEquals(
1110 $rev,
1111 Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
1112 );
1113 }
1114
1115 /**
1116 * @covers Revision::loadFromPageId
1117 */
1118 public function testLoadFromPageId() {
1119 $this->assertRevEquals(
1120 $this->testPage->getRevision(),
1121 Revision::loadFromPageId( wfGetDB( DB_MASTER ), $this->testPage->getId() )
1122 );
1123 }
1124
1125 /**
1126 * @covers Revision::loadFromPageId
1127 */
1128 public function testLoadFromPageIdWithLatestRevId() {
1129 $this->assertRevEquals(
1130 $this->testPage->getRevision(),
1131 Revision::loadFromPageId(
1132 wfGetDB( DB_MASTER ),
1133 $this->testPage->getId(),
1134 $this->testPage->getLatest()
1135 )
1136 );
1137 }
1138
1139 /**
1140 * @covers Revision::loadFromPageId
1141 */
1142 public function testLoadFromPageIdWithNotLatestRevId() {
1143 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1144 $this->assertRevEquals(
1145 $this->testPage->getRevision()->getPrevious(),
1146 Revision::loadFromPageId(
1147 wfGetDB( DB_MASTER ),
1148 $this->testPage->getId(),
1149 $this->testPage->getRevision()->getPrevious()->getId()
1150 )
1151 );
1152 }
1153
1154 /**
1155 * @covers Revision::loadFromTitle
1156 */
1157 public function testLoadFromTitle() {
1158 $this->assertRevEquals(
1159 $this->testPage->getRevision(),
1160 Revision::loadFromTitle( wfGetDB( DB_MASTER ), $this->testPage->getTitle() )
1161 );
1162 }
1163
1164 /**
1165 * @covers Revision::loadFromTitle
1166 */
1167 public function testLoadFromTitleWithLatestRevId() {
1168 $this->assertRevEquals(
1169 $this->testPage->getRevision(),
1170 Revision::loadFromTitle(
1171 wfGetDB( DB_MASTER ),
1172 $this->testPage->getTitle(),
1173 $this->testPage->getLatest()
1174 )
1175 );
1176 }
1177
1178 /**
1179 * @covers Revision::loadFromTitle
1180 */
1181 public function testLoadFromTitleWithNotLatestRevId() {
1182 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1183 $this->assertRevEquals(
1184 $this->testPage->getRevision()->getPrevious(),
1185 Revision::loadFromTitle(
1186 wfGetDB( DB_MASTER ),
1187 $this->testPage->getTitle(),
1188 $this->testPage->getRevision()->getPrevious()->getId()
1189 )
1190 );
1191 }
1192
1193 /**
1194 * @covers Revision::loadFromTimestamp()
1195 */
1196 public function testLoadFromTimestamp() {
1197 $this->assertRevEquals(
1198 $this->testPage->getRevision(),
1199 Revision::loadFromTimestamp(
1200 wfGetDB( DB_MASTER ),
1201 $this->testPage->getTitle(),
1202 $this->testPage->getRevision()->getTimestamp()
1203 )
1204 );
1205 }
1206
1207 /**
1208 * @covers Revision::getParentLengths
1209 */
1210 public function testGetParentLengths_noRevIds() {
1211 $this->assertSame(
1212 [],
1213 Revision::getParentLengths(
1214 wfGetDB( DB_MASTER ),
1215 []
1216 )
1217 );
1218 }
1219
1220 /**
1221 * @covers Revision::getParentLengths
1222 */
1223 public function testGetParentLengths_oneRevId() {
1224 $text = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1225 $textLength = strlen( $text );
1226
1227 $this->testPage->doEditContent( new WikitextContent( $text ), __METHOD__ );
1228 $rev[1] = $this->testPage->getLatest();
1229
1230 $this->assertSame(
1231 [ $rev[1] => $textLength ],
1232 Revision::getParentLengths(
1233 wfGetDB( DB_MASTER ),
1234 [ $rev[1] ]
1235 )
1236 );
1237 }
1238
1239 /**
1240 * @covers Revision::getParentLengths
1241 */
1242 public function testGetParentLengths_multipleRevIds() {
1243 $textOne = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1244 $textOneLength = strlen( $textOne );
1245 $textTwo = '831jr091jr092121j09rj1';
1246 $textTwoLength = strlen( $textTwo );
1247
1248 $this->testPage->doEditContent( new WikitextContent( $textOne ), __METHOD__ );
1249 $rev[1] = $this->testPage->getLatest();
1250 $this->testPage->doEditContent( new WikitextContent( $textTwo ), __METHOD__ );
1251 $rev[2] = $this->testPage->getLatest();
1252
1253 $this->assertSame(
1254 [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
1255 Revision::getParentLengths(
1256 wfGetDB( DB_MASTER ),
1257 [ $rev[1], $rev[2] ]
1258 )
1259 );
1260 }
1261
1262 /**
1263 * @covers Revision::getTitle
1264 */
1265 public function testGetTitle_fromExistingRevision() {
1266 $this->assertTrue(
1267 $this->testPage->getTitle()->equals(
1268 $this->testPage->getRevision()->getTitle()
1269 )
1270 );
1271 }
1272
1273 /**
1274 * @covers Revision::getTitle
1275 */
1276 public function testGetTitle_fromRevisionWhichWillLoadTheTitle() {
1277 $rev = new Revision( [ 'id' => $this->testPage->getLatest() ] );
1278 $this->assertTrue(
1279 $this->testPage->getTitle()->equals(
1280 $rev->getTitle()
1281 )
1282 );
1283 }
1284
1285 /**
1286 * @covers Revision::isMinor
1287 */
1288 public function testIsMinor_true() {
1289 // Use a sysop to ensure we can mark edits as minor
1290 $sysop = $this->getTestSysop()->getUser();
1291
1292 $this->testPage->doEditContent(
1293 new WikitextContent( __METHOD__ ),
1294 __METHOD__,
1295 EDIT_MINOR,
1296 false,
1297 $sysop
1298 );
1299 $rev = $this->testPage->getRevision();
1300
1301 $this->assertSame( true, $rev->isMinor() );
1302 }
1303
1304 /**
1305 * @covers Revision::isMinor
1306 */
1307 public function testIsMinor_false() {
1308 $this->testPage->doEditContent(
1309 new WikitextContent( __METHOD__ ),
1310 __METHOD__,
1311 0
1312 );
1313 $rev = $this->testPage->getRevision();
1314
1315 $this->assertSame( false, $rev->isMinor() );
1316 }
1317
1318 /**
1319 * @covers Revision::getTimestamp
1320 */
1321 public function testGetTimestamp() {
1322 $testTimestamp = wfTimestampNow();
1323
1324 $this->testPage->doEditContent(
1325 new WikitextContent( __METHOD__ ),
1326 __METHOD__
1327 );
1328 $rev = $this->testPage->getRevision();
1329
1330 $this->assertInternalType( 'string', $rev->getTimestamp() );
1331 $this->assertTrue( strlen( $rev->getTimestamp() ) == strlen( 'YYYYMMDDHHMMSS' ) );
1332 $this->assertContains( substr( $testTimestamp, 0, 10 ), $rev->getTimestamp() );
1333 }
1334
1335 /**
1336 * @covers Revision::getUser
1337 * @covers Revision::getUserText
1338 */
1339 public function testGetUserAndText() {
1340 $sysop = $this->getTestSysop()->getUser();
1341
1342 $this->testPage->doEditContent(
1343 new WikitextContent( __METHOD__ ),
1344 __METHOD__,
1345 0,
1346 false,
1347 $sysop
1348 );
1349 $rev = $this->testPage->getRevision();
1350
1351 $this->assertSame( $sysop->getId(), $rev->getUser() );
1352 $this->assertSame( $sysop->getName(), $rev->getUserText() );
1353 }
1354
1355 /**
1356 * @covers Revision::isDeleted
1357 */
1358 public function testIsDeleted_nothingDeleted() {
1359 $rev = $this->testPage->getRevision();
1360
1361 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
1362 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_COMMENT ) );
1363 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
1364 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_USER ) );
1365 }
1366
1367 /**
1368 * @covers Revision::getVisibility
1369 */
1370 public function testGetVisibility_nothingDeleted() {
1371 $rev = $this->testPage->getRevision();
1372
1373 $this->assertSame( 0, $rev->getVisibility() );
1374 }
1375
1376 /**
1377 * @covers Revision::getComment
1378 */
1379 public function testGetComment_notDeleted() {
1380 $expectedSummary = 'goatlicious summary';
1381
1382 $this->testPage->doEditContent(
1383 new WikitextContent( __METHOD__ ),
1384 $expectedSummary
1385 );
1386 $rev = $this->testPage->getRevision();
1387
1388 $this->assertSame( $expectedSummary, $rev->getComment() );
1389 }
1390
1391 /**
1392 * @covers Revision::isUnpatrolled
1393 */
1394 public function testIsUnpatrolled_returnsRecentChangesId() {
1395 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1396 $rev = $this->testPage->getRevision();
1397
1398 $this->assertGreaterThan( 0, $rev->isUnpatrolled() );
1399 $this->assertSame( $rev->getRecentChange()->getAttribute( 'rc_id' ), $rev->isUnpatrolled() );
1400 }
1401
1402 /**
1403 * @covers Revision::isUnpatrolled
1404 */
1405 public function testIsUnpatrolled_returnsZeroIfPatrolled() {
1406 // This assumes that sysops are auto patrolled
1407 $sysop = $this->getTestSysop()->getUser();
1408 $this->testPage->doEditContent(
1409 new WikitextContent( __METHOD__ ),
1410 __METHOD__,
1411 0,
1412 false,
1413 $sysop
1414 );
1415 $rev = $this->testPage->getRevision();
1416
1417 $this->assertSame( 0, $rev->isUnpatrolled() );
1418 }
1419
1420 /**
1421 * This is a simple blanket test for all simple content getters and is methods to provide some
1422 * coverage before the split of Revision into multiple classes for MCR work.
1423 * @covers Revision::getContent
1424 * @covers Revision::getSerializedData
1425 * @covers Revision::getContentModel
1426 * @covers Revision::getContentFormat
1427 * @covers Revision::getContentHandler
1428 */
1429 public function testSimpleContentGetters() {
1430 $expectedText = 'testSimpleContentGetters in Revision. Goats love MCR...';
1431 $expectedSummary = 'goatlicious testSimpleContentGetters summary';
1432
1433 $this->testPage->doEditContent(
1434 new WikitextContent( $expectedText ),
1435 $expectedSummary
1436 );
1437 $rev = $this->testPage->getRevision();
1438
1439 $this->assertSame( $expectedText, $rev->getContent()->getText() );
1440 $this->assertSame( $expectedText, $rev->getSerializedData() );
1441 $this->assertSame( $this->testPage->getContentModel(), $rev->getContentModel() );
1442 $this->assertSame( $this->testPage->getContent()->getDefaultFormat(), $rev->getContentFormat() );
1443 $this->assertSame( $this->testPage->getContentHandler(), $rev->getContentHandler() );
1444 }
1445
1446 /**
1447 * @covers Revision::newKnownCurrent
1448 */
1449 public function testNewKnownCurrent() {
1450 // Setup the services
1451 $this->overrideMwServices();
1452 $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
1453 $this->setService( 'MainWANObjectCache', $cache );
1454 $db = wfGetDB( DB_MASTER );
1455
1456 $now = 1553893742;
1457 $cache->setMockTime( $now );
1458
1459 // Get a fresh revision to use during testing
1460 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1461 $rev = $this->testPage->getRevision();
1462
1463 // Clear any previous cache for the revision during creation
1464 $key = $cache->makeGlobalKey(
1465 RevisionStore::ROW_CACHE_KEY,
1466 $db->getDomainID(),
1467 $rev->getPage(),
1468 $rev->getId()
1469 );
1470 $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
1471 $this->assertFalse( $cache->get( $key ) );
1472
1473 ++$now;
1474
1475 // Get the new revision and make sure it is in the cache and correct
1476 $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
1477 $this->assertRevEquals( $rev, $newRev );
1478
1479 $cachedRow = $cache->get( $key );
1480 $this->assertNotFalse( $cachedRow );
1481 $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
1482 }
1483
1484 public function testNewKnownCurrent_withPageId() {
1485 $db = wfGetDB( DB_MASTER );
1486
1487 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1488 $rev = $this->testPage->getRevision();
1489
1490 $pageId = $this->testPage->getId();
1491
1492 $newRev = Revision::newKnownCurrent( $db, $pageId, $rev->getId() );
1493 $this->assertRevEquals( $rev, $newRev );
1494 }
1495
1496 public function testNewKnownCurrent_returnsFalseWhenTitleDoesntExist() {
1497 $db = wfGetDB( DB_MASTER );
1498
1499 $this->assertFalse( Revision::newKnownCurrent( $db, 0 ) );
1500 }
1501
1502 public function provideUserCanBitfield() {
1503 yield [ 0, 0, [], null, true ];
1504 // Bitfields match, user has no permissions
1505 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], null, false ];
1506 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], null, false ];
1507 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], null, false ];
1508 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], null, false ];
1509 // Bitfields match, user (admin) does have permissions
1510 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], null, true ];
1511 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], null, true ];
1512 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], null, true ];
1513 // Bitfields match, user (admin) does not have permissions
1514 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], null, false ];
1515 // Bitfields match, user (oversight) does have permissions
1516 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], null, true ];
1517 // Check permissions using the title
1518 yield [
1519 Revision::DELETED_TEXT,
1520 Revision::DELETED_TEXT,
1521 [ 'sysop' ],
1522 __METHOD__,
1523 true,
1524 ];
1525 yield [
1526 Revision::DELETED_TEXT,
1527 Revision::DELETED_TEXT,
1528 [],
1529 __METHOD__,
1530 false,
1531 ];
1532 }
1533
1534 /**
1535 * @dataProvider provideUserCanBitfield
1536 * @covers Revision::userCanBitfield
1537 */
1538 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
1539 $title = Title::newFromText( $title );
1540
1541 $this->setMwGlobals(
1542 'wgGroupPermissions',
1543 [
1544 'sysop' => [
1545 'deletedtext' => true,
1546 'deletedhistory' => true,
1547 ],
1548 'oversight' => [
1549 'viewsuppressed' => true,
1550 'suppressrevision' => true,
1551 ],
1552 ]
1553 );
1554 $user = $this->getTestUser( $userGroups )->getUser();
1555
1556 $this->assertSame(
1557 $expected,
1558 Revision::userCanBitfield( $bitField, $field, $user, $title )
1559 );
1560
1561 // Fallback to $wgUser
1562 $this->setMwGlobals(
1563 'wgUser',
1564 $user
1565 );
1566 $this->assertSame(
1567 $expected,
1568 Revision::userCanBitfield( $bitField, $field, null, $title )
1569 );
1570 }
1571
1572 public function provideUserCan() {
1573 yield [ 0, 0, [], true ];
1574 // Bitfields match, user has no permissions
1575 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], false ];
1576 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], false ];
1577 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], false ];
1578 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], false ];
1579 // Bitfields match, user (admin) does have permissions
1580 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], true ];
1581 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], true ];
1582 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], true ];
1583 // Bitfields match, user (admin) does not have permissions
1584 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], false ];
1585 // Bitfields match, user (oversight) does have permissions
1586 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], true ];
1587 }
1588
1589 /**
1590 * @dataProvider provideUserCan
1591 * @covers Revision::userCan
1592 */
1593 public function testUserCan( $bitField, $field, $userGroups, $expected ) {
1594 $this->setMwGlobals(
1595 'wgGroupPermissions',
1596 [
1597 'sysop' => [
1598 'deletedtext' => true,
1599 'deletedhistory' => true,
1600 ],
1601 'oversight' => [
1602 'viewsuppressed' => true,
1603 'suppressrevision' => true,
1604 ],
1605 ]
1606 );
1607 $user = $this->getTestUser( $userGroups )->getUser();
1608 $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
1609
1610 $this->assertSame(
1611 $expected,
1612 $revision->userCan( $field, $user )
1613 );
1614 }
1615
1616 public function provideGetTextId() {
1617 yield [ [], null ];
1618
1619 $slot = new SlotRecord( (object)[
1620 'slot_revision_id' => 42,
1621 'slot_content_id' => 1,
1622 'content_address' => 'tt:789',
1623 'model_name' => CONTENT_MODEL_WIKITEXT,
1624 'role_name' => SlotRecord::MAIN,
1625 'slot_origin' => 1,
1626 ], new WikitextContent( 'Test' ) );
1627
1628 $rec = new MutableRevisionRecord( $this->testPage->getTitle() );
1629 $rec->setId( 42 );
1630 $rec->setSlot( $slot );
1631
1632 yield [ $rec, 789 ];
1633 }
1634
1635 /**
1636 * @dataProvider provideGetTextId
1637 * @covers Revision::getTextId()
1638 */
1639 public function testGetTextId( $spec, $expected ) {
1640 $rev = new Revision( $spec, 0, $this->testPage->getTitle() );
1641 $this->assertSame( $expected, $rev->getTextId() );
1642 }
1643
1644 abstract public function provideGetRevisionText();
1645
1646 /**
1647 * @dataProvider provideGetRevisionText
1648 * @covers Revision::getRevisionText
1649 */
1650 public function testGetRevisionText( array $queryInfoOptions, array $queryInfoExtra = [] ) {
1651 $rev = $this->testPage->getRevisionRecord();
1652
1653 $queryInfo = Revision::getQueryInfo( $queryInfoOptions );
1654 $queryInfo['tables'] = array_merge( $queryInfo['tables'], $queryInfoExtra['tables'] ?? [] );
1655 $queryInfo['fields'] = array_merge( $queryInfo['fields'], $queryInfoExtra['fields'] ?? [] );
1656 $queryInfo['joins'] = array_merge( $queryInfo['joins'], $queryInfoExtra['joins'] ?? [] );
1657
1658 $conds = [ 'rev_id' => $rev->getId() ];
1659 $row = $this->db->selectRow(
1660 $queryInfo['tables'],
1661 $queryInfo['fields'],
1662 $conds,
1663 __METHOD__,
1664 [],
1665 $queryInfo['joins']
1666 );
1667
1668 $expected = $rev->getContent( SlotRecord::MAIN )->serialize();
1669
1670 $this->hideDeprecated( 'Revision::getRevisionText (MCR without SCHEMA_COMPAT_WRITE_OLD)' );
1671 $this->assertSame( $expected, Revision::getRevisionText( $row ) );
1672 }
1673
1674 }