'MediaWiki\\Auth\\Throttler' => __DIR__ . '/includes/auth/Throttler.php',
'MediaWiki\\Auth\\UserDataAuthenticationRequest' => __DIR__ . '/includes/auth/UserDataAuthenticationRequest.php',
'MediaWiki\\Auth\\UsernameAuthenticationRequest' => __DIR__ . '/includes/auth/UsernameAuthenticationRequest.php',
+ 'MediaWiki\\DB\\PatchFileLocation' => __DIR__ . '/includes/db/PatchFileLocation.php',
'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
--- /dev/null
+<?php
+/**
+ * Trait for finding SQL patch files.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\DB;
+
+use RuntimeException;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Trait for finding SQL patch files.
+ *
+ * @since 1.32
+ */
+trait PatchFileLocation {
+
+ /**
+ * Utility function for finding the appropriate SQL patch file for the currently
+ * used database type.
+ *
+ * The file will be searched for in the following locations, in order of preference:
+ * "$patchDir/$name.$dbType.sql",
+ * "$patchDir/$dbType/$name.sql",
+ * "$patchDir/$dbType/archives/$name.sql",
+ * "$patchDir/$name.sql",
+ * "$patchDir/archives/$name.sql"
+ *
+ * @param IDatabase $db
+ * @param string $name The script name (relative to $patchDir, without the '.sql' suffix)
+ * @param string $patchDir The directory to find the script in. Use __DIR__ to search in the
+ * directory the calling code is located in. If omitted, the "maintenance"
+ * directory will be used, where the scripts used by the updater are located.
+ *
+ * @return string
+ * @throws RuntimeException if no matching patch file could be found.
+ */
+ protected function getSqlPatchPath( IDatabase $db, $name, $patchDir = null ) {
+ $dbType = $db->getType();
+
+ if ( $patchDir === null ) {
+ $patchDir = $GLOBALS['IP'] . '/maintenance';
+ }
+
+ $paths = [
+
+ // For a small number of patch files, closely associated with code,
+ // e.g. for unit tests:
+ "$patchDir/$name.$dbType.sql",
+
+ // For a large number of patch files, e.g. for schema updates of extensions:
+ "$patchDir/$dbType/$name.sql",
+
+ // For MediaWiki core schema update patches:
+ "$patchDir/$dbType/archives/$name.sql",
+
+ // Database-agnostic fallback:
+ "$patchDir/$name.sql",
+
+ // Database-agnostic fallback for MediaWiki core schema update patches:
+ "$patchDir/archives/$name.sql"
+ ];
+
+ foreach ( $paths as $p ) {
+ if ( file_exists( $p ) ) {
+ return $p;
+ }
+ }
+
+ throw new RuntimeException( "No SQL script matching $name could be found in $patchDir" );
+ }
+
+}
'SpecialPageExecutor' => "$testDir/phpunit/includes/specials/SpecialPageExecutor.php",
# tests/phpunit/includes/Storage
+ 'MediaWiki\Tests\Storage\McrSchemaDetection' => "$testDir/phpunit/includes/Storage/McrSchemaDetection.php",
'MediaWiki\Tests\Storage\RevisionSlotsTest' => "$testDir/phpunit/includes/Storage/RevisionSlotsTest.php",
'MediaWiki\Tests\Storage\RevisionRecordTests' => "$testDir/phpunit/includes/Storage/RevisionRecordTests.php",
+ 'MediaWiki\Tests\Storage\RevisionStoreDbTestBase' => "$testDir/phpunit/includes/Storage/RevisionStoreDbTestBase.php",
+ 'MediaWiki\Tests\Storage\PreMcrSchemaOverride' => "$testDir/phpunit/includes/Storage/PreMcrSchemaOverride.php",
# tests/phpunit/languages
'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php",
+++ /dev/null
-<?php
-
-/**
- * @group Database
- * @group medium
- * @group ContentHandler
- */
-class RevisionContentHandlerDbTest extends RevisionDbTestBase {
-
- protected function getContentHandlerUseDB() {
- return true;
- }
-
-}
);
}
+ /**
+ * @return int
+ */
+ abstract protected function getMcrMigrationStage();
+
+ /**
+ * @return string[]
+ */
+ abstract protected function getMcrTablesToReset();
+
protected function setUp() {
global $wgContLang;
+ $this->tablesUsed += $this->getMcrTablesToReset();
+
parent::setUp();
$this->mergeMwGlobalArrayValue(
);
$this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
+ $this->setMwGlobals(
+ 'wgMultiContentRevisionSchemaMigrationStage',
+ $this->getMcrMigrationStage()
+ );
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
+ $this->overrideMwServices();
+
if ( !$this->testPage ) {
/**
* We have to create a new page for each subclass as the page creation may result
*/
public function testNewKnownCurrent() {
// Setup the services
+ $this->resetGlobalServices();
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
$this->setService( 'MainWANObjectCache', $cache );
$db = wfGetDB( DB_MASTER );
+++ /dev/null
-<?php
-
-/**
- * @group Database
- * @group medium
- * @group ContentHandler
- */
-class RevisionNoContentHandlerDbTest extends RevisionDbTestBase {
-
- protected function getContentHandlerUseDB() {
- return false;
- }
-
-}
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\PreMcrSchemaOverride;
+
+/**
+ * Tests Revision against the pre-MCR, pre ContentHandler DB schema.
+ *
+ * @covers Revision
+ *
+ * @group Revision
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class RevisionNoContentModelDbTest extends RevisionDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return false;
+ }
+
+}
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\PreMcrSchemaOverride;
+
+/**
+ * Tests Revision against the pre-MCR DB schema.
+ *
+ * @covers Revision
+ *
+ * @group Revision
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class RevisionPreMcrDbTest extends RevisionDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Trait providing methods for detecting which MCR schema migration phase the current schema
+ * is compatible with.
+ */
+trait McrSchemaDetection {
+
+ /**
+ * Returns true if MCR-related tables exist in the database.
+ * If yes, the database is compatible with with MIGRATION_NEW.
+ * If hasPreMcrFields() also returns true, the database supports MIGRATION_WRITE_BOTH mode.
+ *
+ * @param IDatabase $db
+ * @return bool
+ */
+ protected function hasMcrTables( IDatabase $db ) {
+ return $db->tableExists( 'slots', __METHOD__ );
+ }
+
+ /**
+ * Returns true if pre-MCR fields still exist in the database.
+ * If yes, the database is compatible with with MIGRATION_OLD mode.
+ * If hasMcrTables() also returns true, the database supports MIGRATION_WRITE_BOTH mode.
+ *
+ * Note that if the database has been updated in MIGRATION_NEW mode,
+ * the rev_text_id field will be 0 for new revisions. This means that
+ * in MIGRATION_OLD mode, reading such revisions will fail, even though
+ * all the necessary fields exist.
+ * This is not relevant for unit tests, since unit tests reset the database content anyway.
+ *
+ * @param IDatabase $db
+ * @return bool
+ */
+ protected function hasPreMcrFields( IDatabase $db ) {
+ return $db->fieldExists( 'revision', 'rev_content_model', __METHOD__ );
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+/**
+ * Tests RevisionStore against the pre-MCR, pre-ContentHandler DB schema.
+ *
+ * @covers \MediaWiki\Storage\RevisionStore
+ *
+ * @group RevisionStore
+ * @group Storage
+ * @group Database
+ * @group medium
+ */
+class NoContentModelRevisionStoreDbTest extends RevisionStoreDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return false;
+ }
+
+ public function provideGetArchiveQueryInfo() {
+ yield [
+ [
+ 'tables' => [ 'archive' ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields(),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ ]
+ ),
+ 'joins' => [],
+ ]
+ ];
+ }
+
+ public function provideGetQueryInfo() {
+ yield [
+ [],
+ [
+ 'tables' => [ 'revision' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields()
+ ),
+ 'joins' => [],
+ ]
+ ];
+ yield [
+ [ 'page' ],
+ [
+ 'tables' => [ 'revision', 'page' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ ],
+ ]
+ ];
+ yield [
+ [ 'user' ],
+ [
+ 'tables' => [ 'revision', 'user' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'user_name',
+ ]
+ ),
+ 'joins' => [
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ ],
+ ]
+ ];
+ yield [
+ [ 'text' ],
+ [
+ 'tables' => [ 'revision', 'text' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ [
+ 'old_text',
+ 'old_flags',
+ ]
+ ),
+ 'joins' => [
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ],
+ ],
+ ]
+ ];
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+/**
+ * Tests RevisionStore against the pre-MCR DB schema.
+ *
+ * @covers \MediaWiki\Storage\RevisionStore
+ *
+ * @group RevisionStore
+ * @group Storage
+ * @group Database
+ * @group medium
+ */
+class PreMcrRevisionStoreDbTest extends RevisionStoreDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ public function provideGetArchiveQueryInfo() {
+ yield [
+ [
+ 'tables' => [ 'archive' ],
+ 'fields' => array_merge(
+ $this->getDefaultArchiveFields(),
+ [
+ 'ar_comment_text' => 'ar_comment',
+ 'ar_comment_data' => 'NULL',
+ 'ar_comment_cid' => 'NULL',
+ 'ar_user_text' => 'ar_user_text',
+ 'ar_user' => 'ar_user',
+ 'ar_actor' => 'NULL',
+ 'ar_content_format',
+ 'ar_content_model',
+ ]
+ ),
+ 'joins' => [],
+ ]
+ ];
+ }
+
+ public function provideGetQueryInfo() {
+ yield [
+ [],
+ [
+ 'tables' => [ 'revision' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ $this->getContentHandlerQueryFields()
+ ),
+ 'joins' => [],
+ ]
+ ];
+ yield [
+ [ 'page', 'user', 'text' ],
+ [
+ 'tables' => [ 'revision', 'page', 'user', 'text' ],
+ 'fields' => array_merge(
+ $this->getDefaultQueryFields(),
+ $this->getCommentQueryFields(),
+ $this->getActorQueryFields(),
+ $this->getContentHandlerQueryFields(),
+ [
+ 'page_namespace',
+ 'page_title',
+ 'page_id',
+ 'page_latest',
+ 'page_is_redirect',
+ 'page_len',
+ 'user_name',
+ 'old_text',
+ 'old_flags'
+ ]
+ ),
+ 'joins' => [
+ 'page' => [ 'INNER JOIN', [ 'page_id = rev_page' ] ],
+ 'user' => [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ],
+ ],
+ ]
+ ];
+ }
+
+}
--- /dev/null
+<?php
+namespace MediaWiki\Tests\Storage;
+
+use Wikimedia\Rdbms\IMaintainableDatabase;
+use MediaWiki\DB\PatchFileLocation;
+
+/**
+ * Trait providing schema overrides that allow tests to run against the pre-MCR database schema.
+ */
+trait PreMcrSchemaOverride {
+
+ use PatchFileLocation;
+ use McrSchemaDetection;
+
+ /**
+ * @return int
+ */
+ protected function getMcrMigrationStage() {
+ return MIGRATION_OLD;
+ }
+
+ /**
+ * @return string[]
+ */
+ protected function getMcrTablesToReset() {
+ return [];
+ }
+
+ /**
+ * @override MediaWikiTestCase::getSchemaOverrides
+ * @return array[]
+ */
+ protected function getSchemaOverrides( IMaintainableDatabase $db ) {
+ $overrides = [
+ 'scripts' => [],
+ 'drop' => [],
+ 'create' => [],
+ 'alter' => [],
+ ];
+
+ if ( $this->hasMcrTables( $db ) ) {
+ $overrides['drop'] = [ 'slots', 'content', 'slot_roles', 'content_models', ];
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, '/drop-mcr-tables', __DIR__ );
+ }
+
+ if ( !$this->hasPreMcrFields( $db ) ) {
+ $overrides['alter'][] = 'revision';
+ $overrides['scripts'][] = $this->getSqlPatchPath( $db, '/create-pre-mcr-fields', __DIR__ );
+ }
+
+ return $overrides;
+ }
+
+}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Storage;
-
-use CommentStoreComment;
-use Exception;
-use HashBagOStuff;
-use InvalidArgumentException;
-use Language;
-use MediaWiki\Linker\LinkTarget;
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Storage\BlobStoreFactory;
-use MediaWiki\Storage\IncompleteRevisionException;
-use MediaWiki\Storage\MutableRevisionRecord;
-use MediaWiki\Storage\RevisionRecord;
-use MediaWiki\Storage\RevisionStore;
-use MediaWiki\Storage\SlotRecord;
-use MediaWiki\Storage\SqlBlobStore;
-use MediaWikiTestCase;
-use Revision;
-use TestUserRegistry;
-use Title;
-use WANObjectCache;
-use Wikimedia\Rdbms\Database;
-use Wikimedia\Rdbms\DatabaseSqlite;
-use Wikimedia\Rdbms\FakeResultWrapper;
-use Wikimedia\Rdbms\LoadBalancer;
-use Wikimedia\Rdbms\TransactionProfiler;
-use WikiPage;
-use WikitextContent;
-
-/**
- * @group Database
- */
-class RevisionStoreDbTest extends MediaWikiTestCase {
-
- public function setUp() {
- parent::setUp();
- $this->tablesUsed[] = 'archive';
- $this->tablesUsed[] = 'page';
- $this->tablesUsed[] = 'revision';
- $this->tablesUsed[] = 'comment';
- }
-
- /**
- * @return LoadBalancer
- */
- private function getLoadBalancerMock( array $server ) {
- $lb = $this->getMockBuilder( LoadBalancer::class )
- ->setMethods( [ 'reallyOpenConnection' ] )
- ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
- ->getMock();
-
- $lb->method( 'reallyOpenConnection' )->willReturnCallback(
- function ( array $server, $dbNameOverride ) {
- return $this->getDatabaseMock( $server );
- }
- );
-
- return $lb;
- }
-
- /**
- * @return Database
- */
- private function getDatabaseMock( array $params ) {
- $db = $this->getMockBuilder( DatabaseSqlite::class )
- ->setMethods( [ 'select', 'doQuery', 'open', 'closeConnection', 'isOpen' ] )
- ->setConstructorArgs( [ $params ] )
- ->getMock();
-
- $db->method( 'select' )->willReturn( new FakeResultWrapper( [] ) );
- $db->method( 'isOpen' )->willReturn( true );
-
- return $db;
- }
-
- public function provideDomainCheck() {
- yield [ false, 'test', '' ];
- yield [ 'test', 'test', '' ];
-
- yield [ false, 'test', 'foo_' ];
- yield [ 'test-foo_', 'test', 'foo_' ];
-
- yield [ false, 'dash-test', '' ];
- yield [ 'dash-test', 'dash-test', '' ];
-
- yield [ false, 'underscore_test', 'foo_' ];
- yield [ 'underscore_test-foo_', 'underscore_test', 'foo_' ];
- }
-
- /**
- * @dataProvider provideDomainCheck
- * @covers \MediaWiki\Storage\RevisionStore::checkDatabaseWikiId
- */
- public function testDomainCheck( $wikiId, $dbName, $dbPrefix ) {
- $this->setMwGlobals(
- [
- 'wgDBname' => $dbName,
- 'wgDBprefix' => $dbPrefix,
- ]
- );
-
- $loadBalancer = $this->getLoadBalancerMock(
- [
- 'host' => '*dummy*',
- 'dbDirectory' => '*dummy*',
- 'user' => 'test',
- 'password' => 'test',
- 'flags' => 0,
- 'variables' => [],
- 'schema' => '',
- 'cliMode' => true,
- 'agent' => '',
- 'load' => 100,
- 'profiler' => null,
- 'trxProfiler' => new TransactionProfiler(),
- 'connLogger' => new \Psr\Log\NullLogger(),
- 'queryLogger' => new \Psr\Log\NullLogger(),
- 'errorLogger' => function () {
- },
- 'deprecationLogger' => function () {
- },
- 'type' => 'test',
- 'dbname' => $dbName,
- 'tablePrefix' => $dbPrefix,
- ]
- );
- $db = $loadBalancer->getConnection( DB_REPLICA );
-
- $blobStore = $this->getMockBuilder( SqlBlobStore::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $store = new RevisionStore(
- $loadBalancer,
- $blobStore,
- new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ),
- MediaWikiServices::getInstance()->getCommentStore(),
- MediaWikiServices::getInstance()->getActorMigration(),
- $wikiId
- );
-
- $count = $store->countRevisionsByPageId( $db, 0 );
-
- // Dummy check to make PhpUnit happy. We are really only interested in
- // countRevisionsByPageId not failing due to the DB domain check.
- $this->assertSame( 0, $count );
- }
-
- private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
- $this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
- $this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
- $this->assertEquals( $l1->getFragment(), $l2->getFragment() );
- $this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
- }
-
- private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
- $this->assertEquals( $r1->getUser()->getName(), $r2->getUser()->getName() );
- $this->assertEquals( $r1->getUser()->getId(), $r2->getUser()->getId() );
- $this->assertEquals( $r1->getComment(), $r2->getComment() );
- $this->assertEquals( $r1->getPageAsLinkTarget(), $r2->getPageAsLinkTarget() );
- $this->assertEquals( $r1->getTimestamp(), $r2->getTimestamp() );
- $this->assertEquals( $r1->getVisibility(), $r2->getVisibility() );
- $this->assertEquals( $r1->getSha1(), $r2->getSha1() );
- $this->assertEquals( $r1->getParentId(), $r2->getParentId() );
- $this->assertEquals( $r1->getSize(), $r2->getSize() );
- $this->assertEquals( $r1->getPageId(), $r2->getPageId() );
- $this->assertEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
- $this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
- $this->assertEquals( $r1->isMinor(), $r2->isMinor() );
- foreach ( $r1->getSlotRoles() as $role ) {
- $this->assertSlotRecordsEqual( $r1->getSlot( $role ), $r2->getSlot( $role ) );
- $this->assertTrue( $r1->getContent( $role )->equals( $r2->getContent( $role ) ) );
- }
- foreach ( [
- RevisionRecord::DELETED_TEXT,
- RevisionRecord::DELETED_COMMENT,
- RevisionRecord::DELETED_USER,
- RevisionRecord::DELETED_RESTRICTED,
- ] as $field ) {
- $this->assertEquals( $r1->isDeleted( $field ), $r2->isDeleted( $field ) );
- }
- }
-
- private function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
- $this->assertSame( $s1->getRole(), $s2->getRole() );
- $this->assertSame( $s1->getModel(), $s2->getModel() );
- $this->assertSame( $s1->getFormat(), $s2->getFormat() );
- $this->assertSame( $s1->getSha1(), $s2->getSha1() );
- $this->assertSame( $s1->getSize(), $s2->getSize() );
- $this->assertTrue( $s1->getContent()->equals( $s2->getContent() ) );
-
- $s1->hasRevision() ? $this->assertSame( $s1->getRevision(), $s2->getRevision() ) : null;
- $s1->hasAddress() ? $this->assertSame( $s1->hasAddress(), $s2->hasAddress() ) : null;
- }
-
- private function assertRevisionCompleteness( RevisionRecord $r ) {
- foreach ( $r->getSlotRoles() as $role ) {
- $this->assertSlotCompleteness( $r, $r->getSlot( $role ) );
- }
- }
-
- private function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
- $this->assertTrue( $slot->hasAddress() );
- $this->assertSame( $r->getId(), $slot->getRevision() );
- }
-
- /**
- * @param mixed[] $details
- *
- * @return RevisionRecord
- */
- private function getRevisionRecordFromDetailsArray( $title, $details = [] ) {
- // Convert some values that can't be provided by dataProviders
- $page = WikiPage::factory( $title );
- if ( isset( $details['user'] ) && $details['user'] === true ) {
- $details['user'] = $this->getTestUser()->getUser();
- }
- if ( isset( $details['page'] ) && $details['page'] === true ) {
- $details['page'] = $page->getId();
- }
- if ( isset( $details['parent'] ) && $details['parent'] === true ) {
- $details['parent'] = $page->getLatest();
- }
-
- // Create the RevisionRecord with any available data
- $rev = new MutableRevisionRecord( $title );
- isset( $details['slot'] ) ? $rev->setSlot( $details['slot'] ) : null;
- isset( $details['parent'] ) ? $rev->setParentId( $details['parent'] ) : null;
- isset( $details['page'] ) ? $rev->setPageId( $details['page'] ) : null;
- isset( $details['size'] ) ? $rev->setSize( $details['size'] ) : null;
- isset( $details['sha1'] ) ? $rev->setSha1( $details['sha1'] ) : null;
- isset( $details['comment'] ) ? $rev->setComment( $details['comment'] ) : null;
- isset( $details['timestamp'] ) ? $rev->setTimestamp( $details['timestamp'] ) : null;
- isset( $details['minor'] ) ? $rev->setMinorEdit( $details['minor'] ) : null;
- isset( $details['user'] ) ? $rev->setUser( $details['user'] ) : null;
- isset( $details['visibility'] ) ? $rev->setVisibility( $details['visibility'] ) : null;
- isset( $details['id'] ) ? $rev->setId( $details['id'] ) : null;
-
- return $rev;
- }
-
- private function getRandomCommentStoreComment() {
- return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) );
- }
-
- public function provideInsertRevisionOn_successes() {
- yield 'Bare minimum revision insertion' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'parent' => true,
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- 'user' => true,
- ],
- ];
- yield 'Detailed revision insertion' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'parent' => true,
- 'page' => true,
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- 'user' => true,
- 'minor' => true,
- 'visibility' => RevisionRecord::DELETED_RESTRICTED,
- ],
- ];
- }
-
- /**
- * @dataProvider provideInsertRevisionOn_successes
- * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
- */
- public function testInsertRevisionOn_successes( Title $title, array $revDetails = [] ) {
- $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
-
- $this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() );
- $this->assertRevisionRecordsEqual( $rev, $return );
- $this->assertRevisionCompleteness( $return );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
- */
- public function testInsertRevisionOn_blobAddressExists() {
- $title = Title::newFromText( 'UTPage' );
- $revDetails = [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'parent' => true,
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- 'user' => true,
- ];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- // Insert the first revision
- $revOne = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
- $firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_MASTER ) );
- $this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
- $this->assertRevisionRecordsEqual( $revOne, $firstReturn );
-
- // Insert a second revision inheriting the same blob address
- $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( 'main' ) );
- $revTwo = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
- $secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_MASTER ) );
- $this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() );
- $this->assertRevisionRecordsEqual( $revTwo, $secondReturn );
-
- // Assert that the same blob address has been used.
- $this->assertEquals(
- $firstReturn->getSlot( 'main' )->getAddress(),
- $secondReturn->getSlot( 'main' )->getAddress()
- );
- // And that different revisions have been created.
- $this->assertNotSame(
- $firstReturn->getId(),
- $secondReturn->getId()
- );
- }
-
- public function provideInsertRevisionOn_failures() {
- yield 'no slot' => [
- Title::newFromText( 'UTPage' ),
- [
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- 'user' => true,
- ],
- new InvalidArgumentException( 'At least one slot needs to be defined!' )
- ];
- yield 'slot that is not main slot' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'lalala', new WikitextContent( 'Chicken' ) ),
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- 'user' => true,
- ],
- new InvalidArgumentException( 'Only the main slot is supported for now!' )
- ];
- yield 'no timestamp' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'comment' => $this->getRandomCommentStoreComment(),
- 'user' => true,
- ],
- new IncompleteRevisionException( 'timestamp field must not be NULL!' )
- ];
- yield 'no comment' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'timestamp' => '20171117010101',
- 'user' => true,
- ],
- new IncompleteRevisionException( 'comment must not be NULL!' )
- ];
- yield 'no user' => [
- Title::newFromText( 'UTPage' ),
- [
- 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
- 'comment' => $this->getRandomCommentStoreComment(),
- 'timestamp' => '20171117010101',
- ],
- new IncompleteRevisionException( 'user must not be NULL!' )
- ];
- }
-
- /**
- * @dataProvider provideInsertRevisionOn_failures
- * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
- */
- public function testInsertRevisionOn_failures(
- Title $title,
- array $revDetails = [],
- Exception $exception ) {
- $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- $this->setExpectedException(
- get_class( $exception ),
- $exception->getMessage(),
- $exception->getCode()
- );
- $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
- }
-
- public function provideNewNullRevision() {
- yield [
- Title::newFromText( 'UTPage' ),
- CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ),
- true,
- ];
- yield [
- Title::newFromText( 'UTPage' ),
- CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ),
- false,
- ];
- }
-
- /**
- * @dataProvider provideNewNullRevision
- * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
- */
- public function testNewNullRevision( Title $title, $comment, $minor ) {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
-
- $parent = $store->getRevisionByTitle( $title );
- $record = $store->newNullRevision(
- wfGetDB( DB_MASTER ),
- $title,
- $comment,
- $minor,
- $user
- );
-
- $this->assertEquals( $title->getNamespace(), $record->getPageAsLinkTarget()->getNamespace() );
- $this->assertEquals( $title->getDBkey(), $record->getPageAsLinkTarget()->getDBkey() );
- $this->assertEquals( $comment, $record->getComment() );
- $this->assertEquals( $minor, $record->isMinor() );
- $this->assertEquals( $user->getName(), $record->getUser()->getName() );
- $this->assertEquals( $parent->getId(), $record->getParentId() );
-
- $parentSlot = $parent->getSlot( 'main' );
- $slot = $record->getSlot( 'main' );
-
- $this->assertTrue( $slot->isInherited(), 'isInherited' );
- $this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' );
- $this->assertSame( $parentSlot->getAddress(), $slot->getAddress(), 'getAddress' );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
- */
- public function testNewNullRevision_nonExistingTitle() {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $record = $store->newNullRevision(
- wfGetDB( DB_MASTER ),
- Title::newFromText( __METHOD__ . '.iDontExist!' ),
- CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment' ),
- false,
- TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser()
- );
- $this->assertNull( $record );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
- */
- public function testGetRcIdIfUnpatrolled_returnsRecentChangesId() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $status = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revisionRecord = $store->getRevisionById( $rev->getId() );
- $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
-
- $this->assertGreaterThan( 0, $result );
- $this->assertSame(
- $page->getRevision()->getRecentChange()->getAttribute( 'rc_id' ),
- $result
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
- */
- public function testGetRcIdIfUnpatrolled_returnsZeroIfPatrolled() {
- // This assumes that sysops are auto patrolled
- $sysop = $this->getTestSysop()->getUser();
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $status = $page->doEditContent(
- new WikitextContent( __METHOD__ ),
- __METHOD__,
- 0,
- false,
- $sysop
- );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revisionRecord = $store->getRevisionById( $rev->getId() );
- $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
-
- $this->assertSame( 0, $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRecentChange
- */
- public function testGetRecentChange() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $content = new WikitextContent( __METHOD__ );
- $status = $page->doEditContent( $content, __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revRecord = $store->getRevisionById( $rev->getId() );
- $recentChange = $store->getRecentChange( $revRecord );
-
- $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
- $this->assertEquals( $rev->getRecentChange(), $recentChange );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRevisionById
- */
- public function testGetRevisionById() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $content = new WikitextContent( __METHOD__ );
- $status = $page->doEditContent( $content, __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revRecord = $store->getRevisionById( $rev->getId() );
-
- $this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
- $this->assertSame( __METHOD__, $revRecord->getComment()->text );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTitle
- */
- public function testGetRevisionByTitle() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $content = new WikitextContent( __METHOD__ );
- $status = $page->doEditContent( $content, __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revRecord = $store->getRevisionByTitle( $page->getTitle() );
-
- $this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
- $this->assertSame( __METHOD__, $revRecord->getComment()->text );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRevisionByPageId
- */
- public function testGetRevisionByPageId() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $content = new WikitextContent( __METHOD__ );
- $status = $page->doEditContent( $content, __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revRecord = $store->getRevisionByPageId( $page->getId() );
-
- $this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
- $this->assertSame( __METHOD__, $revRecord->getComment()->text );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTimestamp
- */
- public function testGetRevisionByTimestamp() {
- // Make sure there is 1 second between the last revision and the rev we create...
- // Otherwise we might not get the correct revision and the test may fail...
- // :(
- sleep( 1 );
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $content = new WikitextContent( __METHOD__ );
- $status = $page->doEditContent( $content, __METHOD__ );
- /** @var Revision $rev */
- $rev = $status->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $revRecord = $store->getRevisionByTimestamp(
- $page->getTitle(),
- $rev->getTimestamp()
- );
-
- $this->assertSame( $rev->getId(), $revRecord->getId() );
- $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
- $this->assertSame( __METHOD__, $revRecord->getComment()->text );
- }
-
- private function revisionToRow( Revision $rev ) {
- $page = WikiPage::factory( $rev->getTitle() );
-
- return (object)[
- 'rev_id' => (string)$rev->getId(),
- 'rev_page' => (string)$rev->getPage(),
- 'rev_text_id' => (string)$rev->getTextId(),
- 'rev_timestamp' => (string)$rev->getTimestamp(),
- 'rev_user_text' => (string)$rev->getUserText(),
- 'rev_user' => (string)$rev->getUser(),
- 'rev_minor_edit' => $rev->isMinor() ? '1' : '0',
- 'rev_deleted' => (string)$rev->getVisibility(),
- 'rev_len' => (string)$rev->getSize(),
- 'rev_parent_id' => (string)$rev->getParentId(),
- 'rev_sha1' => (string)$rev->getSha1(),
- 'rev_comment_text' => $rev->getComment(),
- 'rev_comment_data' => null,
- 'rev_comment_cid' => null,
- 'rev_content_format' => $rev->getContentFormat(),
- 'rev_content_model' => $rev->getContentModel(),
- 'page_namespace' => (string)$page->getTitle()->getNamespace(),
- 'page_title' => $page->getTitle()->getDBkey(),
- 'page_id' => (string)$page->getId(),
- 'page_latest' => (string)$page->getLatest(),
- 'page_is_redirect' => $page->isRedirect() ? '1' : '0',
- 'page_len' => (string)$page->getContent()->getSize(),
- 'user_name' => (string)$rev->getUserText(),
- ];
- }
-
- private function assertRevisionRecordMatchesRevision(
- Revision $rev,
- RevisionRecord $record
- ) {
- $this->assertSame( $rev->getId(), $record->getId() );
- $this->assertSame( $rev->getPage(), $record->getPageId() );
- $this->assertSame( $rev->getTimestamp(), $record->getTimestamp() );
- $this->assertSame( $rev->getUserText(), $record->getUser()->getName() );
- $this->assertSame( $rev->getUser(), $record->getUser()->getId() );
- $this->assertSame( $rev->isMinor(), $record->isMinor() );
- $this->assertSame( $rev->getVisibility(), $record->getVisibility() );
- $this->assertSame( $rev->getSize(), $record->getSize() );
- /**
- * @note As of MW 1.31, the database schema allows the parent ID to be
- * NULL to indicate that it is unknown.
- */
- $expectedParent = $rev->getParentId();
- if ( $expectedParent === null ) {
- $expectedParent = 0;
- }
- $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->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
- */
- public function testNewRevisionFromRow_anonEdit() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
- $this->overrideMwServices();
-
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $text = __METHOD__ . 'a-ä';
- /** @var Revision $rev */
- $rev = $page->doEditContent(
- new WikitextContent( $text ),
- __METHOD__ . 'a'
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $record = $store->newRevisionFromRow(
- $this->revisionToRow( $rev ),
- [],
- $page->getTitle()
- );
- $this->assertRevisionRecordMatchesRevision( $rev, $record );
- $this->assertSame( $text, $rev->getContent()->serialize() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
- */
- public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
- $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
- $this->overrideMwServices();
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $text = __METHOD__ . 'a-ä';
- /** @var Revision $rev */
- $rev = $page->doEditContent(
- new WikitextContent( $text ),
- __METHOD__. 'a'
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $record = $store->newRevisionFromRow(
- $this->revisionToRow( $rev ),
- [],
- $page->getTitle()
- );
- $this->assertRevisionRecordMatchesRevision( $rev, $record );
- $this->assertSame( $text, $rev->getContent()->serialize() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
- */
- public function testNewRevisionFromRow_userEdit() {
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
- $this->overrideMwServices();
-
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $text = __METHOD__ . 'b-ä';
- /** @var Revision $rev */
- $rev = $page->doEditContent(
- new WikitextContent( $text ),
- __METHOD__ . 'b',
- 0,
- false,
- $this->getTestUser()->getUser()
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $record = $store->newRevisionFromRow(
- $this->revisionToRow( $rev ),
- [],
- $page->getTitle()
- );
- $this->assertRevisionRecordMatchesRevision( $rev, $record );
- $this->assertSame( $text, $rev->getContent()->serialize() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
- */
- public function testNewRevisionFromArchiveRow() {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $title = Title::newFromText( __METHOD__ );
- $text = __METHOD__ . '-bä';
- $page = WikiPage::factory( $title );
- /** @var Revision $orig */
- $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
- ->value['revision'];
- $page->doDeleteArticle( __METHOD__ );
-
- $db = wfGetDB( DB_MASTER );
- $arQuery = $store->getArchiveQueryInfo();
- $res = $db->select(
- $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
- __METHOD__, [], $arQuery['joins']
- );
- $this->assertTrue( is_object( $res ), 'query failed' );
-
- $row = $res->fetchObject();
- $res->free();
- $record = $store->newRevisionFromArchiveRow( $row );
-
- $this->assertRevisionRecordMatchesRevision( $orig, $record );
- $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
- */
- public function testNewRevisionFromArchiveRow_legacyEncoding() {
- $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
- $this->overrideMwServices();
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $title = Title::newFromText( __METHOD__ );
- $text = __METHOD__ . '-bä';
- $page = WikiPage::factory( $title );
- /** @var Revision $orig */
- $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
- ->value['revision'];
- $page->doDeleteArticle( __METHOD__ );
-
- $db = wfGetDB( DB_MASTER );
- $arQuery = $store->getArchiveQueryInfo();
- $res = $db->select(
- $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
- __METHOD__, [], $arQuery['joins']
- );
- $this->assertTrue( is_object( $res ), 'query failed' );
-
- $row = $res->fetchObject();
- $res->free();
- $record = $store->newRevisionFromArchiveRow( $row );
-
- $this->assertRevisionRecordMatchesRevision( $orig, $record );
- $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromId
- */
- public function testLoadRevisionFromId() {
- $title = Title::newFromText( __METHOD__ );
- $page = WikiPage::factory( $title );
- /** @var Revision $rev */
- $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->loadRevisionFromId( wfGetDB( DB_MASTER ), $rev->getId() );
- $this->assertRevisionRecordMatchesRevision( $rev, $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromPageId
- */
- public function testLoadRevisionFromPageId() {
- $title = Title::newFromText( __METHOD__ );
- $page = WikiPage::factory( $title );
- /** @var Revision $rev */
- $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
- $this->assertRevisionRecordMatchesRevision( $rev, $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTitle
- */
- public function testLoadRevisionFromTitle() {
- $title = Title::newFromText( __METHOD__ );
- $page = WikiPage::factory( $title );
- /** @var Revision $rev */
- $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
- $this->assertRevisionRecordMatchesRevision( $rev, $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTimestamp
- */
- public function testLoadRevisionFromTimestamp() {
- $title = Title::newFromText( __METHOD__ );
- $page = WikiPage::factory( $title );
- /** @var Revision $revOne */
- $revOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
- // Sleep to ensure different timestamps... )(evil)
- sleep( 1 );
- /** @var Revision $revTwo */
- $revTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $this->assertNull(
- $store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
- );
- $this->assertSame(
- $revOne->getId(),
- $store->loadRevisionFromTimestamp(
- wfGetDB( DB_MASTER ),
- $title,
- $revOne->getTimestamp()
- )->getId()
- );
- $this->assertSame(
- $revTwo->getId(),
- $store->loadRevisionFromTimestamp(
- wfGetDB( DB_MASTER ),
- $title,
- $revTwo->getTimestamp()
- )->getId()
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::listRevisionSizes
- */
- public function testGetParentLengths() {
- $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
- /** @var Revision $revOne */
- $revOne = $page->doEditContent(
- new WikitextContent( __METHOD__ ), __METHOD__
- )->value['revision'];
- /** @var Revision $revTwo */
- $revTwo = $page->doEditContent(
- new WikitextContent( __METHOD__ . '2' ), __METHOD__
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $this->assertSame(
- [
- $revOne->getId() => strlen( __METHOD__ ),
- ],
- $store->listRevisionSizes(
- wfGetDB( DB_MASTER ),
- [ $revOne->getId() ]
- )
- );
- $this->assertSame(
- [
- $revOne->getId() => strlen( __METHOD__ ),
- $revTwo->getId() => strlen( __METHOD__ ) + 1,
- ],
- $store->listRevisionSizes(
- wfGetDB( DB_MASTER ),
- [ $revOne->getId(), $revTwo->getId() ]
- )
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getPreviousRevision
- */
- public function testGetPreviousRevision() {
- $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
- /** @var Revision $revOne */
- $revOne = $page->doEditContent(
- new WikitextContent( __METHOD__ ), __METHOD__
- )->value['revision'];
- /** @var Revision $revTwo */
- $revTwo = $page->doEditContent(
- new WikitextContent( __METHOD__ . '2' ), __METHOD__
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $this->assertNull(
- $store->getPreviousRevision( $store->getRevisionById( $revOne->getId() ) )
- );
- $this->assertSame(
- $revOne->getId(),
- $store->getPreviousRevision( $store->getRevisionById( $revTwo->getId() ) )->getId()
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getNextRevision
- */
- public function testGetNextRevision() {
- $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
- /** @var Revision $revOne */
- $revOne = $page->doEditContent(
- new WikitextContent( __METHOD__ ), __METHOD__
- )->value['revision'];
- /** @var Revision $revTwo */
- $revTwo = $page->doEditContent(
- new WikitextContent( __METHOD__ . '2' ), __METHOD__
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $this->assertSame(
- $revTwo->getId(),
- $store->getNextRevision( $store->getRevisionById( $revOne->getId() ) )->getId()
- );
- $this->assertNull(
- $store->getNextRevision( $store->getRevisionById( $revTwo->getId() ) )
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
- */
- public function testGetTimestampFromId_found() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- /** @var Revision $rev */
- $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->getTimestampFromId(
- $page->getTitle(),
- $rev->getId()
- );
-
- $this->assertSame( $rev->getTimestamp(), $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
- */
- public function testGetTimestampFromId_notFound() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- /** @var Revision $rev */
- $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
- ->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->getTimestampFromId(
- $page->getTitle(),
- $rev->getId() + 1
- );
-
- $this->assertFalse( $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByPageId
- */
- public function testCountRevisionsByPageId() {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
-
- $this->assertSame(
- 0,
- $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
- );
- $page->doEditContent( new WikitextContent( 'a' ), 'a' );
- $this->assertSame(
- 1,
- $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
- );
- $page->doEditContent( new WikitextContent( 'b' ), 'b' );
- $this->assertSame(
- 2,
- $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByTitle
- */
- public function testCountRevisionsByTitle() {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
-
- $this->assertSame(
- 0,
- $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
- );
- $page->doEditContent( new WikitextContent( 'a' ), 'a' );
- $this->assertSame(
- 1,
- $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
- );
- $page->doEditContent( new WikitextContent( 'b' ), 'b' );
- $this->assertSame(
- 2,
- $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
- */
- public function testUserWasLastToEdit_false() {
- $sysop = $this->getTestSysop()->getUser();
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->userWasLastToEdit(
- wfGetDB( DB_MASTER ),
- $page->getId(),
- $sysop->getId(),
- '20160101010101'
- );
- $this->assertFalse( $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
- */
- public function testUserWasLastToEdit_true() {
- $startTime = wfTimestampNow();
- $sysop = $this->getTestSysop()->getUser();
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- $page->doEditContent(
- new WikitextContent( __METHOD__ ),
- __METHOD__,
- 0,
- false,
- $sysop
- );
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $result = $store->userWasLastToEdit(
- wfGetDB( DB_MASTER ),
- $page->getId(),
- $sysop->getId(),
- $startTime
- );
- $this->assertTrue( $result );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getKnownCurrentRevision
- */
- public function testGetKnownCurrentRevision() {
- $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
- /** @var Revision $rev */
- $rev = $page->doEditContent(
- new WikitextContent( __METHOD__ . 'b' ),
- __METHOD__ . 'b',
- 0,
- false,
- $this->getTestUser()->getUser()
- )->value['revision'];
-
- $store = MediaWikiServices::getInstance()->getRevisionStore();
- $record = $store->getKnownCurrentRevision(
- $page->getTitle(),
- $rev->getId()
- );
-
- $this->assertRevisionRecordMatchesRevision( $rev, $record );
- }
-
- public function provideNewMutableRevisionFromArray() {
- yield 'Basic array, with page & id' => [
- [
- 'id' => 2,
- 'page' => 1,
- 'text_id' => 2,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.2',
- 'user' => 0,
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'content_format' => 'text/x-wiki',
- 'content_model' => 'wikitext',
- ]
- ];
- yield 'Basic array, content object' => [
- [
- 'id' => 2,
- 'page' => 1,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.2',
- 'user' => 0,
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'content' => new WikitextContent( 'Some Content' ),
- ]
- ];
- yield 'Basic array, serialized text' => [
- [
- 'id' => 2,
- 'page' => 1,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.2',
- 'user' => 0,
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
- ]
- ];
- yield 'Basic array, serialized text, utf-8 flags' => [
- [
- 'id' => 2,
- 'page' => 1,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.2',
- 'user' => 0,
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
- 'flags' => 'utf-8',
- ]
- ];
- yield 'Basic array, with title' => [
- [
- 'title' => Title::newFromText( 'SomeText' ),
- 'text_id' => 2,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.2',
- 'user' => 0,
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'content_format' => 'text/x-wiki',
- 'content_model' => 'wikitext',
- ]
- ];
- yield 'Basic array, no user field' => [
- [
- 'id' => 2,
- 'page' => 1,
- 'text_id' => 2,
- 'timestamp' => '20171017114835',
- 'user_text' => '111.0.1.3',
- 'minor_edit' => false,
- 'deleted' => 0,
- 'len' => 46,
- 'parent_id' => 1,
- 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
- 'comment' => 'Goat Comment!',
- 'content_format' => 'text/x-wiki',
- 'content_model' => 'wikitext',
- ]
- ];
- }
-
- /**
- * @dataProvider provideNewMutableRevisionFromArray
- * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
- */
- public function testNewMutableRevisionFromArray( array $array ) {
- $store = MediaWikiServices::getInstance()->getRevisionStore();
-
- $result = $store->newMutableRevisionFromArray( $array );
-
- if ( isset( $array['id'] ) ) {
- $this->assertSame( $array['id'], $result->getId() );
- }
- if ( isset( $array['page'] ) ) {
- $this->assertSame( $array['page'], $result->getPageId() );
- }
- $this->assertSame( $array['timestamp'], $result->getTimestamp() );
- $this->assertSame( $array['user_text'], $result->getUser()->getName() );
- if ( isset( $array['user'] ) ) {
- $this->assertSame( $array['user'], $result->getUser()->getId() );
- }
- $this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
- $this->assertSame( $array['deleted'], $result->getVisibility() );
- $this->assertSame( $array['len'], $result->getSize() );
- $this->assertSame( $array['parent_id'], $result->getParentId() );
- $this->assertSame( $array['sha1'], $result->getSha1() );
- $this->assertSame( $array['comment'], $result->getComment()->text );
- if ( isset( $array['content'] ) ) {
- $this->assertTrue(
- $result->getSlot( 'main' )->getContent()->equals( $array['content'] )
- );
- } elseif ( isset( $array['text'] ) ) {
- $this->assertSame( $array['text'], $result->getSlot( 'main' )->getContent()->serialize() );
- } else {
- $this->assertSame(
- $array['content_format'],
- $result->getSlot( 'main' )->getContent()->getDefaultFormat()
- );
- $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
- }
- }
-
- /**
- * @dataProvider provideNewMutableRevisionFromArray
- * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
- */
- public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
- $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- $blobStore = new SqlBlobStore( $lb, $cache );
- $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
-
- $factory = $this->getMockBuilder( BlobStoreFactory::class )
- ->setMethods( [ 'newBlobStore', 'newSqlBlobStore' ] )
- ->disableOriginalConstructor()
- ->getMock();
- $factory->expects( $this->any() )
- ->method( 'newBlobStore' )
- ->willReturn( $blobStore );
- $factory->expects( $this->any() )
- ->method( 'newSqlBlobStore' )
- ->willReturn( $blobStore );
-
- $this->setService( 'BlobStoreFactory', $factory );
-
- $this->testNewMutableRevisionFromArray( $array );
- }
-
-}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Storage;
+
+use CommentStoreComment;
+use Exception;
+use HashBagOStuff;
+use InvalidArgumentException;
+use Language;
+use MediaWiki\Linker\LinkTarget;
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\BlobStoreFactory;
+use MediaWiki\Storage\IncompleteRevisionException;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+use MediaWiki\Storage\SlotRecord;
+use MediaWiki\Storage\SqlBlobStore;
+use MediaWikiTestCase;
+use PHPUnit_Framework_MockObject_MockObject;
+use Revision;
+use TestUserRegistry;
+use Title;
+use WANObjectCache;
+use Wikimedia\Rdbms\Database;
+use Wikimedia\Rdbms\DatabaseSqlite;
+use Wikimedia\Rdbms\FakeResultWrapper;
+use Wikimedia\Rdbms\LoadBalancer;
+use Wikimedia\Rdbms\TransactionProfiler;
+use WikiPage;
+use WikitextContent;
+
+/**
+ * @group Database
+ * @group RevisionStore
+ */
+abstract class RevisionStoreDbTestBase extends MediaWikiTestCase {
+
+ /**
+ * @return int
+ */
+ abstract protected function getMcrMigrationStage();
+
+ /**
+ * @return bool
+ */
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+ /**
+ * @return string[]
+ */
+ abstract protected function getMcrTablesToReset();
+
+ public function needsDB() {
+ return true;
+ }
+
+ public function setUp() {
+ parent::setUp();
+ $this->tablesUsed[] = 'archive';
+ $this->tablesUsed[] = 'page';
+ $this->tablesUsed[] = 'revision';
+ $this->tablesUsed[] = 'comment';
+
+ $this->tablesUsed += $this->getMcrTablesToReset();
+
+ $this->setMwGlobals(
+ 'wgMultiContentRevisionSchemaMigrationStage',
+ $this->getMcrMigrationStage()
+ );
+
+ $this->setMwGlobals(
+ 'wgContentHandlerUseDB',
+ $this->getContentHandlerUseDB()
+ );
+
+ $this->overrideMwServices();
+ }
+
+ /**
+ * @return LoadBalancer|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getLoadBalancerMock( array $server ) {
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->setMethods( [ 'reallyOpenConnection' ] )
+ ->setConstructorArgs( [ [ 'servers' => [ $server ] ] ] )
+ ->getMock();
+
+ $lb->method( 'reallyOpenConnection' )->willReturnCallback(
+ function ( array $server, $dbNameOverride ) {
+ return $this->getDatabaseMock( $server );
+ }
+ );
+
+ return $lb;
+ }
+
+ /**
+ * @return Database|PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getDatabaseMock( array $params ) {
+ $db = $this->getMockBuilder( DatabaseSqlite::class )
+ ->setMethods( [ 'select', 'doQuery', 'open', 'closeConnection', 'isOpen' ] )
+ ->setConstructorArgs( [ $params ] )
+ ->getMock();
+
+ $db->method( 'select' )->willReturn( new FakeResultWrapper( [] ) );
+ $db->method( 'isOpen' )->willReturn( true );
+
+ return $db;
+ }
+
+ public function provideDomainCheck() {
+ yield [ false, 'test', '' ];
+ yield [ 'test', 'test', '' ];
+
+ yield [ false, 'test', 'foo_' ];
+ yield [ 'test-foo_', 'test', 'foo_' ];
+
+ yield [ false, 'dash-test', '' ];
+ yield [ 'dash-test', 'dash-test', '' ];
+
+ yield [ false, 'underscore_test', 'foo_' ];
+ yield [ 'underscore_test-foo_', 'underscore_test', 'foo_' ];
+ }
+
+ /**
+ * @dataProvider provideDomainCheck
+ * @covers \MediaWiki\Storage\RevisionStore::checkDatabaseWikiId
+ */
+ public function testDomainCheck( $wikiId, $dbName, $dbPrefix ) {
+ $this->setMwGlobals(
+ [
+ 'wgDBname' => $dbName,
+ 'wgDBprefix' => $dbPrefix,
+ ]
+ );
+
+ $loadBalancer = $this->getLoadBalancerMock(
+ [
+ 'host' => '*dummy*',
+ 'dbDirectory' => '*dummy*',
+ 'user' => 'test',
+ 'password' => 'test',
+ 'flags' => 0,
+ 'variables' => [],
+ 'schema' => '',
+ 'cliMode' => true,
+ 'agent' => '',
+ 'load' => 100,
+ 'profiler' => null,
+ 'trxProfiler' => new TransactionProfiler(),
+ 'connLogger' => new \Psr\Log\NullLogger(),
+ 'queryLogger' => new \Psr\Log\NullLogger(),
+ 'errorLogger' => function () {
+ },
+ 'deprecationLogger' => function () {
+ },
+ 'type' => 'test',
+ 'dbname' => $dbName,
+ 'tablePrefix' => $dbPrefix,
+ ]
+ );
+ $db = $loadBalancer->getConnection( DB_REPLICA );
+
+ /** @var SqlBlobStore $blobStore */
+ $blobStore = $this->getMockBuilder( SqlBlobStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $store = new RevisionStore(
+ $loadBalancer,
+ $blobStore,
+ new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ),
+ MediaWikiServices::getInstance()->getCommentStore(),
+ MediaWikiServices::getInstance()->getActorMigration(),
+ $wikiId
+ );
+
+ $count = $store->countRevisionsByPageId( $db, 0 );
+
+ // Dummy check to make PhpUnit happy. We are really only interested in
+ // countRevisionsByPageId not failing due to the DB domain check.
+ $this->assertSame( 0, $count );
+ }
+
+ private function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
+ $this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
+ $this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
+ $this->assertEquals( $l1->getFragment(), $l2->getFragment() );
+ $this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
+ }
+
+ private function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
+ $this->assertEquals( $r1->getUser()->getName(), $r2->getUser()->getName() );
+ $this->assertEquals( $r1->getUser()->getId(), $r2->getUser()->getId() );
+ $this->assertEquals( $r1->getComment(), $r2->getComment() );
+ $this->assertEquals( $r1->getPageAsLinkTarget(), $r2->getPageAsLinkTarget() );
+ $this->assertEquals( $r1->getTimestamp(), $r2->getTimestamp() );
+ $this->assertEquals( $r1->getVisibility(), $r2->getVisibility() );
+ $this->assertEquals( $r1->getSha1(), $r2->getSha1() );
+ $this->assertEquals( $r1->getParentId(), $r2->getParentId() );
+ $this->assertEquals( $r1->getSize(), $r2->getSize() );
+ $this->assertEquals( $r1->getPageId(), $r2->getPageId() );
+ $this->assertEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
+ $this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
+ $this->assertEquals( $r1->isMinor(), $r2->isMinor() );
+ foreach ( $r1->getSlotRoles() as $role ) {
+ $this->assertSlotRecordsEqual( $r1->getSlot( $role ), $r2->getSlot( $role ) );
+ $this->assertTrue( $r1->getContent( $role )->equals( $r2->getContent( $role ) ) );
+ }
+ foreach ( [
+ RevisionRecord::DELETED_TEXT,
+ RevisionRecord::DELETED_COMMENT,
+ RevisionRecord::DELETED_USER,
+ RevisionRecord::DELETED_RESTRICTED,
+ ] as $field ) {
+ $this->assertEquals( $r1->isDeleted( $field ), $r2->isDeleted( $field ) );
+ }
+ }
+
+ private function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
+ $this->assertSame( $s1->getRole(), $s2->getRole() );
+ $this->assertSame( $s1->getModel(), $s2->getModel() );
+ $this->assertSame( $s1->getFormat(), $s2->getFormat() );
+ $this->assertSame( $s1->getSha1(), $s2->getSha1() );
+ $this->assertSame( $s1->getSize(), $s2->getSize() );
+ $this->assertTrue( $s1->getContent()->equals( $s2->getContent() ) );
+
+ $s1->hasRevision() ? $this->assertSame( $s1->getRevision(), $s2->getRevision() ) : null;
+ $s1->hasAddress() ? $this->assertSame( $s1->hasAddress(), $s2->hasAddress() ) : null;
+ }
+
+ private function assertRevisionCompleteness( RevisionRecord $r ) {
+ foreach ( $r->getSlotRoles() as $role ) {
+ $this->assertSlotCompleteness( $r, $r->getSlot( $role ) );
+ }
+ }
+
+ private function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
+ $this->assertTrue( $slot->hasAddress() );
+ $this->assertSame( $r->getId(), $slot->getRevision() );
+ }
+
+ /**
+ * @param mixed[] $details
+ *
+ * @return RevisionRecord
+ */
+ private function getRevisionRecordFromDetailsArray( $title, $details = [] ) {
+ // Convert some values that can't be provided by dataProviders
+ $page = WikiPage::factory( $title );
+ if ( isset( $details['user'] ) && $details['user'] === true ) {
+ $details['user'] = $this->getTestUser()->getUser();
+ }
+ if ( isset( $details['page'] ) && $details['page'] === true ) {
+ $details['page'] = $page->getId();
+ }
+ if ( isset( $details['parent'] ) && $details['parent'] === true ) {
+ $details['parent'] = $page->getLatest();
+ }
+
+ // Create the RevisionRecord with any available data
+ $rev = new MutableRevisionRecord( $title );
+ isset( $details['slot'] ) ? $rev->setSlot( $details['slot'] ) : null;
+ isset( $details['parent'] ) ? $rev->setParentId( $details['parent'] ) : null;
+ isset( $details['page'] ) ? $rev->setPageId( $details['page'] ) : null;
+ isset( $details['size'] ) ? $rev->setSize( $details['size'] ) : null;
+ isset( $details['sha1'] ) ? $rev->setSha1( $details['sha1'] ) : null;
+ isset( $details['comment'] ) ? $rev->setComment( $details['comment'] ) : null;
+ isset( $details['timestamp'] ) ? $rev->setTimestamp( $details['timestamp'] ) : null;
+ isset( $details['minor'] ) ? $rev->setMinorEdit( $details['minor'] ) : null;
+ isset( $details['user'] ) ? $rev->setUser( $details['user'] ) : null;
+ isset( $details['visibility'] ) ? $rev->setVisibility( $details['visibility'] ) : null;
+ isset( $details['id'] ) ? $rev->setId( $details['id'] ) : null;
+
+ return $rev;
+ }
+
+ public function provideInsertRevisionOn_successes() {
+ yield 'Bare minimum revision insertion' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ ];
+ yield 'Detailed revision insertion' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'page' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ 'minor' => true,
+ 'visibility' => RevisionRecord::DELETED_RESTRICTED,
+ ],
+ ];
+ }
+
+ private function getRandomCommentStoreComment() {
+ return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) );
+ }
+
+ /**
+ * @dataProvider provideInsertRevisionOn_successes
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_successes(
+ Title $title,
+ array $revDetails = []
+ ) {
+ $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+
+ $this->overrideMwServices();
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
+
+ $this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() );
+ $this->assertRevisionRecordsEqual( $rev, $return );
+ $this->assertRevisionCompleteness( $return );
+ $this->assertRevisionExistsInDatabase( $return );
+ }
+
+ protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
+ $this->assertSelect(
+ 'revision', [ 'count(*)' ], [ 'rev_id' => $rev->getId() ], [ [ '1' ] ]
+ );
+ }
+
+ /**
+ * @param SlotRecord $a
+ * @param SlotRecord $b
+ */
+ protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) {
+ // Assert that the same blob address has been used.
+ $this->assertSame( $a->getAddress(), $b->getAddress() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_blobAddressExists() {
+ $title = Title::newFromText( 'UTPage' );
+ $revDetails = [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'parent' => true,
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ];
+
+ $this->overrideMwServices();
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ // Insert the first revision
+ $revOne = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+ $firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_MASTER ) );
+ $this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
+ $this->assertRevisionRecordsEqual( $revOne, $firstReturn );
+
+ // Insert a second revision inheriting the same blob address
+ $revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( 'main' ) );
+ $revTwo = $this->getRevisionRecordFromDetailsArray( $title, $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' );
+
+ $this->assertSameSlotContent( $firstMainSlot, $secondMainSlot );
+
+ // And that different revisions have been created.
+ $this->assertNotSame( $firstReturn->getId(), $secondReturn->getId() );
+
+ // Make sure the slot rows reference the correct revision
+ $this->assertSame( $firstReturn->getId(), $firstMainSlot->getRevision() );
+ $this->assertSame( $secondReturn->getId(), $secondMainSlot->getRevision() );
+ }
+
+ public function provideInsertRevisionOn_failures() {
+ yield 'no slot' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new InvalidArgumentException( 'At least one slot needs to be defined!' )
+ ];
+ yield 'slot that is not main slot' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'lalala', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new InvalidArgumentException( 'Only the main slot is supported for now!' )
+ ];
+ yield 'no timestamp' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'user' => true,
+ ],
+ new IncompleteRevisionException( 'timestamp field must not be NULL!' )
+ ];
+ yield 'no comment' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'timestamp' => '20171117010101',
+ 'user' => true,
+ ],
+ new IncompleteRevisionException( 'comment must not be NULL!' )
+ ];
+ yield 'no user' => [
+ Title::newFromText( 'UTPage' ),
+ [
+ 'slot' => SlotRecord::newUnsaved( 'main', new WikitextContent( 'Chicken' ) ),
+ 'comment' => $this->getRandomCommentStoreComment(),
+ 'timestamp' => '20171117010101',
+ ],
+ new IncompleteRevisionException( 'user must not be NULL!' )
+ ];
+ }
+
+ /**
+ * @dataProvider provideInsertRevisionOn_failures
+ * @covers \MediaWiki\Storage\RevisionStore::insertRevisionOn
+ */
+ public function testInsertRevisionOn_failures(
+ Title $title,
+ array $revDetails = [],
+ Exception $exception
+ ) {
+ $rev = $this->getRevisionRecordFromDetailsArray( $title, $revDetails );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $this->setExpectedException(
+ get_class( $exception ),
+ $exception->getMessage(),
+ $exception->getCode()
+ );
+ $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
+ }
+
+ public function provideNewNullRevision() {
+ yield [
+ Title::newFromText( 'UTPage_notAutoCreated' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ),
+ true,
+ ];
+ yield [
+ Title::newFromText( 'UTPage_notAutoCreated' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ),
+ false,
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewNullRevision
+ * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
+ */
+ public function testNewNullRevision( Title $title, $comment, $minor ) {
+ $this->overrideMwServices();
+
+ $page = WikiPage::factory( $title );
+ $status = $page->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false
+ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
+
+ $parent = $store->getRevisionById( $rev->getId() );
+ $record = $store->newNullRevision(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $comment,
+ $minor,
+ $user
+ );
+
+ $this->assertEquals( $title->getNamespace(), $record->getPageAsLinkTarget()->getNamespace() );
+ $this->assertEquals( $title->getDBkey(), $record->getPageAsLinkTarget()->getDBkey() );
+ $this->assertEquals( $comment, $record->getComment() );
+ $this->assertEquals( $minor, $record->isMinor() );
+ $this->assertEquals( $user->getName(), $record->getUser()->getName() );
+ $this->assertEquals( $parent->getId(), $record->getParentId() );
+
+ $parentSlot = $parent->getSlot( 'main' );
+ $slot = $record->getSlot( 'main' );
+
+ $this->assertTrue( $slot->isInherited(), 'isInherited' );
+ $this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' );
+ $this->assertSameSlotContent( $parentSlot, $slot );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newNullRevision
+ */
+ public function testNewNullRevision_nonExistingTitle() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newNullRevision(
+ wfGetDB( DB_MASTER ),
+ Title::newFromText( __METHOD__ . '.iDontExist!' ),
+ CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment' ),
+ false,
+ TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser()
+ );
+ $this->assertNull( $record );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
+ */
+ public function testGetRcIdIfUnpatrolled_returnsRecentChangesId() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $status = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionRecord = $store->getRevisionById( $rev->getId() );
+ $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
+
+ $this->assertGreaterThan( 0, $result );
+ $this->assertSame(
+ $page->getRevision()->getRecentChange()->getAttribute( 'rc_id' ),
+ $result
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRcIdIfUnpatrolled
+ */
+ public function testGetRcIdIfUnpatrolled_returnsZeroIfPatrolled() {
+ // This assumes that sysops are auto patrolled
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $status = $page->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revisionRecord = $store->getRevisionById( $rev->getId() );
+ $result = $store->getRcIdIfUnpatrolled( $revisionRecord );
+
+ $this->assertSame( 0, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRecentChange
+ */
+ public function testGetRecentChange() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionById( $rev->getId() );
+ $recentChange = $store->getRecentChange( $revRecord );
+
+ $this->assertEquals( $rev->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
+ $this->assertEquals( $rev->getRecentChange(), $recentChange );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionById
+ */
+ public function testGetRevisionById() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionById( $rev->getId() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTitle
+ */
+ public function testGetRevisionByTitle() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByTitle( $page->getTitle() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByPageId
+ */
+ public function testGetRevisionByPageId() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByPageId( $page->getId() );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getRevisionByTimestamp
+ */
+ public function testGetRevisionByTimestamp() {
+ // Make sure there is 1 second between the last revision and the rev we create...
+ // Otherwise we might not get the correct revision and the test may fail...
+ // :(
+ sleep( 1 );
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $content = new WikitextContent( __METHOD__ );
+ $status = $page->doEditContent( $content, __METHOD__ );
+ /** @var Revision $rev */
+ $rev = $status->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $revRecord = $store->getRevisionByTimestamp(
+ $page->getTitle(),
+ $rev->getTimestamp()
+ );
+
+ $this->assertSame( $rev->getId(), $revRecord->getId() );
+ $this->assertTrue( $revRecord->getSlot( 'main' )->getContent()->equals( $content ) );
+ $this->assertSame( __METHOD__, $revRecord->getComment()->text );
+ }
+
+ protected function revisionToRow( Revision $rev ) {
+ $page = WikiPage::factory( $rev->getTitle() );
+
+ return (object)[
+ 'rev_id' => (string)$rev->getId(),
+ 'rev_page' => (string)$rev->getPage(),
+ 'rev_text_id' => (string)$rev->getTextId(),
+ 'rev_timestamp' => $this->db->timestamp( $rev->getTimestamp() ),
+ 'rev_user_text' => (string)$rev->getUserText(),
+ 'rev_user' => (string)$rev->getUser(),
+ 'rev_minor_edit' => $rev->isMinor() ? '1' : '0',
+ 'rev_deleted' => (string)$rev->getVisibility(),
+ 'rev_len' => (string)$rev->getSize(),
+ 'rev_parent_id' => (string)$rev->getParentId(),
+ 'rev_sha1' => (string)$rev->getSha1(),
+ 'rev_comment_text' => $rev->getComment(),
+ 'rev_comment_data' => null,
+ 'rev_comment_cid' => null,
+ 'rev_content_format' => $rev->getContentFormat(),
+ 'rev_content_model' => $rev->getContentModel(),
+ 'page_namespace' => (string)$page->getTitle()->getNamespace(),
+ 'page_title' => $page->getTitle()->getDBkey(),
+ 'page_id' => (string)$page->getId(),
+ 'page_latest' => (string)$page->getLatest(),
+ 'page_is_redirect' => $page->isRedirect() ? '1' : '0',
+ 'page_len' => (string)$page->getContent()->getSize(),
+ 'user_name' => (string)$rev->getUserText(),
+ ];
+ }
+
+ private function assertRevisionRecordMatchesRevision(
+ Revision $rev,
+ RevisionRecord $record
+ ) {
+ $this->assertSame( $rev->getId(), $record->getId() );
+ $this->assertSame( $rev->getPage(), $record->getPageId() );
+ $this->assertSame( $rev->getTimestamp(), $record->getTimestamp() );
+ $this->assertSame( $rev->getUserText(), $record->getUser()->getName() );
+ $this->assertSame( $rev->getUser(), $record->getUser()->getId() );
+ $this->assertSame( $rev->isMinor(), $record->isMinor() );
+ $this->assertSame( $rev->getVisibility(), $record->getVisibility() );
+ $this->assertSame( $rev->getSize(), $record->getSize() );
+ /**
+ * @note As of MW 1.31, the database schema allows the parent ID to be
+ * NULL to indicate that it is unknown.
+ */
+ $expectedParent = $rev->getParentId();
+ if ( $expectedParent === null ) {
+ $expectedParent = 0;
+ }
+ $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->assertLinkTargetsEqual( $rev->getTitle(), $record->getPageAsLinkTarget() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_anonEdit() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'a-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__ . 'a'
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
+ $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
+ $this->overrideMwServices();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'a-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__. 'a'
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
+ */
+ public function testNewRevisionFromRow_userEdit() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $text = __METHOD__ . 'b-ä';
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( $text ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->newRevisionFromRow(
+ $this->revisionToRow( $rev ),
+ [],
+ $page->getTitle()
+ );
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ $this->assertSame( $text, $rev->getContent()->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ */
+ public function testNewRevisionFromArchiveRow() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $title = Title::newFromText( __METHOD__ );
+ $text = __METHOD__ . '-bä';
+ $page = WikiPage::factory( $title );
+ /** @var Revision $orig */
+ $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
+ ->value['revision'];
+ $page->doDeleteArticle( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+ $arQuery = $store->getArchiveQueryInfo();
+ $res = $db->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+ $record = $store->newRevisionFromArchiveRow( $row );
+
+ $this->assertRevisionRecordMatchesRevision( $orig, $record );
+ $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromArchiveRow
+ */
+ public function testNewRevisionFromArchiveRow_legacyEncoding() {
+ $this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
+ $this->overrideMwServices();
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $title = Title::newFromText( __METHOD__ );
+ $text = __METHOD__ . '-bä';
+ $page = WikiPage::factory( $title );
+ /** @var Revision $orig */
+ $orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
+ ->value['revision'];
+ $page->doDeleteArticle( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+ $arQuery = $store->getArchiveQueryInfo();
+ $res = $db->select(
+ $arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
+ __METHOD__, [], $arQuery['joins']
+ );
+ $this->assertTrue( is_object( $res ), 'query failed' );
+
+ $row = $res->fetchObject();
+ $res->free();
+ $record = $store->newRevisionFromArchiveRow( $row );
+
+ $this->assertRevisionRecordMatchesRevision( $orig, $record );
+ $this->assertSame( $text, $record->getContent( 'main' )->serialize() );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromId
+ */
+ public function testLoadRevisionFromId() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromId( wfGetDB( DB_MASTER ), $rev->getId() );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromPageId
+ */
+ public function testLoadRevisionFromPageId() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTitle
+ */
+ public function testLoadRevisionFromTitle() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
+ $this->assertRevisionRecordMatchesRevision( $rev, $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::loadRevisionFromTimestamp
+ */
+ public function testLoadRevisionFromTimestamp() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+ // Sleep to ensure different timestamps... )(evil)
+ sleep( 1 );
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertNull(
+ $store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
+ );
+ $this->assertSame(
+ $revOne->getId(),
+ $store->loadRevisionFromTimestamp(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $revOne->getTimestamp()
+ )->getId()
+ );
+ $this->assertSame(
+ $revTwo->getId(),
+ $store->loadRevisionFromTimestamp(
+ wfGetDB( DB_MASTER ),
+ $title,
+ $revTwo->getTimestamp()
+ )->getId()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::listRevisionSizes
+ */
+ public function testGetParentLengths() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertSame(
+ [
+ $revOne->getId() => strlen( __METHOD__ ),
+ ],
+ $store->listRevisionSizes(
+ wfGetDB( DB_MASTER ),
+ [ $revOne->getId() ]
+ )
+ );
+ $this->assertSame(
+ [
+ $revOne->getId() => strlen( __METHOD__ ),
+ $revTwo->getId() => strlen( __METHOD__ ) + 1,
+ ],
+ $store->listRevisionSizes(
+ wfGetDB( DB_MASTER ),
+ [ $revOne->getId(), $revTwo->getId() ]
+ )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getPreviousRevision
+ */
+ public function testGetPreviousRevision() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertNull(
+ $store->getPreviousRevision( $store->getRevisionById( $revOne->getId() ) )
+ );
+ $this->assertSame(
+ $revOne->getId(),
+ $store->getPreviousRevision( $store->getRevisionById( $revTwo->getId() ) )->getId()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getNextRevision
+ */
+ public function testGetNextRevision() {
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+ /** @var Revision $revOne */
+ $revOne = $page->doEditContent(
+ new WikitextContent( __METHOD__ ), __METHOD__
+ )->value['revision'];
+ /** @var Revision $revTwo */
+ $revTwo = $page->doEditContent(
+ new WikitextContent( __METHOD__ . '2' ), __METHOD__
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $this->assertSame(
+ $revTwo->getId(),
+ $store->getNextRevision( $store->getRevisionById( $revOne->getId() ) )->getId()
+ );
+ $this->assertNull(
+ $store->getNextRevision( $store->getRevisionById( $revTwo->getId() ) )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
+ */
+ public function testGetTimestampFromId_found() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->getTimestampFromId(
+ $page->getTitle(),
+ $rev->getId()
+ );
+
+ $this->assertSame( $rev->getTimestamp(), $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getTimestampFromId
+ */
+ public function testGetTimestampFromId_notFound() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
+ ->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->getTimestampFromId(
+ $page->getTitle(),
+ $rev->getId() + 1
+ );
+
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByPageId
+ */
+ public function testCountRevisionsByPageId() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+
+ $this->assertSame(
+ 0,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ $page->doEditContent( new WikitextContent( 'a' ), 'a' );
+ $this->assertSame(
+ 1,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ $page->doEditContent( new WikitextContent( 'b' ), 'b' );
+ $this->assertSame(
+ 2,
+ $store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::countRevisionsByTitle
+ */
+ public function testCountRevisionsByTitle() {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
+
+ $this->assertSame(
+ 0,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ $page->doEditContent( new WikitextContent( 'a' ), 'a' );
+ $this->assertSame(
+ 1,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ $page->doEditContent( new WikitextContent( 'b' ), 'b' );
+ $this->assertSame(
+ 2,
+ $store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
+ */
+ public function testUserWasLastToEdit_false() {
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->userWasLastToEdit(
+ wfGetDB( DB_MASTER ),
+ $page->getId(),
+ $sysop->getId(),
+ '20160101010101'
+ );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::userWasLastToEdit
+ */
+ public function testUserWasLastToEdit_true() {
+ $startTime = wfTimestampNow();
+ $sysop = $this->getTestSysop()->getUser();
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ $page->doEditContent(
+ new WikitextContent( __METHOD__ ),
+ __METHOD__,
+ 0,
+ false,
+ $sysop
+ );
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $result = $store->userWasLastToEdit(
+ wfGetDB( DB_MASTER ),
+ $page->getId(),
+ $sysop->getId(),
+ $startTime
+ );
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Storage\RevisionStore::getKnownCurrentRevision
+ */
+ public function testGetKnownCurrentRevision() {
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ /** @var Revision $rev */
+ $rev = $page->doEditContent(
+ new WikitextContent( __METHOD__ . 'b' ),
+ __METHOD__ . 'b',
+ 0,
+ false,
+ $this->getTestUser()->getUser()
+ )->value['revision'];
+
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+ $record = $store->getKnownCurrentRevision(
+ $page->getTitle(),
+ $rev->getId()
+ );
+
+ $this->assertRevisionRecordMatchesRevision( $rev, $record );
+ }
+
+ public function provideNewMutableRevisionFromArray() {
+ yield 'Basic array, with page & id' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ yield 'Basic array, content object' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content' => new WikitextContent( 'Some Content' ),
+ ]
+ ];
+ yield 'Basic array, serialized text' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
+ ]
+ ];
+ yield 'Basic array, serialized text, utf-8 flags' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
+ 'flags' => 'utf-8',
+ ]
+ ];
+ yield 'Basic array, with title' => [
+ [
+ 'title' => Title::newFromText( 'SomeText' ),
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.2',
+ 'user' => 0,
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ yield 'Basic array, no user field' => [
+ [
+ 'id' => 2,
+ 'page' => 1,
+ 'text_id' => 2,
+ 'timestamp' => '20171017114835',
+ 'user_text' => '111.0.1.3',
+ 'minor_edit' => false,
+ 'deleted' => 0,
+ 'len' => 46,
+ 'parent_id' => 1,
+ 'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
+ 'comment' => 'Goat Comment!',
+ 'content_format' => 'text/x-wiki',
+ 'content_model' => 'wikitext',
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider provideNewMutableRevisionFromArray
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testNewMutableRevisionFromArray( array $array ) {
+ $store = MediaWikiServices::getInstance()->getRevisionStore();
+
+ $result = $store->newMutableRevisionFromArray( $array );
+
+ if ( isset( $array['id'] ) ) {
+ $this->assertSame( $array['id'], $result->getId() );
+ }
+ if ( isset( $array['page'] ) ) {
+ $this->assertSame( $array['page'], $result->getPageId() );
+ }
+ $this->assertSame( $array['timestamp'], $result->getTimestamp() );
+ $this->assertSame( $array['user_text'], $result->getUser()->getName() );
+ if ( isset( $array['user'] ) ) {
+ $this->assertSame( $array['user'], $result->getUser()->getId() );
+ }
+ $this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
+ $this->assertSame( $array['deleted'], $result->getVisibility() );
+ $this->assertSame( $array['len'], $result->getSize() );
+ $this->assertSame( $array['parent_id'], $result->getParentId() );
+ $this->assertSame( $array['sha1'], $result->getSha1() );
+ $this->assertSame( $array['comment'], $result->getComment()->text );
+ if ( isset( $array['content'] ) ) {
+ $this->assertTrue(
+ $result->getSlot( 'main' )->getContent()->equals( $array['content'] )
+ );
+ } elseif ( isset( $array['text'] ) ) {
+ $this->assertSame( $array['text'], $result->getSlot( 'main' )->getContent()->serialize() );
+ } else {
+ $this->assertSame(
+ $array['content_format'],
+ $result->getSlot( 'main' )->getContent()->getDefaultFormat()
+ );
+ $this->assertSame( $array['content_model'], $result->getSlot( 'main' )->getModel() );
+ }
+ }
+
+ /**
+ * @dataProvider provideNewMutableRevisionFromArray
+ * @covers \MediaWiki\Storage\RevisionStore::newMutableRevisionFromArray
+ */
+ public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
+ $cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
+ $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $blobStore = new SqlBlobStore( $lb, $cache );
+ $blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
+
+ $factory = $this->getMockBuilder( BlobStoreFactory::class )
+ ->setMethods( [ 'newBlobStore', 'newSqlBlobStore' ] )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $factory->expects( $this->any() )
+ ->method( 'newBlobStore' )
+ ->willReturn( $blobStore );
+ $factory->expects( $this->any() )
+ ->method( 'newSqlBlobStore' )
+ ->willReturn( $blobStore );
+
+ $this->setService( 'BlobStoreFactory', $factory );
+
+ $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']
+ );
+ }
+
+ /**
+ * 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 );
+ }
+
+}
$this->assertEquals( $expected, $store->getQueryInfo( $options ) );
}
- private function getDefaultArchiveFields() {
- return [
- 'ar_id',
- 'ar_page_id',
- 'ar_namespace',
- 'ar_title',
- 'ar_rev_id',
- 'ar_text_id',
- 'ar_timestamp',
- 'ar_minor_edit',
- 'ar_deleted',
- 'ar_len',
- 'ar_parent_id',
- 'ar_sha1',
- ];
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
- */
- public function testGetArchiveQueryInfo_contentHandlerDb() {
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
- $this->overrideMwServices();
- $store = $this->getRevisionStore();
- $store->setContentHandlerUseDB( true );
- $this->assertEquals(
- [
- 'tables' => [
- 'archive'
- ],
- 'fields' => array_merge(
- $this->getDefaultArchiveFields(),
- [
- 'ar_comment_text' => 'ar_comment',
- 'ar_comment_data' => 'NULL',
- 'ar_comment_cid' => 'NULL',
- 'ar_user_text' => 'ar_user_text',
- 'ar_user' => 'ar_user',
- 'ar_actor' => 'NULL',
- 'ar_content_format',
- 'ar_content_model',
- ]
- ),
- 'joins' => [],
- ],
- $store->getArchiveQueryInfo()
- );
- }
-
- /**
- * @covers \MediaWiki\Storage\RevisionStore::getArchiveQueryInfo
- */
- public function testGetArchiveQueryInfo_noContentHandlerDb() {
- $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
- $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
- $this->overrideMwServices();
- $store = $this->getRevisionStore();
- $store->setContentHandlerUseDB( false );
- $this->assertEquals(
- [
- 'tables' => [
- 'archive'
- ],
- 'fields' => array_merge(
- $this->getDefaultArchiveFields(),
- [
- 'ar_comment_text' => 'ar_comment',
- 'ar_comment_data' => 'NULL',
- 'ar_comment_cid' => 'NULL',
- 'ar_user_text' => 'ar_user_text',
- 'ar_user' => 'ar_user',
- 'ar_actor' => 'NULL',
- ]
- ),
- 'joins' => [],
- ],
- $store->getArchiveQueryInfo()
- );
- }
-
public function testGetTitle_successFromPageId() {
$mockLoadBalancer = $this->getMockLoadBalancer();
// Title calls wfGetDB() so we have to set the main service
--- /dev/null
+ALTER TABLE /*_*/revision ADD rev_text_id INTEGER DEFAULT 0;
+ALTER TABLE /*_*/revision ADD rev_content_model VARBINARY(32) DEFAULT NULL;
+ALTER TABLE /*_*/revision ADD rev_content_format VARBINARY(64) DEFAULT NULL;
--- /dev/null
+DROP TABLE /*_*/slots;
+DROP TABLE /*_*/content;
+DROP TABLE /*_*/content_models;
+DROP TABLE /*_*/slot_roles;
+++ /dev/null
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * @group medium
- */
-class WikiPageContentHandlerDbTest extends WikiPageDbTestBase {
-
- protected function getContentHandlerUseDB() {
- return true;
- }
-
- /**
- * @covers WikiPage::getContentModel
- */
- public function testGetContentModel() {
- $page = $this->createPage(
- __METHOD__,
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
- }
-
- /**
- * @covers WikiPage::getContentHandler
- */
- public function testGetContentHandler() {
- $page = $this->createPage(
- __METHOD__,
- "some text",
- CONTENT_MODEL_JAVASCRIPT
- );
-
- $page = new WikiPage( $page->getTitle() );
- $this->assertEquals( JavaScriptContentHandler::class, get_class( $page->getContentHandler() ) );
- }
-
-}
'iwlinks' ] );
}
+ /**
+ * @return int
+ */
+ abstract protected function getMcrMigrationStage();
+
+ /**
+ * @return string[]
+ */
+ abstract protected function getMcrTablesToReset();
+
protected function setUp() {
parent::setUp();
+
+ $this->tablesUsed += $this->getMcrTablesToReset();
+
$this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
+ $this->setMwGlobals(
+ 'wgMultiContentRevisionSchemaMigrationStage',
+ $this->getMcrMigrationStage()
+ );
$this->pagesToDelete = [];
+
+ $this->overrideMwServices();
}
protected function tearDown() {
+++ /dev/null
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * @group medium
- */
-class WikiPageNoContentHandlerDbTest extends WikiPageDbTestBase {
-
- protected function getContentHandlerUseDB() {
- return false;
- }
-
-}
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\PreMcrSchemaOverride;
+
+/**
+ * Tests WikiPage against the pre-MCR, pre ContentHandler DB schema.
+ *
+ * @covers WikiPage
+ *
+ * @group WikiPage
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPageNoContentModelDbTest extends WikiPageDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return false;
+ }
+
+}
--- /dev/null
+<?php
+use MediaWiki\Tests\Storage\PreMcrSchemaOverride;
+
+/**
+ * Tests WikiPage against the pre-MCR DB schema.
+ *
+ * @covers WikiPage
+ *
+ * @group WikiPage
+ * @group Storage
+ * @group ContentHandler
+ * @group Database
+ * @group medium
+ */
+class WikiPagePreMcrDbTest extends WikiPageDbTestBase {
+
+ use PreMcrSchemaOverride;
+
+ protected function getContentHandlerUseDB() {
+ return true;
+ }
+
+ /**
+ * @covers WikiPage::getContentModel
+ */
+ public function testGetContentModel() {
+ $page = $this->createPage(
+ __METHOD__,
+ "some text",
+ CONTENT_MODEL_JAVASCRIPT
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() );
+ }
+
+ /**
+ * @covers WikiPage::getContentHandler
+ */
+ public function testGetContentHandler() {
+ $page = $this->createPage(
+ __METHOD__,
+ "some text",
+ CONTENT_MODEL_JAVASCRIPT
+ );
+
+ $page = new WikiPage( $page->getTitle() );
+ $this->assertEquals( JavaScriptContentHandler::class, get_class( $page->getContentHandler() ) );
+ }
+
+}