3 namespace MediaWiki\Tests\Storage
;
5 use CommentStoreComment
;
7 use MediaWiki\Storage\RevisionRecord
;
8 use MediaWiki\Storage\RevisionSlots
;
9 use MediaWiki\Storage\RevisionStoreRecord
;
10 use MediaWiki\Storage\SlotRecord
;
11 use MediaWiki\Storage\SuppressedDataException
;
12 use MediaWiki\User\UserIdentityValue
;
16 // PHPCS should not complain about @covers and @dataProvider being used in traits, see T192384
17 // phpcs:disable MediaWiki.Commenting.PhpunitAnnotations.NotTestClass
20 * @covers \MediaWiki\Storage\RevisionRecord
22 * @note Expects to be used in classes that extend MediaWikiTestCase.
24 trait RevisionRecordTests
{
27 * @param array $rowOverrides
29 * @return RevisionRecord
31 protected abstract function newRevision( array $rowOverrides = [] );
33 private function provideAudienceCheckData( $field ) {
34 yield
'field accessible for oversighter (ALL)' => [
35 RevisionRecord
::SUPPRESSED_ALL
,
41 yield
'field accessible for oversighter' => [
42 RevisionRecord
::DELETED_RESTRICTED |
$field,
48 yield
'field not accessible for sysops (ALL)' => [
49 RevisionRecord
::SUPPRESSED_ALL
,
55 yield
'field not accessible for sysops' => [
56 RevisionRecord
::DELETED_RESTRICTED |
$field,
62 yield
'field accessible for sysops' => [
69 yield
'field suppressed for logged in users' => [
76 yield
'unrelated field suppressed' => [
77 $field === RevisionRecord
::DELETED_COMMENT
78 ? RevisionRecord
::DELETED_USER
79 : RevisionRecord
::DELETED_COMMENT
,
85 yield
'nothing suppressed' => [
93 public function testSerialization_fails() {
94 $this->setExpectedException( LogicException
::class );
95 $rev = $this->newRevision();
99 public function provideGetComment_audience() {
100 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_COMMENT
);
103 private function forceStandardPermissions() {
105 'wgGroupPermissions',
108 'viewsuppressed' => false,
109 'suppressrevision' => false,
110 'deletedtext' => false,
111 'deletedhistory' => false,
114 'viewsuppressed' => false,
115 'suppressrevision' => false,
116 'deletedtext' => true,
117 'deletedhistory' => true,
120 'deletedtext' => true,
121 'deletedhistory' => true,
122 'viewsuppressed' => true,
123 'suppressrevision' => true,
130 * @dataProvider provideGetComment_audience
132 public function testGetComment_audience( $visibility, $groups, $userCan, $publicCan ) {
133 $this->forceStandardPermissions();
135 $user = $this->getTestUser( $groups )->getUser();
136 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
138 $this->assertNotNull( $rev->getComment( RevisionRecord
::RAW
), 'raw can' );
142 $rev->getComment( RevisionRecord
::FOR_PUBLIC
) !== null,
147 $rev->getComment( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
152 public function provideGetUser_audience() {
153 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_USER
);
157 * @dataProvider provideGetUser_audience
159 public function testGetUser_audience( $visibility, $groups, $userCan, $publicCan ) {
160 $this->forceStandardPermissions();
162 $user = $this->getTestUser( $groups )->getUser();
163 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
165 $this->assertNotNull( $rev->getUser( RevisionRecord
::RAW
), 'raw can' );
169 $rev->getUser( RevisionRecord
::FOR_PUBLIC
) !== null,
174 $rev->getUser( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
179 public function provideGetSlot_audience() {
180 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_TEXT
);
184 * @dataProvider provideGetSlot_audience
186 public function testGetSlot_audience( $visibility, $groups, $userCan, $publicCan ) {
187 $this->forceStandardPermissions();
189 $user = $this->getTestUser( $groups )->getUser();
190 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
192 // NOTE: slot meta-data is never suppressed, just the content is!
193 $this->assertTrue( $rev->hasSlot( 'main' ), 'hasSlot is never suppressed' );
194 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::RAW
), 'raw meta' );
195 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
), 'public meta' );
197 $this->assertNotNull(
198 $rev->getSlot( 'main', RevisionRecord
::FOR_THIS_USER
, $user ),
203 $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
)->getContent();
205 } catch ( SuppressedDataException
$ex ) {
216 $rev->getSlot( '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( 'main', RevisionRecord
::RAW
), 'raw can' );
242 $rev->getContent( 'main', RevisionRecord
::FOR_PUBLIC
) !== null,
247 $rev->getContent( 'main', RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
252 public function testGetSlot() {
253 $rev = $this->newRevision();
255 $slot = $rev->getSlot( '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( 'main' ) );
264 $this->assertFalse( $rev->hasSlot( 'xyz' ) );
267 public function testGetContent() {
268 $rev = $this->newRevision();
270 $content = $rev->getSlot( '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
,
349 Title
::newFromText( __METHOD__
),
353 RevisionRecord
::DELETED_TEXT
,
354 RevisionRecord
::DELETED_TEXT
,
356 Title
::newFromText( __METHOD__
),
362 * @dataProvider provideUserCanBitfield
363 * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
365 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
366 $this->forceStandardPermissions();
368 $user = $this->getTestUser( $userGroups )->getUser();
372 RevisionRecord
::userCanBitfield( $bitField, $field, $user, $title )
376 public function provideHasSameContent() {
378 * @param SlotRecord[] $slots
380 * @return RevisionStoreRecord
382 $recordCreator = function ( array $slots, $revId ) {
383 $title = Title
::newFromText( 'provideHasSameContent' );
384 $title->resetArticleID( 19 );
385 $slots = new RevisionSlots( $slots );
387 return new RevisionStoreRecord(
389 new UserIdentityValue( 11, __METHOD__
, 0 ),
390 CommentStoreComment
::newUnsavedComment( __METHOD__
),
392 'rev_id' => strval( $revId ),
393 'rev_page' => strval( $title->getArticleID() ),
394 'rev_timestamp' => '20200101000000',
396 'rev_minor_edit' => 0,
397 'rev_parent_id' => '5',
398 'rev_len' => $slots->computeSize(),
399 'rev_sha1' => $slots->computeSha1(),
400 'page_latest' => '18',
406 // Create some slots with content
407 $mainA = SlotRecord
::newUnsaved( 'main', new TextContent( 'A' ) );
408 $mainB = SlotRecord
::newUnsaved( 'main', new TextContent( 'B' ) );
409 $auxA = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
410 $auxB = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
412 $initialRecord = $recordCreator( [ $mainA ], 12 );
415 'same record object' => [
420 'same record content, different object' => [
422 $recordCreator( [ $mainA ], 12 ),
423 $recordCreator( [ $mainA ], 13 ),
425 'same record content, aux slot, different object' => [
427 $recordCreator( [ $auxA ], 12 ),
428 $recordCreator( [ $auxB ], 13 ),
430 'different content' => [
432 $recordCreator( [ $mainA ], 12 ),
433 $recordCreator( [ $mainB ], 13 ),
435 'different content and number of slots' => [
437 $recordCreator( [ $mainA ], 12 ),
438 $recordCreator( [ $mainA, $mainB ], 13 ),
444 * @dataProvider provideHasSameContent
445 * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
448 public function testHasSameContent(
450 RevisionRecord
$record1,
451 RevisionRecord
$record2
455 $record1->hasSameContent( $record2 )
459 public function provideIsDeleted() {
460 yield
'no deletion' => [
463 RevisionRecord
::DELETED_TEXT
=> false,
464 RevisionRecord
::DELETED_COMMENT
=> false,
465 RevisionRecord
::DELETED_USER
=> false,
466 RevisionRecord
::DELETED_RESTRICTED
=> false,
469 yield
'text deleted' => [
470 RevisionRecord
::DELETED_TEXT
,
472 RevisionRecord
::DELETED_TEXT
=> true,
473 RevisionRecord
::DELETED_COMMENT
=> false,
474 RevisionRecord
::DELETED_USER
=> false,
475 RevisionRecord
::DELETED_RESTRICTED
=> false,
478 yield
'text and comment deleted' => [
479 RevisionRecord
::DELETED_TEXT + RevisionRecord
::DELETED_COMMENT
,
481 RevisionRecord
::DELETED_TEXT
=> true,
482 RevisionRecord
::DELETED_COMMENT
=> true,
483 RevisionRecord
::DELETED_USER
=> false,
484 RevisionRecord
::DELETED_RESTRICTED
=> false,
487 yield
'all 4 deleted' => [
488 RevisionRecord
::DELETED_TEXT +
489 RevisionRecord
::DELETED_COMMENT +
490 RevisionRecord
::DELETED_RESTRICTED +
491 RevisionRecord
::DELETED_USER
,
493 RevisionRecord
::DELETED_TEXT
=> true,
494 RevisionRecord
::DELETED_COMMENT
=> true,
495 RevisionRecord
::DELETED_USER
=> true,
496 RevisionRecord
::DELETED_RESTRICTED
=> true,
502 * @dataProvider provideIsDeleted
503 * @covers \MediaWiki\Storage\RevisionRecord::isDeleted
505 public function testIsDeleted( $revDeleted, $assertionMap ) {
506 $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
507 foreach ( $assertionMap as $deletionLevel => $expected ) {
508 $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );