2 use MediaWiki\Linker\LinkTarget
;
3 use MediaWiki\User\UserIdentityValue
;
4 use Wikimedia\Rdbms\LBFactory
;
5 use Wikimedia\Rdbms\LoadBalancer
;
6 use Wikimedia\ScopedCallback
;
7 use Wikimedia\TestingAccessWrapper
;
12 * @covers WatchedItemStore
14 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
17 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
19 private function getMockDb() {
20 return $this->createMock( IDatabase
::class );
24 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
26 private function getMockLoadBalancer(
28 $expectedConnectionType = null
30 $mock = $this->getMockBuilder( LoadBalancer
::class )
31 ->disableOriginalConstructor()
33 if ( $expectedConnectionType !== null ) {
34 $mock->expects( $this->any() )
35 ->method( 'getConnectionRef' )
36 ->with( $expectedConnectionType )
37 ->will( $this->returnValue( $mockDb ) );
39 $mock->expects( $this->any() )
40 ->method( 'getConnectionRef' )
41 ->will( $this->returnValue( $mockDb ) );
47 * @return PHPUnit_Framework_MockObject_MockObject|LBFactory
49 private function getMockLBFactory(
51 $expectedConnectionType = null
53 $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
54 $mock = $this->getMockBuilder( LBFactory
::class )
55 ->disableOriginalConstructor()
57 $mock->expects( $this->any() )
58 ->method( 'getMainLB' )
59 ->will( $this->returnValue( $loadBalancer ) );
64 * @return PHPUnit_Framework_MockObject_MockObject|JobQueueGroup
66 private function getMockJobQueueGroup() {
67 $mock = $this->getMockBuilder( JobQueueGroup
::class )
68 ->disableOriginalConstructor()
70 $mock->expects( $this->any() )
72 ->will( $this->returnCallback( function ( Job
$job ) {
75 $mock->expects( $this->any() )
76 ->method( 'lazyPush' )
77 ->will( $this->returnCallback( function ( Job
$job ) {
84 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
86 private function getMockCache() {
87 $mock = $this->getMockBuilder( HashBagOStuff
::class )
88 ->disableOriginalConstructor()
89 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
91 $mock->expects( $this->any() )
93 ->will( $this->returnCallback( function ( ...$args ) {
94 return implode( ':', $args );
100 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
102 private function getMockReadOnlyMode( $readOnly = false ) {
103 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
104 ->disableOriginalConstructor()
106 $mock->expects( $this->any() )
107 ->method( 'isReadOnly' )
108 ->will( $this->returnValue( $readOnly ) );
112 private function getFakeRow( array $rowValues ) {
113 $fakeRow = new stdClass();
114 foreach ( $rowValues as $valueName => $value ) {
115 $fakeRow->$valueName = $value;
120 private function newWatchedItemStore(
121 LBFactory
$lbFactory,
122 JobQueueGroup
$queueGroup,
123 HashBagOStuff
$cache,
124 ReadOnlyMode
$readOnlyMode
126 return new WatchedItemStore(
136 public function testClearWatchedItems() {
137 $user = new UserIdentityValue( 7, 'MockUser', 0 );
139 $mockDb = $this->getMockDb();
140 $mockDb->expects( $this->once() )
141 ->method( 'selectField' )
146 'wl_user' => $user->getId(),
148 $this->isType( 'string' )
150 ->will( $this->returnValue( 12 ) );
151 $mockDb->expects( $this->once() )
156 $this->isType( 'string' )
159 $mockCache = $this->getMockCache();
160 $mockCache->expects( $this->never() )->method( 'get' );
161 $mockCache->expects( $this->never() )->method( 'set' );
162 $mockCache->expects( $this->once() )
166 $store = $this->newWatchedItemStore(
167 $this->getMockLBFactory( $mockDb ),
168 $this->getMockJobQueueGroup(),
170 $this->getMockReadOnlyMode()
172 TestingAccessWrapper
::newFromObject( $store )
173 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
175 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
178 public function testClearWatchedItems_tooManyItemsWatched() {
179 $user = new UserIdentityValue( 7, 'MockUser', 0 );
181 $mockDb = $this->getMockDb();
182 $mockDb->expects( $this->once() )
183 ->method( 'selectField' )
188 'wl_user' => $user->getId(),
190 $this->isType( 'string' )
192 ->will( $this->returnValue( 99999 ) );
194 $mockCache = $this->getMockCache();
195 $mockCache->expects( $this->never() )->method( 'get' );
196 $mockCache->expects( $this->never() )->method( 'set' );
197 $mockCache->expects( $this->never() )->method( 'delete' );
199 $store = $this->newWatchedItemStore(
200 $this->getMockLBFactory( $mockDb ),
201 $this->getMockJobQueueGroup(),
203 $this->getMockReadOnlyMode()
206 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
209 public function testCountWatchedItems() {
210 $user = new UserIdentityValue( 1, 'MockUser', 0 );
212 $mockDb = $this->getMockDb();
213 $mockDb->expects( $this->exactly( 1 ) )
214 ->method( 'selectField' )
219 'wl_user' => $user->getId(),
221 $this->isType( 'string' )
223 ->will( $this->returnValue( '12' ) );
225 $mockCache = $this->getMockCache();
226 $mockCache->expects( $this->never() )->method( 'get' );
227 $mockCache->expects( $this->never() )->method( 'set' );
228 $mockCache->expects( $this->never() )->method( 'delete' );
230 $store = $this->newWatchedItemStore(
231 $this->getMockLBFactory( $mockDb ),
232 $this->getMockJobQueueGroup(),
234 $this->getMockReadOnlyMode()
237 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
240 public function testCountWatchers() {
241 $titleValue = new TitleValue( 0, 'SomeDbKey' );
243 $mockDb = $this->getMockDb();
244 $mockDb->expects( $this->exactly( 1 ) )
245 ->method( 'selectField' )
250 'wl_namespace' => $titleValue->getNamespace(),
251 'wl_title' => $titleValue->getDBkey(),
253 $this->isType( 'string' )
255 ->will( $this->returnValue( '7' ) );
257 $mockCache = $this->getMockCache();
258 $mockCache->expects( $this->never() )->method( 'get' );
259 $mockCache->expects( $this->never() )->method( 'set' );
260 $mockCache->expects( $this->never() )->method( 'delete' );
262 $store = $this->newWatchedItemStore(
263 $this->getMockLBFactory( $mockDb ),
264 $this->getMockJobQueueGroup(),
266 $this->getMockReadOnlyMode()
269 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
272 public function testCountWatchersMultiple() {
274 new TitleValue( 0, 'SomeDbKey' ),
275 new TitleValue( 0, 'OtherDbKey' ),
276 new TitleValue( 1, 'AnotherDbKey' ),
279 $mockDb = $this->getMockDb();
282 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
283 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
284 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
287 $mockDb->expects( $this->once() )
288 ->method( 'makeWhereFrom2d' )
290 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
291 $this->isType( 'string' ),
292 $this->isType( 'string' )
294 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
295 $mockDb->expects( $this->once() )
299 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
300 [ 'makeWhereFrom2d return value' ],
301 $this->isType( 'string' ),
303 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
307 $this->returnValue( $dbResult )
310 $mockCache = $this->getMockCache();
311 $mockCache->expects( $this->never() )->method( 'get' );
312 $mockCache->expects( $this->never() )->method( 'set' );
313 $mockCache->expects( $this->never() )->method( 'delete' );
315 $store = $this->newWatchedItemStore(
316 $this->getMockLBFactory( $mockDb ),
317 $this->getMockJobQueueGroup(),
319 $this->getMockReadOnlyMode()
323 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
324 1 => [ 'AnotherDbKey' => 500 ],
326 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
329 public function provideIntWithDbUnsafeVersion() {
332 [ "50; DROP TABLE watchlist;\n--" ],
337 * @dataProvider provideIntWithDbUnsafeVersion
339 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
341 new TitleValue( 0, 'SomeDbKey' ),
342 new TitleValue( 0, 'OtherDbKey' ),
343 new TitleValue( 1, 'AnotherDbKey' ),
346 $mockDb = $this->getMockDb();
349 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
350 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
351 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
354 $mockDb->expects( $this->once() )
355 ->method( 'makeWhereFrom2d' )
357 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
358 $this->isType( 'string' ),
359 $this->isType( 'string' )
361 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
362 $mockDb->expects( $this->once() )
366 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
367 [ 'makeWhereFrom2d return value' ],
368 $this->isType( 'string' ),
370 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
371 'HAVING' => 'COUNT(*) >= 50',
375 $this->returnValue( $dbResult )
378 $mockCache = $this->getMockCache();
379 $mockCache->expects( $this->never() )->method( 'get' );
380 $mockCache->expects( $this->never() )->method( 'set' );
381 $mockCache->expects( $this->never() )->method( 'delete' );
383 $store = $this->newWatchedItemStore(
384 $this->getMockLBFactory( $mockDb ),
385 $this->getMockJobQueueGroup(),
387 $this->getMockReadOnlyMode()
391 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
392 1 => [ 'AnotherDbKey' => 500 ],
396 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
400 public function testCountVisitingWatchers() {
401 $titleValue = new TitleValue( 0, 'SomeDbKey' );
403 $mockDb = $this->getMockDb();
404 $mockDb->expects( $this->exactly( 1 ) )
405 ->method( 'selectField' )
410 'wl_namespace' => $titleValue->getNamespace(),
411 'wl_title' => $titleValue->getDBkey(),
412 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
414 $this->isType( 'string' )
416 ->will( $this->returnValue( '7' ) );
417 $mockDb->expects( $this->exactly( 1 ) )
418 ->method( 'addQuotes' )
419 ->will( $this->returnCallback( function ( $value ) {
422 $mockDb->expects( $this->exactly( 1 ) )
423 ->method( 'timestamp' )
424 ->will( $this->returnCallback( function ( $value ) {
425 return 'TS' . $value . 'TS';
428 $mockCache = $this->getMockCache();
429 $mockCache->expects( $this->never() )->method( 'set' );
430 $mockCache->expects( $this->never() )->method( 'get' );
431 $mockCache->expects( $this->never() )->method( 'delete' );
433 $store = $this->newWatchedItemStore(
434 $this->getMockLBFactory( $mockDb ),
435 $this->getMockJobQueueGroup(),
437 $this->getMockReadOnlyMode()
440 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
443 public function testCountVisitingWatchersMultiple() {
444 $titleValuesWithThresholds = [
445 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
446 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
447 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
451 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
452 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
454 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
457 $mockDb = $this->getMockDb();
458 $mockDb->expects( $this->exactly( 2 * 3 ) )
459 ->method( 'addQuotes' )
460 ->will( $this->returnCallback( function ( $value ) {
463 $mockDb->expects( $this->exactly( 3 ) )
464 ->method( 'timestamp' )
465 ->will( $this->returnCallback( function ( $value ) {
466 return 'TS' . $value . 'TS';
468 $mockDb->expects( $this->any() )
469 ->method( 'makeList' )
471 $this->isType( 'array' ),
472 $this->isType( 'int' )
474 ->will( $this->returnCallback( function ( $a, $conj ) {
475 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
476 return implode( $sqlConj, array_map( function ( $s ) {
477 return '(' . $s . ')';
481 $mockDb->expects( $this->never() )
482 ->method( 'makeWhereFrom2d' );
485 '((wl_namespace = 0) AND (' .
486 "(((wl_title = 'SomeDbKey') AND (" .
487 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
489 "(wl_title = 'OtherDbKey') AND (" .
490 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
492 ') OR ((wl_namespace = 1) AND (' .
493 "(((wl_title = 'AnotherDbKey') AND (" .
494 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
496 $mockDb->expects( $this->once() )
500 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
502 $this->isType( 'string' ),
504 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
508 $this->returnValue( $dbResult )
511 $mockCache = $this->getMockCache();
512 $mockCache->expects( $this->never() )->method( 'get' );
513 $mockCache->expects( $this->never() )->method( 'set' );
514 $mockCache->expects( $this->never() )->method( 'delete' );
516 $store = $this->newWatchedItemStore(
517 $this->getMockLBFactory( $mockDb ),
518 $this->getMockJobQueueGroup(),
520 $this->getMockReadOnlyMode()
524 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
525 1 => [ 'AnotherDbKey' => 500 ],
529 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
533 public function testCountVisitingWatchersMultiple_withMissingTargets() {
534 $titleValuesWithThresholds = [
535 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
536 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
537 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
538 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
539 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
543 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
544 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
546 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
549 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
552 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
555 $mockDb = $this->getMockDb();
556 $mockDb->expects( $this->exactly( 2 * 3 ) )
557 ->method( 'addQuotes' )
558 ->will( $this->returnCallback( function ( $value ) {
561 $mockDb->expects( $this->exactly( 3 ) )
562 ->method( 'timestamp' )
563 ->will( $this->returnCallback( function ( $value ) {
564 return 'TS' . $value . 'TS';
566 $mockDb->expects( $this->any() )
567 ->method( 'makeList' )
569 $this->isType( 'array' ),
570 $this->isType( 'int' )
572 ->will( $this->returnCallback( function ( $a, $conj ) {
573 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
574 return implode( $sqlConj, array_map( function ( $s ) {
575 return '(' . $s . ')';
579 $mockDb->expects( $this->once() )
580 ->method( 'makeWhereFrom2d' )
582 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
583 $this->isType( 'string' ),
584 $this->isType( 'string' )
586 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
589 '((wl_namespace = 0) AND (' .
590 "(((wl_title = 'SomeDbKey') AND (" .
591 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
593 "(wl_title = 'OtherDbKey') AND (" .
594 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
596 ') OR ((wl_namespace = 1) AND (' .
597 "(((wl_title = 'AnotherDbKey') AND (" .
598 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
601 '(makeWhereFrom2d return value)';
602 $mockDb->expects( $this->once() )
606 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
608 $this->isType( 'string' ),
610 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
614 $this->returnValue( $dbResult )
617 $mockCache = $this->getMockCache();
618 $mockCache->expects( $this->never() )->method( 'get' );
619 $mockCache->expects( $this->never() )->method( 'set' );
620 $mockCache->expects( $this->never() )->method( 'delete' );
622 $store = $this->newWatchedItemStore(
623 $this->getMockLBFactory( $mockDb ),
624 $this->getMockJobQueueGroup(),
626 $this->getMockReadOnlyMode()
631 'SomeDbKey' => 100, 'OtherDbKey' => 300,
632 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
634 1 => [ 'AnotherDbKey' => 500 ],
638 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
643 * @dataProvider provideIntWithDbUnsafeVersion
645 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
646 $titleValuesWithThresholds = [
647 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
648 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
649 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
652 $mockDb = $this->getMockDb();
653 $mockDb->expects( $this->any() )
654 ->method( 'makeList' )
655 ->will( $this->returnValue( 'makeList return value' ) );
656 $mockDb->expects( $this->once() )
660 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
661 'makeList return value',
662 $this->isType( 'string' ),
664 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
665 'HAVING' => 'COUNT(*) >= 50',
669 $this->returnValue( [] )
672 $mockCache = $this->getMockCache();
673 $mockCache->expects( $this->never() )->method( 'get' );
674 $mockCache->expects( $this->never() )->method( 'set' );
675 $mockCache->expects( $this->never() )->method( 'delete' );
677 $store = $this->newWatchedItemStore(
678 $this->getMockLBFactory( $mockDb ),
679 $this->getMockJobQueueGroup(),
681 $this->getMockReadOnlyMode()
685 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
686 1 => [ 'AnotherDbKey' => 0 ],
690 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
694 public function testCountUnreadNotifications() {
695 $user = new UserIdentityValue( 1, 'MockUser', 0 );
697 $mockDb = $this->getMockDb();
698 $mockDb->expects( $this->exactly( 1 ) )
699 ->method( 'selectRowCount' )
704 "wl_notificationtimestamp IS NOT NULL",
707 $this->isType( 'string' )
709 ->will( $this->returnValue( '9' ) );
711 $mockCache = $this->getMockCache();
712 $mockCache->expects( $this->never() )->method( 'set' );
713 $mockCache->expects( $this->never() )->method( 'get' );
714 $mockCache->expects( $this->never() )->method( 'delete' );
716 $store = $this->newWatchedItemStore(
717 $this->getMockLBFactory( $mockDb ),
718 $this->getMockJobQueueGroup(),
720 $this->getMockReadOnlyMode()
723 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
727 * @dataProvider provideIntWithDbUnsafeVersion
729 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
730 $user = new UserIdentityValue( 1, 'MockUser', 0 );
732 $mockDb = $this->getMockDb();
733 $mockDb->expects( $this->exactly( 1 ) )
734 ->method( 'selectRowCount' )
739 "wl_notificationtimestamp IS NOT NULL",
742 $this->isType( 'string' ),
745 ->will( $this->returnValue( '50' ) );
747 $mockCache = $this->getMockCache();
748 $mockCache->expects( $this->never() )->method( 'set' );
749 $mockCache->expects( $this->never() )->method( 'get' );
750 $mockCache->expects( $this->never() )->method( 'delete' );
752 $store = $this->newWatchedItemStore(
753 $this->getMockLBFactory( $mockDb ),
754 $this->getMockJobQueueGroup(),
756 $this->getMockReadOnlyMode()
761 $store->countUnreadNotifications( $user, $limit )
766 * @dataProvider provideIntWithDbUnsafeVersion
768 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
769 $user = new UserIdentityValue( 1, 'MockUser', 0 );
771 $mockDb = $this->getMockDb();
772 $mockDb->expects( $this->exactly( 1 ) )
773 ->method( 'selectRowCount' )
778 "wl_notificationtimestamp IS NOT NULL",
781 $this->isType( 'string' ),
784 ->will( $this->returnValue( '9' ) );
786 $mockCache = $this->getMockCache();
787 $mockCache->expects( $this->never() )->method( 'set' );
788 $mockCache->expects( $this->never() )->method( 'get' );
789 $mockCache->expects( $this->never() )->method( 'delete' );
791 $store = $this->newWatchedItemStore(
792 $this->getMockLBFactory( $mockDb ),
793 $this->getMockJobQueueGroup(),
795 $this->getMockReadOnlyMode()
800 $store->countUnreadNotifications( $user, $limit )
804 public function testDuplicateEntry_nothingToDuplicate() {
805 $mockDb = $this->getMockDb();
806 $mockDb->expects( $this->once() )
812 'wl_notificationtimestamp',
816 'wl_title' => 'Old_Title',
818 'WatchedItemStore::duplicateEntry',
821 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
823 $store = $this->newWatchedItemStore(
824 $this->getMockLBFactory( $mockDb ),
825 $this->getMockJobQueueGroup(),
826 $this->getMockCache(),
827 $this->getMockReadOnlyMode()
830 $store->duplicateEntry(
831 new TitleValue( 0, 'Old_Title' ),
832 new TitleValue( 0, 'New_Title' )
836 public function testDuplicateEntry_somethingToDuplicate() {
838 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
839 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
842 $mockDb = $this->getMockDb();
843 $mockDb->expects( $this->at( 0 ) )
849 'wl_notificationtimestamp',
853 'wl_title' => 'Old_Title',
856 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
857 $mockDb->expects( $this->at( 1 ) )
858 ->method( 'replace' )
861 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
866 'wl_title' => 'New_Title',
867 'wl_notificationtimestamp' => '20151212010101',
872 'wl_title' => 'New_Title',
873 'wl_notificationtimestamp' => null,
876 $this->isType( 'string' )
879 $mockCache = $this->getMockCache();
880 $mockCache->expects( $this->never() )->method( 'get' );
881 $mockCache->expects( $this->never() )->method( 'delete' );
883 $store = $this->newWatchedItemStore(
884 $this->getMockLBFactory( $mockDb ),
885 $this->getMockJobQueueGroup(),
887 $this->getMockReadOnlyMode()
890 $store->duplicateEntry(
891 new TitleValue( 0, 'Old_Title' ),
892 new TitleValue( 0, 'New_Title' )
896 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
897 $mockDb = $this->getMockDb();
898 $mockDb->expects( $this->at( 0 ) )
904 'wl_notificationtimestamp',
908 'wl_title' => 'Old_Title',
911 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
912 $mockDb->expects( $this->at( 1 ) )
918 'wl_notificationtimestamp',
922 'wl_title' => 'Old_Title',
925 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
927 $mockCache = $this->getMockCache();
928 $mockCache->expects( $this->never() )->method( 'get' );
929 $mockCache->expects( $this->never() )->method( 'delete' );
931 $store = $this->newWatchedItemStore(
932 $this->getMockLBFactory( $mockDb ),
933 $this->getMockJobQueueGroup(),
935 $this->getMockReadOnlyMode()
938 $store->duplicateAllAssociatedEntries(
939 new TitleValue( 0, 'Old_Title' ),
940 new TitleValue( 0, 'New_Title' )
944 public function provideLinkTargetPairs() {
946 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
947 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
952 * @dataProvider provideLinkTargetPairs
954 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
955 LinkTarget
$oldTarget,
956 LinkTarget
$newTarget
959 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
962 $mockDb = $this->getMockDb();
963 $mockDb->expects( $this->at( 0 ) )
969 'wl_notificationtimestamp',
972 'wl_namespace' => $oldTarget->getNamespace(),
973 'wl_title' => $oldTarget->getDBkey(),
976 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
977 $mockDb->expects( $this->at( 1 ) )
978 ->method( 'replace' )
981 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
985 'wl_namespace' => $newTarget->getNamespace(),
986 'wl_title' => $newTarget->getDBkey(),
987 'wl_notificationtimestamp' => '20151212010101',
990 $this->isType( 'string' )
992 $mockDb->expects( $this->at( 2 ) )
998 'wl_notificationtimestamp',
1001 'wl_namespace' => $oldTarget->getNamespace() +
1,
1002 'wl_title' => $oldTarget->getDBkey(),
1005 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1006 $mockDb->expects( $this->at( 3 ) )
1007 ->method( 'replace' )
1010 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1014 'wl_namespace' => $newTarget->getNamespace() +
1,
1015 'wl_title' => $newTarget->getDBkey(),
1016 'wl_notificationtimestamp' => '20151212010101',
1019 $this->isType( 'string' )
1022 $mockCache = $this->getMockCache();
1023 $mockCache->expects( $this->never() )->method( 'get' );
1024 $mockCache->expects( $this->never() )->method( 'delete' );
1026 $store = $this->newWatchedItemStore(
1027 $this->getMockLBFactory( $mockDb ),
1028 $this->getMockJobQueueGroup(),
1030 $this->getMockReadOnlyMode()
1033 $store->duplicateAllAssociatedEntries(
1039 public function testAddWatch_nonAnonymousUser() {
1040 $mockDb = $this->getMockDb();
1041 $mockDb->expects( $this->once() )
1042 ->method( 'insert' )
1048 'wl_namespace' => 0,
1049 'wl_title' => 'Some_Page',
1050 'wl_notificationtimestamp' => null,
1055 $mockCache = $this->getMockCache();
1056 $mockCache->expects( $this->once() )
1057 ->method( 'delete' )
1058 ->with( '0:Some_Page:1' );
1060 $store = $this->newWatchedItemStore(
1061 $this->getMockLBFactory( $mockDb ),
1062 $this->getMockJobQueueGroup(),
1064 $this->getMockReadOnlyMode()
1068 new UserIdentityValue( 1, 'MockUser', 0 ),
1069 new TitleValue( 0, 'Some_Page' )
1073 public function testAddWatch_anonymousUser() {
1074 $mockDb = $this->getMockDb();
1075 $mockDb->expects( $this->never() )
1076 ->method( 'insert' );
1078 $mockCache = $this->getMockCache();
1079 $mockCache->expects( $this->never() )
1080 ->method( 'delete' );
1082 $store = $this->newWatchedItemStore(
1083 $this->getMockLBFactory( $mockDb ),
1084 $this->getMockJobQueueGroup(),
1086 $this->getMockReadOnlyMode()
1090 new UserIdentityValue( 0, 'AnonUser', 0 ),
1091 new TitleValue( 0, 'Some_Page' )
1095 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1096 $store = $this->newWatchedItemStore(
1097 $this->getMockLBFactory( $this->getMockDb() ),
1098 $this->getMockJobQueueGroup(),
1099 $this->getMockCache(),
1100 $this->getMockReadOnlyMode( true )
1104 $store->addWatchBatchForUser(
1105 new UserIdentityValue( 1, 'MockUser', 0 ),
1106 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1111 public function testAddWatchBatchForUser_nonAnonymousUser() {
1112 $mockDb = $this->getMockDb();
1113 $mockDb->expects( $this->once() )
1114 ->method( 'insert' )
1120 'wl_namespace' => 0,
1121 'wl_title' => 'Some_Page',
1122 'wl_notificationtimestamp' => null,
1126 'wl_namespace' => 1,
1127 'wl_title' => 'Some_Page',
1128 'wl_notificationtimestamp' => null,
1133 $mockDb->expects( $this->once() )
1134 ->method( 'affectedRows' )
1137 $mockCache = $this->getMockCache();
1138 $mockCache->expects( $this->exactly( 2 ) )
1139 ->method( 'delete' );
1140 $mockCache->expects( $this->at( 1 ) )
1141 ->method( 'delete' )
1142 ->with( '0:Some_Page:1' );
1143 $mockCache->expects( $this->at( 3 ) )
1144 ->method( 'delete' )
1145 ->with( '1:Some_Page:1' );
1147 $store = $this->newWatchedItemStore(
1148 $this->getMockLBFactory( $mockDb ),
1149 $this->getMockJobQueueGroup(),
1151 $this->getMockReadOnlyMode()
1154 $mockUser = new UserIdentityValue( 1, 'MockUser', 0 );
1157 $store->addWatchBatchForUser(
1159 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1164 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1165 $mockDb = $this->getMockDb();
1166 $mockDb->expects( $this->never() )
1167 ->method( 'insert' );
1169 $mockCache = $this->getMockCache();
1170 $mockCache->expects( $this->never() )
1171 ->method( 'delete' );
1173 $store = $this->newWatchedItemStore(
1174 $this->getMockLBFactory( $mockDb ),
1175 $this->getMockJobQueueGroup(),
1177 $this->getMockReadOnlyMode()
1181 $store->addWatchBatchForUser(
1182 new UserIdentityValue( 0, 'AnonUser', 0 ),
1183 [ new TitleValue( 0, 'Other_Page' ) ]
1188 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1189 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1190 $mockDb = $this->getMockDb();
1191 $mockDb->expects( $this->never() )
1192 ->method( 'insert' );
1194 $mockCache = $this->getMockCache();
1195 $mockCache->expects( $this->never() )
1196 ->method( 'delete' );
1198 $store = $this->newWatchedItemStore(
1199 $this->getMockLBFactory( $mockDb ),
1200 $this->getMockJobQueueGroup(),
1202 $this->getMockReadOnlyMode()
1206 $store->addWatchBatchForUser( $user, [] )
1210 public function testLoadWatchedItem_existingItem() {
1211 $mockDb = $this->getMockDb();
1212 $mockDb->expects( $this->once() )
1213 ->method( 'selectRow' )
1216 'wl_notificationtimestamp',
1219 'wl_namespace' => 0,
1220 'wl_title' => 'SomeDbKey',
1223 ->will( $this->returnValue(
1224 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1227 $mockCache = $this->getMockCache();
1228 $mockCache->expects( $this->once() )
1234 $store = $this->newWatchedItemStore(
1235 $this->getMockLBFactory( $mockDb ),
1236 $this->getMockJobQueueGroup(),
1238 $this->getMockReadOnlyMode()
1241 $watchedItem = $store->loadWatchedItem(
1242 new UserIdentityValue( 1, 'MockUser', 0 ),
1243 new TitleValue( 0, 'SomeDbKey' )
1245 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1246 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1247 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1248 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1251 public function testLoadWatchedItem_noItem() {
1252 $mockDb = $this->getMockDb();
1253 $mockDb->expects( $this->once() )
1254 ->method( 'selectRow' )
1257 'wl_notificationtimestamp',
1260 'wl_namespace' => 0,
1261 'wl_title' => 'SomeDbKey',
1264 ->will( $this->returnValue( [] ) );
1266 $mockCache = $this->getMockCache();
1267 $mockCache->expects( $this->never() )->method( 'get' );
1268 $mockCache->expects( $this->never() )->method( 'delete' );
1270 $store = $this->newWatchedItemStore(
1271 $this->getMockLBFactory( $mockDb ),
1272 $this->getMockJobQueueGroup(),
1274 $this->getMockReadOnlyMode()
1278 $store->loadWatchedItem(
1279 new UserIdentityValue( 1, 'MockUser', 0 ),
1280 new TitleValue( 0, 'SomeDbKey' )
1285 public function testLoadWatchedItem_anonymousUser() {
1286 $mockDb = $this->getMockDb();
1287 $mockDb->expects( $this->never() )
1288 ->method( 'selectRow' );
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 new UserIdentityValue( 0, 'AnonUser', 0 ),
1304 new TitleValue( 0, 'SomeDbKey' )
1309 public function testRemoveWatch_existingItem() {
1310 $mockDb = $this->getMockDb();
1311 $mockDb->expects( $this->once() )
1312 ->method( 'delete' )
1318 'wl_namespace' => 0,
1319 'wl_title' => [ 'SomeDbKey' ],
1326 'wl_namespace' => 1,
1327 'wl_title' => [ 'SomeDbKey' ],
1331 $mockDb->expects( $this->exactly( 1 ) )
1332 ->method( 'affectedRows' )
1335 $mockCache = $this->getMockCache();
1336 $mockCache->expects( $this->never() )->method( 'get' );
1337 $mockCache->expects( $this->once() )
1338 ->method( 'delete' )
1340 [ '0:SomeDbKey:1' ],
1344 $store = $this->newWatchedItemStore(
1345 $this->getMockLBFactory( $mockDb ),
1346 $this->getMockJobQueueGroup(),
1348 $this->getMockReadOnlyMode()
1352 $store->removeWatch(
1353 new UserIdentityValue( 1, 'MockUser', 0 ),
1354 new TitleValue( 0, 'SomeDbKey' )
1359 public function testRemoveWatch_noItem() {
1360 $mockDb = $this->getMockDb();
1361 $mockDb->expects( $this->once() )
1362 ->method( 'delete' )
1368 'wl_namespace' => 0,
1369 'wl_title' => [ 'SomeDbKey' ],
1376 'wl_namespace' => 1,
1377 'wl_title' => [ 'SomeDbKey' ],
1382 $mockDb->expects( $this->once() )
1383 ->method( 'affectedRows' )
1386 $mockCache = $this->getMockCache();
1387 $mockCache->expects( $this->never() )->method( 'get' );
1388 $mockCache->expects( $this->once() )
1389 ->method( 'delete' )
1391 [ '0:SomeDbKey:1' ],
1395 $store = $this->newWatchedItemStore(
1396 $this->getMockLBFactory( $mockDb ),
1397 $this->getMockJobQueueGroup(),
1399 $this->getMockReadOnlyMode()
1403 $store->removeWatch(
1404 new UserIdentityValue( 1, 'MockUser', 0 ),
1405 new TitleValue( 0, 'SomeDbKey' )
1410 public function testRemoveWatch_anonymousUser() {
1411 $mockDb = $this->getMockDb();
1412 $mockDb->expects( $this->never() )
1413 ->method( 'delete' );
1415 $mockCache = $this->getMockCache();
1416 $mockCache->expects( $this->never() )->method( 'get' );
1417 $mockCache->expects( $this->never() )
1418 ->method( 'delete' );
1420 $store = $this->newWatchedItemStore(
1421 $this->getMockLBFactory( $mockDb ),
1422 $this->getMockJobQueueGroup(),
1424 $this->getMockReadOnlyMode()
1428 $store->removeWatch(
1429 new UserIdentityValue( 0, 'AnonUser', 0 ),
1430 new TitleValue( 0, 'SomeDbKey' )
1435 public function testGetWatchedItem_existingItem() {
1436 $mockDb = $this->getMockDb();
1437 $mockDb->expects( $this->once() )
1438 ->method( 'selectRow' )
1441 'wl_notificationtimestamp',
1444 'wl_namespace' => 0,
1445 'wl_title' => 'SomeDbKey',
1448 ->will( $this->returnValue(
1449 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1452 $mockCache = $this->getMockCache();
1453 $mockCache->expects( $this->never() )->method( 'delete' );
1454 $mockCache->expects( $this->once() )
1459 ->will( $this->returnValue( null ) );
1460 $mockCache->expects( $this->once() )
1466 $store = $this->newWatchedItemStore(
1467 $this->getMockLBFactory( $mockDb ),
1468 $this->getMockJobQueueGroup(),
1470 $this->getMockReadOnlyMode()
1473 $watchedItem = $store->getWatchedItem(
1474 new UserIdentityValue( 1, 'MockUser', 0 ),
1475 new TitleValue( 0, 'SomeDbKey' )
1477 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1478 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1479 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1480 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1483 public function testGetWatchedItem_cachedItem() {
1484 $mockDb = $this->getMockDb();
1485 $mockDb->expects( $this->never() )
1486 ->method( 'selectRow' );
1488 $mockUser = new UserIdentityValue( 1, 'MockUser', 0 );
1489 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1490 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1492 $mockCache = $this->getMockCache();
1493 $mockCache->expects( $this->never() )->method( 'delete' );
1494 $mockCache->expects( $this->never() )->method( 'set' );
1495 $mockCache->expects( $this->once() )
1500 ->will( $this->returnValue( $cachedItem ) );
1502 $store = $this->newWatchedItemStore(
1503 $this->getMockLBFactory( $mockDb ),
1504 $this->getMockJobQueueGroup(),
1506 $this->getMockReadOnlyMode()
1509 $this->assertEquals(
1511 $store->getWatchedItem(
1518 public function testGetWatchedItem_noItem() {
1519 $mockDb = $this->getMockDb();
1520 $mockDb->expects( $this->once() )
1521 ->method( 'selectRow' )
1524 'wl_notificationtimestamp',
1527 'wl_namespace' => 0,
1528 'wl_title' => 'SomeDbKey',
1531 ->will( $this->returnValue( [] ) );
1533 $mockCache = $this->getMockCache();
1534 $mockCache->expects( $this->never() )->method( 'set' );
1535 $mockCache->expects( $this->never() )->method( 'delete' );
1536 $mockCache->expects( $this->once() )
1538 ->with( '0:SomeDbKey:1' )
1539 ->will( $this->returnValue( false ) );
1541 $store = $this->newWatchedItemStore(
1542 $this->getMockLBFactory( $mockDb ),
1543 $this->getMockJobQueueGroup(),
1545 $this->getMockReadOnlyMode()
1549 $store->getWatchedItem(
1550 new UserIdentityValue( 1, 'MockUser', 0 ),
1551 new TitleValue( 0, 'SomeDbKey' )
1556 public function testGetWatchedItem_anonymousUser() {
1557 $mockDb = $this->getMockDb();
1558 $mockDb->expects( $this->never() )
1559 ->method( 'selectRow' );
1561 $mockCache = $this->getMockCache();
1562 $mockCache->expects( $this->never() )->method( 'set' );
1563 $mockCache->expects( $this->never() )->method( 'get' );
1564 $mockCache->expects( $this->never() )->method( 'delete' );
1566 $store = $this->newWatchedItemStore(
1567 $this->getMockLBFactory( $mockDb ),
1568 $this->getMockJobQueueGroup(),
1570 $this->getMockReadOnlyMode()
1574 $store->getWatchedItem(
1575 new UserIdentityValue( 0, 'AnonUser', 0 ),
1576 new TitleValue( 0, 'SomeDbKey' )
1581 public function testGetWatchedItemsForUser() {
1582 $mockDb = $this->getMockDb();
1583 $mockDb->expects( $this->once() )
1584 ->method( 'select' )
1587 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1590 ->will( $this->returnValue( [
1591 $this->getFakeRow( [
1592 'wl_namespace' => 0,
1593 'wl_title' => 'Foo1',
1594 'wl_notificationtimestamp' => '20151212010101',
1596 $this->getFakeRow( [
1597 'wl_namespace' => 1,
1598 'wl_title' => 'Foo2',
1599 'wl_notificationtimestamp' => null,
1603 $mockCache = $this->getMockCache();
1604 $mockCache->expects( $this->never() )->method( 'delete' );
1605 $mockCache->expects( $this->never() )->method( 'get' );
1606 $mockCache->expects( $this->never() )->method( 'set' );
1608 $store = $this->newWatchedItemStore(
1609 $this->getMockLBFactory( $mockDb ),
1610 $this->getMockJobQueueGroup(),
1612 $this->getMockReadOnlyMode()
1614 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1616 $watchedItems = $store->getWatchedItemsForUser( $user );
1618 $this->assertInternalType( 'array', $watchedItems );
1619 $this->assertCount( 2, $watchedItems );
1620 foreach ( $watchedItems as $watchedItem ) {
1621 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1623 $this->assertEquals(
1624 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1627 $this->assertEquals(
1628 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1633 public function provideDbTypes() {
1635 [ false, DB_REPLICA
],
1636 [ true, DB_MASTER
],
1641 * @dataProvider provideDbTypes
1643 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1644 $mockDb = $this->getMockDb();
1645 $mockCache = $this->getMockCache();
1646 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1647 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1649 $mockDb->expects( $this->once() )
1650 ->method( 'select' )
1653 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1655 $this->isType( 'string' ),
1656 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1658 ->will( $this->returnValue( [] ) );
1660 $store = $this->newWatchedItemStore(
1662 $this->getMockJobQueueGroup(),
1664 $this->getMockReadOnlyMode()
1667 $watchedItems = $store->getWatchedItemsForUser(
1669 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1671 $this->assertEquals( [], $watchedItems );
1674 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1675 $store = $this->newWatchedItemStore(
1676 $this->getMockLBFactory( $this->getMockDb() ),
1677 $this->getMockJobQueueGroup(),
1678 $this->getMockCache(),
1679 $this->getMockReadOnlyMode()
1682 $this->setExpectedException( InvalidArgumentException
::class );
1683 $store->getWatchedItemsForUser(
1684 new UserIdentityValue( 1, 'MockUser', 0 ),
1689 public function testIsWatchedItem_existingItem() {
1690 $mockDb = $this->getMockDb();
1691 $mockDb->expects( $this->once() )
1692 ->method( 'selectRow' )
1695 'wl_notificationtimestamp',
1698 'wl_namespace' => 0,
1699 'wl_title' => 'SomeDbKey',
1702 ->will( $this->returnValue(
1703 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1706 $mockCache = $this->getMockCache();
1707 $mockCache->expects( $this->never() )->method( 'delete' );
1708 $mockCache->expects( $this->once() )
1710 ->with( '0:SomeDbKey:1' )
1711 ->will( $this->returnValue( false ) );
1712 $mockCache->expects( $this->once() )
1718 $store = $this->newWatchedItemStore(
1719 $this->getMockLBFactory( $mockDb ),
1720 $this->getMockJobQueueGroup(),
1722 $this->getMockReadOnlyMode()
1727 new UserIdentityValue( 1, 'MockUser', 0 ),
1728 new TitleValue( 0, 'SomeDbKey' )
1733 public function testIsWatchedItem_noItem() {
1734 $mockDb = $this->getMockDb();
1735 $mockDb->expects( $this->once() )
1736 ->method( 'selectRow' )
1739 'wl_notificationtimestamp',
1742 'wl_namespace' => 0,
1743 'wl_title' => 'SomeDbKey',
1746 ->will( $this->returnValue( [] ) );
1748 $mockCache = $this->getMockCache();
1749 $mockCache->expects( $this->never() )->method( 'set' );
1750 $mockCache->expects( $this->never() )->method( 'delete' );
1751 $mockCache->expects( $this->once() )
1753 ->with( '0:SomeDbKey:1' )
1754 ->will( $this->returnValue( false ) );
1756 $store = $this->newWatchedItemStore(
1757 $this->getMockLBFactory( $mockDb ),
1758 $this->getMockJobQueueGroup(),
1760 $this->getMockReadOnlyMode()
1765 new UserIdentityValue( 1, 'MockUser', 0 ),
1766 new TitleValue( 0, 'SomeDbKey' )
1771 public function testIsWatchedItem_anonymousUser() {
1772 $mockDb = $this->getMockDb();
1773 $mockDb->expects( $this->never() )
1774 ->method( 'selectRow' );
1776 $mockCache = $this->getMockCache();
1777 $mockCache->expects( $this->never() )->method( 'set' );
1778 $mockCache->expects( $this->never() )->method( 'get' );
1779 $mockCache->expects( $this->never() )->method( 'delete' );
1781 $store = $this->newWatchedItemStore(
1782 $this->getMockLBFactory( $mockDb ),
1783 $this->getMockJobQueueGroup(),
1785 $this->getMockReadOnlyMode()
1790 new UserIdentityValue( 0, 'AnonUser', 0 ),
1791 new TitleValue( 0, 'SomeDbKey' )
1796 public function testGetNotificationTimestampsBatch() {
1798 new TitleValue( 0, 'SomeDbKey' ),
1799 new TitleValue( 1, 'AnotherDbKey' ),
1802 $mockDb = $this->getMockDb();
1804 $this->getFakeRow( [
1805 'wl_namespace' => '0',
1806 'wl_title' => 'SomeDbKey',
1807 'wl_notificationtimestamp' => '20151212010101',
1811 'wl_namespace' => '1',
1812 'wl_title' => 'AnotherDbKey',
1813 'wl_notificationtimestamp' => null,
1818 $mockDb->expects( $this->once() )
1819 ->method( 'makeWhereFrom2d' )
1821 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1822 $this->isType( 'string' ),
1823 $this->isType( 'string' )
1825 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1826 $mockDb->expects( $this->once() )
1827 ->method( 'select' )
1830 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1832 'makeWhereFrom2d return value',
1835 $this->isType( 'string' )
1837 ->will( $this->returnValue( $dbResult ) );
1839 $mockCache = $this->getMockCache();
1840 $mockCache->expects( $this->exactly( 2 ) )
1843 [ '0:SomeDbKey:1' ],
1844 [ '1:AnotherDbKey:1' ]
1846 ->will( $this->returnValue( null ) );
1847 $mockCache->expects( $this->never() )->method( 'set' );
1848 $mockCache->expects( $this->never() )->method( 'delete' );
1850 $store = $this->newWatchedItemStore(
1851 $this->getMockLBFactory( $mockDb ),
1852 $this->getMockJobQueueGroup(),
1854 $this->getMockReadOnlyMode()
1857 $this->assertEquals(
1859 0 => [ 'SomeDbKey' => '20151212010101', ],
1860 1 => [ 'AnotherDbKey' => null, ],
1862 $store->getNotificationTimestampsBatch(
1863 new UserIdentityValue( 1, 'MockUser', 0 ), $targets )
1867 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1869 new TitleValue( 0, 'OtherDbKey' ),
1872 $mockDb = $this->getMockDb();
1874 $mockDb->expects( $this->once() )
1875 ->method( 'makeWhereFrom2d' )
1877 [ [ 'OtherDbKey' => 1 ] ],
1878 $this->isType( 'string' ),
1879 $this->isType( 'string' )
1881 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1882 $mockDb->expects( $this->once() )
1883 ->method( 'select' )
1886 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1888 'makeWhereFrom2d return value',
1891 $this->isType( 'string' )
1893 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1895 $mockCache = $this->getMockCache();
1896 $mockCache->expects( $this->once() )
1898 ->with( '0:OtherDbKey:1' )
1899 ->will( $this->returnValue( null ) );
1900 $mockCache->expects( $this->never() )->method( 'set' );
1901 $mockCache->expects( $this->never() )->method( 'delete' );
1903 $store = $this->newWatchedItemStore(
1904 $this->getMockLBFactory( $mockDb ),
1905 $this->getMockJobQueueGroup(),
1907 $this->getMockReadOnlyMode()
1910 $this->assertEquals(
1912 0 => [ 'OtherDbKey' => false, ],
1914 $store->getNotificationTimestampsBatch(
1915 new UserIdentityValue( 1, 'MockUser', 0 ), $targets )
1919 public function testGetNotificationTimestampsBatch_cachedItem() {
1921 new TitleValue( 0, 'SomeDbKey' ),
1922 new TitleValue( 1, 'AnotherDbKey' ),
1925 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1926 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1928 $mockDb = $this->getMockDb();
1930 $mockDb->expects( $this->once() )
1931 ->method( 'makeWhereFrom2d' )
1933 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1934 $this->isType( 'string' ),
1935 $this->isType( 'string' )
1937 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1938 $mockDb->expects( $this->once() )
1939 ->method( 'select' )
1942 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1944 'makeWhereFrom2d return value',
1947 $this->isType( 'string' )
1949 ->will( $this->returnValue( [
1951 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1955 $mockCache = $this->getMockCache();
1956 $mockCache->expects( $this->at( 1 ) )
1958 ->with( '0:SomeDbKey:1' )
1959 ->will( $this->returnValue( $cachedItem ) );
1960 $mockCache->expects( $this->at( 3 ) )
1962 ->with( '1:AnotherDbKey:1' )
1963 ->will( $this->returnValue( null ) );
1964 $mockCache->expects( $this->never() )->method( 'set' );
1965 $mockCache->expects( $this->never() )->method( 'delete' );
1967 $store = $this->newWatchedItemStore(
1968 $this->getMockLBFactory( $mockDb ),
1969 $this->getMockJobQueueGroup(),
1971 $this->getMockReadOnlyMode()
1974 $this->assertEquals(
1976 0 => [ 'SomeDbKey' => '20151212010101', ],
1977 1 => [ 'AnotherDbKey' => null, ],
1979 $store->getNotificationTimestampsBatch( $user, $targets )
1983 public function testGetNotificationTimestampsBatch_allItemsCached() {
1985 new TitleValue( 0, 'SomeDbKey' ),
1986 new TitleValue( 1, 'AnotherDbKey' ),
1989 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1991 new WatchedItem( $user, $targets[0], '20151212010101' ),
1992 new WatchedItem( $user, $targets[1], null ),
1994 $mockDb = $this->getMockDb();
1995 $mockDb->expects( $this->never() )->method( $this->anything() );
1997 $mockCache = $this->getMockCache();
1998 $mockCache->expects( $this->at( 1 ) )
2000 ->with( '0:SomeDbKey:1' )
2001 ->will( $this->returnValue( $cachedItems[0] ) );
2002 $mockCache->expects( $this->at( 3 ) )
2004 ->with( '1:AnotherDbKey:1' )
2005 ->will( $this->returnValue( $cachedItems[1] ) );
2006 $mockCache->expects( $this->never() )->method( 'set' );
2007 $mockCache->expects( $this->never() )->method( 'delete' );
2009 $store = $this->newWatchedItemStore(
2010 $this->getMockLBFactory( $mockDb ),
2011 $this->getMockJobQueueGroup(),
2013 $this->getMockReadOnlyMode()
2016 $this->assertEquals(
2018 0 => [ 'SomeDbKey' => '20151212010101', ],
2019 1 => [ 'AnotherDbKey' => null, ],
2021 $store->getNotificationTimestampsBatch( $user, $targets )
2025 public function testGetNotificationTimestampsBatch_anonymousUser() {
2027 new TitleValue( 0, 'SomeDbKey' ),
2028 new TitleValue( 1, 'AnotherDbKey' ),
2031 $mockDb = $this->getMockDb();
2032 $mockDb->expects( $this->never() )->method( $this->anything() );
2034 $mockCache = $this->getMockCache();
2035 $mockCache->expects( $this->never() )->method( $this->anything() );
2037 $store = $this->newWatchedItemStore(
2038 $this->getMockLBFactory( $mockDb ),
2039 $this->getMockJobQueueGroup(),
2041 $this->getMockReadOnlyMode()
2044 $this->assertEquals(
2046 0 => [ 'SomeDbKey' => false, ],
2047 1 => [ 'AnotherDbKey' => false, ],
2049 $store->getNotificationTimestampsBatch(
2050 new UserIdentityValue( 0, 'AnonUser', 0 ), $targets )
2054 public function testResetNotificationTimestamp_anonymousUser() {
2055 $mockDb = $this->getMockDb();
2056 $mockDb->expects( $this->never() )
2057 ->method( 'selectRow' );
2059 $mockCache = $this->getMockCache();
2060 $mockCache->expects( $this->never() )->method( 'get' );
2061 $mockCache->expects( $this->never() )->method( 'set' );
2062 $mockCache->expects( $this->never() )->method( 'delete' );
2064 $store = $this->newWatchedItemStore(
2065 $this->getMockLBFactory( $mockDb ),
2066 $this->getMockJobQueueGroup(),
2068 $this->getMockReadOnlyMode()
2072 $store->resetNotificationTimestamp(
2073 new UserIdentityValue( 0, 'AnonUser', 0 ),
2074 Title
::newFromText( 'SomeDbKey' )
2079 public function testResetNotificationTimestamp_noItem() {
2080 $mockDb = $this->getMockDb();
2081 $mockDb->expects( $this->once() )
2082 ->method( 'selectRow' )
2085 'wl_notificationtimestamp',
2088 'wl_namespace' => 0,
2089 'wl_title' => 'SomeDbKey',
2092 ->will( $this->returnValue( [] ) );
2094 $mockCache = $this->getMockCache();
2095 $mockCache->expects( $this->never() )->method( 'get' );
2096 $mockCache->expects( $this->never() )->method( 'set' );
2097 $mockCache->expects( $this->never() )->method( 'delete' );
2099 $store = $this->newWatchedItemStore(
2100 $this->getMockLBFactory( $mockDb ),
2101 $this->getMockJobQueueGroup(),
2103 $this->getMockReadOnlyMode()
2107 $store->resetNotificationTimestamp(
2108 new UserIdentityValue( 1, 'MockUser', 0 ),
2109 Title
::newFromText( 'SomeDbKey' )
2114 public function testResetNotificationTimestamp_item() {
2115 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2116 $title = Title
::newFromText( 'SomeDbKey' );
2118 $mockDb = $this->getMockDb();
2119 $mockDb->expects( $this->once() )
2120 ->method( 'selectRow' )
2123 'wl_notificationtimestamp',
2126 'wl_namespace' => 0,
2127 'wl_title' => 'SomeDbKey',
2130 ->will( $this->returnValue(
2131 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2134 $mockCache = $this->getMockCache();
2135 $mockCache->expects( $this->never() )->method( 'get' );
2136 $mockCache->expects( $this->once() )
2140 $this->isInstanceOf( WatchedItem
::class )
2142 $mockCache->expects( $this->once() )
2143 ->method( 'delete' )
2144 ->with( '0:SomeDbKey:1' );
2146 $mockQueueGroup = $this->getMockJobQueueGroup();
2147 $mockQueueGroup->expects( $this->once() )
2148 ->method( 'lazyPush' )
2149 ->willReturnCallback( function ( ActivityUpdateJob
$job ) {
2153 $store = $this->newWatchedItemStore(
2154 $this->getMockLBFactory( $mockDb ),
2157 $this->getMockReadOnlyMode()
2161 $store->resetNotificationTimestamp(
2168 public function testResetNotificationTimestamp_noItemForced() {
2169 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2170 $title = Title
::newFromText( 'SomeDbKey' );
2172 $mockDb = $this->getMockDb();
2173 $mockDb->expects( $this->never() )
2174 ->method( 'selectRow' );
2176 $mockCache = $this->getMockCache();
2177 $mockCache->expects( $this->never() )->method( 'get' );
2178 $mockCache->expects( $this->never() )->method( 'set' );
2179 $mockCache->expects( $this->once() )
2180 ->method( 'delete' )
2181 ->with( '0:SomeDbKey:1' );
2183 $mockQueueGroup = $this->getMockJobQueueGroup();
2184 $store = $this->newWatchedItemStore(
2185 $this->getMockLBFactory( $mockDb ),
2188 $this->getMockReadOnlyMode()
2191 $mockQueueGroup->expects( $this->any() )
2192 ->method( 'lazyPush' )
2193 ->will( $this->returnCallback( function ( ActivityUpdateJob
$job ) {
2198 $store->resetNotificationTimestamp(
2207 * @param string $text
2210 * @return PHPUnit_Framework_MockObject_MockObject|Title
2212 private function getMockTitle( $text, $ns = 0 ) {
2213 $title = $this->createMock( Title
::class );
2214 $title->expects( $this->any() )
2215 ->method( 'getText' )
2216 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2217 $title->expects( $this->any() )
2218 ->method( 'getDbKey' )
2219 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2220 $title->expects( $this->any() )
2221 ->method( 'getNamespace' )
2222 ->will( $this->returnValue( $ns ) );
2226 private function verifyCallbackJob(
2227 ActivityUpdateJob
$job,
2228 LinkTarget
$expectedTitle,
2230 callable
$notificationTimestampCondition
2232 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2233 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2235 $jobParams = $job->getParams();
2236 $this->assertArrayHasKey( 'type', $jobParams );
2237 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2238 $this->assertArrayHasKey( 'userid', $jobParams );
2239 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2240 $this->assertArrayHasKey( 'notifTime', $jobParams );
2241 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2244 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2245 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2247 $title = $this->getMockTitle( 'SomeTitle' );
2248 $title->expects( $this->once() )
2249 ->method( 'getNextRevisionID' )
2251 ->will( $this->returnValue( false ) );
2253 $mockDb = $this->getMockDb();
2254 $mockDb->expects( $this->never() )
2255 ->method( 'selectRow' );
2257 $mockCache = $this->getMockCache();
2258 $mockCache->expects( $this->never() )->method( 'get' );
2259 $mockCache->expects( $this->never() )->method( 'set' );
2260 $mockCache->expects( $this->once() )
2261 ->method( 'delete' )
2262 ->with( '0:SomeTitle:1' );
2264 $mockQueueGroup = $this->getMockJobQueueGroup();
2265 $store = $this->newWatchedItemStore(
2266 $this->getMockLBFactory( $mockDb ),
2269 $this->getMockReadOnlyMode()
2272 $mockQueueGroup->expects( $this->any() )
2273 ->method( 'lazyPush' )
2274 ->will( $this->returnCallback(
2275 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2276 $this->verifyCallbackJob(
2280 function ( $time ) {
2281 return $time === null;
2288 $store->resetNotificationTimestamp(
2297 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2298 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2300 $title = $this->getMockTitle( 'SomeDbKey' );
2301 $title->expects( $this->once() )
2302 ->method( 'getNextRevisionID' )
2304 ->will( $this->returnValue( 33 ) );
2306 $mockDb = $this->getMockDb();
2307 $mockDb->expects( $this->once() )
2308 ->method( 'selectRow' )
2311 'wl_notificationtimestamp',
2314 'wl_namespace' => 0,
2315 'wl_title' => 'SomeDbKey',
2318 ->will( $this->returnValue(
2319 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2322 $mockCache = $this->getMockCache();
2323 $mockCache->expects( $this->never() )->method( 'get' );
2324 $mockCache->expects( $this->once() )
2326 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2327 $mockCache->expects( $this->once() )
2328 ->method( 'delete' )
2329 ->with( '0:SomeDbKey:1' );
2331 $mockQueueGroup = $this->getMockJobQueueGroup();
2332 $store = $this->newWatchedItemStore(
2333 $this->getMockLBFactory( $mockDb ),
2336 $this->getMockReadOnlyMode()
2339 $mockQueueGroup->expects( $this->any() )
2340 ->method( 'lazyPush' )
2341 ->will( $this->returnCallback(
2342 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2343 $this->verifyCallbackJob(
2347 function ( $time ) {
2348 return $time !== null && $time > '20151212010101';
2354 $getTimestampCallCounter = 0;
2355 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2356 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2357 $getTimestampCallCounter++
;
2358 $this->assertEquals( $title, $titleParam );
2359 $this->assertEquals( $oldid, $oldidParam );
2364 $store->resetNotificationTimestamp(
2371 $this->assertEquals( 2, $getTimestampCallCounter );
2373 ScopedCallback
::consume( $scopedOverrideRevision );
2376 public function testResetNotificationTimestamp_notWatchedPageForced() {
2377 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2379 $title = $this->getMockTitle( 'SomeDbKey' );
2380 $title->expects( $this->once() )
2381 ->method( 'getNextRevisionID' )
2383 ->will( $this->returnValue( 33 ) );
2385 $mockDb = $this->getMockDb();
2386 $mockDb->expects( $this->once() )
2387 ->method( 'selectRow' )
2390 'wl_notificationtimestamp',
2393 'wl_namespace' => 0,
2394 'wl_title' => 'SomeDbKey',
2397 ->will( $this->returnValue( false ) );
2399 $mockCache = $this->getMockCache();
2400 $mockCache->expects( $this->never() )->method( 'get' );
2401 $mockCache->expects( $this->never() )->method( 'set' );
2402 $mockCache->expects( $this->once() )
2403 ->method( 'delete' )
2404 ->with( '0:SomeDbKey:1' );
2406 $mockQueueGroup = $this->getMockJobQueueGroup();
2407 $store = $this->newWatchedItemStore(
2408 $this->getMockLBFactory( $mockDb ),
2411 $this->getMockReadOnlyMode()
2414 $mockQueueGroup->expects( $this->any() )
2415 ->method( 'lazyPush' )
2416 ->will( $this->returnCallback(
2417 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2418 $this->verifyCallbackJob(
2422 function ( $time ) {
2423 return $time === null;
2430 $store->resetNotificationTimestamp(
2439 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2440 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2442 $title = $this->getMockTitle( 'SomeDbKey' );
2443 $title->expects( $this->once() )
2444 ->method( 'getNextRevisionID' )
2446 ->will( $this->returnValue( 33 ) );
2448 $mockDb = $this->getMockDb();
2449 $mockDb->expects( $this->once() )
2450 ->method( 'selectRow' )
2453 'wl_notificationtimestamp',
2456 'wl_namespace' => 0,
2457 'wl_title' => 'SomeDbKey',
2460 ->will( $this->returnValue(
2461 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2464 $mockCache = $this->getMockCache();
2465 $mockCache->expects( $this->never() )->method( 'get' );
2466 $mockCache->expects( $this->once() )
2468 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2469 $mockCache->expects( $this->once() )
2470 ->method( 'delete' )
2471 ->with( '0:SomeDbKey:1' );
2473 $mockQueueGroup = $this->getMockJobQueueGroup();
2474 $store = $this->newWatchedItemStore(
2475 $this->getMockLBFactory( $mockDb ),
2478 $this->getMockReadOnlyMode()
2481 $mockQueueGroup->expects( $this->any() )
2482 ->method( 'lazyPush' )
2483 ->will( $this->returnCallback(
2484 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2485 $this->verifyCallbackJob(
2489 function ( $time ) {
2490 return $time === '30151212010101';
2496 $getTimestampCallCounter = 0;
2497 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2498 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2499 $getTimestampCallCounter++
;
2500 $this->assertEquals( $title, $titleParam );
2501 $this->assertEquals( $oldid, $oldidParam );
2506 $store->resetNotificationTimestamp(
2513 $this->assertEquals( 2, $getTimestampCallCounter );
2515 ScopedCallback
::consume( $scopedOverrideRevision );
2518 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2519 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2521 $title = $this->getMockTitle( 'SomeDbKey' );
2522 $title->expects( $this->once() )
2523 ->method( 'getNextRevisionID' )
2525 ->will( $this->returnValue( 33 ) );
2527 $mockDb = $this->getMockDb();
2528 $mockDb->expects( $this->once() )
2529 ->method( 'selectRow' )
2532 'wl_notificationtimestamp',
2535 'wl_namespace' => 0,
2536 'wl_title' => 'SomeDbKey',
2539 ->will( $this->returnValue(
2540 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2543 $mockCache = $this->getMockCache();
2544 $mockCache->expects( $this->never() )->method( 'get' );
2545 $mockCache->expects( $this->once() )
2547 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2548 $mockCache->expects( $this->once() )
2549 ->method( 'delete' )
2550 ->with( '0:SomeDbKey:1' );
2552 $mockQueueGroup = $this->getMockJobQueueGroup();
2553 $store = $this->newWatchedItemStore(
2554 $this->getMockLBFactory( $mockDb ),
2557 $this->getMockReadOnlyMode()
2560 $mockQueueGroup->expects( $this->any() )
2561 ->method( 'lazyPush' )
2562 ->will( $this->returnCallback(
2563 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2564 $this->verifyCallbackJob(
2568 function ( $time ) {
2569 return $time === false;
2575 $getTimestampCallCounter = 0;
2576 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2577 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2578 $getTimestampCallCounter++
;
2579 $this->assertEquals( $title, $titleParam );
2580 $this->assertEquals( $oldid, $oldidParam );
2585 $store->resetNotificationTimestamp(
2592 $this->assertEquals( 2, $getTimestampCallCounter );
2594 ScopedCallback
::consume( $scopedOverrideRevision );
2597 public function testSetNotificationTimestampsForUser_anonUser() {
2598 $store = $this->newWatchedItemStore(
2599 $this->getMockLBFactory( $this->getMockDb() ),
2600 $this->getMockJobQueueGroup(),
2601 $this->getMockCache(),
2602 $this->getMockReadOnlyMode()
2604 $this->assertFalse( $store->setNotificationTimestampsForUser(
2605 new UserIdentityValue( 0, 'AnonUser', 0 ), '' ) );
2608 public function testSetNotificationTimestampsForUser_allRows() {
2609 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2610 $timestamp = '20100101010101';
2612 $store = $this->newWatchedItemStore(
2613 $this->getMockLBFactory( $this->getMockDb() ),
2614 $this->getMockJobQueueGroup(),
2615 $this->getMockCache(),
2616 $this->getMockReadOnlyMode()
2619 // Note: This does not actually assert the job is correct
2620 $callableCallCounter = 0;
2621 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2622 $callableCallCounter++
;
2623 $this->assertInternalType( 'callable', $callable );
2625 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2628 $store->setNotificationTimestampsForUser( $user, $timestamp )
2630 $this->assertEquals( 1, $callableCallCounter );
2633 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2634 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2637 $store = $this->newWatchedItemStore(
2638 $this->getMockLBFactory( $this->getMockDb() ),
2639 $this->getMockJobQueueGroup(),
2640 $this->getMockCache(),
2641 $this->getMockReadOnlyMode()
2644 // Note: This does not actually assert the job is correct
2645 $callableCallCounter = 0;
2646 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2647 $callableCallCounter++
;
2648 $this->assertInternalType( 'callable', $callable );
2650 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2653 $store->setNotificationTimestampsForUser( $user, $timestamp )
2657 public function testSetNotificationTimestampsForUser_specificTargets() {
2658 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2659 $timestamp = '20100101010101';
2660 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2662 $mockDb = $this->getMockDb();
2663 $mockDb->expects( $this->once() )
2664 ->method( 'update' )
2667 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2668 [ 'wl_user' => 1, 'wl_namespace' => 0, 'wl_title' => [ 'Foo', 'Bar' ] ]
2670 ->will( $this->returnValue( true ) );
2671 $mockDb->expects( $this->exactly( 1 ) )
2672 ->method( 'timestamp' )
2673 ->will( $this->returnCallback( function ( $value ) {
2674 return 'TS' . $value . 'TS';
2676 $mockDb->expects( $this->once() )
2677 ->method( 'affectedRows' )
2678 ->will( $this->returnValue( 2 ) );
2680 $store = $this->newWatchedItemStore(
2681 $this->getMockLBFactory( $mockDb ),
2682 $this->getMockJobQueueGroup(),
2683 $this->getMockCache(),
2684 $this->getMockReadOnlyMode()
2688 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2692 public function testUpdateNotificationTimestamp_watchersExist() {
2693 $mockDb = $this->getMockDb();
2694 $mockDb->expects( $this->once() )
2695 ->method( 'selectFieldValues' )
2701 'wl_namespace' => 0,
2702 'wl_title' => 'SomeDbKey',
2703 'wl_notificationtimestamp IS NULL'
2706 ->will( $this->returnValue( [ '2', '3' ] ) );
2707 $mockDb->expects( $this->once() )
2708 ->method( 'update' )
2711 [ 'wl_notificationtimestamp' => null ],
2713 'wl_user' => [ 2, 3 ],
2714 'wl_namespace' => 0,
2715 'wl_title' => 'SomeDbKey',
2719 $mockCache = $this->getMockCache();
2720 $mockCache->expects( $this->never() )->method( 'set' );
2721 $mockCache->expects( $this->never() )->method( 'get' );
2722 $mockCache->expects( $this->never() )->method( 'delete' );
2724 $store = $this->newWatchedItemStore(
2725 $this->getMockLBFactory( $mockDb ),
2726 $this->getMockJobQueueGroup(),
2728 $this->getMockReadOnlyMode()
2731 $this->assertEquals(
2733 $store->updateNotificationTimestamp(
2734 new UserIdentityValue( 1, 'MockUser', 0 ),
2735 new TitleValue( 0, 'SomeDbKey' ),
2741 public function testUpdateNotificationTimestamp_noWatchers() {
2742 $mockDb = $this->getMockDb();
2743 $mockDb->expects( $this->once() )
2744 ->method( 'selectFieldValues' )
2750 'wl_namespace' => 0,
2751 'wl_title' => 'SomeDbKey',
2752 'wl_notificationtimestamp IS NULL'
2756 $this->returnValue( [] )
2758 $mockDb->expects( $this->never() )
2759 ->method( 'update' );
2761 $mockCache = $this->getMockCache();
2762 $mockCache->expects( $this->never() )->method( 'set' );
2763 $mockCache->expects( $this->never() )->method( 'get' );
2764 $mockCache->expects( $this->never() )->method( 'delete' );
2766 $store = $this->newWatchedItemStore(
2767 $this->getMockLBFactory( $mockDb ),
2768 $this->getMockJobQueueGroup(),
2770 $this->getMockReadOnlyMode()
2773 $watchers = $store->updateNotificationTimestamp(
2774 new UserIdentityValue( 1, 'MockUser', 0 ),
2775 new TitleValue( 0, 'SomeDbKey' ),
2778 $this->assertInternalType( 'array', $watchers );
2779 $this->assertEmpty( $watchers );
2782 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2783 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2784 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2786 $mockDb = $this->getMockDb();
2787 $mockDb->expects( $this->once() )
2788 ->method( 'selectRow' )
2789 ->will( $this->returnValue(
2790 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2792 $mockDb->expects( $this->once() )
2793 ->method( 'selectFieldValues' )
2795 $this->returnValue( [ '2', '3' ] )
2797 $mockDb->expects( $this->once() )
2798 ->method( 'update' );
2800 $mockCache = $this->getMockCache();
2801 $mockCache->expects( $this->once() )
2803 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2804 $mockCache->expects( $this->once() )
2806 ->with( '0:SomeDbKey:1' );
2807 $mockCache->expects( $this->once() )
2808 ->method( 'delete' )
2809 ->with( '0:SomeDbKey:1' );
2811 $store = $this->newWatchedItemStore(
2812 $this->getMockLBFactory( $mockDb ),
2813 $this->getMockJobQueueGroup(),
2815 $this->getMockReadOnlyMode()
2818 // This will add the item to the cache
2819 $store->getWatchedItem( $user, $titleValue );
2821 $store->updateNotificationTimestamp(
2822 new UserIdentityValue( 1, 'MockUser', 0 ),