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;
|
2019-12-17 16:20:32 +00:00
|
|
|
use JavaScriptContent;
|
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;
|
2018-09-20 17:29:04 +00:00
|
|
|
use MediaWiki\Revision\IncompleteRevisionException;
|
|
|
|
|
use MediaWiki\Revision\MutableRevisionRecord;
|
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;
|
2018-01-12 11:52:31 +00:00
|
|
|
use MediaWiki\Storage\BlobStoreFactory;
|
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;
|
2017-11-15 12:02:40 +00:00
|
|
|
use Revision;
|
|
|
|
|
use TestUserRegistry;
|
|
|
|
|
use Title;
|
2020-03-28 19:44:25 +00:00
|
|
|
use TitleValue;
|
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;
|
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
|
|
|
*/
|
2020-06-30 15:09:24 +00:00
|
|
|
abstract class RevisionStoreDbTestBase 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;
|
|
|
|
|
|
2020-06-14 10:51:39 +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.
|
|
|
|
|
$this->ensureMockDatabaseConnection( wfGetDB( DB_MASTER ) );
|
|
|
|
|
|
|
|
|
|
$user = static::getTestSysop()->getUser();
|
|
|
|
|
|
2019-08-30 18:26:00 +00:00
|
|
|
$page->doEditContent(
|
2018-04-17 07:49:20 +00:00
|
|
|
new WikitextContent( 'UTContent-' . __CLASS__ ),
|
|
|
|
|
'UTPageSummary-' . __CLASS__,
|
|
|
|
|
EDIT_NEW | EDIT_SUPPRESS_RC,
|
|
|
|
|
false,
|
|
|
|
|
$user
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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 )
|
|
|
|
|
->setMethods( [ '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 )
|
|
|
|
|
->setMethods( [ 'select', 'doQuery', 'open', 'closeConnection', 'isOpen' ] )
|
|
|
|
|
->setConstructorArgs( [ $params ] )
|
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$db->method( 'select' )->willReturn( new FakeResultWrapper( [] ) );
|
|
|
|
|
$db->method( 'isOpen' )->willReturn( true );
|
|
|
|
|
|
|
|
|
|
return $db;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideDomainCheck() {
|
|
|
|
|
yield [ false, 'test', '' ];
|
|
|
|
|
yield [ 'test', 'test', '' ];
|
|
|
|
|
|
|
|
|
|
yield [ false, 'test', 'foo_' ];
|
|
|
|
|
yield [ 'test-foo_', 'test', 'foo_' ];
|
|
|
|
|
|
|
|
|
|
yield [ false, 'dash-test', '' ];
|
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' => '',
|
|
|
|
|
'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(),
|
2018-04-05 16:13:08 +00:00
|
|
|
'errorLogger' => function () {
|
|
|
|
|
},
|
|
|
|
|
'deprecationLogger' => function () {
|
|
|
|
|
},
|
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(),
|
2020-01-18 20:25:04 +00:00
|
|
|
MediaWikiServices::getInstance()->getContentHandlerFactory(),
|
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,
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
|
$return = $store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
|
|
|
|
|
|
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 ) {
|
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 );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$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() );
|
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 );
|
2017-11-15 12:02:40 +00:00
|
|
|
$firstReturn = $store->insertRevisionOn( $revOne, wfGetDB( DB_MASTER ) );
|
|
|
|
|
$this->assertLinkTargetsEqual( $title, $firstReturn->getPageAsLinkTarget() );
|
|
|
|
|
$this->assertRevisionRecordsEqual( $revOne, $firstReturn );
|
|
|
|
|
|
|
|
|
|
// Insert a second revision inheriting the same blob address
|
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 );
|
2017-11-15 12:02:40 +00:00
|
|
|
$secondReturn = $store->insertRevisionOn( $revTwo, wfGetDB( DB_MASTER ) );
|
|
|
|
|
$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() );
|
2017-11-15 12:02:40 +00:00
|
|
|
$store->insertRevisionOn( $rev, wfGetDB( DB_MASTER ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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() ) {
|
|
|
|
|
$page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__, EDIT_NEW );
|
|
|
|
|
}
|
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
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
$dbw = wfGetDB( DB_MASTER );
|
|
|
|
|
$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(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
$title,
|
|
|
|
|
$comment,
|
|
|
|
|
$minor,
|
|
|
|
|
$user
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $title->getNamespace(), $record->getPageAsLinkTarget()->getNamespace() );
|
|
|
|
|
$this->assertEquals( $title->getDBkey(), $record->getPageAsLinkTarget()->getDBkey() );
|
|
|
|
|
$this->assertEquals( $comment, $record->getComment() );
|
|
|
|
|
$this->assertEquals( $minor, $record->isMinor() );
|
|
|
|
|
$this->assertEquals( $user->getName(), $record->getUser()->getName() );
|
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(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
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();
|
2017-11-15 12:02:40 +00:00
|
|
|
$status = $page->doEditContent( new WikitextContent( __METHOD__ ), __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();
|
2017-11-15 12:02:40 +00:00
|
|
|
$status = $page->doEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
__METHOD__,
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
$sysop
|
|
|
|
|
);
|
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() {
|
2020-03-30 17:52:30 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getRecentChange' );
|
2020-07-02 06:22:52 +00:00
|
|
|
$this->hideDeprecated( 'Revision::__construct' );
|
2020-03-30 17:52:30 +00:00
|
|
|
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __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' ) );
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
( new Revision( $revRecord ) )->getRecentChange(),
|
|
|
|
|
$recentChange
|
|
|
|
|
);
|
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__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __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 );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
2020-07-19 19:50:37 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getRevisionById
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRevisionById_undefinedContentModel() {
|
|
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$content = new WikitextContent( __METHOD__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __METHOD__ );
|
|
|
|
|
/** @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
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetRevisionByTitle() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __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->getRevisionByTitle( $page->getTitle() );
|
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
|
|
|
}
|
|
|
|
|
|
2020-03-28 19:44:25 +00:00
|
|
|
/**
|
|
|
|
|
* 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__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __METHOD__ );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $status->value['revision-record'];
|
2020-03-28 19:44:25 +00:00
|
|
|
|
|
|
|
|
$dbDomain = 'some_foreign_wiki';
|
|
|
|
|
|
|
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
|
|
|
|
|
|
// 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(),
|
|
|
|
|
$services->getContentHandlerFactory(),
|
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 );
|
2020-03-28 19:44:25 +00:00
|
|
|
|
2021-01-11 22:36:33 +00:00
|
|
|
$storeRecord = $store->getRevisionByTitle(
|
|
|
|
|
new TitleValue( $page->getTitle()->getNamespace(), $page->getTitle()->getDBkey() )
|
|
|
|
|
);
|
2021-01-25 14:22:41 +00:00
|
|
|
$this->assertSame( $revRecord->getId(), $storeRecord->getId( $dbDomain ) );
|
2021-01-11 22:36:33 +00:00
|
|
|
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
|
|
|
|
|
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
|
|
|
|
|
} finally {
|
|
|
|
|
// Restore the original load balancer to make test teardown work
|
|
|
|
|
$this->setService( 'DBLoadBalancer', $dbLoadBalancer );
|
|
|
|
|
}
|
2020-03-28 19:44:25 +00:00
|
|
|
}
|
|
|
|
|
|
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__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __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
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
2017-12-27 15:46:03 +00:00
|
|
|
public function testGetRevisionByTimestamp() {
|
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();
|
2020-04-02 20:12:09 +00:00
|
|
|
MWTimestamp::setFakeTime( '20110401090001' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$content = new WikitextContent( __METHOD__ );
|
|
|
|
|
$status = $page->doEditContent( $content, __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(
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getTitle(),
|
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
|
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
$fields = [
|
|
|
|
|
'rev_id' => (string)$revRecord->getId(),
|
|
|
|
|
'rev_page' => (string)$revRecord->getPageId(),
|
|
|
|
|
'rev_timestamp' => $this->db->timestamp( $revRecord->getTimestamp() ),
|
|
|
|
|
'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() {
|
2020-06-08 02:38:00 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContent' );
|
|
|
|
|
|
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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2019-05-19 08:48:10 +00:00
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
__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() {
|
2020-06-08 02:38:00 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContent' );
|
|
|
|
|
|
2018-09-03 17:15:37 +00:00
|
|
|
$page = $this->getTestPage();
|
|
|
|
|
$text = __METHOD__ . 'a-ä';
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2018-09-03 17:15:37 +00:00
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
__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() {
|
2020-06-08 02:38:00 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContent' );
|
|
|
|
|
|
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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
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() {
|
2020-06-08 02:38:00 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContent' );
|
|
|
|
|
|
2018-01-12 11:52:31 +00:00
|
|
|
$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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
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() {
|
2020-06-08 02:38:00 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContent' );
|
|
|
|
|
|
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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2018-01-12 11:52:31 +00:00
|
|
|
new WikitextContent( $text ),
|
2017-11-15 12:02:40 +00:00
|
|
|
__METHOD__ . 'b',
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
$this->getTestUser()->getUser()
|
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
|
|
|
}
|
|
|
|
|
|
2020-03-30 20:20:27 +00:00
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newRevisionFromArchiveRowAndSlots
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getArchiveQueryInfo
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionFromArchiveRowAndSlots_getArchiveQueryInfo() {
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
$text = __METHOD__ . '-bä';
|
|
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2020-03-30 20:20:27 +00:00
|
|
|
$orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
|
2020-06-10 01:08:37 +00:00
|
|
|
->value['revision-record'];
|
2020-03-30 20:20:27 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
|
|
|
|
|
$db = wfGetDB( DB_MASTER );
|
|
|
|
|
$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();
|
2020-06-10 01:08:37 +00:00
|
|
|
$storeRecord = $store->newRevisionFromArchiveRowAndSlots(
|
|
|
|
|
$row,
|
|
|
|
|
iterator_to_array( $slotRows )
|
|
|
|
|
);
|
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 */
|
2018-01-12 11:52:31 +00:00
|
|
|
$orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
|
2020-06-10 01:08:37 +00:00
|
|
|
->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
|
|
|
|
|
|
|
|
$db = wfGetDB( DB_MASTER );
|
|
|
|
|
$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 */
|
2018-01-12 11:52:31 +00:00
|
|
|
$orig = $page->doEditContent( new WikitextContent( $text ), __METHOD__ )
|
2020-06-10 01:08:37 +00:00
|
|
|
->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
|
|
|
|
|
|
|
|
$db = wfGetDB( DB_MASTER );
|
|
|
|
|
$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() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
|
2018-04-10 15:31:33 +00:00
|
|
|
*/
|
|
|
|
|
public function testInsertRevisionOn_archive() {
|
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.
|
|
|
|
|
|
2018-04-10 15:31:33 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $orig */
|
2018-04-10 15:31:33 +00:00
|
|
|
$page->doEditContent( new WikitextContent( "First" ), __METHOD__ . '-first' );
|
2020-06-10 01:08:37 +00:00
|
|
|
$orig = $page->doEditContent( new WikitextContent( "Foo" ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
2020-03-25 17:05:26 +00:00
|
|
|
$page->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
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
|
|
|
|
|
$page->doEditContent( new WikitextContent( 'Two' ), __METHOD__ );
|
|
|
|
|
|
2018-04-10 15:31:33 +00:00
|
|
|
$db = wfGetDB( DB_MASTER );
|
|
|
|
|
$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,
|
|
|
|
|
$title,
|
|
|
|
|
[ '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
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::loadRevisionFromPageId
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testLoadRevisionFromPageId() {
|
|
|
|
|
$title = Title::newFromText( __METHOD__ );
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-03-04 01:02:30 +00:00
|
|
|
$this->hideDeprecated( RevisionStore::class . '::loadRevisionFromPageId' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$result = $store->loadRevisionFromPageId( wfGetDB( DB_MASTER ), $page->getId() );
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $result );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::loadRevisionFromTitle
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testLoadRevisionFromTitle() {
|
2020-02-29 00:22:03 +00:00
|
|
|
$this->hideDeprecated( RevisionStore::class . '::loadRevisionFromTitle' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$title = Title::newFromText( __METHOD__ );
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->loadRevisionFromTitle( wfGetDB( DB_MASTER ), $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertRevisionRecordsEqual( $revRecord, $result );
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::loadRevisionFromTimestamp
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testLoadRevisionFromTimestamp() {
|
2020-04-02 20:12:09 +00:00
|
|
|
MWTimestamp::setFakeTime( '20110401090000' );
|
2018-04-17 07:49:20 +00:00
|
|
|
$title = Title::newFromText( __METHOD__ );
|
|
|
|
|
$page = WikiPage::factory( $title );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecordOne */
|
|
|
|
|
$revRecordOne = $page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
2020-04-02 20:12:09 +00:00
|
|
|
// Ensure different timestamps...
|
|
|
|
|
MWTimestamp::setFakeTime( '20110401090001' );
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
|
|
|
|
$revRecordTwo = $page->doEditContent( new WikitextContent( __METHOD__ . 'a' ), '' )
|
|
|
|
|
->value['revision-record'];
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
2020-03-04 01:39:36 +00:00
|
|
|
$this->hideDeprecated( RevisionStore::class . '::loadRevisionFromTimestamp' );
|
2017-11-15 12:02:40 +00:00
|
|
|
$this->assertNull(
|
|
|
|
|
$store->loadRevisionFromTimestamp( wfGetDB( DB_MASTER ), $title, '20150101010101' )
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordOne->getId(),
|
2017-11-15 12:02:40 +00:00
|
|
|
$store->loadRevisionFromTimestamp(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
$title,
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordOne->getTimestamp()
|
2017-11-15 12:02:40 +00:00
|
|
|
)->getId()
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordTwo->getId(),
|
2017-11-15 12:02:40 +00:00
|
|
|
$store->loadRevisionFromTimestamp(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
$title,
|
2020-06-10 01:08:37 +00:00
|
|
|
$revRecordTwo->getTimestamp()
|
2017-11-15 12:02:40 +00:00
|
|
|
)->getId()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
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 */
|
|
|
|
|
$revRecordOne = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ ), __METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
|
|
|
|
$revRecordTwo = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ . '2' ), __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 */
|
|
|
|
|
$revRecordOne = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ ), __METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
|
|
|
|
$revRecordTwo = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ . '2' ), __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 */
|
|
|
|
|
$revRecordOne = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ ), __METHOD__
|
2020-06-10 01:08:37 +00:00
|
|
|
)->value['revision-record'];
|
|
|
|
|
/** @var RevisionRecord $revRecordTwo */
|
|
|
|
|
$revRecordTwo = $page->doEditContent(
|
2017-11-15 12:02:40 +00:00
|
|
|
new WikitextContent( __METHOD__ . '2' ), __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 ];
|
|
|
|
|
|
|
|
|
|
$user = new UserIdentityValue( 7, 'Frank', 0 );
|
|
|
|
|
$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 */
|
|
|
|
|
$revRecord = $page->doEditContent( new WikitextContent( __METHOD__ ), __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 */
|
|
|
|
|
$revRecord = $page->doEditContent( new WikitextContent( __METHOD__ ), __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__ ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
0,
|
|
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
|
|
|
|
|
);
|
|
|
|
|
$page->doEditContent( new WikitextContent( 'a' ), 'a' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
1,
|
|
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
|
|
|
|
|
);
|
|
|
|
|
$page->doEditContent( new WikitextContent( 'b' ), 'b' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
2,
|
|
|
|
|
$store->countRevisionsByPageId( wfGetDB( DB_MASTER ), $page->getId() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
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__ ) );
|
2017-11-15 12:02:40 +00:00
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
0,
|
|
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
|
|
|
|
|
);
|
|
|
|
|
$page->doEditContent( new WikitextContent( 'a' ), 'a' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
1,
|
|
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
|
|
|
|
|
);
|
|
|
|
|
$page->doEditContent( new WikitextContent( 'b' ), 'b' );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
2,
|
|
|
|
|
$store->countRevisionsByTitle( wfGetDB( DB_MASTER ), $page->getTitle() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
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();
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->userWasLastToEdit(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
$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();
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->doEditContent(
|
|
|
|
|
new WikitextContent( __METHOD__ ),
|
|
|
|
|
__METHOD__,
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
$sysop
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
$result = $store->userWasLastToEdit(
|
|
|
|
|
wfGetDB( DB_MASTER ),
|
|
|
|
|
$page->getId(),
|
|
|
|
|
$sysop->getId(),
|
|
|
|
|
$startTime
|
|
|
|
|
);
|
|
|
|
|
$this->assertTrue( $result );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getKnownCurrentRevision
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testGetKnownCurrentRevision() {
|
2018-04-17 07:49:20 +00:00
|
|
|
$page = $this->getTestPage();
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2017-09-12 17:12:29 +00:00
|
|
|
new WikitextContent( __METHOD__ . 'b' ),
|
2017-11-15 12:02:40 +00:00
|
|
|
__METHOD__ . 'b',
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
$this->getTestUser()->getUser()
|
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(
|
2017-11-15 12:02:40 +00:00
|
|
|
$page->getTitle(),
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideNewMutableRevisionFromArray() {
|
|
|
|
|
yield 'Basic array, content object' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
|
|
|
|
'content' => new WikitextContent( 'Some Content' ),
|
|
|
|
|
]
|
|
|
|
|
];
|
2018-01-12 11:52:31 +00:00
|
|
|
yield 'Basic array, serialized text' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
|
|
|
|
'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
|
|
|
|
|
]
|
|
|
|
|
];
|
2019-12-17 16:20:32 +00:00
|
|
|
yield 'Basic array, serialized text and model' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
|
|
|
|
'text' => ( new JavaScriptContent( 'alert("test")' ) )->serialize(),
|
|
|
|
|
'content_model' => CONTENT_MODEL_JAVASCRIPT
|
|
|
|
|
]
|
|
|
|
|
];
|
2018-01-12 11:52:31 +00:00
|
|
|
yield 'Basic array, serialized text, utf-8 flags' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
|
|
|
|
'text' => ( new WikitextContent( 'Söme Content' ) )->serialize(),
|
|
|
|
|
'flags' => 'utf-8',
|
|
|
|
|
]
|
|
|
|
|
];
|
2017-11-15 12:02:40 +00:00
|
|
|
yield 'Basic array, with title' => [
|
|
|
|
|
[
|
|
|
|
|
'title' => Title::newFromText( 'SomeText' ),
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.2',
|
|
|
|
|
'user' => 0,
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
2018-04-17 07:49:20 +00:00
|
|
|
'content' => new WikitextContent( 'Some Content' ),
|
2017-11-15 12:02:40 +00:00
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
yield 'Basic array, no user field' => [
|
|
|
|
|
[
|
|
|
|
|
'id' => 2,
|
|
|
|
|
'page' => 1,
|
|
|
|
|
'timestamp' => '20171017114835',
|
|
|
|
|
'user_text' => '111.0.1.3',
|
|
|
|
|
'minor_edit' => false,
|
|
|
|
|
'deleted' => 0,
|
|
|
|
|
'len' => 46,
|
|
|
|
|
'parent_id' => 1,
|
|
|
|
|
'sha1' => 'rdqbbzs3pkhihgbs8qf2q9jsvheag5z',
|
|
|
|
|
'comment' => 'Goat Comment!',
|
2018-04-17 07:49:20 +00:00
|
|
|
'content' => new WikitextContent( 'Some Content' ),
|
2017-11-15 12:02:40 +00:00
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewMutableRevisionFromArray
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray
|
2017-11-15 12:02:40 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewMutableRevisionFromArray( array $array ) {
|
2021-02-03 16:31:17 +00:00
|
|
|
$this->hideDeprecated( 'MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray' );
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
$store = MediaWikiServices::getInstance()->getRevisionStore();
|
|
|
|
|
|
2018-06-12 16:36:34 +00:00
|
|
|
// HACK: if $array['page'] is given, make sure that the page exists
|
|
|
|
|
if ( isset( $array['page'] ) ) {
|
|
|
|
|
$t = Title::newFromID( $array['page'] );
|
|
|
|
|
if ( !$t || !$t->exists() ) {
|
|
|
|
|
$t = Title::makeTitle( NS_MAIN, __METHOD__ );
|
|
|
|
|
$info = $this->insertPage( $t );
|
|
|
|
|
$array['page'] = $info['id'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-15 12:02:40 +00:00
|
|
|
$result = $store->newMutableRevisionFromArray( $array );
|
|
|
|
|
|
|
|
|
|
if ( isset( $array['id'] ) ) {
|
|
|
|
|
$this->assertSame( $array['id'], $result->getId() );
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $array['page'] ) ) {
|
|
|
|
|
$this->assertSame( $array['page'], $result->getPageId() );
|
|
|
|
|
}
|
|
|
|
|
$this->assertSame( $array['timestamp'], $result->getTimestamp() );
|
|
|
|
|
$this->assertSame( $array['user_text'], $result->getUser()->getName() );
|
|
|
|
|
if ( isset( $array['user'] ) ) {
|
|
|
|
|
$this->assertSame( $array['user'], $result->getUser()->getId() );
|
|
|
|
|
}
|
|
|
|
|
$this->assertSame( (bool)$array['minor_edit'], $result->isMinor() );
|
|
|
|
|
$this->assertSame( $array['deleted'], $result->getVisibility() );
|
|
|
|
|
$this->assertSame( $array['len'], $result->getSize() );
|
|
|
|
|
$this->assertSame( $array['parent_id'], $result->getParentId() );
|
|
|
|
|
$this->assertSame( $array['sha1'], $result->getSha1() );
|
|
|
|
|
$this->assertSame( $array['comment'], $result->getComment()->text );
|
|
|
|
|
if ( isset( $array['content'] ) ) {
|
2018-04-17 07:49:20 +00:00
|
|
|
foreach ( $array['content'] as $role => $content ) {
|
|
|
|
|
$this->assertTrue(
|
|
|
|
|
$result->getContent( $role )->equals( $content )
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-01-12 11:52:31 +00:00
|
|
|
} elseif ( isset( $array['text'] ) ) {
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertSame( $array['text'],
|
|
|
|
|
$result->getSlot( SlotRecord::MAIN )->getContent()->serialize() );
|
2019-12-17 16:20:32 +00:00
|
|
|
|
|
|
|
|
if ( isset( $array['content_format'] ) ) {
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$array['content_format'],
|
|
|
|
|
$result->getSlot( SlotRecord::MAIN )->getContent()->getDefaultFormat()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if ( isset( $array['content_model'] ) ) {
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$array['content_model'],
|
|
|
|
|
$result->getSlot( SlotRecord::MAIN )->getModel()
|
|
|
|
|
);
|
|
|
|
|
}
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-12 11:52:31 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideNewMutableRevisionFromArray
|
2018-09-20 17:29:04 +00:00
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray
|
2018-01-12 11:52:31 +00:00
|
|
|
*/
|
|
|
|
|
public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
|
|
|
|
|
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
|
2018-02-27 06:24:46 +00:00
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
|
$lb = $services->getDBLoadBalancer();
|
|
|
|
|
$access = $services->getExternalStoreAccess();
|
|
|
|
|
$blobStore = new SqlBlobStore( $lb, $access, $cache );
|
2019-10-01 16:20:45 +00:00
|
|
|
$blobStore->setLegacyEncoding( 'windows-1252' );
|
2018-01-12 11:52:31 +00:00
|
|
|
|
|
|
|
|
$factory = $this->getMockBuilder( BlobStoreFactory::class )
|
|
|
|
|
->setMethods( [ 'newBlobStore', 'newSqlBlobStore' ] )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->getMock();
|
|
|
|
|
$factory->expects( $this->any() )
|
|
|
|
|
->method( 'newBlobStore' )
|
|
|
|
|
->willReturn( $blobStore );
|
|
|
|
|
$factory->expects( $this->any() )
|
|
|
|
|
->method( 'newSqlBlobStore' )
|
|
|
|
|
->willReturn( $blobStore );
|
|
|
|
|
|
|
|
|
|
$this->setService( 'BlobStoreFactory', $factory );
|
|
|
|
|
|
|
|
|
|
$this->testNewMutableRevisionFromArray( $array );
|
|
|
|
|
}
|
|
|
|
|
|
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 );
|
|
|
|
|
|
|
|
|
|
// Fore bad article ID
|
|
|
|
|
$title = $page->getTitle();
|
|
|
|
|
$title->resetArticleID( 886655 );
|
|
|
|
|
|
|
|
|
|
$result = $store->getKnownCurrentRevision( $page->getTitle(), $rev->getId() );
|
|
|
|
|
|
|
|
|
|
// Redundant, we really only care that no exception is thrown.
|
|
|
|
|
$this->assertSame( $rev->getId(), $result->getId() );
|
|
|
|
|
}
|
|
|
|
|
|
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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2019-02-27 21:26:17 +00:00
|
|
|
new WikitextContent( $text ),
|
2019-04-04 19:23:41 +00:00
|
|
|
__METHOD__,
|
|
|
|
|
0,
|
|
|
|
|
false,
|
|
|
|
|
$this->getMutableTestUser()->getUser()
|
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 */
|
|
|
|
|
$revRecord = $page->doEditContent(
|
2019-02-27 21:26:17 +00:00
|
|
|
new WikitextContent( $text ),
|
|
|
|
|
__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 ) {
|
2020-06-03 23:01:21 +00:00
|
|
|
$this->hideDeprecated( 'Revision::getContentModel' );
|
2020-07-02 06:22:52 +00:00
|
|
|
$this->hideDeprecated( 'Revision::__construct' );
|
2020-06-03 23:01:21 +00:00
|
|
|
|
2019-09-26 12:23:40 +00:00
|
|
|
$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 ];
|
|
|
|
|
|
|
|
|
|
if ( $mainSlotRow1->model_name ) {
|
2020-06-10 01:08:37 +00:00
|
|
|
$this->assertSame(
|
|
|
|
|
( new Revision( $revRecord1 ) )->getContentModel(),
|
|
|
|
|
$mainSlotRow1->model_name
|
|
|
|
|
);
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
( new Revision( $revRecord2 ) )->getContentModel(),
|
|
|
|
|
$mainSlotRow2->model_name
|
|
|
|
|
);
|
2019-09-26 12:23:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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' ],
|
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' ],
|
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' ],
|
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' ],
|
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' ],
|
|
|
|
|
'Other_Page',
|
|
|
|
|
[
|
|
|
|
|
'slots' => [ SlotRecord::MAIN ],
|
|
|
|
|
'content' => true
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
yield 'Preload slots and content, multiple pages, preload page fields' => [
|
|
|
|
|
[ 'page', 'comment' ],
|
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
|
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,
|
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
|
2020-03-30 20:20:27 +00:00
|
|
|
* @param string|null $otherPageTitle
|
|
|
|
|
* @param array|null $options
|
|
|
|
|
*/
|
|
|
|
|
public function testNewRevisionsFromBatch_archive(
|
|
|
|
|
$queryOptions,
|
|
|
|
|
$otherPageTitle = null,
|
|
|
|
|
array $options = []
|
|
|
|
|
) {
|
|
|
|
|
$title1 = Title::newFromText( __METHOD__ );
|
|
|
|
|
$text1 = __METHOD__ . '-bä';
|
|
|
|
|
$page1 = WikiPage::factory( $title1 );
|
|
|
|
|
|
|
|
|
|
$title2 = $otherPageTitle ? Title::newFromText( $otherPageTitle ) : $title1;
|
|
|
|
|
$text2 = __METHOD__ . '-bö';
|
|
|
|
|
$page2 = $otherPageTitle ? WikiPage::factory( $title2 ) : $page1;
|
|
|
|
|
|
2020-06-10 01:08:37 +00:00
|
|
|
/** @var RevisionRecord $revRecord1 */
|
|
|
|
|
/** @var RevisionRecord $revRecord2 */
|
|
|
|
|
$revRecord1 = $page1->doEditContent( new WikitextContent( $text1 ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
|
|
|
|
$revRecord2 = $page2->doEditContent( new WikitextContent( $text2 ), __METHOD__ )
|
|
|
|
|
->value['revision-record'];
|
2020-03-30 20:20:27 +00:00
|
|
|
$page1->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
|
|
|
|
|
if ( $page2 !== $page1 ) {
|
|
|
|
|
$page2->doDeleteArticleReal( __METHOD__, $this->getTestSysop()->getUser() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$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(
|
|
|
|
|
$rows, $options, 0, $otherPageTitle ? null : $title1 );
|
|
|
|
|
|
|
|
|
|
$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
|
|
|
|
|
*/
|
|
|
|
|
public function testGetFirstRevision() {
|
|
|
|
|
$pageTitle = Title::newFromText( 'Test_Get_First_Revision' );
|
|
|
|
|
$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()
|
|
|
|
|
->getFirstRevision( $pageTitle )
|
|
|
|
|
->getId()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Revision\RevisionStore::getFirstRevision
|
|
|
|
|
*/
|
|
|
|
|
public function testGetFirstRevision_nonexistent_page() {
|
|
|
|
|
$this->assertNull(
|
|
|
|
|
MediaWikiServices::getInstance()
|
|
|
|
|
->getRevisionStore()
|
|
|
|
|
->getFirstRevision( $this->getNonexistingTestPage( __METHOD__ )->getTitle() )
|
|
|
|
|
);
|
|
|
|
|
}
|
2017-11-15 12:02:40 +00:00
|
|
|
}
|