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 ( ...$args ) {
93 return implode( ':', $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 ) );
123 $mock->expects( $this->any() )
124 ->method( 'getUserPage' )
125 ->will( $this->returnValue( Title
::makeTitle( NS_USER
, 'MockUser' ) ) );
132 private function getAnonUser() {
133 return User
::newFromName( 'Anon_User' );
136 private function getFakeRow( array $rowValues ) {
137 $fakeRow = new stdClass();
138 foreach ( $rowValues as $valueName => $value ) {
139 $fakeRow->$valueName = $value;
144 private function newWatchedItemStore(
145 LBFactory
$lbFactory,
146 JobQueueGroup
$queueGroup,
147 HashBagOStuff
$cache,
148 ReadOnlyMode
$readOnlyMode
150 return new WatchedItemStore(
160 public function testClearWatchedItems() {
161 $user = $this->getMockNonAnonUserWithId( 7 );
163 $mockDb = $this->getMockDb();
164 $mockDb->expects( $this->once() )
165 ->method( 'selectField' )
170 'wl_user' => $user->getId(),
172 $this->isType( 'string' )
174 ->will( $this->returnValue( 12 ) );
175 $mockDb->expects( $this->once() )
180 $this->isType( 'string' )
183 $mockCache = $this->getMockCache();
184 $mockCache->expects( $this->never() )->method( 'get' );
185 $mockCache->expects( $this->never() )->method( 'set' );
186 $mockCache->expects( $this->once() )
190 $store = $this->newWatchedItemStore(
191 $this->getMockLBFactory( $mockDb ),
192 $this->getMockJobQueueGroup(),
194 $this->getMockReadOnlyMode()
196 TestingAccessWrapper
::newFromObject( $store )
197 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
199 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
202 public function testClearWatchedItems_tooManyItemsWatched() {
203 $user = $this->getMockNonAnonUserWithId( 7 );
205 $mockDb = $this->getMockDb();
206 $mockDb->expects( $this->once() )
207 ->method( 'selectField' )
212 'wl_user' => $user->getId(),
214 $this->isType( 'string' )
216 ->will( $this->returnValue( 99999 ) );
218 $mockCache = $this->getMockCache();
219 $mockCache->expects( $this->never() )->method( 'get' );
220 $mockCache->expects( $this->never() )->method( 'set' );
221 $mockCache->expects( $this->never() )->method( 'delete' );
223 $store = $this->newWatchedItemStore(
224 $this->getMockLBFactory( $mockDb ),
225 $this->getMockJobQueueGroup(),
227 $this->getMockReadOnlyMode()
230 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
233 public function testCountWatchedItems() {
234 $user = $this->getMockNonAnonUserWithId( 1 );
236 $mockDb = $this->getMockDb();
237 $mockDb->expects( $this->exactly( 1 ) )
238 ->method( 'selectField' )
243 'wl_user' => $user->getId(),
245 $this->isType( 'string' )
247 ->will( $this->returnValue( '12' ) );
249 $mockCache = $this->getMockCache();
250 $mockCache->expects( $this->never() )->method( 'get' );
251 $mockCache->expects( $this->never() )->method( 'set' );
252 $mockCache->expects( $this->never() )->method( 'delete' );
254 $store = $this->newWatchedItemStore(
255 $this->getMockLBFactory( $mockDb ),
256 $this->getMockJobQueueGroup(),
258 $this->getMockReadOnlyMode()
261 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
264 public function testCountWatchers() {
265 $titleValue = new TitleValue( 0, 'SomeDbKey' );
267 $mockDb = $this->getMockDb();
268 $mockDb->expects( $this->exactly( 1 ) )
269 ->method( 'selectField' )
274 'wl_namespace' => $titleValue->getNamespace(),
275 'wl_title' => $titleValue->getDBkey(),
277 $this->isType( 'string' )
279 ->will( $this->returnValue( '7' ) );
281 $mockCache = $this->getMockCache();
282 $mockCache->expects( $this->never() )->method( 'get' );
283 $mockCache->expects( $this->never() )->method( 'set' );
284 $mockCache->expects( $this->never() )->method( 'delete' );
286 $store = $this->newWatchedItemStore(
287 $this->getMockLBFactory( $mockDb ),
288 $this->getMockJobQueueGroup(),
290 $this->getMockReadOnlyMode()
293 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
296 public function testCountWatchersMultiple() {
298 new TitleValue( 0, 'SomeDbKey' ),
299 new TitleValue( 0, 'OtherDbKey' ),
300 new TitleValue( 1, 'AnotherDbKey' ),
303 $mockDb = $this->getMockDb();
306 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
307 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
308 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
311 $mockDb->expects( $this->once() )
312 ->method( 'makeWhereFrom2d' )
314 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
315 $this->isType( 'string' ),
316 $this->isType( 'string' )
318 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
319 $mockDb->expects( $this->once() )
323 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
324 [ 'makeWhereFrom2d return value' ],
325 $this->isType( 'string' ),
327 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
331 $this->returnValue( $dbResult )
334 $mockCache = $this->getMockCache();
335 $mockCache->expects( $this->never() )->method( 'get' );
336 $mockCache->expects( $this->never() )->method( 'set' );
337 $mockCache->expects( $this->never() )->method( 'delete' );
339 $store = $this->newWatchedItemStore(
340 $this->getMockLBFactory( $mockDb ),
341 $this->getMockJobQueueGroup(),
343 $this->getMockReadOnlyMode()
347 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
348 1 => [ 'AnotherDbKey' => 500 ],
350 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
353 public function provideIntWithDbUnsafeVersion() {
356 [ "50; DROP TABLE watchlist;\n--" ],
361 * @dataProvider provideIntWithDbUnsafeVersion
363 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
365 new TitleValue( 0, 'SomeDbKey' ),
366 new TitleValue( 0, 'OtherDbKey' ),
367 new TitleValue( 1, 'AnotherDbKey' ),
370 $mockDb = $this->getMockDb();
373 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
374 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
375 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
378 $mockDb->expects( $this->once() )
379 ->method( 'makeWhereFrom2d' )
381 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
382 $this->isType( 'string' ),
383 $this->isType( 'string' )
385 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
386 $mockDb->expects( $this->once() )
390 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
391 [ 'makeWhereFrom2d return value' ],
392 $this->isType( 'string' ),
394 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
395 'HAVING' => 'COUNT(*) >= 50',
399 $this->returnValue( $dbResult )
402 $mockCache = $this->getMockCache();
403 $mockCache->expects( $this->never() )->method( 'get' );
404 $mockCache->expects( $this->never() )->method( 'set' );
405 $mockCache->expects( $this->never() )->method( 'delete' );
407 $store = $this->newWatchedItemStore(
408 $this->getMockLBFactory( $mockDb ),
409 $this->getMockJobQueueGroup(),
411 $this->getMockReadOnlyMode()
415 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
416 1 => [ 'AnotherDbKey' => 500 ],
420 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
424 public function testCountVisitingWatchers() {
425 $titleValue = new TitleValue( 0, 'SomeDbKey' );
427 $mockDb = $this->getMockDb();
428 $mockDb->expects( $this->exactly( 1 ) )
429 ->method( 'selectField' )
434 'wl_namespace' => $titleValue->getNamespace(),
435 'wl_title' => $titleValue->getDBkey(),
436 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
438 $this->isType( 'string' )
440 ->will( $this->returnValue( '7' ) );
441 $mockDb->expects( $this->exactly( 1 ) )
442 ->method( 'addQuotes' )
443 ->will( $this->returnCallback( function ( $value ) {
446 $mockDb->expects( $this->exactly( 1 ) )
447 ->method( 'timestamp' )
448 ->will( $this->returnCallback( function ( $value ) {
449 return 'TS' . $value . 'TS';
452 $mockCache = $this->getMockCache();
453 $mockCache->expects( $this->never() )->method( 'set' );
454 $mockCache->expects( $this->never() )->method( 'get' );
455 $mockCache->expects( $this->never() )->method( 'delete' );
457 $store = $this->newWatchedItemStore(
458 $this->getMockLBFactory( $mockDb ),
459 $this->getMockJobQueueGroup(),
461 $this->getMockReadOnlyMode()
464 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
467 public function testCountVisitingWatchersMultiple() {
468 $titleValuesWithThresholds = [
469 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
470 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
471 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
475 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
476 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
478 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
481 $mockDb = $this->getMockDb();
482 $mockDb->expects( $this->exactly( 2 * 3 ) )
483 ->method( 'addQuotes' )
484 ->will( $this->returnCallback( function ( $value ) {
487 $mockDb->expects( $this->exactly( 3 ) )
488 ->method( 'timestamp' )
489 ->will( $this->returnCallback( function ( $value ) {
490 return 'TS' . $value . 'TS';
492 $mockDb->expects( $this->any() )
493 ->method( 'makeList' )
495 $this->isType( 'array' ),
496 $this->isType( 'int' )
498 ->will( $this->returnCallback( function ( $a, $conj ) {
499 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
500 return implode( $sqlConj, array_map( function ( $s ) {
501 return '(' . $s . ')';
505 $mockDb->expects( $this->never() )
506 ->method( 'makeWhereFrom2d' );
509 '((wl_namespace = 0) AND (' .
510 "(((wl_title = 'SomeDbKey') AND (" .
511 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
513 "(wl_title = 'OtherDbKey') AND (" .
514 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
516 ') OR ((wl_namespace = 1) AND (' .
517 "(((wl_title = 'AnotherDbKey') AND (" .
518 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
520 $mockDb->expects( $this->once() )
524 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
526 $this->isType( 'string' ),
528 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
532 $this->returnValue( $dbResult )
535 $mockCache = $this->getMockCache();
536 $mockCache->expects( $this->never() )->method( 'get' );
537 $mockCache->expects( $this->never() )->method( 'set' );
538 $mockCache->expects( $this->never() )->method( 'delete' );
540 $store = $this->newWatchedItemStore(
541 $this->getMockLBFactory( $mockDb ),
542 $this->getMockJobQueueGroup(),
544 $this->getMockReadOnlyMode()
548 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
549 1 => [ 'AnotherDbKey' => 500 ],
553 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
557 public function testCountVisitingWatchersMultiple_withMissingTargets() {
558 $titleValuesWithThresholds = [
559 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
560 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
561 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
562 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
563 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
567 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
568 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
570 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
573 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
576 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
579 $mockDb = $this->getMockDb();
580 $mockDb->expects( $this->exactly( 2 * 3 ) )
581 ->method( 'addQuotes' )
582 ->will( $this->returnCallback( function ( $value ) {
585 $mockDb->expects( $this->exactly( 3 ) )
586 ->method( 'timestamp' )
587 ->will( $this->returnCallback( function ( $value ) {
588 return 'TS' . $value . 'TS';
590 $mockDb->expects( $this->any() )
591 ->method( 'makeList' )
593 $this->isType( 'array' ),
594 $this->isType( 'int' )
596 ->will( $this->returnCallback( function ( $a, $conj ) {
597 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
598 return implode( $sqlConj, array_map( function ( $s ) {
599 return '(' . $s . ')';
603 $mockDb->expects( $this->once() )
604 ->method( 'makeWhereFrom2d' )
606 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
607 $this->isType( 'string' ),
608 $this->isType( 'string' )
610 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
613 '((wl_namespace = 0) AND (' .
614 "(((wl_title = 'SomeDbKey') AND (" .
615 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
617 "(wl_title = 'OtherDbKey') AND (" .
618 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
620 ') OR ((wl_namespace = 1) AND (' .
621 "(((wl_title = 'AnotherDbKey') AND (" .
622 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
625 '(makeWhereFrom2d return value)';
626 $mockDb->expects( $this->once() )
630 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
632 $this->isType( 'string' ),
634 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
638 $this->returnValue( $dbResult )
641 $mockCache = $this->getMockCache();
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'set' );
644 $mockCache->expects( $this->never() )->method( 'delete' );
646 $store = $this->newWatchedItemStore(
647 $this->getMockLBFactory( $mockDb ),
648 $this->getMockJobQueueGroup(),
650 $this->getMockReadOnlyMode()
655 'SomeDbKey' => 100, 'OtherDbKey' => 300,
656 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
658 1 => [ 'AnotherDbKey' => 500 ],
662 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
667 * @dataProvider provideIntWithDbUnsafeVersion
669 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
670 $titleValuesWithThresholds = [
671 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
672 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
673 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
676 $mockDb = $this->getMockDb();
677 $mockDb->expects( $this->any() )
678 ->method( 'makeList' )
679 ->will( $this->returnValue( 'makeList return value' ) );
680 $mockDb->expects( $this->once() )
684 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
685 'makeList return value',
686 $this->isType( 'string' ),
688 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
689 'HAVING' => 'COUNT(*) >= 50',
693 $this->returnValue( [] )
696 $mockCache = $this->getMockCache();
697 $mockCache->expects( $this->never() )->method( 'get' );
698 $mockCache->expects( $this->never() )->method( 'set' );
699 $mockCache->expects( $this->never() )->method( 'delete' );
701 $store = $this->newWatchedItemStore(
702 $this->getMockLBFactory( $mockDb ),
703 $this->getMockJobQueueGroup(),
705 $this->getMockReadOnlyMode()
709 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
710 1 => [ 'AnotherDbKey' => 0 ],
714 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
718 public function testCountUnreadNotifications() {
719 $user = $this->getMockNonAnonUserWithId( 1 );
721 $mockDb = $this->getMockDb();
722 $mockDb->expects( $this->exactly( 1 ) )
723 ->method( 'selectRowCount' )
728 "wl_notificationtimestamp IS NOT NULL",
731 $this->isType( 'string' )
733 ->will( $this->returnValue( '9' ) );
735 $mockCache = $this->getMockCache();
736 $mockCache->expects( $this->never() )->method( 'set' );
737 $mockCache->expects( $this->never() )->method( 'get' );
738 $mockCache->expects( $this->never() )->method( 'delete' );
740 $store = $this->newWatchedItemStore(
741 $this->getMockLBFactory( $mockDb ),
742 $this->getMockJobQueueGroup(),
744 $this->getMockReadOnlyMode()
747 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
751 * @dataProvider provideIntWithDbUnsafeVersion
753 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
754 $user = $this->getMockNonAnonUserWithId( 1 );
756 $mockDb = $this->getMockDb();
757 $mockDb->expects( $this->exactly( 1 ) )
758 ->method( 'selectRowCount' )
763 "wl_notificationtimestamp IS NOT NULL",
766 $this->isType( 'string' ),
769 ->will( $this->returnValue( '50' ) );
771 $mockCache = $this->getMockCache();
772 $mockCache->expects( $this->never() )->method( 'set' );
773 $mockCache->expects( $this->never() )->method( 'get' );
774 $mockCache->expects( $this->never() )->method( 'delete' );
776 $store = $this->newWatchedItemStore(
777 $this->getMockLBFactory( $mockDb ),
778 $this->getMockJobQueueGroup(),
780 $this->getMockReadOnlyMode()
785 $store->countUnreadNotifications( $user, $limit )
790 * @dataProvider provideIntWithDbUnsafeVersion
792 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
793 $user = $this->getMockNonAnonUserWithId( 1 );
795 $mockDb = $this->getMockDb();
796 $mockDb->expects( $this->exactly( 1 ) )
797 ->method( 'selectRowCount' )
802 "wl_notificationtimestamp IS NOT NULL",
805 $this->isType( 'string' ),
808 ->will( $this->returnValue( '9' ) );
810 $mockCache = $this->getMockCache();
811 $mockCache->expects( $this->never() )->method( 'set' );
812 $mockCache->expects( $this->never() )->method( 'get' );
813 $mockCache->expects( $this->never() )->method( 'delete' );
815 $store = $this->newWatchedItemStore(
816 $this->getMockLBFactory( $mockDb ),
817 $this->getMockJobQueueGroup(),
819 $this->getMockReadOnlyMode()
824 $store->countUnreadNotifications( $user, $limit )
828 public function testDuplicateEntry_nothingToDuplicate() {
829 $mockDb = $this->getMockDb();
830 $mockDb->expects( $this->once() )
836 'wl_notificationtimestamp',
840 'wl_title' => 'Old_Title',
842 'WatchedItemStore::duplicateEntry',
845 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
847 $store = $this->newWatchedItemStore(
848 $this->getMockLBFactory( $mockDb ),
849 $this->getMockJobQueueGroup(),
850 $this->getMockCache(),
851 $this->getMockReadOnlyMode()
854 $store->duplicateEntry(
855 Title
::newFromText( 'Old_Title' ),
856 Title
::newFromText( 'New_Title' )
860 public function testDuplicateEntry_somethingToDuplicate() {
862 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
863 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
866 $mockDb = $this->getMockDb();
867 $mockDb->expects( $this->at( 0 ) )
873 'wl_notificationtimestamp',
877 'wl_title' => 'Old_Title',
880 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
881 $mockDb->expects( $this->at( 1 ) )
882 ->method( 'replace' )
885 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
890 'wl_title' => 'New_Title',
891 'wl_notificationtimestamp' => '20151212010101',
896 'wl_title' => 'New_Title',
897 'wl_notificationtimestamp' => null,
900 $this->isType( 'string' )
903 $mockCache = $this->getMockCache();
904 $mockCache->expects( $this->never() )->method( 'get' );
905 $mockCache->expects( $this->never() )->method( 'delete' );
907 $store = $this->newWatchedItemStore(
908 $this->getMockLBFactory( $mockDb ),
909 $this->getMockJobQueueGroup(),
911 $this->getMockReadOnlyMode()
914 $store->duplicateEntry(
915 Title
::newFromText( 'Old_Title' ),
916 Title
::newFromText( 'New_Title' )
920 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
921 $mockDb = $this->getMockDb();
922 $mockDb->expects( $this->at( 0 ) )
928 'wl_notificationtimestamp',
932 'wl_title' => 'Old_Title',
935 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
936 $mockDb->expects( $this->at( 1 ) )
942 'wl_notificationtimestamp',
946 'wl_title' => 'Old_Title',
949 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
951 $mockCache = $this->getMockCache();
952 $mockCache->expects( $this->never() )->method( 'get' );
953 $mockCache->expects( $this->never() )->method( 'delete' );
955 $store = $this->newWatchedItemStore(
956 $this->getMockLBFactory( $mockDb ),
957 $this->getMockJobQueueGroup(),
959 $this->getMockReadOnlyMode()
962 $store->duplicateAllAssociatedEntries(
963 Title
::newFromText( 'Old_Title' ),
964 Title
::newFromText( 'New_Title' )
968 public function provideLinkTargetPairs() {
970 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
971 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
976 * @dataProvider provideLinkTargetPairs
978 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
979 LinkTarget
$oldTarget,
980 LinkTarget
$newTarget
983 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
986 $mockDb = $this->getMockDb();
987 $mockDb->expects( $this->at( 0 ) )
993 'wl_notificationtimestamp',
996 'wl_namespace' => $oldTarget->getNamespace(),
997 'wl_title' => $oldTarget->getDBkey(),
1000 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1001 $mockDb->expects( $this->at( 1 ) )
1002 ->method( 'replace' )
1005 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1009 'wl_namespace' => $newTarget->getNamespace(),
1010 'wl_title' => $newTarget->getDBkey(),
1011 'wl_notificationtimestamp' => '20151212010101',
1014 $this->isType( 'string' )
1016 $mockDb->expects( $this->at( 2 ) )
1017 ->method( 'select' )
1022 'wl_notificationtimestamp',
1025 'wl_namespace' => $oldTarget->getNamespace() +
1,
1026 'wl_title' => $oldTarget->getDBkey(),
1029 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1030 $mockDb->expects( $this->at( 3 ) )
1031 ->method( 'replace' )
1034 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1038 'wl_namespace' => $newTarget->getNamespace() +
1,
1039 'wl_title' => $newTarget->getDBkey(),
1040 'wl_notificationtimestamp' => '20151212010101',
1043 $this->isType( 'string' )
1046 $mockCache = $this->getMockCache();
1047 $mockCache->expects( $this->never() )->method( 'get' );
1048 $mockCache->expects( $this->never() )->method( 'delete' );
1050 $store = $this->newWatchedItemStore(
1051 $this->getMockLBFactory( $mockDb ),
1052 $this->getMockJobQueueGroup(),
1054 $this->getMockReadOnlyMode()
1057 $store->duplicateAllAssociatedEntries(
1063 public function testAddWatch_nonAnonymousUser() {
1064 $mockDb = $this->getMockDb();
1065 $mockDb->expects( $this->once() )
1066 ->method( 'insert' )
1072 'wl_namespace' => 0,
1073 'wl_title' => 'Some_Page',
1074 'wl_notificationtimestamp' => null,
1079 $mockCache = $this->getMockCache();
1080 $mockCache->expects( $this->once() )
1081 ->method( 'delete' )
1082 ->with( '0:Some_Page:1' );
1084 $store = $this->newWatchedItemStore(
1085 $this->getMockLBFactory( $mockDb ),
1086 $this->getMockJobQueueGroup(),
1088 $this->getMockReadOnlyMode()
1092 $this->getMockNonAnonUserWithId( 1 ),
1093 Title
::newFromText( 'Some_Page' )
1097 public function testAddWatch_anonymousUser() {
1098 $mockDb = $this->getMockDb();
1099 $mockDb->expects( $this->never() )
1100 ->method( 'insert' );
1102 $mockCache = $this->getMockCache();
1103 $mockCache->expects( $this->never() )
1104 ->method( 'delete' );
1106 $store = $this->newWatchedItemStore(
1107 $this->getMockLBFactory( $mockDb ),
1108 $this->getMockJobQueueGroup(),
1110 $this->getMockReadOnlyMode()
1114 $this->getAnonUser(),
1115 Title
::newFromText( 'Some_Page' )
1119 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1120 $store = $this->newWatchedItemStore(
1121 $this->getMockLBFactory( $this->getMockDb() ),
1122 $this->getMockJobQueueGroup(),
1123 $this->getMockCache(),
1124 $this->getMockReadOnlyMode( true )
1128 $store->addWatchBatchForUser(
1129 $this->getMockNonAnonUserWithId( 1 ),
1130 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1135 public function testAddWatchBatchForUser_nonAnonymousUser() {
1136 $mockDb = $this->getMockDb();
1137 $mockDb->expects( $this->once() )
1138 ->method( 'insert' )
1144 'wl_namespace' => 0,
1145 'wl_title' => 'Some_Page',
1146 'wl_notificationtimestamp' => null,
1150 'wl_namespace' => 1,
1151 'wl_title' => 'Some_Page',
1152 'wl_notificationtimestamp' => null,
1157 $mockDb->expects( $this->once() )
1158 ->method( 'affectedRows' )
1161 $mockCache = $this->getMockCache();
1162 $mockCache->expects( $this->exactly( 2 ) )
1163 ->method( 'delete' );
1164 $mockCache->expects( $this->at( 1 ) )
1165 ->method( 'delete' )
1166 ->with( '0:Some_Page:1' );
1167 $mockCache->expects( $this->at( 3 ) )
1168 ->method( 'delete' )
1169 ->with( '1:Some_Page:1' );
1171 $store = $this->newWatchedItemStore(
1172 $this->getMockLBFactory( $mockDb ),
1173 $this->getMockJobQueueGroup(),
1175 $this->getMockReadOnlyMode()
1178 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1181 $store->addWatchBatchForUser(
1183 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1188 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1189 $mockDb = $this->getMockDb();
1190 $mockDb->expects( $this->never() )
1191 ->method( 'insert' );
1193 $mockCache = $this->getMockCache();
1194 $mockCache->expects( $this->never() )
1195 ->method( 'delete' );
1197 $store = $this->newWatchedItemStore(
1198 $this->getMockLBFactory( $mockDb ),
1199 $this->getMockJobQueueGroup(),
1201 $this->getMockReadOnlyMode()
1205 $store->addWatchBatchForUser(
1206 $this->getAnonUser(),
1207 [ new TitleValue( 0, 'Other_Page' ) ]
1212 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1213 $user = $this->getMockNonAnonUserWithId( 1 );
1214 $mockDb = $this->getMockDb();
1215 $mockDb->expects( $this->never() )
1216 ->method( 'insert' );
1218 $mockCache = $this->getMockCache();
1219 $mockCache->expects( $this->never() )
1220 ->method( 'delete' );
1222 $store = $this->newWatchedItemStore(
1223 $this->getMockLBFactory( $mockDb ),
1224 $this->getMockJobQueueGroup(),
1226 $this->getMockReadOnlyMode()
1230 $store->addWatchBatchForUser( $user, [] )
1234 public function testLoadWatchedItem_existingItem() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->once() )
1237 ->method( 'selectRow' )
1240 'wl_notificationtimestamp',
1243 'wl_namespace' => 0,
1244 'wl_title' => 'SomeDbKey',
1247 ->will( $this->returnValue(
1248 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1251 $mockCache = $this->getMockCache();
1252 $mockCache->expects( $this->once() )
1258 $store = $this->newWatchedItemStore(
1259 $this->getMockLBFactory( $mockDb ),
1260 $this->getMockJobQueueGroup(),
1262 $this->getMockReadOnlyMode()
1265 $watchedItem = $store->loadWatchedItem(
1266 $this->getMockNonAnonUserWithId( 1 ),
1267 new TitleValue( 0, 'SomeDbKey' )
1269 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1270 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1271 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1272 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1275 public function testLoadWatchedItem_noItem() {
1276 $mockDb = $this->getMockDb();
1277 $mockDb->expects( $this->once() )
1278 ->method( 'selectRow' )
1281 'wl_notificationtimestamp',
1284 'wl_namespace' => 0,
1285 'wl_title' => 'SomeDbKey',
1288 ->will( $this->returnValue( [] ) );
1290 $mockCache = $this->getMockCache();
1291 $mockCache->expects( $this->never() )->method( 'get' );
1292 $mockCache->expects( $this->never() )->method( 'delete' );
1294 $store = $this->newWatchedItemStore(
1295 $this->getMockLBFactory( $mockDb ),
1296 $this->getMockJobQueueGroup(),
1298 $this->getMockReadOnlyMode()
1302 $store->loadWatchedItem(
1303 $this->getMockNonAnonUserWithId( 1 ),
1304 new TitleValue( 0, 'SomeDbKey' )
1309 public function testLoadWatchedItem_anonymousUser() {
1310 $mockDb = $this->getMockDb();
1311 $mockDb->expects( $this->never() )
1312 ->method( 'selectRow' );
1314 $mockCache = $this->getMockCache();
1315 $mockCache->expects( $this->never() )->method( 'get' );
1316 $mockCache->expects( $this->never() )->method( 'delete' );
1318 $store = $this->newWatchedItemStore(
1319 $this->getMockLBFactory( $mockDb ),
1320 $this->getMockJobQueueGroup(),
1322 $this->getMockReadOnlyMode()
1326 $store->loadWatchedItem(
1327 $this->getAnonUser(),
1328 new TitleValue( 0, 'SomeDbKey' )
1333 public function testRemoveWatch_existingItem() {
1334 $mockDb = $this->getMockDb();
1335 $mockDb->expects( $this->once() )
1336 ->method( 'delete' )
1342 'wl_namespace' => 0,
1343 'wl_title' => [ 'SomeDbKey' ],
1350 'wl_namespace' => 1,
1351 'wl_title' => [ 'SomeDbKey' ],
1355 $mockDb->expects( $this->exactly( 1 ) )
1356 ->method( 'affectedRows' )
1359 $mockCache = $this->getMockCache();
1360 $mockCache->expects( $this->never() )->method( 'get' );
1361 $mockCache->expects( $this->once() )
1362 ->method( 'delete' )
1364 [ '0:SomeDbKey:1' ],
1368 $store = $this->newWatchedItemStore(
1369 $this->getMockLBFactory( $mockDb ),
1370 $this->getMockJobQueueGroup(),
1372 $this->getMockReadOnlyMode()
1375 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1377 $store->removeWatch(
1378 $this->getMockNonAnonUserWithId( 1 ),
1379 Title
::newFromTitleValue( $titleValue )
1384 public function testRemoveWatch_noItem() {
1385 $mockDb = $this->getMockDb();
1386 $mockDb->expects( $this->once() )
1387 ->method( 'delete' )
1393 'wl_namespace' => 0,
1394 'wl_title' => [ 'SomeDbKey' ],
1401 'wl_namespace' => 1,
1402 'wl_title' => [ 'SomeDbKey' ],
1407 $mockDb->expects( $this->once() )
1408 ->method( 'affectedRows' )
1411 $mockCache = $this->getMockCache();
1412 $mockCache->expects( $this->never() )->method( 'get' );
1413 $mockCache->expects( $this->once() )
1414 ->method( 'delete' )
1416 [ '0:SomeDbKey:1' ],
1420 $store = $this->newWatchedItemStore(
1421 $this->getMockLBFactory( $mockDb ),
1422 $this->getMockJobQueueGroup(),
1424 $this->getMockReadOnlyMode()
1427 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1429 $store->removeWatch(
1430 $this->getMockNonAnonUserWithId( 1 ),
1431 Title
::newFromTitleValue( $titleValue )
1436 public function testRemoveWatch_anonymousUser() {
1437 $mockDb = $this->getMockDb();
1438 $mockDb->expects( $this->never() )
1439 ->method( 'delete' );
1441 $mockCache = $this->getMockCache();
1442 $mockCache->expects( $this->never() )->method( 'get' );
1443 $mockCache->expects( $this->never() )
1444 ->method( 'delete' );
1446 $store = $this->newWatchedItemStore(
1447 $this->getMockLBFactory( $mockDb ),
1448 $this->getMockJobQueueGroup(),
1450 $this->getMockReadOnlyMode()
1454 $store->removeWatch(
1455 $this->getAnonUser(),
1456 new TitleValue( 0, 'SomeDbKey' )
1461 public function testGetWatchedItem_existingItem() {
1462 $mockDb = $this->getMockDb();
1463 $mockDb->expects( $this->once() )
1464 ->method( 'selectRow' )
1467 'wl_notificationtimestamp',
1470 'wl_namespace' => 0,
1471 'wl_title' => 'SomeDbKey',
1474 ->will( $this->returnValue(
1475 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1478 $mockCache = $this->getMockCache();
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1480 $mockCache->expects( $this->once() )
1485 ->will( $this->returnValue( null ) );
1486 $mockCache->expects( $this->once() )
1492 $store = $this->newWatchedItemStore(
1493 $this->getMockLBFactory( $mockDb ),
1494 $this->getMockJobQueueGroup(),
1496 $this->getMockReadOnlyMode()
1499 $watchedItem = $store->getWatchedItem(
1500 $this->getMockNonAnonUserWithId( 1 ),
1501 new TitleValue( 0, 'SomeDbKey' )
1503 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1504 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1505 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1506 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1509 public function testGetWatchedItem_cachedItem() {
1510 $mockDb = $this->getMockDb();
1511 $mockDb->expects( $this->never() )
1512 ->method( 'selectRow' );
1514 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1515 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1516 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1518 $mockCache = $this->getMockCache();
1519 $mockCache->expects( $this->never() )->method( 'delete' );
1520 $mockCache->expects( $this->never() )->method( 'set' );
1521 $mockCache->expects( $this->once() )
1526 ->will( $this->returnValue( $cachedItem ) );
1528 $store = $this->newWatchedItemStore(
1529 $this->getMockLBFactory( $mockDb ),
1530 $this->getMockJobQueueGroup(),
1532 $this->getMockReadOnlyMode()
1535 $this->assertEquals(
1537 $store->getWatchedItem(
1544 public function testGetWatchedItem_noItem() {
1545 $mockDb = $this->getMockDb();
1546 $mockDb->expects( $this->once() )
1547 ->method( 'selectRow' )
1550 'wl_notificationtimestamp',
1553 'wl_namespace' => 0,
1554 'wl_title' => 'SomeDbKey',
1557 ->will( $this->returnValue( [] ) );
1559 $mockCache = $this->getMockCache();
1560 $mockCache->expects( $this->never() )->method( 'set' );
1561 $mockCache->expects( $this->never() )->method( 'delete' );
1562 $mockCache->expects( $this->once() )
1564 ->with( '0:SomeDbKey:1' )
1565 ->will( $this->returnValue( false ) );
1567 $store = $this->newWatchedItemStore(
1568 $this->getMockLBFactory( $mockDb ),
1569 $this->getMockJobQueueGroup(),
1571 $this->getMockReadOnlyMode()
1575 $store->getWatchedItem(
1576 $this->getMockNonAnonUserWithId( 1 ),
1577 new TitleValue( 0, 'SomeDbKey' )
1582 public function testGetWatchedItem_anonymousUser() {
1583 $mockDb = $this->getMockDb();
1584 $mockDb->expects( $this->never() )
1585 ->method( 'selectRow' );
1587 $mockCache = $this->getMockCache();
1588 $mockCache->expects( $this->never() )->method( 'set' );
1589 $mockCache->expects( $this->never() )->method( 'get' );
1590 $mockCache->expects( $this->never() )->method( 'delete' );
1592 $store = $this->newWatchedItemStore(
1593 $this->getMockLBFactory( $mockDb ),
1594 $this->getMockJobQueueGroup(),
1596 $this->getMockReadOnlyMode()
1600 $store->getWatchedItem(
1601 $this->getAnonUser(),
1602 new TitleValue( 0, 'SomeDbKey' )
1607 public function testGetWatchedItemsForUser() {
1608 $mockDb = $this->getMockDb();
1609 $mockDb->expects( $this->once() )
1610 ->method( 'select' )
1613 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1616 ->will( $this->returnValue( [
1617 $this->getFakeRow( [
1618 'wl_namespace' => 0,
1619 'wl_title' => 'Foo1',
1620 'wl_notificationtimestamp' => '20151212010101',
1622 $this->getFakeRow( [
1623 'wl_namespace' => 1,
1624 'wl_title' => 'Foo2',
1625 'wl_notificationtimestamp' => null,
1629 $mockCache = $this->getMockCache();
1630 $mockCache->expects( $this->never() )->method( 'delete' );
1631 $mockCache->expects( $this->never() )->method( 'get' );
1632 $mockCache->expects( $this->never() )->method( 'set' );
1634 $store = $this->newWatchedItemStore(
1635 $this->getMockLBFactory( $mockDb ),
1636 $this->getMockJobQueueGroup(),
1638 $this->getMockReadOnlyMode()
1640 $user = $this->getMockNonAnonUserWithId( 1 );
1642 $watchedItems = $store->getWatchedItemsForUser( $user );
1644 $this->assertInternalType( 'array', $watchedItems );
1645 $this->assertCount( 2, $watchedItems );
1646 foreach ( $watchedItems as $watchedItem ) {
1647 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1649 $this->assertEquals(
1650 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1653 $this->assertEquals(
1654 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1659 public function provideDbTypes() {
1661 [ false, DB_REPLICA
],
1662 [ true, DB_MASTER
],
1667 * @dataProvider provideDbTypes
1669 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1670 $mockDb = $this->getMockDb();
1671 $mockCache = $this->getMockCache();
1672 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1673 $user = $this->getMockNonAnonUserWithId( 1 );
1675 $mockDb->expects( $this->once() )
1676 ->method( 'select' )
1679 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1681 $this->isType( 'string' ),
1682 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1684 ->will( $this->returnValue( [] ) );
1686 $store = $this->newWatchedItemStore(
1688 $this->getMockJobQueueGroup(),
1690 $this->getMockReadOnlyMode()
1693 $watchedItems = $store->getWatchedItemsForUser(
1695 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1697 $this->assertEquals( [], $watchedItems );
1700 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1701 $store = $this->newWatchedItemStore(
1702 $this->getMockLBFactory( $this->getMockDb() ),
1703 $this->getMockJobQueueGroup(),
1704 $this->getMockCache(),
1705 $this->getMockReadOnlyMode()
1708 $this->setExpectedException( InvalidArgumentException
::class );
1709 $store->getWatchedItemsForUser(
1710 $this->getMockNonAnonUserWithId( 1 ),
1715 public function testIsWatchedItem_existingItem() {
1716 $mockDb = $this->getMockDb();
1717 $mockDb->expects( $this->once() )
1718 ->method( 'selectRow' )
1721 'wl_notificationtimestamp',
1724 'wl_namespace' => 0,
1725 'wl_title' => 'SomeDbKey',
1728 ->will( $this->returnValue(
1729 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1732 $mockCache = $this->getMockCache();
1733 $mockCache->expects( $this->never() )->method( 'delete' );
1734 $mockCache->expects( $this->once() )
1736 ->with( '0:SomeDbKey:1' )
1737 ->will( $this->returnValue( false ) );
1738 $mockCache->expects( $this->once() )
1744 $store = $this->newWatchedItemStore(
1745 $this->getMockLBFactory( $mockDb ),
1746 $this->getMockJobQueueGroup(),
1748 $this->getMockReadOnlyMode()
1753 $this->getMockNonAnonUserWithId( 1 ),
1754 new TitleValue( 0, 'SomeDbKey' )
1759 public function testIsWatchedItem_noItem() {
1760 $mockDb = $this->getMockDb();
1761 $mockDb->expects( $this->once() )
1762 ->method( 'selectRow' )
1765 'wl_notificationtimestamp',
1768 'wl_namespace' => 0,
1769 'wl_title' => 'SomeDbKey',
1772 ->will( $this->returnValue( [] ) );
1774 $mockCache = $this->getMockCache();
1775 $mockCache->expects( $this->never() )->method( 'set' );
1776 $mockCache->expects( $this->never() )->method( 'delete' );
1777 $mockCache->expects( $this->once() )
1779 ->with( '0:SomeDbKey:1' )
1780 ->will( $this->returnValue( false ) );
1782 $store = $this->newWatchedItemStore(
1783 $this->getMockLBFactory( $mockDb ),
1784 $this->getMockJobQueueGroup(),
1786 $this->getMockReadOnlyMode()
1791 $this->getMockNonAnonUserWithId( 1 ),
1792 new TitleValue( 0, 'SomeDbKey' )
1797 public function testIsWatchedItem_anonymousUser() {
1798 $mockDb = $this->getMockDb();
1799 $mockDb->expects( $this->never() )
1800 ->method( 'selectRow' );
1802 $mockCache = $this->getMockCache();
1803 $mockCache->expects( $this->never() )->method( 'set' );
1804 $mockCache->expects( $this->never() )->method( 'get' );
1805 $mockCache->expects( $this->never() )->method( 'delete' );
1807 $store = $this->newWatchedItemStore(
1808 $this->getMockLBFactory( $mockDb ),
1809 $this->getMockJobQueueGroup(),
1811 $this->getMockReadOnlyMode()
1816 $this->getAnonUser(),
1817 new TitleValue( 0, 'SomeDbKey' )
1822 public function testGetNotificationTimestampsBatch() {
1824 new TitleValue( 0, 'SomeDbKey' ),
1825 new TitleValue( 1, 'AnotherDbKey' ),
1828 $mockDb = $this->getMockDb();
1830 $this->getFakeRow( [
1831 'wl_namespace' => '0',
1832 'wl_title' => 'SomeDbKey',
1833 'wl_notificationtimestamp' => '20151212010101',
1837 'wl_namespace' => '1',
1838 'wl_title' => 'AnotherDbKey',
1839 'wl_notificationtimestamp' => null,
1844 $mockDb->expects( $this->once() )
1845 ->method( 'makeWhereFrom2d' )
1847 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1848 $this->isType( 'string' ),
1849 $this->isType( 'string' )
1851 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1852 $mockDb->expects( $this->once() )
1853 ->method( 'select' )
1856 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1858 'makeWhereFrom2d return value',
1861 $this->isType( 'string' )
1863 ->will( $this->returnValue( $dbResult ) );
1865 $mockCache = $this->getMockCache();
1866 $mockCache->expects( $this->exactly( 2 ) )
1869 [ '0:SomeDbKey:1' ],
1870 [ '1:AnotherDbKey:1' ]
1872 ->will( $this->returnValue( null ) );
1873 $mockCache->expects( $this->never() )->method( 'set' );
1874 $mockCache->expects( $this->never() )->method( 'delete' );
1876 $store = $this->newWatchedItemStore(
1877 $this->getMockLBFactory( $mockDb ),
1878 $this->getMockJobQueueGroup(),
1880 $this->getMockReadOnlyMode()
1883 $this->assertEquals(
1885 0 => [ 'SomeDbKey' => '20151212010101', ],
1886 1 => [ 'AnotherDbKey' => null, ],
1888 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1892 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1894 new TitleValue( 0, 'OtherDbKey' ),
1897 $mockDb = $this->getMockDb();
1899 $mockDb->expects( $this->once() )
1900 ->method( 'makeWhereFrom2d' )
1902 [ [ 'OtherDbKey' => 1 ] ],
1903 $this->isType( 'string' ),
1904 $this->isType( 'string' )
1906 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1907 $mockDb->expects( $this->once() )
1908 ->method( 'select' )
1911 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1913 'makeWhereFrom2d return value',
1916 $this->isType( 'string' )
1918 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1920 $mockCache = $this->getMockCache();
1921 $mockCache->expects( $this->once() )
1923 ->with( '0:OtherDbKey:1' )
1924 ->will( $this->returnValue( null ) );
1925 $mockCache->expects( $this->never() )->method( 'set' );
1926 $mockCache->expects( $this->never() )->method( 'delete' );
1928 $store = $this->newWatchedItemStore(
1929 $this->getMockLBFactory( $mockDb ),
1930 $this->getMockJobQueueGroup(),
1932 $this->getMockReadOnlyMode()
1935 $this->assertEquals(
1937 0 => [ 'OtherDbKey' => false, ],
1939 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1943 public function testGetNotificationTimestampsBatch_cachedItem() {
1945 new TitleValue( 0, 'SomeDbKey' ),
1946 new TitleValue( 1, 'AnotherDbKey' ),
1949 $user = $this->getMockNonAnonUserWithId( 1 );
1950 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1952 $mockDb = $this->getMockDb();
1954 $mockDb->expects( $this->once() )
1955 ->method( 'makeWhereFrom2d' )
1957 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1958 $this->isType( 'string' ),
1959 $this->isType( 'string' )
1961 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1962 $mockDb->expects( $this->once() )
1963 ->method( 'select' )
1966 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1968 'makeWhereFrom2d return value',
1971 $this->isType( 'string' )
1973 ->will( $this->returnValue( [
1975 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1979 $mockCache = $this->getMockCache();
1980 $mockCache->expects( $this->at( 1 ) )
1982 ->with( '0:SomeDbKey:1' )
1983 ->will( $this->returnValue( $cachedItem ) );
1984 $mockCache->expects( $this->at( 3 ) )
1986 ->with( '1:AnotherDbKey:1' )
1987 ->will( $this->returnValue( null ) );
1988 $mockCache->expects( $this->never() )->method( 'set' );
1989 $mockCache->expects( $this->never() )->method( 'delete' );
1991 $store = $this->newWatchedItemStore(
1992 $this->getMockLBFactory( $mockDb ),
1993 $this->getMockJobQueueGroup(),
1995 $this->getMockReadOnlyMode()
1998 $this->assertEquals(
2000 0 => [ 'SomeDbKey' => '20151212010101', ],
2001 1 => [ 'AnotherDbKey' => null, ],
2003 $store->getNotificationTimestampsBatch( $user, $targets )
2007 public function testGetNotificationTimestampsBatch_allItemsCached() {
2009 new TitleValue( 0, 'SomeDbKey' ),
2010 new TitleValue( 1, 'AnotherDbKey' ),
2013 $user = $this->getMockNonAnonUserWithId( 1 );
2015 new WatchedItem( $user, $targets[0], '20151212010101' ),
2016 new WatchedItem( $user, $targets[1], null ),
2018 $mockDb = $this->getMockDb();
2019 $mockDb->expects( $this->never() )->method( $this->anything() );
2021 $mockCache = $this->getMockCache();
2022 $mockCache->expects( $this->at( 1 ) )
2024 ->with( '0:SomeDbKey:1' )
2025 ->will( $this->returnValue( $cachedItems[0] ) );
2026 $mockCache->expects( $this->at( 3 ) )
2028 ->with( '1:AnotherDbKey:1' )
2029 ->will( $this->returnValue( $cachedItems[1] ) );
2030 $mockCache->expects( $this->never() )->method( 'set' );
2031 $mockCache->expects( $this->never() )->method( 'delete' );
2033 $store = $this->newWatchedItemStore(
2034 $this->getMockLBFactory( $mockDb ),
2035 $this->getMockJobQueueGroup(),
2037 $this->getMockReadOnlyMode()
2040 $this->assertEquals(
2042 0 => [ 'SomeDbKey' => '20151212010101', ],
2043 1 => [ 'AnotherDbKey' => null, ],
2045 $store->getNotificationTimestampsBatch( $user, $targets )
2049 public function testGetNotificationTimestampsBatch_anonymousUser() {
2051 new TitleValue( 0, 'SomeDbKey' ),
2052 new TitleValue( 1, 'AnotherDbKey' ),
2055 $mockDb = $this->getMockDb();
2056 $mockDb->expects( $this->never() )->method( $this->anything() );
2058 $mockCache = $this->getMockCache();
2059 $mockCache->expects( $this->never() )->method( $this->anything() );
2061 $store = $this->newWatchedItemStore(
2062 $this->getMockLBFactory( $mockDb ),
2063 $this->getMockJobQueueGroup(),
2065 $this->getMockReadOnlyMode()
2068 $this->assertEquals(
2070 0 => [ 'SomeDbKey' => false, ],
2071 1 => [ 'AnotherDbKey' => false, ],
2073 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
2077 public function testResetNotificationTimestamp_anonymousUser() {
2078 $mockDb = $this->getMockDb();
2079 $mockDb->expects( $this->never() )
2080 ->method( 'selectRow' );
2082 $mockCache = $this->getMockCache();
2083 $mockCache->expects( $this->never() )->method( 'get' );
2084 $mockCache->expects( $this->never() )->method( 'set' );
2085 $mockCache->expects( $this->never() )->method( 'delete' );
2087 $store = $this->newWatchedItemStore(
2088 $this->getMockLBFactory( $mockDb ),
2089 $this->getMockJobQueueGroup(),
2091 $this->getMockReadOnlyMode()
2095 $store->resetNotificationTimestamp(
2096 $this->getAnonUser(),
2097 Title
::newFromText( 'SomeDbKey' )
2102 public function testResetNotificationTimestamp_noItem() {
2103 $mockDb = $this->getMockDb();
2104 $mockDb->expects( $this->once() )
2105 ->method( 'selectRow' )
2108 'wl_notificationtimestamp',
2111 'wl_namespace' => 0,
2112 'wl_title' => 'SomeDbKey',
2115 ->will( $this->returnValue( [] ) );
2117 $mockCache = $this->getMockCache();
2118 $mockCache->expects( $this->never() )->method( 'get' );
2119 $mockCache->expects( $this->never() )->method( 'set' );
2120 $mockCache->expects( $this->never() )->method( 'delete' );
2122 $store = $this->newWatchedItemStore(
2123 $this->getMockLBFactory( $mockDb ),
2124 $this->getMockJobQueueGroup(),
2126 $this->getMockReadOnlyMode()
2130 $store->resetNotificationTimestamp(
2131 $this->getMockNonAnonUserWithId( 1 ),
2132 Title
::newFromText( 'SomeDbKey' )
2137 public function testResetNotificationTimestamp_item() {
2138 $user = $this->getMockNonAnonUserWithId( 1 );
2139 $title = Title
::newFromText( 'SomeDbKey' );
2141 $mockDb = $this->getMockDb();
2142 $mockDb->expects( $this->once() )
2143 ->method( 'selectRow' )
2146 'wl_notificationtimestamp',
2149 'wl_namespace' => 0,
2150 'wl_title' => 'SomeDbKey',
2153 ->will( $this->returnValue(
2154 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2157 $mockCache = $this->getMockCache();
2158 $mockCache->expects( $this->never() )->method( 'get' );
2159 $mockCache->expects( $this->once() )
2163 $this->isInstanceOf( WatchedItem
::class )
2165 $mockCache->expects( $this->once() )
2166 ->method( 'delete' )
2167 ->with( '0:SomeDbKey:1' );
2169 $mockQueueGroup = $this->getMockJobQueueGroup();
2170 $mockQueueGroup->expects( $this->once() )
2171 ->method( 'lazyPush' )
2172 ->willReturnCallback( function ( ActivityUpdateJob
$job ) {
2176 $store = $this->newWatchedItemStore(
2177 $this->getMockLBFactory( $mockDb ),
2180 $this->getMockReadOnlyMode()
2184 $store->resetNotificationTimestamp(
2191 public function testResetNotificationTimestamp_noItemForced() {
2192 $user = $this->getMockNonAnonUserWithId( 1 );
2193 $title = Title
::newFromText( 'SomeDbKey' );
2195 $mockDb = $this->getMockDb();
2196 $mockDb->expects( $this->never() )
2197 ->method( 'selectRow' );
2199 $mockCache = $this->getMockCache();
2200 $mockCache->expects( $this->never() )->method( 'get' );
2201 $mockCache->expects( $this->never() )->method( 'set' );
2202 $mockCache->expects( $this->once() )
2203 ->method( 'delete' )
2204 ->with( '0:SomeDbKey:1' );
2206 $mockQueueGroup = $this->getMockJobQueueGroup();
2207 $store = $this->newWatchedItemStore(
2208 $this->getMockLBFactory( $mockDb ),
2211 $this->getMockReadOnlyMode()
2214 $mockQueueGroup->expects( $this->any() )
2215 ->method( 'lazyPush' )
2216 ->will( $this->returnCallback( function ( ActivityUpdateJob
$job ) {
2221 $store->resetNotificationTimestamp(
2230 * @param string $text
2233 * @return PHPUnit_Framework_MockObject_MockObject|Title
2235 private function getMockTitle( $text, $ns = 0 ) {
2236 $title = $this->createMock( Title
::class );
2237 $title->expects( $this->any() )
2238 ->method( 'getText' )
2239 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2240 $title->expects( $this->any() )
2241 ->method( 'getDbKey' )
2242 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2243 $title->expects( $this->any() )
2244 ->method( 'getNamespace' )
2245 ->will( $this->returnValue( $ns ) );
2249 private function verifyCallbackJob(
2250 ActivityUpdateJob
$job,
2251 LinkTarget
$expectedTitle,
2253 callable
$notificationTimestampCondition
2255 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2256 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2258 $jobParams = $job->getParams();
2259 $this->assertArrayHasKey( 'type', $jobParams );
2260 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2261 $this->assertArrayHasKey( 'userid', $jobParams );
2262 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2263 $this->assertArrayHasKey( 'notifTime', $jobParams );
2264 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2267 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2268 $user = $this->getMockNonAnonUserWithId( 1 );
2270 $title = $this->getMockTitle( 'SomeTitle' );
2271 $title->expects( $this->once() )
2272 ->method( 'getNextRevisionID' )
2274 ->will( $this->returnValue( false ) );
2276 $mockDb = $this->getMockDb();
2277 $mockDb->expects( $this->never() )
2278 ->method( 'selectRow' );
2280 $mockCache = $this->getMockCache();
2281 $mockCache->expects( $this->never() )->method( 'get' );
2282 $mockCache->expects( $this->never() )->method( 'set' );
2283 $mockCache->expects( $this->once() )
2284 ->method( 'delete' )
2285 ->with( '0:SomeTitle:1' );
2287 $mockQueueGroup = $this->getMockJobQueueGroup();
2288 $store = $this->newWatchedItemStore(
2289 $this->getMockLBFactory( $mockDb ),
2292 $this->getMockReadOnlyMode()
2295 $mockQueueGroup->expects( $this->any() )
2296 ->method( 'lazyPush' )
2297 ->will( $this->returnCallback(
2298 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2299 $this->verifyCallbackJob(
2303 function ( $time ) {
2304 return $time === null;
2311 $store->resetNotificationTimestamp(
2320 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2321 $user = $this->getMockNonAnonUserWithId( 1 );
2323 $title = $this->getMockTitle( 'SomeDbKey' );
2324 $title->expects( $this->once() )
2325 ->method( 'getNextRevisionID' )
2327 ->will( $this->returnValue( 33 ) );
2329 $mockDb = $this->getMockDb();
2330 $mockDb->expects( $this->once() )
2331 ->method( 'selectRow' )
2334 'wl_notificationtimestamp',
2337 'wl_namespace' => 0,
2338 'wl_title' => 'SomeDbKey',
2341 ->will( $this->returnValue(
2342 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2345 $mockCache = $this->getMockCache();
2346 $mockCache->expects( $this->never() )->method( 'get' );
2347 $mockCache->expects( $this->once() )
2349 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2350 $mockCache->expects( $this->once() )
2351 ->method( 'delete' )
2352 ->with( '0:SomeDbKey:1' );
2354 $mockQueueGroup = $this->getMockJobQueueGroup();
2355 $store = $this->newWatchedItemStore(
2356 $this->getMockLBFactory( $mockDb ),
2359 $this->getMockReadOnlyMode()
2362 $mockQueueGroup->expects( $this->any() )
2363 ->method( 'lazyPush' )
2364 ->will( $this->returnCallback(
2365 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2366 $this->verifyCallbackJob(
2370 function ( $time ) {
2371 return $time !== null && $time > '20151212010101';
2377 $getTimestampCallCounter = 0;
2378 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2379 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2380 $getTimestampCallCounter++
;
2381 $this->assertEquals( $title, $titleParam );
2382 $this->assertEquals( $oldid, $oldidParam );
2387 $store->resetNotificationTimestamp(
2394 $this->assertEquals( 2, $getTimestampCallCounter );
2396 ScopedCallback
::consume( $scopedOverrideRevision );
2399 public function testResetNotificationTimestamp_notWatchedPageForced() {
2400 $user = $this->getMockNonAnonUserWithId( 1 );
2402 $title = $this->getMockTitle( 'SomeDbKey' );
2403 $title->expects( $this->once() )
2404 ->method( 'getNextRevisionID' )
2406 ->will( $this->returnValue( 33 ) );
2408 $mockDb = $this->getMockDb();
2409 $mockDb->expects( $this->once() )
2410 ->method( 'selectRow' )
2413 'wl_notificationtimestamp',
2416 'wl_namespace' => 0,
2417 'wl_title' => 'SomeDbKey',
2420 ->will( $this->returnValue( false ) );
2422 $mockCache = $this->getMockCache();
2423 $mockCache->expects( $this->never() )->method( 'get' );
2424 $mockCache->expects( $this->never() )->method( 'set' );
2425 $mockCache->expects( $this->once() )
2426 ->method( 'delete' )
2427 ->with( '0:SomeDbKey:1' );
2429 $mockQueueGroup = $this->getMockJobQueueGroup();
2430 $store = $this->newWatchedItemStore(
2431 $this->getMockLBFactory( $mockDb ),
2434 $this->getMockReadOnlyMode()
2437 $mockQueueGroup->expects( $this->any() )
2438 ->method( 'lazyPush' )
2439 ->will( $this->returnCallback(
2440 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2441 $this->verifyCallbackJob(
2445 function ( $time ) {
2446 return $time === null;
2453 $store->resetNotificationTimestamp(
2462 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2463 $user = $this->getMockNonAnonUserWithId( 1 );
2465 $title = $this->getMockTitle( 'SomeDbKey' );
2466 $title->expects( $this->once() )
2467 ->method( 'getNextRevisionID' )
2469 ->will( $this->returnValue( 33 ) );
2471 $mockDb = $this->getMockDb();
2472 $mockDb->expects( $this->once() )
2473 ->method( 'selectRow' )
2476 'wl_notificationtimestamp',
2479 'wl_namespace' => 0,
2480 'wl_title' => 'SomeDbKey',
2483 ->will( $this->returnValue(
2484 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2487 $mockCache = $this->getMockCache();
2488 $mockCache->expects( $this->never() )->method( 'get' );
2489 $mockCache->expects( $this->once() )
2491 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2492 $mockCache->expects( $this->once() )
2493 ->method( 'delete' )
2494 ->with( '0:SomeDbKey:1' );
2496 $mockQueueGroup = $this->getMockJobQueueGroup();
2497 $store = $this->newWatchedItemStore(
2498 $this->getMockLBFactory( $mockDb ),
2501 $this->getMockReadOnlyMode()
2504 $mockQueueGroup->expects( $this->any() )
2505 ->method( 'lazyPush' )
2506 ->will( $this->returnCallback(
2507 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2508 $this->verifyCallbackJob(
2512 function ( $time ) {
2513 return $time === '30151212010101';
2519 $getTimestampCallCounter = 0;
2520 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2521 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2522 $getTimestampCallCounter++
;
2523 $this->assertEquals( $title, $titleParam );
2524 $this->assertEquals( $oldid, $oldidParam );
2529 $store->resetNotificationTimestamp(
2536 $this->assertEquals( 2, $getTimestampCallCounter );
2538 ScopedCallback
::consume( $scopedOverrideRevision );
2541 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2542 $user = $this->getMockNonAnonUserWithId( 1 );
2544 $title = $this->getMockTitle( 'SomeDbKey' );
2545 $title->expects( $this->once() )
2546 ->method( 'getNextRevisionID' )
2548 ->will( $this->returnValue( 33 ) );
2550 $mockDb = $this->getMockDb();
2551 $mockDb->expects( $this->once() )
2552 ->method( 'selectRow' )
2555 'wl_notificationtimestamp',
2558 'wl_namespace' => 0,
2559 'wl_title' => 'SomeDbKey',
2562 ->will( $this->returnValue(
2563 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2566 $mockCache = $this->getMockCache();
2567 $mockCache->expects( $this->never() )->method( 'get' );
2568 $mockCache->expects( $this->once() )
2570 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2571 $mockCache->expects( $this->once() )
2572 ->method( 'delete' )
2573 ->with( '0:SomeDbKey:1' );
2575 $mockQueueGroup = $this->getMockJobQueueGroup();
2576 $store = $this->newWatchedItemStore(
2577 $this->getMockLBFactory( $mockDb ),
2580 $this->getMockReadOnlyMode()
2583 $mockQueueGroup->expects( $this->any() )
2584 ->method( 'lazyPush' )
2585 ->will( $this->returnCallback(
2586 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2587 $this->verifyCallbackJob(
2591 function ( $time ) {
2592 return $time === false;
2598 $getTimestampCallCounter = 0;
2599 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2600 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2601 $getTimestampCallCounter++
;
2602 $this->assertEquals( $title, $titleParam );
2603 $this->assertEquals( $oldid, $oldidParam );
2608 $store->resetNotificationTimestamp(
2615 $this->assertEquals( 2, $getTimestampCallCounter );
2617 ScopedCallback
::consume( $scopedOverrideRevision );
2620 public function testSetNotificationTimestampsForUser_anonUser() {
2621 $store = $this->newWatchedItemStore(
2622 $this->getMockLBFactory( $this->getMockDb() ),
2623 $this->getMockJobQueueGroup(),
2624 $this->getMockCache(),
2625 $this->getMockReadOnlyMode()
2627 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2630 public function testSetNotificationTimestampsForUser_allRows() {
2631 $user = $this->getMockNonAnonUserWithId( 1 );
2632 $timestamp = '20100101010101';
2634 $store = $this->newWatchedItemStore(
2635 $this->getMockLBFactory( $this->getMockDb() ),
2636 $this->getMockJobQueueGroup(),
2637 $this->getMockCache(),
2638 $this->getMockReadOnlyMode()
2641 // Note: This does not actually assert the job is correct
2642 $callableCallCounter = 0;
2643 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2644 $callableCallCounter++
;
2645 $this->assertInternalType( 'callable', $callable );
2647 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2650 $store->setNotificationTimestampsForUser( $user, $timestamp )
2652 $this->assertEquals( 1, $callableCallCounter );
2655 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2656 $user = $this->getMockNonAnonUserWithId( 1 );
2659 $store = $this->newWatchedItemStore(
2660 $this->getMockLBFactory( $this->getMockDb() ),
2661 $this->getMockJobQueueGroup(),
2662 $this->getMockCache(),
2663 $this->getMockReadOnlyMode()
2666 // Note: This does not actually assert the job is correct
2667 $callableCallCounter = 0;
2668 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2669 $callableCallCounter++
;
2670 $this->assertInternalType( 'callable', $callable );
2672 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2675 $store->setNotificationTimestampsForUser( $user, $timestamp )
2679 public function testSetNotificationTimestampsForUser_specificTargets() {
2680 $user = $this->getMockNonAnonUserWithId( 1 );
2681 $timestamp = '20100101010101';
2682 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2684 $mockDb = $this->getMockDb();
2685 $mockDb->expects( $this->once() )
2686 ->method( 'update' )
2689 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2690 [ 'wl_user' => 1, 'wl_namespace' => 0, 'wl_title' => [ 'Foo', 'Bar' ] ]
2692 ->will( $this->returnValue( true ) );
2693 $mockDb->expects( $this->exactly( 1 ) )
2694 ->method( 'timestamp' )
2695 ->will( $this->returnCallback( function ( $value ) {
2696 return 'TS' . $value . 'TS';
2698 $mockDb->expects( $this->once() )
2699 ->method( 'affectedRows' )
2700 ->will( $this->returnValue( 2 ) );
2702 $store = $this->newWatchedItemStore(
2703 $this->getMockLBFactory( $mockDb ),
2704 $this->getMockJobQueueGroup(),
2705 $this->getMockCache(),
2706 $this->getMockReadOnlyMode()
2710 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2714 public function testUpdateNotificationTimestamp_watchersExist() {
2715 $mockDb = $this->getMockDb();
2716 $mockDb->expects( $this->once() )
2717 ->method( 'selectFieldValues' )
2723 'wl_namespace' => 0,
2724 'wl_title' => 'SomeDbKey',
2725 'wl_notificationtimestamp IS NULL'
2728 ->will( $this->returnValue( [ '2', '3' ] ) );
2729 $mockDb->expects( $this->once() )
2730 ->method( 'update' )
2733 [ 'wl_notificationtimestamp' => null ],
2735 'wl_user' => [ 2, 3 ],
2736 'wl_namespace' => 0,
2737 'wl_title' => 'SomeDbKey',
2741 $mockCache = $this->getMockCache();
2742 $mockCache->expects( $this->never() )->method( 'set' );
2743 $mockCache->expects( $this->never() )->method( 'get' );
2744 $mockCache->expects( $this->never() )->method( 'delete' );
2746 $store = $this->newWatchedItemStore(
2747 $this->getMockLBFactory( $mockDb ),
2748 $this->getMockJobQueueGroup(),
2750 $this->getMockReadOnlyMode()
2753 $this->assertEquals(
2755 $store->updateNotificationTimestamp(
2756 $this->getMockNonAnonUserWithId( 1 ),
2757 new TitleValue( 0, 'SomeDbKey' ),
2763 public function testUpdateNotificationTimestamp_noWatchers() {
2764 $mockDb = $this->getMockDb();
2765 $mockDb->expects( $this->once() )
2766 ->method( 'selectFieldValues' )
2772 'wl_namespace' => 0,
2773 'wl_title' => 'SomeDbKey',
2774 'wl_notificationtimestamp IS NULL'
2778 $this->returnValue( [] )
2780 $mockDb->expects( $this->never() )
2781 ->method( 'update' );
2783 $mockCache = $this->getMockCache();
2784 $mockCache->expects( $this->never() )->method( 'set' );
2785 $mockCache->expects( $this->never() )->method( 'get' );
2786 $mockCache->expects( $this->never() )->method( 'delete' );
2788 $store = $this->newWatchedItemStore(
2789 $this->getMockLBFactory( $mockDb ),
2790 $this->getMockJobQueueGroup(),
2792 $this->getMockReadOnlyMode()
2795 $watchers = $store->updateNotificationTimestamp(
2796 $this->getMockNonAnonUserWithId( 1 ),
2797 new TitleValue( 0, 'SomeDbKey' ),
2800 $this->assertInternalType( 'array', $watchers );
2801 $this->assertEmpty( $watchers );
2804 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2805 $user = $this->getMockNonAnonUserWithId( 1 );
2806 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2808 $mockDb = $this->getMockDb();
2809 $mockDb->expects( $this->once() )
2810 ->method( 'selectRow' )
2811 ->will( $this->returnValue(
2812 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2814 $mockDb->expects( $this->once() )
2815 ->method( 'selectFieldValues' )
2817 $this->returnValue( [ '2', '3' ] )
2819 $mockDb->expects( $this->once() )
2820 ->method( 'update' );
2822 $mockCache = $this->getMockCache();
2823 $mockCache->expects( $this->once() )
2825 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2826 $mockCache->expects( $this->once() )
2828 ->with( '0:SomeDbKey:1' );
2829 $mockCache->expects( $this->once() )
2830 ->method( 'delete' )
2831 ->with( '0:SomeDbKey:1' );
2833 $store = $this->newWatchedItemStore(
2834 $this->getMockLBFactory( $mockDb ),
2835 $this->getMockJobQueueGroup(),
2837 $this->getMockReadOnlyMode()
2840 // This will add the item to the cache
2841 $store->getWatchedItem( $user, $titleValue );
2843 $store->updateNotificationTimestamp(
2844 $this->getMockNonAnonUserWithId( 1 ),