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