2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LoadBalancer
;
4 use Wikimedia\ScopedCallback
;
9 * @covers WatchedItemStore
11 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
14 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
16 private function getMockDb() {
17 return $this->createMock( IDatabase
::class );
21 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
23 private function getMockLoadBalancer(
25 $expectedConnectionType = null
27 $mock = $this->getMockBuilder( LoadBalancer
::class )
28 ->disableOriginalConstructor()
30 if ( $expectedConnectionType !== null ) {
31 $mock->expects( $this->any() )
32 ->method( 'getConnectionRef' )
33 ->with( $expectedConnectionType )
34 ->will( $this->returnValue( $mockDb ) );
36 $mock->expects( $this->any() )
37 ->method( 'getConnectionRef' )
38 ->will( $this->returnValue( $mockDb ) );
44 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
46 private function getMockCache() {
47 $mock = $this->getMockBuilder( HashBagOStuff
::class )
48 ->disableOriginalConstructor()
50 $mock->expects( $this->any() )
52 ->will( $this->returnCallback( function () {
53 return implode( ':', func_get_args() );
59 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
61 private function getMockReadOnlyMode( $readOnly = false ) {
62 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
63 ->disableOriginalConstructor()
65 $mock->expects( $this->any() )
66 ->method( 'isReadOnly' )
67 ->will( $this->returnValue( $readOnly ) );
73 * @return PHPUnit_Framework_MockObject_MockObject|User
75 private function getMockNonAnonUserWithId( $id ) {
76 $mock = $this->createMock( User
::class );
77 $mock->expects( $this->any() )
79 ->will( $this->returnValue( false ) );
80 $mock->expects( $this->any() )
82 ->will( $this->returnValue( $id ) );
89 private function getAnonUser() {
90 return User
::newFromName( 'Anon_User' );
93 private function getFakeRow( array $rowValues ) {
94 $fakeRow = new stdClass();
95 foreach ( $rowValues as $valueName => $value ) {
96 $fakeRow->$valueName = $value;
101 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache,
102 ReadOnlyMode
$readOnlyMode
104 return new WatchedItemStore(
111 public function testCountWatchedItems() {
112 $user = $this->getMockNonAnonUserWithId( 1 );
114 $mockDb = $this->getMockDb();
115 $mockDb->expects( $this->exactly( 1 ) )
116 ->method( 'selectField' )
121 'wl_user' => $user->getId(),
123 $this->isType( 'string' )
125 ->will( $this->returnValue( '12' ) );
127 $mockCache = $this->getMockCache();
128 $mockCache->expects( $this->never() )->method( 'get' );
129 $mockCache->expects( $this->never() )->method( 'set' );
130 $mockCache->expects( $this->never() )->method( 'delete' );
132 $store = $this->newWatchedItemStore(
133 $this->getMockLoadBalancer( $mockDb ),
135 $this->getMockReadOnlyMode()
138 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
141 public function testCountWatchers() {
142 $titleValue = new TitleValue( 0, 'SomeDbKey' );
144 $mockDb = $this->getMockDb();
145 $mockDb->expects( $this->exactly( 1 ) )
146 ->method( 'selectField' )
151 'wl_namespace' => $titleValue->getNamespace(),
152 'wl_title' => $titleValue->getDBkey(),
154 $this->isType( 'string' )
156 ->will( $this->returnValue( '7' ) );
158 $mockCache = $this->getMockCache();
159 $mockCache->expects( $this->never() )->method( 'get' );
160 $mockCache->expects( $this->never() )->method( 'set' );
161 $mockCache->expects( $this->never() )->method( 'delete' );
163 $store = $this->newWatchedItemStore(
164 $this->getMockLoadBalancer( $mockDb ),
166 $this->getMockReadOnlyMode()
169 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
172 public function testCountWatchersMultiple() {
174 new TitleValue( 0, 'SomeDbKey' ),
175 new TitleValue( 0, 'OtherDbKey' ),
176 new TitleValue( 1, 'AnotherDbKey' ),
179 $mockDb = $this->getMockDb();
182 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
183 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
184 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
187 $mockDb->expects( $this->once() )
188 ->method( 'makeWhereFrom2d' )
190 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
191 $this->isType( 'string' ),
192 $this->isType( 'string' )
194 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
195 $mockDb->expects( $this->once() )
199 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
200 [ 'makeWhereFrom2d return value' ],
201 $this->isType( 'string' ),
203 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
207 $this->returnValue( $dbResult )
210 $mockCache = $this->getMockCache();
211 $mockCache->expects( $this->never() )->method( 'get' );
212 $mockCache->expects( $this->never() )->method( 'set' );
213 $mockCache->expects( $this->never() )->method( 'delete' );
215 $store = $this->newWatchedItemStore(
216 $this->getMockLoadBalancer( $mockDb ),
218 $this->getMockReadOnlyMode()
222 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
223 1 => [ 'AnotherDbKey' => 500 ],
225 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
228 public function provideIntWithDbUnsafeVersion() {
231 [ "50; DROP TABLE watchlist;\n--" ],
236 * @dataProvider provideIntWithDbUnsafeVersion
238 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
240 new TitleValue( 0, 'SomeDbKey' ),
241 new TitleValue( 0, 'OtherDbKey' ),
242 new TitleValue( 1, 'AnotherDbKey' ),
245 $mockDb = $this->getMockDb();
248 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
249 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
250 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
253 $mockDb->expects( $this->once() )
254 ->method( 'makeWhereFrom2d' )
256 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
257 $this->isType( 'string' ),
258 $this->isType( 'string' )
260 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
261 $mockDb->expects( $this->once() )
265 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
266 [ 'makeWhereFrom2d return value' ],
267 $this->isType( 'string' ),
269 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
270 'HAVING' => 'COUNT(*) >= 50',
274 $this->returnValue( $dbResult )
277 $mockCache = $this->getMockCache();
278 $mockCache->expects( $this->never() )->method( 'get' );
279 $mockCache->expects( $this->never() )->method( 'set' );
280 $mockCache->expects( $this->never() )->method( 'delete' );
282 $store = $this->newWatchedItemStore(
283 $this->getMockLoadBalancer( $mockDb ),
285 $this->getMockReadOnlyMode()
289 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
290 1 => [ 'AnotherDbKey' => 500 ],
294 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
298 public function testCountVisitingWatchers() {
299 $titleValue = new TitleValue( 0, 'SomeDbKey' );
301 $mockDb = $this->getMockDb();
302 $mockDb->expects( $this->exactly( 1 ) )
303 ->method( 'selectField' )
308 'wl_namespace' => $titleValue->getNamespace(),
309 'wl_title' => $titleValue->getDBkey(),
310 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
312 $this->isType( 'string' )
314 ->will( $this->returnValue( '7' ) );
315 $mockDb->expects( $this->exactly( 1 ) )
316 ->method( 'addQuotes' )
317 ->will( $this->returnCallback( function ( $value ) {
320 $mockDb->expects( $this->exactly( 1 ) )
321 ->method( 'timestamp' )
322 ->will( $this->returnCallback( function ( $value ) {
323 return 'TS' . $value . 'TS';
326 $mockCache = $this->getMockCache();
327 $mockCache->expects( $this->never() )->method( 'set' );
328 $mockCache->expects( $this->never() )->method( 'get' );
329 $mockCache->expects( $this->never() )->method( 'delete' );
331 $store = $this->newWatchedItemStore(
332 $this->getMockLoadBalancer( $mockDb ),
334 $this->getMockReadOnlyMode()
337 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
340 public function testCountVisitingWatchersMultiple() {
341 $titleValuesWithThresholds = [
342 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
343 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
344 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
348 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
349 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
351 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
354 $mockDb = $this->getMockDb();
355 $mockDb->expects( $this->exactly( 2 * 3 ) )
356 ->method( 'addQuotes' )
357 ->will( $this->returnCallback( function ( $value ) {
360 $mockDb->expects( $this->exactly( 3 ) )
361 ->method( 'timestamp' )
362 ->will( $this->returnCallback( function ( $value ) {
363 return 'TS' . $value . 'TS';
365 $mockDb->expects( $this->any() )
366 ->method( 'makeList' )
368 $this->isType( 'array' ),
369 $this->isType( 'int' )
371 ->will( $this->returnCallback( function ( $a, $conj ) {
372 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
373 return join( $sqlConj, array_map( function ( $s ) {
374 return '(' . $s . ')';
378 $mockDb->expects( $this->never() )
379 ->method( 'makeWhereFrom2d' );
382 '((wl_namespace = 0) AND (' .
383 "(((wl_title = 'SomeDbKey') AND (" .
384 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
386 "(wl_title = 'OtherDbKey') AND (" .
387 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
389 ') OR ((wl_namespace = 1) AND (' .
390 "(((wl_title = 'AnotherDbKey') AND (".
391 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
393 $mockDb->expects( $this->once() )
397 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
399 $this->isType( 'string' ),
401 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
405 $this->returnValue( $dbResult )
408 $mockCache = $this->getMockCache();
409 $mockCache->expects( $this->never() )->method( 'get' );
410 $mockCache->expects( $this->never() )->method( 'set' );
411 $mockCache->expects( $this->never() )->method( 'delete' );
413 $store = $this->newWatchedItemStore(
414 $this->getMockLoadBalancer( $mockDb ),
416 $this->getMockReadOnlyMode()
420 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
421 1 => [ 'AnotherDbKey' => 500 ],
425 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
429 public function testCountVisitingWatchersMultiple_withMissingTargets() {
430 $titleValuesWithThresholds = [
431 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
432 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
433 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
434 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
435 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
439 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
440 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
442 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
445 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
448 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
451 $mockDb = $this->getMockDb();
452 $mockDb->expects( $this->exactly( 2 * 3 ) )
453 ->method( 'addQuotes' )
454 ->will( $this->returnCallback( function ( $value ) {
457 $mockDb->expects( $this->exactly( 3 ) )
458 ->method( 'timestamp' )
459 ->will( $this->returnCallback( function ( $value ) {
460 return 'TS' . $value . 'TS';
462 $mockDb->expects( $this->any() )
463 ->method( 'makeList' )
465 $this->isType( 'array' ),
466 $this->isType( 'int' )
468 ->will( $this->returnCallback( function ( $a, $conj ) {
469 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
470 return join( $sqlConj, array_map( function ( $s ) {
471 return '(' . $s . ')';
475 $mockDb->expects( $this->once() )
476 ->method( 'makeWhereFrom2d' )
478 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
479 $this->isType( 'string' ),
480 $this->isType( 'string' )
482 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
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)" .
497 '(makeWhereFrom2d return value)';
498 $mockDb->expects( $this->once() )
502 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
504 $this->isType( 'string' ),
506 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
510 $this->returnValue( $dbResult )
513 $mockCache = $this->getMockCache();
514 $mockCache->expects( $this->never() )->method( 'get' );
515 $mockCache->expects( $this->never() )->method( 'set' );
516 $mockCache->expects( $this->never() )->method( 'delete' );
518 $store = $this->newWatchedItemStore(
519 $this->getMockLoadBalancer( $mockDb ),
521 $this->getMockReadOnlyMode()
526 'SomeDbKey' => 100, 'OtherDbKey' => 300,
527 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
529 1 => [ 'AnotherDbKey' => 500 ],
533 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
538 * @dataProvider provideIntWithDbUnsafeVersion
540 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
541 $titleValuesWithThresholds = [
542 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
543 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
544 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
547 $mockDb = $this->getMockDb();
548 $mockDb->expects( $this->any() )
549 ->method( 'makeList' )
550 ->will( $this->returnValue( 'makeList return value' ) );
551 $mockDb->expects( $this->once() )
555 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
556 'makeList return value',
557 $this->isType( 'string' ),
559 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
560 'HAVING' => 'COUNT(*) >= 50',
564 $this->returnValue( [] )
567 $mockCache = $this->getMockCache();
568 $mockCache->expects( $this->never() )->method( 'get' );
569 $mockCache->expects( $this->never() )->method( 'set' );
570 $mockCache->expects( $this->never() )->method( 'delete' );
572 $store = $this->newWatchedItemStore(
573 $this->getMockLoadBalancer( $mockDb ),
575 $this->getMockReadOnlyMode()
579 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
580 1 => [ 'AnotherDbKey' => 0 ],
584 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
588 public function testCountUnreadNotifications() {
589 $user = $this->getMockNonAnonUserWithId( 1 );
591 $mockDb = $this->getMockDb();
592 $mockDb->expects( $this->exactly( 1 ) )
593 ->method( 'selectRowCount' )
598 "wl_notificationtimestamp IS NOT NULL",
601 $this->isType( 'string' )
603 ->will( $this->returnValue( '9' ) );
605 $mockCache = $this->getMockCache();
606 $mockCache->expects( $this->never() )->method( 'set' );
607 $mockCache->expects( $this->never() )->method( 'get' );
608 $mockCache->expects( $this->never() )->method( 'delete' );
610 $store = $this->newWatchedItemStore(
611 $this->getMockLoadBalancer( $mockDb ),
613 $this->getMockReadOnlyMode()
616 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
620 * @dataProvider provideIntWithDbUnsafeVersion
622 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
623 $user = $this->getMockNonAnonUserWithId( 1 );
625 $mockDb = $this->getMockDb();
626 $mockDb->expects( $this->exactly( 1 ) )
627 ->method( 'selectRowCount' )
632 "wl_notificationtimestamp IS NOT NULL",
635 $this->isType( 'string' ),
638 ->will( $this->returnValue( '50' ) );
640 $mockCache = $this->getMockCache();
641 $mockCache->expects( $this->never() )->method( 'set' );
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'delete' );
645 $store = $this->newWatchedItemStore(
646 $this->getMockLoadBalancer( $mockDb ),
648 $this->getMockReadOnlyMode()
653 $store->countUnreadNotifications( $user, $limit )
658 * @dataProvider provideIntWithDbUnsafeVersion
660 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
661 $user = $this->getMockNonAnonUserWithId( 1 );
663 $mockDb = $this->getMockDb();
664 $mockDb->expects( $this->exactly( 1 ) )
665 ->method( 'selectRowCount' )
670 "wl_notificationtimestamp IS NOT NULL",
673 $this->isType( 'string' ),
676 ->will( $this->returnValue( '9' ) );
678 $mockCache = $this->getMockCache();
679 $mockCache->expects( $this->never() )->method( 'set' );
680 $mockCache->expects( $this->never() )->method( 'get' );
681 $mockCache->expects( $this->never() )->method( 'delete' );
683 $store = $this->newWatchedItemStore(
684 $this->getMockLoadBalancer( $mockDb ),
686 $this->getMockReadOnlyMode()
691 $store->countUnreadNotifications( $user, $limit )
695 public function testDuplicateEntry_nothingToDuplicate() {
696 $mockDb = $this->getMockDb();
697 $mockDb->expects( $this->once() )
703 'wl_notificationtimestamp',
707 'wl_title' => 'Old_Title',
709 'WatchedItemStore::duplicateEntry',
712 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
714 $store = $this->newWatchedItemStore(
715 $this->getMockLoadBalancer( $mockDb ),
716 $this->getMockCache(),
717 $this->getMockReadOnlyMode()
720 $store->duplicateEntry(
721 Title
::newFromText( 'Old_Title' ),
722 Title
::newFromText( 'New_Title' )
726 public function testDuplicateEntry_somethingToDuplicate() {
728 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
729 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
732 $mockDb = $this->getMockDb();
733 $mockDb->expects( $this->at( 0 ) )
739 'wl_notificationtimestamp',
743 'wl_title' => 'Old_Title',
746 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
747 $mockDb->expects( $this->at( 1 ) )
748 ->method( 'replace' )
751 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
756 'wl_title' => 'New_Title',
757 'wl_notificationtimestamp' => '20151212010101',
762 'wl_title' => 'New_Title',
763 'wl_notificationtimestamp' => null,
766 $this->isType( 'string' )
769 $mockCache = $this->getMockCache();
770 $mockCache->expects( $this->never() )->method( 'get' );
771 $mockCache->expects( $this->never() )->method( 'delete' );
773 $store = $this->newWatchedItemStore(
774 $this->getMockLoadBalancer( $mockDb ),
776 $this->getMockReadOnlyMode()
779 $store->duplicateEntry(
780 Title
::newFromText( 'Old_Title' ),
781 Title
::newFromText( 'New_Title' )
785 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
786 $mockDb = $this->getMockDb();
787 $mockDb->expects( $this->at( 0 ) )
793 'wl_notificationtimestamp',
797 'wl_title' => 'Old_Title',
800 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
801 $mockDb->expects( $this->at( 1 ) )
807 'wl_notificationtimestamp',
811 'wl_title' => 'Old_Title',
814 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
816 $mockCache = $this->getMockCache();
817 $mockCache->expects( $this->never() )->method( 'get' );
818 $mockCache->expects( $this->never() )->method( 'delete' );
820 $store = $this->newWatchedItemStore(
821 $this->getMockLoadBalancer( $mockDb ),
823 $this->getMockReadOnlyMode()
826 $store->duplicateAllAssociatedEntries(
827 Title
::newFromText( 'Old_Title' ),
828 Title
::newFromText( 'New_Title' )
832 public function provideLinkTargetPairs() {
834 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
835 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
840 * @dataProvider provideLinkTargetPairs
842 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
843 LinkTarget
$oldTarget,
844 LinkTarget
$newTarget
847 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
850 $mockDb = $this->getMockDb();
851 $mockDb->expects( $this->at( 0 ) )
857 'wl_notificationtimestamp',
860 'wl_namespace' => $oldTarget->getNamespace(),
861 'wl_title' => $oldTarget->getDBkey(),
864 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
865 $mockDb->expects( $this->at( 1 ) )
866 ->method( 'replace' )
869 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
873 'wl_namespace' => $newTarget->getNamespace(),
874 'wl_title' => $newTarget->getDBkey(),
875 'wl_notificationtimestamp' => '20151212010101',
878 $this->isType( 'string' )
880 $mockDb->expects( $this->at( 2 ) )
886 'wl_notificationtimestamp',
889 'wl_namespace' => $oldTarget->getNamespace() +
1,
890 'wl_title' => $oldTarget->getDBkey(),
893 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
894 $mockDb->expects( $this->at( 3 ) )
895 ->method( 'replace' )
898 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
902 'wl_namespace' => $newTarget->getNamespace() +
1,
903 'wl_title' => $newTarget->getDBkey(),
904 'wl_notificationtimestamp' => '20151212010101',
907 $this->isType( 'string' )
910 $mockCache = $this->getMockCache();
911 $mockCache->expects( $this->never() )->method( 'get' );
912 $mockCache->expects( $this->never() )->method( 'delete' );
914 $store = $this->newWatchedItemStore(
915 $this->getMockLoadBalancer( $mockDb ),
917 $this->getMockReadOnlyMode()
920 $store->duplicateAllAssociatedEntries(
926 public function testAddWatch_nonAnonymousUser() {
927 $mockDb = $this->getMockDb();
928 $mockDb->expects( $this->once() )
936 'wl_title' => 'Some_Page',
937 'wl_notificationtimestamp' => null,
942 $mockCache = $this->getMockCache();
943 $mockCache->expects( $this->once() )
945 ->with( '0:Some_Page:1' );
947 $store = $this->newWatchedItemStore(
948 $this->getMockLoadBalancer( $mockDb ),
950 $this->getMockReadOnlyMode()
954 $this->getMockNonAnonUserWithId( 1 ),
955 Title
::newFromText( 'Some_Page' )
959 public function testAddWatch_anonymousUser() {
960 $mockDb = $this->getMockDb();
961 $mockDb->expects( $this->never() )
962 ->method( 'insert' );
964 $mockCache = $this->getMockCache();
965 $mockCache->expects( $this->never() )
966 ->method( 'delete' );
968 $store = $this->newWatchedItemStore(
969 $this->getMockLoadBalancer( $mockDb ),
971 $this->getMockReadOnlyMode()
975 $this->getAnonUser(),
976 Title
::newFromText( 'Some_Page' )
980 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
981 $store = $this->newWatchedItemStore(
982 $this->getMockLoadBalancer( $this->getMockDb() ),
983 $this->getMockCache(),
984 $this->getMockReadOnlyMode( true )
988 $store->addWatchBatchForUser(
989 $this->getMockNonAnonUserWithId( 1 ),
990 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
995 public function testAddWatchBatchForUser_nonAnonymousUser() {
996 $mockDb = $this->getMockDb();
997 $mockDb->expects( $this->once() )
1004 'wl_namespace' => 0,
1005 'wl_title' => 'Some_Page',
1006 'wl_notificationtimestamp' => null,
1010 'wl_namespace' => 1,
1011 'wl_title' => 'Some_Page',
1012 'wl_notificationtimestamp' => null,
1017 $mockCache = $this->getMockCache();
1018 $mockCache->expects( $this->exactly( 2 ) )
1019 ->method( 'delete' );
1020 $mockCache->expects( $this->at( 1 ) )
1021 ->method( 'delete' )
1022 ->with( '0:Some_Page:1' );
1023 $mockCache->expects( $this->at( 3 ) )
1024 ->method( 'delete' )
1025 ->with( '1:Some_Page:1' );
1027 $store = $this->newWatchedItemStore(
1028 $this->getMockLoadBalancer( $mockDb ),
1030 $this->getMockReadOnlyMode()
1033 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1036 $store->addWatchBatchForUser(
1038 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1043 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1044 $mockDb = $this->getMockDb();
1045 $mockDb->expects( $this->never() )
1046 ->method( 'insert' );
1048 $mockCache = $this->getMockCache();
1049 $mockCache->expects( $this->never() )
1050 ->method( 'delete' );
1052 $store = $this->newWatchedItemStore(
1053 $this->getMockLoadBalancer( $mockDb ),
1055 $this->getMockReadOnlyMode()
1059 $store->addWatchBatchForUser(
1060 $this->getAnonUser(),
1061 [ new TitleValue( 0, 'Other_Page' ) ]
1066 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1067 $user = $this->getMockNonAnonUserWithId( 1 );
1068 $mockDb = $this->getMockDb();
1069 $mockDb->expects( $this->never() )
1070 ->method( 'insert' );
1072 $mockCache = $this->getMockCache();
1073 $mockCache->expects( $this->never() )
1074 ->method( 'delete' );
1076 $store = $this->newWatchedItemStore(
1077 $this->getMockLoadBalancer( $mockDb ),
1079 $this->getMockReadOnlyMode()
1083 $store->addWatchBatchForUser( $user, [] )
1087 public function testLoadWatchedItem_existingItem() {
1088 $mockDb = $this->getMockDb();
1089 $mockDb->expects( $this->once() )
1090 ->method( 'selectRow' )
1093 'wl_notificationtimestamp',
1096 'wl_namespace' => 0,
1097 'wl_title' => 'SomeDbKey',
1100 ->will( $this->returnValue(
1101 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1104 $mockCache = $this->getMockCache();
1105 $mockCache->expects( $this->once() )
1111 $store = $this->newWatchedItemStore(
1112 $this->getMockLoadBalancer( $mockDb ),
1114 $this->getMockReadOnlyMode()
1117 $watchedItem = $store->loadWatchedItem(
1118 $this->getMockNonAnonUserWithId( 1 ),
1119 new TitleValue( 0, 'SomeDbKey' )
1121 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1122 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1123 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1124 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1127 public function testLoadWatchedItem_noItem() {
1128 $mockDb = $this->getMockDb();
1129 $mockDb->expects( $this->once() )
1130 ->method( 'selectRow' )
1133 'wl_notificationtimestamp',
1136 'wl_namespace' => 0,
1137 'wl_title' => 'SomeDbKey',
1140 ->will( $this->returnValue( [] ) );
1142 $mockCache = $this->getMockCache();
1143 $mockCache->expects( $this->never() )->method( 'get' );
1144 $mockCache->expects( $this->never() )->method( 'delete' );
1146 $store = $this->newWatchedItemStore(
1147 $this->getMockLoadBalancer( $mockDb ),
1149 $this->getMockReadOnlyMode()
1153 $store->loadWatchedItem(
1154 $this->getMockNonAnonUserWithId( 1 ),
1155 new TitleValue( 0, 'SomeDbKey' )
1160 public function testLoadWatchedItem_anonymousUser() {
1161 $mockDb = $this->getMockDb();
1162 $mockDb->expects( $this->never() )
1163 ->method( 'selectRow' );
1165 $mockCache = $this->getMockCache();
1166 $mockCache->expects( $this->never() )->method( 'get' );
1167 $mockCache->expects( $this->never() )->method( 'delete' );
1169 $store = $this->newWatchedItemStore(
1170 $this->getMockLoadBalancer( $mockDb ),
1172 $this->getMockReadOnlyMode()
1176 $store->loadWatchedItem(
1177 $this->getAnonUser(),
1178 new TitleValue( 0, 'SomeDbKey' )
1183 public function testRemoveWatch_existingItem() {
1184 $mockDb = $this->getMockDb();
1185 $mockDb->expects( $this->once() )
1186 ->method( 'delete' )
1191 'wl_namespace' => 0,
1192 'wl_title' => 'SomeDbKey',
1195 $mockDb->expects( $this->once() )
1196 ->method( 'affectedRows' )
1197 ->will( $this->returnValue( 1 ) );
1199 $mockCache = $this->getMockCache();
1200 $mockCache->expects( $this->never() )->method( 'get' );
1201 $mockCache->expects( $this->once() )
1202 ->method( 'delete' )
1203 ->with( '0:SomeDbKey:1' );
1205 $store = $this->newWatchedItemStore(
1206 $this->getMockLoadBalancer( $mockDb ),
1208 $this->getMockReadOnlyMode()
1212 $store->removeWatch(
1213 $this->getMockNonAnonUserWithId( 1 ),
1214 new TitleValue( 0, 'SomeDbKey' )
1219 public function testRemoveWatch_noItem() {
1220 $mockDb = $this->getMockDb();
1221 $mockDb->expects( $this->once() )
1222 ->method( 'delete' )
1227 'wl_namespace' => 0,
1228 'wl_title' => 'SomeDbKey',
1231 $mockDb->expects( $this->once() )
1232 ->method( 'affectedRows' )
1233 ->will( $this->returnValue( 0 ) );
1235 $mockCache = $this->getMockCache();
1236 $mockCache->expects( $this->never() )->method( 'get' );
1237 $mockCache->expects( $this->once() )
1238 ->method( 'delete' )
1239 ->with( '0:SomeDbKey:1' );
1241 $store = $this->newWatchedItemStore(
1242 $this->getMockLoadBalancer( $mockDb ),
1244 $this->getMockReadOnlyMode()
1248 $store->removeWatch(
1249 $this->getMockNonAnonUserWithId( 1 ),
1250 new TitleValue( 0, 'SomeDbKey' )
1255 public function testRemoveWatch_anonymousUser() {
1256 $mockDb = $this->getMockDb();
1257 $mockDb->expects( $this->never() )
1258 ->method( 'delete' );
1260 $mockCache = $this->getMockCache();
1261 $mockCache->expects( $this->never() )->method( 'get' );
1262 $mockCache->expects( $this->never() )
1263 ->method( 'delete' );
1265 $store = $this->newWatchedItemStore(
1266 $this->getMockLoadBalancer( $mockDb ),
1268 $this->getMockReadOnlyMode()
1272 $store->removeWatch(
1273 $this->getAnonUser(),
1274 new TitleValue( 0, 'SomeDbKey' )
1279 public function testGetWatchedItem_existingItem() {
1280 $mockDb = $this->getMockDb();
1281 $mockDb->expects( $this->once() )
1282 ->method( 'selectRow' )
1285 'wl_notificationtimestamp',
1288 'wl_namespace' => 0,
1289 'wl_title' => 'SomeDbKey',
1292 ->will( $this->returnValue(
1293 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1296 $mockCache = $this->getMockCache();
1297 $mockCache->expects( $this->never() )->method( 'delete' );
1298 $mockCache->expects( $this->once() )
1303 ->will( $this->returnValue( null ) );
1304 $mockCache->expects( $this->once() )
1310 $store = $this->newWatchedItemStore(
1311 $this->getMockLoadBalancer( $mockDb ),
1313 $this->getMockReadOnlyMode()
1316 $watchedItem = $store->getWatchedItem(
1317 $this->getMockNonAnonUserWithId( 1 ),
1318 new TitleValue( 0, 'SomeDbKey' )
1320 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1321 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1322 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1323 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1326 public function testGetWatchedItem_cachedItem() {
1327 $mockDb = $this->getMockDb();
1328 $mockDb->expects( $this->never() )
1329 ->method( 'selectRow' );
1331 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1332 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1333 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1335 $mockCache = $this->getMockCache();
1336 $mockCache->expects( $this->never() )->method( 'delete' );
1337 $mockCache->expects( $this->never() )->method( 'set' );
1338 $mockCache->expects( $this->once() )
1343 ->will( $this->returnValue( $cachedItem ) );
1345 $store = $this->newWatchedItemStore(
1346 $this->getMockLoadBalancer( $mockDb ),
1348 $this->getMockReadOnlyMode()
1351 $this->assertEquals(
1353 $store->getWatchedItem(
1360 public function testGetWatchedItem_noItem() {
1361 $mockDb = $this->getMockDb();
1362 $mockDb->expects( $this->once() )
1363 ->method( 'selectRow' )
1366 'wl_notificationtimestamp',
1369 'wl_namespace' => 0,
1370 'wl_title' => 'SomeDbKey',
1373 ->will( $this->returnValue( [] ) );
1375 $mockCache = $this->getMockCache();
1376 $mockCache->expects( $this->never() )->method( 'set' );
1377 $mockCache->expects( $this->never() )->method( 'delete' );
1378 $mockCache->expects( $this->once() )
1380 ->with( '0:SomeDbKey:1' )
1381 ->will( $this->returnValue( false ) );
1383 $store = $this->newWatchedItemStore(
1384 $this->getMockLoadBalancer( $mockDb ),
1386 $this->getMockReadOnlyMode()
1390 $store->getWatchedItem(
1391 $this->getMockNonAnonUserWithId( 1 ),
1392 new TitleValue( 0, 'SomeDbKey' )
1397 public function testGetWatchedItem_anonymousUser() {
1398 $mockDb = $this->getMockDb();
1399 $mockDb->expects( $this->never() )
1400 ->method( 'selectRow' );
1402 $mockCache = $this->getMockCache();
1403 $mockCache->expects( $this->never() )->method( 'set' );
1404 $mockCache->expects( $this->never() )->method( 'get' );
1405 $mockCache->expects( $this->never() )->method( 'delete' );
1407 $store = $this->newWatchedItemStore(
1408 $this->getMockLoadBalancer( $mockDb ),
1410 $this->getMockReadOnlyMode()
1414 $store->getWatchedItem(
1415 $this->getAnonUser(),
1416 new TitleValue( 0, 'SomeDbKey' )
1421 public function testGetWatchedItemsForUser() {
1422 $mockDb = $this->getMockDb();
1423 $mockDb->expects( $this->once() )
1424 ->method( 'select' )
1427 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1430 ->will( $this->returnValue( [
1431 $this->getFakeRow( [
1432 'wl_namespace' => 0,
1433 'wl_title' => 'Foo1',
1434 'wl_notificationtimestamp' => '20151212010101',
1436 $this->getFakeRow( [
1437 'wl_namespace' => 1,
1438 'wl_title' => 'Foo2',
1439 'wl_notificationtimestamp' => null,
1443 $mockCache = $this->getMockCache();
1444 $mockCache->expects( $this->never() )->method( 'delete' );
1445 $mockCache->expects( $this->never() )->method( 'get' );
1446 $mockCache->expects( $this->never() )->method( 'set' );
1448 $store = $this->newWatchedItemStore(
1449 $this->getMockLoadBalancer( $mockDb ),
1451 $this->getMockReadOnlyMode()
1453 $user = $this->getMockNonAnonUserWithId( 1 );
1455 $watchedItems = $store->getWatchedItemsForUser( $user );
1457 $this->assertInternalType( 'array', $watchedItems );
1458 $this->assertCount( 2, $watchedItems );
1459 foreach ( $watchedItems as $watchedItem ) {
1460 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1462 $this->assertEquals(
1463 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1466 $this->assertEquals(
1467 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1472 public function provideDbTypes() {
1474 [ false, DB_REPLICA
],
1475 [ true, DB_MASTER
],
1480 * @dataProvider provideDbTypes
1482 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1483 $mockDb = $this->getMockDb();
1484 $mockCache = $this->getMockCache();
1485 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1486 $user = $this->getMockNonAnonUserWithId( 1 );
1488 $mockDb->expects( $this->once() )
1489 ->method( 'select' )
1492 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1494 $this->isType( 'string' ),
1495 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1497 ->will( $this->returnValue( [] ) );
1499 $store = $this->newWatchedItemStore(
1502 $this->getMockReadOnlyMode()
1505 $watchedItems = $store->getWatchedItemsForUser(
1507 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1509 $this->assertEquals( [], $watchedItems );
1512 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1513 $store = $this->newWatchedItemStore(
1514 $this->getMockLoadBalancer( $this->getMockDb() ),
1515 $this->getMockCache(),
1516 $this->getMockReadOnlyMode()
1519 $this->setExpectedException( 'InvalidArgumentException' );
1520 $store->getWatchedItemsForUser(
1521 $this->getMockNonAnonUserWithId( 1 ),
1526 public function testIsWatchedItem_existingItem() {
1527 $mockDb = $this->getMockDb();
1528 $mockDb->expects( $this->once() )
1529 ->method( 'selectRow' )
1532 'wl_notificationtimestamp',
1535 'wl_namespace' => 0,
1536 'wl_title' => 'SomeDbKey',
1539 ->will( $this->returnValue(
1540 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1543 $mockCache = $this->getMockCache();
1544 $mockCache->expects( $this->never() )->method( 'delete' );
1545 $mockCache->expects( $this->once() )
1547 ->with( '0:SomeDbKey:1' )
1548 ->will( $this->returnValue( false ) );
1549 $mockCache->expects( $this->once() )
1555 $store = $this->newWatchedItemStore(
1556 $this->getMockLoadBalancer( $mockDb ),
1558 $this->getMockReadOnlyMode()
1563 $this->getMockNonAnonUserWithId( 1 ),
1564 new TitleValue( 0, 'SomeDbKey' )
1569 public function testIsWatchedItem_noItem() {
1570 $mockDb = $this->getMockDb();
1571 $mockDb->expects( $this->once() )
1572 ->method( 'selectRow' )
1575 'wl_notificationtimestamp',
1578 'wl_namespace' => 0,
1579 'wl_title' => 'SomeDbKey',
1582 ->will( $this->returnValue( [] ) );
1584 $mockCache = $this->getMockCache();
1585 $mockCache->expects( $this->never() )->method( 'set' );
1586 $mockCache->expects( $this->never() )->method( 'delete' );
1587 $mockCache->expects( $this->once() )
1589 ->with( '0:SomeDbKey:1' )
1590 ->will( $this->returnValue( false ) );
1592 $store = $this->newWatchedItemStore(
1593 $this->getMockLoadBalancer( $mockDb ),
1595 $this->getMockReadOnlyMode()
1600 $this->getMockNonAnonUserWithId( 1 ),
1601 new TitleValue( 0, 'SomeDbKey' )
1606 public function testIsWatchedItem_anonymousUser() {
1607 $mockDb = $this->getMockDb();
1608 $mockDb->expects( $this->never() )
1609 ->method( 'selectRow' );
1611 $mockCache = $this->getMockCache();
1612 $mockCache->expects( $this->never() )->method( 'set' );
1613 $mockCache->expects( $this->never() )->method( 'get' );
1614 $mockCache->expects( $this->never() )->method( 'delete' );
1616 $store = $this->newWatchedItemStore(
1617 $this->getMockLoadBalancer( $mockDb ),
1619 $this->getMockReadOnlyMode()
1624 $this->getAnonUser(),
1625 new TitleValue( 0, 'SomeDbKey' )
1630 public function testGetNotificationTimestampsBatch() {
1632 new TitleValue( 0, 'SomeDbKey' ),
1633 new TitleValue( 1, 'AnotherDbKey' ),
1636 $mockDb = $this->getMockDb();
1638 $this->getFakeRow( [
1639 'wl_namespace' => '0',
1640 'wl_title' => 'SomeDbKey',
1641 'wl_notificationtimestamp' => '20151212010101',
1645 'wl_namespace' => '1',
1646 'wl_title' => 'AnotherDbKey',
1647 'wl_notificationtimestamp' => null,
1652 $mockDb->expects( $this->once() )
1653 ->method( 'makeWhereFrom2d' )
1655 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1656 $this->isType( 'string' ),
1657 $this->isType( 'string' )
1659 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1660 $mockDb->expects( $this->once() )
1661 ->method( 'select' )
1664 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1666 'makeWhereFrom2d return value',
1669 $this->isType( 'string' )
1671 ->will( $this->returnValue( $dbResult ) );
1673 $mockCache = $this->getMockCache();
1674 $mockCache->expects( $this->exactly( 2 ) )
1677 [ '0:SomeDbKey:1' ],
1678 [ '1:AnotherDbKey:1' ]
1680 ->will( $this->returnValue( null ) );
1681 $mockCache->expects( $this->never() )->method( 'set' );
1682 $mockCache->expects( $this->never() )->method( 'delete' );
1684 $store = $this->newWatchedItemStore(
1685 $this->getMockLoadBalancer( $mockDb ),
1687 $this->getMockReadOnlyMode()
1690 $this->assertEquals(
1692 0 => [ 'SomeDbKey' => '20151212010101', ],
1693 1 => [ 'AnotherDbKey' => null, ],
1695 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1699 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1701 new TitleValue( 0, 'OtherDbKey' ),
1704 $mockDb = $this->getMockDb();
1706 $mockDb->expects( $this->once() )
1707 ->method( 'makeWhereFrom2d' )
1709 [ [ 'OtherDbKey' => 1 ] ],
1710 $this->isType( 'string' ),
1711 $this->isType( 'string' )
1713 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1714 $mockDb->expects( $this->once() )
1715 ->method( 'select' )
1718 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1720 'makeWhereFrom2d return value',
1723 $this->isType( 'string' )
1725 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1727 $mockCache = $this->getMockCache();
1728 $mockCache->expects( $this->once() )
1730 ->with( '0:OtherDbKey:1' )
1731 ->will( $this->returnValue( null ) );
1732 $mockCache->expects( $this->never() )->method( 'set' );
1733 $mockCache->expects( $this->never() )->method( 'delete' );
1735 $store = $this->newWatchedItemStore(
1736 $this->getMockLoadBalancer( $mockDb ),
1738 $this->getMockReadOnlyMode()
1741 $this->assertEquals(
1743 0 => [ 'OtherDbKey' => false, ],
1745 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1749 public function testGetNotificationTimestampsBatch_cachedItem() {
1751 new TitleValue( 0, 'SomeDbKey' ),
1752 new TitleValue( 1, 'AnotherDbKey' ),
1755 $user = $this->getMockNonAnonUserWithId( 1 );
1756 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1758 $mockDb = $this->getMockDb();
1760 $mockDb->expects( $this->once() )
1761 ->method( 'makeWhereFrom2d' )
1763 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1764 $this->isType( 'string' ),
1765 $this->isType( 'string' )
1767 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1768 $mockDb->expects( $this->once() )
1769 ->method( 'select' )
1772 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1774 'makeWhereFrom2d return value',
1777 $this->isType( 'string' )
1779 ->will( $this->returnValue( [
1781 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1785 $mockCache = $this->getMockCache();
1786 $mockCache->expects( $this->at( 1 ) )
1788 ->with( '0:SomeDbKey:1' )
1789 ->will( $this->returnValue( $cachedItem ) );
1790 $mockCache->expects( $this->at( 3 ) )
1792 ->with( '1:AnotherDbKey:1' )
1793 ->will( $this->returnValue( null ) );
1794 $mockCache->expects( $this->never() )->method( 'set' );
1795 $mockCache->expects( $this->never() )->method( 'delete' );
1797 $store = $this->newWatchedItemStore(
1798 $this->getMockLoadBalancer( $mockDb ),
1800 $this->getMockReadOnlyMode()
1803 $this->assertEquals(
1805 0 => [ 'SomeDbKey' => '20151212010101', ],
1806 1 => [ 'AnotherDbKey' => null, ],
1808 $store->getNotificationTimestampsBatch( $user, $targets )
1812 public function testGetNotificationTimestampsBatch_allItemsCached() {
1814 new TitleValue( 0, 'SomeDbKey' ),
1815 new TitleValue( 1, 'AnotherDbKey' ),
1818 $user = $this->getMockNonAnonUserWithId( 1 );
1820 new WatchedItem( $user, $targets[0], '20151212010101' ),
1821 new WatchedItem( $user, $targets[1], null ),
1823 $mockDb = $this->getMockDb();
1824 $mockDb->expects( $this->never() )->method( $this->anything() );
1826 $mockCache = $this->getMockCache();
1827 $mockCache->expects( $this->at( 1 ) )
1829 ->with( '0:SomeDbKey:1' )
1830 ->will( $this->returnValue( $cachedItems[0] ) );
1831 $mockCache->expects( $this->at( 3 ) )
1833 ->with( '1:AnotherDbKey:1' )
1834 ->will( $this->returnValue( $cachedItems[1] ) );
1835 $mockCache->expects( $this->never() )->method( 'set' );
1836 $mockCache->expects( $this->never() )->method( 'delete' );
1838 $store = $this->newWatchedItemStore(
1839 $this->getMockLoadBalancer( $mockDb ),
1841 $this->getMockReadOnlyMode()
1844 $this->assertEquals(
1846 0 => [ 'SomeDbKey' => '20151212010101', ],
1847 1 => [ 'AnotherDbKey' => null, ],
1849 $store->getNotificationTimestampsBatch( $user, $targets )
1853 public function testGetNotificationTimestampsBatch_anonymousUser() {
1855 new TitleValue( 0, 'SomeDbKey' ),
1856 new TitleValue( 1, 'AnotherDbKey' ),
1859 $mockDb = $this->getMockDb();
1860 $mockDb->expects( $this->never() )->method( $this->anything() );
1862 $mockCache = $this->getMockCache();
1863 $mockCache->expects( $this->never() )->method( $this->anything() );
1865 $store = $this->newWatchedItemStore(
1866 $this->getMockLoadBalancer( $mockDb ),
1868 $this->getMockReadOnlyMode()
1871 $this->assertEquals(
1873 0 => [ 'SomeDbKey' => false, ],
1874 1 => [ 'AnotherDbKey' => false, ],
1876 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1880 public function testResetNotificationTimestamp_anonymousUser() {
1881 $mockDb = $this->getMockDb();
1882 $mockDb->expects( $this->never() )
1883 ->method( 'selectRow' );
1885 $mockCache = $this->getMockCache();
1886 $mockCache->expects( $this->never() )->method( 'get' );
1887 $mockCache->expects( $this->never() )->method( 'set' );
1888 $mockCache->expects( $this->never() )->method( 'delete' );
1890 $store = $this->newWatchedItemStore(
1891 $this->getMockLoadBalancer( $mockDb ),
1893 $this->getMockReadOnlyMode()
1897 $store->resetNotificationTimestamp(
1898 $this->getAnonUser(),
1899 Title
::newFromText( 'SomeDbKey' )
1904 public function testResetNotificationTimestamp_noItem() {
1905 $mockDb = $this->getMockDb();
1906 $mockDb->expects( $this->once() )
1907 ->method( 'selectRow' )
1910 'wl_notificationtimestamp',
1913 'wl_namespace' => 0,
1914 'wl_title' => 'SomeDbKey',
1917 ->will( $this->returnValue( [] ) );
1919 $mockCache = $this->getMockCache();
1920 $mockCache->expects( $this->never() )->method( 'get' );
1921 $mockCache->expects( $this->never() )->method( 'set' );
1922 $mockCache->expects( $this->never() )->method( 'delete' );
1924 $store = $this->newWatchedItemStore(
1925 $this->getMockLoadBalancer( $mockDb ),
1927 $this->getMockReadOnlyMode()
1931 $store->resetNotificationTimestamp(
1932 $this->getMockNonAnonUserWithId( 1 ),
1933 Title
::newFromText( 'SomeDbKey' )
1938 public function testResetNotificationTimestamp_item() {
1939 $user = $this->getMockNonAnonUserWithId( 1 );
1940 $title = Title
::newFromText( 'SomeDbKey' );
1942 $mockDb = $this->getMockDb();
1943 $mockDb->expects( $this->once() )
1944 ->method( 'selectRow' )
1947 'wl_notificationtimestamp',
1950 'wl_namespace' => 0,
1951 'wl_title' => 'SomeDbKey',
1954 ->will( $this->returnValue(
1955 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1958 $mockCache = $this->getMockCache();
1959 $mockCache->expects( $this->never() )->method( 'get' );
1960 $mockCache->expects( $this->once() )
1964 $this->isInstanceOf( WatchedItem
::class )
1966 $mockCache->expects( $this->once() )
1967 ->method( 'delete' )
1968 ->with( '0:SomeDbKey:1' );
1970 $store = $this->newWatchedItemStore(
1971 $this->getMockLoadBalancer( $mockDb ),
1973 $this->getMockReadOnlyMode()
1976 // Note: This does not actually assert the job is correct
1977 $callableCallCounter = 0;
1978 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
1979 $callableCallCounter++
;
1980 $this->assertInternalType( 'callable', $callable );
1982 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1985 $store->resetNotificationTimestamp(
1990 $this->assertEquals( 1, $callableCallCounter );
1992 ScopedCallback
::consume( $scopedOverride );
1995 public function testResetNotificationTimestamp_noItemForced() {
1996 $user = $this->getMockNonAnonUserWithId( 1 );
1997 $title = Title
::newFromText( 'SomeDbKey' );
1999 $mockDb = $this->getMockDb();
2000 $mockDb->expects( $this->never() )
2001 ->method( 'selectRow' );
2003 $mockCache = $this->getMockCache();
2004 $mockDb->expects( $this->never() )
2006 $mockDb->expects( $this->never() )
2008 $mockDb->expects( $this->never() )
2009 ->method( 'delete' );
2011 $store = $this->newWatchedItemStore(
2012 $this->getMockLoadBalancer( $mockDb ),
2014 $this->getMockReadOnlyMode()
2017 // Note: This does not actually assert the job is correct
2018 $callableCallCounter = 0;
2019 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2020 $callableCallCounter++
;
2021 $this->assertInternalType( 'callable', $callable );
2023 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2026 $store->resetNotificationTimestamp(
2032 $this->assertEquals( 1, $callableCallCounter );
2034 ScopedCallback
::consume( $scopedOverride );
2038 * @param string $text
2041 * @return PHPUnit_Framework_MockObject_MockObject|Title
2043 private function getMockTitle( $text, $ns = 0 ) {
2044 $title = $this->createMock( Title
::class );
2045 $title->expects( $this->any() )
2046 ->method( 'getText' )
2047 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2048 $title->expects( $this->any() )
2049 ->method( 'getDbKey' )
2050 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2051 $title->expects( $this->any() )
2052 ->method( 'getNamespace' )
2053 ->will( $this->returnValue( $ns ) );
2057 private function verifyCallbackJob(
2059 LinkTarget
$expectedTitle,
2061 callable
$notificationTimestampCondition
2063 $this->assertInternalType( 'callable', $callback );
2065 $callbackReflector = new ReflectionFunction( $callback );
2066 $vars = $callbackReflector->getStaticVariables();
2067 $this->assertArrayHasKey( 'job', $vars );
2068 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2070 /** @var ActivityUpdateJob $job */
2071 $job = $vars['job'];
2072 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2073 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2075 $jobParams = $job->getParams();
2076 $this->assertArrayHasKey( 'type', $jobParams );
2077 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2078 $this->assertArrayHasKey( 'userid', $jobParams );
2079 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2080 $this->assertArrayHasKey( 'notifTime', $jobParams );
2081 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2084 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2085 $user = $this->getMockNonAnonUserWithId( 1 );
2087 $title = $this->getMockTitle( 'SomeTitle' );
2088 $title->expects( $this->once() )
2089 ->method( 'getNextRevisionID' )
2091 ->will( $this->returnValue( false ) );
2093 $mockDb = $this->getMockDb();
2094 $mockDb->expects( $this->never() )
2095 ->method( 'selectRow' );
2097 $mockCache = $this->getMockCache();
2098 $mockDb->expects( $this->never() )
2100 $mockDb->expects( $this->never() )
2102 $mockDb->expects( $this->never() )
2103 ->method( 'delete' );
2105 $store = $this->newWatchedItemStore(
2106 $this->getMockLoadBalancer( $mockDb ),
2108 $this->getMockReadOnlyMode()
2111 $callableCallCounter = 0;
2112 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2113 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2114 $callableCallCounter++
;
2115 $this->verifyCallbackJob(
2119 function ( $time ) {
2120 return $time === null;
2127 $store->resetNotificationTimestamp(
2134 $this->assertEquals( 1, $callableCallCounter );
2136 ScopedCallback
::consume( $scopedOverride );
2139 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2140 $user = $this->getMockNonAnonUserWithId( 1 );
2142 $title = $this->getMockTitle( 'SomeDbKey' );
2143 $title->expects( $this->once() )
2144 ->method( 'getNextRevisionID' )
2146 ->will( $this->returnValue( 33 ) );
2148 $mockDb = $this->getMockDb();
2149 $mockDb->expects( $this->once() )
2150 ->method( 'selectRow' )
2153 'wl_notificationtimestamp',
2156 'wl_namespace' => 0,
2157 'wl_title' => 'SomeDbKey',
2160 ->will( $this->returnValue(
2161 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2164 $mockCache = $this->getMockCache();
2165 $mockDb->expects( $this->never() )
2167 $mockDb->expects( $this->never() )
2169 $mockDb->expects( $this->never() )
2170 ->method( 'delete' );
2172 $store = $this->newWatchedItemStore(
2173 $this->getMockLoadBalancer( $mockDb ),
2175 $this->getMockReadOnlyMode()
2178 $addUpdateCallCounter = 0;
2179 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2180 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2181 $addUpdateCallCounter++
;
2182 $this->verifyCallbackJob(
2186 function ( $time ) {
2187 return $time !== null && $time > '20151212010101';
2193 $getTimestampCallCounter = 0;
2194 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2195 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2196 $getTimestampCallCounter++
;
2197 $this->assertEquals( $title, $titleParam );
2198 $this->assertEquals( $oldid, $oldidParam );
2203 $store->resetNotificationTimestamp(
2210 $this->assertEquals( 1, $addUpdateCallCounter );
2211 $this->assertEquals( 1, $getTimestampCallCounter );
2213 ScopedCallback
::consume( $scopedOverrideDeferred );
2214 ScopedCallback
::consume( $scopedOverrideRevision );
2217 public function testResetNotificationTimestamp_notWatchedPageForced() {
2218 $user = $this->getMockNonAnonUserWithId( 1 );
2220 $title = $this->getMockTitle( 'SomeDbKey' );
2221 $title->expects( $this->once() )
2222 ->method( 'getNextRevisionID' )
2224 ->will( $this->returnValue( 33 ) );
2226 $mockDb = $this->getMockDb();
2227 $mockDb->expects( $this->once() )
2228 ->method( 'selectRow' )
2231 'wl_notificationtimestamp',
2234 'wl_namespace' => 0,
2235 'wl_title' => 'SomeDbKey',
2238 ->will( $this->returnValue( false ) );
2240 $mockCache = $this->getMockCache();
2241 $mockDb->expects( $this->never() )
2243 $mockDb->expects( $this->never() )
2245 $mockDb->expects( $this->never() )
2246 ->method( 'delete' );
2248 $store = $this->newWatchedItemStore(
2249 $this->getMockLoadBalancer( $mockDb ),
2251 $this->getMockReadOnlyMode()
2254 $callableCallCounter = 0;
2255 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2256 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2257 $callableCallCounter++
;
2258 $this->verifyCallbackJob(
2262 function ( $time ) {
2263 return $time === null;
2270 $store->resetNotificationTimestamp(
2277 $this->assertEquals( 1, $callableCallCounter );
2279 ScopedCallback
::consume( $scopedOverride );
2282 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2283 $user = $this->getMockNonAnonUserWithId( 1 );
2285 $title = $this->getMockTitle( 'SomeDbKey' );
2286 $title->expects( $this->once() )
2287 ->method( 'getNextRevisionID' )
2289 ->will( $this->returnValue( 33 ) );
2291 $mockDb = $this->getMockDb();
2292 $mockDb->expects( $this->once() )
2293 ->method( 'selectRow' )
2296 'wl_notificationtimestamp',
2299 'wl_namespace' => 0,
2300 'wl_title' => 'SomeDbKey',
2303 ->will( $this->returnValue(
2304 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2307 $mockCache = $this->getMockCache();
2308 $mockDb->expects( $this->never() )
2310 $mockDb->expects( $this->never() )
2312 $mockDb->expects( $this->never() )
2313 ->method( 'delete' );
2315 $store = $this->newWatchedItemStore(
2316 $this->getMockLoadBalancer( $mockDb ),
2318 $this->getMockReadOnlyMode()
2321 $addUpdateCallCounter = 0;
2322 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2323 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2324 $addUpdateCallCounter++
;
2325 $this->verifyCallbackJob(
2329 function ( $time ) {
2330 return $time === '30151212010101';
2336 $getTimestampCallCounter = 0;
2337 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2338 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2339 $getTimestampCallCounter++
;
2340 $this->assertEquals( $title, $titleParam );
2341 $this->assertEquals( $oldid, $oldidParam );
2346 $store->resetNotificationTimestamp(
2353 $this->assertEquals( 1, $addUpdateCallCounter );
2354 $this->assertEquals( 1, $getTimestampCallCounter );
2356 ScopedCallback
::consume( $scopedOverrideDeferred );
2357 ScopedCallback
::consume( $scopedOverrideRevision );
2360 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2361 $user = $this->getMockNonAnonUserWithId( 1 );
2363 $title = $this->getMockTitle( 'SomeDbKey' );
2364 $title->expects( $this->once() )
2365 ->method( 'getNextRevisionID' )
2367 ->will( $this->returnValue( 33 ) );
2369 $mockDb = $this->getMockDb();
2370 $mockDb->expects( $this->once() )
2371 ->method( 'selectRow' )
2374 'wl_notificationtimestamp',
2377 'wl_namespace' => 0,
2378 'wl_title' => 'SomeDbKey',
2381 ->will( $this->returnValue(
2382 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2385 $mockCache = $this->getMockCache();
2386 $mockDb->expects( $this->never() )
2388 $mockDb->expects( $this->never() )
2390 $mockDb->expects( $this->never() )
2391 ->method( 'delete' );
2393 $store = $this->newWatchedItemStore(
2394 $this->getMockLoadBalancer( $mockDb ),
2396 $this->getMockReadOnlyMode()
2399 $addUpdateCallCounter = 0;
2400 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2401 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2402 $addUpdateCallCounter++
;
2403 $this->verifyCallbackJob(
2407 function ( $time ) {
2408 return $time === false;
2414 $getTimestampCallCounter = 0;
2415 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2416 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2417 $getTimestampCallCounter++
;
2418 $this->assertEquals( $title, $titleParam );
2419 $this->assertEquals( $oldid, $oldidParam );
2424 $store->resetNotificationTimestamp(
2431 $this->assertEquals( 1, $addUpdateCallCounter );
2432 $this->assertEquals( 1, $getTimestampCallCounter );
2434 ScopedCallback
::consume( $scopedOverrideDeferred );
2435 ScopedCallback
::consume( $scopedOverrideRevision );
2438 public function testSetNotificationTimestampsForUser_anonUser() {
2439 $store = $this->newWatchedItemStore(
2440 $this->getMockLoadBalancer( $this->getMockDb() ),
2441 $this->getMockCache(),
2442 $this->getMockReadOnlyMode()
2444 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2447 public function testSetNotificationTimestampsForUser_allRows() {
2448 $user = $this->getMockNonAnonUserWithId( 1 );
2449 $timestamp = '20100101010101';
2451 $mockDb = $this->getMockDb();
2452 $mockDb->expects( $this->once() )
2453 ->method( 'update' )
2456 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2459 ->will( $this->returnValue( true ) );
2460 $mockDb->expects( $this->exactly( 1 ) )
2461 ->method( 'timestamp' )
2462 ->will( $this->returnCallback( function ( $value ) {
2463 return 'TS' . $value . 'TS';
2466 $store = $this->newWatchedItemStore(
2467 $this->getMockLoadBalancer( $mockDb ),
2468 $this->getMockCache(),
2469 $this->getMockReadOnlyMode()
2473 $store->setNotificationTimestampsForUser( $user, $timestamp )
2477 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2478 $user = $this->getMockNonAnonUserWithId( 1 );
2481 $mockDb = $this->getMockDb();
2482 $mockDb->expects( $this->once() )
2483 ->method( 'update' )
2486 [ 'wl_notificationtimestamp' => null ],
2489 ->will( $this->returnValue( true ) );
2490 $mockDb->expects( $this->exactly( 0 ) )
2491 ->method( 'timestamp' )
2492 ->will( $this->returnCallback( function ( $value ) {
2493 return 'TS' . $value . 'TS';
2496 $store = $this->newWatchedItemStore(
2497 $this->getMockLoadBalancer( $mockDb ),
2498 $this->getMockCache(),
2499 $this->getMockReadOnlyMode()
2503 $store->setNotificationTimestampsForUser( $user, $timestamp )
2507 public function testSetNotificationTimestampsForUser_specificTargets() {
2508 $user = $this->getMockNonAnonUserWithId( 1 );
2509 $timestamp = '20100101010101';
2510 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2512 $mockDb = $this->getMockDb();
2513 $mockDb->expects( $this->once() )
2514 ->method( 'update' )
2517 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2518 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2520 ->will( $this->returnValue( true ) );
2521 $mockDb->expects( $this->exactly( 1 ) )
2522 ->method( 'timestamp' )
2523 ->will( $this->returnCallback( function ( $value ) {
2524 return 'TS' . $value . 'TS';
2526 $mockDb->expects( $this->once() )
2527 ->method( 'makeWhereFrom2d' )
2529 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2530 $this->isType( 'string' ),
2531 $this->isType( 'string' )
2533 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2535 $store = $this->newWatchedItemStore(
2536 $this->getMockLoadBalancer( $mockDb ),
2537 $this->getMockCache(),
2538 $this->getMockReadOnlyMode()
2542 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2546 public function testUpdateNotificationTimestamp_watchersExist() {
2547 $mockDb = $this->getMockDb();
2548 $mockDb->expects( $this->once() )
2549 ->method( 'selectFieldValues' )
2555 'wl_namespace' => 0,
2556 'wl_title' => 'SomeDbKey',
2557 'wl_notificationtimestamp IS NULL'
2560 ->will( $this->returnValue( [ '2', '3' ] ) );
2561 $mockDb->expects( $this->once() )
2562 ->method( 'update' )
2565 [ 'wl_notificationtimestamp' => null ],
2567 'wl_user' => [ 2, 3 ],
2568 'wl_namespace' => 0,
2569 'wl_title' => 'SomeDbKey',
2573 $mockCache = $this->getMockCache();
2574 $mockCache->expects( $this->never() )->method( 'set' );
2575 $mockCache->expects( $this->never() )->method( 'get' );
2576 $mockCache->expects( $this->never() )->method( 'delete' );
2578 $store = $this->newWatchedItemStore(
2579 $this->getMockLoadBalancer( $mockDb ),
2581 $this->getMockReadOnlyMode()
2584 $this->assertEquals(
2586 $store->updateNotificationTimestamp(
2587 $this->getMockNonAnonUserWithId( 1 ),
2588 new TitleValue( 0, 'SomeDbKey' ),
2594 public function testUpdateNotificationTimestamp_noWatchers() {
2595 $mockDb = $this->getMockDb();
2596 $mockDb->expects( $this->once() )
2597 ->method( 'selectFieldValues' )
2603 'wl_namespace' => 0,
2604 'wl_title' => 'SomeDbKey',
2605 'wl_notificationtimestamp IS NULL'
2609 $this->returnValue( [] )
2611 $mockDb->expects( $this->never() )
2612 ->method( 'update' );
2614 $mockCache = $this->getMockCache();
2615 $mockCache->expects( $this->never() )->method( 'set' );
2616 $mockCache->expects( $this->never() )->method( 'get' );
2617 $mockCache->expects( $this->never() )->method( 'delete' );
2619 $store = $this->newWatchedItemStore(
2620 $this->getMockLoadBalancer( $mockDb ),
2622 $this->getMockReadOnlyMode()
2625 $watchers = $store->updateNotificationTimestamp(
2626 $this->getMockNonAnonUserWithId( 1 ),
2627 new TitleValue( 0, 'SomeDbKey' ),
2630 $this->assertInternalType( 'array', $watchers );
2631 $this->assertEmpty( $watchers );
2634 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2635 $user = $this->getMockNonAnonUserWithId( 1 );
2636 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2638 $mockDb = $this->getMockDb();
2639 $mockDb->expects( $this->once() )
2640 ->method( 'selectRow' )
2641 ->will( $this->returnValue(
2642 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2644 $mockDb->expects( $this->once() )
2645 ->method( 'selectFieldValues' )
2647 $this->returnValue( [ '2', '3' ] )
2649 $mockDb->expects( $this->once() )
2650 ->method( 'update' );
2652 $mockCache = $this->getMockCache();
2653 $mockCache->expects( $this->once() )
2655 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2656 $mockCache->expects( $this->once() )
2658 ->with( '0:SomeDbKey:1' );
2659 $mockCache->expects( $this->once() )
2660 ->method( 'delete' )
2661 ->with( '0:SomeDbKey:1' );
2663 $store = $this->newWatchedItemStore(
2664 $this->getMockLoadBalancer( $mockDb ),
2666 $this->getMockReadOnlyMode()
2669 // This will add the item to the cache
2670 $store->getWatchedItem( $user, $titleValue );
2672 $store->updateNotificationTimestamp(
2673 $this->getMockNonAnonUserWithId( 1 ),