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
;
17 * @covers \MediaWiki\Storage\RevisionRecord
19 * @note Expects to be used in classes that extend MediaWikiTestCase.
21 trait RevisionRecordTests
{
24 * @param array $rowOverrides
26 * @return RevisionRecord
28 protected abstract function newRevision( array $rowOverrides = [] );
30 private function provideAudienceCheckData( $field ) {
31 yield
'field accessible for oversighter (ALL)' => [
32 RevisionRecord
::SUPPRESSED_ALL
,
38 yield
'field accessible for oversighter' => [
39 RevisionRecord
::DELETED_RESTRICTED |
$field,
45 yield
'field not accessible for sysops (ALL)' => [
46 RevisionRecord
::SUPPRESSED_ALL
,
52 yield
'field not accessible for sysops' => [
53 RevisionRecord
::DELETED_RESTRICTED |
$field,
59 yield
'field accessible for sysops' => [
66 yield
'field suppressed for logged in users' => [
73 yield
'unrelated field suppressed' => [
74 $field === RevisionRecord
::DELETED_COMMENT
75 ? RevisionRecord
::DELETED_USER
76 : RevisionRecord
::DELETED_COMMENT
,
82 yield
'nothing suppressed' => [
90 public function testSerialization_fails() {
91 $this->setExpectedException( LogicException
::class );
92 $rev = $this->newRevision();
96 public function provideGetComment_audience() {
97 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_COMMENT
);
100 private function forceStandardPermissions() {
102 'wgGroupPermissions',
105 'viewsuppressed' => false,
106 'suppressrevision' => false,
107 'deletedtext' => false,
108 'deletedhistory' => false,
111 'viewsuppressed' => false,
112 'suppressrevision' => false,
113 'deletedtext' => true,
114 'deletedhistory' => true,
117 'deletedtext' => true,
118 'deletedhistory' => true,
119 'viewsuppressed' => true,
120 'suppressrevision' => true,
127 * @dataProvider provideGetComment_audience
129 public function testGetComment_audience( $visibility, $groups, $userCan, $publicCan ) {
130 $this->forceStandardPermissions();
132 $user = $this->getTestUser( $groups )->getUser();
133 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
135 $this->assertNotNull( $rev->getComment( RevisionRecord
::RAW
), 'raw can' );
139 $rev->getComment( RevisionRecord
::FOR_PUBLIC
) !== null,
144 $rev->getComment( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
149 public function provideGetUser_audience() {
150 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_USER
);
154 * @dataProvider provideGetUser_audience
156 public function testGetUser_audience( $visibility, $groups, $userCan, $publicCan ) {
157 $this->forceStandardPermissions();
159 $user = $this->getTestUser( $groups )->getUser();
160 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
162 $this->assertNotNull( $rev->getUser( RevisionRecord
::RAW
), 'raw can' );
166 $rev->getUser( RevisionRecord
::FOR_PUBLIC
) !== null,
171 $rev->getUser( RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
176 public function provideGetSlot_audience() {
177 return $this->provideAudienceCheckData( RevisionRecord
::DELETED_TEXT
);
181 * @dataProvider provideGetSlot_audience
183 public function testGetSlot_audience( $visibility, $groups, $userCan, $publicCan ) {
184 $this->forceStandardPermissions();
186 $user = $this->getTestUser( $groups )->getUser();
187 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
189 // NOTE: slot meta-data is never suppressed, just the content is!
190 $this->assertTrue( $rev->hasSlot( 'main' ), 'hasSlot is never suppressed' );
191 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::RAW
), 'raw meta' );
192 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
), 'public meta' );
194 $this->assertNotNull(
195 $rev->getSlot( 'main', RevisionRecord
::FOR_THIS_USER
, $user ),
200 $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
)->getContent();
202 } catch ( SuppressedDataException
$ex ) {
213 $rev->getSlot( 'main', RevisionRecord
::FOR_THIS_USER
, $user )->getContent();
215 } catch ( SuppressedDataException
$ex ) {
227 * @dataProvider provideGetSlot_audience
229 public function testGetContent_audience( $visibility, $groups, $userCan, $publicCan ) {
230 $this->forceStandardPermissions();
232 $user = $this->getTestUser( $groups )->getUser();
233 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
235 $this->assertNotNull( $rev->getContent( 'main', RevisionRecord
::RAW
), 'raw can' );
239 $rev->getContent( 'main', RevisionRecord
::FOR_PUBLIC
) !== null,
244 $rev->getContent( 'main', RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
249 public function testGetSlot() {
250 $rev = $this->newRevision();
252 $slot = $rev->getSlot( 'main' );
253 $this->assertNotNull( $slot, 'getSlot()' );
254 $this->assertSame( 'main', $slot->getRole(), 'getRole()' );
257 public function testHasSlot() {
258 $rev = $this->newRevision();
260 $this->assertTrue( $rev->hasSlot( 'main' ) );
261 $this->assertFalse( $rev->hasSlot( 'xyz' ) );
264 public function testGetContent() {
265 $rev = $this->newRevision();
267 $content = $rev->getSlot( 'main' );
268 $this->assertNotNull( $content, 'getContent()' );
269 $this->assertSame( CONTENT_MODEL_TEXT
, $content->getModel(), 'getModel()' );
272 public function provideUserCanBitfield() {
273 yield
[ 0, 0, [], null, true ];
274 // Bitfields match, user has no permissions
276 RevisionRecord
::DELETED_TEXT
,
277 RevisionRecord
::DELETED_TEXT
,
283 RevisionRecord
::DELETED_COMMENT
,
284 RevisionRecord
::DELETED_COMMENT
,
290 RevisionRecord
::DELETED_USER
,
291 RevisionRecord
::DELETED_USER
,
297 RevisionRecord
::DELETED_RESTRICTED
,
298 RevisionRecord
::DELETED_RESTRICTED
,
303 // Bitfields match, user (admin) does have permissions
305 RevisionRecord
::DELETED_TEXT
,
306 RevisionRecord
::DELETED_TEXT
,
312 RevisionRecord
::DELETED_COMMENT
,
313 RevisionRecord
::DELETED_COMMENT
,
319 RevisionRecord
::DELETED_USER
,
320 RevisionRecord
::DELETED_USER
,
325 // Bitfields match, user (admin) does not have permissions
327 RevisionRecord
::DELETED_RESTRICTED
,
328 RevisionRecord
::DELETED_RESTRICTED
,
333 // Bitfields match, user (oversight) does have permissions
335 RevisionRecord
::DELETED_RESTRICTED
,
336 RevisionRecord
::DELETED_RESTRICTED
,
341 // Check permissions using the title
343 RevisionRecord
::DELETED_TEXT
,
344 RevisionRecord
::DELETED_TEXT
,
346 Title
::newFromText( __METHOD__
),
350 RevisionRecord
::DELETED_TEXT
,
351 RevisionRecord
::DELETED_TEXT
,
353 Title
::newFromText( __METHOD__
),
359 * @dataProvider provideUserCanBitfield
360 * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
362 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
363 $this->forceStandardPermissions();
365 $user = $this->getTestUser( $userGroups )->getUser();
369 RevisionRecord
::userCanBitfield( $bitField, $field, $user, $title )
373 public function provideHasSameContent() {
375 * @param SlotRecord[] $slots
377 * @return RevisionStoreRecord
379 $recordCreator = function ( array $slots, $revId ) {
380 $title = Title
::newFromText( 'provideHasSameContent' );
381 $title->resetArticleID( 19 );
382 $slots = new RevisionSlots( $slots );
384 return new RevisionStoreRecord(
386 new UserIdentityValue( 11, __METHOD__
, 0 ),
387 CommentStoreComment
::newUnsavedComment( __METHOD__
),
389 'rev_id' => strval( $revId ),
390 'rev_page' => strval( $title->getArticleID() ),
391 'rev_timestamp' => '20200101000000',
393 'rev_minor_edit' => 0,
394 'rev_parent_id' => '5',
395 'rev_len' => $slots->computeSize(),
396 'rev_sha1' => $slots->computeSha1(),
397 'page_latest' => '18',
403 // Create some slots with content
404 $mainA = SlotRecord
::newUnsaved( 'main', new TextContent( 'A' ) );
405 $mainB = SlotRecord
::newUnsaved( 'main', new TextContent( 'B' ) );
406 $auxA = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
407 $auxB = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
409 $initialRecord = $recordCreator( [ $mainA ], 12 );
412 'same record object' => [
417 'same record content, different object' => [
419 $recordCreator( [ $mainA ], 12 ),
420 $recordCreator( [ $mainA ], 13 ),
422 'same record content, aux slot, different object' => [
424 $recordCreator( [ $auxA ], 12 ),
425 $recordCreator( [ $auxB ], 13 ),
427 'different content' => [
429 $recordCreator( [ $mainA ], 12 ),
430 $recordCreator( [ $mainB ], 13 ),
432 'different content and number of slots' => [
434 $recordCreator( [ $mainA ], 12 ),
435 $recordCreator( [ $mainA, $mainB ], 13 ),
441 * @dataProvider provideHasSameContent
442 * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
445 public function testHasSameContent(
447 RevisionRecord
$record1,
448 RevisionRecord
$record2
452 $record1->hasSameContent( $record2 )
456 public function provideIsDeleted() {
457 yield
'no deletion' => [
460 RevisionRecord
::DELETED_TEXT
=> false,
461 RevisionRecord
::DELETED_COMMENT
=> false,
462 RevisionRecord
::DELETED_USER
=> false,
463 RevisionRecord
::DELETED_RESTRICTED
=> false,
466 yield
'text deleted' => [
467 RevisionRecord
::DELETED_TEXT
,
469 RevisionRecord
::DELETED_TEXT
=> true,
470 RevisionRecord
::DELETED_COMMENT
=> false,
471 RevisionRecord
::DELETED_USER
=> false,
472 RevisionRecord
::DELETED_RESTRICTED
=> false,
475 yield
'text and comment deleted' => [
476 RevisionRecord
::DELETED_TEXT + RevisionRecord
::DELETED_COMMENT
,
478 RevisionRecord
::DELETED_TEXT
=> true,
479 RevisionRecord
::DELETED_COMMENT
=> true,
480 RevisionRecord
::DELETED_USER
=> false,
481 RevisionRecord
::DELETED_RESTRICTED
=> false,
484 yield
'all 4 deleted' => [
485 RevisionRecord
::DELETED_TEXT +
486 RevisionRecord
::DELETED_COMMENT +
487 RevisionRecord
::DELETED_RESTRICTED +
488 RevisionRecord
::DELETED_USER
,
490 RevisionRecord
::DELETED_TEXT
=> true,
491 RevisionRecord
::DELETED_COMMENT
=> true,
492 RevisionRecord
::DELETED_USER
=> true,
493 RevisionRecord
::DELETED_RESTRICTED
=> true,
499 * @dataProvider provideIsDeleted
500 * @covers \MediaWiki\Storage\RevisionRecord::isDeleted
502 public function testIsDeleted( $revDeleted, $assertionMap ) {
503 $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
504 foreach ( $assertionMap as $deletionLevel => $expected ) {
505 $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );