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
,
350 RevisionRecord
::DELETED_TEXT
,
351 RevisionRecord
::DELETED_TEXT
,
359 * @dataProvider provideUserCanBitfield
360 * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
362 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
363 if ( is_string( $title ) ) {
364 // NOTE: Data providers cannot instantiate Title objects! See T202641.
365 $title = Title
::newFromText( $title );
368 $this->forceStandardPermissions();
370 $user = $this->getTestUser( $userGroups )->getUser();
374 RevisionRecord
::userCanBitfield( $bitField, $field, $user, $title )
378 public function provideHasSameContent() {
379 // Create some slots with content
380 $mainA = SlotRecord
::newUnsaved( 'main', new TextContent( 'A' ) );
381 $mainB = SlotRecord
::newUnsaved( 'main', new TextContent( 'B' ) );
382 $auxA = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
383 $auxB = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
385 $initialRecordSpec = [ [ $mainA ], 12 ];
388 'same record object' => [
393 'same record content, different object' => [
398 'same record content, aux slot, different object' => [
403 'different content' => [
408 'different content and number of slots' => [
411 [ [ $mainA, $mainB ], 13 ],
417 * @note Do not call directly from a data provider! Data providers cannot instantiate
418 * Title objects! See T202641.
420 * @param SlotRecord[] $slots
422 * @return RevisionStoreRecord
424 private function makeHasSameContentTestRecord( array $slots, $revId ) {
425 $title = Title
::newFromText( 'provideHasSameContent' );
426 $title->resetArticleID( 19 );
427 $slots = new RevisionSlots( $slots );
429 return new RevisionStoreRecord(
431 new UserIdentityValue( 11, __METHOD__
, 0 ),
432 CommentStoreComment
::newUnsavedComment( __METHOD__
),
434 'rev_id' => strval( $revId ),
435 'rev_page' => strval( $title->getArticleID() ),
436 'rev_timestamp' => '20200101000000',
438 'rev_minor_edit' => 0,
439 'rev_parent_id' => '5',
440 'rev_len' => $slots->computeSize(),
441 'rev_sha1' => $slots->computeSha1(),
442 'page_latest' => '18',
449 * @dataProvider provideHasSameContent
450 * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
453 public function testHasSameContent(
458 $record1 = $this->makeHasSameContentTestRecord( ...$recordSpec1 );
459 $record2 = $this->makeHasSameContentTestRecord( ...$recordSpec2 );
463 $record1->hasSameContent( $record2 )
467 public function provideIsDeleted() {
468 yield
'no deletion' => [
471 RevisionRecord
::DELETED_TEXT
=> false,
472 RevisionRecord
::DELETED_COMMENT
=> false,
473 RevisionRecord
::DELETED_USER
=> false,
474 RevisionRecord
::DELETED_RESTRICTED
=> false,
477 yield
'text deleted' => [
478 RevisionRecord
::DELETED_TEXT
,
480 RevisionRecord
::DELETED_TEXT
=> true,
481 RevisionRecord
::DELETED_COMMENT
=> false,
482 RevisionRecord
::DELETED_USER
=> false,
483 RevisionRecord
::DELETED_RESTRICTED
=> false,
486 yield
'text and comment deleted' => [
487 RevisionRecord
::DELETED_TEXT + RevisionRecord
::DELETED_COMMENT
,
489 RevisionRecord
::DELETED_TEXT
=> true,
490 RevisionRecord
::DELETED_COMMENT
=> true,
491 RevisionRecord
::DELETED_USER
=> false,
492 RevisionRecord
::DELETED_RESTRICTED
=> false,
495 yield
'all 4 deleted' => [
496 RevisionRecord
::DELETED_TEXT +
497 RevisionRecord
::DELETED_COMMENT +
498 RevisionRecord
::DELETED_RESTRICTED +
499 RevisionRecord
::DELETED_USER
,
501 RevisionRecord
::DELETED_TEXT
=> true,
502 RevisionRecord
::DELETED_COMMENT
=> true,
503 RevisionRecord
::DELETED_USER
=> true,
504 RevisionRecord
::DELETED_RESTRICTED
=> true,
510 * @dataProvider provideIsDeleted
511 * @covers \MediaWiki\Storage\RevisionRecord::isDeleted
513 public function testIsDeleted( $revDeleted, $assertionMap ) {
514 $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
515 foreach ( $assertionMap as $deletionLevel => $expected ) {
516 $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );
520 public function testIsReadyForInsertion() {
521 $rev = $this->newRevision();
522 $this->assertTrue( $rev->isReadyForInsertion() );