wiki.techinc.nl/tests/phpunit/includes/Storage/RevisionRecordTests.php
daniel 040d5b2552 Fix handling of ar_length and ar_sha1 in RevisionArchiveRecord.
This makes sure that length and hash are calculated if not known.

This patch also adds missing unit tests for RevisionArchiveRecord,
and consolidates unit tests for the different RevisionRecord
subclasses using a trait.

Bug: T192189
Change-Id: I5e1d17ba96e61e068b6aa5ac9c45ac0f657905a6
2018-04-18 21:47:57 +02:00

512 lines
12 KiB
PHP

<?php
namespace MediaWiki\Tests\Storage;
use CommentStoreComment;
use LogicException;
use MediaWiki\Storage\RevisionRecord;
use MediaWiki\Storage\RevisionSlots;
use MediaWiki\Storage\RevisionStoreRecord;
use MediaWiki\Storage\SlotRecord;
use MediaWiki\Storage\SuppressedDataException;
use MediaWiki\User\UserIdentityValue;
use TextContent;
use Title;
// PHPCS should not complain about @covers and @dataProvider being used in traits, see T192384
// phpcs:disable MediaWiki.Commenting.PhpunitAnnotations.NotTestClass
/**
* @covers \MediaWiki\Storage\RevisionRecord
*
* @note Expects to be used in classes that extend MediaWikiTestCase.
*/
trait RevisionRecordTests {
/**
* @param array $rowOverrides
*
* @return RevisionRecord
*/
protected abstract function newRevision( array $rowOverrides = [] );
private function provideAudienceCheckData( $field ) {
yield 'field accessible for oversighter (ALL)' => [
RevisionRecord::SUPPRESSED_ALL,
[ 'oversight' ],
true,
false
];
yield 'field accessible for oversighter' => [
RevisionRecord::DELETED_RESTRICTED | $field,
[ 'oversight' ],
true,
false
];
yield 'field not accessible for sysops (ALL)' => [
RevisionRecord::SUPPRESSED_ALL,
[ 'sysop' ],
false,
false
];
yield 'field not accessible for sysops' => [
RevisionRecord::DELETED_RESTRICTED | $field,
[ 'sysop' ],
false,
false
];
yield 'field accessible for sysops' => [
$field,
[ 'sysop' ],
true,
false
];
yield 'field suppressed for logged in users' => [
$field,
[ 'user' ],
false,
false
];
yield 'unrelated field suppressed' => [
$field === RevisionRecord::DELETED_COMMENT
? RevisionRecord::DELETED_USER
: RevisionRecord::DELETED_COMMENT,
[ 'user' ],
true,
true
];
yield 'nothing suppressed' => [
0,
[ 'user' ],
true,
true
];
}
public function testSerialization_fails() {
$this->setExpectedException( LogicException::class );
$rev = $this->newRevision();
serialize( $rev );
}
public function provideGetComment_audience() {
return $this->provideAudienceCheckData( RevisionRecord::DELETED_COMMENT );
}
private function forceStandardPermissions() {
$this->setMwGlobals(
'wgGroupPermissions',
[
'user' => [
'viewsuppressed' => false,
'suppressrevision' => false,
'deletedtext' => false,
'deletedhistory' => false,
],
'sysop' => [
'viewsuppressed' => false,
'suppressrevision' => false,
'deletedtext' => true,
'deletedhistory' => true,
],
'oversight' => [
'deletedtext' => true,
'deletedhistory' => true,
'viewsuppressed' => true,
'suppressrevision' => true,
],
]
);
}
/**
* @dataProvider provideGetComment_audience
*/
public function testGetComment_audience( $visibility, $groups, $userCan, $publicCan ) {
$this->forceStandardPermissions();
$user = $this->getTestUser( $groups )->getUser();
$rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
$this->assertNotNull( $rev->getComment( RevisionRecord::RAW ), 'raw can' );
$this->assertSame(
$publicCan,
$rev->getComment( RevisionRecord::FOR_PUBLIC ) !== null,
'public can'
);
$this->assertSame(
$userCan,
$rev->getComment( RevisionRecord::FOR_THIS_USER, $user ) !== null,
'user can'
);
}
public function provideGetUser_audience() {
return $this->provideAudienceCheckData( RevisionRecord::DELETED_USER );
}
/**
* @dataProvider provideGetUser_audience
*/
public function testGetUser_audience( $visibility, $groups, $userCan, $publicCan ) {
$this->forceStandardPermissions();
$user = $this->getTestUser( $groups )->getUser();
$rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
$this->assertNotNull( $rev->getUser( RevisionRecord::RAW ), 'raw can' );
$this->assertSame(
$publicCan,
$rev->getUser( RevisionRecord::FOR_PUBLIC ) !== null,
'public can'
);
$this->assertSame(
$userCan,
$rev->getUser( RevisionRecord::FOR_THIS_USER, $user ) !== null,
'user can'
);
}
public function provideGetSlot_audience() {
return $this->provideAudienceCheckData( RevisionRecord::DELETED_TEXT );
}
/**
* @dataProvider provideGetSlot_audience
*/
public function testGetSlot_audience( $visibility, $groups, $userCan, $publicCan ) {
$this->forceStandardPermissions();
$user = $this->getTestUser( $groups )->getUser();
$rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
// NOTE: slot meta-data is never suppressed, just the content is!
$this->assertTrue( $rev->hasSlot( 'main' ), 'hasSlot is never suppressed' );
$this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::RAW ), 'raw meta' );
$this->assertNotNull( $rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC ), 'public meta' );
$this->assertNotNull(
$rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user ),
'user can'
);
try {
$rev->getSlot( 'main', RevisionRecord::FOR_PUBLIC )->getContent();
$exception = null;
} catch ( SuppressedDataException $ex ) {
$exception = $ex;
}
$this->assertSame(
$publicCan,
$exception === null,
'public can'
);
try {
$rev->getSlot( 'main', RevisionRecord::FOR_THIS_USER, $user )->getContent();
$exception = null;
} catch ( SuppressedDataException $ex ) {
$exception = $ex;
}
$this->assertSame(
$userCan,
$exception === null,
'user can'
);
}
/**
* @dataProvider provideGetSlot_audience
*/
public function testGetContent_audience( $visibility, $groups, $userCan, $publicCan ) {
$this->forceStandardPermissions();
$user = $this->getTestUser( $groups )->getUser();
$rev = $this->newRevision( [ 'rev_deleted' => $visibility ] );
$this->assertNotNull( $rev->getContent( 'main', RevisionRecord::RAW ), 'raw can' );
$this->assertSame(
$publicCan,
$rev->getContent( 'main', RevisionRecord::FOR_PUBLIC ) !== null,
'public can'
);
$this->assertSame(
$userCan,
$rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $user ) !== null,
'user can'
);
}
public function testGetSlot() {
$rev = $this->newRevision();
$slot = $rev->getSlot( 'main' );
$this->assertNotNull( $slot, 'getSlot()' );
$this->assertSame( 'main', $slot->getRole(), 'getRole()' );
}
public function testHasSlot() {
$rev = $this->newRevision();
$this->assertTrue( $rev->hasSlot( 'main' ) );
$this->assertFalse( $rev->hasSlot( 'xyz' ) );
}
public function testGetContent() {
$rev = $this->newRevision();
$content = $rev->getSlot( 'main' );
$this->assertNotNull( $content, 'getContent()' );
$this->assertSame( CONTENT_MODEL_TEXT, $content->getModel(), 'getModel()' );
}
public function provideUserCanBitfield() {
yield [ 0, 0, [], null, true ];
// Bitfields match, user has no permissions
yield [
RevisionRecord::DELETED_TEXT,
RevisionRecord::DELETED_TEXT,
[],
null,
false
];
yield [
RevisionRecord::DELETED_COMMENT,
RevisionRecord::DELETED_COMMENT,
[],
null,
false,
];
yield [
RevisionRecord::DELETED_USER,
RevisionRecord::DELETED_USER,
[],
null,
false
];
yield [
RevisionRecord::DELETED_RESTRICTED,
RevisionRecord::DELETED_RESTRICTED,
[],
null,
false,
];
// Bitfields match, user (admin) does have permissions
yield [
RevisionRecord::DELETED_TEXT,
RevisionRecord::DELETED_TEXT,
[ 'sysop' ],
null,
true,
];
yield [
RevisionRecord::DELETED_COMMENT,
RevisionRecord::DELETED_COMMENT,
[ 'sysop' ],
null,
true,
];
yield [
RevisionRecord::DELETED_USER,
RevisionRecord::DELETED_USER,
[ 'sysop' ],
null,
true,
];
// Bitfields match, user (admin) does not have permissions
yield [
RevisionRecord::DELETED_RESTRICTED,
RevisionRecord::DELETED_RESTRICTED,
[ 'sysop' ],
null,
false,
];
// Bitfields match, user (oversight) does have permissions
yield [
RevisionRecord::DELETED_RESTRICTED,
RevisionRecord::DELETED_RESTRICTED,
[ 'oversight' ],
null,
true,
];
// Check permissions using the title
yield [
RevisionRecord::DELETED_TEXT,
RevisionRecord::DELETED_TEXT,
[ 'sysop' ],
Title::newFromText( __METHOD__ ),
true,
];
yield [
RevisionRecord::DELETED_TEXT,
RevisionRecord::DELETED_TEXT,
[],
Title::newFromText( __METHOD__ ),
false,
];
}
/**
* @dataProvider provideUserCanBitfield
* @covers \MediaWiki\Storage\RevisionRecord::userCanBitfield
*/
public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
$this->forceStandardPermissions();
$user = $this->getTestUser( $userGroups )->getUser();
$this->assertSame(
$expected,
RevisionRecord::userCanBitfield( $bitField, $field, $user, $title )
);
}
public function provideHasSameContent() {
/**
* @param SlotRecord[] $slots
* @param int $revId
* @return RevisionStoreRecord
*/
$recordCreator = function ( array $slots, $revId ) {
$title = Title::newFromText( 'provideHasSameContent' );
$title->resetArticleID( 19 );
$slots = new RevisionSlots( $slots );
return new RevisionStoreRecord(
$title,
new UserIdentityValue( 11, __METHOD__, 0 ),
CommentStoreComment::newUnsavedComment( __METHOD__ ),
(object)[
'rev_id' => strval( $revId ),
'rev_page' => strval( $title->getArticleID() ),
'rev_timestamp' => '20200101000000',
'rev_deleted' => 0,
'rev_minor_edit' => 0,
'rev_parent_id' => '5',
'rev_len' => $slots->computeSize(),
'rev_sha1' => $slots->computeSha1(),
'page_latest' => '18',
],
$slots
);
};
// Create some slots with content
$mainA = SlotRecord::newUnsaved( 'main', new TextContent( 'A' ) );
$mainB = SlotRecord::newUnsaved( 'main', new TextContent( 'B' ) );
$auxA = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
$auxB = SlotRecord::newUnsaved( 'aux', new TextContent( 'A' ) );
$initialRecord = $recordCreator( [ $mainA ], 12 );
return [
'same record object' => [
true,
$initialRecord,
$initialRecord,
],
'same record content, different object' => [
true,
$recordCreator( [ $mainA ], 12 ),
$recordCreator( [ $mainA ], 13 ),
],
'same record content, aux slot, different object' => [
true,
$recordCreator( [ $auxA ], 12 ),
$recordCreator( [ $auxB ], 13 ),
],
'different content' => [
false,
$recordCreator( [ $mainA ], 12 ),
$recordCreator( [ $mainB ], 13 ),
],
'different content and number of slots' => [
false,
$recordCreator( [ $mainA ], 12 ),
$recordCreator( [ $mainA, $mainB ], 13 ),
],
];
}
/**
* @dataProvider provideHasSameContent
* @covers \MediaWiki\Storage\RevisionRecord::hasSameContent
* @group Database
*/
public function testHasSameContent(
$expected,
RevisionRecord $record1,
RevisionRecord $record2
) {
$this->assertSame(
$expected,
$record1->hasSameContent( $record2 )
);
}
public function provideIsDeleted() {
yield 'no deletion' => [
0,
[
RevisionRecord::DELETED_TEXT => false,
RevisionRecord::DELETED_COMMENT => false,
RevisionRecord::DELETED_USER => false,
RevisionRecord::DELETED_RESTRICTED => false,
]
];
yield 'text deleted' => [
RevisionRecord::DELETED_TEXT,
[
RevisionRecord::DELETED_TEXT => true,
RevisionRecord::DELETED_COMMENT => false,
RevisionRecord::DELETED_USER => false,
RevisionRecord::DELETED_RESTRICTED => false,
]
];
yield 'text and comment deleted' => [
RevisionRecord::DELETED_TEXT + RevisionRecord::DELETED_COMMENT,
[
RevisionRecord::DELETED_TEXT => true,
RevisionRecord::DELETED_COMMENT => true,
RevisionRecord::DELETED_USER => false,
RevisionRecord::DELETED_RESTRICTED => false,
]
];
yield 'all 4 deleted' => [
RevisionRecord::DELETED_TEXT +
RevisionRecord::DELETED_COMMENT +
RevisionRecord::DELETED_RESTRICTED +
RevisionRecord::DELETED_USER,
[
RevisionRecord::DELETED_TEXT => true,
RevisionRecord::DELETED_COMMENT => true,
RevisionRecord::DELETED_USER => true,
RevisionRecord::DELETED_RESTRICTED => true,
]
];
}
/**
* @dataProvider provideIsDeleted
* @covers \MediaWiki\Storage\RevisionRecord::isDeleted
*/
public function testIsDeleted( $revDeleted, $assertionMap ) {
$rev = $this->newRevision( [ 'rev_deleted' => $revDeleted ] );
foreach ( $assertionMap as $deletionLevel => $expected ) {
$this->assertSame( $expected, $rev->isDeleted( $deletionLevel ) );
}
}
}