Add session_write_close() calls to SessionManager tests
[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_id'] );
354 return $f;
355 },
356 ];
357 yield [
358 function ( $f ) {
359 unset( $f['ar_page_id'] );
360 return $f;
361 },
362 ];
363 yield [
364 function ( $f ) {
365 unset( $f['ar_parent_id'] );
366 return $f;
367 },
368 ];
369 yield [
370 function ( $f ) {
371 unset( $f['ar_rev_id'] );
372 return $f;
373 },
374 ];
375 yield [
376 function ( $f ) {
377 unset( $f['ar_sha1'] );
378 return $f;
379 },
380 ];
381 }
382
383 /**
384 * @dataProvider provideNewFromArchiveRow
385 * @covers Revision::newFromArchiveRow
386 */
387 public function testNewFromArchiveRow( $selectModifier ) {
388 $services = MediaWikiServices::getInstance();
389
390 $store = new RevisionStore(
391 $services->getDBLoadBalancer(),
392 $services->getService( '_SqlBlobStore' ),
393 $services->getMainWANObjectCache(),
394 $services->getCommentStore(),
395 $services->getActorMigration()
396 );
397
398 $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
399 $this->setService( 'RevisionStore', $store );
400
401 $page = $this->createPage(
402 'RevisionStorageTest_testNewFromArchiveRow',
403 'Lorem Ipsum',
404 CONTENT_MODEL_WIKITEXT
405 );
406 $orig = $page->getRevision();
407 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
408
409 $dbr = wfGetDB( DB_REPLICA );
410 $arQuery = Revision::getArchiveQueryInfo();
411 $arQuery['fields'] = $selectModifier( $arQuery['fields'] );
412 $res = $dbr->select(
413 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
414 __METHOD__, [], $arQuery['joins']
415 );
416 $this->assertTrue( is_object( $res ), 'query failed' );
417
418 $row = $res->fetchObject();
419 $res->free();
420
421 // MCR migration note: $row is now required to contain ar_title and ar_namespace.
422 // Alternatively, a Title object can be passed to RevisionStore::newRevisionFromArchiveRow
423 $rev = Revision::newFromArchiveRow( $row );
424
425 $this->assertRevEquals( $orig, $rev );
426 }
427
428 /**
429 * @covers Revision::newFromArchiveRow
430 */
431 public function testNewFromArchiveRowOverrides() {
432 $page = $this->createPage(
433 'RevisionStorageTest_testNewFromArchiveRow',
434 'Lorem Ipsum',
435 CONTENT_MODEL_WIKITEXT
436 );
437 $orig = $page->getRevision();
438 $page->doDeleteArticle( 'test Revision::newFromArchiveRow' );
439
440 $dbr = wfGetDB( DB_REPLICA );
441 $arQuery = Revision::getArchiveQueryInfo();
442 $res = $dbr->select(
443 $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
444 __METHOD__, [], $arQuery['joins']
445 );
446 $this->assertTrue( is_object( $res ), 'query failed' );
447
448 $row = $res->fetchObject();
449 $res->free();
450
451 $rev = Revision::newFromArchiveRow( $row, [ 'comment_text' => 'SOMEOVERRIDE' ] );
452
453 $this->assertNotEquals( $orig->getComment(), $rev->getComment() );
454 $this->assertEquals( 'SOMEOVERRIDE', $rev->getComment() );
455 }
456
457 /**
458 * @covers Revision::newFromId
459 */
460 public function testNewFromId() {
461 $orig = $this->testPage->getRevision();
462 $rev = Revision::newFromId( $orig->getId() );
463 $this->assertRevEquals( $orig, $rev );
464 }
465
466 /**
467 * @covers Revision::newFromPageId
468 */
469 public function testNewFromPageId() {
470 $rev = Revision::newFromPageId( $this->testPage->getId() );
471 $this->assertRevEquals(
472 $this->testPage->getRevision(),
473 $rev
474 );
475 }
476
477 /**
478 * @covers Revision::newFromPageId
479 */
480 public function testNewFromPageIdWithLatestId() {
481 $rev = Revision::newFromPageId(
482 $this->testPage->getId(),
483 $this->testPage->getLatest()
484 );
485 $this->assertRevEquals(
486 $this->testPage->getRevision(),
487 $rev
488 );
489 }
490
491 /**
492 * @covers Revision::newFromPageId
493 */
494 public function testNewFromPageIdWithNotLatestId() {
495 $content = new WikitextContent( __METHOD__ );
496 $this->testPage->doEditContent( $content, __METHOD__ );
497 $rev = Revision::newFromPageId(
498 $this->testPage->getId(),
499 $this->testPage->getRevision()->getPrevious()->getId()
500 );
501 $this->assertRevEquals(
502 $this->testPage->getRevision()->getPrevious(),
503 $rev
504 );
505 }
506
507 /**
508 * @covers Revision::fetchRevision
509 */
510 public function testFetchRevision() {
511 // Hidden process cache assertion below
512 $this->testPage->getRevision()->getId();
513
514 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
515 $id = $this->testPage->getRevision()->getId();
516
517 $this->hideDeprecated( 'Revision::fetchRevision' );
518 $res = Revision::fetchRevision( $this->testPage->getTitle() );
519
520 # note: order is unspecified
521 $rows = [];
522 while ( ( $row = $res->fetchObject() ) ) {
523 $rows[$row->rev_id] = $row;
524 }
525
526 $this->assertEmpty( $rows, 'expected empty set' );
527 }
528
529 /**
530 * @covers Revision::getPage
531 */
532 public function testGetPage() {
533 $page = $this->testPage;
534
535 $orig = $this->makeRevisionWithProps( [ 'page' => $page->getId() ] );
536 $rev = Revision::newFromId( $orig->getId() );
537
538 $this->assertEquals( $page->getId(), $rev->getPage() );
539 }
540
541 /**
542 * @covers Revision::isCurrent
543 */
544 public function testIsCurrent() {
545 $rev1 = $this->testPage->getRevision();
546
547 # @todo find out if this should be true
548 # $this->assertTrue( $rev1->isCurrent() );
549
550 $rev1x = Revision::newFromId( $rev1->getId() );
551 $this->assertTrue( $rev1x->isCurrent() );
552
553 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
554 $rev2 = $this->testPage->getRevision();
555
556 # @todo find out if this should be true
557 # $this->assertTrue( $rev2->isCurrent() );
558
559 $rev1x = Revision::newFromId( $rev1->getId() );
560 $this->assertFalse( $rev1x->isCurrent() );
561
562 $rev2x = Revision::newFromId( $rev2->getId() );
563 $this->assertTrue( $rev2x->isCurrent() );
564 }
565
566 /**
567 * @covers Revision::getPrevious
568 */
569 public function testGetPrevious() {
570 $oldestRevision = $this->testPage->getOldestRevision();
571 $latestRevision = $this->testPage->getLatest();
572
573 $this->assertNull( $oldestRevision->getPrevious() );
574
575 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
576 $newRevision = $this->testPage->getRevision();
577
578 $this->assertNotNull( $newRevision->getPrevious() );
579 $this->assertEquals( $latestRevision, $newRevision->getPrevious()->getId() );
580 }
581
582 /**
583 * @covers Revision::getNext
584 */
585 public function testGetNext() {
586 $rev1 = $this->testPage->getRevision();
587
588 $this->assertNull( $rev1->getNext() );
589
590 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
591 $rev2 = $this->testPage->getRevision();
592
593 $this->assertNotNull( $rev1->getNext() );
594 $this->assertEquals( $rev2->getId(), $rev1->getNext()->getId() );
595 }
596
597 /**
598 * @covers Revision::newNullRevision
599 */
600 public function testNewNullRevision() {
601 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
602 $orig = $this->testPage->getRevision();
603
604 $dbw = wfGetDB( DB_MASTER );
605 $rev = Revision::newNullRevision( $dbw, $this->testPage->getId(), 'a null revision', false );
606
607 $this->assertNotEquals( $orig->getId(), $rev->getId(),
608 'new null revision should have a different id from the original revision' );
609 $this->assertEquals( $orig->getTextId(), $rev->getTextId(),
610 'new null revision should have the same text id as the original revision' );
611 $this->assertEquals( $orig->getSha1(), $rev->getSha1(),
612 'new null revision should have the same SHA1 as the original revision' );
613 $this->assertTrue( $orig->getRevisionRecord()->hasSameContent( $rev->getRevisionRecord() ),
614 'new null revision should have the same content as the original revision' );
615 $this->assertEquals( __METHOD__, $rev->getContent()->getNativeData() );
616 }
617
618 /**
619 * @covers Revision::newNullRevision
620 */
621 public function testNewNullRevision_badPage() {
622 $dbw = wfGetDB( DB_MASTER );
623 $rev = Revision::newNullRevision( $dbw, -1, 'a null revision', false );
624
625 $this->assertNull( $rev );
626 }
627
628 /**
629 * @covers Revision::insertOn
630 */
631 public function testInsertOn() {
632 $ip = '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7';
633
634 $orig = $this->makeRevisionWithProps( [
635 'user_text' => $ip
636 ] );
637
638 // Make sure the revision was copied to ip_changes
639 $dbr = wfGetDB( DB_REPLICA );
640 $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $orig->getId() ] );
641 $row = $res->fetchObject();
642
643 $this->assertEquals( IP::toHex( $ip ), $row->ipc_hex );
644 $this->assertEquals(
645 $orig->getTimestamp(),
646 wfTimestamp( TS_MW, $row->ipc_rev_timestamp )
647 );
648 }
649
650 public static function provideUserWasLastToEdit() {
651 yield 'actually the last edit' => [ 3, true ];
652 yield 'not the current edit, but still by this user' => [ 2, true ];
653 yield 'edit by another user' => [ 1, false ];
654 yield 'first edit, by this user, but another user edited in the mean time' => [ 0, false ];
655 }
656
657 /**
658 * @covers Revision::userWasLastToEdit
659 * @dataProvider provideUserWasLastToEdit
660 */
661 public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) {
662 $userA = User::newFromName( "RevisionStorageTest_userA" );
663 $userB = User::newFromName( "RevisionStorageTest_userB" );
664
665 if ( $userA->getId() === 0 ) {
666 $userA = User::createNew( $userA->getName() );
667 }
668
669 if ( $userB->getId() === 0 ) {
670 $userB = User::createNew( $userB->getName() );
671 }
672
673 $ns = $this->getDefaultWikitextNS();
674
675 $dbw = wfGetDB( DB_MASTER );
676 $revisions = [];
677
678 // create revisions -----------------------------
679 $page = WikiPage::factory( Title::newFromText(
680 'RevisionStorageTest_testUserWasLastToEdit', $ns ) );
681 $page->insertOn( $dbw );
682
683 $revisions[0] = new Revision( [
684 'page' => $page->getId(),
685 // we need the title to determine the page's default content model
686 'title' => $page->getTitle(),
687 'timestamp' => '20120101000000',
688 'user' => $userA->getId(),
689 'text' => 'zero',
690 'content_model' => CONTENT_MODEL_WIKITEXT,
691 'comment' => 'edit zero'
692 ] );
693 $revisions[0]->insertOn( $dbw );
694
695 $revisions[1] = new Revision( [
696 'page' => $page->getId(),
697 // still need the title, because $page->getId() is 0 (there's no entry in the page table)
698 'title' => $page->getTitle(),
699 'timestamp' => '20120101000100',
700 'user' => $userA->getId(),
701 'text' => 'one',
702 'content_model' => CONTENT_MODEL_WIKITEXT,
703 'comment' => 'edit one'
704 ] );
705 $revisions[1]->insertOn( $dbw );
706
707 $revisions[2] = new Revision( [
708 'page' => $page->getId(),
709 'title' => $page->getTitle(),
710 'timestamp' => '20120101000200',
711 'user' => $userB->getId(),
712 'text' => 'two',
713 'content_model' => CONTENT_MODEL_WIKITEXT,
714 'comment' => 'edit two'
715 ] );
716 $revisions[2]->insertOn( $dbw );
717
718 $revisions[3] = new Revision( [
719 'page' => $page->getId(),
720 'title' => $page->getTitle(),
721 'timestamp' => '20120101000300',
722 'user' => $userA->getId(),
723 'text' => 'three',
724 'content_model' => CONTENT_MODEL_WIKITEXT,
725 'comment' => 'edit three'
726 ] );
727 $revisions[3]->insertOn( $dbw );
728
729 $revisions[4] = new Revision( [
730 'page' => $page->getId(),
731 'title' => $page->getTitle(),
732 'timestamp' => '20120101000200',
733 'user' => $userA->getId(),
734 'text' => 'zero',
735 'content_model' => CONTENT_MODEL_WIKITEXT,
736 'comment' => 'edit four'
737 ] );
738 $revisions[4]->insertOn( $dbw );
739
740 // test it ---------------------------------
741 $since = $revisions[$sinceIdx]->getTimestamp();
742
743 $revQuery = Revision::getQueryInfo();
744 $allRows = iterator_to_array( $dbw->select(
745 $revQuery['tables'],
746 [ 'rev_id', 'rev_timestamp', 'rev_user' => $revQuery['fields']['rev_user'] ],
747 [
748 'rev_page' => $page->getId(),
749 //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
750 ],
751 __METHOD__,
752 [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
753 $revQuery['joins']
754 ) );
755
756 $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
757
758 $this->assertEquals( $expectedLast, $wasLast );
759 }
760
761 /**
762 * @param string $text
763 * @param string $title
764 * @param string $model
765 * @param string $format
766 *
767 * @return Revision
768 */
769 private function newTestRevision( $text, $title = "Test",
770 $model = CONTENT_MODEL_WIKITEXT, $format = null
771 ) {
772 if ( is_string( $title ) ) {
773 $title = Title::newFromText( $title );
774 }
775
776 $content = ContentHandler::makeContent( $text, $title, $model, $format );
777
778 $rev = new Revision(
779 [
780 'id' => 42,
781 'page' => 23,
782 'title' => $title,
783
784 'content' => $content,
785 'length' => $content->getSize(),
786 'comment' => "testing",
787 'minor_edit' => false,
788
789 'content_format' => $format,
790 ]
791 );
792
793 return $rev;
794 }
795
796 public function provideGetContentModel() {
797 // NOTE: we expect the help namespace to always contain wikitext
798 return [
799 [ 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ],
800 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ],
801 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
802 ];
803 }
804
805 /**
806 * @dataProvider provideGetContentModel
807 * @covers Revision::getContentModel
808 */
809 public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) {
810 $rev = $this->newTestRevision( $text, $title, $model, $format );
811
812 $this->assertEquals( $expectedModel, $rev->getContentModel() );
813 }
814
815 public function provideGetContentFormat() {
816 // NOTE: we expect the help namespace to always contain wikitext
817 return [
818 [ 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ],
819 [ 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ],
820 [ 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ],
821 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentForTesting::MODEL_ID ],
822 ];
823 }
824
825 /**
826 * @dataProvider provideGetContentFormat
827 * @covers Revision::getContentFormat
828 */
829 public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) {
830 $rev = $this->newTestRevision( $text, $title, $model, $format );
831
832 $this->assertEquals( $expectedFormat, $rev->getContentFormat() );
833 }
834
835 public function provideGetContentHandler() {
836 // NOTE: we expect the help namespace to always contain wikitext
837 return [
838 [ 'hello world', 'Help:Hello', null, null, WikitextContentHandler::class ],
839 [ 'hello world', 'User:hello/there.css', null, null, CssContentHandler::class ],
840 [ serialize( 'hello world' ), 'Dummy:Hello', null, null, DummyContentHandlerForTesting::class ],
841 ];
842 }
843
844 /**
845 * @dataProvider provideGetContentHandler
846 * @covers Revision::getContentHandler
847 */
848 public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) {
849 $rev = $this->newTestRevision( $text, $title, $model, $format );
850
851 $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) );
852 }
853
854 public function provideGetContent() {
855 // NOTE: we expect the help namespace to always contain wikitext
856 return [
857 [ 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ],
858 [
859 serialize( 'hello world' ),
860 'Hello',
861 DummyContentForTesting::MODEL_ID,
862 null,
863 Revision::FOR_PUBLIC,
864 serialize( 'hello world' )
865 ],
866 [
867 serialize( 'hello world' ),
868 'Dummy:Hello',
869 null,
870 null,
871 Revision::FOR_PUBLIC,
872 serialize( 'hello world' )
873 ],
874 ];
875 }
876
877 /**
878 * @dataProvider provideGetContent
879 * @covers Revision::getContent
880 */
881 public function testGetContent( $text, $title, $model, $format,
882 $audience, $expectedSerialization
883 ) {
884 $rev = $this->newTestRevision( $text, $title, $model, $format );
885 $content = $rev->getContent( $audience );
886
887 $this->assertEquals(
888 $expectedSerialization,
889 is_null( $content ) ? null : $content->serialize( $format )
890 );
891 }
892
893 /**
894 * @covers Revision::getContent
895 */
896 public function testGetContent_failure() {
897 $rev = new Revision( [
898 'page' => $this->testPage->getId(),
899 'content_model' => $this->testPage->getContentModel(),
900 'text_id' => 123456789, // not in the test DB
901 ] );
902
903 Wikimedia\suppressWarnings(); // bad text_id will trigger a warning.
904
905 $this->assertNull( $rev->getContent(),
906 "getContent() should return null if the revision's text blob could not be loaded." );
907
908 // NOTE: check this twice, once for lazy initialization, and once with the cached value.
909 $this->assertNull( $rev->getContent(),
910 "getContent() should return null if the revision's text blob could not be loaded." );
911
912 Wikimedia\restoreWarnings();
913 }
914
915 public function provideGetSize() {
916 return [
917 [ "hello world.", CONTENT_MODEL_WIKITEXT, 12 ],
918 [ serialize( "hello world." ), DummyContentForTesting::MODEL_ID, 12 ],
919 ];
920 }
921
922 /**
923 * @covers Revision::getSize
924 * @dataProvider provideGetSize
925 */
926 public function testGetSize( $text, $model, $expected_size ) {
927 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model );
928 $this->assertEquals( $expected_size, $rev->getSize() );
929 }
930
931 public function provideGetSha1() {
932 return [
933 [ "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ],
934 [
935 serialize( "hello world." ),
936 DummyContentForTesting::MODEL_ID,
937 Revision::base36Sha1( serialize( "hello world." ) )
938 ],
939 ];
940 }
941
942 /**
943 * @covers Revision::getSha1
944 * @dataProvider provideGetSha1
945 */
946 public function testGetSha1( $text, $model, $expected_hash ) {
947 $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model );
948 $this->assertEquals( $expected_hash, $rev->getSha1() );
949 }
950
951 /**
952 * Tests whether $rev->getContent() returns a clone when needed.
953 *
954 * @covers Revision::getContent
955 */
956 public function testGetContentClone() {
957 $content = new RevisionTestModifyableContent( "foo" );
958
959 $rev = new Revision(
960 [
961 'id' => 42,
962 'page' => 23,
963 'title' => Title::newFromText( "testGetContentClone_dummy" ),
964
965 'content' => $content,
966 'length' => $content->getSize(),
967 'comment' => "testing",
968 'minor_edit' => false,
969 ]
970 );
971
972 /** @var RevisionTestModifyableContent $content */
973 $content = $rev->getContent( Revision::RAW );
974 $content->setText( "bar" );
975
976 /** @var RevisionTestModifyableContent $content2 */
977 $content2 = $rev->getContent( Revision::RAW );
978 // content is mutable, expect clone
979 $this->assertNotSame( $content, $content2, "expected a clone" );
980 // clone should contain the original text
981 $this->assertEquals( "foo", $content2->getText() );
982
983 $content2->setText( "bla bla" );
984 // clones should be independent
985 $this->assertEquals( "bar", $content->getText() );
986 }
987
988 /**
989 * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
990 * @covers Revision::getContent
991 */
992 public function testGetContentUncloned() {
993 $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
994 $content = $rev->getContent( Revision::RAW );
995 $content2 = $rev->getContent( Revision::RAW );
996
997 // for immutable content like wikitext, this should be the same object
998 $this->assertSame( $content, $content2 );
999 }
1000
1001 /**
1002 * @covers Revision::loadFromId
1003 */
1004 public function testLoadFromId() {
1005 $rev = $this->testPage->getRevision();
1006 $this->hideDeprecated( 'Revision::loadFromId' );
1007 $this->assertRevEquals(
1008 $rev,
1009 Revision::loadFromId( wfGetDB( DB_MASTER ), $rev->getId() )
1010 );
1011 }
1012
1013 /**
1014 * @covers Revision::loadFromPageId
1015 */
1016 public function testLoadFromPageId() {
1017 $this->assertRevEquals(
1018 $this->testPage->getRevision(),
1019 Revision::loadFromPageId( wfGetDB( DB_MASTER ), $this->testPage->getId() )
1020 );
1021 }
1022
1023 /**
1024 * @covers Revision::loadFromPageId
1025 */
1026 public function testLoadFromPageIdWithLatestRevId() {
1027 $this->assertRevEquals(
1028 $this->testPage->getRevision(),
1029 Revision::loadFromPageId(
1030 wfGetDB( DB_MASTER ),
1031 $this->testPage->getId(),
1032 $this->testPage->getLatest()
1033 )
1034 );
1035 }
1036
1037 /**
1038 * @covers Revision::loadFromPageId
1039 */
1040 public function testLoadFromPageIdWithNotLatestRevId() {
1041 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1042 $this->assertRevEquals(
1043 $this->testPage->getRevision()->getPrevious(),
1044 Revision::loadFromPageId(
1045 wfGetDB( DB_MASTER ),
1046 $this->testPage->getId(),
1047 $this->testPage->getRevision()->getPrevious()->getId()
1048 )
1049 );
1050 }
1051
1052 /**
1053 * @covers Revision::loadFromTitle
1054 */
1055 public function testLoadFromTitle() {
1056 $this->assertRevEquals(
1057 $this->testPage->getRevision(),
1058 Revision::loadFromTitle( wfGetDB( DB_MASTER ), $this->testPage->getTitle() )
1059 );
1060 }
1061
1062 /**
1063 * @covers Revision::loadFromTitle
1064 */
1065 public function testLoadFromTitleWithLatestRevId() {
1066 $this->assertRevEquals(
1067 $this->testPage->getRevision(),
1068 Revision::loadFromTitle(
1069 wfGetDB( DB_MASTER ),
1070 $this->testPage->getTitle(),
1071 $this->testPage->getLatest()
1072 )
1073 );
1074 }
1075
1076 /**
1077 * @covers Revision::loadFromTitle
1078 */
1079 public function testLoadFromTitleWithNotLatestRevId() {
1080 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1081 $this->assertRevEquals(
1082 $this->testPage->getRevision()->getPrevious(),
1083 Revision::loadFromTitle(
1084 wfGetDB( DB_MASTER ),
1085 $this->testPage->getTitle(),
1086 $this->testPage->getRevision()->getPrevious()->getId()
1087 )
1088 );
1089 }
1090
1091 /**
1092 * @covers Revision::loadFromTimestamp()
1093 */
1094 public function testLoadFromTimestamp() {
1095 $this->assertRevEquals(
1096 $this->testPage->getRevision(),
1097 Revision::loadFromTimestamp(
1098 wfGetDB( DB_MASTER ),
1099 $this->testPage->getTitle(),
1100 $this->testPage->getRevision()->getTimestamp()
1101 )
1102 );
1103 }
1104
1105 /**
1106 * @covers Revision::getParentLengths
1107 */
1108 public function testGetParentLengths_noRevIds() {
1109 $this->assertSame(
1110 [],
1111 Revision::getParentLengths(
1112 wfGetDB( DB_MASTER ),
1113 []
1114 )
1115 );
1116 }
1117
1118 /**
1119 * @covers Revision::getParentLengths
1120 */
1121 public function testGetParentLengths_oneRevId() {
1122 $text = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1123 $textLength = strlen( $text );
1124
1125 $this->testPage->doEditContent( new WikitextContent( $text ), __METHOD__ );
1126 $rev[1] = $this->testPage->getLatest();
1127
1128 $this->assertSame(
1129 [ $rev[1] => $textLength ],
1130 Revision::getParentLengths(
1131 wfGetDB( DB_MASTER ),
1132 [ $rev[1] ]
1133 )
1134 );
1135 }
1136
1137 /**
1138 * @covers Revision::getParentLengths
1139 */
1140 public function testGetParentLengths_multipleRevIds() {
1141 $textOne = '831jr091jr0921kr21kr0921kjr0921j09rj1';
1142 $textOneLength = strlen( $textOne );
1143 $textTwo = '831jr091jr092121j09rj1';
1144 $textTwoLength = strlen( $textTwo );
1145
1146 $this->testPage->doEditContent( new WikitextContent( $textOne ), __METHOD__ );
1147 $rev[1] = $this->testPage->getLatest();
1148 $this->testPage->doEditContent( new WikitextContent( $textTwo ), __METHOD__ );
1149 $rev[2] = $this->testPage->getLatest();
1150
1151 $this->assertSame(
1152 [ $rev[1] => $textOneLength, $rev[2] => $textTwoLength ],
1153 Revision::getParentLengths(
1154 wfGetDB( DB_MASTER ),
1155 [ $rev[1], $rev[2] ]
1156 )
1157 );
1158 }
1159
1160 /**
1161 * @covers Revision::getTitle
1162 */
1163 public function testGetTitle_fromExistingRevision() {
1164 $this->assertTrue(
1165 $this->testPage->getTitle()->equals(
1166 $this->testPage->getRevision()->getTitle()
1167 )
1168 );
1169 }
1170
1171 /**
1172 * @covers Revision::getTitle
1173 */
1174 public function testGetTitle_fromRevisionWhichWillLoadTheTitle() {
1175 $rev = new Revision( [ 'id' => $this->testPage->getLatest() ] );
1176 $this->assertTrue(
1177 $this->testPage->getTitle()->equals(
1178 $rev->getTitle()
1179 )
1180 );
1181 }
1182
1183 /**
1184 * @covers Revision::isMinor
1185 */
1186 public function testIsMinor_true() {
1187 // Use a sysop to ensure we can mark edits as minor
1188 $sysop = $this->getTestSysop()->getUser();
1189
1190 $this->testPage->doEditContent(
1191 new WikitextContent( __METHOD__ ),
1192 __METHOD__,
1193 EDIT_MINOR,
1194 false,
1195 $sysop
1196 );
1197 $rev = $this->testPage->getRevision();
1198
1199 $this->assertSame( true, $rev->isMinor() );
1200 }
1201
1202 /**
1203 * @covers Revision::isMinor
1204 */
1205 public function testIsMinor_false() {
1206 $this->testPage->doEditContent(
1207 new WikitextContent( __METHOD__ ),
1208 __METHOD__,
1209 0
1210 );
1211 $rev = $this->testPage->getRevision();
1212
1213 $this->assertSame( false, $rev->isMinor() );
1214 }
1215
1216 /**
1217 * @covers Revision::getTimestamp
1218 */
1219 public function testGetTimestamp() {
1220 $testTimestamp = wfTimestampNow();
1221
1222 $this->testPage->doEditContent(
1223 new WikitextContent( __METHOD__ ),
1224 __METHOD__
1225 );
1226 $rev = $this->testPage->getRevision();
1227
1228 $this->assertInternalType( 'string', $rev->getTimestamp() );
1229 $this->assertTrue( strlen( $rev->getTimestamp() ) == strlen( 'YYYYMMDDHHMMSS' ) );
1230 $this->assertContains( substr( $testTimestamp, 0, 10 ), $rev->getTimestamp() );
1231 }
1232
1233 /**
1234 * @covers Revision::getUser
1235 * @covers Revision::getUserText
1236 */
1237 public function testGetUserAndText() {
1238 $sysop = $this->getTestSysop()->getUser();
1239
1240 $this->testPage->doEditContent(
1241 new WikitextContent( __METHOD__ ),
1242 __METHOD__,
1243 0,
1244 false,
1245 $sysop
1246 );
1247 $rev = $this->testPage->getRevision();
1248
1249 $this->assertSame( $sysop->getId(), $rev->getUser() );
1250 $this->assertSame( $sysop->getName(), $rev->getUserText() );
1251 }
1252
1253 /**
1254 * @covers Revision::isDeleted
1255 */
1256 public function testIsDeleted_nothingDeleted() {
1257 $rev = $this->testPage->getRevision();
1258
1259 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_TEXT ) );
1260 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_COMMENT ) );
1261 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
1262 $this->assertSame( false, $rev->isDeleted( Revision::DELETED_USER ) );
1263 }
1264
1265 /**
1266 * @covers Revision::getVisibility
1267 */
1268 public function testGetVisibility_nothingDeleted() {
1269 $rev = $this->testPage->getRevision();
1270
1271 $this->assertSame( 0, $rev->getVisibility() );
1272 }
1273
1274 /**
1275 * @covers Revision::getComment
1276 */
1277 public function testGetComment_notDeleted() {
1278 $expectedSummary = 'goatlicious summary';
1279
1280 $this->testPage->doEditContent(
1281 new WikitextContent( __METHOD__ ),
1282 $expectedSummary
1283 );
1284 $rev = $this->testPage->getRevision();
1285
1286 $this->assertSame( $expectedSummary, $rev->getComment() );
1287 }
1288
1289 /**
1290 * @covers Revision::isUnpatrolled
1291 */
1292 public function testIsUnpatrolled_returnsRecentChangesId() {
1293 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1294 $rev = $this->testPage->getRevision();
1295
1296 $this->assertGreaterThan( 0, $rev->isUnpatrolled() );
1297 $this->assertSame( $rev->getRecentChange()->getAttribute( 'rc_id' ), $rev->isUnpatrolled() );
1298 }
1299
1300 /**
1301 * @covers Revision::isUnpatrolled
1302 */
1303 public function testIsUnpatrolled_returnsZeroIfPatrolled() {
1304 // This assumes that sysops are auto patrolled
1305 $sysop = $this->getTestSysop()->getUser();
1306 $this->testPage->doEditContent(
1307 new WikitextContent( __METHOD__ ),
1308 __METHOD__,
1309 0,
1310 false,
1311 $sysop
1312 );
1313 $rev = $this->testPage->getRevision();
1314
1315 $this->assertSame( 0, $rev->isUnpatrolled() );
1316 }
1317
1318 /**
1319 * This is a simple blanket test for all simple content getters and is methods to provide some
1320 * coverage before the split of Revision into multiple classes for MCR work.
1321 * @covers Revision::getContent
1322 * @covers Revision::getSerializedData
1323 * @covers Revision::getContentModel
1324 * @covers Revision::getContentFormat
1325 * @covers Revision::getContentHandler
1326 */
1327 public function testSimpleContentGetters() {
1328 $expectedText = 'testSimpleContentGetters in Revision. Goats love MCR...';
1329 $expectedSummary = 'goatlicious testSimpleContentGetters summary';
1330
1331 $this->testPage->doEditContent(
1332 new WikitextContent( $expectedText ),
1333 $expectedSummary
1334 );
1335 $rev = $this->testPage->getRevision();
1336
1337 $this->assertSame( $expectedText, $rev->getContent()->getNativeData() );
1338 $this->assertSame( $expectedText, $rev->getSerializedData() );
1339 $this->assertSame( $this->testPage->getContentModel(), $rev->getContentModel() );
1340 $this->assertSame( $this->testPage->getContent()->getDefaultFormat(), $rev->getContentFormat() );
1341 $this->assertSame( $this->testPage->getContentHandler(), $rev->getContentHandler() );
1342 }
1343
1344 /**
1345 * @covers Revision::newKnownCurrent
1346 */
1347 public function testNewKnownCurrent() {
1348 // Setup the services
1349 $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
1350 $this->setService( 'MainWANObjectCache', $cache );
1351 $db = wfGetDB( DB_MASTER );
1352
1353 // Get a fresh revision to use during testing
1354 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1355 $rev = $this->testPage->getRevision();
1356
1357 // Clear any previous cache for the revision during creation
1358 $key = $cache->makeGlobalKey( 'revision-row-1.29',
1359 $db->getDomainID(),
1360 $rev->getPage(),
1361 $rev->getId()
1362 );
1363 $cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
1364 $this->assertFalse( $cache->get( $key ) );
1365
1366 // Get the new revision and make sure it is in the cache and correct
1367 $newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
1368 $this->assertRevEquals( $rev, $newRev );
1369
1370 $cachedRow = $cache->get( $key );
1371 $this->assertNotFalse( $cachedRow );
1372 $this->assertEquals( $rev->getId(), $cachedRow->rev_id );
1373 }
1374
1375 public function testNewKnownCurrent_withPageId() {
1376 $db = wfGetDB( DB_MASTER );
1377
1378 $this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
1379 $rev = $this->testPage->getRevision();
1380
1381 $pageId = $this->testPage->getId();
1382
1383 $newRev = Revision::newKnownCurrent( $db, $pageId, $rev->getId() );
1384 $this->assertRevEquals( $rev, $newRev );
1385 }
1386
1387 public function testNewKnownCurrent_returnsFalseWhenTitleDoesntExist() {
1388 $db = wfGetDB( DB_MASTER );
1389
1390 $this->assertFalse( Revision::newKnownCurrent( $db, 0 ) );
1391 }
1392
1393 public function provideUserCanBitfield() {
1394 yield [ 0, 0, [], null, true ];
1395 // Bitfields match, user has no permissions
1396 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], null, false ];
1397 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], null, false ];
1398 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], null, false ];
1399 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], null, false ];
1400 // Bitfields match, user (admin) does have permissions
1401 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], null, true ];
1402 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], null, true ];
1403 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], null, true ];
1404 // Bitfields match, user (admin) does not have permissions
1405 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], null, false ];
1406 // Bitfields match, user (oversight) does have permissions
1407 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], null, true ];
1408 // Check permissions using the title
1409 yield [
1410 Revision::DELETED_TEXT,
1411 Revision::DELETED_TEXT,
1412 [ 'sysop' ],
1413 Title::newFromText( __METHOD__ ),
1414 true,
1415 ];
1416 yield [
1417 Revision::DELETED_TEXT,
1418 Revision::DELETED_TEXT,
1419 [],
1420 Title::newFromText( __METHOD__ ),
1421 false,
1422 ];
1423 }
1424
1425 /**
1426 * @dataProvider provideUserCanBitfield
1427 * @covers Revision::userCanBitfield
1428 */
1429 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
1430 $this->setMwGlobals(
1431 'wgGroupPermissions',
1432 [
1433 'sysop' => [
1434 'deletedtext' => true,
1435 'deletedhistory' => true,
1436 ],
1437 'oversight' => [
1438 'viewsuppressed' => true,
1439 'suppressrevision' => true,
1440 ],
1441 ]
1442 );
1443 $user = $this->getTestUser( $userGroups )->getUser();
1444
1445 $this->assertSame(
1446 $expected,
1447 Revision::userCanBitfield( $bitField, $field, $user, $title )
1448 );
1449
1450 // Fallback to $wgUser
1451 $this->setMwGlobals(
1452 'wgUser',
1453 $user
1454 );
1455 $this->assertSame(
1456 $expected,
1457 Revision::userCanBitfield( $bitField, $field, null, $title )
1458 );
1459 }
1460
1461 public function provideUserCan() {
1462 yield [ 0, 0, [], true ];
1463 // Bitfields match, user has no permissions
1464 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [], false ];
1465 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [], false ];
1466 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [], false ];
1467 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [], false ];
1468 // Bitfields match, user (admin) does have permissions
1469 yield [ Revision::DELETED_TEXT, Revision::DELETED_TEXT, [ 'sysop' ], true ];
1470 yield [ Revision::DELETED_COMMENT, Revision::DELETED_COMMENT, [ 'sysop' ], true ];
1471 yield [ Revision::DELETED_USER, Revision::DELETED_USER, [ 'sysop' ], true ];
1472 // Bitfields match, user (admin) does not have permissions
1473 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'sysop' ], false ];
1474 // Bitfields match, user (oversight) does have permissions
1475 yield [ Revision::DELETED_RESTRICTED, Revision::DELETED_RESTRICTED, [ 'oversight' ], true ];
1476 }
1477
1478 /**
1479 * @dataProvider provideUserCan
1480 * @covers Revision::userCan
1481 */
1482 public function testUserCan( $bitField, $field, $userGroups, $expected ) {
1483 $this->setMwGlobals(
1484 'wgGroupPermissions',
1485 [
1486 'sysop' => [
1487 'deletedtext' => true,
1488 'deletedhistory' => true,
1489 ],
1490 'oversight' => [
1491 'viewsuppressed' => true,
1492 'suppressrevision' => true,
1493 ],
1494 ]
1495 );
1496 $user = $this->getTestUser( $userGroups )->getUser();
1497 $revision = new Revision( [ 'deleted' => $bitField ], 0, $this->testPage->getTitle() );
1498
1499 $this->assertSame(
1500 $expected,
1501 $revision->userCan( $field, $user )
1502 );
1503 }
1504
1505 }