Merge "Add WatchedItemStore::getWatchedItemsForUser"
[lhc/web/wiklou.git] / tests / phpunit / includes / WatchedItemStoreUnitTest.php
index 2996abf..1354b1c 100644 (file)
@@ -323,6 +323,247 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                $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 );
 
@@ -1334,6 +1575,251 @@ class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
                );
        }
 
+       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() )