use MediaWiki\Storage\RevisionStore;
use MediaWiki\Storage\SlotRecord;
use MediaWiki\Storage\SqlBlobStore;
+use MediaWiki\User\UserIdentityValue;
use MediaWikiTestCase;
use PHPUnit_Framework_MockObject_MockObject;
use Revision;
*/
abstract protected function getMcrTablesToReset();
- public function needsDB() {
- return true;
- }
-
public function setUp() {
parent::setUp();
$this->tablesUsed[] = 'archive';
$this->assertEquals( $r1->getSha1(), $r2->getSha1() );
$this->assertEquals( $r1->getSize(), $r2->getSize() );
$this->assertEquals( $r1->getPageId(), $r2->getPageId() );
- $this->assertArrayEqualsIgnoringIntKeyOrder( $r1->getSlotRoles(), $r2->getSlotRoles() );
+ $this->assertArrayEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
$this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
$this->assertEquals( $r1->isMinor(), $r2->isMinor() );
foreach ( $r1->getSlotRoles() as $role ) {
}
protected function assertRevisionCompleteness( RevisionRecord $r ) {
- $this->assertTrue( $r->hasSlot( 'main' ) );
- $this->assertInstanceOf( SlotRecord::class, $r->getSlot( 'main' ) );
- $this->assertInstanceOf( Content::class, $r->getContent( 'main' ) );
+ $this->assertTrue( $r->hasSlot( SlotRecord::MAIN ) );
+ $this->assertInstanceOf( SlotRecord::class, $r->getSlot( SlotRecord::MAIN ) );
+ $this->assertInstanceOf( Content::class, $r->getContent( SlotRecord::MAIN ) );
foreach ( $r->getSlotRoles() as $role ) {
$this->assertSlotCompleteness( $r, $r->getSlot( $role ) );
public function provideInsertRevisionOn_successes() {
yield 'Bare minimum revision insertion' => [
[
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'page' => true,
'comment' => $this->getRandomCommentStoreComment(),
'timestamp' => '20171117010101',
];
yield 'Detailed revision insertion' => [
[
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'parent' => true,
'page' => true,
'comment' => $this->getRandomCommentStoreComment(),
public function testInsertRevisionOn_successes(
array $revDetails = []
) {
- // FIXME: fails under postgres
- $this->markTestSkippedIfDbType( 'postgres' );
-
$title = $this->getTestPageTitle();
$rev = $this->getRevisionRecordFromDetailsArray( $revDetails );
public function testInsertRevisionOn_blobAddressExists() {
$title = $this->getTestPageTitle();
$revDetails = [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'parent' => true,
'comment' => $this->getRandomCommentStoreComment(),
'timestamp' => '20171117010101',
$this->assertRevisionRecordsEqual( $revOne, $firstReturn );
// Insert a second revision inheriting the same blob address
- $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( 'main' ) );
+ $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( SlotRecord::MAIN ) );
$revTwo = $this->getRevisionRecordFromDetailsArray( $revDetails );
$secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_MASTER ) );
$this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() );
$this->assertRevisionRecordsEqual( $revTwo, $secondReturn );
- $firstMainSlot = $firstReturn->getSlot( 'main' );
- $secondMainSlot = $secondReturn->getSlot( 'main' );
+ $firstMainSlot = $firstReturn->getSlot( SlotRecord::MAIN );
+ $secondMainSlot = $secondReturn->getSlot( SlotRecord::MAIN );
$this->assertSameSlotContent( $firstMainSlot, $secondMainSlot );
];
yield 'no timestamp' => [
[
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'comment' => $this->getRandomCommentStoreComment(),
'user' => true,
],
];
yield 'no comment' => [
[
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'timestamp' => '20171117010101',
'user' => true,
],
];
yield 'no user' => [
[
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
'comment' => $this->getRandomCommentStoreComment(),
'timestamp' => '20171117010101',
],
$this->assertEquals( $user->getName(), $record->getUser()->getName() );
$this->assertEquals( $baseRev->getId(), $record->getParentId() );
- $this->assertArrayEqualsIgnoringIntKeyOrder(
+ $this->assertArrayEquals(
$baseRev->getSlotRoles(),
$record->getSlotRoles()
);
$revRecord = $store->getRevisionById( $rev->getId() );
$this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertTrue( $revRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $revRecord->getComment()->text );
}
$revRecord = $store->getRevisionByTitle( $page->getTitle() );
$this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertTrue( $revRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $revRecord->getComment()->text );
}
$revRecord = $store->getRevisionByPageId( $page->getId() );
$this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertTrue( $revRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $revRecord->getComment()->text );
}
);
$this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertTrue( $revRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $revRecord->getComment()->text );
}
return (object)$fields;
}
- private function assertRevisionRecordMatchesRevision(
+ protected function assertRevisionRecordMatchesRevision(
Revision $rev,
RevisionRecord $record
) {
$this->assertSame( $expectedParent, $record->getParentId() );
$this->assertSame( $rev->getSha1(), $record->getSha1() );
$this->assertSame( $rev->getComment(), $record->getComment()->text );
- $this->assertSame( $rev->getContentFormat(), $record->getContent( 'main' )->getDefaultFormat() );
- $this->assertSame( $rev->getContentModel(), $record->getContent( 'main' )->getModel() );
+ $this->assertSame( $rev->getContentFormat(),
+ $record->getContent( SlotRecord::MAIN )->getDefaultFormat() );
+ $this->assertSame( $rev->getContentModel(), $record->getContent( SlotRecord::MAIN )->getModel() );
$this->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
$revRec = $rev->getRevisionRecord();
- $revMain = $revRec->getSlot( 'main' );
- $recMain = $record->getSlot( 'main' );
+ $revMain = $revRec->getSlot( SlotRecord::MAIN );
+ $recMain = $record->getSlot( SlotRecord::MAIN );
$this->assertSame( $revMain->hasOrigin(), $recMain->hasOrigin(), 'hasOrigin' );
$this->assertSame( $revMain->hasAddress(), $recMain->hasAddress(), 'hasAddress' );
}
}
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::getQueryInfo
+ */
+ public function testNewRevisionFromRow_getQueryInfo() {
+ $page = $this->getTestPage();
+ $text = __METHOD__ . 'a-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__ . 'a'
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $info = $store->getQueryInfo();
+ $row = $this->db->selectRow(
+ $info['tables'],
+ $info['fields'],
+ [ 'rev_id' => $rev->getId() ],
+ __METHOD__,
+ [],
+ $info['joins']
+ );
+ $record = $store->newRevisionFromRow(
+ $row,
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
/**
* @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
*/
/** @var Revision $rev */
$rev = $page->doEditContent(
new WikitextContent( $text ),
- __METHOD__. 'a'
+ __METHOD__ . 'a'
)->value['revision'];
$store = MediaWikiServices::getInstance()->getRevisionStore();
/**
* @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
*/
- public function testNewRevisionFromArchiveRow() {
+ public function testNewRevisionFromArchiveRow_getArchiveQueryInfo() {
$store = MediaWikiServices::getInstance()->getRevisionStore();
$title = Title::newFromText( __METHOD__ );
$text = __METHOD__ . '-bä';
$record = $store->newRevisionFromArchiveRow( $row );
$this->assertRevisionRecordMatchesRevision( $orig, $record );
- $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ $this->assertSame( $text, $record->getContent( SlotRecord::MAIN )->serialize() );
}
/**
$record = $store->newRevisionFromArchiveRow( $row );
$this->assertRevisionRecordMatchesRevision( $orig, $record );
- $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ $this->assertSame( $text, $record->getContent( SlotRecord::MAIN )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ */
+ public function testNewRevisionFromArchiveRow_no_user() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $row = (object)[
+ 'ar_id' => '1',
+ 'ar_page_id' => '2',
+ 'ar_namespace' => '0',
+ 'ar_title' => 'Something',
+ 'ar_rev_id' => '2',
+ 'ar_text_id' => '47',
+ 'ar_timestamp' => '20180528192356',
+ 'ar_minor_edit' => '0',
+ 'ar_deleted' => '0',
+ 'ar_len' => '78',
+ 'ar_parent_id' => '0',
+ 'ar_sha1' => 'deadbeef',
+ 'ar_comment_text' => 'whatever',
+ 'ar_comment_data' => null,
+ 'ar_comment_cid' => null,
+ 'ar_user' => '0',
+ 'ar_user_text' => '', // this is the important bit
+ 'ar_actor' => null,
+ 'ar_content_format' => null,
+ 'ar_content_model' => null,
+ ];
+
+ \Wikimedia\suppressWarnings();
+ $record = $store->newRevisionFromArchiveRow( $row );
+ \Wikimedia\suppressWarnings( true );
+
+ $this->assertInstanceOf( RevisionRecord::class, $record );
+ $this->assertInstanceOf( UserIdentityValue::class, $record->getUser() );
+ $this->assertSame( 'Unknown user', $record->getUser()->getName() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ */
+ public function testNewRevisionFromRow_no_user() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $title = Title::newFromText( __METHOD__ );
+
+ $row = (object)[
+ 'rev_id' => '2',
+ 'rev_page' => '2',
+ 'page_namespace' => '0',
+ 'page_title' => $title->getText(),
+ 'rev_text_id' => '47',
+ 'rev_timestamp' => '20180528192356',
+ 'rev_minor_edit' => '0',
+ 'rev_deleted' => '0',
+ 'rev_len' => '78',
+ 'rev_parent_id' => '0',
+ 'rev_sha1' => 'deadbeef',
+ 'rev_comment_text' => 'whatever',
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ 'rev_user' => '0',
+ 'rev_user_text' => '', // this is the important bit
+ 'rev_actor' => null,
+ 'rev_content_format' => null,
+ 'rev_content_model' => null,
+ ];
+
+ \Wikimedia\suppressWarnings();
+ $record = $store->newRevisionFromRow( $row, 0, $title );
+ \Wikimedia\suppressWarnings( true );
+
+ $this->assertNotNull( $record );
+ $this->assertNotNull( $record->getUser() );
+ $this->assertNotEmpty( $record->getUser()->getName() );
}
/**
* @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
*/
public function testInsertRevisionOn_archive() {
+ // This is a round trip test for deletion and undeletion of a
+ // revision row via the archive table.
+
$store = MediaWikiServices::getInstance()->getRevisionStore();
$title = Title::newFromText( __METHOD__ );
$orig = $origRev->getRevisionRecord();
$page->doDeleteArticle( __METHOD__ );
+ // re-create page, so we can later load revisions for it
+ $page->doEditContent( new WikitextContent( 'Two' ), __METHOD__ );
+
$db = wfGetDB( DB_MASTER );
$arQuery = $store->getArchiveQueryInfo();
$row = $db->selectRow(
__METHOD__, [], $arQuery['joins']
);
- $record = $store->newRevisionFromArchiveRow( $row );
+ $this->assertNotFalse( $row, 'query failed' );
+
+ $record = $store->newRevisionFromArchiveRow(
+ $row,
+ 0,
+ $title,
+ [ 'page_id' => $title->getArticleID() ]
+ );
$restored = $store->insertRevisionOn( $record, $db );
- $this->assertSame( $orig->getPageId(), $restored->getPageId() );
- $this->assertSame( $orig->getId(), $restored->getId() );
- $this->assertSame( $orig->getComment()->text, $restored->getComment()->text );
- $origMain = $orig->getSlot( 'main' );
- $restoredMain = $restored->getSlot( 'main' );
- $this->assertSame(
- $origMain->getOrigin(),
- $restoredMain->getOrigin()
- );
+ // is the new revision correct?
+ $this->assertRevisionCompleteness( $restored );
+ $this->assertRevisionRecordsEqual( $record, $restored );
- if ( $origMain->hasContentId() ) {
- $this->assertSame(
- $origMain->getContentId(),
- $restoredMain->getContentId()
- );
- }
+ // does the new revision use the original slot?
+ $recMain = $record->getSlot( SlotRecord::MAIN );
+ $restMain = $restored->getSlot( SlotRecord::MAIN );
+ $this->assertSame( $recMain->getAddress(), $restMain->getAddress() );
+ $this->assertSame( $recMain->getContentId(), $restMain->getContentId() );
+ $this->assertSame( $recMain->getOrigin(), $restMain->getOrigin() );
+ $this->assertSame( 'Foo', $restMain->getContent()->serialize() );
- // NOTE: we didn't restore the page row, so we can't use RevisionStore::getRevisionById
- $this->assertSelect(
- 'revision',
- [ 'rev_id' ],
- [ 'rev_id' => $orig->getId() ],
- [ [ $orig->getId() ] ]
- );
+ // can we load it from the store?
+ $loaded = $store->getRevisionById( $restored->getId() );
+ $this->assertNotNull( $loaded );
+ $this->assertRevisionCompleteness( $loaded );
+ $this->assertRevisionRecordsEqual( $restored, $loaded );
+
+ // can we find it directly in the database?
+ $this->assertRevisionExistsInDatabase( $restored );
}
/**
);
}
} elseif ( isset( $array['text'] ) ) {
- $this->assertSame( $array['text'], $result->getSlot( 'main' )->getContent()->serialize() );
+ $this->assertSame( $array['text'],
+ $result->getSlot( SlotRecord::MAIN )->getContent()->serialize() );
} elseif ( isset( $array['content_format'] ) ) {
$this->assertSame(
$array['content_format'],
- $result->getSlot( 'main' )->getContent()->getDefaultFormat()
+ $result->getSlot( SlotRecord::MAIN )->getContent()->getDefaultFormat()
);
- $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
+ $this->assertSame( $array['content_model'], $result->getSlot( SlotRecord::MAIN )->getModel() );
}
}
$this->testNewMutableRevisionFromArray( $array );
}
- protected function getDefaultQueryFields( $returnTextIdField = true ) {
- $fields = [
- 'rev_id',
- 'rev_page',
- 'rev_timestamp',
- 'rev_minor_edit',
- 'rev_deleted',
- 'rev_len',
- 'rev_parent_id',
- 'rev_sha1',
- ];
- if ( $returnTextIdField ) {
- $fields[] = 'rev_text_id';
- }
- return $fields;
- }
-
- protected function getCommentQueryFields() {
- return [
- 'rev_comment_text' => 'rev_comment',
- 'rev_comment_data' => 'NULL',
- 'rev_comment_cid' => 'NULL',
- ];
- }
-
- protected function getActorQueryFields() {
- return [
- 'rev_user' => 'rev_user',
- 'rev_user_text' => 'rev_user_text',
- 'rev_actor' => 'NULL',
- ];
- }
-
- protected function getContentHandlerQueryFields() {
- return [
- 'rev_content_format',
- 'rev_content_model',
- ];
- }
-
- abstract public function provideGetQueryInfo();
-
- /**
- * @dataProvider provideGetQueryInfo
- * @covers \MediaWiki\Storage\RevisionStore::getQueryInfo
- */
- public function testGetQueryInfo( $options, $expected ) {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- $queryInfo = $store->getQueryInfo( $options );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['tables'],
- $queryInfo['tables']
- );
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['fields'],
- $queryInfo['fields']
- );
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['joins'],
- $queryInfo['joins']
- );
- }
-
- protected function getDefaultArchiveFields( $returnTextFields = true ) {
- $fields = [
- 'ar_id',
- 'ar_page_id',
- 'ar_namespace',
- 'ar_title',
- 'ar_rev_id',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_deleted',
- 'ar_len',
- 'ar_parent_id',
- 'ar_sha1',
- ];
- if ( $returnTextFields ) {
- $fields[] = 'ar_text_id';
- }
- return $fields;
- }
-
- abstract public function provideGetArchiveQueryInfo();
-
- /**
- * @dataProvider provideGetArchiveQueryInfo
- * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
- */
- public function testGetArchiveQueryInfo( $expected ) {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- $archiveQueryInfo = $store->getArchiveQueryInfo();
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['tables'],
- $archiveQueryInfo['tables']
- );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['fields'],
- $archiveQueryInfo['fields']
- );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['joins'],
- $archiveQueryInfo['joins']
- );
- }
-
- abstract public function provideGetSlotsQueryInfo();
-
- /**
- * @dataProvider provideGetSlotsQueryInfo
- * @covers \MediaWiki\Storage\RevisionStore::getSlotsQueryInfo
- */
- public function testGetSlotsQueryInfo( $options, $expected ) {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- $archiveQueryInfo = $store->getSlotsQueryInfo( $options );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['tables'],
- $archiveQueryInfo['tables']
- );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['fields'],
- $archiveQueryInfo['fields']
- );
-
- $this->assertArrayEqualsIgnoringIntKeyOrder(
- $expected['joins'],
- $archiveQueryInfo['joins']
- );
- }
-
- /**
- * Assert that the two arrays passed are equal, ignoring the order of the values that integer
- * keys.
- *
- * Note: Failures of this assertion can be slightly confusing as the arrays are actually
- * split into a string key array and an int key array before assertions occur.
- *
- * @param array $expected
- * @param array $actual
- */
- private function assertArrayEqualsIgnoringIntKeyOrder( array $expected, array $actual ) {
- $this->objectAssociativeSort( $expected );
- $this->objectAssociativeSort( $actual );
-
- // Separate the int key values from the string key values so that assertion failures are
- // easier to understand.
- $expectedIntKeyValues = [];
- $actualIntKeyValues = [];
-
- // Remove all int keys and re add them at the end after sorting by value
- // This will result in all int keys being in the same order with same ints at the end of
- // the array
- foreach ( $expected as $key => $value ) {
- if ( is_int( $key ) ) {
- unset( $expected[$key] );
- $expectedIntKeyValues[] = $value;
- }
- }
- foreach ( $actual as $key => $value ) {
- if ( is_int( $key ) ) {
- unset( $actual[$key] );
- $actualIntKeyValues[] = $value;
- }
- }
-
- $this->assertArrayEquals( $expected, $actual, false, true );
- $this->assertArrayEquals( $expectedIntKeyValues, $actualIntKeyValues, false, true );
- }
-
}