2017-11-15 12:02:40 +00:00
|
|
|
<?php
|
|
|
|
|
|
2018-09-20 17:29:04 +00:00
|
|
|
namespace MediaWiki\Tests\Revision;
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
use CommentStoreComment;
|
2018-04-17 07:49:20 +00:00
|
|
|
use Content;
|
2019-09-18 00:25:43 +00:00
|
|
|
use ContentHandler;
|
2017-11-15 12:02:40 +00:00
|
|
|
use Exception;
|
2020-07-19 19:50:37 +00:00
|
|
|
use FallbackContent;
|
2018-01-03 14:42:13 +00:00
|
|
|
use HashBagOStuff;
|
2019-09-18 00:25:43 +00:00
|
|
|
use IDBAccessObject;
|
2017-11-15 12:02:40 +00:00
|
|
|
use InvalidArgumentException;
|
2020-07-19 19:50:37 +00:00
|
|
|
use MediaWiki\Content\IContentHandlerFactory;
|
2017-11-15 12:02:40 +00:00
|
|
|
use MediaWiki\Linker\LinkTarget;
|
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2021-02-08 16:33:25 +00:00
|
|
|
use MediaWiki\Page\PageIdentity;
|
2021-02-24 05:12:55 +00:00
|
|
|
use MediaWiki\Page\PageIdentityValue;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\IncompleteRevisionException;
|
|
|
|
|
use MediaWiki\Revision\MutableRevisionRecord;
|
2021-03-17 22:13:35 +00:00
|
|
|
use MediaWiki\Revision\RevisionAccessException;
|
2018-11-23 12:30:35 +00:00
|
|
|
use MediaWiki\Revision\RevisionArchiveRecord;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2018-11-23 12:30:35 +00:00
|
|
|
use MediaWiki\Revision\RevisionSlots;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\RevisionStore;
|
2020-01-10 00:00:51 +00:00
|
|
|
use MediaWiki\Revision\RevisionStoreRecord;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\SlotRecord;
|
2021-06-24 15:25:39 +00:00
|
|
|
use MediaWiki\Storage\BlobStore;
|
2018-01-03 14:42:13 +00:00
|
|
|
use MediaWiki\Storage\SqlBlobStore;
|
2018-05-28 19:19:11 +00:00
|
|
|
use MediaWiki\User\UserIdentityValue;
|
2020-06-30 15:09:24 +00:00
|
|
|
use MediaWikiIntegrationTestCase;
|
2020-04-02 20:12:09 +00:00
|
|
|
use MWTimestamp;
|
2019-10-06 04:54:59 +00:00
|
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
2019-07-14 22:43:26 +00:00
|
|
|
use Psr\Log\NullLogger;
|
2021-06-24 15:25:39 +00:00
|
|
|
use StatusValue;
|
2017-11-15 12:02:40 +00:00
|
|
|
use TestUserRegistry;
|
2021-06-24 15:25:39 +00:00
|
|
|
use TextContent;
|
2017-11-15 12:02:40 +00:00
|
|
|
use Title;
|
2021-03-15 18:09:19 +00:00
|
|
|
use TitleFactory;
|
2019-10-24 19:08:33 +00:00
|
|
|
use User;
|
2018-01-03 14:42:13 +00:00
|
|
|
use WANObjectCache;
|
2020-01-06 13:20:16 +00:00
|
|
|
use Wikimedia\Assert\PreconditionException;
|
2018-01-03 14:42:13 +00:00
|
|
|
use Wikimedia\Rdbms\Database;
|
2018-08-14 23:44:41 +00:00
|
|
|
use Wikimedia\Rdbms\DatabaseDomain;
|
2018-01-03 14:42:13 +00:00
|
|
|
use Wikimedia\Rdbms\DatabaseSqlite;
|
|
|
|
|
use Wikimedia\Rdbms\FakeResultWrapper;
|
2020-03-28 19:44:25 +00:00
|
|
|
use Wikimedia\Rdbms\ILoadBalancer;
|
2018-01-03 14:42:13 +00:00
|
|
|
use Wikimedia\Rdbms\LoadBalancer;
|
|
|
|
|
use Wikimedia\Rdbms\TransactionProfiler;
|
2021-06-24 15:25:39 +00:00
|
|
|
use Wikimedia\TestingAccessWrapper;
|
2017-11-15 12:02:40 +00:00
|
|
|
use WikiPage;
|
|
|
|
|
use WikitextContent;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @group Database
|
2018-05-14 11:57:43 +00:00
|
|
|
* @group RevisionStore
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2021-06-24 15:25:39 +00:00
|
|
|
class RevisionStoreDbTest extends MediaWikiIntegrationTestCase {
|
2018-05-14 11:57:43 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
/**
|
|
|
|
|
* @var Title
|
|
|
|
|
*/
|
|
|
|
|
private $testPageTitle;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var WikiPage
|
|
|
|
|
*/
|
|
|
|
|
private $testPage;
|
|
|
|
|
|
2021-07-22 03:11:47 +00:00
|
|
|
protected function setUp(): void {
|
2018-01-29 17:08:58 +00:00
|
|
|
parent::setUp();
|
|
|
|
|
$this->tablesUsed[] = 'archive';
|
|
|
|
|
$this->tablesUsed[] = 'page';
|
|
|
|
|
$this->tablesUsed[] = 'revision';
|
|
|
|
|
$this->tablesUsed[] = 'comment';
|
2019-11-26 20:59:00 +00:00
|
|
|
$this->tablesUsed[] = 'actor';
|
2019-12-04 14:41:18 +00:00
|
|
|
$this->tablesUsed[] = 'recentchanges';
|
2019-12-17 16:20:32 +00:00
|
|
|
$this->tablesUsed[] = 'content';
|
|
|
|
|
$this->tablesUsed[] = 'slots';
|
|
|
|
|
$this->tablesUsed[] = 'content_models';
|
|
|
|
|
$this->tablesUsed[] = 'slot_roles';
|
2018-06-12 16:36:34 +00:00
|
|
|
}
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
/**
|
|
|
|
|
* @return Title
|
|
|
|
|
*/
|
|
|
|
|
protected function getTestPageTitle() {
|
|
|
|
|
if ( $this->testPageTitle ) {
|
|
|
|
|
return $this->testPageTitle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->testPageTitle = Title::newFromText( 'UTPage-' . __CLASS__ );
|
|
|
|
|
return $this->testPageTitle;
|
|
|
|
|
}
|
2019-01-15 15:04:58 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
/**
|
2019-08-30 18:26:00 +00:00
|
|
|
* @param string|null $pageTitle whether to force-create a new page
|
2018-04-17 07:49:20 +00:00
|
|
|
* @return WikiPage
|
|
|
|
|
*/
|
2019-08-30 18:26:00 +00:00
|
|
|
protected function getTestPage( $pageTitle = null ) {
|
2020-01-09 23:48:34 +00:00
|
|
|
if ( $pageTitle === null && $this->testPage ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
return $this->testPage;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 23:48:34 +00:00
|
|
|
$title = $pageTitle === null ? $this->getTestPageTitle() : Title::newFromText( $pageTitle );
|
2019-08-30 18:26:00 +00:00
|
|
|
$page = WikiPage::factory( $title );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
if ( !$page->exists() ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
// Make sure we don't write to the live db.
|
2021-04-29 02:37:11 +00:00
|
|
|
$this->ensureMockDatabaseConnection( wfGetDB( DB_PRIMARY ) );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
|
|
|
|
$user = static::getTestSysop()->getUser();
|
|
|
|
|
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent(
|
2018-04-17 07:49:20 +00:00
|
|
|
new WikitextContent( 'UTContent-' . __CLASS__ ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$user,
|
2018-04-17 07:49:20 +00:00
|
|
|
'UTPageSummary-' . __CLASS__,
|
2021-06-24 08:42:19 +00:00
|
|
|
EDIT_NEW | EDIT_SUPPRESS_RC
|
2018-04-17 07:49:20 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 23:48:34 +00:00
|
|
|
if ( $pageTitle === null ) {
|
2019-08-30 18:26:00 +00:00
|
|
|
$this->testPage = $page;
|
|
|
|
|
}
|
|
|
|
|
return $page;
|
2018-04-17 07:49:20 +00:00
|
|
|
}
|
|
|
|
|
|
2018-01-03 14:42:13 +00:00
|
|
|
/**
|
2021-01-16 19:44:17 +00:00
|
|
|
* @param array $server
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return LoadBalancer|MockObject
|
2018-01-03 14:42:13 +00:00
|
|
|
*/
|
|
|
|
|
private function getLoadBalancerMock( array $server ) {
|
2018-08-14 23:44:41 +00:00
|
|
|
$domain = new DatabaseDomain( $server['dbname'], null, $server['tablePrefix'] );
|
|
|
|
|
|
2018-01-03 14:42:13 +00:00
|
|
|
$lb = $this->getMockBuilder( LoadBalancer::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'reallyOpenConnection' ] )
|
2018-08-14 23:44:41 +00:00
|
|
|
->setConstructorArgs( [
|
|
|
|
|
[ 'servers' => [ $server ], 'localDomain' => $domain ]
|
|
|
|
|
] )
|
2018-01-03 14:42:13 +00:00
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$lb->method( 'reallyOpenConnection' )->willReturnCallback(
|
2019-07-14 22:43:26 +00:00
|
|
|
function () use ( $server ) {
|
2018-01-03 14:42:13 +00:00
|
|
|
return $this->getDatabaseMock( $server );
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $lb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-01-16 19:44:17 +00:00
|
|
|
* @param array $params
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return Database|MockObject
|
2018-01-03 14:42:13 +00:00
|
|
|
*/
|
|
|
|
|
private function getDatabaseMock( array $params ) {
|
|
|
|
|
$db = $this->getMockBuilder( DatabaseSqlite::class )
|
2021-03-20 15:18:58 +00:00
|
|
|
->onlyMethods( [ 'select', 'doQuery', 'open', 'closeConnection', 'isOpen' ] )
|
2018-01-03 14:42:13 +00:00
|
|
|
->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', '' ];
|
2019-07-03 22:02:41 +00:00
|
|
|
yield [ 'dash?htest', 'dash-test', '' ];
|
2018-01-03 14:42:13 +00:00
|
|
|
|
|
|
|
|
yield [ false, 'underscore_test', 'foo_' ];
|
|
|
|
|
yield [ 'underscore_test-foo_', 'underscore_test', 'foo_' ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideDomainCheck
|
2019-07-04 07:46:39 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::checkDatabaseDomain
|
2018-01-03 14:42:13 +00:00
|
|
|
*/
|
2019-07-03 22:02:41 +00:00
|
|
|
public function testDomainCheck( $dbDomain, $dbName, $dbPrefix ) {
|
2018-01-03 14:42:13 +00:00
|
|
|
$this->setMwGlobals(
|
|
|
|
|
[
|
|
|
|
|
'wgDBname' => $dbName,
|
|
|
|
|
'wgDBprefix' => $dbPrefix,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$loadBalancer = $this->getLoadBalancerMock(
|
|
|
|
|
[
|
|
|
|
|
'host' => '*dummy*',
|
|
|
|
|
'dbDirectory' => '*dummy*',
|
|
|
|
|
'user' => 'test',
|
|
|
|
|
'password' => 'test',
|
|
|
|
|
'flags' => 0,
|
|
|
|
|
'variables' => [],
|
|
|
|
|
'schema' => '',
|
|
|
|
|
'cliMode' => true,
|
2019-07-14 22:43:26 +00:00
|
|
|
'topologyRole' => Database::ROLE_STREAMING_MASTER,
|
|
|
|
|
'topologicalMaster' => null,
|
2018-01-03 14:42:13 +00:00
|
|
|
'agent' => '',
|
2021-03-10 01:38:23 +00:00
|
|
|
'serverName' => '*dummy*',
|
2018-01-03 14:42:13 +00:00
|
|
|
'load' => 100,
|
2019-07-14 22:43:26 +00:00
|
|
|
'srvCache' => new HashBagOStuff(),
|
2018-01-03 14:42:13 +00:00
|
|
|
'profiler' => null,
|
|
|
|
|
'trxProfiler' => new TransactionProfiler(),
|
2019-07-14 22:43:26 +00:00
|
|
|
'connLogger' => new NullLogger(),
|
|
|
|
|
'queryLogger' => new NullLogger(),
|
2019-10-11 16:57:11 +00:00
|
|
|
'replLogger' => new NullLogger(),
|
2021-02-06 19:30:20 +00:00
|
|
|
'errorLogger' => static function () {
|
2018-04-05 16:13:08 +00:00
|
|
|
},
|
2021-02-06 19:30:20 +00:00
|
|
|
'deprecationLogger' => static function () {
|
2018-04-05 16:13:08 +00:00
|
|
|
},
|
2018-01-03 14:42:13 +00:00
|
|
|
'type' => 'test',
|
|
|
|
|
'dbname' => $dbName,
|
|
|
|
|
'tablePrefix' => $dbPrefix,
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$db = $loadBalancer->getConnection( DB_REPLICA );
|
|
|
|
|
|
2018-05-14 11:57:43 +00:00
|
|
|
/** @var SqlBlobStore $blobStore */
|
2018-01-03 14:42:13 +00:00
|
|
|
$blobStore = $this->getMockBuilder( SqlBlobStore::class )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$store = new RevisionStore(
|
|
|
|
|
$loadBalancer,
|
|
|
|
|
$blobStore,
|
|
|
|
|
new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ),
|
2018-01-29 14:25:49 +00:00
|
|
|
MediaWikiServices::getInstance()->getCommentStore(),
|
2018-01-29 15:54:02 +00:00
|
|
|
MediaWikiServices::getInstance()->getContentModelStore(),
|
|
|
|
|
MediaWikiServices::getInstance()->getSlotRoleStore(),
|
2018-11-19 11:39:56 +00:00
|
|
|
MediaWikiServices::getInstance()->getSlotRoleRegistry(),
|
2017-09-12 17:12:29 +00:00
|
|
|
MediaWikiServices::getInstance()->getActorMigration(),
|
2021-02-04 02:43:09 +00:00
|
|
|
MediaWikiServices::getInstance()->getActorStoreFactory()->getActorStore( $dbDomain ),
|
2020-01-18 20:25:04 +00:00
|
|
|
MediaWikiServices::getInstance()->getContentHandlerFactory(),
|
2021-03-17 22:13:35 +00:00
|
|
|
MediaWikiServices::getInstance()->getPageStore(),
|
2021-03-15 18:09:19 +00:00
|
|
|
MediaWikiServices::getInstance()->getTitleFactory(),
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
MediaWikiServices::getInstance()->getHookContainer(),
|
2019-07-03 22:02:41 +00:00
|
|
|
$dbDomain
|
2018-01-03 14:42:13 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$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 );
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 15:59:56 +00:00
|
|
|
protected function assertLinkTargetsEqual( LinkTarget $l1, LinkTarget $l2 ) {
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertEquals( $l1->getDBkey(), $l2->getDBkey() );
|
|
|
|
|
$this->assertEquals( $l1->getNamespace(), $l2->getNamespace() );
|
|
|
|
|
$this->assertEquals( $l1->getFragment(), $l2->getFragment() );
|
|
|
|
|
$this->assertEquals( $l1->getInterwiki(), $l2->getInterwiki() );
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 15:59:56 +00:00
|
|
|
protected function assertRevisionRecordsEqual( RevisionRecord $r1, RevisionRecord $r2 ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$this->assertEquals(
|
|
|
|
|
$r1->getPageAsLinkTarget()->getNamespace(),
|
|
|
|
|
$r2->getPageAsLinkTarget()->getNamespace()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$r1->getPageAsLinkTarget()->getText(),
|
|
|
|
|
$r2->getPageAsLinkTarget()->getText()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( $r1->getParentId() ) {
|
|
|
|
|
$this->assertEquals( $r1->getParentId(), $r2->getParentId() );
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
$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->getTimestamp(), $r2->getTimestamp() );
|
|
|
|
|
$this->assertEquals( $r1->getVisibility(), $r2->getVisibility() );
|
|
|
|
|
$this->assertEquals( $r1->getSha1(), $r2->getSha1() );
|
|
|
|
|
$this->assertEquals( $r1->getSize(), $r2->getSize() );
|
|
|
|
|
$this->assertEquals( $r1->getPageId(), $r2->getPageId() );
|
2018-09-03 17:15:37 +00:00
|
|
|
$this->assertArrayEquals( $r1->getSlotRoles(), $r2->getSlotRoles() );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertEquals( $r1->getWikiId(), $r2->getWikiId() );
|
|
|
|
|
$this->assertEquals( $r1->isMinor(), $r2->isMinor() );
|
|
|
|
|
foreach ( $r1->getSlotRoles() as $role ) {
|
2018-03-06 18:46:13 +00:00
|
|
|
$this->assertSlotRecordsEqual( $r1->getSlot( $role ), $r2->getSlot( $role ) );
|
|
|
|
|
$this->assertTrue( $r1->getContent( $role )->equals( $r2->getContent( $role ) ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
foreach ( [
|
|
|
|
|
RevisionRecord::DELETED_TEXT,
|
|
|
|
|
RevisionRecord::DELETED_COMMENT,
|
|
|
|
|
RevisionRecord::DELETED_USER,
|
|
|
|
|
RevisionRecord::DELETED_RESTRICTED,
|
|
|
|
|
] as $field ) {
|
|
|
|
|
$this->assertEquals( $r1->isDeleted( $field ), $r2->isDeleted( $field ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 15:59:56 +00:00
|
|
|
protected function assertSlotRecordsEqual( SlotRecord $s1, SlotRecord $s2 ) {
|
2018-03-06 18:46:13 +00:00
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 15:59:56 +00:00
|
|
|
protected function assertRevisionCompleteness( RevisionRecord $r ) {
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertTrue( $r->hasSlot( SlotRecord::MAIN ) );
|
|
|
|
|
$this->assertInstanceOf( SlotRecord::class, $r->getSlot( SlotRecord::MAIN ) );
|
|
|
|
|
$this->assertInstanceOf( Content::class, $r->getContent( SlotRecord::MAIN ) );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
2018-03-06 18:46:13 +00:00
|
|
|
foreach ( $r->getSlotRoles() as $role ) {
|
|
|
|
|
$this->assertSlotCompleteness( $r, $r->getSlot( $role ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-22 15:59:56 +00:00
|
|
|
protected function assertSlotCompleteness( RevisionRecord $r, SlotRecord $slot ) {
|
2018-03-06 18:46:13 +00:00
|
|
|
$this->assertTrue( $slot->hasAddress() );
|
|
|
|
|
$this->assertSame( $r->getId(), $slot->getRevision() );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
|
|
|
|
$this->assertInstanceOf( Content::class, $slot->getContent() );
|
2018-03-06 18:46:13 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
|
|
|
|
* @param mixed[] $details
|
|
|
|
|
*
|
|
|
|
|
* @return RevisionRecord
|
|
|
|
|
*/
|
2018-04-17 07:49:20 +00:00
|
|
|
private function getRevisionRecordFromDetailsArray( $details = [] ) {
|
2017-11-15 12:02:40 +00:00
|
|
|
// Convert some values that can't be provided by dataProviders
|
|
|
|
|
if ( isset( $details['user'] ) && $details['user'] === true ) {
|
|
|
|
|
$details['user'] = $this->getTestUser()->getUser();
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $details['page'] ) && $details['page'] === true ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$details['page'] = $this->getTestPage()->getId();
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
if ( isset( $details['parent'] ) && $details['parent'] === true ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$details['parent'] = $this->getTestPage()->getLatest();
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the RevisionRecord with any available data
|
2018-04-17 07:49:20 +00:00
|
|
|
$rev = new MutableRevisionRecord( $this->getTestPageTitle() );
|
2017-11-15 12:02:40 +00:00
|
|
|
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;
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
if ( isset( $details['content'] ) ) {
|
|
|
|
|
foreach ( $details['content'] as $role => $content ) {
|
|
|
|
|
$rev->setContent( $role, $content );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
return $rev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideInsertRevisionOn_successes() {
|
|
|
|
|
yield 'Bare minimum revision insertion' => [
|
|
|
|
|
[
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2018-04-17 07:49:20 +00:00
|
|
|
'page' => true,
|
2017-11-15 12:02:40 +00:00
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
yield 'Detailed revision insertion' => [
|
|
|
|
|
[
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'parent' => true,
|
|
|
|
|
'page' => true,
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
'minor' => true,
|
|
|
|
|
'visibility' => RevisionRecord::DELETED_RESTRICTED,
|
|
|
|
|
],
|
|
|
|
|
];
|
2021-06-24 15:25:39 +00:00
|
|
|
yield 'Multi-slot revision insertion' => [
|
|
|
|
|
[
|
|
|
|
|
'content' => [
|
|
|
|
|
'main' => new WikitextContent( 'Chicken' ),
|
|
|
|
|
'aux' => new TextContent( 'Egg' ),
|
|
|
|
|
],
|
|
|
|
|
'page' => true,
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
|
|
|
|
];
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
protected function getRandomCommentStoreComment() {
|
2018-05-14 11:57:43 +00:00
|
|
|
return CommentStoreComment::newUnsavedComment( __METHOD__ . '.' . rand( 0, 1000 ) );
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideInsertRevisionOn_successes
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertSlotRowOn
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertContentRowOn
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2018-05-14 11:57:43 +00:00
|
|
|
public function testInsertRevisionOn_successes(
|
|
|
|
|
array $revDetails = []
|
|
|
|
|
) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$title = $this->getTestPageTitle();
|
|
|
|
|
$rev = $this->getRevisionRecordFromDetailsArray( $revDetails );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2021-04-29 02:37:11 +00:00
|
|
|
$return = $store->insertRevisionOn( $rev, wfGetDB( DB_PRIMARY ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
// is the new revision correct?
|
|
|
|
|
$this->assertRevisionCompleteness( $return );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertLinkTargetsEqual( $title, $return->getPageAsLinkTarget() );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $rev, $return );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
|
|
|
|
// can we load it from the store?
|
|
|
|
|
$loaded = $store->getRevisionById( $return->getId() );
|
|
|
|
|
$this->assertRevisionCompleteness( $loaded );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $return, $loaded );
|
|
|
|
|
|
|
|
|
|
// can we find it directly in the database?
|
2018-05-14 11:57:43 +00:00
|
|
|
$this->assertRevisionExistsInDatabase( $return );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
|
2021-06-24 15:25:39 +00:00
|
|
|
$numberOfSlots = count( $rev->getSlotRoles() );
|
|
|
|
|
|
|
|
|
|
// new schema is written
|
|
|
|
|
$this->assertSelect(
|
|
|
|
|
'slots',
|
|
|
|
|
[ 'count(*)' ],
|
|
|
|
|
[ 'slot_revision_id' => $rev->getId() ],
|
|
|
|
|
[ [ (string)$numberOfSlots ] ]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$revQuery = $store->getSlotsQueryInfo( [ 'content' ] );
|
|
|
|
|
|
|
|
|
|
$this->assertSelect(
|
|
|
|
|
$revQuery['tables'],
|
|
|
|
|
[ 'count(*)' ],
|
|
|
|
|
[
|
|
|
|
|
'slot_revision_id' => $rev->getId(),
|
|
|
|
|
],
|
|
|
|
|
[ [ (string)$numberOfSlots ] ],
|
|
|
|
|
[],
|
|
|
|
|
$revQuery['joins']
|
|
|
|
|
);
|
|
|
|
|
|
2020-06-10 08:40:05 +00:00
|
|
|
$row = $this->revisionRecordToRow( $rev, [] );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
|
|
|
|
// unset nulled fields
|
|
|
|
|
unset( $row->rev_content_model );
|
|
|
|
|
unset( $row->rev_content_format );
|
|
|
|
|
|
|
|
|
|
// unset fake fields
|
|
|
|
|
unset( $row->rev_comment_text );
|
|
|
|
|
unset( $row->rev_comment_data );
|
|
|
|
|
unset( $row->rev_comment_cid );
|
|
|
|
|
unset( $row->rev_comment_id );
|
|
|
|
|
|
|
|
|
|
$queryInfo = $store->getQueryInfo( [ 'user' ] );
|
|
|
|
|
|
|
|
|
|
$row = get_object_vars( $row );
|
2019-04-04 19:23:41 +00:00
|
|
|
|
|
|
|
|
// Use aliased fields from $queryInfo, e.g. rev_user
|
|
|
|
|
$keys = array_keys( $row );
|
|
|
|
|
$keys = array_combine( $keys, $keys );
|
|
|
|
|
$fields = array_intersect_key( $queryInfo['fields'], $keys ) + $keys;
|
|
|
|
|
|
|
|
|
|
// assertSelect() fails unless the orders match.
|
|
|
|
|
ksort( $fields );
|
|
|
|
|
ksort( $row );
|
|
|
|
|
|
2018-05-14 11:57:43 +00:00
|
|
|
$this->assertSelect(
|
2018-04-17 07:49:20 +00:00
|
|
|
$queryInfo['tables'],
|
2019-04-04 19:23:41 +00:00
|
|
|
$fields,
|
2018-04-17 07:49:20 +00:00
|
|
|
[ 'rev_id' => $rev->getId() ],
|
|
|
|
|
[ array_values( $row ) ],
|
|
|
|
|
[],
|
|
|
|
|
$queryInfo['joins']
|
2018-05-14 11:57:43 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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() );
|
2021-06-24 15:25:39 +00:00
|
|
|
// Assert that the same content ID has been used
|
|
|
|
|
$this->assertSame( $a->getContentId(), $b->getContentId() );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionOn_blobAddressExists() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$title = $this->getTestPageTitle();
|
2017-11-15 12:02:40 +00:00
|
|
|
$revDetails = [
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'parent' => true,
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
// Insert the first revision
|
2018-04-17 07:49:20 +00:00
|
|
|
$revOne = $this->getRevisionRecordFromDetailsArray( $revDetails );
|
2021-04-29 02:37:11 +00:00
|
|
|
$firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_PRIMARY ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revOne, $firstReturn );
|
|
|
|
|
|
|
|
|
|
// Insert a second revision inheriting the same blob address
|
2018-09-24 21:10:08 +00:00
|
|
|
$revDetails['slot'] = SlotRecord::newInherited( $firstReturn->getSlot( SlotRecord::MAIN ) );
|
2018-04-17 07:49:20 +00:00
|
|
|
$revTwo = $this->getRevisionRecordFromDetailsArray( $revDetails );
|
2021-04-29 02:37:11 +00:00
|
|
|
$secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_PRIMARY ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertLinkTargetsEqual( $title, $secondReturn->getPageAsLinkTarget() );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revTwo, $secondReturn );
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$firstMainSlot = $firstReturn->getSlot( SlotRecord::MAIN );
|
|
|
|
|
$secondMainSlot = $secondReturn->getSlot( SlotRecord::MAIN );
|
2018-05-14 11:57:43 +00:00
|
|
|
|
|
|
|
|
$this->assertSameSlotContent( $firstMainSlot, $secondMainSlot );
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
// And that different revisions have been created.
|
2018-05-14 11:57:43 +00:00
|
|
|
$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() );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideInsertRevisionOn_failures() {
|
|
|
|
|
yield 'no slot' => [
|
|
|
|
|
[
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
2019-12-17 16:20:32 +00:00
|
|
|
new IncompleteRevisionException( 'main slot must be provided' )
|
2017-11-15 12:02:40 +00:00
|
|
|
];
|
2018-04-17 07:49:20 +00:00
|
|
|
yield 'no main slot' => [
|
2017-11-15 12:02:40 +00:00
|
|
|
[
|
2018-04-17 07:49:20 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( 'aux', new WikitextContent( 'Turkey' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
2019-12-17 16:20:32 +00:00
|
|
|
new IncompleteRevisionException( 'main slot must be provided' )
|
2017-11-15 12:02:40 +00:00
|
|
|
];
|
|
|
|
|
yield 'no timestamp' => [
|
|
|
|
|
[
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
|
|
|
|
new IncompleteRevisionException( 'timestamp field must not be NULL!' )
|
|
|
|
|
];
|
|
|
|
|
yield 'no comment' => [
|
|
|
|
|
[
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
],
|
|
|
|
|
new IncompleteRevisionException( 'comment must not be NULL!' )
|
|
|
|
|
];
|
|
|
|
|
yield 'no user' => [
|
|
|
|
|
[
|
2018-09-24 21:10:08 +00:00
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
2017-11-15 12:02:40 +00:00
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
],
|
|
|
|
|
new IncompleteRevisionException( 'user must not be NULL!' )
|
|
|
|
|
];
|
2020-01-06 13:20:16 +00:00
|
|
|
yield 'size mismatch' => [
|
|
|
|
|
[
|
|
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
'size' => 123456
|
|
|
|
|
],
|
|
|
|
|
new PreconditionException( 'T239717' )
|
|
|
|
|
];
|
|
|
|
|
yield 'sha1 mismatch' => [
|
|
|
|
|
[
|
|
|
|
|
'slot' => SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'Chicken' ) ),
|
|
|
|
|
'comment' => $this->getRandomCommentStoreComment(),
|
|
|
|
|
'timestamp' => '20171117010101',
|
|
|
|
|
'user' => true,
|
|
|
|
|
'sha1' => 'DEADBEEF',
|
|
|
|
|
],
|
|
|
|
|
new PreconditionException( 'T239717' )
|
|
|
|
|
];
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideInsertRevisionOn_failures
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionOn_failures(
|
2020-03-19 13:09:08 +00:00
|
|
|
array $revDetails,
|
2018-05-14 11:57:43 +00:00
|
|
|
Exception $exception
|
|
|
|
|
) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$rev = $this->getRevisionRecordFromDetailsArray( $revDetails );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
2019-10-06 09:52:39 +00:00
|
|
|
$this->expectException( get_class( $exception ) );
|
|
|
|
|
$this->expectExceptionMessage( $exception->getMessage() );
|
|
|
|
|
$this->expectExceptionCode( $exception->getCode() );
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->insertRevisionOn( $rev, wfGetDB( DB_PRIMARY ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideNewNullRevision() {
|
|
|
|
|
yield [
|
2018-04-17 07:49:20 +00:00
|
|
|
Title::newFromText( 'UTPage_notAutoCreated' ),
|
|
|
|
|
[ 'content' => [ 'main' => new WikitextContent( 'Flubber1' ) ] ],
|
2017-11-15 12:02:40 +00:00
|
|
|
CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment1' ),
|
|
|
|
|
true,
|
|
|
|
|
];
|
|
|
|
|
yield [
|
2018-04-17 07:49:20 +00:00
|
|
|
Title::newFromText( 'UTPage_notAutoCreated' ),
|
|
|
|
|
[ 'content' => [ 'main' => new WikitextContent( 'Flubber2' ) ] ],
|
2017-11-15 12:02:40 +00:00
|
|
|
CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment2', [ 'a' => 1 ] ),
|
|
|
|
|
false,
|
|
|
|
|
];
|
2021-06-24 15:25:39 +00:00
|
|
|
yield [
|
|
|
|
|
Title::newFromText( 'UTPage_notAutoCreated' ),
|
|
|
|
|
[
|
|
|
|
|
'content' => [
|
|
|
|
|
'main' => new WikitextContent( 'Chicken' ),
|
|
|
|
|
'aux' => new WikitextContent( 'Omelet' ),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment multi' ),
|
|
|
|
|
];
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewNullRevision
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newNullRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2018-04-17 07:49:20 +00:00
|
|
|
public function testNewNullRevision( Title $title, $revDetails, $comment, $minor = false ) {
|
|
|
|
|
$user = TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser();
|
|
|
|
|
$page = WikiPage::factory( $title );
|
|
|
|
|
|
|
|
|
|
if ( !$page->exists() ) {
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__,
|
|
|
|
|
EDIT_NEW
|
|
|
|
|
);
|
2018-04-17 07:49:20 +00:00
|
|
|
}
|
2018-05-14 11:57:43 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
$revDetails['page'] = $page->getId();
|
|
|
|
|
$revDetails['timestamp'] = wfTimestampNow();
|
|
|
|
|
$revDetails['comment'] = CommentStoreComment::newUnsavedComment( 'Base' );
|
|
|
|
|
$revDetails['user'] = $user;
|
|
|
|
|
|
|
|
|
|
$baseRev = $this->getRevisionRecordFromDetailsArray( $revDetails );
|
2017-11-15 12:02:40 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2018-03-06 14:42:43 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$dbw = wfGetDB( DB_PRIMARY );
|
2018-04-17 07:49:20 +00:00
|
|
|
$baseRev = $store->insertRevisionOn( $baseRev, $dbw );
|
2020-04-24 00:55:09 +00:00
|
|
|
$page->updateRevisionOn( $dbw, $baseRev, $page->getLatest() );
|
2018-04-17 07:49:20 +00:00
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
$record = $store->newNullRevision(
|
2021-04-29 02:37:11 +00:00
|
|
|
wfGetDB( DB_PRIMARY ),
|
2017-11-15 12:02:40 +00:00
|
|
|
$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() );
|
2018-04-17 07:49:20 +00:00
|
|
|
$this->assertEquals( $baseRev->getId(), $record->getParentId() );
|
|
|
|
|
|
2018-09-03 17:15:37 +00:00
|
|
|
$this->assertArrayEquals(
|
2018-04-17 07:49:20 +00:00
|
|
|
$baseRev->getSlotRoles(),
|
|
|
|
|
$record->getSlotRoles()
|
|
|
|
|
);
|
2018-03-06 14:42:43 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
foreach ( $baseRev->getSlotRoles() as $role ) {
|
|
|
|
|
$parentSlot = $baseRev->getSlot( $role );
|
|
|
|
|
$slot = $record->getSlot( $role );
|
2018-03-06 14:42:43 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
$this->assertTrue( $slot->isInherited(), 'isInherited' );
|
|
|
|
|
$this->assertSame( $parentSlot->getOrigin(), $slot->getOrigin(), 'getOrigin' );
|
|
|
|
|
$this->assertSameSlotContent( $parentSlot, $slot );
|
|
|
|
|
}
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newNullRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewNullRevision_nonExistingTitle() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$record = $store->newNullRevision(
|
2021-04-29 02:37:11 +00:00
|
|
|
wfGetDB( DB_PRIMARY ),
|
2017-11-15 12:02:40 +00:00
|
|
|
Title::newFromText( __METHOD__ . '.iDontExist!' ),
|
|
|
|
|
CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment' ),
|
|
|
|
|
false,
|
|
|
|
|
TestUserRegistry::getMutableTestUser( __METHOD__ )->getUser()
|
|
|
|
|
);
|
|
|
|
|
$this->assertNull( $record );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRcIdIfUnpatrolled
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2017-12-27 15:46:03 +00:00
|
|
|
public function testGetRcIdIfUnpatrolled_returnsRecentChangesId() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionById( $revRecord->getId() );
|
|
|
|
|
$result = $store->getRcIdIfUnpatrolled( $storeRecord );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertGreaterThan( 0, $result );
|
|
|
|
|
$this->assertSame(
|
2020-06-10 01:08:37 +00:00
|
|
|
$store->getRecentChange( $storeRecord )->getAttribute( 'rc_id' ),
|
2017-11-15 12:02:40 +00:00
|
|
|
$result
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRcIdIfUnpatrolled
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2017-12-27 15:46:03 +00:00
|
|
|
public function testGetRcIdIfUnpatrolled_returnsZeroIfPatrolled() {
|
2017-11-15 12:02:40 +00:00
|
|
|
// This assumes that sysops are auto patrolled
|
|
|
|
|
$sysop = $this->getTestSysop()->getUser();
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$sysop,
|
|
|
|
|
__METHOD__
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionById( $revRecord->getId() );
|
|
|
|
|
$result = $store->getRcIdIfUnpatrolled( $storeRecord );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertSame( 0, $result );
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-25 04:04:11 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRecentChange
|
2017-12-25 04:04:11 +00:00
|
|
|
*/
|
2017-11-15 12:02:40 +00:00
|
|
|
public function testGetRecentChange() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionById( $revRecord->getId() );
|
|
|
|
|
$recentChange = $store->getRecentChange( $storeRecord );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertEquals( $revRecord->getId(), $recentChange->getAttribute( 'rc_this_oldid' ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionById
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetRevisionById() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionById( $revRecord->getId() );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
2021-03-15 16:51:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionById
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionById_crossWiki_withPage() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2021-03-15 16:51:47 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
|
|
|
|
$revId = $revRecord->getId();
|
|
|
|
|
|
|
|
|
|
// Pretend the local test DB is a sister site
|
|
|
|
|
$wikiId = $this->db->getDomainID();
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore( $wikiId );
|
|
|
|
|
|
|
|
|
|
// Construct a ProperPageIdentity with the sister site's wiki Id
|
|
|
|
|
$pageIdentity = new PageIdentityValue(
|
|
|
|
|
$page->getId(), $page->getNamespace(), $page->getDBkey(), $wikiId
|
|
|
|
|
);
|
|
|
|
|
$storeRecord = $store->getRevisionById( $revId, 0, $pageIdentity );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $wikiId, $storeRecord->getWikiId() );
|
|
|
|
|
$this->assertSame( $revId, $storeRecord->getId( $wikiId ) );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-03-15 18:09:19 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionById
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionById_crossWiki() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2021-03-15 18:09:19 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
|
|
|
|
$revId = $revRecord->getId();
|
|
|
|
|
$pageId = $revRecord->getPageId();
|
|
|
|
|
|
|
|
|
|
// Make TitleFactory always fail, since it should not be used for the cross-wiki case.
|
|
|
|
|
$noOpTitleFactory = $this->createNoOpMock( TitleFactory::class );
|
|
|
|
|
$this->setService( 'TitleFactory', $noOpTitleFactory );
|
|
|
|
|
|
|
|
|
|
// Pretend the local test DB is a sister site
|
|
|
|
|
$wikiId = $this->db->getDomainID();
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore( $wikiId );
|
|
|
|
|
|
|
|
|
|
$storeRecord = $store->getRevisionById( $revId );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $wikiId, $storeRecord->getWikiId() );
|
|
|
|
|
$this->assertSame( $wikiId, $storeRecord->getPage()->getWikiId() );
|
|
|
|
|
$this->assertNotInstanceOf( Title::class, $storeRecord->getPage() );
|
|
|
|
|
$this->assertSame( $revId, $storeRecord->getId( $wikiId ) );
|
|
|
|
|
$this->assertSame( $pageId, $storeRecord->getPage()->getId( $wikiId ) );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-19 19:50:37 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionById
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionById_undefinedContentModel() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-07-19 19:50:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
|
|
|
|
|
|
|
|
|
$mockContentHandlerFactory =
|
|
|
|
|
$this->createNoOpMock( IContentHandlerFactory::class, [ 'isDefinedModel' ] );
|
|
|
|
|
|
|
|
|
|
$mockContentHandlerFactory->method( 'isDefinedModel' )
|
|
|
|
|
->willReturn( false );
|
|
|
|
|
|
|
|
|
|
$this->setService( 'ContentHandlerFactory', $mockContentHandlerFactory );
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$storeRecord = $store->getRevisionById( $revRecord->getId() );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
|
|
|
|
|
|
|
|
|
|
$actualContent = $storeRecord->getSlot( SlotRecord::MAIN )->getContent();
|
|
|
|
|
$this->assertInstanceOf( FallbackContent::class, $actualContent );
|
|
|
|
|
$this->assertSame( __METHOD__, $actualContent->serialize() );
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionByTitle
|
2021-02-08 16:33:25 +00:00
|
|
|
* @dataProvider provideRevisionByTitle
|
|
|
|
|
*
|
|
|
|
|
* @param callable $getTitle
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2021-02-08 16:33:25 +00:00
|
|
|
public function testGetRevisionByTitle( $getTitle ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-02-08 16:33:25 +00:00
|
|
|
$title = $getTitle();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2021-02-08 16:33:25 +00:00
|
|
|
$storeRecord = $store->getRevisionByTitle( $title );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
public function provideRevisionByTitle() {
|
|
|
|
|
return [
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle();
|
|
|
|
|
} ],
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle()->toPageIdentity();
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 23:21:53 +00:00
|
|
|
private function executeWithForeignStore( string $dbDomain, callable $callback ) {
|
|
|
|
|
$services = $this->getServiceContainer();
|
2020-03-28 19:44:25 +00:00
|
|
|
// Configure the load balancer to route queries for the "foreign" domain to the test DB
|
|
|
|
|
$dbLoadBalancer = $services->getDBLoadBalancer();
|
|
|
|
|
$dbLoadBalancer->setDomainAliases( [ $dbDomain => $dbLoadBalancer->getLocalDomainID() ] );
|
|
|
|
|
$store = new RevisionStore(
|
|
|
|
|
$dbLoadBalancer,
|
|
|
|
|
$services->getBlobStore(),
|
|
|
|
|
$services->getMainWANObjectCache(),
|
|
|
|
|
$services->getCommentStore(),
|
|
|
|
|
$services->getContentModelStore(),
|
|
|
|
|
$services->getSlotRoleStore(),
|
|
|
|
|
$services->getSlotRoleRegistry(),
|
|
|
|
|
$services->getActorMigration(),
|
2021-02-04 02:43:09 +00:00
|
|
|
$services->getActorStoreFactory()->getActorStore( $dbDomain ),
|
2020-03-28 19:44:25 +00:00
|
|
|
$services->getContentHandlerFactory(),
|
2021-06-17 23:21:53 +00:00
|
|
|
$services->getPageStoreFactory()->getPageStore( $dbDomain ),
|
2021-03-15 18:09:19 +00:00
|
|
|
$services->getTitleFactory(),
|
Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.
General principles:
* Use DI if it is already used. We're not changing the way state is
managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
is a service, it's a more generic interface, it is the only
thing that provides isRegistered() which is needed in some cases,
and a HookRunner can be efficiently constructed from it
(confirmed by benchmark). Because HookContainer is needed
for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
SpecialPage and ApiBase have getHookContainer() and getHookRunner()
methods in the base class, and classes that extend that base class
are not expected to know or care where the base class gets its
HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
getHookRunner() methods, getting them from the global service
container. The point of this is to ease migration to DI by ensuring
that call sites ask their local friendly base class rather than
getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
methods did not seem warranted, there is a private HookRunner property
which is accessed directly. Very rarely (two cases), there is a
protected property, for consistency with code that conventionally
assumes protected=private, but in cases where the class might actually
be overridden, a protected accessor is preferred over a protected
property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
global code. In a few cases it was used for objects with broken
construction schemes, out of horror or laziness.
Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore
Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router
setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine
Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-03-19 02:42:09 +00:00
|
|
|
$services->getHookContainer(),
|
2020-03-28 19:44:25 +00:00
|
|
|
$dbDomain
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Redefine the DBLoadBalancer service to verify Title doesn't attempt to resolve its ID
|
|
|
|
|
// via wfGetDB()
|
|
|
|
|
$localLoadBalancerMock = $this->createMock( ILoadBalancer::class );
|
|
|
|
|
$localLoadBalancerMock->expects( $this->never() )
|
|
|
|
|
->method( $this->anything() );
|
|
|
|
|
|
2021-01-11 22:36:33 +00:00
|
|
|
try {
|
|
|
|
|
$this->setService( 'DBLoadBalancer', $localLoadBalancerMock );
|
2021-06-17 23:21:53 +00:00
|
|
|
$callback( $store );
|
2021-01-11 22:36:33 +00:00
|
|
|
} finally {
|
|
|
|
|
// Restore the original load balancer to make test teardown work
|
|
|
|
|
$this->setService( 'DBLoadBalancer', $dbLoadBalancer );
|
|
|
|
|
}
|
2020-03-28 19:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
2021-06-17 23:21:53 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionByTitle
|
|
|
|
|
*/
|
|
|
|
|
public function testGetLatestKnownRevision_foreigh() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$status = $this->editPage( $page, __METHOD__ );
|
|
|
|
|
$this->assertTrue( $status->isGood(), 'Sanity: edited a page' );
|
|
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
|
|
|
|
$dbDomain = 'some_foreign_wiki';
|
|
|
|
|
$this->executeWithForeignStore(
|
|
|
|
|
$dbDomain,
|
|
|
|
|
function ( RevisionStore $store ) use ( $page, $dbDomain, $revRecord ) {
|
|
|
|
|
$storeRecord = $store->getKnownCurrentRevision(
|
|
|
|
|
new PageIdentityValue( $page->getId(), $page->getNamespace(), $page->getDBkey(), $dbDomain )
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( $dbDomain, $storeRecord->getWikiId() );
|
|
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId( $dbDomain ) );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that getRevisionByTitle doesn't try to use the local wiki DB (T248756)
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionByTitle
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionByTitle_doesNotUseLocalLoadBalancerForForeignWiki() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$content = new WikitextContent( __METHOD__ );
|
|
|
|
|
$comment = __METHOD__;
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
$comment
|
|
|
|
|
);
|
2021-06-17 23:21:53 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
|
|
|
|
$dbDomain = 'some_foreign_wiki';
|
|
|
|
|
$this->executeWithForeignStore(
|
|
|
|
|
$dbDomain,
|
|
|
|
|
function ( RevisionStore $store ) use ( $page, $dbDomain, $revRecord, $content, $comment ) {
|
|
|
|
|
$storeRecord = $store->getRevisionByTitle(
|
|
|
|
|
new PageIdentityValue(
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$page->getTitle()->getNamespace(),
|
|
|
|
|
$page->getTitle()->getDBkey(),
|
|
|
|
|
$dbDomain
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId( $dbDomain ) );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( $comment, $storeRecord->getComment()->text );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionByPageId
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetRevisionByPageId() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionByPageId( $page->getId() );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionByTimestamp
|
2021-02-08 16:33:25 +00:00
|
|
|
* @dataProvider provideRevisionByTimestamp
|
|
|
|
|
*
|
|
|
|
|
* @param callable $getTitle
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2021-02-08 16:33:25 +00:00
|
|
|
public function testGetRevisionByTimestamp( $getTitle ) {
|
2017-11-15 12:02:40 +00:00
|
|
|
// 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...
|
2020-04-02 20:12:09 +00:00
|
|
|
MWTimestamp::setFakeTime( '20110401090000' );
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-02-08 16:33:25 +00:00
|
|
|
$title = $getTitle();
|
2020-04-02 20:12:09 +00:00
|
|
|
MWTimestamp::setFakeTime( '20110401090001' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
2021-06-24 08:42:19 +00:00
|
|
|
$status = $page->doUserEditContent(
|
|
|
|
|
$content,
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getRevisionByTimestamp(
|
2021-02-08 16:33:25 +00:00
|
|
|
$title,
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecord->getTimestamp()
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
|
|
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
public function provideRevisionByTimestamp() {
|
|
|
|
|
return [
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle();
|
|
|
|
|
} ],
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle()->toPageIdentity();
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
protected function revisionRecordToRow( RevisionRecord $revRecord, $options = [ 'page', 'user', 'comment' ] ) {
|
|
|
|
|
// XXX: the WikiPage object loads another RevisionRecord from the database. Not great.
|
|
|
|
|
$page = WikiPage::factory(
|
|
|
|
|
Title::newFromLinkTarget( $revRecord->getPageAsLinkTarget() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$revUser = $revRecord->getUser();
|
2021-02-15 18:58:09 +00:00
|
|
|
$actorId = $this->getServiceContainer()
|
|
|
|
|
->getActorNormalization()->findActorId( $revUser, $this->db );
|
2020-06-10 01:08:37 +00:00
|
|
|
|
|
|
|
|
$fields = [
|
|
|
|
|
'rev_id' => (string)$revRecord->getId(),
|
|
|
|
|
'rev_page' => (string)$revRecord->getPageId(),
|
|
|
|
|
'rev_timestamp' => $this->db->timestamp( $revRecord->getTimestamp() ),
|
2021-02-15 18:58:09 +00:00
|
|
|
'rev_actor' => $actorId,
|
2020-06-10 01:08:37 +00:00
|
|
|
'rev_user_text' => $revUser ? $revUser->getName() : '',
|
|
|
|
|
'rev_user' => (string)( $revUser ? $revUser->getId() : 0 ) ?: null,
|
|
|
|
|
'rev_minor_edit' => $revRecord->isMinor() ? '1' : '0',
|
|
|
|
|
'rev_deleted' => (string)$revRecord->getVisibility(),
|
|
|
|
|
'rev_len' => (string)$revRecord->getSize(),
|
|
|
|
|
'rev_parent_id' => (string)$revRecord->getParentId(),
|
|
|
|
|
'rev_sha1' => (string)$revRecord->getSha1(),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if ( in_array( 'page', $options ) ) {
|
|
|
|
|
$fields += [
|
|
|
|
|
'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(),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( in_array( 'user', $options ) ) {
|
|
|
|
|
$fields += [
|
|
|
|
|
'user_name' => $revUser ? $revUser->getName() : ''
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( in_array( 'comment', $options ) ) {
|
|
|
|
|
$revComment = $revRecord->getComment();
|
|
|
|
|
$fields += [
|
|
|
|
|
'rev_comment_text' => $revComment ? $revComment->text : null,
|
|
|
|
|
'rev_comment_data' => $revComment ? $revComment->data : null,
|
|
|
|
|
'rev_comment_cid' => $revComment ? $revComment->id : null,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $revRecord->getId() ) {
|
|
|
|
|
$fields += [
|
|
|
|
|
'rev_id' => (string)$revRecord->getId(),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (object)$fields;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-19 08:48:10 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getQueryInfo
|
|
|
|
|
*/
|
2020-03-30 20:20:27 +00:00
|
|
|
public function testNewRevisionFromRowAndSlots_getQueryInfo() {
|
2019-05-19 08:48:10 +00:00
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'o-ö';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2019-05-19 08:48:10 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2019-05-19 08:48:10 +00:00
|
|
|
__METHOD__ . 'a'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2019-05-19 08:48:10 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$info = $store->getQueryInfo();
|
|
|
|
|
$row = $this->db->selectRow(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'rev_id' => $revRecord->getId() ],
|
2019-05-19 08:48:10 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$info = $store->getSlotsQueryInfo( [ 'content' ] );
|
|
|
|
|
$slotRows = $this->db->select(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'slot_revision_id' => $revRecord->getId() ],
|
2019-05-19 08:48:10 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
|
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRowAndSlots(
|
2019-05-19 08:48:10 +00:00
|
|
|
$row,
|
|
|
|
|
iterator_to_array( $slotRows ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2019-05-19 08:48:10 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $revRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2019-05-19 08:48:10 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-03 17:15:37 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
2019-05-19 08:48:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getQueryInfo
|
2018-09-03 17:15:37 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_getQueryInfo() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'a-ä';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2018-09-03 17:15:37 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2018-09-03 17:15:37 +00:00
|
|
|
__METHOD__ . 'a'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2018-09-03 17:15:37 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$info = $store->getQueryInfo();
|
|
|
|
|
$row = $this->db->selectRow(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'rev_id' => $revRecord->getId() ],
|
2018-09-03 17:15:37 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
2018-09-03 17:15:37 +00:00
|
|
|
$row,
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2018-09-03 17:15:37 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $revRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2018-09-03 17:15:37 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
2019-05-19 08:48:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_anonEdit() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2018-01-12 11:52:31 +00:00
|
|
|
$text = __METHOD__ . 'a-ä';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2017-09-12 17:12:29 +00:00
|
|
|
__METHOD__ . 'a'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
|
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $revRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2018-01-12 11:52:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
2019-05-19 08:48:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
2018-01-12 11:52:31 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_anonEdit_legacyEncoding() {
|
|
|
|
|
$this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2018-01-12 11:52:31 +00:00
|
|
|
$text = __METHOD__ . 'a-ä';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2018-09-07 17:01:32 +00:00
|
|
|
__METHOD__ . 'a'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2018-01-12 11:52:31 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
|
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2018-01-12 11:52:31 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $revRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
2019-05-19 08:48:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_userEdit() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2018-01-12 11:52:31 +00:00
|
|
|
$text = __METHOD__ . 'b-ä';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
__METHOD__ . 'b'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
|
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $revRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
private function buildRevisionStore( string $text, PageIdentity $pageIdentity ) {
|
2020-03-30 20:20:27 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2021-06-05 07:06:06 +00:00
|
|
|
$page = WikiPage::factory( $pageIdentity );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2021-06-24 08:42:19 +00:00
|
|
|
$orig = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
2020-03-30 20:20:27 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$db = wfGetDB( DB_PRIMARY );
|
2020-03-30 20:20:27 +00:00
|
|
|
$arQuery = $store->getArchiveQueryInfo();
|
|
|
|
|
$res = $db->select(
|
|
|
|
|
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
|
|
|
|
|
__METHOD__, [], $arQuery['joins']
|
|
|
|
|
);
|
|
|
|
|
$this->assertIsObject( $res, 'query failed' );
|
|
|
|
|
|
|
|
|
|
$info = $store->getSlotsQueryInfo( [ 'content' ] );
|
|
|
|
|
$slotRows = $this->db->select(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
|
|
|
|
[ 'slot_revision_id' => $orig->getId() ],
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$row = $res->fetchObject();
|
|
|
|
|
$res->free();
|
2021-02-08 16:33:25 +00:00
|
|
|
return [ $store, $row, $slotRows, $orig ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getArchiveQueryInfo
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRowAndSlots_getArchiveQueryInfo() {
|
|
|
|
|
$text = __METHOD__ . '-bä';
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
list( $store, $row, $slotRows, $orig ) = $this->buildRevisionStore( $text, $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromArchiveRowAndSlots(
|
|
|
|
|
$row,
|
|
|
|
|
iterator_to_array( $slotRows )
|
|
|
|
|
);
|
2021-02-08 16:33:25 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $orig, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $storeRecord->getContent( SlotRecord::MAIN )->serialize() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideNewRevisionFromArchiveRowAndSlotsTitles() {
|
|
|
|
|
return [
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
return Title::newFromText( 'Test_NewRevisionFromArchiveRowAndSlotsTitles' );
|
|
|
|
|
} ],
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
return Title::newFromText( 'Test_NewRevisionFromArchiveRowAndSlotsTitles' )->toPageIdentity();
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewRevisionFromArchiveRowAndSlotsTitles
|
2021-06-24 15:25:39 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2021-02-08 16:33:25 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRowAndSlots_getArchiveQueryInfoWithTitle( $getPageIdentity ) {
|
|
|
|
|
$text = __METHOD__ . '-bä';
|
|
|
|
|
$page = $getPageIdentity();
|
|
|
|
|
list( $store, $row, $slotRows, $orig ) = $this->buildRevisionStore( $text, $page );
|
|
|
|
|
$storeRecord = $store->newRevisionFromArchiveRowAndSlots(
|
|
|
|
|
$row,
|
|
|
|
|
iterator_to_array( $slotRows ),
|
|
|
|
|
0,
|
|
|
|
|
$page
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertRevisionRecordsEqual( $orig, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $storeRecord->getContent( SlotRecord::MAIN )->serialize() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideNewRevisionFromArchiveRowAndSlotsInArray() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'title' => Title::newFromText( 'Test_NewRevisionFromArchiveRowAndSlotsInArray' )
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'title' => Title::newFromText( 'Test_NewRevisionFromArchiveRowAndSlotsInArray' )->toPageIdentity()
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewRevisionFromArchiveRowAndSlotsInArray
|
2021-06-24 15:25:39 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2021-02-08 16:33:25 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRowAndSlots_getArchiveQueryInfoWithTitleInArray( $array ) {
|
|
|
|
|
$text = __METHOD__ . '-bä';
|
|
|
|
|
$page = $array[ 'title' ];
|
|
|
|
|
list( $store, $row, $slotRows, $orig ) = $this->buildRevisionStore( $text, $page );
|
|
|
|
|
$storeRecord = $store->newRevisionFromArchiveRowAndSlots(
|
|
|
|
|
$row,
|
|
|
|
|
iterator_to_array( $slotRows ),
|
|
|
|
|
0,
|
|
|
|
|
null,
|
|
|
|
|
$array
|
|
|
|
|
);
|
2020-03-30 20:20:27 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $orig, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $storeRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2020-03-30 20:20:27 +00:00
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRow
|
2020-03-30 20:20:27 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getArchiveQueryInfo
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2018-09-03 17:15:37 +00:00
|
|
|
public function testNewRevisionFromArchiveRow_getArchiveQueryInfo() {
|
2017-11-15 12:02:40 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
2018-01-12 11:52:31 +00:00
|
|
|
$text = __METHOD__ . '-bä';
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2021-06-24 08:42:19 +00:00
|
|
|
$orig = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
2020-03-25 17:05:26 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
2018-01-12 11:52:31 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$db = wfGetDB( DB_PRIMARY );
|
2018-01-12 11:52:31 +00:00
|
|
|
$arQuery = $store->getArchiveQueryInfo();
|
|
|
|
|
$res = $db->select(
|
|
|
|
|
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
|
|
|
|
|
__METHOD__, [], $arQuery['joins']
|
|
|
|
|
);
|
2019-12-29 10:50:03 +00:00
|
|
|
$this->assertIsObject( $res, 'query failed' );
|
2018-01-12 11:52:31 +00:00
|
|
|
|
|
|
|
|
$row = $res->fetchObject();
|
|
|
|
|
$res->free();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromArchiveRow( $row );
|
2018-01-12 11:52:31 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $orig, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $storeRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2018-01-12 11:52:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRow
|
2020-03-30 20:20:27 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2018-01-12 11:52:31 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRow_legacyEncoding() {
|
|
|
|
|
$this->setMwGlobals( 'wgLegacyEncoding', 'windows-1252' );
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
$text = __METHOD__ . '-bä';
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2021-06-24 08:42:19 +00:00
|
|
|
$orig = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
2020-03-25 17:05:26 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$db = wfGetDB( DB_PRIMARY );
|
2017-11-15 12:02:40 +00:00
|
|
|
$arQuery = $store->getArchiveQueryInfo();
|
|
|
|
|
$res = $db->select(
|
|
|
|
|
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
|
|
|
|
|
__METHOD__, [], $arQuery['joins']
|
|
|
|
|
);
|
2019-12-29 10:50:03 +00:00
|
|
|
$this->assertIsObject( $res, 'query failed' );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$row = $res->fetchObject();
|
|
|
|
|
$res->free();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromArchiveRow( $row );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $orig, $storeRecord );
|
|
|
|
|
$this->assertSame( $text, $storeRecord->getContent( SlotRecord::MAIN )->serialize() );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2018-04-10 15:31:33 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRow
|
2020-03-30 20:20:27 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2018-05-28 19:19:11 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRow_no_user() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'ar_id' => '1',
|
|
|
|
|
'ar_page_id' => '2',
|
|
|
|
|
'ar_namespace' => '0',
|
|
|
|
|
'ar_title' => 'Something',
|
|
|
|
|
'ar_rev_id' => '2',
|
|
|
|
|
'ar_timestamp' => '20180528192356',
|
|
|
|
|
'ar_minor_edit' => '0',
|
|
|
|
|
'ar_deleted' => '0',
|
|
|
|
|
'ar_len' => '78',
|
|
|
|
|
'ar_parent_id' => '0',
|
|
|
|
|
'ar_sha1' => 'deadbeef',
|
|
|
|
|
'ar_comment_text' => 'whatever',
|
|
|
|
|
'ar_comment_data' => null,
|
|
|
|
|
'ar_comment_cid' => null,
|
|
|
|
|
'ar_user' => '0',
|
|
|
|
|
'ar_user_text' => '', // this is the important bit
|
|
|
|
|
'ar_actor' => null,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
\Wikimedia\suppressWarnings();
|
|
|
|
|
$record = $store->newRevisionFromArchiveRow( $row );
|
|
|
|
|
\Wikimedia\suppressWarnings( true );
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf( RevisionRecord::class, $record );
|
|
|
|
|
$this->assertInstanceOf( UserIdentityValue::class, $record->getUser() );
|
|
|
|
|
$this->assertSame( 'Unknown user', $record->getUser()->getName() );
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-26 20:59:00 +00:00
|
|
|
/**
|
|
|
|
|
* Test for T236624.
|
|
|
|
|
*
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRow
|
2020-03-30 20:20:27 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
2019-11-26 20:59:00 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRow_empty_actor() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'ar_id' => '1',
|
|
|
|
|
'ar_page_id' => '2',
|
|
|
|
|
'ar_namespace' => '0',
|
|
|
|
|
'ar_title' => 'Something',
|
|
|
|
|
'ar_rev_id' => '2',
|
|
|
|
|
'ar_text_id' => '47',
|
|
|
|
|
'ar_timestamp' => '20180528192356',
|
|
|
|
|
'ar_minor_edit' => '0',
|
|
|
|
|
'ar_deleted' => '0',
|
|
|
|
|
'ar_len' => '78',
|
|
|
|
|
'ar_parent_id' => '0',
|
|
|
|
|
'ar_sha1' => 'deadbeef',
|
|
|
|
|
'ar_comment_text' => 'whatever',
|
|
|
|
|
'ar_comment_data' => null,
|
|
|
|
|
'ar_comment_cid' => null,
|
|
|
|
|
'ar_user' => '0',
|
|
|
|
|
'ar_user_text' => '', // this is the important bit
|
|
|
|
|
'ar_actor' => null, // we will fill this in below
|
|
|
|
|
'ar_content_format' => null,
|
|
|
|
|
'ar_content_model' => null,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// create an actor row for the empty user name (see also T225469)
|
|
|
|
|
$this->db->insert( 'actor', [ [
|
|
|
|
|
'actor_user' => $row->ar_user,
|
|
|
|
|
'actor_name' => $row->ar_user_text,
|
|
|
|
|
] ] );
|
|
|
|
|
|
|
|
|
|
$row->ar_actor = $this->db->insertId();
|
|
|
|
|
|
|
|
|
|
\Wikimedia\suppressWarnings();
|
|
|
|
|
$record = $store->newRevisionFromArchiveRow( $row );
|
|
|
|
|
\Wikimedia\suppressWarnings( true );
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf( RevisionRecord::class, $record );
|
|
|
|
|
$this->assertInstanceOf( UserIdentityValue::class, $record->getUser() );
|
|
|
|
|
$this->assertSame( 'Unknown user', $record->getUser()->getName() );
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-28 19:19:11 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
2019-05-19 08:48:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRowAndSlots
|
2018-05-28 19:19:11 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_no_user() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'rev_id' => '2',
|
|
|
|
|
'rev_page' => '2',
|
|
|
|
|
'page_namespace' => '0',
|
|
|
|
|
'page_title' => $title->getText(),
|
|
|
|
|
'rev_text_id' => '47',
|
|
|
|
|
'rev_timestamp' => '20180528192356',
|
|
|
|
|
'rev_minor_edit' => '0',
|
|
|
|
|
'rev_deleted' => '0',
|
|
|
|
|
'rev_len' => '78',
|
|
|
|
|
'rev_parent_id' => '0',
|
|
|
|
|
'rev_sha1' => 'deadbeef',
|
|
|
|
|
'rev_comment_text' => 'whatever',
|
|
|
|
|
'rev_comment_data' => null,
|
|
|
|
|
'rev_comment_cid' => null,
|
|
|
|
|
'rev_user' => '0',
|
|
|
|
|
'rev_user_text' => '', // this is the important bit
|
|
|
|
|
'rev_actor' => null,
|
|
|
|
|
'rev_content_format' => null,
|
|
|
|
|
'rev_content_model' => null,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
\Wikimedia\suppressWarnings();
|
|
|
|
|
$record = $store->newRevisionFromRow( $row, 0, $title );
|
|
|
|
|
\Wikimedia\suppressWarnings( true );
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull( $record );
|
|
|
|
|
$this->assertNotNull( $record->getUser() );
|
|
|
|
|
$this->assertNotEmpty( $record->getUser()->getName() );
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-15 18:09:19 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getPage
|
2021-03-17 22:13:35 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::wrapPage
|
2021-03-15 18:09:19 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_noPage() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$page = $this->getExistingTestPage();
|
|
|
|
|
|
|
|
|
|
$info = $store->getQueryInfo();
|
|
|
|
|
$row = $this->db->selectRow(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
|
|
|
|
[ 'rev_page' => $page->getId(), 'rev_id' => $page->getLatest() ],
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$record = $store->newRevisionFromRow( $row );
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull( $record );
|
|
|
|
|
$this->assertTrue( $page->isSamePageAs( $record->getPage() ) );
|
|
|
|
|
|
|
|
|
|
// NOTE: This should return a Title object for now, until we no longer have a need
|
|
|
|
|
// to frequently convert to Title.
|
|
|
|
|
$this->assertInstanceOf( Title::class, $record->getPage() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getPage
|
2021-03-17 22:13:35 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::wrapPage
|
2021-03-15 18:09:19 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_noPage_crossWiki() {
|
|
|
|
|
// Make TitleFactory always fail, since it should not be used for the cross-wiki case.
|
|
|
|
|
$noOpTitleFactory = $this->createNoOpMock( TitleFactory::class );
|
|
|
|
|
$this->setService( 'TitleFactory', $noOpTitleFactory );
|
|
|
|
|
|
|
|
|
|
// Pretend the local test DB is a sister site
|
|
|
|
|
$wikiId = $this->db->getDomainID();
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore( $wikiId );
|
|
|
|
|
|
|
|
|
|
$page = $this->getExistingTestPage();
|
|
|
|
|
|
|
|
|
|
$info = $store->getQueryInfo();
|
|
|
|
|
$row = $this->db->selectRow(
|
|
|
|
|
$info['tables'],
|
|
|
|
|
$info['fields'],
|
|
|
|
|
[ 'rev_page' => $page->getId(), 'rev_id' => $page->getLatest() ],
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$info['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$record = $store->newRevisionFromRow( $row );
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull( $record );
|
|
|
|
|
$this->assertSame( $page->getLatest(), $record->getId( $wikiId ) );
|
|
|
|
|
|
|
|
|
|
$this->assertNotInstanceOf( Title::class, $record->getPage() );
|
|
|
|
|
$this->assertSame( $page->getId(), $record->getPage()->getId( $wikiId ) );
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-28 19:19:11 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
2021-02-08 16:33:25 +00:00
|
|
|
* @dataProvider provideInsertRevisionOn
|
|
|
|
|
*
|
|
|
|
|
* @param callable $getPageIdentity
|
2018-04-10 15:31:33 +00:00
|
|
|
*/
|
2021-02-08 16:33:25 +00:00
|
|
|
public function testInsertRevisionOn_archive( $getPageIdentity ) {
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
// This is a round trip test for deletion and undeletion of a
|
|
|
|
|
// revision row via the archive table.
|
2021-02-08 16:33:25 +00:00
|
|
|
list( $title, $pageIdentity ) = $getPageIdentity();
|
2018-04-10 15:31:33 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$page = WikiPage::factory( $title );
|
2021-06-24 08:42:19 +00:00
|
|
|
$user = $this->getTestSysop()->getUser();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( "First" ), $user, __METHOD__ . '-first' );
|
|
|
|
|
$orig = $page->doUserEditContent( new WikitextContent( "Foo" ), $user, __METHOD__ )
|
2020-06-10 01:08:37 +00:00
|
|
|
->value['revision-record'];
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $user );
|
2018-04-10 15:31:33 +00:00
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
// re-create page, so we can later load revisions for it
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( 'Two' ), $user, __METHOD__ );
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
|
2021-04-29 02:37:11 +00:00
|
|
|
$db = wfGetDB( DB_PRIMARY );
|
2018-04-10 15:31:33 +00:00
|
|
|
$arQuery = $store->getArchiveQueryInfo();
|
|
|
|
|
$row = $db->selectRow(
|
|
|
|
|
$arQuery['tables'], $arQuery['fields'], [ 'ar_rev_id' => $orig->getId() ],
|
|
|
|
|
__METHOD__, [], $arQuery['joins']
|
|
|
|
|
);
|
|
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
$this->assertNotFalse( $row, 'query failed' );
|
|
|
|
|
|
|
|
|
|
$record = $store->newRevisionFromArchiveRow(
|
|
|
|
|
$row,
|
|
|
|
|
0,
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageIdentity,
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
[ 'page_id' => $title->getArticleID() ]
|
|
|
|
|
);
|
2018-04-10 15:31:33 +00:00
|
|
|
|
|
|
|
|
$restored = $store->insertRevisionOn( $record, $db );
|
|
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
// is the new revision correct?
|
|
|
|
|
$this->assertRevisionCompleteness( $restored );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $record, $restored );
|
2018-04-10 15:31:33 +00:00
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
// does the new revision use the original slot?
|
2018-09-24 21:10:08 +00:00
|
|
|
$recMain = $record->getSlot( SlotRecord::MAIN );
|
|
|
|
|
$restMain = $restored->getSlot( SlotRecord::MAIN );
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
$this->assertSame( $recMain->getAddress(), $restMain->getAddress() );
|
|
|
|
|
$this->assertSame( $recMain->getContentId(), $restMain->getContentId() );
|
|
|
|
|
$this->assertSame( $recMain->getOrigin(), $restMain->getOrigin() );
|
|
|
|
|
$this->assertSame( 'Foo', $restMain->getContent()->serialize() );
|
2018-04-10 15:31:33 +00:00
|
|
|
|
Fix undeletion write-both/read-old mode.
With the new schema, undeletion does not create now slot rows.
However, when in read-old mode, we may still have un-migrated
archive rows. When undeletion based on such a row, we do need
to insert a slot row.
This also changes the return value of SlotRecord for
SCHEMA_COMPAT_READ_OLD mode from null to the negative value of
rev_text_id, for consistency. Conceptually, the emulated slots
in SCHEMA_COMPAT_OLD now have a "virtual" content ID, hich is
however distinct from any real content ID that may exist in
the future. Such virtual content IDs are however not assigned
in SCHEMA_COMPAT_WRITE_BOTH mode. In that mode, unmigrated
rows can be detected by calling hasContentId() on the main
slot. Migrated rows will return true here and provide the
id of the associated content row, even in
SCHEMA_COMPAT_READ_OLD mode. This is particularly essential
for undeletion, which needs to maintain the association
between revision and slot rows even in SCHEMA_COMPAT_READ_OLD
mode.
Bug: T174024
Bug: T194015
Bug: T183488
Change-Id: I88ee9809b9752e1e72d635d62e82008315402901
2018-08-27 19:45:40 +00:00
|
|
|
// can we load it from the store?
|
|
|
|
|
$loaded = $store->getRevisionById( $restored->getId() );
|
|
|
|
|
$this->assertNotNull( $loaded );
|
|
|
|
|
$this->assertRevisionCompleteness( $loaded );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $restored, $loaded );
|
|
|
|
|
|
|
|
|
|
// can we find it directly in the database?
|
|
|
|
|
$this->assertRevisionExistsInDatabase( $restored );
|
2018-04-10 15:31:33 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
public function provideInsertRevisionOn() {
|
|
|
|
|
return [
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_Insert_Revision_On' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
} ],
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_Insert_Revision_On' );
|
|
|
|
|
return [ $pageTitle, $pageTitle->toPageIdentity() ];
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2020-03-07 02:10:00 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionSizes
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetParentLengths() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecordOne */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordOne = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordTwo = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ . '2' ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
[
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordOne->getId() => strlen( __METHOD__ ),
|
2017-11-15 12:02:40 +00:00
|
|
|
],
|
2020-06-10 01:08:37 +00:00
|
|
|
$store->getRevisionSizes( [ $revRecordOne->getId() ] )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
[
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordOne->getId() => strlen( __METHOD__ ),
|
|
|
|
|
$revRecordTwo->getId() => strlen( __METHOD__ ) + 1,
|
2017-11-15 12:02:40 +00:00
|
|
|
],
|
2020-06-10 01:08:37 +00:00
|
|
|
$store->getRevisionSizes(
|
|
|
|
|
[ $revRecordOne->getId(), $revRecordTwo->getId() ]
|
|
|
|
|
)
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getPreviousRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetPreviousRevision() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecordOne */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordOne = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordTwo = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ . '2' ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertNull(
|
2020-06-10 01:08:37 +00:00
|
|
|
$store->getPreviousRevision(
|
|
|
|
|
$store->getRevisionById( $revRecordOne->getId() )
|
|
|
|
|
)
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordOne->getId(),
|
|
|
|
|
$store->getPreviousRevision(
|
|
|
|
|
$store->getRevisionById( $revRecordTwo->getId() )
|
|
|
|
|
)->getId()
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getNextRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetNextRevision() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecordOne */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordOne = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecordTwo = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ . '2' ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertSame(
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordTwo->getId(),
|
|
|
|
|
$store->getNextRevision(
|
|
|
|
|
$store->getRevisionById( $revRecordOne->getId() )
|
|
|
|
|
)->getId()
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
$this->assertNull(
|
2020-06-10 01:08:37 +00:00
|
|
|
$store->getNextRevision( $store->getRevisionById( $revRecordTwo->getId() ) )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-23 12:30:35 +00:00
|
|
|
public function provideNonHistoryRevision() {
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
$rev = new MutableRevisionRecord( $title );
|
|
|
|
|
yield [ $rev ];
|
|
|
|
|
|
2021-02-15 18:58:09 +00:00
|
|
|
$user = new UserIdentityValue( 7, 'Frank' );
|
2018-11-23 12:30:35 +00:00
|
|
|
$comment = CommentStoreComment::newUnsavedComment( 'Test' );
|
|
|
|
|
$row = (object)[
|
|
|
|
|
'ar_id' => 3,
|
|
|
|
|
'ar_rev_id' => 34567,
|
|
|
|
|
'ar_page_id' => 5,
|
|
|
|
|
'ar_deleted' => 0,
|
|
|
|
|
'ar_minor_edit' => 0,
|
|
|
|
|
'ar_timestamp' => '20180101020202',
|
|
|
|
|
];
|
|
|
|
|
$slots = new RevisionSlots( [] );
|
|
|
|
|
$rev = new RevisionArchiveRecord( $title, $user, $comment, $row, $slots );
|
|
|
|
|
yield [ $rev ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNonHistoryRevision
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getPreviousRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetPreviousRevision_bad( RevisionRecord $rev ) {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertNull( $store->getPreviousRevision( $rev ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNonHistoryRevision
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getNextRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetNextRevision_bad( RevisionRecord $rev ) {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertNull( $store->getNextRevision( $rev ) );
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTimestampFromId
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetTimestampFromId_found() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$result = $store->getTimestampFromId( $revRecord->getId() );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame( $revRecord->getTimestamp(), $result );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTimestampFromId
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetTimestampFromId_notFound() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$result = $store->getTimestampFromId( $revRecord->getId() + 1 );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertFalse( $result );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countRevisionsByPageId
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testCountRevisionsByPageId() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
|
2021-06-24 08:42:19 +00:00
|
|
|
$user = $this->getTestSysop()->getUser();
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
0,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_PRIMARY ), $page->getId() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( 'a' ), $user, 'a' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
1,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_PRIMARY ), $page->getId() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( 'b' ), $user, 'b' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
2,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_PRIMARY ), $page->getId() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countRevisionsByTitle
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testCountRevisionsByTitle() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( Title::newFromText( __METHOD__ ) );
|
2021-06-24 08:42:19 +00:00
|
|
|
$user = $this->getTestSysop()->getUser();
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
0,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_PRIMARY ), $page->getTitle() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( 'a' ), $user, 'a' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
1,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_PRIMARY ), $page->getTitle() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent( new WikitextContent( 'b' ), $user, 'b' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
2,
|
2021-04-29 02:37:11 +00:00
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_PRIMARY ), $page->getTitle() )
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::userWasLastToEdit
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testUserWasLastToEdit_false() {
|
|
|
|
|
$sysop = $this->getTestSysop()->getUser();
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
$this->getTestUser()->getUser(), // not the $sysop
|
|
|
|
|
__METHOD__
|
|
|
|
|
);
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->userWasLastToEdit(
|
2021-04-29 02:37:11 +00:00
|
|
|
wfGetDB( DB_PRIMARY ),
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getId(),
|
|
|
|
|
$sysop->getId(),
|
|
|
|
|
'20160101010101'
|
|
|
|
|
);
|
|
|
|
|
$this->assertFalse( $result );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::userWasLastToEdit
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testUserWasLastToEdit_true() {
|
|
|
|
|
$startTime = wfTimestampNow();
|
|
|
|
|
$sysop = $this->getTestSysop()->getUser();
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2021-06-24 08:42:19 +00:00
|
|
|
$page->doUserEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$sysop,
|
|
|
|
|
__METHOD__
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->userWasLastToEdit(
|
2021-04-29 02:37:11 +00:00
|
|
|
wfGetDB( DB_PRIMARY ),
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getId(),
|
|
|
|
|
$sysop->getId(),
|
|
|
|
|
$startTime
|
|
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
2021-02-08 16:33:25 +00:00
|
|
|
* @dataProvider provideGetKnownCurrentRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2021-02-08 16:33:25 +00:00
|
|
|
public function testGetKnownCurrentRevision( $getPageIdentity ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2017-09-12 17:12:29 +00:00
|
|
|
new WikitextContent( __METHOD__ . 'b' ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
__METHOD__ . 'b'
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->getKnownCurrentRevision(
|
2021-02-08 16:33:25 +00:00
|
|
|
$getPageIdentity(),
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecord->getId()
|
2017-11-15 12:02:40 +00:00
|
|
|
);
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $storeRecord );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
public function provideGetKnownCurrentRevision() {
|
|
|
|
|
return [
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle();
|
|
|
|
|
} ],
|
|
|
|
|
[ function () {
|
|
|
|
|
return $this->getTestPage()->getTitle()->toPageIdentity();
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-27 21:26:17 +00:00
|
|
|
/**
|
|
|
|
|
* Creates a new revision for testing caching behavior
|
|
|
|
|
*
|
|
|
|
|
* @param WikiPage $page the page for the new revision
|
|
|
|
|
* @param RevisionStore $store store object to use for creating the revision
|
|
|
|
|
* @return bool|RevisionStoreRecord the revision created, or false if missing
|
|
|
|
|
*/
|
|
|
|
|
private function createRevisionStoreCacheRecord( $page, $store ) {
|
2020-06-30 15:09:24 +00:00
|
|
|
$user = MediaWikiIntegrationTestCase::getMutableTestUser()->getUser();
|
2019-02-27 21:26:17 +00:00
|
|
|
$updater = $page->newPageUpdater( $user );
|
|
|
|
|
$updater->setContent( SlotRecord::MAIN, new WikitextContent( __METHOD__ ) );
|
|
|
|
|
$summary = CommentStoreComment::newUnsavedComment( __METHOD__ );
|
|
|
|
|
$rev = $updater->saveRevision( $summary, EDIT_NEW );
|
|
|
|
|
return $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetKnownCurrentRevision_userNameChange() {
|
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
|
|
|
$this->setService( 'MainWANObjectCache', $cache );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$page = $this->getNonexistingTestPage();
|
|
|
|
|
$rev = $this->createRevisionStoreCacheRecord( $page, $store );
|
|
|
|
|
|
|
|
|
|
// Grab the user name
|
|
|
|
|
$userNameBefore = $rev->getUser()->getName();
|
|
|
|
|
|
|
|
|
|
// Change the user name in the database, "behind the back" of the cache
|
|
|
|
|
$newUserName = "Renamed $userNameBefore";
|
|
|
|
|
$this->db->update( 'user',
|
|
|
|
|
[ 'user_name' => $newUserName ],
|
|
|
|
|
[ 'user_id' => $rev->getUser()->getId() ] );
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->db->update( 'actor',
|
|
|
|
|
[ 'actor_name' => $newUserName ],
|
|
|
|
|
[ 'actor_user' => $rev->getUser()->getId() ] );
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
// Reload the revision and regrab the user name.
|
|
|
|
|
$revAfter = $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
|
|
|
|
|
$userNameAfter = $revAfter->getUser()->getName();
|
|
|
|
|
|
|
|
|
|
// The two user names should be different.
|
|
|
|
|
// If they are the same, we are seeing a cached value, which is bad.
|
|
|
|
|
$this->assertNotSame( $userNameBefore, $userNameAfter );
|
|
|
|
|
|
|
|
|
|
// This is implied by the above assertion, but explicitly check it, for completeness
|
|
|
|
|
$this->assertSame( $newUserName, $userNameAfter );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
2020-04-09 12:06:22 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetKnownCurrentRevision_stalePageId() {
|
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
|
|
|
$this->setService( 'MainWANObjectCache', $cache );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$page = $this->getNonexistingTestPage();
|
|
|
|
|
$rev = $this->createRevisionStoreCacheRecord( $page, $store );
|
|
|
|
|
|
2021-04-28 09:20:42 +00:00
|
|
|
// Force bad article ID
|
2020-04-09 12:06:22 +00:00
|
|
|
$title = $page->getTitle();
|
|
|
|
|
$title->resetArticleID( 886655 );
|
|
|
|
|
|
2021-04-28 09:20:42 +00:00
|
|
|
$result = $store->getKnownCurrentRevision( $title, $rev->getId() );
|
2020-04-09 12:06:22 +00:00
|
|
|
|
2021-04-28 09:20:42 +00:00
|
|
|
$this->assertSame( $rev->getPageId(), $result->getPageId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetKnownCurrentRevision_wrongTitle() {
|
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
|
|
|
$this->setService( 'MainWANObjectCache', $cache );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$page = $this->getNonexistingTestPage();
|
|
|
|
|
$rev = $this->createRevisionStoreCacheRecord( $page, $store );
|
|
|
|
|
|
|
|
|
|
// Get title of another page
|
|
|
|
|
$title = $this->getExistingTestPage( __FUNCTION__ )->getTitle();
|
|
|
|
|
$result = $store->getKnownCurrentRevision( $title, $rev->getId() );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $rev->getPageId(), $result->getPageId() );
|
|
|
|
|
$this->assertTrue( $rev->getPage()->isSamePageAs( $result->getPage() ) );
|
2020-04-09 12:06:22 +00:00
|
|
|
}
|
|
|
|
|
|
2019-02-27 21:26:17 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetKnownCurrentRevision_revDelete() {
|
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
|
|
|
|
$this->setService( 'MainWANObjectCache', $cache );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$page = $this->getNonexistingTestPage();
|
|
|
|
|
$rev = $this->createRevisionStoreCacheRecord( $page, $store );
|
|
|
|
|
|
|
|
|
|
// Grab the deleted bitmask
|
|
|
|
|
$deletedBefore = $rev->getVisibility();
|
|
|
|
|
|
|
|
|
|
// Change the deleted bitmask in the database, "behind the back" of the cache
|
|
|
|
|
$this->db->update( 'revision',
|
|
|
|
|
[ 'rev_deleted' => RevisionRecord::DELETED_TEXT ],
|
|
|
|
|
[ 'rev_id' => $rev->getId() ] );
|
|
|
|
|
|
|
|
|
|
// Reload the revision and regrab the visibility flag.
|
|
|
|
|
$revAfter = $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
|
|
|
|
|
$deletedAfter = $revAfter->getVisibility();
|
|
|
|
|
|
|
|
|
|
// The two deleted flags should be different.
|
|
|
|
|
// If they are the same, we are seeing a cached value, which is bad.
|
|
|
|
|
$this->assertNotSame( $deletedBefore, $deletedAfter );
|
|
|
|
|
|
|
|
|
|
// This is implied by the above assertion, but explicitly check it, for completeness
|
|
|
|
|
$this->assertSame( RevisionRecord::DELETED_TEXT, $deletedAfter );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_userNameChange() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__;
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2019-02-27 21:26:17 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getMutableTestUser()->getUser(),
|
|
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
|
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2019-02-27 21:26:17 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Grab the user name
|
2020-06-10 01:08:37 +00:00
|
|
|
$userNameBefore = $storeRecord->getUser()->getName();
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
// Change the user name in the database
|
|
|
|
|
$newUserName = "Renamed $userNameBefore";
|
|
|
|
|
$this->db->update( 'user',
|
|
|
|
|
[ 'user_name' => $newUserName ],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'user_id' => $storeRecord->getUser()->getId() ] );
|
2019-07-23 17:40:52 +00:00
|
|
|
$this->db->update( 'actor',
|
|
|
|
|
[ 'actor_name' => $newUserName ],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'actor_user' => $storeRecord->getUser()->getId() ] );
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
// Reload the record, passing $fromCache as true to force fresh info from the db,
|
|
|
|
|
// and regrab the user name
|
|
|
|
|
$recordAfter = $store->newRevisionFromRow(
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2019-02-27 21:26:17 +00:00
|
|
|
$page->getTitle(),
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
$userNameAfter = $recordAfter->getUser()->getName();
|
|
|
|
|
|
|
|
|
|
// The two user names should be different.
|
|
|
|
|
// If they are the same, we are seeing a cached value, which is bad.
|
|
|
|
|
$this->assertNotSame( $userNameBefore, $userNameAfter );
|
|
|
|
|
|
|
|
|
|
// This is implied by the above assertion, but explicitly check it, for completeness
|
|
|
|
|
$this->assertSame( $newUserName, $userNameAfter );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromRow
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromRow_revDelete() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__;
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord = $page->doUserEditContent(
|
2019-02-27 21:26:17 +00:00
|
|
|
new WikitextContent( $text ),
|
2021-06-24 08:42:19 +00:00
|
|
|
$this->getTestSysop()->getUser(),
|
2019-02-27 21:26:17 +00:00
|
|
|
__METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromRow(
|
|
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2019-02-27 21:26:17 +00:00
|
|
|
$page->getTitle()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Grab the deleted bitmask
|
2020-06-10 01:08:37 +00:00
|
|
|
$deletedBefore = $storeRecord->getVisibility();
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
// Change the deleted bitmask in the database
|
|
|
|
|
$this->db->update( 'revision',
|
|
|
|
|
[ 'rev_deleted' => RevisionRecord::DELETED_TEXT ],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'rev_id' => $storeRecord->getId() ] );
|
2019-02-27 21:26:17 +00:00
|
|
|
|
|
|
|
|
// Reload the record, passing $fromCache as true to force fresh info from the db,
|
|
|
|
|
// and regrab the deleted bitmask
|
|
|
|
|
$recordAfter = $store->newRevisionFromRow(
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->revisionRecordToRow( $revRecord ),
|
2020-11-26 18:39:34 +00:00
|
|
|
0,
|
2019-02-27 21:26:17 +00:00
|
|
|
$page->getTitle(),
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
$deletedAfter = $recordAfter->getVisibility();
|
|
|
|
|
|
|
|
|
|
// The two deleted flags should be different, because we modified the database.
|
|
|
|
|
$this->assertNotSame( $deletedBefore, $deletedAfter );
|
|
|
|
|
|
|
|
|
|
// This is implied by the above assertion, but explicitly check it, for completeness
|
|
|
|
|
$this->assertSame( RevisionRecord::DELETED_TEXT, $deletedAfter );
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-26 12:23:40 +00:00
|
|
|
public function provideGetContentBlobsForBatchOptions() {
|
|
|
|
|
yield 'all slots' => [ null ];
|
|
|
|
|
yield 'no slots' => [ [] ];
|
|
|
|
|
yield 'main slot' => [ [ SlotRecord::MAIN ] ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideGetContentBlobsForBatchOptions
|
2021-01-09 11:03:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getContentBlobsForBatch
|
2019-09-26 12:23:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetContentBlobsForBatch( $slots ) {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
2019-09-26 12:23:40 +00:00
|
|
|
|
|
|
|
|
$page2 = $this->getTestPage( $page1->getTitle()->getPrefixedText() . '_other' );
|
|
|
|
|
$editStatus = $this->editPage( $page2->getTitle()->getPrefixedDBkey(), $text . '2' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 2' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord2 */
|
|
|
|
|
$revRecord2 = $editStatus->getValue()['revision-record'];
|
2019-09-26 12:23:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-06-10 01:08:37 +00:00
|
|
|
$result = $store->getContentBlobsForBatch(
|
|
|
|
|
[ $revRecord1->getId(), $revRecord2->getId() ],
|
|
|
|
|
$slots
|
|
|
|
|
);
|
2019-09-26 12:23:40 +00:00
|
|
|
$this->assertTrue( $result->isGood() );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $result->getErrors() );
|
2019-09-26 12:23:40 +00:00
|
|
|
|
|
|
|
|
$rowSetsByRevId = $result->getValue();
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertArrayHasKey( $revRecord1->getId(), $rowSetsByRevId );
|
|
|
|
|
$this->assertArrayHasKey( $revRecord2->getId(), $rowSetsByRevId );
|
2019-09-26 12:23:40 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$rev1rows = $rowSetsByRevId[$revRecord1->getId()];
|
|
|
|
|
$rev2rows = $rowSetsByRevId[$revRecord2->getId()];
|
2019-09-26 12:23:40 +00:00
|
|
|
|
|
|
|
|
if ( is_array( $slots ) && !in_array( SlotRecord::MAIN, $slots ) ) {
|
|
|
|
|
$this->assertArrayNotHasKey( SlotRecord::MAIN, $rev1rows );
|
|
|
|
|
$this->assertArrayNotHasKey( SlotRecord::MAIN, $rev2rows );
|
|
|
|
|
} else {
|
|
|
|
|
$this->assertArrayHasKey( SlotRecord::MAIN, $rev1rows );
|
|
|
|
|
$this->assertArrayHasKey( SlotRecord::MAIN, $rev2rows );
|
|
|
|
|
|
|
|
|
|
$mainSlotRow1 = $rev1rows[ SlotRecord::MAIN ];
|
|
|
|
|
$mainSlotRow2 = $rev2rows[ SlotRecord::MAIN ];
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $text . '1', $mainSlotRow1->blob_data );
|
|
|
|
|
$this->assertSame( $text . '2', $mainSlotRow2->blob_data );
|
|
|
|
|
}
|
2020-03-30 20:20:27 +00:00
|
|
|
|
|
|
|
|
// try again, with objects instead of ids:
|
|
|
|
|
$result2 = $store->getContentBlobsForBatch( [
|
2020-06-10 01:08:37 +00:00
|
|
|
(object)[ 'rev_id' => $revRecord1->getId() ],
|
|
|
|
|
(object)[ 'rev_id' => $revRecord2->getId() ],
|
2020-03-30 20:20:27 +00:00
|
|
|
], $slots );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $result2->isGood() );
|
|
|
|
|
$exp1 = var_export( $result->getValue(), true );
|
|
|
|
|
$exp2 = var_export( $result2->getValue(), true );
|
|
|
|
|
$this->assertSame( $exp1, $exp2 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getContentBlobsForBatch
|
|
|
|
|
* @throws \MWException
|
|
|
|
|
*/
|
|
|
|
|
public function testGetContentBlobsForBatch_archive() {
|
|
|
|
|
$page1 = $this->getTestPage( __METHOD__ );
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
2020-03-30 20:20:27 +00:00
|
|
|
$page1->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
|
|
|
|
|
$page2 = $this->getTestPage( $page1->getTitle()->getPrefixedText() . '_other' );
|
|
|
|
|
$editStatus = $this->editPage( $page2->getTitle()->getPrefixedDBkey(), $text . '2' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 2' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord2 */
|
|
|
|
|
$revRecord2 = $editStatus->getValue()['revision-record'];
|
2020-03-30 20:20:27 +00:00
|
|
|
$page2->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->getContentBlobsForBatch( [
|
2020-06-10 01:08:37 +00:00
|
|
|
(object)[ 'ar_rev_id' => $revRecord1->getId() ],
|
|
|
|
|
(object)[ 'ar_rev_id' => $revRecord2->getId() ],
|
2020-03-30 20:20:27 +00:00
|
|
|
] );
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
|
|
|
|
$this->assertSame( [], $result->getErrors() );
|
|
|
|
|
|
|
|
|
|
$rowSetsByRevId = $result->getValue();
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertArrayHasKey( $revRecord1->getId(), $rowSetsByRevId );
|
|
|
|
|
$this->assertArrayHasKey( $revRecord2->getId(), $rowSetsByRevId );
|
2019-09-26 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testGetContentBlobsForBatch_emptyBatch() {
|
|
|
|
|
$rows = new FakeResultWrapper( [] );
|
|
|
|
|
$result = MediaWikiServices::getInstance()->getRevisionStore()
|
|
|
|
|
->getContentBlobsForBatch( $rows );
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $result->getValue() );
|
|
|
|
|
$this->assertSame( [], $result->getErrors() );
|
2019-09-26 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
public function provideNewRevisionsFromBatchOptions() {
|
|
|
|
|
yield 'No preload slots or content, single page' => [
|
2019-09-25 20:17:38 +00:00
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
|
|
|
|
null,
|
|
|
|
|
[]
|
|
|
|
|
];
|
|
|
|
|
yield 'No preload slots or content, single page and with PageIdentity' => [
|
|
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle->toPageIdentity() ];
|
|
|
|
|
},
|
2019-08-30 18:26:00 +00:00
|
|
|
null,
|
|
|
|
|
[]
|
|
|
|
|
];
|
|
|
|
|
yield 'Preload slots and content, single page' => [
|
2019-09-25 20:17:38 +00:00
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
2019-08-30 18:26:00 +00:00
|
|
|
null,
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
];
|
2019-09-26 12:23:40 +00:00
|
|
|
yield 'Ask for no slots' => [
|
2019-09-25 20:17:38 +00:00
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
2019-09-26 12:23:40 +00:00
|
|
|
null,
|
|
|
|
|
[ 'slots' => [] ]
|
|
|
|
|
];
|
2019-08-30 18:26:00 +00:00
|
|
|
yield 'No preload slots or content, multiple pages' => [
|
2019-09-25 20:17:38 +00:00
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
2019-08-30 18:26:00 +00:00
|
|
|
'Other_Page',
|
|
|
|
|
[]
|
|
|
|
|
];
|
|
|
|
|
yield 'Preload slots and content, multiple pages' => [
|
2019-09-25 20:17:38 +00:00
|
|
|
[ 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
2019-09-25 20:17:38 +00:00
|
|
|
'Other_Page',
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
yield 'Preload slots and content, multiple pages, preload page fields' => [
|
|
|
|
|
[ 'page', 'comment' ],
|
2021-02-10 22:31:02 +00:00
|
|
|
static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_New_Revision_From_Batch' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
},
|
2019-08-30 18:26:00 +00:00
|
|
|
'Other_Page',
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewRevisionsFromBatchOptions
|
2021-01-09 11:03:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
2020-06-10 01:08:37 +00:00
|
|
|
* @param array|null $queryOptions options to provide to revisionRecordToRow
|
2021-02-08 16:33:25 +00:00
|
|
|
* @param callable $getPageIdentity
|
2019-08-30 18:26:00 +00:00
|
|
|
* @param string|null $otherPageTitle
|
|
|
|
|
* @param array|null $options
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_preloadContent(
|
2019-09-25 20:17:38 +00:00
|
|
|
$queryOptions,
|
2021-02-08 16:33:25 +00:00
|
|
|
$getPageIdentity,
|
2019-08-30 18:26:00 +00:00
|
|
|
$otherPageTitle = null,
|
|
|
|
|
array $options = []
|
|
|
|
|
) {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
2019-09-18 00:25:43 +00:00
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
2019-09-18 00:25:43 +00:00
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
$page2 = $this->getTestPage( $otherPageTitle );
|
2019-09-18 00:25:43 +00:00
|
|
|
$editStatus = $this->editPage( $page2->getTitle()->getPrefixedDBkey(), $text . '2' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 2' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord2 */
|
|
|
|
|
$revRecord2 = $editStatus->getValue()['revision-record'];
|
2019-09-18 00:25:43 +00:00
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->newRevisionsFromBatch(
|
2019-09-25 20:17:38 +00:00
|
|
|
[
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->revisionRecordToRow( $revRecord1, $queryOptions ),
|
|
|
|
|
$this->revisionRecordToRow( $revRecord2, $queryOptions )
|
2019-09-25 20:17:38 +00:00
|
|
|
],
|
2020-03-30 20:20:27 +00:00
|
|
|
$options,
|
|
|
|
|
0, $otherPageTitle ? null : $page1->getTitle()
|
2019-08-30 18:26:00 +00:00
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $result->getErrors() );
|
2019-09-18 00:25:43 +00:00
|
|
|
/** @var RevisionRecord[] $records */
|
2019-08-30 18:26:00 +00:00
|
|
|
$records = $result->getValue();
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord1, $records[$revRecord1->getId()] );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revRecord2, $records[$revRecord2->getId()] );
|
2019-08-30 18:26:00 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
$text . '1',
|
|
|
|
|
ContentHandler::getContentText(
|
|
|
|
|
$records[$revRecord1->getId()]->getContent( SlotRecord::MAIN )
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$text . '2',
|
|
|
|
|
ContentHandler::getContentText(
|
|
|
|
|
$records[$revRecord2->getId()]->getContent( SlotRecord::MAIN )
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$page1->getTitle()->getDBkey(),
|
|
|
|
|
$records[$revRecord1->getId()]->getPageAsLinkTarget()->getDBkey()
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$page2->getTitle()->getDBkey(),
|
|
|
|
|
$records[$revRecord2->getId()]->getPageAsLinkTarget()->getDBkey()
|
|
|
|
|
);
|
2019-08-30 18:26:00 +00:00
|
|
|
}
|
|
|
|
|
|
2020-03-30 20:20:27 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewRevisionsFromBatchOptions
|
2021-01-09 11:03:10 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
2020-06-10 01:08:37 +00:00
|
|
|
* @param array|null $queryOptions options to provide to revisionRecordToRow
|
2021-02-08 16:33:25 +00:00
|
|
|
* @param callable $getPageIdentity
|
2020-03-30 20:20:27 +00:00
|
|
|
* @param string|null $otherPageTitle
|
|
|
|
|
* @param array|null $options
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_archive(
|
|
|
|
|
$queryOptions,
|
2021-02-08 16:33:25 +00:00
|
|
|
$getPageIdentity,
|
2020-03-30 20:20:27 +00:00
|
|
|
$otherPageTitle = null,
|
|
|
|
|
array $options = []
|
|
|
|
|
) {
|
2021-02-08 16:33:25 +00:00
|
|
|
list( $title1, $pageIdentity ) = $getPageIdentity();
|
2020-03-30 20:20:27 +00:00
|
|
|
$text1 = __METHOD__ . '-bä';
|
|
|
|
|
$page1 = WikiPage::factory( $title1 );
|
|
|
|
|
|
|
|
|
|
$title2 = $otherPageTitle ? Title::newFromText( $otherPageTitle ) : $title1;
|
|
|
|
|
$text2 = __METHOD__ . '-bö';
|
|
|
|
|
$page2 = $otherPageTitle ? WikiPage::factory( $title2 ) : $page1;
|
|
|
|
|
|
2021-06-24 08:42:19 +00:00
|
|
|
$sysop = $this->getTestSysop()->getUser();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
/** @var RevisionRecord $revRecord2 */
|
2021-06-24 08:42:19 +00:00
|
|
|
$revRecord1 = $page1->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text1 ),
|
|
|
|
|
$sysop,
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
|
|
|
|
$revRecord2 = $page2->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text2 ),
|
|
|
|
|
$sysop,
|
|
|
|
|
__METHOD__
|
|
|
|
|
)->value['revision-record'];
|
|
|
|
|
$page1->doDeleteArticleReal( __METHOD__, $sysop );
|
2020-03-30 20:20:27 +00:00
|
|
|
|
|
|
|
|
if ( $page2 !== $page1 ) {
|
2021-06-24 08:42:19 +00:00
|
|
|
$page2->doDeleteArticleReal( __METHOD__, $sysop );
|
2020-03-30 20:20:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$queryInfo = $store->getArchiveQueryInfo();
|
|
|
|
|
$rows = $this->db->select(
|
|
|
|
|
$queryInfo['tables'],
|
|
|
|
|
$queryInfo['fields'],
|
2020-06-10 01:08:37 +00:00
|
|
|
[ 'ar_rev_id' => [ $revRecord1->getId(), $revRecord2->getId() ] ],
|
2020-03-30 20:20:27 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
[],
|
|
|
|
|
$queryInfo['joins']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$options['archive'] = true;
|
|
|
|
|
$rows = iterator_to_array( $rows );
|
|
|
|
|
$result = $store->newRevisionsFromBatch(
|
2021-02-08 16:33:25 +00:00
|
|
|
$rows, $options, 0, $otherPageTitle ? null : $pageIdentity );
|
2020-03-30 20:20:27 +00:00
|
|
|
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
|
|
|
|
$this->assertSame( [], $result->getErrors() );
|
|
|
|
|
/** @var RevisionRecord[] $records */
|
|
|
|
|
$records = $result->getValue();
|
|
|
|
|
$this->assertCount( 2, $records );
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord1, $records[$revRecord1->getId()] );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revRecord2, $records[$revRecord2->getId()] );
|
2020-03-30 20:20:27 +00:00
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
$text1,
|
|
|
|
|
ContentHandler::getContentText(
|
|
|
|
|
$records[$revRecord1->getId()]->getContent( SlotRecord::MAIN )
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$text2,
|
|
|
|
|
ContentHandler::getContentText(
|
|
|
|
|
$records[$revRecord2->getId()]->getContent( SlotRecord::MAIN )
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$page1->getTitle()->getDBkey(),
|
|
|
|
|
$records[$revRecord1->getId()]->getPageAsLinkTarget()->getDBkey()
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$page2->getTitle()->getDBkey(),
|
|
|
|
|
$records[$revRecord2->getId()]->getPageAsLinkTarget()->getDBkey()
|
|
|
|
|
);
|
2020-03-30 20:20:27 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_emptyBatch() {
|
2019-09-26 12:23:40 +00:00
|
|
|
$rows = new FakeResultWrapper( [] );
|
2019-08-30 18:26:00 +00:00
|
|
|
$result = MediaWikiServices::getInstance()->getRevisionStore()
|
|
|
|
|
->newRevisionsFromBatch(
|
2019-09-26 12:23:40 +00:00
|
|
|
$rows,
|
2019-08-30 18:26:00 +00:00
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $result->getValue() );
|
|
|
|
|
$this->assertSame( [], $result->getErrors() );
|
2019-08-30 18:26:00 +00:00
|
|
|
}
|
2019-09-18 00:25:43 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_wrongTitle() {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $rev1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
2019-09-18 00:25:43 +00:00
|
|
|
|
2019-10-05 15:39:46 +00:00
|
|
|
$this->expectException( InvalidArgumentException::class );
|
2019-09-18 00:25:43 +00:00
|
|
|
MediaWikiServices::getInstance()->getRevisionStore()
|
|
|
|
|
->newRevisionsFromBatch(
|
2020-06-10 01:08:37 +00:00
|
|
|
[ $this->revisionRecordToRow( $revRecord1 ) ],
|
2019-09-18 00:25:43 +00:00
|
|
|
[],
|
|
|
|
|
IDBAccessObject::READ_NORMAL,
|
|
|
|
|
$this->getTestPage( 'Title_Other_Then_The_One_Revision_Belongs_To' )->getTitle()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_DuplicateRows() {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
2019-09-18 00:25:43 +00:00
|
|
|
|
2019-09-24 17:39:54 +00:00
|
|
|
$status = MediaWikiServices::getInstance()->getRevisionStore()
|
2020-06-10 01:08:37 +00:00
|
|
|
->newRevisionsFromBatch(
|
|
|
|
|
[
|
|
|
|
|
$this->revisionRecordToRow( $revRecord1 ),
|
|
|
|
|
$this->revisionRecordToRow( $revRecord1 )
|
|
|
|
|
]
|
|
|
|
|
);
|
2019-09-24 17:39:54 +00:00
|
|
|
|
|
|
|
|
$this->assertFalse( $status->isGood() );
|
2020-04-28 19:28:59 +00:00
|
|
|
$this->assertTrue( $status->hasMessage( 'internalerror_info' ) );
|
2019-09-18 00:25:43 +00:00
|
|
|
}
|
2019-09-26 12:23:40 +00:00
|
|
|
|
2020-07-22 16:07:50 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionIdsBetween
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionIdsBetween() {
|
|
|
|
|
$NUM = 5;
|
|
|
|
|
$MAX = 1;
|
|
|
|
|
$page = $this->getTestPage( __METHOD__ );
|
|
|
|
|
$revisions = [];
|
|
|
|
|
$revisionIds = [];
|
|
|
|
|
for ( $revNum = 0; $revNum < $NUM; $revNum++ ) {
|
|
|
|
|
$editStatus = $this->editPage( $page->getTitle()->getPrefixedDBkey(), 'Revision ' . $revNum );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision ' . $revNum );
|
|
|
|
|
$newRevision = $editStatus->getValue()['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $newRevision */
|
|
|
|
|
$revisions[] = $newRevision;
|
|
|
|
|
$revisionIds[] = $newRevision->getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
[],
|
|
|
|
|
$revisionStore->getRevisionIdsBetween( $page->getId(), $revisions[0], $revisions[0] ),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
'Must return an empty array if the same old and new revisions provided'
|
|
|
|
|
);
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
[],
|
|
|
|
|
$revisionStore->getRevisionIdsBetween( $page->getId(), $revisions[0], $revisions[1] ),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
'Must return an empty array if the consecutive old and new revisions provided'
|
|
|
|
|
);
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
array_slice( $revisionIds, 1, -2 ),
|
|
|
|
|
$revisionStore->getRevisionIdsBetween( $page->getId(), $revisions[0], $revisions[$NUM - 2] ),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
'The result is non-inclusive on both ends if both beginning and end are provided'
|
|
|
|
|
);
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
array_slice( $revisionIds, 1, -1 ),
|
|
|
|
|
$revisionStore->getRevisionIdsBetween(
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$revisions[0],
|
|
|
|
|
$revisions[$NUM - 2],
|
|
|
|
|
null,
|
2020-08-17 18:22:17 +00:00
|
|
|
RevisionStore::INCLUDE_NEW
|
2020-07-22 16:07:50 +00:00
|
|
|
),
|
|
|
|
|
'The inclusion string options are respected'
|
|
|
|
|
);
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
array_slice( $revisionIds, 0, -1 ),
|
|
|
|
|
$revisionStore->getRevisionIdsBetween(
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$revisions[0],
|
|
|
|
|
$revisions[$NUM - 2],
|
|
|
|
|
null,
|
2020-08-17 18:22:17 +00:00
|
|
|
[ RevisionStore::INCLUDE_BOTH ]
|
2020-07-22 16:07:50 +00:00
|
|
|
),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
'The inclusion array options are respected'
|
|
|
|
|
);
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
array_slice( $revisionIds, 1 ),
|
|
|
|
|
$revisionStore->getRevisionIdsBetween( $page->getId(), $revisions[0] ),
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
'The result is inclusive on the end if the end is omitted'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayEquals(
|
|
|
|
|
array_reverse( array_slice( $revisionIds, 1, -2 ) ),
|
|
|
|
|
$revisionStore->getRevisionIdsBetween(
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$revisions[0],
|
|
|
|
|
$revisions[$NUM - 2],
|
|
|
|
|
null,
|
|
|
|
|
[],
|
|
|
|
|
RevisionStore::ORDER_NEWEST_TO_OLDEST
|
|
|
|
|
),
|
|
|
|
|
true,
|
|
|
|
|
false,
|
|
|
|
|
'$order parameter is respected'
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$MAX + 1, // Returns array of length $max + 1 to detect truncation.
|
|
|
|
|
count( $revisionStore->getRevisionIdsBetween(
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$revisions[0],
|
|
|
|
|
$revisions[$NUM - 1],
|
|
|
|
|
$MAX
|
|
|
|
|
) ),
|
|
|
|
|
'$max is incremented to detect truncation'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 20:10:58 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countRevisionsBetween
|
|
|
|
|
*/
|
|
|
|
|
public function testCountRevisionsBetween() {
|
|
|
|
|
$NUM = 5;
|
|
|
|
|
$MAX = 1;
|
2019-10-16 20:39:02 +00:00
|
|
|
$page = $this->getTestPage( __METHOD__ );
|
2019-10-16 20:10:58 +00:00
|
|
|
$revisions = [];
|
|
|
|
|
for ( $revNum = 0; $revNum < $NUM; $revNum++ ) {
|
|
|
|
|
$editStatus = $this->editPage( $page->getTitle()->getPrefixedDBkey(), 'Revision ' . $revNum );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision ' . $revNum );
|
2020-06-10 01:08:37 +00:00
|
|
|
$revisions[] = $editStatus->getValue()['revision-record'];
|
2019-10-16 20:10:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-01-09 23:23:19 +00:00
|
|
|
$this->assertSame( 0,
|
2019-10-16 20:39:02 +00:00
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0], $revisions[0] ),
|
|
|
|
|
'Must return 0 if the same old and new revisions provided' );
|
2020-01-09 23:23:19 +00:00
|
|
|
$this->assertSame( 0,
|
2019-10-16 20:39:02 +00:00
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0], $revisions[1] ),
|
|
|
|
|
'Must return 0 if the consecutive old and new revisions provided' );
|
|
|
|
|
$this->assertEquals( $NUM - 3,
|
|
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0], $revisions[$NUM - 2] ),
|
|
|
|
|
'The count is non-inclusive on both ends if both beginning and end are provided' );
|
2019-10-24 19:08:33 +00:00
|
|
|
$this->assertEquals( $NUM - 2,
|
|
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0], $revisions[$NUM - 2],
|
2020-08-17 18:22:17 +00:00
|
|
|
null, RevisionStore::INCLUDE_NEW ),
|
2019-10-24 19:08:33 +00:00
|
|
|
'The count string options are respected' );
|
|
|
|
|
$this->assertEquals( $NUM - 1,
|
|
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0], $revisions[$NUM - 2],
|
2020-08-17 18:22:17 +00:00
|
|
|
null, [ RevisionStore::INCLUDE_BOTH ] ),
|
2019-10-24 19:08:33 +00:00
|
|
|
'The count array options are respected' );
|
2019-10-16 20:39:02 +00:00
|
|
|
$this->assertEquals( $NUM - 1,
|
|
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0] ),
|
|
|
|
|
'The count is inclusive on the end if the end is omitted' );
|
|
|
|
|
$this->assertEquals( $NUM + 1, // There was one revision from creating a page, thus NUM + 1
|
|
|
|
|
$revisionStore->countRevisionsBetween( $page->getId() ),
|
|
|
|
|
'The count is inclusive if both beginning and end are omitted' );
|
2019-10-16 20:10:58 +00:00
|
|
|
$this->assertEquals( $MAX + 1, // Returns $max + 1 to detect truncation.
|
2019-10-16 20:39:02 +00:00
|
|
|
$revisionStore->countRevisionsBetween( $page->getId(), $revisions[0],
|
|
|
|
|
$revisions[$NUM - 1], $MAX ),
|
|
|
|
|
'The $max is incremented to detect truncation' );
|
2019-10-16 20:10:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-24 19:08:33 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getAuthorsBetween
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countAuthorsBetween
|
|
|
|
|
*/
|
|
|
|
|
public function testAuthorsBetween() {
|
|
|
|
|
$NUM = 5;
|
|
|
|
|
$page = $this->getTestPage( __METHOD__ );
|
|
|
|
|
$users = [
|
|
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
$this->getTestSysop()->getUser(),
|
|
|
|
|
new User(),
|
|
|
|
|
$this->getMutableTestUser()->getUser()
|
|
|
|
|
];
|
|
|
|
|
$revisions = [];
|
|
|
|
|
for ( $revNum = 0; $revNum < $NUM; $revNum++ ) {
|
|
|
|
|
$editStatus = $this->editPage(
|
|
|
|
|
$page->getTitle()->getPrefixedDBkey(),
|
|
|
|
|
'Revision ' . $revNum,
|
|
|
|
|
'',
|
|
|
|
|
NS_MAIN,
|
|
|
|
|
$users[$revNum] );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision ' . $revNum );
|
2020-06-10 01:08:37 +00:00
|
|
|
$revisions[] = $editStatus->getValue()['revision-record'];
|
2019-10-24 19:08:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-01-09 23:23:19 +00:00
|
|
|
$this->assertSame( 0,
|
2019-10-24 19:08:33 +00:00
|
|
|
$revisionStore->countAuthorsBetween( $page->getId(), $revisions[0], $revisions[0] ),
|
|
|
|
|
'countAuthorsBetween must return 0 if the same old and new revisions provided' );
|
|
|
|
|
$this->assertArrayEquals( [],
|
|
|
|
|
$revisionStore->getAuthorsBetween( $page->getId(), $revisions[0], $revisions[0] ),
|
|
|
|
|
'getAuthorsBetween must return [] if the same old and new revisions provided' );
|
|
|
|
|
|
2020-01-09 23:23:19 +00:00
|
|
|
$this->assertSame( 0,
|
2019-10-24 19:08:33 +00:00
|
|
|
$revisionStore->countAuthorsBetween( $page->getId(), $revisions[0], $revisions[1] ),
|
|
|
|
|
'countAuthorsBetween must return 0 if the consecutive old and new revisions provided' );
|
|
|
|
|
$this->assertArrayEquals( [],
|
|
|
|
|
$revisionStore->getAuthorsBetween( $page->getId(), $revisions[0], $revisions[1] ),
|
|
|
|
|
'getAuthorsBetween must return [] if the consecutive old and new revisions provided' );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( 2,
|
|
|
|
|
$revisionStore->countAuthorsBetween( $page->getId(), $revisions[0], $revisions[$NUM - 2] ),
|
|
|
|
|
'countAuthorsBetween is non-inclusive on both ends if both beginning and end are provided' );
|
|
|
|
|
$result = $revisionStore->getAuthorsBetween( $page->getId(),
|
|
|
|
|
$revisions[0], $revisions[$NUM - 2] );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertCount( 2, $result,
|
2019-10-24 19:08:33 +00:00
|
|
|
'getAuthorsBetween provides right number of users' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideBetweenMethodNames() {
|
2020-07-22 16:07:50 +00:00
|
|
|
yield [ 'getRevisionIdsBetween' ];
|
2019-10-24 19:08:33 +00:00
|
|
|
yield [ 'countRevisionsBetween' ];
|
|
|
|
|
yield [ 'countAuthorsBetween' ];
|
|
|
|
|
yield [ 'getAuthorsBetween' ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideBetweenMethodNames
|
|
|
|
|
*
|
2020-07-22 16:07:50 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionIdsBetween
|
2019-10-16 20:10:58 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countRevisionsBetween
|
2019-10-24 19:08:33 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countAuthorsBetween
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getAuthorsBetween
|
|
|
|
|
*
|
|
|
|
|
* @param string $method the name of the method to test
|
2019-10-16 20:10:58 +00:00
|
|
|
*/
|
2019-10-24 19:08:33 +00:00
|
|
|
public function testBetweenMethod_differentPages( $method ) {
|
2019-10-16 20:39:02 +00:00
|
|
|
$page1 = $this->getTestPage( __METHOD__ );
|
2019-10-16 20:10:58 +00:00
|
|
|
$page2 = $this->getTestPage( 'Other_Page' );
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), 'Revision 1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
$rev1 = $editStatus->getValue()['revision-record'];
|
2019-10-16 20:10:58 +00:00
|
|
|
$editStatus = $this->editPage( $page2->getTitle()->getPrefixedDBkey(), 'Revision 1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
2020-06-10 01:08:37 +00:00
|
|
|
$rev2 = $editStatus->getValue()['revision-record'];
|
2019-10-16 20:10:58 +00:00
|
|
|
|
|
|
|
|
$this->expectException( InvalidArgumentException::class );
|
2019-10-16 20:39:02 +00:00
|
|
|
MediaWikiServices::getInstance()->getRevisionStore()
|
2019-10-24 19:08:33 +00:00
|
|
|
->{$method}( $page1->getId(), $rev1, $rev2 );
|
2019-10-16 20:10:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-10-24 19:08:33 +00:00
|
|
|
* @dataProvider provideBetweenMethodNames
|
|
|
|
|
*
|
2020-07-22 16:07:50 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionIdsBetween
|
2019-10-16 20:10:58 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countRevisionsBetween
|
2019-10-24 19:08:33 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::countAuthorsBetween
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getAuthorsBetween
|
|
|
|
|
*
|
|
|
|
|
* @param string $method the name of the method to test
|
2019-10-16 20:10:58 +00:00
|
|
|
*/
|
2019-10-24 19:08:33 +00:00
|
|
|
public function testBetweenMethod_unsavedRevision( $method ) {
|
2019-10-16 20:10:58 +00:00
|
|
|
$rev1 = new MutableRevisionRecord( $this->getTestPageTitle() );
|
|
|
|
|
$rev2 = new MutableRevisionRecord( $this->getTestPageTitle() );
|
|
|
|
|
|
|
|
|
|
$this->expectException( InvalidArgumentException::class );
|
2019-10-24 19:08:33 +00:00
|
|
|
MediaWikiServices::getInstance()->getRevisionStore()->{$method}(
|
|
|
|
|
$this->getTestPage()->getId(), $rev1, $rev2 );
|
2019-10-16 20:10:58 +00:00
|
|
|
}
|
2020-03-07 07:07:42 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getFirstRevision
|
2021-02-08 16:33:25 +00:00
|
|
|
*
|
|
|
|
|
* @dataProvider provideGetFirstRevision
|
|
|
|
|
* @param callable $getPageIdentity
|
2020-03-07 07:07:42 +00:00
|
|
|
*/
|
2021-02-08 16:33:25 +00:00
|
|
|
public function testGetFirstRevision( $getPageIdentity ) {
|
|
|
|
|
list( $pageTitle, $pageIdentity ) = $getPageIdentity();
|
2020-03-07 07:07:42 +00:00
|
|
|
$editStatus = $this->editPage( $pageTitle->getPrefixedDBkey(), 'First Revision' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create first revision' );
|
2020-06-10 01:08:37 +00:00
|
|
|
$firstRevId = $editStatus->getValue()['revision-record']->getID();
|
2020-03-07 07:07:42 +00:00
|
|
|
$editStatus = $this->editPage( $pageTitle->getPrefixedText(), 'New Revision' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create new revision' );
|
|
|
|
|
$this->assertNotSame(
|
|
|
|
|
$firstRevId,
|
2020-06-10 01:08:37 +00:00
|
|
|
$editStatus->getValue()['revision-record']->getID(),
|
2020-03-07 07:07:42 +00:00
|
|
|
'Sanity: new revision must have different id'
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$firstRevId,
|
|
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStore()
|
2021-02-08 16:33:25 +00:00
|
|
|
->getFirstRevision( $pageIdentity )
|
2020-03-07 07:07:42 +00:00
|
|
|
->getId()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-08 16:33:25 +00:00
|
|
|
public function provideGetFirstRevision() {
|
|
|
|
|
return [
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_Get_First_Revision' );
|
|
|
|
|
return [ $pageTitle, $pageTitle ];
|
|
|
|
|
} ],
|
2021-02-10 22:31:02 +00:00
|
|
|
[ static function () {
|
2021-02-08 16:33:25 +00:00
|
|
|
$pageTitle = Title::newFromText( 'Test_Get_First_Revision' );
|
|
|
|
|
return [ $pageTitle, $pageTitle->toPageIdentity() ];
|
|
|
|
|
} ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-07 07:07:42 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getFirstRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetFirstRevision_nonexistent_page() {
|
|
|
|
|
$this->assertNull(
|
|
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStore()
|
|
|
|
|
->getFirstRevision( $this->getNonexistingTestPage( __METHOD__ )->getTitle() )
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-02-04 02:43:09 +00:00
|
|
|
|
|
|
|
|
public function provideInsertRevisionByAnonAssignsNewActor() {
|
2021-02-10 22:31:02 +00:00
|
|
|
yield 'User' => [ '127.1.1.0', static function ( MediaWikiServices $services, string $ip ) {
|
2021-02-04 02:43:09 +00:00
|
|
|
return $services->getUserFactory()->newAnonymous( $ip );
|
|
|
|
|
} ];
|
2021-02-10 22:31:02 +00:00
|
|
|
yield 'User identity, anon' => [ '127.1.1.1', static function ( MediaWikiServices $services, string $ip ) {
|
2021-02-15 18:58:09 +00:00
|
|
|
return new UserIdentityValue( 0, $ip );
|
2021-02-04 02:43:09 +00:00
|
|
|
} ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideInsertRevisionByAnonAssignsNewActor
|
2021-06-24 15:25:39 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
2021-02-04 02:43:09 +00:00
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionByAnonAssignsNewActor( string $ip, callable $userInitCallback ) {
|
|
|
|
|
$user = $userInitCallback( $this->getServiceContainer(), $ip );
|
2021-02-15 18:58:09 +00:00
|
|
|
|
|
|
|
|
$actorNormalization = $this->getServiceContainer()->getActorNormalization();
|
|
|
|
|
$actorId = $actorNormalization->findActorId( $user, $this->db );
|
|
|
|
|
$this->assertNull( $actorId, 'Sanity, new actor has no actor_id' );
|
2021-02-04 02:43:09 +00:00
|
|
|
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$rev = new MutableRevisionRecord( $page->getTitle() );
|
|
|
|
|
$rev->setTimestamp( '20180101000000' )
|
|
|
|
|
->setComment( CommentStoreComment::newUnsavedComment( 'test' ) )
|
|
|
|
|
->setUser( $user )
|
|
|
|
|
->setContent( 'main', new WikitextContent( 'Text' ) )
|
|
|
|
|
->setPageId( $page->getId() );
|
|
|
|
|
|
|
|
|
|
$return = $this->getServiceContainer()->getRevisionStore()->insertRevisionOn( $rev, $this->db );
|
|
|
|
|
$this->assertSame( $ip, $return->getUser()->getName() );
|
2021-02-15 18:58:09 +00:00
|
|
|
|
|
|
|
|
$actorId = $actorNormalization->findActorId( $user, $this->db );
|
|
|
|
|
$this->assertNotNull( $actorId );
|
2021-02-04 02:43:09 +00:00
|
|
|
}
|
2021-03-17 22:13:35 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTitle
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTitle_successFromPageId() {
|
|
|
|
|
$page = $this->getExistingTestPage();
|
|
|
|
|
|
|
|
|
|
$store = $this->getServiceContainer()->getRevisionStore();
|
|
|
|
|
$title = $store->getTitle( $page->getId(), 0, RevisionStore::READ_NORMAL );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $page->isSamePageAs( $title ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTitle
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTitle_successFromRevId() {
|
|
|
|
|
$page = $this->getExistingTestPage();
|
|
|
|
|
|
|
|
|
|
$store = $this->getServiceContainer()->getRevisionStore();
|
|
|
|
|
$title = $store->getTitle( 0, $page->getLatest(), RevisionStore::READ_NORMAL );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $page->isSamePageAs( $title ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getTitle
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTitle_failure() {
|
|
|
|
|
$store = $this->getServiceContainer()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$this->expectException( RevisionAccessException::class );
|
|
|
|
|
$store->getTitle( 113349857, 897234779, RevisionStore::READ_NORMAL );
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-24 15:25:39 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getQueryInfo
|
|
|
|
|
*/
|
|
|
|
|
public function testGetQueryInfo_NoSlotDataJoin() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$queryInfo = $store->getQueryInfo();
|
|
|
|
|
|
|
|
|
|
// with the new schema enabled, query info should not join the main slot info
|
|
|
|
|
$this->assertArrayNotHasKey( 'a_slot_data', $queryInfo['tables'] );
|
|
|
|
|
$this->assertArrayNotHasKey( 'a_slot_data', $queryInfo['joins'] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertSlotRowOn
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertContentRowOn
|
|
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionOn_T202032() {
|
|
|
|
|
// This test only makes sense for MySQL
|
|
|
|
|
if ( $this->db->getType() !== 'mysql' ) {
|
|
|
|
|
$this->assertTrue( true );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: must be done before checking MAX(rev_id)
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
|
|
|
|
|
$maxRevId = $this->db->selectField( 'revision', 'MAX(rev_id)' );
|
|
|
|
|
|
|
|
|
|
// Construct a slot row that will conflict with the insertion of the next revision ID,
|
|
|
|
|
// to emulate the failure mode described in T202032. Nothing will ever read this row,
|
|
|
|
|
// we just need it to trigger a primary key conflict.
|
|
|
|
|
$this->db->insert( 'slots', [
|
|
|
|
|
'slot_revision_id' => $maxRevId + 1,
|
|
|
|
|
'slot_role_id' => 1,
|
|
|
|
|
'slot_content_id' => 0,
|
|
|
|
|
'slot_origin' => 0
|
|
|
|
|
], __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$rev = new MutableRevisionRecord( $page->getTitle() );
|
|
|
|
|
$rev->setTimestamp( '20180101000000' )
|
|
|
|
|
->setComment( CommentStoreComment::newUnsavedComment( 'test' ) )
|
|
|
|
|
->setUser( $this->getTestUser()->getUser() )
|
|
|
|
|
->setContent( 'main', new WikitextContent( 'Text' ) )
|
|
|
|
|
->setPageId( $page->getId() );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$return = $store->insertRevisionOn( $rev, $this->db );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $maxRevId + 2, $return->getId() );
|
|
|
|
|
|
|
|
|
|
// is the new revision correct?
|
|
|
|
|
$this->assertRevisionCompleteness( $return );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $rev, $return );
|
|
|
|
|
|
|
|
|
|
// can we find it directly in the database?
|
|
|
|
|
$this->assertRevisionExistsInDatabase( $return );
|
|
|
|
|
|
|
|
|
|
// can we load it from the store?
|
|
|
|
|
$loaded = $store->getRevisionById( $return->getId() );
|
|
|
|
|
$this->assertRevisionCompleteness( $loaded );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $return, $loaded );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getContentBlobsForBatch
|
|
|
|
|
* @throws \MWException
|
|
|
|
|
*/
|
|
|
|
|
public function testGetContentBlobsForBatch_error() {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
|
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
|
|
|
|
|
|
|
|
|
$contentAddress = $revRecord1->getSlot( SlotRecord::MAIN )->getAddress();
|
|
|
|
|
$blobStatus = StatusValue::newGood( [] );
|
|
|
|
|
$blobStatus->warning( 'internalerror_info', 'oops!' );
|
|
|
|
|
|
|
|
|
|
$mockBlobStore = $this->createMock( BlobStore::class );
|
|
|
|
|
$mockBlobStore->method( 'getBlobBatch' )
|
|
|
|
|
->willReturn( $blobStatus );
|
|
|
|
|
|
|
|
|
|
$revStore = MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore();
|
|
|
|
|
$wrappedRevStore = TestingAccessWrapper::newFromObject( $revStore );
|
|
|
|
|
$wrappedRevStore->blobStore = $mockBlobStore;
|
|
|
|
|
|
|
|
|
|
$result = $revStore->getContentBlobsForBatch( [ $revRecord1->getId() ] );
|
|
|
|
|
$this->assertTrue( $result->isOK() );
|
|
|
|
|
$this->assertFalse( $result->isGood() );
|
|
|
|
|
$this->assertNotEmpty( $result->getErrors() );
|
|
|
|
|
|
|
|
|
|
$records = $result->getValue();
|
|
|
|
|
$this->assertArrayHasKey( $revRecord1->getId(), $records );
|
|
|
|
|
|
|
|
|
|
$mainRow = $records[$revRecord1->getId()][SlotRecord::MAIN];
|
|
|
|
|
$this->assertNull( $mainRow->blob_data );
|
|
|
|
|
$this->assertSame( [
|
|
|
|
|
[
|
|
|
|
|
'type' => 'warning',
|
|
|
|
|
'message' => 'internalerror_info',
|
|
|
|
|
'params' => [
|
|
|
|
|
"oops!"
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'type' => 'warning',
|
|
|
|
|
'message' => 'internalerror_info',
|
|
|
|
|
'params' => [
|
|
|
|
|
"Couldn't find blob data for rev " . $revRecord1->getId()
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
], $result->getErrors() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getContentBlobsForBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testGetContentBlobsForBatchUsesGetBlobBatch() {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
|
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
|
|
|
|
|
|
|
|
|
$contentAddress = $revRecord1->getSlot( SlotRecord::MAIN )->getAddress();
|
|
|
|
|
$mockBlobStore = $this->getMockBuilder( SqlBlobStore::class )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->getMock();
|
|
|
|
|
$mockBlobStore
|
|
|
|
|
->expects( $this->once() )
|
|
|
|
|
->method( 'getBlobBatch' )
|
|
|
|
|
->with( [ $contentAddress ], $this->anything() )
|
|
|
|
|
->willReturn( StatusValue::newGood( [
|
|
|
|
|
$contentAddress => 'Content_From_Mock'
|
|
|
|
|
] ) );
|
|
|
|
|
$mockBlobStore
|
|
|
|
|
->expects( $this->never() )
|
|
|
|
|
->method( 'getBlob' );
|
|
|
|
|
|
|
|
|
|
$revStore = MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore();
|
|
|
|
|
$wrappedRevStore = TestingAccessWrapper::newFromObject( $revStore );
|
|
|
|
|
$wrappedRevStore->blobStore = $mockBlobStore;
|
|
|
|
|
|
|
|
|
|
$result = $revStore->getContentBlobsForBatch(
|
|
|
|
|
[ $revRecord1->getId() ],
|
|
|
|
|
[ SlotRecord::MAIN ]
|
|
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
|
|
|
|
$this->assertSame( 'Content_From_Mock',
|
|
|
|
|
$result->getValue()[$revRecord1->getId()][SlotRecord::MAIN]->blob_data );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
* @throws \MWException
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_error() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $page->doUserEditContent(
|
|
|
|
|
new WikitextContent( $text . '1' ),
|
|
|
|
|
$this->getTestUser()->getUser(),
|
|
|
|
|
__METHOD__ . 'b'
|
|
|
|
|
)->value['revision-record'];
|
|
|
|
|
|
|
|
|
|
$invalidRow = $this->revisionRecordToRow( $revRecord1 );
|
|
|
|
|
$invalidRow->rev_id = 100500;
|
|
|
|
|
$result = MediaWikiServices::getInstance()->getRevisionStore()
|
|
|
|
|
->newRevisionsFromBatch(
|
|
|
|
|
[ $this->revisionRecordToRow( $revRecord1 ), $invalidRow ],
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$this->assertFalse( $result->isGood() );
|
|
|
|
|
$this->assertNotEmpty( $result->getErrors() );
|
|
|
|
|
$records = $result->getValue();
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revRecord1, $records[$revRecord1->getId()] );
|
|
|
|
|
$this->assertSame( $text . '1',
|
|
|
|
|
$records[$revRecord1->getId()]->getContent( SlotRecord::MAIN )->serialize() );
|
|
|
|
|
$this->assertEquals( $page->getTitle()->getDBkey(),
|
|
|
|
|
$records[$revRecord1->getId()]->getPageAsLinkTarget()->getDBkey() );
|
|
|
|
|
$this->assertNull( $records[$invalidRow->rev_id] );
|
|
|
|
|
$this->assertSame( [ [
|
|
|
|
|
'type' => 'warning',
|
|
|
|
|
'message' => 'internalerror_info',
|
|
|
|
|
'params' => [
|
|
|
|
|
"Couldn't find slots for rev 100500"
|
|
|
|
|
]
|
|
|
|
|
] ], $result->getErrors() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromBatchUsesGetBlobBatch() {
|
|
|
|
|
$page1 = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'b-ä';
|
|
|
|
|
$editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
|
|
|
|
|
$this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
|
|
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
$revRecord1 = $editStatus->getValue()['revision-record'];
|
|
|
|
|
|
|
|
|
|
$contentAddress = $revRecord1->getSlot( SlotRecord::MAIN )->getAddress();
|
|
|
|
|
$mockBlobStore = $this->getMockBuilder( SqlBlobStore::class )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->getMock();
|
|
|
|
|
$mockBlobStore
|
|
|
|
|
->expects( $this->once() )
|
|
|
|
|
->method( 'getBlobBatch' )
|
|
|
|
|
->with( [ $contentAddress ], $this->anything() )
|
|
|
|
|
->willReturn( StatusValue::newGood( [
|
|
|
|
|
$contentAddress => 'Content_From_Mock'
|
|
|
|
|
] ) );
|
|
|
|
|
$mockBlobStore
|
|
|
|
|
->expects( $this->never() )
|
|
|
|
|
->method( 'getBlob' );
|
|
|
|
|
|
|
|
|
|
$revStore = MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStoreFactory()
|
|
|
|
|
->getRevisionStore();
|
|
|
|
|
$wrappedRevStore = TestingAccessWrapper::newFromObject( $revStore );
|
|
|
|
|
$wrappedRevStore->blobStore = $mockBlobStore;
|
|
|
|
|
|
|
|
|
|
$result = $revStore->newRevisionsFromBatch(
|
|
|
|
|
[ $this->revisionRecordToRow( $revRecord1 ) ],
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result->isGood() );
|
|
|
|
|
$this->assertSame( 'Content_From_Mock',
|
|
|
|
|
ContentHandler::getContentText( $result->getValue()[$revRecord1->getId()]
|
|
|
|
|
->getContent( SlotRecord::MAIN ) ) );
|
|
|
|
|
}
|
2021-07-24 15:23:49 +00:00
|
|
|
|
|
|
|
|
/** @covers \MediaWiki\Revision\RevisionStore::findIdenticalRevision */
|
|
|
|
|
public function testFindIdenticalRevision() {
|
|
|
|
|
// Prepare a page with 3 revisions
|
|
|
|
|
$page = $this->getExistingTestPage( __METHOD__ );
|
|
|
|
|
$status = $this->editPage( $page, 'Content 1' );
|
|
|
|
|
$this->assertTrue( $status->isGood(), 'Sanity: edit 1' );
|
|
|
|
|
$originalRev = $status->value[ 'revision-record' ];
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $this->editPage( $page, 'Content 2' )->isGood(), 'Sanity: edit 2' );
|
|
|
|
|
|
|
|
|
|
$status = $this->editPage( $page, 'Content 1' );
|
|
|
|
|
$this->assertTrue( $status->isGood(), 'Sanity: edit 3' );
|
|
|
|
|
$latestRev = $status->value[ 'revision-record' ];
|
|
|
|
|
|
|
|
|
|
$store = $this->getServiceContainer()->getRevisionStore();
|
|
|
|
|
|
|
|
|
|
$this->assertNull( $store->findIdenticalRevision( $latestRev, 0 ) );
|
|
|
|
|
$this->assertNull( $store->findIdenticalRevision( $latestRev, 1 ) );
|
|
|
|
|
$foundRev = $store->findIdenticalRevision( $latestRev, 1000 );
|
|
|
|
|
$this->assertSame( $originalRev->getId(), $foundRev->getId() );
|
|
|
|
|
}
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|