<?php
+use MediaWiki\User\UserIdentityValue;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\TestingAccessWrapper;
/**
*/
class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
- use MediaWikiCoversValidator;
-
- private function overrideCommentStore() {
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|CommentStore
+ */
+ private function getMockCommentStore() {
$mockStore = $this->getMockBuilder( CommentStore::class )
->disableOriginalConstructor()
->getMock();
'fields' => [ 'commentstore' => 'field' ],
'joins' => [ 'commentstore' => 'join' ],
] );
-
- $this->setService( 'CommentStore', $mockStore );
+ return $mockStore;
}
/**
- * @return PHPUnit_Framework_MockObject_MockObject|Database
+ * @return PHPUnit_Framework_MockObject_MockObject|ActorMigration
*/
- private function getMockDb() {
- $mock = $this->getMockBuilder( Database::class )
+ private function getMockActorMigration() {
+ $mockStore = $this->getMockBuilder( ActorMigration::class )
->disableOriginalConstructor()
->getMock();
+ $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;
+ }
+
+ /**
+ * @param PHPUnit_Framework_MockObject_MockObject|Database $mockDb
+ * @return WatchedItemQueryService
+ */
+ private function newService( $mockDb ) {
+ return new WatchedItemQueryService(
+ $this->getMockLoadBalancer( $mockDb ),
+ $this->getMockCommentStore(),
+ $this->getMockActorMigration(),
+ $this->getMockWatchedItemStore()
+ );
+ }
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
+ */
+ private function getMockDb() {
+ $mock = $this->createMock( IDatabase::class );
$mock->expects( $this->any() )
->method( 'makeList' )
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
- return '(' . $s . ')';
- }, $a
- ) );
+ $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 );
} ) );
$mock->expects( $this->any() )
}
/**
- * @param PHPUnit_Framework_MockObject_MockObject|Database $mockDb
+ * @param PHPUnit_Framework_MockObject_MockObject|IDatabase $mockDb
* @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
*/
private function getMockLoadBalancer( $mockDb ) {
return $mock;
}
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|WatchedItemStore
+ */
+ private function getMockWatchedItemStore() {
+ $mock = $this->getMockBuilder( WatchedItemStore::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mock->expects( $this->any() )
+ ->method( 'getLatestNotificationTimestamp' )
+ ->will( $this->returnCallback( function ( $timestamp ) {
+ return $timestamp;
+ } ) );
+ return $mock;
+ }
+
/**
* @param int $id
+ * @param string[] $extraMethods Extra methods that are expected might be called
* @return PHPUnit_Framework_MockObject_MockObject|User
*/
- private function getMockNonAnonUserWithId( $id ) {
+ private function getMockNonAnonUserWithId( $id, array $extraMethods = [] ) {
$mock = $this->getMockBuilder( User::class )->getMock();
- $mock->expects( $this->any() )
- ->method( 'isAnon' )
- ->will( $this->returnValue( false ) );
- $mock->expects( $this->any() )
- ->method( 'getId' )
- ->will( $this->returnValue( $id ) );
+ $mock->method( 'isRegistered' )->willReturn( true );
+ $mock->method( 'getId' )->willReturn( $id );
+ $methods = array_merge( [
+ 'isRegistered',
+ 'getId',
+ ], $extraMethods );
+ $mock->expects( $this->never() )->method( $this->anythingBut( ...$methods ) );
return $mock;
}
/**
* @param int $id
+ * @param string[] $extraMethods Extra methods that are expected might be called
* @return PHPUnit_Framework_MockObject_MockObject|User
*/
- private function getMockUnrestrictedNonAnonUserWithId( $id ) {
- $mock = $this->getMockNonAnonUserWithId( $id );
- $mock->expects( $this->any() )
- ->method( 'isAllowed' )
- ->will( $this->returnValue( true ) );
- $mock->expects( $this->any() )
- ->method( 'isAllowedAny' )
- ->will( $this->returnValue( true ) );
- $mock->expects( $this->any() )
- ->method( 'useRCPatrol' )
- ->will( $this->returnValue( true ) );
+ private function getMockUnrestrictedNonAnonUserWithId( $id, array $extraMethods = [] ) {
+ $mock = $this->getMockNonAnonUserWithId( $id,
+ array_merge( [ 'isAllowed', 'isAllowedAny', 'useRCPatrol' ], $extraMethods ) );
+ $mock->method( 'isAllowed' )->willReturn( true );
+ $mock->method( 'isAllowedAny' )->willReturn( true );
+ $mock->method( 'useRCPatrol' )->willReturn( true );
return $mock;
}
* @return PHPUnit_Framework_MockObject_MockObject|User
*/
private function getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction ) {
- $mock = $this->getMockNonAnonUserWithId( $id );
+ $mock = $this->getMockNonAnonUserWithId( $id,
+ [ 'isAllowed', 'isAllowedAny', 'useRCPatrol', 'useNPPatrol' ] );
- $mock->expects( $this->any() )
- ->method( 'isAllowed' )
+ $mock->method( 'isAllowed' )
->will( $this->returnCallback( function ( $action ) use ( $notAllowedAction ) {
return $action !== $notAllowedAction;
} ) );
- $mock->expects( $this->any() )
- ->method( 'isAllowedAny' )
- ->will( $this->returnCallback( function () use ( $notAllowedAction ) {
- $actions = func_get_args();
+ $mock->method( 'isAllowedAny' )
+ ->will( $this->returnCallback( function ( ...$actions ) use ( $notAllowedAction ) {
return !in_array( $notAllowedAction, $actions );
} ) );
+ $mock->method( 'useRCPatrol' )->willReturn( false );
+ $mock->method( 'useNPPatrol' )->willReturn( false );
return $mock;
}
* @return PHPUnit_Framework_MockObject_MockObject|User
*/
private function getMockNonAnonUserWithIdAndNoPatrolRights( $id ) {
- $mock = $this->getMockNonAnonUserWithId( $id );
+ $mock = $this->getMockNonAnonUserWithId( $id,
+ [ 'isAllowed', 'isAllowedAny', 'useRCPatrol', 'useNPPatrol' ] );
$mock->expects( $this->any() )
->method( 'isAllowed' )
return $mock;
}
- private function getMockAnonUser() {
- $mock = $this->getMockBuilder( User::class )->getMock();
- $mock->expects( $this->any() )
- ->method( 'isAnon' )
- ->will( $this->returnValue( true ) );
- return $mock;
- }
-
private function getFakeRow( array $rowValues ) {
$fakeRow = new stdClass();
foreach ( $rowValues as $valueName => $value ) {
],
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
] ),
] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$startFrom = null;
],
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
$startFrom = [ '20160203123456', 42 ];
} ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ];
$startFrom = null;
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
null,
- [],
- [ 'rc_user_text' ],
- [],
+ [ 'actormigration' => 'table' ],
+ [ 'rc_user_text' => 'actormigration_user_text' ],
[],
[],
+ [ 'actormigration' => 'join' ],
],
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
null,
- [],
- [ 'rc_user' ],
- [],
+ [ 'actormigration' => 'table' ],
+ [ 'rc_user' => 'actormigration_user' ],
[],
[],
+ [ 'actormigration' => 'join' ],
],
[
[ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
[
[ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration is anon' ],
[],
- [ 'rc_user = 0' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration is not anon' ],
[],
- [ 'rc_user != 0' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
null,
[],
[],
- [ 'rc_patrolled = 0' ],
+ [ 'rc_patrolled' => 0 ],
[],
[],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'actormigration_conds' ],
[],
- [ 'rc_user_text' => 'SomeOtherUser' ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'notByUser' => 'SomeOtherUser' ],
null,
+ [ 'actormigration' => 'table' ],
[],
+ [ 'NOT(actormigration_conds)' ],
[],
- [ "rc_user_text != 'SomeOtherUser'" ],
- [],
- [],
+ [ 'actormigration' => 'join' ],
],
[
[ 'dir' => WatchedItemQueryService::DIR_OLDER ],
array $expectedDbOptions,
array $expectedExtraJoinConds
) {
- $this->overrideCommentStore();
-
$expectedTables = array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables );
$expectedFields = array_merge(
[
$expectedJoinConds = array_merge(
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
$user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
$user,
[ 'filters' => [ $filtersOption ] ]
->method( 'getType' )
->will( $this->returnValue( $dbType ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
[
[],
'deletedhistory',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
LogPage::DELETED_ACTION . ')'
],
+ [],
],
[
[],
'suppressrevision',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [],
],
[
[],
'viewsuppressed',
+ [],
[
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'deletedhistory',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
'(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
LogPage::DELETED_ACTION . ')'
],
+ [ 'actormigration' => 'join' ],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'suppressrevision',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [ 'actormigration' => 'join' ],
],
[
[ 'onlyByUser' => 'SomeOtherUser' ],
'viewsuppressed',
+ [ 'actormigration' => 'table' ],
[
- 'rc_user_text' => 'SomeOtherUser',
+ 'actormigration_conds',
'(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
'(rc_type != ' . RC_LOG . ') OR (' .
'(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
],
+ [ 'actormigration' => 'join' ],
],
];
}
public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
array $options,
$notAllowedAction,
- array $expectedExtraConds
+ array $expectedExtraTables,
+ array $expectedExtraConds,
+ array $expectedExtraJoins
) {
$commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
$conds = array_merge( $commonConds, $expectedExtraConds );
$mockDb->expects( $this->once() )
->method( 'select' )
->with(
- [ 'recentchanges', 'watchlist', 'page' ],
+ array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables ),
$this->isType( 'array' ),
$conds,
$this->isType( 'string' ),
$this->isType( 'array' ),
- $this->isType( 'array' )
+ array_merge( [
+ 'watchlist' => [ 'JOIN', [ 'wl_namespace=rc_namespace', 'wl_title=rc_title' ] ],
+ 'page' => [ 'LEFT JOIN', 'rc_cur_id=page_id' ],
+ ], $expectedExtraJoins )
)
->will( $this->returnValue( [] ) );
$user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
$this->assertEmpty( $items );
[],
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
[],
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
[],
[
'watchlist' => [
- 'INNER JOIN',
+ 'JOIN',
[
'wl_namespace=rc_namespace',
'wl_title=rc_title'
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsWithRecentChangeInfo(
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
- $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
+ $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2, [ 'getOption' ] );
$otherUser->expects( $this->once() )
->method( 'getOption' )
->with( 'watchlisttoken' )
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
- $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
+ $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2, [ 'getOption' ] );
$otherUser->expects( $this->once() )
->method( 'getOption' )
->with( 'watchlisttoken' )
] ),
] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$user = $this->getMockNonAnonUserWithId( 1 );
$items = $queryService->getWatchedItemsForUser( $user );
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsForUser( $user, $options );
$this->assertEmpty( $items );
)
->will( $this->returnCallback( function ( $a, $conj ) {
$sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
- return join( $sqlConj, array_map( function ( $s ) {
+ return implode( $sqlConj, array_map( function ( $s ) {
return '(' . $s . ')';
}, $a
) );
)
->will( $this->returnValue( [] ) );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
$items = $queryService->getWatchedItemsForUser( $user, $options );
$this->assertEmpty( $items );
array $options,
$expectedInExceptionMessage
) {
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $this->getMockDb() ) );
+ $queryService = $this->newService( $this->getMockDb() );
$this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
$queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
$mockDb->expects( $this->never() )
->method( $this->anything() );
- $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
+ $queryService = $this->newService( $mockDb );
- $items = $queryService->getWatchedItemsForUser( $this->getMockAnonUser() );
+ $items = $queryService->getWatchedItemsForUser(
+ new UserIdentityValue( 0, 'AnonUser', 0 ) );
$this->assertEmpty( $items );
}