2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LBFactory
;
4 use Wikimedia\Rdbms\LoadBalancer
;
5 use Wikimedia\ScopedCallback
;
6 use Wikimedia\TestingAccessWrapper
;
11 * @covers WatchedItemStore
13 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
16 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
18 private function getMockDb() {
19 return $this->createMock( IDatabase
::class );
23 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
25 private function getMockLoadBalancer(
27 $expectedConnectionType = null
29 $mock = $this->getMockBuilder( LoadBalancer
::class )
30 ->disableOriginalConstructor()
32 if ( $expectedConnectionType !== null ) {
33 $mock->expects( $this->any() )
34 ->method( 'getConnectionRef' )
35 ->with( $expectedConnectionType )
36 ->will( $this->returnValue( $mockDb ) );
38 $mock->expects( $this->any() )
39 ->method( 'getConnectionRef' )
40 ->will( $this->returnValue( $mockDb ) );
46 * @return PHPUnit_Framework_MockObject_MockObject|LBFactory
48 private function getMockLBFactory(
50 $expectedConnectionType = null
52 $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
53 $mock = $this->getMockBuilder( LBFactory
::class )
54 ->disableOriginalConstructor()
56 $mock->expects( $this->any() )
57 ->method( 'getMainLB' )
58 ->will( $this->returnValue( $loadBalancer ) );
63 * @return PHPUnit_Framework_MockObject_MockObject|JobQueueGroup
65 private function getMockJobQueueGroup() {
66 $mock = $this->getMockBuilder( JobQueueGroup
::class )
67 ->disableOriginalConstructor()
69 $mock->expects( $this->any() )
71 ->will( $this->returnCallback( function ( Job
$job ) {
74 $mock->expects( $this->any() )
75 ->method( 'lazyPush' )
76 ->will( $this->returnCallback( function ( Job
$job ) {
83 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
85 private function getMockCache() {
86 $mock = $this->getMockBuilder( HashBagOStuff
::class )
87 ->disableOriginalConstructor()
88 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
90 $mock->expects( $this->any() )
92 ->will( $this->returnCallback( function () {
93 return implode( ':', func_get_args() );
99 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
101 private function getMockReadOnlyMode( $readOnly = false ) {
102 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
103 ->disableOriginalConstructor()
105 $mock->expects( $this->any() )
106 ->method( 'isReadOnly' )
107 ->will( $this->returnValue( $readOnly ) );
113 * @return PHPUnit_Framework_MockObject_MockObject|User
115 private function getMockNonAnonUserWithId( $id ) {
116 $mock = $this->createMock( User
::class );
117 $mock->expects( $this->any() )
119 ->will( $this->returnValue( false ) );
120 $mock->expects( $this->any() )
122 ->will( $this->returnValue( $id ) );
129 private function getAnonUser() {
130 return User
::newFromName( 'Anon_User' );
133 private function getFakeRow( array $rowValues ) {
134 $fakeRow = new stdClass();
135 foreach ( $rowValues as $valueName => $value ) {
136 $fakeRow->$valueName = $value;
141 private function newWatchedItemStore(
142 LBFactory
$lbFactory,
143 JobQueueGroup
$queueGroup,
144 HashBagOStuff
$cache,
145 ReadOnlyMode
$readOnlyMode
147 return new WatchedItemStore(
157 public function testClearWatchedItems() {
158 $user = $this->getMockNonAnonUserWithId( 7 );
160 $mockDb = $this->getMockDb();
161 $mockDb->expects( $this->once() )
162 ->method( 'selectField' )
167 'wl_user' => $user->getId(),
169 $this->isType( 'string' )
171 ->will( $this->returnValue( 12 ) );
172 $mockDb->expects( $this->once() )
177 $this->isType( 'string' )
180 $mockCache = $this->getMockCache();
181 $mockCache->expects( $this->never() )->method( 'get' );
182 $mockCache->expects( $this->never() )->method( 'set' );
183 $mockCache->expects( $this->once() )
187 $store = $this->newWatchedItemStore(
188 $this->getMockLBFactory( $mockDb ),
189 $this->getMockJobQueueGroup(),
191 $this->getMockReadOnlyMode()
193 TestingAccessWrapper
::newFromObject( $store )
194 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
196 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
199 public function testClearWatchedItems_tooManyItemsWatched() {
200 $user = $this->getMockNonAnonUserWithId( 7 );
202 $mockDb = $this->getMockDb();
203 $mockDb->expects( $this->once() )
204 ->method( 'selectField' )
209 'wl_user' => $user->getId(),
211 $this->isType( 'string' )
213 ->will( $this->returnValue( 99999 ) );
215 $mockCache = $this->getMockCache();
216 $mockCache->expects( $this->never() )->method( 'get' );
217 $mockCache->expects( $this->never() )->method( 'set' );
218 $mockCache->expects( $this->never() )->method( 'delete' );
220 $store = $this->newWatchedItemStore(
221 $this->getMockLBFactory( $mockDb ),
222 $this->getMockJobQueueGroup(),
224 $this->getMockReadOnlyMode()
227 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
230 public function testCountWatchedItems() {
231 $user = $this->getMockNonAnonUserWithId( 1 );
233 $mockDb = $this->getMockDb();
234 $mockDb->expects( $this->exactly( 1 ) )
235 ->method( 'selectField' )
240 'wl_user' => $user->getId(),
242 $this->isType( 'string' )
244 ->will( $this->returnValue( '12' ) );
246 $mockCache = $this->getMockCache();
247 $mockCache->expects( $this->never() )->method( 'get' );
248 $mockCache->expects( $this->never() )->method( 'set' );
249 $mockCache->expects( $this->never() )->method( 'delete' );
251 $store = $this->newWatchedItemStore(
252 $this->getMockLBFactory( $mockDb ),
253 $this->getMockJobQueueGroup(),
255 $this->getMockReadOnlyMode()
258 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
261 public function testCountWatchers() {
262 $titleValue = new TitleValue( 0, 'SomeDbKey' );
264 $mockDb = $this->getMockDb();
265 $mockDb->expects( $this->exactly( 1 ) )
266 ->method( 'selectField' )
271 'wl_namespace' => $titleValue->getNamespace(),
272 'wl_title' => $titleValue->getDBkey(),
274 $this->isType( 'string' )
276 ->will( $this->returnValue( '7' ) );
278 $mockCache = $this->getMockCache();
279 $mockCache->expects( $this->never() )->method( 'get' );
280 $mockCache->expects( $this->never() )->method( 'set' );
281 $mockCache->expects( $this->never() )->method( 'delete' );
283 $store = $this->newWatchedItemStore(
284 $this->getMockLBFactory( $mockDb ),
285 $this->getMockJobQueueGroup(),
287 $this->getMockReadOnlyMode()
290 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
293 public function testCountWatchersMultiple() {
295 new TitleValue( 0, 'SomeDbKey' ),
296 new TitleValue( 0, 'OtherDbKey' ),
297 new TitleValue( 1, 'AnotherDbKey' ),
300 $mockDb = $this->getMockDb();
303 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
304 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
305 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
308 $mockDb->expects( $this->once() )
309 ->method( 'makeWhereFrom2d' )
311 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
312 $this->isType( 'string' ),
313 $this->isType( 'string' )
315 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
316 $mockDb->expects( $this->once() )
320 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
321 [ 'makeWhereFrom2d return value' ],
322 $this->isType( 'string' ),
324 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
328 $this->returnValue( $dbResult )
331 $mockCache = $this->getMockCache();
332 $mockCache->expects( $this->never() )->method( 'get' );
333 $mockCache->expects( $this->never() )->method( 'set' );
334 $mockCache->expects( $this->never() )->method( 'delete' );
336 $store = $this->newWatchedItemStore(
337 $this->getMockLBFactory( $mockDb ),
338 $this->getMockJobQueueGroup(),
340 $this->getMockReadOnlyMode()
344 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
345 1 => [ 'AnotherDbKey' => 500 ],
347 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
350 public function provideIntWithDbUnsafeVersion() {
353 [ "50; DROP TABLE watchlist;\n--" ],
358 * @dataProvider provideIntWithDbUnsafeVersion
360 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
362 new TitleValue( 0, 'SomeDbKey' ),
363 new TitleValue( 0, 'OtherDbKey' ),
364 new TitleValue( 1, 'AnotherDbKey' ),
367 $mockDb = $this->getMockDb();
370 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
371 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
372 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
375 $mockDb->expects( $this->once() )
376 ->method( 'makeWhereFrom2d' )
378 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
379 $this->isType( 'string' ),
380 $this->isType( 'string' )
382 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
383 $mockDb->expects( $this->once() )
387 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
388 [ 'makeWhereFrom2d return value' ],
389 $this->isType( 'string' ),
391 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
392 'HAVING' => 'COUNT(*) >= 50',
396 $this->returnValue( $dbResult )
399 $mockCache = $this->getMockCache();
400 $mockCache->expects( $this->never() )->method( 'get' );
401 $mockCache->expects( $this->never() )->method( 'set' );
402 $mockCache->expects( $this->never() )->method( 'delete' );
404 $store = $this->newWatchedItemStore(
405 $this->getMockLBFactory( $mockDb ),
406 $this->getMockJobQueueGroup(),
408 $this->getMockReadOnlyMode()
412 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
413 1 => [ 'AnotherDbKey' => 500 ],
417 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
421 public function testCountVisitingWatchers() {
422 $titleValue = new TitleValue( 0, 'SomeDbKey' );
424 $mockDb = $this->getMockDb();
425 $mockDb->expects( $this->exactly( 1 ) )
426 ->method( 'selectField' )
431 'wl_namespace' => $titleValue->getNamespace(),
432 'wl_title' => $titleValue->getDBkey(),
433 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
435 $this->isType( 'string' )
437 ->will( $this->returnValue( '7' ) );
438 $mockDb->expects( $this->exactly( 1 ) )
439 ->method( 'addQuotes' )
440 ->will( $this->returnCallback( function ( $value ) {
443 $mockDb->expects( $this->exactly( 1 ) )
444 ->method( 'timestamp' )
445 ->will( $this->returnCallback( function ( $value ) {
446 return 'TS' . $value . 'TS';
449 $mockCache = $this->getMockCache();
450 $mockCache->expects( $this->never() )->method( 'set' );
451 $mockCache->expects( $this->never() )->method( 'get' );
452 $mockCache->expects( $this->never() )->method( 'delete' );
454 $store = $this->newWatchedItemStore(
455 $this->getMockLBFactory( $mockDb ),
456 $this->getMockJobQueueGroup(),
458 $this->getMockReadOnlyMode()
461 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
464 public function testCountVisitingWatchersMultiple() {
465 $titleValuesWithThresholds = [
466 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
467 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
468 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
472 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
473 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
475 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
478 $mockDb = $this->getMockDb();
479 $mockDb->expects( $this->exactly( 2 * 3 ) )
480 ->method( 'addQuotes' )
481 ->will( $this->returnCallback( function ( $value ) {
484 $mockDb->expects( $this->exactly( 3 ) )
485 ->method( 'timestamp' )
486 ->will( $this->returnCallback( function ( $value ) {
487 return 'TS' . $value . 'TS';
489 $mockDb->expects( $this->any() )
490 ->method( 'makeList' )
492 $this->isType( 'array' ),
493 $this->isType( 'int' )
495 ->will( $this->returnCallback( function ( $a, $conj ) {
496 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
497 return implode( $sqlConj, array_map( function ( $s ) {
498 return '(' . $s . ')';
502 $mockDb->expects( $this->never() )
503 ->method( 'makeWhereFrom2d' );
506 '((wl_namespace = 0) AND (' .
507 "(((wl_title = 'SomeDbKey') AND (" .
508 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
510 "(wl_title = 'OtherDbKey') AND (" .
511 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
513 ') OR ((wl_namespace = 1) AND (' .
514 "(((wl_title = 'AnotherDbKey') AND (" .
515 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
517 $mockDb->expects( $this->once() )
521 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
523 $this->isType( 'string' ),
525 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
529 $this->returnValue( $dbResult )
532 $mockCache = $this->getMockCache();
533 $mockCache->expects( $this->never() )->method( 'get' );
534 $mockCache->expects( $this->never() )->method( 'set' );
535 $mockCache->expects( $this->never() )->method( 'delete' );
537 $store = $this->newWatchedItemStore(
538 $this->getMockLBFactory( $mockDb ),
539 $this->getMockJobQueueGroup(),
541 $this->getMockReadOnlyMode()
545 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
546 1 => [ 'AnotherDbKey' => 500 ],
550 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
554 public function testCountVisitingWatchersMultiple_withMissingTargets() {
555 $titleValuesWithThresholds = [
556 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
557 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
558 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
559 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
560 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
564 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
565 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
567 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
570 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
573 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
576 $mockDb = $this->getMockDb();
577 $mockDb->expects( $this->exactly( 2 * 3 ) )
578 ->method( 'addQuotes' )
579 ->will( $this->returnCallback( function ( $value ) {
582 $mockDb->expects( $this->exactly( 3 ) )
583 ->method( 'timestamp' )
584 ->will( $this->returnCallback( function ( $value ) {
585 return 'TS' . $value . 'TS';
587 $mockDb->expects( $this->any() )
588 ->method( 'makeList' )
590 $this->isType( 'array' ),
591 $this->isType( 'int' )
593 ->will( $this->returnCallback( function ( $a, $conj ) {
594 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
595 return implode( $sqlConj, array_map( function ( $s ) {
596 return '(' . $s . ')';
600 $mockDb->expects( $this->once() )
601 ->method( 'makeWhereFrom2d' )
603 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
604 $this->isType( 'string' ),
605 $this->isType( 'string' )
607 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
610 '((wl_namespace = 0) AND (' .
611 "(((wl_title = 'SomeDbKey') AND (" .
612 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
614 "(wl_title = 'OtherDbKey') AND (" .
615 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
617 ') OR ((wl_namespace = 1) AND (' .
618 "(((wl_title = 'AnotherDbKey') AND (" .
619 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
622 '(makeWhereFrom2d return value)';
623 $mockDb->expects( $this->once() )
627 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
629 $this->isType( 'string' ),
631 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
635 $this->returnValue( $dbResult )
638 $mockCache = $this->getMockCache();
639 $mockCache->expects( $this->never() )->method( 'get' );
640 $mockCache->expects( $this->never() )->method( 'set' );
641 $mockCache->expects( $this->never() )->method( 'delete' );
643 $store = $this->newWatchedItemStore(
644 $this->getMockLBFactory( $mockDb ),
645 $this->getMockJobQueueGroup(),
647 $this->getMockReadOnlyMode()
652 'SomeDbKey' => 100, 'OtherDbKey' => 300,
653 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
655 1 => [ 'AnotherDbKey' => 500 ],
659 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
664 * @dataProvider provideIntWithDbUnsafeVersion
666 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
667 $titleValuesWithThresholds = [
668 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
669 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
670 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
673 $mockDb = $this->getMockDb();
674 $mockDb->expects( $this->any() )
675 ->method( 'makeList' )
676 ->will( $this->returnValue( 'makeList return value' ) );
677 $mockDb->expects( $this->once() )
681 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
682 'makeList return value',
683 $this->isType( 'string' ),
685 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
686 'HAVING' => 'COUNT(*) >= 50',
690 $this->returnValue( [] )
693 $mockCache = $this->getMockCache();
694 $mockCache->expects( $this->never() )->method( 'get' );
695 $mockCache->expects( $this->never() )->method( 'set' );
696 $mockCache->expects( $this->never() )->method( 'delete' );
698 $store = $this->newWatchedItemStore(
699 $this->getMockLBFactory( $mockDb ),
700 $this->getMockJobQueueGroup(),
702 $this->getMockReadOnlyMode()
706 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
707 1 => [ 'AnotherDbKey' => 0 ],
711 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
715 public function testCountUnreadNotifications() {
716 $user = $this->getMockNonAnonUserWithId( 1 );
718 $mockDb = $this->getMockDb();
719 $mockDb->expects( $this->exactly( 1 ) )
720 ->method( 'selectRowCount' )
725 "wl_notificationtimestamp IS NOT NULL",
728 $this->isType( 'string' )
730 ->will( $this->returnValue( '9' ) );
732 $mockCache = $this->getMockCache();
733 $mockCache->expects( $this->never() )->method( 'set' );
734 $mockCache->expects( $this->never() )->method( 'get' );
735 $mockCache->expects( $this->never() )->method( 'delete' );
737 $store = $this->newWatchedItemStore(
738 $this->getMockLBFactory( $mockDb ),
739 $this->getMockJobQueueGroup(),
741 $this->getMockReadOnlyMode()
744 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
748 * @dataProvider provideIntWithDbUnsafeVersion
750 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
751 $user = $this->getMockNonAnonUserWithId( 1 );
753 $mockDb = $this->getMockDb();
754 $mockDb->expects( $this->exactly( 1 ) )
755 ->method( 'selectRowCount' )
760 "wl_notificationtimestamp IS NOT NULL",
763 $this->isType( 'string' ),
766 ->will( $this->returnValue( '50' ) );
768 $mockCache = $this->getMockCache();
769 $mockCache->expects( $this->never() )->method( 'set' );
770 $mockCache->expects( $this->never() )->method( 'get' );
771 $mockCache->expects( $this->never() )->method( 'delete' );
773 $store = $this->newWatchedItemStore(
774 $this->getMockLBFactory( $mockDb ),
775 $this->getMockJobQueueGroup(),
777 $this->getMockReadOnlyMode()
782 $store->countUnreadNotifications( $user, $limit )
787 * @dataProvider provideIntWithDbUnsafeVersion
789 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
790 $user = $this->getMockNonAnonUserWithId( 1 );
792 $mockDb = $this->getMockDb();
793 $mockDb->expects( $this->exactly( 1 ) )
794 ->method( 'selectRowCount' )
799 "wl_notificationtimestamp IS NOT NULL",
802 $this->isType( 'string' ),
805 ->will( $this->returnValue( '9' ) );
807 $mockCache = $this->getMockCache();
808 $mockCache->expects( $this->never() )->method( 'set' );
809 $mockCache->expects( $this->never() )->method( 'get' );
810 $mockCache->expects( $this->never() )->method( 'delete' );
812 $store = $this->newWatchedItemStore(
813 $this->getMockLBFactory( $mockDb ),
814 $this->getMockJobQueueGroup(),
816 $this->getMockReadOnlyMode()
821 $store->countUnreadNotifications( $user, $limit )
825 public function testDuplicateEntry_nothingToDuplicate() {
826 $mockDb = $this->getMockDb();
827 $mockDb->expects( $this->once() )
833 'wl_notificationtimestamp',
837 'wl_title' => 'Old_Title',
839 'WatchedItemStore::duplicateEntry',
842 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
844 $store = $this->newWatchedItemStore(
845 $this->getMockLBFactory( $mockDb ),
846 $this->getMockJobQueueGroup(),
847 $this->getMockCache(),
848 $this->getMockReadOnlyMode()
851 $store->duplicateEntry(
852 Title
::newFromText( 'Old_Title' ),
853 Title
::newFromText( 'New_Title' )
857 public function testDuplicateEntry_somethingToDuplicate() {
859 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
860 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
863 $mockDb = $this->getMockDb();
864 $mockDb->expects( $this->at( 0 ) )
870 'wl_notificationtimestamp',
874 'wl_title' => 'Old_Title',
877 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
878 $mockDb->expects( $this->at( 1 ) )
879 ->method( 'replace' )
882 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
887 'wl_title' => 'New_Title',
888 'wl_notificationtimestamp' => '20151212010101',
893 'wl_title' => 'New_Title',
894 'wl_notificationtimestamp' => null,
897 $this->isType( 'string' )
900 $mockCache = $this->getMockCache();
901 $mockCache->expects( $this->never() )->method( 'get' );
902 $mockCache->expects( $this->never() )->method( 'delete' );
904 $store = $this->newWatchedItemStore(
905 $this->getMockLBFactory( $mockDb ),
906 $this->getMockJobQueueGroup(),
908 $this->getMockReadOnlyMode()
911 $store->duplicateEntry(
912 Title
::newFromText( 'Old_Title' ),
913 Title
::newFromText( 'New_Title' )
917 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
918 $mockDb = $this->getMockDb();
919 $mockDb->expects( $this->at( 0 ) )
925 'wl_notificationtimestamp',
929 'wl_title' => 'Old_Title',
932 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
933 $mockDb->expects( $this->at( 1 ) )
939 'wl_notificationtimestamp',
943 'wl_title' => 'Old_Title',
946 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
948 $mockCache = $this->getMockCache();
949 $mockCache->expects( $this->never() )->method( 'get' );
950 $mockCache->expects( $this->never() )->method( 'delete' );
952 $store = $this->newWatchedItemStore(
953 $this->getMockLBFactory( $mockDb ),
954 $this->getMockJobQueueGroup(),
956 $this->getMockReadOnlyMode()
959 $store->duplicateAllAssociatedEntries(
960 Title
::newFromText( 'Old_Title' ),
961 Title
::newFromText( 'New_Title' )
965 public function provideLinkTargetPairs() {
967 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
968 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
973 * @dataProvider provideLinkTargetPairs
975 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
976 LinkTarget
$oldTarget,
977 LinkTarget
$newTarget
980 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
983 $mockDb = $this->getMockDb();
984 $mockDb->expects( $this->at( 0 ) )
990 'wl_notificationtimestamp',
993 'wl_namespace' => $oldTarget->getNamespace(),
994 'wl_title' => $oldTarget->getDBkey(),
997 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
998 $mockDb->expects( $this->at( 1 ) )
999 ->method( 'replace' )
1002 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1006 'wl_namespace' => $newTarget->getNamespace(),
1007 'wl_title' => $newTarget->getDBkey(),
1008 'wl_notificationtimestamp' => '20151212010101',
1011 $this->isType( 'string' )
1013 $mockDb->expects( $this->at( 2 ) )
1014 ->method( 'select' )
1019 'wl_notificationtimestamp',
1022 'wl_namespace' => $oldTarget->getNamespace() +
1,
1023 'wl_title' => $oldTarget->getDBkey(),
1026 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1027 $mockDb->expects( $this->at( 3 ) )
1028 ->method( 'replace' )
1031 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1035 'wl_namespace' => $newTarget->getNamespace() +
1,
1036 'wl_title' => $newTarget->getDBkey(),
1037 'wl_notificationtimestamp' => '20151212010101',
1040 $this->isType( 'string' )
1043 $mockCache = $this->getMockCache();
1044 $mockCache->expects( $this->never() )->method( 'get' );
1045 $mockCache->expects( $this->never() )->method( 'delete' );
1047 $store = $this->newWatchedItemStore(
1048 $this->getMockLBFactory( $mockDb ),
1049 $this->getMockJobQueueGroup(),
1051 $this->getMockReadOnlyMode()
1054 $store->duplicateAllAssociatedEntries(
1060 public function testAddWatch_nonAnonymousUser() {
1061 $mockDb = $this->getMockDb();
1062 $mockDb->expects( $this->once() )
1063 ->method( 'insert' )
1069 'wl_namespace' => 0,
1070 'wl_title' => 'Some_Page',
1071 'wl_notificationtimestamp' => null,
1076 $mockCache = $this->getMockCache();
1077 $mockCache->expects( $this->once() )
1078 ->method( 'delete' )
1079 ->with( '0:Some_Page:1' );
1081 $store = $this->newWatchedItemStore(
1082 $this->getMockLBFactory( $mockDb ),
1083 $this->getMockJobQueueGroup(),
1085 $this->getMockReadOnlyMode()
1089 $this->getMockNonAnonUserWithId( 1 ),
1090 Title
::newFromText( 'Some_Page' )
1094 public function testAddWatch_anonymousUser() {
1095 $mockDb = $this->getMockDb();
1096 $mockDb->expects( $this->never() )
1097 ->method( 'insert' );
1099 $mockCache = $this->getMockCache();
1100 $mockCache->expects( $this->never() )
1101 ->method( 'delete' );
1103 $store = $this->newWatchedItemStore(
1104 $this->getMockLBFactory( $mockDb ),
1105 $this->getMockJobQueueGroup(),
1107 $this->getMockReadOnlyMode()
1111 $this->getAnonUser(),
1112 Title
::newFromText( 'Some_Page' )
1116 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1117 $store = $this->newWatchedItemStore(
1118 $this->getMockLBFactory( $this->getMockDb() ),
1119 $this->getMockJobQueueGroup(),
1120 $this->getMockCache(),
1121 $this->getMockReadOnlyMode( true )
1125 $store->addWatchBatchForUser(
1126 $this->getMockNonAnonUserWithId( 1 ),
1127 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1132 public function testAddWatchBatchForUser_nonAnonymousUser() {
1133 $mockDb = $this->getMockDb();
1134 $mockDb->expects( $this->once() )
1135 ->method( 'insert' )
1141 'wl_namespace' => 0,
1142 'wl_title' => 'Some_Page',
1143 'wl_notificationtimestamp' => null,
1147 'wl_namespace' => 1,
1148 'wl_title' => 'Some_Page',
1149 'wl_notificationtimestamp' => null,
1154 $mockDb->expects( $this->once() )
1155 ->method( 'affectedRows' )
1158 $mockCache = $this->getMockCache();
1159 $mockCache->expects( $this->exactly( 2 ) )
1160 ->method( 'delete' );
1161 $mockCache->expects( $this->at( 1 ) )
1162 ->method( 'delete' )
1163 ->with( '0:Some_Page:1' );
1164 $mockCache->expects( $this->at( 3 ) )
1165 ->method( 'delete' )
1166 ->with( '1:Some_Page:1' );
1168 $store = $this->newWatchedItemStore(
1169 $this->getMockLBFactory( $mockDb ),
1170 $this->getMockJobQueueGroup(),
1172 $this->getMockReadOnlyMode()
1175 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1178 $store->addWatchBatchForUser(
1180 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1185 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1186 $mockDb = $this->getMockDb();
1187 $mockDb->expects( $this->never() )
1188 ->method( 'insert' );
1190 $mockCache = $this->getMockCache();
1191 $mockCache->expects( $this->never() )
1192 ->method( 'delete' );
1194 $store = $this->newWatchedItemStore(
1195 $this->getMockLBFactory( $mockDb ),
1196 $this->getMockJobQueueGroup(),
1198 $this->getMockReadOnlyMode()
1202 $store->addWatchBatchForUser(
1203 $this->getAnonUser(),
1204 [ new TitleValue( 0, 'Other_Page' ) ]
1209 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1210 $user = $this->getMockNonAnonUserWithId( 1 );
1211 $mockDb = $this->getMockDb();
1212 $mockDb->expects( $this->never() )
1213 ->method( 'insert' );
1215 $mockCache = $this->getMockCache();
1216 $mockCache->expects( $this->never() )
1217 ->method( 'delete' );
1219 $store = $this->newWatchedItemStore(
1220 $this->getMockLBFactory( $mockDb ),
1221 $this->getMockJobQueueGroup(),
1223 $this->getMockReadOnlyMode()
1227 $store->addWatchBatchForUser( $user, [] )
1231 public function testLoadWatchedItem_existingItem() {
1232 $mockDb = $this->getMockDb();
1233 $mockDb->expects( $this->once() )
1234 ->method( 'selectRow' )
1237 'wl_notificationtimestamp',
1240 'wl_namespace' => 0,
1241 'wl_title' => 'SomeDbKey',
1244 ->will( $this->returnValue(
1245 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1248 $mockCache = $this->getMockCache();
1249 $mockCache->expects( $this->once() )
1255 $store = $this->newWatchedItemStore(
1256 $this->getMockLBFactory( $mockDb ),
1257 $this->getMockJobQueueGroup(),
1259 $this->getMockReadOnlyMode()
1262 $watchedItem = $store->loadWatchedItem(
1263 $this->getMockNonAnonUserWithId( 1 ),
1264 new TitleValue( 0, 'SomeDbKey' )
1266 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1267 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1268 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1269 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1272 public function testLoadWatchedItem_noItem() {
1273 $mockDb = $this->getMockDb();
1274 $mockDb->expects( $this->once() )
1275 ->method( 'selectRow' )
1278 'wl_notificationtimestamp',
1281 'wl_namespace' => 0,
1282 'wl_title' => 'SomeDbKey',
1285 ->will( $this->returnValue( [] ) );
1287 $mockCache = $this->getMockCache();
1288 $mockCache->expects( $this->never() )->method( 'get' );
1289 $mockCache->expects( $this->never() )->method( 'delete' );
1291 $store = $this->newWatchedItemStore(
1292 $this->getMockLBFactory( $mockDb ),
1293 $this->getMockJobQueueGroup(),
1295 $this->getMockReadOnlyMode()
1299 $store->loadWatchedItem(
1300 $this->getMockNonAnonUserWithId( 1 ),
1301 new TitleValue( 0, 'SomeDbKey' )
1306 public function testLoadWatchedItem_anonymousUser() {
1307 $mockDb = $this->getMockDb();
1308 $mockDb->expects( $this->never() )
1309 ->method( 'selectRow' );
1311 $mockCache = $this->getMockCache();
1312 $mockCache->expects( $this->never() )->method( 'get' );
1313 $mockCache->expects( $this->never() )->method( 'delete' );
1315 $store = $this->newWatchedItemStore(
1316 $this->getMockLBFactory( $mockDb ),
1317 $this->getMockJobQueueGroup(),
1319 $this->getMockReadOnlyMode()
1323 $store->loadWatchedItem(
1324 $this->getAnonUser(),
1325 new TitleValue( 0, 'SomeDbKey' )
1330 public function testRemoveWatch_existingItem() {
1331 $mockDb = $this->getMockDb();
1332 $mockDb->expects( $this->once() )
1333 ->method( 'delete' )
1339 'wl_namespace' => 0,
1340 'wl_title' => [ 'SomeDbKey' ],
1347 'wl_namespace' => 1,
1348 'wl_title' => [ 'SomeDbKey' ],
1352 $mockDb->expects( $this->exactly( 1 ) )
1353 ->method( 'affectedRows' )
1356 $mockCache = $this->getMockCache();
1357 $mockCache->expects( $this->never() )->method( 'get' );
1358 $mockCache->expects( $this->once() )
1359 ->method( 'delete' )
1361 [ '0:SomeDbKey:1' ],
1365 $store = $this->newWatchedItemStore(
1366 $this->getMockLBFactory( $mockDb ),
1367 $this->getMockJobQueueGroup(),
1369 $this->getMockReadOnlyMode()
1372 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1374 $store->removeWatch(
1375 $this->getMockNonAnonUserWithId( 1 ),
1376 Title
::newFromTitleValue( $titleValue )
1381 public function testRemoveWatch_noItem() {
1382 $mockDb = $this->getMockDb();
1383 $mockDb->expects( $this->once() )
1384 ->method( 'delete' )
1390 'wl_namespace' => 0,
1391 'wl_title' => [ 'SomeDbKey' ],
1398 'wl_namespace' => 1,
1399 'wl_title' => [ 'SomeDbKey' ],
1404 $mockDb->expects( $this->once() )
1405 ->method( 'affectedRows' )
1408 $mockCache = $this->getMockCache();
1409 $mockCache->expects( $this->never() )->method( 'get' );
1410 $mockCache->expects( $this->once() )
1411 ->method( 'delete' )
1413 [ '0:SomeDbKey:1' ],
1417 $store = $this->newWatchedItemStore(
1418 $this->getMockLBFactory( $mockDb ),
1419 $this->getMockJobQueueGroup(),
1421 $this->getMockReadOnlyMode()
1424 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1426 $store->removeWatch(
1427 $this->getMockNonAnonUserWithId( 1 ),
1428 Title
::newFromTitleValue( $titleValue )
1433 public function testRemoveWatch_anonymousUser() {
1434 $mockDb = $this->getMockDb();
1435 $mockDb->expects( $this->never() )
1436 ->method( 'delete' );
1438 $mockCache = $this->getMockCache();
1439 $mockCache->expects( $this->never() )->method( 'get' );
1440 $mockCache->expects( $this->never() )
1441 ->method( 'delete' );
1443 $store = $this->newWatchedItemStore(
1444 $this->getMockLBFactory( $mockDb ),
1445 $this->getMockJobQueueGroup(),
1447 $this->getMockReadOnlyMode()
1451 $store->removeWatch(
1452 $this->getAnonUser(),
1453 new TitleValue( 0, 'SomeDbKey' )
1458 public function testGetWatchedItem_existingItem() {
1459 $mockDb = $this->getMockDb();
1460 $mockDb->expects( $this->once() )
1461 ->method( 'selectRow' )
1464 'wl_notificationtimestamp',
1467 'wl_namespace' => 0,
1468 'wl_title' => 'SomeDbKey',
1471 ->will( $this->returnValue(
1472 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1475 $mockCache = $this->getMockCache();
1476 $mockCache->expects( $this->never() )->method( 'delete' );
1477 $mockCache->expects( $this->once() )
1482 ->will( $this->returnValue( null ) );
1483 $mockCache->expects( $this->once() )
1489 $store = $this->newWatchedItemStore(
1490 $this->getMockLBFactory( $mockDb ),
1491 $this->getMockJobQueueGroup(),
1493 $this->getMockReadOnlyMode()
1496 $watchedItem = $store->getWatchedItem(
1497 $this->getMockNonAnonUserWithId( 1 ),
1498 new TitleValue( 0, 'SomeDbKey' )
1500 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1501 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1502 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1503 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1506 public function testGetWatchedItem_cachedItem() {
1507 $mockDb = $this->getMockDb();
1508 $mockDb->expects( $this->never() )
1509 ->method( 'selectRow' );
1511 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1512 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1513 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1515 $mockCache = $this->getMockCache();
1516 $mockCache->expects( $this->never() )->method( 'delete' );
1517 $mockCache->expects( $this->never() )->method( 'set' );
1518 $mockCache->expects( $this->once() )
1523 ->will( $this->returnValue( $cachedItem ) );
1525 $store = $this->newWatchedItemStore(
1526 $this->getMockLBFactory( $mockDb ),
1527 $this->getMockJobQueueGroup(),
1529 $this->getMockReadOnlyMode()
1532 $this->assertEquals(
1534 $store->getWatchedItem(
1541 public function testGetWatchedItem_noItem() {
1542 $mockDb = $this->getMockDb();
1543 $mockDb->expects( $this->once() )
1544 ->method( 'selectRow' )
1547 'wl_notificationtimestamp',
1550 'wl_namespace' => 0,
1551 'wl_title' => 'SomeDbKey',
1554 ->will( $this->returnValue( [] ) );
1556 $mockCache = $this->getMockCache();
1557 $mockCache->expects( $this->never() )->method( 'set' );
1558 $mockCache->expects( $this->never() )->method( 'delete' );
1559 $mockCache->expects( $this->once() )
1561 ->with( '0:SomeDbKey:1' )
1562 ->will( $this->returnValue( false ) );
1564 $store = $this->newWatchedItemStore(
1565 $this->getMockLBFactory( $mockDb ),
1566 $this->getMockJobQueueGroup(),
1568 $this->getMockReadOnlyMode()
1572 $store->getWatchedItem(
1573 $this->getMockNonAnonUserWithId( 1 ),
1574 new TitleValue( 0, 'SomeDbKey' )
1579 public function testGetWatchedItem_anonymousUser() {
1580 $mockDb = $this->getMockDb();
1581 $mockDb->expects( $this->never() )
1582 ->method( 'selectRow' );
1584 $mockCache = $this->getMockCache();
1585 $mockCache->expects( $this->never() )->method( 'set' );
1586 $mockCache->expects( $this->never() )->method( 'get' );
1587 $mockCache->expects( $this->never() )->method( 'delete' );
1589 $store = $this->newWatchedItemStore(
1590 $this->getMockLBFactory( $mockDb ),
1591 $this->getMockJobQueueGroup(),
1593 $this->getMockReadOnlyMode()
1597 $store->getWatchedItem(
1598 $this->getAnonUser(),
1599 new TitleValue( 0, 'SomeDbKey' )
1604 public function testGetWatchedItemsForUser() {
1605 $mockDb = $this->getMockDb();
1606 $mockDb->expects( $this->once() )
1607 ->method( 'select' )
1610 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1613 ->will( $this->returnValue( [
1614 $this->getFakeRow( [
1615 'wl_namespace' => 0,
1616 'wl_title' => 'Foo1',
1617 'wl_notificationtimestamp' => '20151212010101',
1619 $this->getFakeRow( [
1620 'wl_namespace' => 1,
1621 'wl_title' => 'Foo2',
1622 'wl_notificationtimestamp' => null,
1626 $mockCache = $this->getMockCache();
1627 $mockCache->expects( $this->never() )->method( 'delete' );
1628 $mockCache->expects( $this->never() )->method( 'get' );
1629 $mockCache->expects( $this->never() )->method( 'set' );
1631 $store = $this->newWatchedItemStore(
1632 $this->getMockLBFactory( $mockDb ),
1633 $this->getMockJobQueueGroup(),
1635 $this->getMockReadOnlyMode()
1637 $user = $this->getMockNonAnonUserWithId( 1 );
1639 $watchedItems = $store->getWatchedItemsForUser( $user );
1641 $this->assertInternalType( 'array', $watchedItems );
1642 $this->assertCount( 2, $watchedItems );
1643 foreach ( $watchedItems as $watchedItem ) {
1644 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1646 $this->assertEquals(
1647 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1650 $this->assertEquals(
1651 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1656 public function provideDbTypes() {
1658 [ false, DB_REPLICA
],
1659 [ true, DB_MASTER
],
1664 * @dataProvider provideDbTypes
1666 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1667 $mockDb = $this->getMockDb();
1668 $mockCache = $this->getMockCache();
1669 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1670 $user = $this->getMockNonAnonUserWithId( 1 );
1672 $mockDb->expects( $this->once() )
1673 ->method( 'select' )
1676 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1678 $this->isType( 'string' ),
1679 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1681 ->will( $this->returnValue( [] ) );
1683 $store = $this->newWatchedItemStore(
1685 $this->getMockJobQueueGroup(),
1687 $this->getMockReadOnlyMode()
1690 $watchedItems = $store->getWatchedItemsForUser(
1692 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1694 $this->assertEquals( [], $watchedItems );
1697 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1698 $store = $this->newWatchedItemStore(
1699 $this->getMockLBFactory( $this->getMockDb() ),
1700 $this->getMockJobQueueGroup(),
1701 $this->getMockCache(),
1702 $this->getMockReadOnlyMode()
1705 $this->setExpectedException( InvalidArgumentException
::class );
1706 $store->getWatchedItemsForUser(
1707 $this->getMockNonAnonUserWithId( 1 ),
1712 public function testIsWatchedItem_existingItem() {
1713 $mockDb = $this->getMockDb();
1714 $mockDb->expects( $this->once() )
1715 ->method( 'selectRow' )
1718 'wl_notificationtimestamp',
1721 'wl_namespace' => 0,
1722 'wl_title' => 'SomeDbKey',
1725 ->will( $this->returnValue(
1726 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1729 $mockCache = $this->getMockCache();
1730 $mockCache->expects( $this->never() )->method( 'delete' );
1731 $mockCache->expects( $this->once() )
1733 ->with( '0:SomeDbKey:1' )
1734 ->will( $this->returnValue( false ) );
1735 $mockCache->expects( $this->once() )
1741 $store = $this->newWatchedItemStore(
1742 $this->getMockLBFactory( $mockDb ),
1743 $this->getMockJobQueueGroup(),
1745 $this->getMockReadOnlyMode()
1750 $this->getMockNonAnonUserWithId( 1 ),
1751 new TitleValue( 0, 'SomeDbKey' )
1756 public function testIsWatchedItem_noItem() {
1757 $mockDb = $this->getMockDb();
1758 $mockDb->expects( $this->once() )
1759 ->method( 'selectRow' )
1762 'wl_notificationtimestamp',
1765 'wl_namespace' => 0,
1766 'wl_title' => 'SomeDbKey',
1769 ->will( $this->returnValue( [] ) );
1771 $mockCache = $this->getMockCache();
1772 $mockCache->expects( $this->never() )->method( 'set' );
1773 $mockCache->expects( $this->never() )->method( 'delete' );
1774 $mockCache->expects( $this->once() )
1776 ->with( '0:SomeDbKey:1' )
1777 ->will( $this->returnValue( false ) );
1779 $store = $this->newWatchedItemStore(
1780 $this->getMockLBFactory( $mockDb ),
1781 $this->getMockJobQueueGroup(),
1783 $this->getMockReadOnlyMode()
1788 $this->getMockNonAnonUserWithId( 1 ),
1789 new TitleValue( 0, 'SomeDbKey' )
1794 public function testIsWatchedItem_anonymousUser() {
1795 $mockDb = $this->getMockDb();
1796 $mockDb->expects( $this->never() )
1797 ->method( 'selectRow' );
1799 $mockCache = $this->getMockCache();
1800 $mockCache->expects( $this->never() )->method( 'set' );
1801 $mockCache->expects( $this->never() )->method( 'get' );
1802 $mockCache->expects( $this->never() )->method( 'delete' );
1804 $store = $this->newWatchedItemStore(
1805 $this->getMockLBFactory( $mockDb ),
1806 $this->getMockJobQueueGroup(),
1808 $this->getMockReadOnlyMode()
1813 $this->getAnonUser(),
1814 new TitleValue( 0, 'SomeDbKey' )
1819 public function testGetNotificationTimestampsBatch() {
1821 new TitleValue( 0, 'SomeDbKey' ),
1822 new TitleValue( 1, 'AnotherDbKey' ),
1825 $mockDb = $this->getMockDb();
1827 $this->getFakeRow( [
1828 'wl_namespace' => '0',
1829 'wl_title' => 'SomeDbKey',
1830 'wl_notificationtimestamp' => '20151212010101',
1834 'wl_namespace' => '1',
1835 'wl_title' => 'AnotherDbKey',
1836 'wl_notificationtimestamp' => null,
1841 $mockDb->expects( $this->once() )
1842 ->method( 'makeWhereFrom2d' )
1844 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1845 $this->isType( 'string' ),
1846 $this->isType( 'string' )
1848 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1849 $mockDb->expects( $this->once() )
1850 ->method( 'select' )
1853 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1855 'makeWhereFrom2d return value',
1858 $this->isType( 'string' )
1860 ->will( $this->returnValue( $dbResult ) );
1862 $mockCache = $this->getMockCache();
1863 $mockCache->expects( $this->exactly( 2 ) )
1866 [ '0:SomeDbKey:1' ],
1867 [ '1:AnotherDbKey:1' ]
1869 ->will( $this->returnValue( null ) );
1870 $mockCache->expects( $this->never() )->method( 'set' );
1871 $mockCache->expects( $this->never() )->method( 'delete' );
1873 $store = $this->newWatchedItemStore(
1874 $this->getMockLBFactory( $mockDb ),
1875 $this->getMockJobQueueGroup(),
1877 $this->getMockReadOnlyMode()
1880 $this->assertEquals(
1882 0 => [ 'SomeDbKey' => '20151212010101', ],
1883 1 => [ 'AnotherDbKey' => null, ],
1885 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1889 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1891 new TitleValue( 0, 'OtherDbKey' ),
1894 $mockDb = $this->getMockDb();
1896 $mockDb->expects( $this->once() )
1897 ->method( 'makeWhereFrom2d' )
1899 [ [ 'OtherDbKey' => 1 ] ],
1900 $this->isType( 'string' ),
1901 $this->isType( 'string' )
1903 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1904 $mockDb->expects( $this->once() )
1905 ->method( 'select' )
1908 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1910 'makeWhereFrom2d return value',
1913 $this->isType( 'string' )
1915 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1917 $mockCache = $this->getMockCache();
1918 $mockCache->expects( $this->once() )
1920 ->with( '0:OtherDbKey:1' )
1921 ->will( $this->returnValue( null ) );
1922 $mockCache->expects( $this->never() )->method( 'set' );
1923 $mockCache->expects( $this->never() )->method( 'delete' );
1925 $store = $this->newWatchedItemStore(
1926 $this->getMockLBFactory( $mockDb ),
1927 $this->getMockJobQueueGroup(),
1929 $this->getMockReadOnlyMode()
1932 $this->assertEquals(
1934 0 => [ 'OtherDbKey' => false, ],
1936 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1940 public function testGetNotificationTimestampsBatch_cachedItem() {
1942 new TitleValue( 0, 'SomeDbKey' ),
1943 new TitleValue( 1, 'AnotherDbKey' ),
1946 $user = $this->getMockNonAnonUserWithId( 1 );
1947 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1949 $mockDb = $this->getMockDb();
1951 $mockDb->expects( $this->once() )
1952 ->method( 'makeWhereFrom2d' )
1954 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1955 $this->isType( 'string' ),
1956 $this->isType( 'string' )
1958 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1959 $mockDb->expects( $this->once() )
1960 ->method( 'select' )
1963 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1965 'makeWhereFrom2d return value',
1968 $this->isType( 'string' )
1970 ->will( $this->returnValue( [
1972 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1976 $mockCache = $this->getMockCache();
1977 $mockCache->expects( $this->at( 1 ) )
1979 ->with( '0:SomeDbKey:1' )
1980 ->will( $this->returnValue( $cachedItem ) );
1981 $mockCache->expects( $this->at( 3 ) )
1983 ->with( '1:AnotherDbKey:1' )
1984 ->will( $this->returnValue( null ) );
1985 $mockCache->expects( $this->never() )->method( 'set' );
1986 $mockCache->expects( $this->never() )->method( 'delete' );
1988 $store = $this->newWatchedItemStore(
1989 $this->getMockLBFactory( $mockDb ),
1990 $this->getMockJobQueueGroup(),
1992 $this->getMockReadOnlyMode()
1995 $this->assertEquals(
1997 0 => [ 'SomeDbKey' => '20151212010101', ],
1998 1 => [ 'AnotherDbKey' => null, ],
2000 $store->getNotificationTimestampsBatch( $user, $targets )
2004 public function testGetNotificationTimestampsBatch_allItemsCached() {
2006 new TitleValue( 0, 'SomeDbKey' ),
2007 new TitleValue( 1, 'AnotherDbKey' ),
2010 $user = $this->getMockNonAnonUserWithId( 1 );
2012 new WatchedItem( $user, $targets[0], '20151212010101' ),
2013 new WatchedItem( $user, $targets[1], null ),
2015 $mockDb = $this->getMockDb();
2016 $mockDb->expects( $this->never() )->method( $this->anything() );
2018 $mockCache = $this->getMockCache();
2019 $mockCache->expects( $this->at( 1 ) )
2021 ->with( '0:SomeDbKey:1' )
2022 ->will( $this->returnValue( $cachedItems[0] ) );
2023 $mockCache->expects( $this->at( 3 ) )
2025 ->with( '1:AnotherDbKey:1' )
2026 ->will( $this->returnValue( $cachedItems[1] ) );
2027 $mockCache->expects( $this->never() )->method( 'set' );
2028 $mockCache->expects( $this->never() )->method( 'delete' );
2030 $store = $this->newWatchedItemStore(
2031 $this->getMockLBFactory( $mockDb ),
2032 $this->getMockJobQueueGroup(),
2034 $this->getMockReadOnlyMode()
2037 $this->assertEquals(
2039 0 => [ 'SomeDbKey' => '20151212010101', ],
2040 1 => [ 'AnotherDbKey' => null, ],
2042 $store->getNotificationTimestampsBatch( $user, $targets )
2046 public function testGetNotificationTimestampsBatch_anonymousUser() {
2048 new TitleValue( 0, 'SomeDbKey' ),
2049 new TitleValue( 1, 'AnotherDbKey' ),
2052 $mockDb = $this->getMockDb();
2053 $mockDb->expects( $this->never() )->method( $this->anything() );
2055 $mockCache = $this->getMockCache();
2056 $mockCache->expects( $this->never() )->method( $this->anything() );
2058 $store = $this->newWatchedItemStore(
2059 $this->getMockLBFactory( $mockDb ),
2060 $this->getMockJobQueueGroup(),
2062 $this->getMockReadOnlyMode()
2065 $this->assertEquals(
2067 0 => [ 'SomeDbKey' => false, ],
2068 1 => [ 'AnotherDbKey' => false, ],
2070 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
2074 public function testResetNotificationTimestamp_anonymousUser() {
2075 $mockDb = $this->getMockDb();
2076 $mockDb->expects( $this->never() )
2077 ->method( 'selectRow' );
2079 $mockCache = $this->getMockCache();
2080 $mockCache->expects( $this->never() )->method( 'get' );
2081 $mockCache->expects( $this->never() )->method( 'set' );
2082 $mockCache->expects( $this->never() )->method( 'delete' );
2084 $store = $this->newWatchedItemStore(
2085 $this->getMockLBFactory( $mockDb ),
2086 $this->getMockJobQueueGroup(),
2088 $this->getMockReadOnlyMode()
2092 $store->resetNotificationTimestamp(
2093 $this->getAnonUser(),
2094 Title
::newFromText( 'SomeDbKey' )
2099 public function testResetNotificationTimestamp_noItem() {
2100 $mockDb = $this->getMockDb();
2101 $mockDb->expects( $this->once() )
2102 ->method( 'selectRow' )
2105 'wl_notificationtimestamp',
2108 'wl_namespace' => 0,
2109 'wl_title' => 'SomeDbKey',
2112 ->will( $this->returnValue( [] ) );
2114 $mockCache = $this->getMockCache();
2115 $mockCache->expects( $this->never() )->method( 'get' );
2116 $mockCache->expects( $this->never() )->method( 'set' );
2117 $mockCache->expects( $this->never() )->method( 'delete' );
2119 $store = $this->newWatchedItemStore(
2120 $this->getMockLBFactory( $mockDb ),
2121 $this->getMockJobQueueGroup(),
2123 $this->getMockReadOnlyMode()
2127 $store->resetNotificationTimestamp(
2128 $this->getMockNonAnonUserWithId( 1 ),
2129 Title
::newFromText( 'SomeDbKey' )
2134 public function testResetNotificationTimestamp_item() {
2135 $user = $this->getMockNonAnonUserWithId( 1 );
2136 $title = Title
::newFromText( 'SomeDbKey' );
2138 $mockDb = $this->getMockDb();
2139 $mockDb->expects( $this->once() )
2140 ->method( 'selectRow' )
2143 'wl_notificationtimestamp',
2146 'wl_namespace' => 0,
2147 'wl_title' => 'SomeDbKey',
2150 ->will( $this->returnValue(
2151 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2154 $mockCache = $this->getMockCache();
2155 $mockCache->expects( $this->never() )->method( 'get' );
2156 $mockCache->expects( $this->once() )
2160 $this->isInstanceOf( WatchedItem
::class )
2162 $mockCache->expects( $this->once() )
2163 ->method( 'delete' )
2164 ->with( '0:SomeDbKey:1' );
2166 $mockQueueGroup = $this->getMockJobQueueGroup();
2167 $mockQueueGroup->expects( $this->once() )
2168 ->method( 'lazyPush' )
2169 ->willReturnCallback( function ( ActivityUpdateJob
$job ) {
2173 $store = $this->newWatchedItemStore(
2174 $this->getMockLBFactory( $mockDb ),
2177 $this->getMockReadOnlyMode()
2181 $store->resetNotificationTimestamp(
2188 public function testResetNotificationTimestamp_noItemForced() {
2189 $user = $this->getMockNonAnonUserWithId( 1 );
2190 $title = Title
::newFromText( 'SomeDbKey' );
2192 $mockDb = $this->getMockDb();
2193 $mockDb->expects( $this->never() )
2194 ->method( 'selectRow' );
2196 $mockCache = $this->getMockCache();
2197 $mockCache->expects( $this->never() )->method( 'get' );
2198 $mockCache->expects( $this->never() )->method( 'set' );
2199 $mockCache->expects( $this->once() )
2200 ->method( 'delete' )
2201 ->with( '0:SomeDbKey:1' );
2203 $mockQueueGroup = $this->getMockJobQueueGroup();
2204 $store = $this->newWatchedItemStore(
2205 $this->getMockLBFactory( $mockDb ),
2208 $this->getMockReadOnlyMode()
2211 $mockQueueGroup->expects( $this->any() )
2212 ->method( 'lazyPush' )
2213 ->will( $this->returnCallback( function ( ActivityUpdateJob
$job ) {
2218 $store->resetNotificationTimestamp(
2227 * @param string $text
2230 * @return PHPUnit_Framework_MockObject_MockObject|Title
2232 private function getMockTitle( $text, $ns = 0 ) {
2233 $title = $this->createMock( Title
::class );
2234 $title->expects( $this->any() )
2235 ->method( 'getText' )
2236 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2237 $title->expects( $this->any() )
2238 ->method( 'getDbKey' )
2239 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2240 $title->expects( $this->any() )
2241 ->method( 'getNamespace' )
2242 ->will( $this->returnValue( $ns ) );
2246 private function verifyCallbackJob(
2247 ActivityUpdateJob
$job,
2248 LinkTarget
$expectedTitle,
2250 callable
$notificationTimestampCondition
2252 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2253 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2255 $jobParams = $job->getParams();
2256 $this->assertArrayHasKey( 'type', $jobParams );
2257 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2258 $this->assertArrayHasKey( 'userid', $jobParams );
2259 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2260 $this->assertArrayHasKey( 'notifTime', $jobParams );
2261 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2264 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2265 $user = $this->getMockNonAnonUserWithId( 1 );
2267 $title = $this->getMockTitle( 'SomeTitle' );
2268 $title->expects( $this->once() )
2269 ->method( 'getNextRevisionID' )
2271 ->will( $this->returnValue( false ) );
2273 $mockDb = $this->getMockDb();
2274 $mockDb->expects( $this->never() )
2275 ->method( 'selectRow' );
2277 $mockCache = $this->getMockCache();
2278 $mockCache->expects( $this->never() )->method( 'get' );
2279 $mockCache->expects( $this->never() )->method( 'set' );
2280 $mockCache->expects( $this->once() )
2281 ->method( 'delete' )
2282 ->with( '0:SomeTitle:1' );
2284 $mockQueueGroup = $this->getMockJobQueueGroup();
2285 $store = $this->newWatchedItemStore(
2286 $this->getMockLBFactory( $mockDb ),
2289 $this->getMockReadOnlyMode()
2292 $mockQueueGroup->expects( $this->any() )
2293 ->method( 'lazyPush' )
2294 ->will( $this->returnCallback(
2295 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2296 $this->verifyCallbackJob(
2300 function ( $time ) {
2301 return $time === null;
2308 $store->resetNotificationTimestamp(
2317 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2318 $user = $this->getMockNonAnonUserWithId( 1 );
2320 $title = $this->getMockTitle( 'SomeDbKey' );
2321 $title->expects( $this->once() )
2322 ->method( 'getNextRevisionID' )
2324 ->will( $this->returnValue( 33 ) );
2326 $mockDb = $this->getMockDb();
2327 $mockDb->expects( $this->once() )
2328 ->method( 'selectRow' )
2331 'wl_notificationtimestamp',
2334 'wl_namespace' => 0,
2335 'wl_title' => 'SomeDbKey',
2338 ->will( $this->returnValue(
2339 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2342 $mockCache = $this->getMockCache();
2343 $mockCache->expects( $this->never() )->method( 'get' );
2344 $mockCache->expects( $this->once() )
2346 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2347 $mockCache->expects( $this->once() )
2348 ->method( 'delete' )
2349 ->with( '0:SomeDbKey:1' );
2351 $mockQueueGroup = $this->getMockJobQueueGroup();
2352 $store = $this->newWatchedItemStore(
2353 $this->getMockLBFactory( $mockDb ),
2356 $this->getMockReadOnlyMode()
2359 $mockQueueGroup->expects( $this->any() )
2360 ->method( 'lazyPush' )
2361 ->will( $this->returnCallback(
2362 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2363 $this->verifyCallbackJob(
2367 function ( $time ) {
2368 return $time !== null && $time > '20151212010101';
2374 $getTimestampCallCounter = 0;
2375 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2376 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2377 $getTimestampCallCounter++
;
2378 $this->assertEquals( $title, $titleParam );
2379 $this->assertEquals( $oldid, $oldidParam );
2384 $store->resetNotificationTimestamp(
2391 $this->assertEquals( 1, $getTimestampCallCounter );
2393 ScopedCallback
::consume( $scopedOverrideRevision );
2396 public function testResetNotificationTimestamp_notWatchedPageForced() {
2397 $user = $this->getMockNonAnonUserWithId( 1 );
2399 $title = $this->getMockTitle( 'SomeDbKey' );
2400 $title->expects( $this->once() )
2401 ->method( 'getNextRevisionID' )
2403 ->will( $this->returnValue( 33 ) );
2405 $mockDb = $this->getMockDb();
2406 $mockDb->expects( $this->once() )
2407 ->method( 'selectRow' )
2410 'wl_notificationtimestamp',
2413 'wl_namespace' => 0,
2414 'wl_title' => 'SomeDbKey',
2417 ->will( $this->returnValue( false ) );
2419 $mockCache = $this->getMockCache();
2420 $mockCache->expects( $this->never() )->method( 'get' );
2421 $mockCache->expects( $this->never() )->method( 'set' );
2422 $mockCache->expects( $this->once() )
2423 ->method( 'delete' )
2424 ->with( '0:SomeDbKey:1' );
2426 $mockQueueGroup = $this->getMockJobQueueGroup();
2427 $store = $this->newWatchedItemStore(
2428 $this->getMockLBFactory( $mockDb ),
2431 $this->getMockReadOnlyMode()
2434 $mockQueueGroup->expects( $this->any() )
2435 ->method( 'lazyPush' )
2436 ->will( $this->returnCallback(
2437 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2438 $this->verifyCallbackJob(
2442 function ( $time ) {
2443 return $time === null;
2450 $store->resetNotificationTimestamp(
2459 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2460 $user = $this->getMockNonAnonUserWithId( 1 );
2462 $title = $this->getMockTitle( 'SomeDbKey' );
2463 $title->expects( $this->once() )
2464 ->method( 'getNextRevisionID' )
2466 ->will( $this->returnValue( 33 ) );
2468 $mockDb = $this->getMockDb();
2469 $mockDb->expects( $this->once() )
2470 ->method( 'selectRow' )
2473 'wl_notificationtimestamp',
2476 'wl_namespace' => 0,
2477 'wl_title' => 'SomeDbKey',
2480 ->will( $this->returnValue(
2481 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2484 $mockCache = $this->getMockCache();
2485 $mockCache->expects( $this->never() )->method( 'get' );
2486 $mockCache->expects( $this->once() )
2488 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2489 $mockCache->expects( $this->once() )
2490 ->method( 'delete' )
2491 ->with( '0:SomeDbKey:1' );
2493 $mockQueueGroup = $this->getMockJobQueueGroup();
2494 $store = $this->newWatchedItemStore(
2495 $this->getMockLBFactory( $mockDb ),
2498 $this->getMockReadOnlyMode()
2501 $mockQueueGroup->expects( $this->any() )
2502 ->method( 'lazyPush' )
2503 ->will( $this->returnCallback(
2504 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2505 $this->verifyCallbackJob(
2509 function ( $time ) {
2510 return $time === '30151212010101';
2516 $getTimestampCallCounter = 0;
2517 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2518 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2519 $getTimestampCallCounter++
;
2520 $this->assertEquals( $title, $titleParam );
2521 $this->assertEquals( $oldid, $oldidParam );
2526 $store->resetNotificationTimestamp(
2533 $this->assertEquals( 1, $getTimestampCallCounter );
2535 ScopedCallback
::consume( $scopedOverrideRevision );
2538 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2539 $user = $this->getMockNonAnonUserWithId( 1 );
2541 $title = $this->getMockTitle( 'SomeDbKey' );
2542 $title->expects( $this->once() )
2543 ->method( 'getNextRevisionID' )
2545 ->will( $this->returnValue( 33 ) );
2547 $mockDb = $this->getMockDb();
2548 $mockDb->expects( $this->once() )
2549 ->method( 'selectRow' )
2552 'wl_notificationtimestamp',
2555 'wl_namespace' => 0,
2556 'wl_title' => 'SomeDbKey',
2559 ->will( $this->returnValue(
2560 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2563 $mockCache = $this->getMockCache();
2564 $mockCache->expects( $this->never() )->method( 'get' );
2565 $mockCache->expects( $this->once() )
2567 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2568 $mockCache->expects( $this->once() )
2569 ->method( 'delete' )
2570 ->with( '0:SomeDbKey:1' );
2572 $mockQueueGroup = $this->getMockJobQueueGroup();
2573 $store = $this->newWatchedItemStore(
2574 $this->getMockLBFactory( $mockDb ),
2577 $this->getMockReadOnlyMode()
2580 $mockQueueGroup->expects( $this->any() )
2581 ->method( 'lazyPush' )
2582 ->will( $this->returnCallback(
2583 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2584 $this->verifyCallbackJob(
2588 function ( $time ) {
2589 return $time === false;
2595 $getTimestampCallCounter = 0;
2596 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2597 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2598 $getTimestampCallCounter++
;
2599 $this->assertEquals( $title, $titleParam );
2600 $this->assertEquals( $oldid, $oldidParam );
2605 $store->resetNotificationTimestamp(
2612 $this->assertEquals( 1, $getTimestampCallCounter );
2614 ScopedCallback
::consume( $scopedOverrideRevision );
2617 public function testSetNotificationTimestampsForUser_anonUser() {
2618 $store = $this->newWatchedItemStore(
2619 $this->getMockLBFactory( $this->getMockDb() ),
2620 $this->getMockJobQueueGroup(),
2621 $this->getMockCache(),
2622 $this->getMockReadOnlyMode()
2624 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2627 public function testSetNotificationTimestampsForUser_allRows() {
2628 $user = $this->getMockNonAnonUserWithId( 1 );
2629 $timestamp = '20100101010101';
2631 $mockDb = $this->getMockDb();
2632 $mockDb->expects( $this->once() )
2633 ->method( 'update' )
2636 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2639 ->will( $this->returnValue( true ) );
2640 $mockDb->expects( $this->exactly( 1 ) )
2641 ->method( 'timestamp' )
2642 ->will( $this->returnCallback( function ( $value ) {
2643 return 'TS' . $value . 'TS';
2646 $store = $this->newWatchedItemStore(
2647 $this->getMockLBFactory( $mockDb ),
2648 $this->getMockJobQueueGroup(),
2649 $this->getMockCache(),
2650 $this->getMockReadOnlyMode()
2654 $store->setNotificationTimestampsForUser( $user, $timestamp )
2658 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2659 $user = $this->getMockNonAnonUserWithId( 1 );
2662 $mockDb = $this->getMockDb();
2663 $mockDb->expects( $this->once() )
2664 ->method( 'update' )
2667 [ 'wl_notificationtimestamp' => null ],
2670 ->will( $this->returnValue( true ) );
2671 $mockDb->expects( $this->exactly( 0 ) )
2672 ->method( 'timestamp' )
2673 ->will( $this->returnCallback( function ( $value ) {
2674 return 'TS' . $value . 'TS';
2677 $store = $this->newWatchedItemStore(
2678 $this->getMockLBFactory( $mockDb ),
2679 $this->getMockJobQueueGroup(),
2680 $this->getMockCache(),
2681 $this->getMockReadOnlyMode()
2685 $store->setNotificationTimestampsForUser( $user, $timestamp )
2689 public function testSetNotificationTimestampsForUser_specificTargets() {
2690 $user = $this->getMockNonAnonUserWithId( 1 );
2691 $timestamp = '20100101010101';
2692 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2694 $mockDb = $this->getMockDb();
2695 $mockDb->expects( $this->once() )
2696 ->method( 'update' )
2699 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2700 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2702 ->will( $this->returnValue( true ) );
2703 $mockDb->expects( $this->exactly( 1 ) )
2704 ->method( 'timestamp' )
2705 ->will( $this->returnCallback( function ( $value ) {
2706 return 'TS' . $value . 'TS';
2708 $mockDb->expects( $this->once() )
2709 ->method( 'makeWhereFrom2d' )
2711 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2712 $this->isType( 'string' ),
2713 $this->isType( 'string' )
2715 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2717 $store = $this->newWatchedItemStore(
2718 $this->getMockLBFactory( $mockDb ),
2719 $this->getMockJobQueueGroup(),
2720 $this->getMockCache(),
2721 $this->getMockReadOnlyMode()
2725 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2729 public function testUpdateNotificationTimestamp_watchersExist() {
2730 $mockDb = $this->getMockDb();
2731 $mockDb->expects( $this->once() )
2732 ->method( 'selectFieldValues' )
2738 'wl_namespace' => 0,
2739 'wl_title' => 'SomeDbKey',
2740 'wl_notificationtimestamp IS NULL'
2743 ->will( $this->returnValue( [ '2', '3' ] ) );
2744 $mockDb->expects( $this->once() )
2745 ->method( 'update' )
2748 [ 'wl_notificationtimestamp' => null ],
2750 'wl_user' => [ 2, 3 ],
2751 'wl_namespace' => 0,
2752 'wl_title' => 'SomeDbKey',
2756 $mockCache = $this->getMockCache();
2757 $mockCache->expects( $this->never() )->method( 'set' );
2758 $mockCache->expects( $this->never() )->method( 'get' );
2759 $mockCache->expects( $this->never() )->method( 'delete' );
2761 $store = $this->newWatchedItemStore(
2762 $this->getMockLBFactory( $mockDb ),
2763 $this->getMockJobQueueGroup(),
2765 $this->getMockReadOnlyMode()
2768 $this->assertEquals(
2770 $store->updateNotificationTimestamp(
2771 $this->getMockNonAnonUserWithId( 1 ),
2772 new TitleValue( 0, 'SomeDbKey' ),
2778 public function testUpdateNotificationTimestamp_noWatchers() {
2779 $mockDb = $this->getMockDb();
2780 $mockDb->expects( $this->once() )
2781 ->method( 'selectFieldValues' )
2787 'wl_namespace' => 0,
2788 'wl_title' => 'SomeDbKey',
2789 'wl_notificationtimestamp IS NULL'
2793 $this->returnValue( [] )
2795 $mockDb->expects( $this->never() )
2796 ->method( 'update' );
2798 $mockCache = $this->getMockCache();
2799 $mockCache->expects( $this->never() )->method( 'set' );
2800 $mockCache->expects( $this->never() )->method( 'get' );
2801 $mockCache->expects( $this->never() )->method( 'delete' );
2803 $store = $this->newWatchedItemStore(
2804 $this->getMockLBFactory( $mockDb ),
2805 $this->getMockJobQueueGroup(),
2807 $this->getMockReadOnlyMode()
2810 $watchers = $store->updateNotificationTimestamp(
2811 $this->getMockNonAnonUserWithId( 1 ),
2812 new TitleValue( 0, 'SomeDbKey' ),
2815 $this->assertInternalType( 'array', $watchers );
2816 $this->assertEmpty( $watchers );
2819 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2820 $user = $this->getMockNonAnonUserWithId( 1 );
2821 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2823 $mockDb = $this->getMockDb();
2824 $mockDb->expects( $this->once() )
2825 ->method( 'selectRow' )
2826 ->will( $this->returnValue(
2827 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2829 $mockDb->expects( $this->once() )
2830 ->method( 'selectFieldValues' )
2832 $this->returnValue( [ '2', '3' ] )
2834 $mockDb->expects( $this->once() )
2835 ->method( 'update' );
2837 $mockCache = $this->getMockCache();
2838 $mockCache->expects( $this->once() )
2840 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2841 $mockCache->expects( $this->once() )
2843 ->with( '0:SomeDbKey:1' );
2844 $mockCache->expects( $this->once() )
2845 ->method( 'delete' )
2846 ->with( '0:SomeDbKey:1' );
2848 $store = $this->newWatchedItemStore(
2849 $this->getMockLBFactory( $mockDb ),
2850 $this->getMockJobQueueGroup(),
2852 $this->getMockReadOnlyMode()
2855 // This will add the item to the cache
2856 $store->getWatchedItem( $user, $titleValue );
2858 $store->updateNotificationTimestamp(
2859 $this->getMockNonAnonUserWithId( 1 ),