2016-04-19 09:34:31 +00:00
|
|
|
<?php
|
|
|
|
|
|
2019-09-13 20:39:50 +00:00
|
|
|
use MediaWiki\Permissions\PermissionManager;
|
2020-07-03 00:20:38 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2020-10-12 21:32:55 +00:00
|
|
|
use MediaWiki\User\UserFactory;
|
2019-04-28 11:07:18 +00:00
|
|
|
use MediaWiki\User\UserIdentityValue;
|
2019-10-06 04:54:59 +00:00
|
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
2019-05-01 15:31:13 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2018-01-26 19:17:27 +00:00
|
|
|
use Wikimedia\Rdbms\LoadBalancer;
|
2017-04-19 19:37:35 +00:00
|
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
|
|
2016-04-19 09:34:31 +00:00
|
|
|
/**
|
|
|
|
|
* @covers WatchedItemQueryService
|
|
|
|
|
*/
|
2021-01-10 02:19:44 +00:00
|
|
|
class WatchedItemQueryServiceUnitTest extends MediaWikiUnitTestCase {
|
2016-04-19 09:34:31 +00:00
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
/**
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|CommentStore
|
2018-01-26 19:17:27 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockCommentStore() {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mockStore = $this->createMock( CommentStore::class );
|
2018-01-24 23:41:01 +00:00
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'getFields' )
|
|
|
|
|
->willReturn( [ 'commentstore' => 'fields' ] );
|
|
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'getJoin' )
|
|
|
|
|
->willReturn( [
|
|
|
|
|
'tables' => [ 'commentstore' => 'table' ],
|
|
|
|
|
'fields' => [ 'commentstore' => 'field' ],
|
|
|
|
|
'joins' => [ 'commentstore' => 'join' ],
|
|
|
|
|
] );
|
2018-01-26 19:17:27 +00:00
|
|
|
return $mockStore;
|
|
|
|
|
}
|
2018-01-24 23:41:01 +00:00
|
|
|
|
2017-09-12 17:12:29 +00:00
|
|
|
/**
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|ActorMigration
|
2017-09-12 17:12:29 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockActorMigration() {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mockStore = $this->createMock( ActorMigration::class );
|
2017-09-12 17:12:29 +00:00
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'getJoin' )
|
|
|
|
|
->willReturn( [
|
|
|
|
|
'tables' => [ 'actormigration' => 'table' ],
|
|
|
|
|
'fields' => [
|
|
|
|
|
'rc_user' => 'actormigration_user',
|
|
|
|
|
'rc_user_text' => 'actormigration_user_text',
|
|
|
|
|
'rc_actor' => 'actormigration_actor',
|
|
|
|
|
],
|
|
|
|
|
'joins' => [ 'actormigration' => 'join' ],
|
|
|
|
|
] );
|
|
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'getWhere' )
|
|
|
|
|
->willReturn( [
|
|
|
|
|
'tables' => [ 'actormigration' => 'table' ],
|
|
|
|
|
'conds' => 'actormigration_conds',
|
|
|
|
|
'joins' => [ 'actormigration' => 'join' ],
|
|
|
|
|
] );
|
|
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'isAnon' )
|
|
|
|
|
->willReturn( 'actormigration is anon' );
|
|
|
|
|
$mockStore->expects( $this->any() )
|
|
|
|
|
->method( 'isNotAnon' )
|
|
|
|
|
->willReturn( 'actormigration is not anon' );
|
|
|
|
|
return $mockStore;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
/**
|
2021-01-14 08:20:36 +00:00
|
|
|
* @param IDatabase $mockDb
|
2021-01-09 11:03:10 +00:00
|
|
|
* @param PermissionManager|null $mockPM
|
2018-01-26 19:17:27 +00:00
|
|
|
* @return WatchedItemQueryService
|
|
|
|
|
*/
|
2019-09-13 20:39:50 +00:00
|
|
|
private function newService( $mockDb, $mockPM = null ) {
|
2018-01-26 19:17:27 +00:00
|
|
|
return new WatchedItemQueryService(
|
|
|
|
|
$this->getMockLoadBalancer( $mockDb ),
|
2017-09-12 17:12:29 +00:00
|
|
|
$this->getMockCommentStore(),
|
2018-03-03 00:21:36 +00:00
|
|
|
$this->getMockActorMigration(),
|
2019-09-13 20:39:50 +00:00
|
|
|
$this->getMockWatchedItemStore(),
|
2020-03-06 06:03:44 +00:00
|
|
|
$mockPM ?: $this->getMockPermissionManager(),
|
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
|
|
|
$this->createHookContainer(),
|
2020-10-12 21:32:55 +00:00
|
|
|
$this->createMock( UserFactory::class ),
|
2020-03-06 06:03:44 +00:00
|
|
|
false
|
2018-01-26 19:17:27 +00:00
|
|
|
);
|
2018-01-24 23:41:01 +00:00
|
|
|
}
|
|
|
|
|
|
2016-04-19 09:34:31 +00:00
|
|
|
/**
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|IDatabase
|
2016-04-19 09:34:31 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockDb() {
|
2019-05-01 15:31:13 +00:00
|
|
|
$mock = $this->createMock( IDatabase::class );
|
2016-04-19 09:34:31 +00:00
|
|
|
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'makeList' )
|
|
|
|
|
->with(
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'int' )
|
|
|
|
|
)
|
2017-06-26 16:35:31 +00:00
|
|
|
->will( $this->returnCallback( function ( $a, $conj ) {
|
2016-04-19 09:34:31 +00:00
|
|
|
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
|
2017-09-12 17:12:29 +00:00
|
|
|
$conds = [];
|
|
|
|
|
foreach ( $a as $k => $v ) {
|
|
|
|
|
if ( is_int( $k ) ) {
|
|
|
|
|
$conds[] = "($v)";
|
|
|
|
|
} elseif ( is_array( $v ) ) {
|
|
|
|
|
$conds[] = "($k IN ('" . implode( "','", $v ) . "'))";
|
|
|
|
|
} else {
|
|
|
|
|
$conds[] = "($k = '$v')";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return implode( $sqlConj, $conds );
|
2016-04-19 09:34:31 +00:00
|
|
|
} ) );
|
|
|
|
|
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'addQuotes' )
|
2017-06-26 16:35:31 +00:00
|
|
|
->will( $this->returnCallback( function ( $value ) {
|
2016-04-19 09:34:31 +00:00
|
|
|
return "'$value'";
|
|
|
|
|
} ) );
|
|
|
|
|
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'timestamp' )
|
|
|
|
|
->will( $this->returnArgument( 0 ) );
|
|
|
|
|
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'bitAnd' )
|
2017-06-26 16:35:31 +00:00
|
|
|
->willReturnCallback( function ( $a, $b ) {
|
2016-04-19 09:34:31 +00:00
|
|
|
return "($a & $b)";
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-01-14 08:20:36 +00:00
|
|
|
* @param IDatabase $mockDb
|
|
|
|
|
* @return LoadBalancer
|
2016-04-19 09:34:31 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockLoadBalancer( $mockDb ) {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mock = $this->createMock( LoadBalancer::class );
|
2016-04-19 09:34:31 +00:00
|
|
|
$mock->expects( $this->any() )
|
2016-10-06 17:11:17 +00:00
|
|
|
->method( 'getConnectionRef' )
|
2017-08-04 18:53:34 +00:00
|
|
|
->with( DB_REPLICA )
|
2016-04-19 09:34:31 +00:00
|
|
|
->will( $this->returnValue( $mockDb ) );
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-03 00:21:36 +00:00
|
|
|
/**
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|WatchedItemStore
|
2018-03-03 00:21:36 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockWatchedItemStore() {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mock = $this->createMock( WatchedItemStore::class );
|
2018-03-03 00:21:36 +00:00
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'getLatestNotificationTimestamp' )
|
|
|
|
|
->will( $this->returnCallback( function ( $timestamp ) {
|
|
|
|
|
return $timestamp;
|
|
|
|
|
} ) );
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-13 20:39:50 +00:00
|
|
|
/**
|
2021-01-14 08:20:36 +00:00
|
|
|
* @param string|null $notAllowedAction
|
|
|
|
|
* @return PermissionManager
|
2019-09-13 20:39:50 +00:00
|
|
|
*/
|
|
|
|
|
private function getMockPermissionManager( $notAllowedAction = null ) {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mock = $this->createMock( PermissionManager::class );
|
2019-09-13 20:39:50 +00:00
|
|
|
$mock->method( 'userHasRight' )
|
|
|
|
|
->will( $this->returnCallback( function ( $user, $action ) use ( $notAllowedAction ) {
|
|
|
|
|
return $action !== $notAllowedAction;
|
|
|
|
|
} ) );
|
|
|
|
|
$mock->method( 'userHasAnyRight' )
|
|
|
|
|
->will( $this->returnCallback( function ( $user, ...$actions ) use ( $notAllowedAction ) {
|
|
|
|
|
return !in_array( $notAllowedAction, $actions );
|
|
|
|
|
} ) );
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-19 09:34:31 +00:00
|
|
|
/**
|
|
|
|
|
* @param int $id
|
2019-04-28 11:07:18 +00:00
|
|
|
* @param string[] $extraMethods Extra methods that are expected might be called
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|User
|
2016-04-19 09:34:31 +00:00
|
|
|
*/
|
2019-04-28 11:07:18 +00:00
|
|
|
private function getMockNonAnonUserWithId( $id, array $extraMethods = [] ) {
|
2021-01-10 02:19:44 +00:00
|
|
|
$mock = $this->createNoOpMock(
|
|
|
|
|
User::class,
|
|
|
|
|
array_merge( [ 'isRegistered', 'getId', ], $extraMethods )
|
|
|
|
|
);
|
2019-04-28 11:07:18 +00:00
|
|
|
$mock->method( 'isRegistered' )->willReturn( true );
|
|
|
|
|
$mock->method( 'getId' )->willReturn( $id );
|
2016-04-19 09:34:31 +00:00
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param int $id
|
2019-04-28 11:07:18 +00:00
|
|
|
* @param string[] $extraMethods Extra methods that are expected might be called
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|User
|
2016-04-19 09:34:31 +00:00
|
|
|
*/
|
2019-04-28 11:07:18 +00:00
|
|
|
private function getMockUnrestrictedNonAnonUserWithId( $id, array $extraMethods = [] ) {
|
|
|
|
|
$mock = $this->getMockNonAnonUserWithId( $id,
|
2019-09-13 20:39:50 +00:00
|
|
|
array_merge( [ 'useRCPatrol' ], $extraMethods ) );
|
2019-04-28 11:07:18 +00:00
|
|
|
$mock->method( 'useRCPatrol' )->willReturn( true );
|
2016-04-19 09:34:31 +00:00
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param int $id
|
2019-10-06 04:54:59 +00:00
|
|
|
* @return MockObject|User
|
2016-04-19 09:34:31 +00:00
|
|
|
*/
|
2021-01-10 02:19:44 +00:00
|
|
|
private function getMockNonAnonUserWithIdAndNoPatrolRights( $id ) {
|
|
|
|
|
$mock = $this->getMockNonAnonUserWithId( $id, [ 'useRCPatrol', 'useNPPatrol' ] );
|
2019-04-28 11:07:18 +00:00
|
|
|
$mock->method( 'useRCPatrol' )->willReturn( false );
|
|
|
|
|
$mock->method( 'useNPPatrol' )->willReturn( false );
|
2016-04-19 09:34:31 +00:00
|
|
|
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist', 'page' ],
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
'rc_cur_id',
|
|
|
|
|
'rc_this_oldid',
|
|
|
|
|
'rc_last_oldid',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'wl_user' => 1,
|
|
|
|
|
'(rc_this_oldid=page_latest) OR (rc_type=3)',
|
|
|
|
|
],
|
|
|
|
|
$this->isType( 'string' ),
|
2016-10-11 20:17:22 +00:00
|
|
|
[
|
|
|
|
|
'LIMIT' => 3,
|
|
|
|
|
],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
'page' => [
|
|
|
|
|
'LEFT JOIN',
|
|
|
|
|
'rc_cur_id=page_id',
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [
|
2021-01-10 02:19:44 +00:00
|
|
|
(object)[
|
2016-04-19 09:34:31 +00:00
|
|
|
'rc_id' => 1,
|
|
|
|
|
'rc_namespace' => 0,
|
|
|
|
|
'rc_title' => 'Foo1',
|
|
|
|
|
'rc_timestamp' => '20151212010101',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'wl_notificationtimestamp' => '20151212010101',
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
|
|
|
|
(object)[
|
2016-04-19 09:34:31 +00:00
|
|
|
'rc_id' => 2,
|
|
|
|
|
'rc_namespace' => 1,
|
|
|
|
|
'rc_title' => 'Foo2',
|
|
|
|
|
'rc_timestamp' => '20151212010102',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'wl_notificationtimestamp' => null,
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
|
|
|
|
(object)[
|
2016-10-11 20:17:22 +00:00
|
|
|
'rc_id' => 3,
|
|
|
|
|
'rc_namespace' => 1,
|
|
|
|
|
'rc_title' => 'Foo3',
|
|
|
|
|
'rc_timestamp' => '20151212010103',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'wl_notificationtimestamp' => null,
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
2016-04-19 09:34:31 +00:00
|
|
|
] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
2016-10-11 20:17:22 +00:00
|
|
|
$startFrom = null;
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user, [ 'limit' => 2 ], $startFrom
|
|
|
|
|
);
|
2016-04-19 09:34:31 +00:00
|
|
|
|
2019-12-13 14:29:10 +00:00
|
|
|
$this->assertIsArray( $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
$this->assertCount( 2, $items );
|
|
|
|
|
|
|
|
|
|
foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
|
|
|
|
|
$this->assertInstanceOf( WatchedItem::class, $watchedItem );
|
2019-12-13 14:29:10 +00:00
|
|
|
$this->assertIsArray( $recentChangeInfo );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
|
|
|
|
|
$items[0][0]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[
|
|
|
|
|
'rc_id' => 1,
|
|
|
|
|
'rc_namespace' => 0,
|
|
|
|
|
'rc_title' => 'Foo1',
|
|
|
|
|
'rc_timestamp' => '20151212010101',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
],
|
|
|
|
|
$items[0][1]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
|
|
|
|
|
$items[1][0]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[
|
|
|
|
|
'rc_id' => 2,
|
|
|
|
|
'rc_namespace' => 1,
|
|
|
|
|
'rc_title' => 'Foo2',
|
|
|
|
|
'rc_timestamp' => '20151212010102',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
],
|
|
|
|
|
$items[1][1]
|
|
|
|
|
);
|
2016-10-11 20:17:22 +00:00
|
|
|
|
|
|
|
|
$this->assertEquals( [ '20151212010103', 3 ], $startFrom );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_extension() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist', 'page', 'extension_dummy_table' ],
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
'rc_cur_id',
|
|
|
|
|
'rc_this_oldid',
|
|
|
|
|
'rc_last_oldid',
|
|
|
|
|
'extension_dummy_field',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'wl_user' => 1,
|
|
|
|
|
'(rc_this_oldid=page_latest) OR (rc_type=3)',
|
|
|
|
|
'extension_dummy_cond',
|
|
|
|
|
],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
[
|
|
|
|
|
'extension_dummy_option',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2016-10-11 20:17:22 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
'page' => [
|
|
|
|
|
'LEFT JOIN',
|
|
|
|
|
'rc_cur_id=page_id',
|
|
|
|
|
],
|
|
|
|
|
'extension_dummy_join_cond' => [],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [
|
2021-01-10 02:19:44 +00:00
|
|
|
(object)[
|
2016-10-11 20:17:22 +00:00
|
|
|
'rc_id' => 1,
|
|
|
|
|
'rc_namespace' => 0,
|
|
|
|
|
'rc_title' => 'Foo1',
|
|
|
|
|
'rc_timestamp' => '20151212010101',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'wl_notificationtimestamp' => '20151212010101',
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
|
|
|
|
(object)[
|
2016-10-11 20:17:22 +00:00
|
|
|
'rc_id' => 2,
|
|
|
|
|
'rc_namespace' => 1,
|
|
|
|
|
'rc_title' => 'Foo2',
|
|
|
|
|
'rc_timestamp' => '20151212010102',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'wl_notificationtimestamp' => null,
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
2016-10-11 20:17:22 +00:00
|
|
|
] ) );
|
|
|
|
|
|
|
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$mockExtension = $this->getMockBuilder( WatchedItemQueryServiceExtension::class )
|
|
|
|
|
->getMock();
|
|
|
|
|
$mockExtension->expects( $this->once() )
|
|
|
|
|
->method( 'modifyWatchedItemsWithRCInfoQuery' )
|
|
|
|
|
->with(
|
|
|
|
|
$this->identicalTo( $user ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isInstanceOf( IDatabase::class ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' )
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnCallback( function (
|
|
|
|
|
$user, $options, $db, &$tables, &$fields, &$conds, &$dbOptions, &$joinConds
|
|
|
|
|
) {
|
|
|
|
|
$tables[] = 'extension_dummy_table';
|
|
|
|
|
$fields[] = 'extension_dummy_field';
|
|
|
|
|
$conds[] = 'extension_dummy_cond';
|
|
|
|
|
$dbOptions[] = 'extension_dummy_option';
|
|
|
|
|
$joinConds['extension_dummy_join_cond'] = [];
|
|
|
|
|
} ) );
|
|
|
|
|
$mockExtension->expects( $this->once() )
|
|
|
|
|
->method( 'modifyWatchedItemsWithRCInfo' )
|
|
|
|
|
->with(
|
|
|
|
|
$this->identicalTo( $user ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isInstanceOf( IDatabase::class ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->anything(),
|
|
|
|
|
$this->anything() // Can't test for null here, PHPUnit applies this after the callback
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnCallback( function ( $user, $options, $db, &$items, $res, &$startFrom ) {
|
|
|
|
|
foreach ( $items as $i => &$item ) {
|
|
|
|
|
$item[1]['extension_dummy_field'] = $i;
|
|
|
|
|
}
|
|
|
|
|
unset( $item );
|
|
|
|
|
|
|
|
|
|
$this->assertNull( $startFrom );
|
|
|
|
|
$startFrom = [ '20160203123456', 42 ];
|
|
|
|
|
} ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-10-11 20:17:22 +00:00
|
|
|
TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ];
|
|
|
|
|
|
|
|
|
|
$startFrom = null;
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user, [], $startFrom
|
|
|
|
|
);
|
|
|
|
|
|
2019-12-13 14:29:10 +00:00
|
|
|
$this->assertIsArray( $items );
|
2016-10-11 20:17:22 +00:00
|
|
|
$this->assertCount( 2, $items );
|
|
|
|
|
|
|
|
|
|
foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
|
|
|
|
|
$this->assertInstanceOf( WatchedItem::class, $watchedItem );
|
2019-12-13 14:29:10 +00:00
|
|
|
$this->assertIsArray( $recentChangeInfo );
|
2016-10-11 20:17:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
|
|
|
|
|
$items[0][0]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[
|
|
|
|
|
'rc_id' => 1,
|
|
|
|
|
'rc_namespace' => 0,
|
|
|
|
|
'rc_title' => 'Foo1',
|
|
|
|
|
'rc_timestamp' => '20151212010101',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'extension_dummy_field' => 0,
|
|
|
|
|
],
|
|
|
|
|
$items[0][1]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
|
|
|
|
|
$items[1][0]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
[
|
|
|
|
|
'rc_id' => 2,
|
|
|
|
|
'rc_namespace' => 1,
|
|
|
|
|
'rc_title' => 'Foo2',
|
|
|
|
|
'rc_timestamp' => '20151212010102',
|
|
|
|
|
'rc_type' => RC_NEW,
|
|
|
|
|
'rc_deleted' => 0,
|
|
|
|
|
'extension_dummy_field' => 1,
|
|
|
|
|
],
|
|
|
|
|
$items[1][1]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( [ '20160203123456', 42 ], $startFrom );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getWatchedItemsWithRecentChangeInfoOptionsProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_type', 'rc_minor', 'rc_bot' ],
|
|
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
|
|
|
|
[ 'rc_user_text' => 'actormigration_user_text' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
|
|
|
|
[ 'rc_user' => 'actormigration_user' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
|
|
|
|
|
null,
|
2018-01-24 23:41:01 +00:00
|
|
|
[ 'commentstore' => 'table' ],
|
|
|
|
|
[ 'commentstore' => 'field' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
|
|
|
|
[],
|
2018-01-24 23:41:01 +00:00
|
|
|
[ 'commentstore' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_patrolled', 'rc_log_type' ],
|
|
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_old_len', 'rc_new_len' ],
|
|
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
|
|
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'namespaceIds' => [ 0, 1 ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'wl_namespace' => [ 0, 1 ] ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'wl_namespace' => [ 0, 1 ] ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
|
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_NEWER ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
|
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp <= '20151212010101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp >= '20151212010101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'dir' => WatchedItemQueryService::DIR_OLDER,
|
|
|
|
|
'start' => '20151212020101',
|
|
|
|
|
'end' => '20151212010101'
|
|
|
|
|
],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp >= '20151212010101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp <= '20151212010101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'dir' => WatchedItemQueryService::DIR_NEWER,
|
|
|
|
|
'start' => '20151212010101',
|
|
|
|
|
'end' => '20151212020101'
|
|
|
|
|
],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
|
|
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'limit' => 10 ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'LIMIT' => 11 ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'limit' => "10; DROP TABLE watchlist;\n--" ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'LIMIT' => 11 ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_minor != 0' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_minor = 0' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_bot != 0' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_bot = 0' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration is anon' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration is not anon' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_patrolled != 0' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2018-04-13 21:36:34 +00:00
|
|
|
[ 'rc_patrolled' => 0 ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'rc_timestamp >= wl_notificationtimestamp' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
|
|
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'onlyByUser' => 'SomeOtherUser' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration_conds' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'notByUser' => 'SomeOtherUser' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'NOT(actormigration_conds)' ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ '20151212010101', 123 ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
"(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
|
|
|
|
|
],
|
|
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_NEWER ],
|
|
|
|
|
[ '20151212010101', 123 ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
"(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
|
|
|
|
|
],
|
|
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
"(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
|
|
|
|
|
],
|
|
|
|
|
[ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
|
2017-06-06 17:39:14 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider getWatchedItemsWithRecentChangeInfoOptionsProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
|
|
|
|
|
array $options,
|
2016-10-11 20:17:22 +00:00
|
|
|
$startFrom,
|
2017-06-06 17:39:14 +00:00
|
|
|
array $expectedExtraTables,
|
2016-04-19 09:34:31 +00:00
|
|
|
array $expectedExtraFields,
|
|
|
|
|
array $expectedExtraConds,
|
2017-06-06 17:39:14 +00:00
|
|
|
array $expectedDbOptions,
|
2018-01-24 23:41:01 +00:00
|
|
|
array $expectedExtraJoinConds
|
2016-04-19 09:34:31 +00:00
|
|
|
) {
|
2017-06-06 17:39:14 +00:00
|
|
|
$expectedTables = array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables );
|
2016-04-19 09:34:31 +00:00
|
|
|
$expectedFields = array_merge(
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
|
|
|
|
|
'rc_cur_id',
|
|
|
|
|
'rc_this_oldid',
|
|
|
|
|
'rc_last_oldid',
|
|
|
|
|
],
|
|
|
|
|
$expectedExtraFields
|
|
|
|
|
);
|
|
|
|
|
$expectedConds = array_merge(
|
|
|
|
|
[ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
|
|
|
|
|
$expectedExtraConds
|
|
|
|
|
);
|
2017-06-06 17:39:14 +00:00
|
|
|
$expectedJoinConds = array_merge(
|
|
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2017-06-06 17:39:14 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
'page' => [
|
|
|
|
|
'LEFT JOIN',
|
|
|
|
|
'rc_cur_id=page_id',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
$expectedExtraJoinConds
|
|
|
|
|
);
|
2016-04-19 09:34:31 +00:00
|
|
|
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
2017-06-06 17:39:14 +00:00
|
|
|
$expectedTables,
|
2016-04-19 09:34:31 +00:00
|
|
|
$expectedFields,
|
|
|
|
|
$expectedConds,
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$expectedDbOptions,
|
2017-06-06 17:39:14 +00:00
|
|
|
$expectedJoinConds
|
2016-04-19 09:34:31 +00:00
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
2016-10-11 20:17:22 +00:00
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
|
2016-04-19 09:34:31 +00:00
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-10-11 20:17:22 +00:00
|
|
|
$this->assertNull( $startFrom );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function filterPatrolledOptionProvider() {
|
|
|
|
|
return [
|
2021-01-10 02:19:44 +00:00
|
|
|
'Patrolled' => [ WatchedItemQueryService::FILTER_PATROLLED ],
|
|
|
|
|
'Not patrolled' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ],
|
2016-04-19 09:34:31 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider filterPatrolledOptionProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights(
|
|
|
|
|
$filtersOption
|
|
|
|
|
) {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist', 'page' ],
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
[ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' )
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
|
|
|
|
$user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user,
|
|
|
|
|
[ 'filters' => [ $filtersOption ] ]
|
|
|
|
|
);
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function mysqlIndexOptimizationProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
'mysql',
|
|
|
|
|
[],
|
|
|
|
|
[ "rc_timestamp > ''" ],
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'mysql',
|
|
|
|
|
[ 'start' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ "rc_timestamp <= '20151212010101'" ],
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'mysql',
|
|
|
|
|
[ 'end' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ "rc_timestamp >= '20151212010101'" ],
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'postgres',
|
|
|
|
|
[],
|
|
|
|
|
[],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider mysqlIndexOptimizationProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization(
|
|
|
|
|
$dbType,
|
|
|
|
|
array $options,
|
|
|
|
|
array $expectedExtraConds
|
|
|
|
|
) {
|
|
|
|
|
$commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
|
|
|
|
|
$conds = array_merge( $commonConds, $expectedExtraConds );
|
|
|
|
|
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist', 'page' ],
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$conds,
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' )
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
$mockDb->expects( $this->any() )
|
|
|
|
|
->method( 'getType' )
|
|
|
|
|
->will( $this->returnValue( $dbType ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function userPermissionRelatedExtraChecksProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[],
|
|
|
|
|
'deletedhistory',
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
|
|
|
|
|
LogPage::DELETED_ACTION . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[],
|
|
|
|
|
'suppressrevision',
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'(rc_type != ' . RC_LOG . ') OR (' .
|
|
|
|
|
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[],
|
|
|
|
|
'viewsuppressed',
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'(rc_type != ' . RC_LOG . ') OR (' .
|
|
|
|
|
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'onlyByUser' => 'SomeOtherUser' ],
|
|
|
|
|
'deletedhistory',
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
2017-09-12 17:12:29 +00:00
|
|
|
'actormigration_conds',
|
2020-07-03 00:20:38 +00:00
|
|
|
'(rc_deleted & ' . RevisionRecord::DELETED_USER . ') != ' . RevisionRecord::DELETED_USER,
|
2016-04-19 09:34:31 +00:00
|
|
|
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
|
|
|
|
|
LogPage::DELETED_ACTION . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'onlyByUser' => 'SomeOtherUser' ],
|
|
|
|
|
'suppressrevision',
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
2017-09-12 17:12:29 +00:00
|
|
|
'actormigration_conds',
|
2020-07-03 00:20:38 +00:00
|
|
|
'(rc_deleted & ' . ( RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED ),
|
2016-04-19 09:34:31 +00:00
|
|
|
'(rc_type != ' . RC_LOG . ') OR (' .
|
|
|
|
|
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'onlyByUser' => 'SomeOtherUser' ],
|
|
|
|
|
'viewsuppressed',
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'table' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
2017-09-12 17:12:29 +00:00
|
|
|
'actormigration_conds',
|
2020-07-03 00:20:38 +00:00
|
|
|
'(rc_deleted & ' . ( RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED ),
|
2016-04-19 09:34:31 +00:00
|
|
|
'(rc_type != ' . RC_LOG . ') OR (' .
|
|
|
|
|
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
|
|
|
|
|
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
|
|
|
|
|
],
|
2017-09-12 17:12:29 +00:00
|
|
|
[ 'actormigration' => 'join' ],
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider userPermissionRelatedExtraChecksProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
|
|
|
|
|
array $options,
|
|
|
|
|
$notAllowedAction,
|
2017-09-12 17:12:29 +00:00
|
|
|
array $expectedExtraTables,
|
|
|
|
|
array $expectedExtraConds,
|
|
|
|
|
array $expectedExtraJoins
|
2016-04-19 09:34:31 +00:00
|
|
|
) {
|
|
|
|
|
$commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
|
|
|
|
|
$conds = array_merge( $commonConds, $expectedExtraConds );
|
|
|
|
|
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
2017-09-12 17:12:29 +00:00
|
|
|
array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables ),
|
2016-04-19 09:34:31 +00:00
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$conds,
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$this->isType( 'array' ),
|
2017-09-12 17:12:29 +00:00
|
|
|
array_merge( [
|
2019-03-06 17:17:27 +00:00
|
|
|
'watchlist' => [ 'JOIN', [ 'wl_namespace=rc_namespace', 'wl_title=rc_title' ] ],
|
2017-09-12 17:12:29 +00:00
|
|
|
'page' => [ 'LEFT JOIN', 'rc_cur_id=page_id' ],
|
|
|
|
|
], $expectedExtraJoins )
|
2016-04-19 09:34:31 +00:00
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2019-09-13 20:39:50 +00:00
|
|
|
$permissionManager = $this->getMockPermissionManager( $notAllowedAction );
|
2021-01-10 02:19:44 +00:00
|
|
|
$user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
|
2016-04-19 09:34:31 +00:00
|
|
|
|
2019-09-13 20:39:50 +00:00
|
|
|
$queryService = $this->newService( $mockDb, $permissionManager );
|
2016-04-19 09:34:31 +00:00
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist' ],
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
|
|
|
|
|
'rc_cur_id',
|
|
|
|
|
'rc_this_oldid',
|
|
|
|
|
'rc_last_oldid',
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_user' => 1, ],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
[],
|
|
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[ 'rcTypes' => [ 1337 ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'rcTypes\']',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'rcTypes' => [ 'edit' ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'rcTypes\']',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'rcTypes' => [ RC_EDIT, 1337 ] ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'rcTypes\']',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'dir' => 'foo' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'dir\']',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'start' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'dir\']: must be provided',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'end' => '20151212010101' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'dir\']: must be provided',
|
|
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[],
|
|
|
|
|
[ '20151212010101', 123 ],
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'dir\']: must be provided',
|
|
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
'20151212010101',
|
|
|
|
|
'Bad value for parameter $startFrom: must be a two-element array',
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ '20151212010101' ],
|
|
|
|
|
'Bad value for parameter $startFrom: must be a two-element array',
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
2016-10-11 20:17:22 +00:00
|
|
|
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
|
|
|
|
|
[ '20151212010101', 123, 'foo' ],
|
|
|
|
|
'Bad value for parameter $startFrom: must be a two-element array',
|
2016-04-19 09:34:31 +00:00
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'watchlistOwnerToken\']',
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
|
2016-10-11 20:17:22 +00:00
|
|
|
null,
|
2016-04-19 09:34:31 +00:00
|
|
|
'Bad value for parameter $options[\'watchlistOwner\']',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions(
|
|
|
|
|
array $options,
|
2016-10-11 20:17:22 +00:00
|
|
|
$startFrom,
|
2016-04-19 09:34:31 +00:00
|
|
|
$expectedInExceptionMessage
|
|
|
|
|
) {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->never() )
|
|
|
|
|
->method( $this->anything() );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
2019-10-06 09:52:39 +00:00
|
|
|
$this->expectException( InvalidArgumentException::class );
|
|
|
|
|
$this->expectExceptionMessage( $expectedInExceptionMessage );
|
2016-10-11 20:17:22 +00:00
|
|
|
$queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist', 'page' ],
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
'rc_cur_id',
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
[],
|
|
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
'page' => [
|
|
|
|
|
'LEFT JOIN',
|
|
|
|
|
'rc_cur_id=page_id',
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user,
|
|
|
|
|
[ 'usedInGenerator' => true ]
|
|
|
|
|
);
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
[ 'recentchanges', 'watchlist' ],
|
|
|
|
|
[
|
|
|
|
|
'rc_id',
|
|
|
|
|
'rc_namespace',
|
|
|
|
|
'rc_title',
|
|
|
|
|
'rc_timestamp',
|
|
|
|
|
'rc_type',
|
|
|
|
|
'rc_deleted',
|
|
|
|
|
'wl_notificationtimestamp',
|
|
|
|
|
'rc_this_oldid',
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_user' => 1 ],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
[],
|
|
|
|
|
[
|
|
|
|
|
'watchlist' => [
|
2019-03-06 17:17:27 +00:00
|
|
|
'JOIN',
|
2016-04-19 09:34:31 +00:00
|
|
|
[
|
|
|
|
|
'wl_namespace=rc_namespace',
|
|
|
|
|
'wl_title=rc_title'
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user,
|
|
|
|
|
[ 'usedInGenerator' => true, 'allRevisions' => true, ]
|
|
|
|
|
);
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
[
|
|
|
|
|
'wl_user' => 2,
|
|
|
|
|
'(rc_this_oldid=page_latest) OR (rc_type=3)',
|
|
|
|
|
],
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'array' )
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-04-19 09:34:31 +00:00
|
|
|
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
|
2019-04-28 11:07:18 +00:00
|
|
|
$otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2, [ 'getOption' ] );
|
2016-04-19 09:34:31 +00:00
|
|
|
$otherUser->expects( $this->once() )
|
|
|
|
|
->method( 'getOption' )
|
|
|
|
|
->with( 'watchlisttoken' )
|
|
|
|
|
->willReturn( '0123456789abcdef' );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
|
|
|
|
|
$user,
|
|
|
|
|
[ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
|
|
|
|
|
);
|
|
|
|
|
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|
|
|
|
|
|
2016-06-17 11:11:05 +00:00
|
|
|
public function testGetWatchedItemsForUser() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
'watchlist',
|
|
|
|
|
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
|
|
|
|
|
[ 'wl_user' => 1 ]
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [
|
2021-01-10 02:19:44 +00:00
|
|
|
(object)[
|
2016-06-17 11:11:05 +00:00
|
|
|
'wl_namespace' => 0,
|
|
|
|
|
'wl_title' => 'Foo1',
|
|
|
|
|
'wl_notificationtimestamp' => '20151212010101',
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
|
|
|
|
(object)[
|
2016-06-17 11:11:05 +00:00
|
|
|
'wl_namespace' => 1,
|
|
|
|
|
'wl_title' => 'Foo2',
|
|
|
|
|
'wl_notificationtimestamp' => null,
|
2021-01-10 02:19:44 +00:00
|
|
|
],
|
2016-06-17 11:11:05 +00:00
|
|
|
] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-06-17 11:11:05 +00:00
|
|
|
$user = $this->getMockNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsForUser( $user );
|
|
|
|
|
|
2019-12-13 14:29:10 +00:00
|
|
|
$this->assertIsArray( $items );
|
2016-06-17 11:11:05 +00:00
|
|
|
$this->assertCount( 2, $items );
|
|
|
|
|
$this->assertContainsOnlyInstancesOf( WatchedItem::class, $items );
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
|
|
|
|
|
$items[0]
|
|
|
|
|
);
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
|
|
|
|
|
$items[1]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideGetWatchedItemsForUserOptions() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[ 'namespaceIds' => [ 0, 1 ], ],
|
|
|
|
|
[ 'wl_namespace' => [ 0, 1 ], ],
|
|
|
|
|
[]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'sort' => WatchedItemQueryService::SORT_ASC, ],
|
|
|
|
|
[],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'namespaceIds' => [ 0 ],
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_ASC,
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_namespace' => [ 0 ], ],
|
|
|
|
|
[ 'ORDER BY' => 'wl_title ASC' ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'limit' => 10 ],
|
|
|
|
|
[],
|
|
|
|
|
[ 'LIMIT' => 10 ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ],
|
|
|
|
|
'limit' => "10; DROP TABLE watchlist;\n--",
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_namespace' => [ 0, 1 ], ],
|
|
|
|
|
[ 'LIMIT' => 10 ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filter' => WatchedItemQueryService::FILTER_CHANGED ],
|
|
|
|
|
[ 'wl_notificationtimestamp IS NOT NULL' ],
|
|
|
|
|
[]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filter' => WatchedItemQueryService::FILTER_NOT_CHANGED ],
|
|
|
|
|
[ 'wl_notificationtimestamp IS NULL' ],
|
|
|
|
|
[]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'sort' => WatchedItemQueryService::SORT_DESC, ],
|
|
|
|
|
[],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'namespaceIds' => [ 0 ],
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_DESC,
|
|
|
|
|
],
|
|
|
|
|
[ 'wl_namespace' => [ 0 ], ],
|
|
|
|
|
[ 'ORDER BY' => 'wl_title DESC' ]
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideGetWatchedItemsForUserOptions
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsForUser_optionsAndEmptyResult(
|
|
|
|
|
array $options,
|
|
|
|
|
array $expectedConds,
|
|
|
|
|
array $expectedDbOptions
|
|
|
|
|
) {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$user = $this->getMockNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
'watchlist',
|
|
|
|
|
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
|
|
|
|
|
$expectedConds,
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$expectedDbOptions
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-06-17 11:11:05 +00:00
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsForUser( $user, $options );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-06-17 11:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function provideGetWatchedItemsForUser_fromUntilStartFromOptions() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'from' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_ASC
|
|
|
|
|
],
|
|
|
|
|
[ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'from' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_DESC,
|
|
|
|
|
],
|
|
|
|
|
[ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'until' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_ASC
|
|
|
|
|
],
|
|
|
|
|
[ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'until' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_DESC
|
|
|
|
|
],
|
|
|
|
|
[ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'from' => new TitleValue( 0, 'AnotherDbKey' ),
|
|
|
|
|
'until' => new TitleValue( 0, 'SomeOtherDbKey' ),
|
|
|
|
|
'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_ASC
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
"(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
|
|
|
|
|
"(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
|
|
|
|
|
"(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))",
|
|
|
|
|
],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[
|
|
|
|
|
'from' => new TitleValue( 0, 'SomeOtherDbKey' ),
|
|
|
|
|
'until' => new TitleValue( 0, 'AnotherDbKey' ),
|
|
|
|
|
'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
|
|
|
|
|
'sort' => WatchedItemQueryService::SORT_DESC
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
"(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
|
|
|
|
|
"(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
|
|
|
|
|
"(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))",
|
|
|
|
|
],
|
|
|
|
|
[ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideGetWatchedItemsForUser_fromUntilStartFromOptions
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsForUser_fromUntilStartFromOptions(
|
|
|
|
|
array $options,
|
|
|
|
|
array $expectedConds,
|
|
|
|
|
array $expectedDbOptions
|
|
|
|
|
) {
|
|
|
|
|
$user = $this->getMockNonAnonUserWithId( 1 );
|
|
|
|
|
|
|
|
|
|
$expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
|
|
|
|
|
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
$mockDb->expects( $this->any() )
|
|
|
|
|
->method( 'addQuotes' )
|
2017-06-26 16:35:31 +00:00
|
|
|
->will( $this->returnCallback( function ( $value ) {
|
2016-06-17 11:11:05 +00:00
|
|
|
return "'$value'";
|
|
|
|
|
} ) );
|
|
|
|
|
$mockDb->expects( $this->any() )
|
|
|
|
|
->method( 'makeList' )
|
|
|
|
|
->with(
|
|
|
|
|
$this->isType( 'array' ),
|
|
|
|
|
$this->isType( 'int' )
|
|
|
|
|
)
|
2017-06-26 16:35:31 +00:00
|
|
|
->will( $this->returnCallback( function ( $a, $conj ) {
|
2016-06-17 11:11:05 +00:00
|
|
|
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
|
2021-02-06 19:40:52 +00:00
|
|
|
return implode( $sqlConj, array_map( static function ( $s ) {
|
2016-06-17 11:11:05 +00:00
|
|
|
return '(' . $s . ')';
|
|
|
|
|
}, $a
|
|
|
|
|
) );
|
|
|
|
|
} ) );
|
|
|
|
|
$mockDb->expects( $this->once() )
|
|
|
|
|
->method( 'select' )
|
|
|
|
|
->with(
|
|
|
|
|
'watchlist',
|
|
|
|
|
[ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
|
|
|
|
|
$expectedConds,
|
|
|
|
|
$this->isType( 'string' ),
|
|
|
|
|
$expectedDbOptions
|
|
|
|
|
)
|
|
|
|
|
->will( $this->returnValue( [] ) );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-06-17 11:11:05 +00:00
|
|
|
|
|
|
|
|
$items = $queryService->getWatchedItemsForUser( $user, $options );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-06-17 11:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getWatchedItemsForUserInvalidOptionsProvider() {
|
|
|
|
|
return [
|
|
|
|
|
[
|
|
|
|
|
[ 'sort' => 'foo' ],
|
|
|
|
|
'Bad value for parameter $options[\'sort\']'
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'filter' => 'foo' ],
|
|
|
|
|
'Bad value for parameter $options[\'filter\']'
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'from' => new TitleValue( 0, 'SomeDbKey' ), ],
|
|
|
|
|
'Bad value for parameter $options[\'sort\']: must be provided'
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'until' => new TitleValue( 0, 'SomeDbKey' ), ],
|
|
|
|
|
'Bad value for parameter $options[\'sort\']: must be provided'
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
[ 'startFrom' => new TitleValue( 0, 'SomeDbKey' ), ],
|
|
|
|
|
'Bad value for parameter $options[\'sort\']: must be provided'
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider getWatchedItemsForUserInvalidOptionsProvider
|
|
|
|
|
*/
|
|
|
|
|
public function testGetWatchedItemsForUser_invalidOptionThrowsException(
|
|
|
|
|
array $options,
|
|
|
|
|
$expectedInExceptionMessage
|
|
|
|
|
) {
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $this->getMockDb() );
|
2016-06-17 11:11:05 +00:00
|
|
|
|
2019-10-06 09:52:39 +00:00
|
|
|
$this->expectException( InvalidArgumentException::class );
|
|
|
|
|
$this->expectExceptionMessage( $expectedInExceptionMessage );
|
2016-06-17 11:11:05 +00:00
|
|
|
$queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetWatchedItemsForUser_userNotAllowedToViewWatchlist() {
|
|
|
|
|
$mockDb = $this->getMockDb();
|
|
|
|
|
|
|
|
|
|
$mockDb->expects( $this->never() )
|
|
|
|
|
->method( $this->anything() );
|
|
|
|
|
|
2018-01-26 19:17:27 +00:00
|
|
|
$queryService = $this->newService( $mockDb );
|
2016-06-17 11:11:05 +00:00
|
|
|
|
2019-04-28 11:07:18 +00:00
|
|
|
$items = $queryService->getWatchedItemsForUser(
|
|
|
|
|
new UserIdentityValue( 0, 'AnonUser', 0 ) );
|
2020-02-28 15:45:22 +00:00
|
|
|
$this->assertSame( [], $items );
|
2016-06-17 11:11:05 +00:00
|
|
|
}
|
|
|
|
|
|
2016-04-19 09:34:31 +00:00
|
|
|
}
|