3 // phpcs:disable MediaWiki.Commenting.PhpunitAnnotations.NotClassTrait
5 namespace MediaWiki\Tests\Storage
;
7 use CommentStoreComment
;
9 use MediaWiki\Storage\RevisionRecord
;
10 use MediaWiki\Storage\RevisionSlots
;
11 use MediaWiki\Storage\RevisionStoreRecord
;
12 use MediaWiki\Storage\SlotRecord
;
13 use MediaWiki\Storage\SuppressedDataException
;
14 use MediaWiki\User\UserIdentityValue
;
19 * @covers \MediaWiki\Storage\RevisionRecord
21 * @note Expects to be used in classes that extend MediaWikiTestCase.
23 trait RevisionRecordTests
{
26 * @param array $rowOverrides
28 * @return RevisionRecord
30 protected abstract 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( 'main' ), 'hasSlot is never suppressed' );
193 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::RAW
), 'raw meta' );
194 $this->assertNotNull( $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
), 'public meta' );
196 $this->assertNotNull(
197 $rev->getSlot( 'main', RevisionRecord
::FOR_THIS_USER
, $user ),
202 $rev->getSlot( 'main', RevisionRecord
::FOR_PUBLIC
)->getContent();
204 } catch ( SuppressedDataException
$ex ) {
215 $rev->getSlot( 'main', RevisionRecord
::FOR_THIS_USER
, $user )->getContent();
217 } catch ( SuppressedDataException
$ex ) {
229 * @dataProvider provideGetSlot_audience
231 public function testGetContent_audience( $visibility, $groups, $userCan, $publicCan ) {
232 $this->forceStandardPermissions();
234 $user = $this->getTestUser( $groups )->getUser();
235 $rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
237 $this->assertNotNull( $rev->getContent( 'main', RevisionRecord
::RAW
), 'raw can' );
241 $rev->getContent( 'main', RevisionRecord
::FOR_PUBLIC
) !== null,
246 $rev->getContent( 'main', RevisionRecord
::FOR_THIS_USER
, $user ) !== null,
251 public function testGetSlot() {
252 $rev = $this->newRevision();
254 $slot = $rev->getSlot( 'main' );
255 $this->assertNotNull( $slot, 'getSlot()' );
256 $this->assertSame( 'main', $slot->getRole(), 'getRole()' );
259 public function testHasSlot() {
260 $rev = $this->newRevision();
262 $this->assertTrue( $rev->hasSlot( 'main' ) );
263 $this->assertFalse( $rev->hasSlot( 'xyz' ) );
266 public function testGetContent() {
267 $rev = $this->newRevision();
269 $content = $rev->getSlot( 'main' );
270 $this->assertNotNull( $content, 'getContent()' );
271 $this->assertSame( CONTENT_MODEL_TEXT
, $content->getModel(), 'getModel()' );
274 public function provideUserCanBitfield() {
275 yield
[ 0, 0, [], null, true ];
276 // Bitfields match, user has no permissions
278 RevisionRecord
::DELETED_TEXT
,
279 RevisionRecord
::DELETED_TEXT
,
285 RevisionRecord
::DELETED_COMMENT
,
286 RevisionRecord
::DELETED_COMMENT
,
292 RevisionRecord
::DELETED_USER
,
293 RevisionRecord
::DELETED_USER
,
299 RevisionRecord
::DELETED_RESTRICTED
,
300 RevisionRecord
::DELETED_RESTRICTED
,
305 // Bitfields match, user (admin) does have permissions
307 RevisionRecord
::DELETED_TEXT
,
308 RevisionRecord
::DELETED_TEXT
,
314 RevisionRecord
::DELETED_COMMENT
,
315 RevisionRecord
::DELETED_COMMENT
,
321 RevisionRecord
::DELETED_USER
,
322 RevisionRecord
::DELETED_USER
,
327 // Bitfields match, user (admin) does not have permissions
329 RevisionRecord
::DELETED_RESTRICTED
,
330 RevisionRecord
::DELETED_RESTRICTED
,
335 // Bitfields match, user (oversight) does have permissions
337 RevisionRecord
::DELETED_RESTRICTED
,
338 RevisionRecord
::DELETED_RESTRICTED
,
343 // Check permissions using the title
345 RevisionRecord
::DELETED_TEXT
,
346 RevisionRecord
::DELETED_TEXT
,
352 RevisionRecord
::DELETED_TEXT
,
353 RevisionRecord
::DELETED_TEXT
,
361 * @dataProvider provideUserCanBitfield
362 * @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
364 public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
365 if ( is_string( $title ) ) {
366 // NOTE: Data providers cannot instantiate Title objects! See T202641.
367 $title = Title
::newFromText( $title );
370 $this->forceStandardPermissions();
372 $user = $this->getTestUser( $userGroups )->getUser();
376 RevisionRecord
::userCanBitfield( $bitField, $field, $user, $title )
380 public function provideHasSameContent() {
381 // Create some slots with content
382 $mainA = SlotRecord
::newUnsaved( 'main', new TextContent( 'A' ) );
383 $mainB = SlotRecord
::newUnsaved( 'main', new TextContent( 'B' ) );
384 $auxA = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
385 $auxB = SlotRecord
::newUnsaved( 'aux', new TextContent( 'A' ) );
387 $initialRecordSpec = [ [ $mainA ], 12 ];
390 'same record object' => [
395 'same record content, different object' => [
400 'same record content, aux slot, different object' => [
405 'different content' => [
410 'different content and number of slots' => [
413 [ [ $mainA, $mainB ], 13 ],
419 * @note Do not call directly from a data provider! Data providers cannot instantiate
420 * Title objects! See T202641.
422 * @param SlotRecord[] $slots
424 * @return RevisionStoreRecord
426 private function makeHasSameContentTestRecord( array $slots, $revId ) {
427 $title = Title
::newFromText( 'provideHasSameContent' );
428 $title->resetArticleID( 19 );
429 $slots = new RevisionSlots( $slots );
431 return new RevisionStoreRecord(
433 new UserIdentityValue( 11, __METHOD__
, 0 ),
434 CommentStoreComment
::newUnsavedComment( __METHOD__
),
436 'rev_id' => strval( $revId ),
437 'rev_page' => strval( $title->getArticleID() ),
438 'rev_timestamp' => '20200101000000',
440 'rev_minor_edit' => 0,
441 'rev_parent_id' => '5',
442 'rev_len' => $slots->computeSize(),
443 'rev_sha1' => $slots->computeSha1(),
444 'page_latest' => '18',
451 * @dataProvider provideHasSameContent
452 * @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
455 public function testHasSameContent(
460 $record1 = $this->makeHasSameContentTestRecord( ...$recordSpec1 );
461 $record2 = $this->makeHasSameContentTestRecord( ...$recordSpec2 );
465 $record1->hasSameContent( $record2 )
469 public function provideIsDeleted() {
470 yield
'no deletion' => [
473 RevisionRecord
::DELETED_TEXT
=> false,
474 RevisionRecord
::DELETED_COMMENT
=> false,
475 RevisionRecord
::DELETED_USER
=> false,
476 RevisionRecord
::DELETED_RESTRICTED
=> false,
479 yield
'text deleted' => [
480 RevisionRecord
::DELETED_TEXT
,
482 RevisionRecord
::DELETED_TEXT
=> true,
483 RevisionRecord
::DELETED_COMMENT
=> false,
484 RevisionRecord
::DELETED_USER
=> false,
485 RevisionRecord
::DELETED_RESTRICTED
=> false,
488 yield
'text and comment deleted' => [
489 RevisionRecord
::DELETED_TEXT + RevisionRecord
::DELETED_COMMENT
,
491 RevisionRecord
::DELETED_TEXT
=> true,
492 RevisionRecord
::DELETED_COMMENT
=> true,
493 RevisionRecord
::DELETED_USER
=> false,
494 RevisionRecord
::DELETED_RESTRICTED
=> false,
497 yield
'all 4 deleted' => [
498 RevisionRecord
::DELETED_TEXT +
499 RevisionRecord
::DELETED_COMMENT +
500 RevisionRecord
::DELETED_RESTRICTED +
501 RevisionRecord
::DELETED_USER
,
503 RevisionRecord
::DELETED_TEXT
=> true,
504 RevisionRecord
::DELETED_COMMENT
=> true,
505 RevisionRecord
::DELETED_USER
=> true,
506 RevisionRecord
::DELETED_RESTRICTED
=> true,
512 * @dataProvider provideIsDeleted
513 * @covers \MediaWiki\Storage\RevisionRecord::isDeleted
515 public function testIsDeleted( $revDeleted, $assertionMap ) {
516 $rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
517 foreach ( $assertionMap as $deletionLevel => $expected ) {
518 $this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );
522 public function testIsReadyForInsertion() {
523 $rev = $this->newRevision();
524 $this->assertTrue( $rev->isReadyForInsertion() );