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