$this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
}
+ public function testCountVisitingWatchersMultiple() {
+ $titleValuesWithThresholds = [
+ [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
+ [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
+ [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
+ ];
+
+ $dbResult = [
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
+ ];
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->exactly( 2 * 3 ) )
+ ->method( 'addQuotes' )
+ ->will( $this->returnCallback( function( $value ) {
+ return "'$value'";
+ } ) );
+ $mockDb->expects( $this->exactly( 3 ) )
+ ->method( 'timestamp' )
+ ->will( $this->returnCallback( function( $value ) {
+ return 'TS' . $value . 'TS';
+ } ) );
+ $mockDb->expects( $this->any() )
+ ->method( 'makeList' )
+ ->with(
+ $this->isType( 'array' ),
+ $this->isType( 'int' )
+ )
+ ->will( $this->returnCallback( function( $a, $conj ) {
+ $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
+ return join( $sqlConj, array_map( function( $s ) {
+ return '(' . $s . ')';
+ }, $a
+ ) );
+ } ) );
+ $mockDb->expects( $this->never() )
+ ->method( 'makeWhereFrom2d' );
+
+ $expectedCond =
+ '((wl_namespace = 0) AND (' .
+ "(((wl_title = 'SomeDbKey') AND (" .
+ "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
+ ')) OR (' .
+ "(wl_title = 'OtherDbKey') AND (" .
+ "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
+ '))))' .
+ ') OR ((wl_namespace = 1) AND (' .
+ "(((wl_title = 'AnotherDbKey') AND (".
+ "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
+ ')))))';
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
+ $expectedCond,
+ $this->isType( 'string' ),
+ [
+ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
+ ]
+ )
+ ->will(
+ $this->returnValue( $dbResult )
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $expected = [
+ 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
+ 1 => [ 'AnotherDbKey' => 500 ],
+ ];
+ $this->assertEquals(
+ $expected,
+ $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
+ );
+ }
+
+ public function testCountVisitingWatchersMultiple_withMissingTargets() {
+ $titleValuesWithThresholds = [
+ [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
+ [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
+ [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
+ [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
+ [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
+ ];
+
+ $dbResult = [
+ $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
+ $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
+ $this->getFakeRow(
+ [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
+ ),
+ $this->getFakeRow(
+ [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
+ ),
+ ];
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->exactly( 2 * 3 ) )
+ ->method( 'addQuotes' )
+ ->will( $this->returnCallback( function( $value ) {
+ return "'$value'";
+ } ) );
+ $mockDb->expects( $this->exactly( 3 ) )
+ ->method( 'timestamp' )
+ ->will( $this->returnCallback( function( $value ) {
+ return 'TS' . $value . 'TS';
+ } ) );
+ $mockDb->expects( $this->any() )
+ ->method( 'makeList' )
+ ->with(
+ $this->isType( 'array' ),
+ $this->isType( 'int' )
+ )
+ ->will( $this->returnCallback( function( $a, $conj ) {
+ $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
+ return join( $sqlConj, array_map( function( $s ) {
+ return '(' . $s . ')';
+ }, $a
+ ) );
+ } ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+
+ $expectedCond =
+ '((wl_namespace = 0) AND (' .
+ "(((wl_title = 'SomeDbKey') AND (" .
+ "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
+ ')) OR (' .
+ "(wl_title = 'OtherDbKey') AND (" .
+ "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
+ '))))' .
+ ') OR ((wl_namespace = 1) AND (' .
+ "(((wl_title = 'AnotherDbKey') AND (".
+ "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
+ '))))' .
+ ') OR ' .
+ '(makeWhereFrom2d return value)';
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
+ $expectedCond,
+ $this->isType( 'string' ),
+ [
+ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
+ ]
+ )
+ ->will(
+ $this->returnValue( $dbResult )
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $expected = [
+ 0 => [
+ 'SomeDbKey' => 100, 'OtherDbKey' => 300,
+ 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
+ ],
+ 1 => [ 'AnotherDbKey' => 500 ],
+ ];
+ $this->assertEquals(
+ $expected,
+ $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
+ );
+ }
+
+ /**
+ * @dataProvider provideIntWithDbUnsafeVersion
+ */
+ public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
+ $titleValuesWithThresholds = [
+ [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
+ [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
+ [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
+ ];
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->any() )
+ ->method( 'makeList' )
+ ->will( $this->returnValue( 'makeList return value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
+ 'makeList return value',
+ $this->isType( 'string' ),
+ [
+ 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
+ 'HAVING' => 'COUNT(*) >= 50',
+ ]
+ )
+ ->will(
+ $this->returnValue( [] )
+ );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $expected = [
+ 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
+ 1 => [ 'AnotherDbKey' => 0 ],
+ ];
+ $this->assertEquals(
+ $expected,
+ $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
+ );
+ }
+
public function testCountUnreadNotifications() {
$user = $this->getMockNonAnonUserWithId( 1 );
);
}
+ public function testGetNotificationTimestampsBatch() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $dbResult = [
+ $this->getFakeRow( [
+ 'wl_namespace' => 0,
+ 'wl_title' => 'SomeDbKey',
+ 'wl_notificationtimestamp' => '20151212010101',
+ ] ),
+ $this->getFakeRow(
+ [
+ 'wl_namespace' => 1,
+ 'wl_title' => 'AnotherDbKey',
+ 'wl_notificationtimestamp' => null,
+ ]
+ ),
+ ];
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( $dbResult ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->exactly( 2 ) )
+ ->method( 'get' )
+ ->withConsecutive(
+ [ '0:SomeDbKey:1' ],
+ [ '1:AnotherDbKey:1' ]
+ )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_notWatchedTarget() {
+ $targets = [
+ new TitleValue( 0, 'OtherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ [ 'OtherDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->once() )
+ ->method( 'get' )
+ ->with( '0:OtherDbKey:1' )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'OtherDbKey' => false, ],
+ ],
+ $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_cachedItem() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
+
+ $mockDb = $this->getMockDb();
+
+ $mockDb->expects( $this->once() )
+ ->method( 'makeWhereFrom2d' )
+ ->with(
+ [ 1 => [ 'AnotherDbKey' => 1 ] ],
+ $this->isType( 'string' ),
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
+ $mockDb->expects( $this->once() )
+ ->method( 'select' )
+ ->with(
+ 'watchlist',
+ [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
+ [
+ 'makeWhereFrom2d return value',
+ 'wl_user' => 1
+ ],
+ $this->isType( 'string' )
+ )
+ ->will( $this->returnValue( [
+ $this->getFakeRow(
+ [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
+ )
+ ] ) );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->at( 1 ) )
+ ->method( 'get' )
+ ->with( '0:SomeDbKey:1' )
+ ->will( $this->returnValue( $cachedItem ) );
+ $mockCache->expects( $this->at( 3 ) )
+ ->method( 'get' )
+ ->with( '1:AnotherDbKey:1' )
+ ->will( $this->returnValue( null ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch( $user, $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_allItemsCached() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $user = $this->getMockNonAnonUserWithId( 1 );
+ $cachedItems = [
+ new WatchedItem( $user, $targets[0], '20151212010101' ),
+ new WatchedItem( $user, $targets[1], null ),
+ ];
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )->method( $this->anything() );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->at( 1 ) )
+ ->method( 'get' )
+ ->with( '0:SomeDbKey:1' )
+ ->will( $this->returnValue( $cachedItems[0] ) );
+ $mockCache->expects( $this->at( 3 ) )
+ ->method( 'get' )
+ ->with( '1:AnotherDbKey:1' )
+ ->will( $this->returnValue( $cachedItems[1] ) );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->never() )->method( 'delete' );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => '20151212010101', ],
+ 1 => [ 'AnotherDbKey' => null, ],
+ ],
+ $store->getNotificationTimestampsBatch( $user, $targets )
+ );
+ }
+
+ public function testGetNotificationTimestampsBatch_anonymousUser() {
+ $targets = [
+ new TitleValue( 0, 'SomeDbKey' ),
+ new TitleValue( 1, 'AnotherDbKey' ),
+ ];
+
+ $mockDb = $this->getMockDb();
+ $mockDb->expects( $this->never() )->method( $this->anything() );
+
+ $mockCache = $this->getMockCache();
+ $mockCache->expects( $this->never() )->method( $this->anything() );
+
+ $store = new WatchedItemStore(
+ $this->getMockLoadBalancer( $mockDb ),
+ $mockCache
+ );
+
+ $this->assertEquals(
+ [
+ 0 => [ 'SomeDbKey' => false, ],
+ 1 => [ 'AnotherDbKey' => false, ],
+ ],
+ $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
+ );
+ }
+
public function testResetNotificationTimestamp_anonymousUser() {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->never() )