3 // phpcs:disable MediaWiki.Commenting.PhpunitAnnotations.NotClass
5 namespace MediaWiki\Tests\Revision
;
7 use CommentStoreComment
;
9 use MediaWiki\Revision\RevisionRecord
;
10 use MediaWiki\Revision\RevisionSlots
;
11 use MediaWiki\Revision\RevisionStoreRecord
;
12 use MediaWiki\Revision\SlotRecord
;
13 use MediaWiki\Revision\SuppressedDataException
;
14 use MediaWiki\User\UserIdentityValue
;
19 * @covers \MediaWiki\Revision\RevisionRecord
21 * @note Expects to be used in classes that extend MediaWikiTestCase.
23 trait RevisionRecordTests
{
26 * @param array $rowOverrides
28 * @return RevisionRecord
30 abstract protected function newRevision( array $rowOverrides = [] );
32 private function provideAudienceCheckData( $field ) {
33 yield
'field accessible for oversighter (ALL)' => [
34 RevisionRecord
::SUPPRESSED_ALL
,
40 yield
'field accessible for oversighter' => [
41 RevisionRecord
::DELETED_RESTRICTED |
$field,
47 yield
'field not accessible for sysops (ALL)' => [
48 RevisionRecord
::SUPPRESSED_ALL
,
54 yield
'field not accessible for sysops' => [
55 RevisionRecord
::DELETED_RESTRICTED |
$field,
61 yield
'field accessible for sysops' => [
68 yield
'field suppressed for logged in users' => [
75 yield
'unrelated field suppressed' => [
76 $field === RevisionRecord
::DELETED_COMMENT
77 ? RevisionRecord
::DELETED_USER
78 : RevisionRecord
::DELETED_COMMENT
,
84 yield
'nothing suppressed' => [
92 public function testSerialization_fails() {
93 $this->setExpectedException( LogicException
::class );
94 $rev = $this->newRevision();
98 public function provideGetComment_audience() {
99 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_COMMENT
);
102 private function forceStandardPermissions() {
104 'wgGroupPermissions',
107 'viewsuppressed' => false,
108 'suppressrevision' => false,
109 'deletedtext' => false,
110 'deletedhistory' => false,
113 'viewsuppressed' => false,
114 'suppressrevision' => false,
115 'deletedtext' => true,
116 'deletedhistory' => true,
119 'deletedtext' => true,
120 'deletedhistory' => true,
121 'viewsuppressed' => true,
122 'suppressrevision' => true,
129 * @dataProvider provideGetComment_audience
131 public function testGetComment_audience( $visibility, $groups, $userCan, $publicCan ) {
132 $this->forceStandardPermissions();
134 $user = $this->getTestUser( $groups )->getUser();
135 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
137 $this->assertNotNull( $rev->getComment( RevisionRecord
::RAW
), 'raw can' );
141 $rev->getComment( RevisionRecord
::FOR_PUBLIC
) !== null,
146 $rev->getComment( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
151 public function provideGetUser_audience() {
152 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_USER
);
156 * @dataProvider provideGetUser_audience
158 public function testGetUser_audience( $visibility, $groups, $userCan, $publicCan ) {
159 $this->forceStandardPermissions();
161 $user = $this->getTestUser( $groups )->getUser();
162 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
164 $this->assertNotNull( $rev->getUser( RevisionRecord
::RAW
), 'raw can' );
168 $rev->getUser( RevisionRecord
::FOR_PUBLIC
) !== null,
173 $rev->getUser( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
178 public function provideGetSlot_audience() {
179 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_TEXT
);
183 * @dataProvider provideGetSlot_audience
185 public function testGetSlot_audience( $visibility, $groups, $userCan, $publicCan ) {
186 $this->forceStandardPermissions();
188 $user = $this->getTestUser( $groups )->getUser();
189 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
191 // NOTE: slot meta-data is never suppressed, just the content is!
192 $this->assertTrue( $rev->hasSlot( SlotRecord
::MAIN
), 'hasSlot is never suppressed' );
193 $this->assertNotNull( $rev->getSlot( SlotRecord
::MAIN
, RevisionRecord
::RAW
), 'raw meta' );
194 $this->assertNotNull( $rev->getSlot( SlotRecord
::MAIN
, RevisionRecord
::FOR_PUBLIC
),
197 $this->assertNotNull(
198 $rev->getSlot( SlotRecord
::MAIN
, RevisionRecord
::FOR_THIS_USER
, $user ),
203 $rev->getSlot( SlotRecord
::MAIN
, RevisionRecord
::FOR_PUBLIC
)->getContent();
205 } catch ( SuppressedDataException
$ex ) {
216 $rev->getSlot( SlotRecord
::MAIN
, RevisionRecord
::FOR_THIS_USER
, $user )->getContent();
218 } catch ( SuppressedDataException
$ex ) {
230 * @dataProvider provideGetSlot_audience
232 public function testGetContent_audience( $visibility, $groups, $userCan, $publicCan ) {
233 $this->forceStandardPermissions();
235 $user = $this->getTestUser( $groups )->getUser();
236 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
238 $this->assertNotNull( $rev->getContent( SlotRecord
::MAIN
, RevisionRecord
::RAW
), 'raw can' );
242 $rev->getContent( SlotRecord
::MAIN
, RevisionRecord
::FOR_PUBLIC
) !== null,
247 $rev->getContent( SlotRecord
::MAIN
, RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
252 public function testGetSlot() {
253 $rev = $this->newRevision();
255 $slot = $rev->getSlot( SlotRecord
::MAIN
);
256 $this->assertNotNull( $slot, 'getSlot()' );
257 $this->assertSame( 'main', $slot->getRole(), 'getRole()' );
260 public function testHasSlot() {
261 $rev = $this->newRevision();
263 $this->assertTrue( $rev->hasSlot( SlotRecord
::MAIN
) );
264 $this->assertFalse( $rev->hasSlot( 'xyz' ) );
267 public function testGetContent() {
268 $rev = $this->newRevision();
270 $content = $rev->getSlot( SlotRecord
::MAIN
);
271 $this->assertNotNull( $content, 'getContent()' );
272 $this->assertSame( CONTENT_MODEL_TEXT
, $content->getModel(), 'getModel()' );
275 public function provideUserCanBitfield() {
276 yield
[ 0, 0, [], null, true ];
277 // Bitfields match, user has no permissions
279 RevisionRecord
::DELETED_TEXT
,
280 RevisionRecord
::DELETED_TEXT
,
286 RevisionRecord
::DELETED_COMMENT
,
287 RevisionRecord
::DELETED_COMMENT
,
293 RevisionRecord
::DELETED_USER
,
294 RevisionRecord
::DELETED_USER
,
300 RevisionRecord
::DELETED_RESTRICTED
,
301 RevisionRecord
::DELETED_RESTRICTED
,
306 // Bitfields match, user (admin) does have permissions
308 RevisionRecord
::DELETED_TEXT
,
309 RevisionRecord
::DELETED_TEXT
,
315 RevisionRecord
::DELETED_COMMENT
,
316 RevisionRecord
::DELETED_COMMENT
,
322 RevisionRecord
::DELETED_USER
,
323 RevisionRecord
::DELETED_USER
,
328 // Bitfields match, user (admin) does not have permissions
330 RevisionRecord
::DELETED_RESTRICTED
,
331 RevisionRecord
::DELETED_RESTRICTED
,
336 // Bitfields match, user (oversight) does have permissions
338 RevisionRecord
::DELETED_RESTRICTED
,
339 RevisionRecord
::DELETED_RESTRICTED
,
344 // Check permissions using the title
346 RevisionRecord
::DELETED_TEXT
,
347 RevisionRecord
::DELETED_TEXT
,
353 RevisionRecord
::DELETED_TEXT
,
354 RevisionRecord
::DELETED_TEXT
,
362 * @dataProvider provideUserCanBitfield
363 * @covers \MediaWiki\Revision\RevisionRecord::userCanBitfield
365 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
366 if ( is_string( $title ) ) {
367 // NOTE: Data providers cannot instantiate Title objects! See T202641.
368 $title = Title
::newFromText( $title );
371 $this->forceStandardPermissions();
373 $user = $this->getTestUser( $userGroups )->getUser();
377 RevisionRecord
::userCanBitfield( $bitField, $field, $user, $title )
381 public function provideHasSameContent() {
382 // Create some slots with content
383 $mainA = SlotRecord
::newUnsaved( SlotRecord
::MAIN
, new TextContent( 'A' ) );
384 $mainB = SlotRecord
::newUnsaved( SlotRecord
::MAIN
, new TextContent( 'B' ) );
385 $auxA = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
386 $auxB = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
388 $initialRecordSpec = [ [ $mainA ], 12 ];
391 'same record object' => [
396 'same record content, different object' => [
401 'same record content, aux slot, different object' => [
406 'different content' => [
411 'different content and number of slots' => [
414 [ [ $mainA, $mainB ], 13 ],
420 * @note Do not call directly from a data provider! Data providers cannot instantiate
421 * Title objects! See T202641.
423 * @param SlotRecord[] $slots
425 * @return RevisionStoreRecord
427 private function makeHasSameContentTestRecord( array $slots, $revId ) {
428 $title = Title
::newFromText( 'provideHasSameContent' );
429 $title->resetArticleID( 19 );
430 $slots = new RevisionSlots( $slots );
432 return new RevisionStoreRecord(
434 new UserIdentityValue( 11, __METHOD__
, 0 ),
435 CommentStoreComment
::newUnsavedComment( __METHOD__
),
437 'rev_id' => strval( $revId ),
438 'rev_page' => strval( $title->getArticleID() ),
439 'rev_timestamp' => '20200101000000',
441 'rev_minor_edit' => 0,
442 'rev_parent_id' => '5',
443 'rev_len' => $slots->computeSize(),
444 'rev_sha1' => $slots->computeSha1(),
445 'page_latest' => '18',
452 * @dataProvider provideHasSameContent
453 * @covers \MediaWiki\Revision\RevisionRecord::hasSameContent
456 public function testHasSameContent(
461 $record1 = $this->makeHasSameContentTestRecord( ...$recordSpec1 );
462 $record2 = $this->makeHasSameContentTestRecord( ...$recordSpec2 );
466 $record1->hasSameContent( $record2 )
470 public function provideIsDeleted() {
471 yield
'no deletion' => [
474 RevisionRecord
::DELETED_TEXT
=> false,
475 RevisionRecord
::DELETED_COMMENT
=> false,
476 RevisionRecord
::DELETED_USER
=> false,
477 RevisionRecord
::DELETED_RESTRICTED
=> false,
480 yield
'text deleted' => [
481 RevisionRecord
::DELETED_TEXT
,
483 RevisionRecord
::DELETED_TEXT
=> true,
484 RevisionRecord
::DELETED_COMMENT
=> false,
485 RevisionRecord
::DELETED_USER
=> false,
486 RevisionRecord
::DELETED_RESTRICTED
=> false,
489 yield
'text and comment deleted' => [
490 RevisionRecord
::DELETED_TEXT + RevisionRecord
::DELETED_COMMENT
,
492 RevisionRecord
::DELETED_TEXT
=> true,
493 RevisionRecord
::DELETED_COMMENT
=> true,
494 RevisionRecord
::DELETED_USER
=> false,
495 RevisionRecord
::DELETED_RESTRICTED
=> false,
498 yield
'all 4 deleted' => [
499 RevisionRecord
::DELETED_TEXT +
500 RevisionRecord
::DELETED_COMMENT +
501 RevisionRecord
::DELETED_RESTRICTED +
502 RevisionRecord
::DELETED_USER
,
504 RevisionRecord
::DELETED_TEXT
=> true,
505 RevisionRecord
::DELETED_COMMENT
=> true,
506 RevisionRecord
::DELETED_USER
=> true,
507 RevisionRecord
::DELETED_RESTRICTED
=> true,
513 * @dataProvider provideIsDeleted
514 * @covers \MediaWiki\Revision\RevisionRecord::isDeleted
516 public function testIsDeleted( $revDeleted, $assertionMap ) {
517 $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
518 foreach ( $assertionMap as $deletionLevel => $expected ) {
519 $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );
523 public function testIsReadyForInsertion() {
524 $rev = $this->newRevision();
525 $this->assertTrue( $rev->isReadyForInsertion() );