2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LoadBalancer
;
4 use Wikimedia\ScopedCallback
;
5 use Wikimedia\TestingAccessWrapper
;
10 * @covers WatchedItemStore
12 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
15 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
17 private function getMockDb() {
18 return $this->createMock( IDatabase
::class );
22 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
24 private function getMockLoadBalancer(
26 $expectedConnectionType = null
28 $mock = $this->getMockBuilder( LoadBalancer
::class )
29 ->disableOriginalConstructor()
31 if ( $expectedConnectionType !== null ) {
32 $mock->expects( $this->any() )
33 ->method( 'getConnectionRef' )
34 ->with( $expectedConnectionType )
35 ->will( $this->returnValue( $mockDb ) );
37 $mock->expects( $this->any() )
38 ->method( 'getConnectionRef' )
39 ->will( $this->returnValue( $mockDb ) );
45 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
47 private function getMockCache() {
48 $mock = $this->getMockBuilder( HashBagOStuff
::class )
49 ->disableOriginalConstructor()
50 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
52 $mock->expects( $this->any() )
54 ->will( $this->returnCallback( function () {
55 return implode( ':', func_get_args() );
61 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
63 private function getMockReadOnlyMode( $readOnly = false ) {
64 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
65 ->disableOriginalConstructor()
67 $mock->expects( $this->any() )
68 ->method( 'isReadOnly' )
69 ->will( $this->returnValue( $readOnly ) );
75 * @return PHPUnit_Framework_MockObject_MockObject|User
77 private function getMockNonAnonUserWithId( $id ) {
78 $mock = $this->createMock( User
::class );
79 $mock->expects( $this->any() )
81 ->will( $this->returnValue( false ) );
82 $mock->expects( $this->any() )
84 ->will( $this->returnValue( $id ) );
91 private function getAnonUser() {
92 return User
::newFromName( 'Anon_User' );
95 private function getFakeRow( array $rowValues ) {
96 $fakeRow = new stdClass();
97 foreach ( $rowValues as $valueName => $value ) {
98 $fakeRow->$valueName = $value;
103 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache,
104 ReadOnlyMode
$readOnlyMode
106 return new WatchedItemStore(
114 public function testClearWatchedItems() {
115 $user = $this->getMockNonAnonUserWithId( 7 );
117 $mockDb = $this->getMockDb();
118 $mockDb->expects( $this->once() )
119 ->method( 'selectField' )
124 'wl_user' => $user->getId(),
126 $this->isType( 'string' )
128 ->will( $this->returnValue( 12 ) );
129 $mockDb->expects( $this->once() )
134 $this->isType( 'string' )
137 $mockCache = $this->getMockCache();
138 $mockCache->expects( $this->never() )->method( 'get' );
139 $mockCache->expects( $this->never() )->method( 'set' );
140 $mockCache->expects( $this->once() )
144 $store = $this->newWatchedItemStore(
145 $this->getMockLoadBalancer( $mockDb ),
147 $this->getMockReadOnlyMode()
149 TestingAccessWrapper
::newFromObject( $store )
150 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
152 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
155 public function testClearWatchedItems_tooManyItemsWatched() {
156 $user = $this->getMockNonAnonUserWithId( 7 );
158 $mockDb = $this->getMockDb();
159 $mockDb->expects( $this->once() )
160 ->method( 'selectField' )
165 'wl_user' => $user->getId(),
167 $this->isType( 'string' )
169 ->will( $this->returnValue( 99999 ) );
171 $mockCache = $this->getMockCache();
172 $mockCache->expects( $this->never() )->method( 'get' );
173 $mockCache->expects( $this->never() )->method( 'set' );
174 $mockCache->expects( $this->never() )->method( 'delete' );
176 $store = $this->newWatchedItemStore(
177 $this->getMockLoadBalancer( $mockDb ),
179 $this->getMockReadOnlyMode()
182 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
185 public function testCountWatchedItems() {
186 $user = $this->getMockNonAnonUserWithId( 1 );
188 $mockDb = $this->getMockDb();
189 $mockDb->expects( $this->exactly( 1 ) )
190 ->method( 'selectField' )
195 'wl_user' => $user->getId(),
197 $this->isType( 'string' )
199 ->will( $this->returnValue( '12' ) );
201 $mockCache = $this->getMockCache();
202 $mockCache->expects( $this->never() )->method( 'get' );
203 $mockCache->expects( $this->never() )->method( 'set' );
204 $mockCache->expects( $this->never() )->method( 'delete' );
206 $store = $this->newWatchedItemStore(
207 $this->getMockLoadBalancer( $mockDb ),
209 $this->getMockReadOnlyMode()
212 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
215 public function testCountWatchers() {
216 $titleValue = new TitleValue( 0, 'SomeDbKey' );
218 $mockDb = $this->getMockDb();
219 $mockDb->expects( $this->exactly( 1 ) )
220 ->method( 'selectField' )
225 'wl_namespace' => $titleValue->getNamespace(),
226 'wl_title' => $titleValue->getDBkey(),
228 $this->isType( 'string' )
230 ->will( $this->returnValue( '7' ) );
232 $mockCache = $this->getMockCache();
233 $mockCache->expects( $this->never() )->method( 'get' );
234 $mockCache->expects( $this->never() )->method( 'set' );
235 $mockCache->expects( $this->never() )->method( 'delete' );
237 $store = $this->newWatchedItemStore(
238 $this->getMockLoadBalancer( $mockDb ),
240 $this->getMockReadOnlyMode()
243 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
246 public function testCountWatchersMultiple() {
248 new TitleValue( 0, 'SomeDbKey' ),
249 new TitleValue( 0, 'OtherDbKey' ),
250 new TitleValue( 1, 'AnotherDbKey' ),
253 $mockDb = $this->getMockDb();
256 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
257 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
258 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
261 $mockDb->expects( $this->once() )
262 ->method( 'makeWhereFrom2d' )
264 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
265 $this->isType( 'string' ),
266 $this->isType( 'string' )
268 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
269 $mockDb->expects( $this->once() )
273 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
274 [ 'makeWhereFrom2d return value' ],
275 $this->isType( 'string' ),
277 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
281 $this->returnValue( $dbResult )
284 $mockCache = $this->getMockCache();
285 $mockCache->expects( $this->never() )->method( 'get' );
286 $mockCache->expects( $this->never() )->method( 'set' );
287 $mockCache->expects( $this->never() )->method( 'delete' );
289 $store = $this->newWatchedItemStore(
290 $this->getMockLoadBalancer( $mockDb ),
292 $this->getMockReadOnlyMode()
296 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
297 1 => [ 'AnotherDbKey' => 500 ],
299 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
302 public function provideIntWithDbUnsafeVersion() {
305 [ "50; DROP TABLE watchlist;\n--" ],
310 * @dataProvider provideIntWithDbUnsafeVersion
312 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
314 new TitleValue( 0, 'SomeDbKey' ),
315 new TitleValue( 0, 'OtherDbKey' ),
316 new TitleValue( 1, 'AnotherDbKey' ),
319 $mockDb = $this->getMockDb();
322 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
323 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
324 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
327 $mockDb->expects( $this->once() )
328 ->method( 'makeWhereFrom2d' )
330 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
331 $this->isType( 'string' ),
332 $this->isType( 'string' )
334 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
335 $mockDb->expects( $this->once() )
339 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
340 [ 'makeWhereFrom2d return value' ],
341 $this->isType( 'string' ),
343 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
344 'HAVING' => 'COUNT(*) >= 50',
348 $this->returnValue( $dbResult )
351 $mockCache = $this->getMockCache();
352 $mockCache->expects( $this->never() )->method( 'get' );
353 $mockCache->expects( $this->never() )->method( 'set' );
354 $mockCache->expects( $this->never() )->method( 'delete' );
356 $store = $this->newWatchedItemStore(
357 $this->getMockLoadBalancer( $mockDb ),
359 $this->getMockReadOnlyMode()
363 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
364 1 => [ 'AnotherDbKey' => 500 ],
368 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
372 public function testCountVisitingWatchers() {
373 $titleValue = new TitleValue( 0, 'SomeDbKey' );
375 $mockDb = $this->getMockDb();
376 $mockDb->expects( $this->exactly( 1 ) )
377 ->method( 'selectField' )
382 'wl_namespace' => $titleValue->getNamespace(),
383 'wl_title' => $titleValue->getDBkey(),
384 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
386 $this->isType( 'string' )
388 ->will( $this->returnValue( '7' ) );
389 $mockDb->expects( $this->exactly( 1 ) )
390 ->method( 'addQuotes' )
391 ->will( $this->returnCallback( function ( $value ) {
394 $mockDb->expects( $this->exactly( 1 ) )
395 ->method( 'timestamp' )
396 ->will( $this->returnCallback( function ( $value ) {
397 return 'TS' . $value . 'TS';
400 $mockCache = $this->getMockCache();
401 $mockCache->expects( $this->never() )->method( 'set' );
402 $mockCache->expects( $this->never() )->method( 'get' );
403 $mockCache->expects( $this->never() )->method( 'delete' );
405 $store = $this->newWatchedItemStore(
406 $this->getMockLoadBalancer( $mockDb ),
408 $this->getMockReadOnlyMode()
411 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
414 public function testCountVisitingWatchersMultiple() {
415 $titleValuesWithThresholds = [
416 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
417 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
418 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
422 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
423 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
425 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
428 $mockDb = $this->getMockDb();
429 $mockDb->expects( $this->exactly( 2 * 3 ) )
430 ->method( 'addQuotes' )
431 ->will( $this->returnCallback( function ( $value ) {
434 $mockDb->expects( $this->exactly( 3 ) )
435 ->method( 'timestamp' )
436 ->will( $this->returnCallback( function ( $value ) {
437 return 'TS' . $value . 'TS';
439 $mockDb->expects( $this->any() )
440 ->method( 'makeList' )
442 $this->isType( 'array' ),
443 $this->isType( 'int' )
445 ->will( $this->returnCallback( function ( $a, $conj ) {
446 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
447 return implode( $sqlConj, array_map( function ( $s ) {
448 return '(' . $s . ')';
452 $mockDb->expects( $this->never() )
453 ->method( 'makeWhereFrom2d' );
456 '((wl_namespace = 0) AND (' .
457 "(((wl_title = 'SomeDbKey') AND (" .
458 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
460 "(wl_title = 'OtherDbKey') AND (" .
461 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
463 ') OR ((wl_namespace = 1) AND (' .
464 "(((wl_title = 'AnotherDbKey') AND (" .
465 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
467 $mockDb->expects( $this->once() )
471 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
473 $this->isType( 'string' ),
475 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
479 $this->returnValue( $dbResult )
482 $mockCache = $this->getMockCache();
483 $mockCache->expects( $this->never() )->method( 'get' );
484 $mockCache->expects( $this->never() )->method( 'set' );
485 $mockCache->expects( $this->never() )->method( 'delete' );
487 $store = $this->newWatchedItemStore(
488 $this->getMockLoadBalancer( $mockDb ),
490 $this->getMockReadOnlyMode()
494 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
495 1 => [ 'AnotherDbKey' => 500 ],
499 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
503 public function testCountVisitingWatchersMultiple_withMissingTargets() {
504 $titleValuesWithThresholds = [
505 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
506 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
507 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
508 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
509 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
513 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
514 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
516 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
519 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
522 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
525 $mockDb = $this->getMockDb();
526 $mockDb->expects( $this->exactly( 2 * 3 ) )
527 ->method( 'addQuotes' )
528 ->will( $this->returnCallback( function ( $value ) {
531 $mockDb->expects( $this->exactly( 3 ) )
532 ->method( 'timestamp' )
533 ->will( $this->returnCallback( function ( $value ) {
534 return 'TS' . $value . 'TS';
536 $mockDb->expects( $this->any() )
537 ->method( 'makeList' )
539 $this->isType( 'array' ),
540 $this->isType( 'int' )
542 ->will( $this->returnCallback( function ( $a, $conj ) {
543 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
544 return implode( $sqlConj, array_map( function ( $s ) {
545 return '(' . $s . ')';
549 $mockDb->expects( $this->once() )
550 ->method( 'makeWhereFrom2d' )
552 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
553 $this->isType( 'string' ),
554 $this->isType( 'string' )
556 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
559 '((wl_namespace = 0) AND (' .
560 "(((wl_title = 'SomeDbKey') AND (" .
561 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
563 "(wl_title = 'OtherDbKey') AND (" .
564 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
566 ') OR ((wl_namespace = 1) AND (' .
567 "(((wl_title = 'AnotherDbKey') AND (" .
568 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
571 '(makeWhereFrom2d return value)';
572 $mockDb->expects( $this->once() )
576 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
578 $this->isType( 'string' ),
580 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
584 $this->returnValue( $dbResult )
587 $mockCache = $this->getMockCache();
588 $mockCache->expects( $this->never() )->method( 'get' );
589 $mockCache->expects( $this->never() )->method( 'set' );
590 $mockCache->expects( $this->never() )->method( 'delete' );
592 $store = $this->newWatchedItemStore(
593 $this->getMockLoadBalancer( $mockDb ),
595 $this->getMockReadOnlyMode()
600 'SomeDbKey' => 100, 'OtherDbKey' => 300,
601 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
603 1 => [ 'AnotherDbKey' => 500 ],
607 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
612 * @dataProvider provideIntWithDbUnsafeVersion
614 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
615 $titleValuesWithThresholds = [
616 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
617 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
618 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
621 $mockDb = $this->getMockDb();
622 $mockDb->expects( $this->any() )
623 ->method( 'makeList' )
624 ->will( $this->returnValue( 'makeList return value' ) );
625 $mockDb->expects( $this->once() )
629 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
630 'makeList return value',
631 $this->isType( 'string' ),
633 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
634 'HAVING' => 'COUNT(*) >= 50',
638 $this->returnValue( [] )
641 $mockCache = $this->getMockCache();
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'set' );
644 $mockCache->expects( $this->never() )->method( 'delete' );
646 $store = $this->newWatchedItemStore(
647 $this->getMockLoadBalancer( $mockDb ),
649 $this->getMockReadOnlyMode()
653 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
654 1 => [ 'AnotherDbKey' => 0 ],
658 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
662 public function testCountUnreadNotifications() {
663 $user = $this->getMockNonAnonUserWithId( 1 );
665 $mockDb = $this->getMockDb();
666 $mockDb->expects( $this->exactly( 1 ) )
667 ->method( 'selectRowCount' )
672 "wl_notificationtimestamp IS NOT NULL",
675 $this->isType( 'string' )
677 ->will( $this->returnValue( '9' ) );
679 $mockCache = $this->getMockCache();
680 $mockCache->expects( $this->never() )->method( 'set' );
681 $mockCache->expects( $this->never() )->method( 'get' );
682 $mockCache->expects( $this->never() )->method( 'delete' );
684 $store = $this->newWatchedItemStore(
685 $this->getMockLoadBalancer( $mockDb ),
687 $this->getMockReadOnlyMode()
690 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
694 * @dataProvider provideIntWithDbUnsafeVersion
696 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
697 $user = $this->getMockNonAnonUserWithId( 1 );
699 $mockDb = $this->getMockDb();
700 $mockDb->expects( $this->exactly( 1 ) )
701 ->method( 'selectRowCount' )
706 "wl_notificationtimestamp IS NOT NULL",
709 $this->isType( 'string' ),
712 ->will( $this->returnValue( '50' ) );
714 $mockCache = $this->getMockCache();
715 $mockCache->expects( $this->never() )->method( 'set' );
716 $mockCache->expects( $this->never() )->method( 'get' );
717 $mockCache->expects( $this->never() )->method( 'delete' );
719 $store = $this->newWatchedItemStore(
720 $this->getMockLoadBalancer( $mockDb ),
722 $this->getMockReadOnlyMode()
727 $store->countUnreadNotifications( $user, $limit )
732 * @dataProvider provideIntWithDbUnsafeVersion
734 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
735 $user = $this->getMockNonAnonUserWithId( 1 );
737 $mockDb = $this->getMockDb();
738 $mockDb->expects( $this->exactly( 1 ) )
739 ->method( 'selectRowCount' )
744 "wl_notificationtimestamp IS NOT NULL",
747 $this->isType( 'string' ),
750 ->will( $this->returnValue( '9' ) );
752 $mockCache = $this->getMockCache();
753 $mockCache->expects( $this->never() )->method( 'set' );
754 $mockCache->expects( $this->never() )->method( 'get' );
755 $mockCache->expects( $this->never() )->method( 'delete' );
757 $store = $this->newWatchedItemStore(
758 $this->getMockLoadBalancer( $mockDb ),
760 $this->getMockReadOnlyMode()
765 $store->countUnreadNotifications( $user, $limit )
769 public function testDuplicateEntry_nothingToDuplicate() {
770 $mockDb = $this->getMockDb();
771 $mockDb->expects( $this->once() )
777 'wl_notificationtimestamp',
781 'wl_title' => 'Old_Title',
783 'WatchedItemStore::duplicateEntry',
786 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
788 $store = $this->newWatchedItemStore(
789 $this->getMockLoadBalancer( $mockDb ),
790 $this->getMockCache(),
791 $this->getMockReadOnlyMode()
794 $store->duplicateEntry(
795 Title
::newFromText( 'Old_Title' ),
796 Title
::newFromText( 'New_Title' )
800 public function testDuplicateEntry_somethingToDuplicate() {
802 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
803 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
806 $mockDb = $this->getMockDb();
807 $mockDb->expects( $this->at( 0 ) )
813 'wl_notificationtimestamp',
817 'wl_title' => 'Old_Title',
820 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
821 $mockDb->expects( $this->at( 1 ) )
822 ->method( 'replace' )
825 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
830 'wl_title' => 'New_Title',
831 'wl_notificationtimestamp' => '20151212010101',
836 'wl_title' => 'New_Title',
837 'wl_notificationtimestamp' => null,
840 $this->isType( 'string' )
843 $mockCache = $this->getMockCache();
844 $mockCache->expects( $this->never() )->method( 'get' );
845 $mockCache->expects( $this->never() )->method( 'delete' );
847 $store = $this->newWatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
850 $this->getMockReadOnlyMode()
853 $store->duplicateEntry(
854 Title
::newFromText( 'Old_Title' ),
855 Title
::newFromText( 'New_Title' )
859 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
860 $mockDb = $this->getMockDb();
861 $mockDb->expects( $this->at( 0 ) )
867 'wl_notificationtimestamp',
871 'wl_title' => 'Old_Title',
874 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
875 $mockDb->expects( $this->at( 1 ) )
881 'wl_notificationtimestamp',
885 'wl_title' => 'Old_Title',
888 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
890 $mockCache = $this->getMockCache();
891 $mockCache->expects( $this->never() )->method( 'get' );
892 $mockCache->expects( $this->never() )->method( 'delete' );
894 $store = $this->newWatchedItemStore(
895 $this->getMockLoadBalancer( $mockDb ),
897 $this->getMockReadOnlyMode()
900 $store->duplicateAllAssociatedEntries(
901 Title
::newFromText( 'Old_Title' ),
902 Title
::newFromText( 'New_Title' )
906 public function provideLinkTargetPairs() {
908 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
909 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
914 * @dataProvider provideLinkTargetPairs
916 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
917 LinkTarget
$oldTarget,
918 LinkTarget
$newTarget
921 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
924 $mockDb = $this->getMockDb();
925 $mockDb->expects( $this->at( 0 ) )
931 'wl_notificationtimestamp',
934 'wl_namespace' => $oldTarget->getNamespace(),
935 'wl_title' => $oldTarget->getDBkey(),
938 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
939 $mockDb->expects( $this->at( 1 ) )
940 ->method( 'replace' )
943 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
947 'wl_namespace' => $newTarget->getNamespace(),
948 'wl_title' => $newTarget->getDBkey(),
949 'wl_notificationtimestamp' => '20151212010101',
952 $this->isType( 'string' )
954 $mockDb->expects( $this->at( 2 ) )
960 'wl_notificationtimestamp',
963 'wl_namespace' => $oldTarget->getNamespace() +
1,
964 'wl_title' => $oldTarget->getDBkey(),
967 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
968 $mockDb->expects( $this->at( 3 ) )
969 ->method( 'replace' )
972 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
976 'wl_namespace' => $newTarget->getNamespace() +
1,
977 'wl_title' => $newTarget->getDBkey(),
978 'wl_notificationtimestamp' => '20151212010101',
981 $this->isType( 'string' )
984 $mockCache = $this->getMockCache();
985 $mockCache->expects( $this->never() )->method( 'get' );
986 $mockCache->expects( $this->never() )->method( 'delete' );
988 $store = $this->newWatchedItemStore(
989 $this->getMockLoadBalancer( $mockDb ),
991 $this->getMockReadOnlyMode()
994 $store->duplicateAllAssociatedEntries(
1000 public function testAddWatch_nonAnonymousUser() {
1001 $mockDb = $this->getMockDb();
1002 $mockDb->expects( $this->once() )
1003 ->method( 'insert' )
1009 'wl_namespace' => 0,
1010 'wl_title' => 'Some_Page',
1011 'wl_notificationtimestamp' => null,
1016 $mockCache = $this->getMockCache();
1017 $mockCache->expects( $this->once() )
1018 ->method( 'delete' )
1019 ->with( '0:Some_Page:1' );
1021 $store = $this->newWatchedItemStore(
1022 $this->getMockLoadBalancer( $mockDb ),
1024 $this->getMockReadOnlyMode()
1028 $this->getMockNonAnonUserWithId( 1 ),
1029 Title
::newFromText( 'Some_Page' )
1033 public function testAddWatch_anonymousUser() {
1034 $mockDb = $this->getMockDb();
1035 $mockDb->expects( $this->never() )
1036 ->method( 'insert' );
1038 $mockCache = $this->getMockCache();
1039 $mockCache->expects( $this->never() )
1040 ->method( 'delete' );
1042 $store = $this->newWatchedItemStore(
1043 $this->getMockLoadBalancer( $mockDb ),
1045 $this->getMockReadOnlyMode()
1049 $this->getAnonUser(),
1050 Title
::newFromText( 'Some_Page' )
1054 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1055 $store = $this->newWatchedItemStore(
1056 $this->getMockLoadBalancer( $this->getMockDb() ),
1057 $this->getMockCache(),
1058 $this->getMockReadOnlyMode( true )
1062 $store->addWatchBatchForUser(
1063 $this->getMockNonAnonUserWithId( 1 ),
1064 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1069 public function testAddWatchBatchForUser_nonAnonymousUser() {
1070 $mockDb = $this->getMockDb();
1071 $mockDb->expects( $this->once() )
1072 ->method( 'insert' )
1078 'wl_namespace' => 0,
1079 'wl_title' => 'Some_Page',
1080 'wl_notificationtimestamp' => null,
1084 'wl_namespace' => 1,
1085 'wl_title' => 'Some_Page',
1086 'wl_notificationtimestamp' => null,
1091 $mockCache = $this->getMockCache();
1092 $mockCache->expects( $this->exactly( 2 ) )
1093 ->method( 'delete' );
1094 $mockCache->expects( $this->at( 1 ) )
1095 ->method( 'delete' )
1096 ->with( '0:Some_Page:1' );
1097 $mockCache->expects( $this->at( 3 ) )
1098 ->method( 'delete' )
1099 ->with( '1:Some_Page:1' );
1101 $store = $this->newWatchedItemStore(
1102 $this->getMockLoadBalancer( $mockDb ),
1104 $this->getMockReadOnlyMode()
1107 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1110 $store->addWatchBatchForUser(
1112 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1117 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1118 $mockDb = $this->getMockDb();
1119 $mockDb->expects( $this->never() )
1120 ->method( 'insert' );
1122 $mockCache = $this->getMockCache();
1123 $mockCache->expects( $this->never() )
1124 ->method( 'delete' );
1126 $store = $this->newWatchedItemStore(
1127 $this->getMockLoadBalancer( $mockDb ),
1129 $this->getMockReadOnlyMode()
1133 $store->addWatchBatchForUser(
1134 $this->getAnonUser(),
1135 [ new TitleValue( 0, 'Other_Page' ) ]
1140 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1141 $user = $this->getMockNonAnonUserWithId( 1 );
1142 $mockDb = $this->getMockDb();
1143 $mockDb->expects( $this->never() )
1144 ->method( 'insert' );
1146 $mockCache = $this->getMockCache();
1147 $mockCache->expects( $this->never() )
1148 ->method( 'delete' );
1150 $store = $this->newWatchedItemStore(
1151 $this->getMockLoadBalancer( $mockDb ),
1153 $this->getMockReadOnlyMode()
1157 $store->addWatchBatchForUser( $user, [] )
1161 public function testLoadWatchedItem_existingItem() {
1162 $mockDb = $this->getMockDb();
1163 $mockDb->expects( $this->once() )
1164 ->method( 'selectRow' )
1167 'wl_notificationtimestamp',
1170 'wl_namespace' => 0,
1171 'wl_title' => 'SomeDbKey',
1174 ->will( $this->returnValue(
1175 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1178 $mockCache = $this->getMockCache();
1179 $mockCache->expects( $this->once() )
1185 $store = $this->newWatchedItemStore(
1186 $this->getMockLoadBalancer( $mockDb ),
1188 $this->getMockReadOnlyMode()
1191 $watchedItem = $store->loadWatchedItem(
1192 $this->getMockNonAnonUserWithId( 1 ),
1193 new TitleValue( 0, 'SomeDbKey' )
1195 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1196 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1197 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1198 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1201 public function testLoadWatchedItem_noItem() {
1202 $mockDb = $this->getMockDb();
1203 $mockDb->expects( $this->once() )
1204 ->method( 'selectRow' )
1207 'wl_notificationtimestamp',
1210 'wl_namespace' => 0,
1211 'wl_title' => 'SomeDbKey',
1214 ->will( $this->returnValue( [] ) );
1216 $mockCache = $this->getMockCache();
1217 $mockCache->expects( $this->never() )->method( 'get' );
1218 $mockCache->expects( $this->never() )->method( 'delete' );
1220 $store = $this->newWatchedItemStore(
1221 $this->getMockLoadBalancer( $mockDb ),
1223 $this->getMockReadOnlyMode()
1227 $store->loadWatchedItem(
1228 $this->getMockNonAnonUserWithId( 1 ),
1229 new TitleValue( 0, 'SomeDbKey' )
1234 public function testLoadWatchedItem_anonymousUser() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->never() )
1237 ->method( 'selectRow' );
1239 $mockCache = $this->getMockCache();
1240 $mockCache->expects( $this->never() )->method( 'get' );
1241 $mockCache->expects( $this->never() )->method( 'delete' );
1243 $store = $this->newWatchedItemStore(
1244 $this->getMockLoadBalancer( $mockDb ),
1246 $this->getMockReadOnlyMode()
1250 $store->loadWatchedItem(
1251 $this->getAnonUser(),
1252 new TitleValue( 0, 'SomeDbKey' )
1257 public function testRemoveWatch_existingItem() {
1258 $mockDb = $this->getMockDb();
1259 $mockDb->expects( $this->once() )
1260 ->method( 'delete' )
1265 'wl_namespace' => 0,
1266 'wl_title' => 'SomeDbKey',
1269 $mockDb->expects( $this->once() )
1270 ->method( 'affectedRows' )
1271 ->will( $this->returnValue( 1 ) );
1273 $mockCache = $this->getMockCache();
1274 $mockCache->expects( $this->never() )->method( 'get' );
1275 $mockCache->expects( $this->once() )
1276 ->method( 'delete' )
1277 ->with( '0:SomeDbKey:1' );
1279 $store = $this->newWatchedItemStore(
1280 $this->getMockLoadBalancer( $mockDb ),
1282 $this->getMockReadOnlyMode()
1286 $store->removeWatch(
1287 $this->getMockNonAnonUserWithId( 1 ),
1288 new TitleValue( 0, 'SomeDbKey' )
1293 public function testRemoveWatch_noItem() {
1294 $mockDb = $this->getMockDb();
1295 $mockDb->expects( $this->once() )
1296 ->method( 'delete' )
1301 'wl_namespace' => 0,
1302 'wl_title' => 'SomeDbKey',
1305 $mockDb->expects( $this->once() )
1306 ->method( 'affectedRows' )
1307 ->will( $this->returnValue( 0 ) );
1309 $mockCache = $this->getMockCache();
1310 $mockCache->expects( $this->never() )->method( 'get' );
1311 $mockCache->expects( $this->once() )
1312 ->method( 'delete' )
1313 ->with( '0:SomeDbKey:1' );
1315 $store = $this->newWatchedItemStore(
1316 $this->getMockLoadBalancer( $mockDb ),
1318 $this->getMockReadOnlyMode()
1322 $store->removeWatch(
1323 $this->getMockNonAnonUserWithId( 1 ),
1324 new TitleValue( 0, 'SomeDbKey' )
1329 public function testRemoveWatch_anonymousUser() {
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->never() )
1332 ->method( 'delete' );
1334 $mockCache = $this->getMockCache();
1335 $mockCache->expects( $this->never() )->method( 'get' );
1336 $mockCache->expects( $this->never() )
1337 ->method( 'delete' );
1339 $store = $this->newWatchedItemStore(
1340 $this->getMockLoadBalancer( $mockDb ),
1342 $this->getMockReadOnlyMode()
1346 $store->removeWatch(
1347 $this->getAnonUser(),
1348 new TitleValue( 0, 'SomeDbKey' )
1353 public function testGetWatchedItem_existingItem() {
1354 $mockDb = $this->getMockDb();
1355 $mockDb->expects( $this->once() )
1356 ->method( 'selectRow' )
1359 'wl_notificationtimestamp',
1362 'wl_namespace' => 0,
1363 'wl_title' => 'SomeDbKey',
1366 ->will( $this->returnValue(
1367 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'delete' );
1372 $mockCache->expects( $this->once() )
1377 ->will( $this->returnValue( null ) );
1378 $mockCache->expects( $this->once() )
1384 $store = $this->newWatchedItemStore(
1385 $this->getMockLoadBalancer( $mockDb ),
1387 $this->getMockReadOnlyMode()
1390 $watchedItem = $store->getWatchedItem(
1391 $this->getMockNonAnonUserWithId( 1 ),
1392 new TitleValue( 0, 'SomeDbKey' )
1394 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1395 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1396 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1397 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1400 public function testGetWatchedItem_cachedItem() {
1401 $mockDb = $this->getMockDb();
1402 $mockDb->expects( $this->never() )
1403 ->method( 'selectRow' );
1405 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1406 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1407 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1409 $mockCache = $this->getMockCache();
1410 $mockCache->expects( $this->never() )->method( 'delete' );
1411 $mockCache->expects( $this->never() )->method( 'set' );
1412 $mockCache->expects( $this->once() )
1417 ->will( $this->returnValue( $cachedItem ) );
1419 $store = $this->newWatchedItemStore(
1420 $this->getMockLoadBalancer( $mockDb ),
1422 $this->getMockReadOnlyMode()
1425 $this->assertEquals(
1427 $store->getWatchedItem(
1434 public function testGetWatchedItem_noItem() {
1435 $mockDb = $this->getMockDb();
1436 $mockDb->expects( $this->once() )
1437 ->method( 'selectRow' )
1440 'wl_notificationtimestamp',
1443 'wl_namespace' => 0,
1444 'wl_title' => 'SomeDbKey',
1447 ->will( $this->returnValue( [] ) );
1449 $mockCache = $this->getMockCache();
1450 $mockCache->expects( $this->never() )->method( 'set' );
1451 $mockCache->expects( $this->never() )->method( 'delete' );
1452 $mockCache->expects( $this->once() )
1454 ->with( '0:SomeDbKey:1' )
1455 ->will( $this->returnValue( false ) );
1457 $store = $this->newWatchedItemStore(
1458 $this->getMockLoadBalancer( $mockDb ),
1460 $this->getMockReadOnlyMode()
1464 $store->getWatchedItem(
1465 $this->getMockNonAnonUserWithId( 1 ),
1466 new TitleValue( 0, 'SomeDbKey' )
1471 public function testGetWatchedItem_anonymousUser() {
1472 $mockDb = $this->getMockDb();
1473 $mockDb->expects( $this->never() )
1474 ->method( 'selectRow' );
1476 $mockCache = $this->getMockCache();
1477 $mockCache->expects( $this->never() )->method( 'set' );
1478 $mockCache->expects( $this->never() )->method( 'get' );
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1481 $store = $this->newWatchedItemStore(
1482 $this->getMockLoadBalancer( $mockDb ),
1484 $this->getMockReadOnlyMode()
1488 $store->getWatchedItem(
1489 $this->getAnonUser(),
1490 new TitleValue( 0, 'SomeDbKey' )
1495 public function testGetWatchedItemsForUser() {
1496 $mockDb = $this->getMockDb();
1497 $mockDb->expects( $this->once() )
1498 ->method( 'select' )
1501 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1504 ->will( $this->returnValue( [
1505 $this->getFakeRow( [
1506 'wl_namespace' => 0,
1507 'wl_title' => 'Foo1',
1508 'wl_notificationtimestamp' => '20151212010101',
1510 $this->getFakeRow( [
1511 'wl_namespace' => 1,
1512 'wl_title' => 'Foo2',
1513 'wl_notificationtimestamp' => null,
1517 $mockCache = $this->getMockCache();
1518 $mockCache->expects( $this->never() )->method( 'delete' );
1519 $mockCache->expects( $this->never() )->method( 'get' );
1520 $mockCache->expects( $this->never() )->method( 'set' );
1522 $store = $this->newWatchedItemStore(
1523 $this->getMockLoadBalancer( $mockDb ),
1525 $this->getMockReadOnlyMode()
1527 $user = $this->getMockNonAnonUserWithId( 1 );
1529 $watchedItems = $store->getWatchedItemsForUser( $user );
1531 $this->assertInternalType( 'array', $watchedItems );
1532 $this->assertCount( 2, $watchedItems );
1533 foreach ( $watchedItems as $watchedItem ) {
1534 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1536 $this->assertEquals(
1537 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1540 $this->assertEquals(
1541 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1546 public function provideDbTypes() {
1548 [ false, DB_REPLICA
],
1549 [ true, DB_MASTER
],
1554 * @dataProvider provideDbTypes
1556 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1557 $mockDb = $this->getMockDb();
1558 $mockCache = $this->getMockCache();
1559 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1560 $user = $this->getMockNonAnonUserWithId( 1 );
1562 $mockDb->expects( $this->once() )
1563 ->method( 'select' )
1566 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1568 $this->isType( 'string' ),
1569 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1571 ->will( $this->returnValue( [] ) );
1573 $store = $this->newWatchedItemStore(
1576 $this->getMockReadOnlyMode()
1579 $watchedItems = $store->getWatchedItemsForUser(
1581 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1583 $this->assertEquals( [], $watchedItems );
1586 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1587 $store = $this->newWatchedItemStore(
1588 $this->getMockLoadBalancer( $this->getMockDb() ),
1589 $this->getMockCache(),
1590 $this->getMockReadOnlyMode()
1593 $this->setExpectedException( InvalidArgumentException
::class );
1594 $store->getWatchedItemsForUser(
1595 $this->getMockNonAnonUserWithId( 1 ),
1600 public function testIsWatchedItem_existingItem() {
1601 $mockDb = $this->getMockDb();
1602 $mockDb->expects( $this->once() )
1603 ->method( 'selectRow' )
1606 'wl_notificationtimestamp',
1609 'wl_namespace' => 0,
1610 'wl_title' => 'SomeDbKey',
1613 ->will( $this->returnValue(
1614 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1617 $mockCache = $this->getMockCache();
1618 $mockCache->expects( $this->never() )->method( 'delete' );
1619 $mockCache->expects( $this->once() )
1621 ->with( '0:SomeDbKey:1' )
1622 ->will( $this->returnValue( false ) );
1623 $mockCache->expects( $this->once() )
1629 $store = $this->newWatchedItemStore(
1630 $this->getMockLoadBalancer( $mockDb ),
1632 $this->getMockReadOnlyMode()
1637 $this->getMockNonAnonUserWithId( 1 ),
1638 new TitleValue( 0, 'SomeDbKey' )
1643 public function testIsWatchedItem_noItem() {
1644 $mockDb = $this->getMockDb();
1645 $mockDb->expects( $this->once() )
1646 ->method( 'selectRow' )
1649 'wl_notificationtimestamp',
1652 'wl_namespace' => 0,
1653 'wl_title' => 'SomeDbKey',
1656 ->will( $this->returnValue( [] ) );
1658 $mockCache = $this->getMockCache();
1659 $mockCache->expects( $this->never() )->method( 'set' );
1660 $mockCache->expects( $this->never() )->method( 'delete' );
1661 $mockCache->expects( $this->once() )
1663 ->with( '0:SomeDbKey:1' )
1664 ->will( $this->returnValue( false ) );
1666 $store = $this->newWatchedItemStore(
1667 $this->getMockLoadBalancer( $mockDb ),
1669 $this->getMockReadOnlyMode()
1674 $this->getMockNonAnonUserWithId( 1 ),
1675 new TitleValue( 0, 'SomeDbKey' )
1680 public function testIsWatchedItem_anonymousUser() {
1681 $mockDb = $this->getMockDb();
1682 $mockDb->expects( $this->never() )
1683 ->method( 'selectRow' );
1685 $mockCache = $this->getMockCache();
1686 $mockCache->expects( $this->never() )->method( 'set' );
1687 $mockCache->expects( $this->never() )->method( 'get' );
1688 $mockCache->expects( $this->never() )->method( 'delete' );
1690 $store = $this->newWatchedItemStore(
1691 $this->getMockLoadBalancer( $mockDb ),
1693 $this->getMockReadOnlyMode()
1698 $this->getAnonUser(),
1699 new TitleValue( 0, 'SomeDbKey' )
1704 public function testGetNotificationTimestampsBatch() {
1706 new TitleValue( 0, 'SomeDbKey' ),
1707 new TitleValue( 1, 'AnotherDbKey' ),
1710 $mockDb = $this->getMockDb();
1712 $this->getFakeRow( [
1713 'wl_namespace' => '0',
1714 'wl_title' => 'SomeDbKey',
1715 'wl_notificationtimestamp' => '20151212010101',
1719 'wl_namespace' => '1',
1720 'wl_title' => 'AnotherDbKey',
1721 'wl_notificationtimestamp' => null,
1726 $mockDb->expects( $this->once() )
1727 ->method( 'makeWhereFrom2d' )
1729 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1730 $this->isType( 'string' ),
1731 $this->isType( 'string' )
1733 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1734 $mockDb->expects( $this->once() )
1735 ->method( 'select' )
1738 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1740 'makeWhereFrom2d return value',
1743 $this->isType( 'string' )
1745 ->will( $this->returnValue( $dbResult ) );
1747 $mockCache = $this->getMockCache();
1748 $mockCache->expects( $this->exactly( 2 ) )
1751 [ '0:SomeDbKey:1' ],
1752 [ '1:AnotherDbKey:1' ]
1754 ->will( $this->returnValue( null ) );
1755 $mockCache->expects( $this->never() )->method( 'set' );
1756 $mockCache->expects( $this->never() )->method( 'delete' );
1758 $store = $this->newWatchedItemStore(
1759 $this->getMockLoadBalancer( $mockDb ),
1761 $this->getMockReadOnlyMode()
1764 $this->assertEquals(
1766 0 => [ 'SomeDbKey' => '20151212010101', ],
1767 1 => [ 'AnotherDbKey' => null, ],
1769 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1773 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1775 new TitleValue( 0, 'OtherDbKey' ),
1778 $mockDb = $this->getMockDb();
1780 $mockDb->expects( $this->once() )
1781 ->method( 'makeWhereFrom2d' )
1783 [ [ 'OtherDbKey' => 1 ] ],
1784 $this->isType( 'string' ),
1785 $this->isType( 'string' )
1787 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1788 $mockDb->expects( $this->once() )
1789 ->method( 'select' )
1792 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1794 'makeWhereFrom2d return value',
1797 $this->isType( 'string' )
1799 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1801 $mockCache = $this->getMockCache();
1802 $mockCache->expects( $this->once() )
1804 ->with( '0:OtherDbKey:1' )
1805 ->will( $this->returnValue( null ) );
1806 $mockCache->expects( $this->never() )->method( 'set' );
1807 $mockCache->expects( $this->never() )->method( 'delete' );
1809 $store = $this->newWatchedItemStore(
1810 $this->getMockLoadBalancer( $mockDb ),
1812 $this->getMockReadOnlyMode()
1815 $this->assertEquals(
1817 0 => [ 'OtherDbKey' => false, ],
1819 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1823 public function testGetNotificationTimestampsBatch_cachedItem() {
1825 new TitleValue( 0, 'SomeDbKey' ),
1826 new TitleValue( 1, 'AnotherDbKey' ),
1829 $user = $this->getMockNonAnonUserWithId( 1 );
1830 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1832 $mockDb = $this->getMockDb();
1834 $mockDb->expects( $this->once() )
1835 ->method( 'makeWhereFrom2d' )
1837 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1838 $this->isType( 'string' ),
1839 $this->isType( 'string' )
1841 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1842 $mockDb->expects( $this->once() )
1843 ->method( 'select' )
1846 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1848 'makeWhereFrom2d return value',
1851 $this->isType( 'string' )
1853 ->will( $this->returnValue( [
1855 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1859 $mockCache = $this->getMockCache();
1860 $mockCache->expects( $this->at( 1 ) )
1862 ->with( '0:SomeDbKey:1' )
1863 ->will( $this->returnValue( $cachedItem ) );
1864 $mockCache->expects( $this->at( 3 ) )
1866 ->with( '1:AnotherDbKey:1' )
1867 ->will( $this->returnValue( null ) );
1868 $mockCache->expects( $this->never() )->method( 'set' );
1869 $mockCache->expects( $this->never() )->method( 'delete' );
1871 $store = $this->newWatchedItemStore(
1872 $this->getMockLoadBalancer( $mockDb ),
1874 $this->getMockReadOnlyMode()
1877 $this->assertEquals(
1879 0 => [ 'SomeDbKey' => '20151212010101', ],
1880 1 => [ 'AnotherDbKey' => null, ],
1882 $store->getNotificationTimestampsBatch( $user, $targets )
1886 public function testGetNotificationTimestampsBatch_allItemsCached() {
1888 new TitleValue( 0, 'SomeDbKey' ),
1889 new TitleValue( 1, 'AnotherDbKey' ),
1892 $user = $this->getMockNonAnonUserWithId( 1 );
1894 new WatchedItem( $user, $targets[0], '20151212010101' ),
1895 new WatchedItem( $user, $targets[1], null ),
1897 $mockDb = $this->getMockDb();
1898 $mockDb->expects( $this->never() )->method( $this->anything() );
1900 $mockCache = $this->getMockCache();
1901 $mockCache->expects( $this->at( 1 ) )
1903 ->with( '0:SomeDbKey:1' )
1904 ->will( $this->returnValue( $cachedItems[0] ) );
1905 $mockCache->expects( $this->at( 3 ) )
1907 ->with( '1:AnotherDbKey:1' )
1908 ->will( $this->returnValue( $cachedItems[1] ) );
1909 $mockCache->expects( $this->never() )->method( 'set' );
1910 $mockCache->expects( $this->never() )->method( 'delete' );
1912 $store = $this->newWatchedItemStore(
1913 $this->getMockLoadBalancer( $mockDb ),
1915 $this->getMockReadOnlyMode()
1918 $this->assertEquals(
1920 0 => [ 'SomeDbKey' => '20151212010101', ],
1921 1 => [ 'AnotherDbKey' => null, ],
1923 $store->getNotificationTimestampsBatch( $user, $targets )
1927 public function testGetNotificationTimestampsBatch_anonymousUser() {
1929 new TitleValue( 0, 'SomeDbKey' ),
1930 new TitleValue( 1, 'AnotherDbKey' ),
1933 $mockDb = $this->getMockDb();
1934 $mockDb->expects( $this->never() )->method( $this->anything() );
1936 $mockCache = $this->getMockCache();
1937 $mockCache->expects( $this->never() )->method( $this->anything() );
1939 $store = $this->newWatchedItemStore(
1940 $this->getMockLoadBalancer( $mockDb ),
1942 $this->getMockReadOnlyMode()
1945 $this->assertEquals(
1947 0 => [ 'SomeDbKey' => false, ],
1948 1 => [ 'AnotherDbKey' => false, ],
1950 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1954 public function testResetNotificationTimestamp_anonymousUser() {
1955 $mockDb = $this->getMockDb();
1956 $mockDb->expects( $this->never() )
1957 ->method( 'selectRow' );
1959 $mockCache = $this->getMockCache();
1960 $mockCache->expects( $this->never() )->method( 'get' );
1961 $mockCache->expects( $this->never() )->method( 'set' );
1962 $mockCache->expects( $this->never() )->method( 'delete' );
1964 $store = $this->newWatchedItemStore(
1965 $this->getMockLoadBalancer( $mockDb ),
1967 $this->getMockReadOnlyMode()
1971 $store->resetNotificationTimestamp(
1972 $this->getAnonUser(),
1973 Title
::newFromText( 'SomeDbKey' )
1978 public function testResetNotificationTimestamp_noItem() {
1979 $mockDb = $this->getMockDb();
1980 $mockDb->expects( $this->once() )
1981 ->method( 'selectRow' )
1984 'wl_notificationtimestamp',
1987 'wl_namespace' => 0,
1988 'wl_title' => 'SomeDbKey',
1991 ->will( $this->returnValue( [] ) );
1993 $mockCache = $this->getMockCache();
1994 $mockCache->expects( $this->never() )->method( 'get' );
1995 $mockCache->expects( $this->never() )->method( 'set' );
1996 $mockCache->expects( $this->never() )->method( 'delete' );
1998 $store = $this->newWatchedItemStore(
1999 $this->getMockLoadBalancer( $mockDb ),
2001 $this->getMockReadOnlyMode()
2005 $store->resetNotificationTimestamp(
2006 $this->getMockNonAnonUserWithId( 1 ),
2007 Title
::newFromText( 'SomeDbKey' )
2012 public function testResetNotificationTimestamp_item() {
2013 $user = $this->getMockNonAnonUserWithId( 1 );
2014 $title = Title
::newFromText( 'SomeDbKey' );
2016 $mockDb = $this->getMockDb();
2017 $mockDb->expects( $this->once() )
2018 ->method( 'selectRow' )
2021 'wl_notificationtimestamp',
2024 'wl_namespace' => 0,
2025 'wl_title' => 'SomeDbKey',
2028 ->will( $this->returnValue(
2029 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2032 $mockCache = $this->getMockCache();
2033 $mockCache->expects( $this->never() )->method( 'get' );
2034 $mockCache->expects( $this->once() )
2038 $this->isInstanceOf( WatchedItem
::class )
2040 $mockCache->expects( $this->once() )
2041 ->method( 'delete' )
2042 ->with( '0:SomeDbKey:1' );
2044 $store = $this->newWatchedItemStore(
2045 $this->getMockLoadBalancer( $mockDb ),
2047 $this->getMockReadOnlyMode()
2050 // Note: This does not actually assert the job is correct
2051 $callableCallCounter = 0;
2052 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2053 $callableCallCounter++
;
2054 $this->assertInternalType( 'callable', $callable );
2056 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2059 $store->resetNotificationTimestamp(
2064 $this->assertEquals( 1, $callableCallCounter );
2066 ScopedCallback
::consume( $scopedOverride );
2069 public function testResetNotificationTimestamp_noItemForced() {
2070 $user = $this->getMockNonAnonUserWithId( 1 );
2071 $title = Title
::newFromText( 'SomeDbKey' );
2073 $mockDb = $this->getMockDb();
2074 $mockDb->expects( $this->never() )
2075 ->method( 'selectRow' );
2077 $mockCache = $this->getMockCache();
2078 $mockCache->expects( $this->never() )->method( 'get' );
2079 $mockCache->expects( $this->never() )->method( 'set' );
2080 $mockCache->expects( $this->once() )
2081 ->method( 'delete' )
2082 ->with( '0:SomeDbKey:1' );
2084 $store = $this->newWatchedItemStore(
2085 $this->getMockLoadBalancer( $mockDb ),
2087 $this->getMockReadOnlyMode()
2090 // Note: This does not actually assert the job is correct
2091 $callableCallCounter = 0;
2092 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2093 $callableCallCounter++
;
2094 $this->assertInternalType( 'callable', $callable );
2096 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2099 $store->resetNotificationTimestamp(
2105 $this->assertEquals( 1, $callableCallCounter );
2107 ScopedCallback
::consume( $scopedOverride );
2111 * @param string $text
2114 * @return PHPUnit_Framework_MockObject_MockObject|Title
2116 private function getMockTitle( $text, $ns = 0 ) {
2117 $title = $this->createMock( Title
::class );
2118 $title->expects( $this->any() )
2119 ->method( 'getText' )
2120 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2121 $title->expects( $this->any() )
2122 ->method( 'getDbKey' )
2123 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2124 $title->expects( $this->any() )
2125 ->method( 'getNamespace' )
2126 ->will( $this->returnValue( $ns ) );
2130 private function verifyCallbackJob(
2132 LinkTarget
$expectedTitle,
2134 callable
$notificationTimestampCondition
2136 $this->assertInternalType( 'callable', $callback );
2138 $callbackReflector = new ReflectionFunction( $callback );
2139 $vars = $callbackReflector->getStaticVariables();
2140 $this->assertArrayHasKey( 'job', $vars );
2141 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2143 /** @var ActivityUpdateJob $job */
2144 $job = $vars['job'];
2145 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2146 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2148 $jobParams = $job->getParams();
2149 $this->assertArrayHasKey( 'type', $jobParams );
2150 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2151 $this->assertArrayHasKey( 'userid', $jobParams );
2152 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2153 $this->assertArrayHasKey( 'notifTime', $jobParams );
2154 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2157 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2158 $user = $this->getMockNonAnonUserWithId( 1 );
2160 $title = $this->getMockTitle( 'SomeTitle' );
2161 $title->expects( $this->once() )
2162 ->method( 'getNextRevisionID' )
2164 ->will( $this->returnValue( false ) );
2166 $mockDb = $this->getMockDb();
2167 $mockDb->expects( $this->never() )
2168 ->method( 'selectRow' );
2170 $mockCache = $this->getMockCache();
2171 $mockCache->expects( $this->never() )->method( 'get' );
2172 $mockCache->expects( $this->never() )->method( 'set' );
2173 $mockCache->expects( $this->once() )
2174 ->method( 'delete' )
2175 ->with( '0:SomeTitle:1' );
2177 $store = $this->newWatchedItemStore(
2178 $this->getMockLoadBalancer( $mockDb ),
2180 $this->getMockReadOnlyMode()
2183 $callableCallCounter = 0;
2184 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2185 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2186 $callableCallCounter++
;
2187 $this->verifyCallbackJob(
2191 function ( $time ) {
2192 return $time === null;
2199 $store->resetNotificationTimestamp(
2206 $this->assertEquals( 1, $callableCallCounter );
2208 ScopedCallback
::consume( $scopedOverride );
2211 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2212 $user = $this->getMockNonAnonUserWithId( 1 );
2214 $title = $this->getMockTitle( 'SomeDbKey' );
2215 $title->expects( $this->once() )
2216 ->method( 'getNextRevisionID' )
2218 ->will( $this->returnValue( 33 ) );
2220 $mockDb = $this->getMockDb();
2221 $mockDb->expects( $this->once() )
2222 ->method( 'selectRow' )
2225 'wl_notificationtimestamp',
2228 'wl_namespace' => 0,
2229 'wl_title' => 'SomeDbKey',
2232 ->will( $this->returnValue(
2233 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2236 $mockCache = $this->getMockCache();
2237 $mockCache->expects( $this->never() )->method( 'get' );
2238 $mockCache->expects( $this->once() )
2240 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2241 $mockCache->expects( $this->once() )
2242 ->method( 'delete' )
2243 ->with( '0:SomeDbKey:1' );
2245 $store = $this->newWatchedItemStore(
2246 $this->getMockLoadBalancer( $mockDb ),
2248 $this->getMockReadOnlyMode()
2251 $addUpdateCallCounter = 0;
2252 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2253 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2254 $addUpdateCallCounter++
;
2255 $this->verifyCallbackJob(
2259 function ( $time ) {
2260 return $time !== null && $time > '20151212010101';
2266 $getTimestampCallCounter = 0;
2267 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2268 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2269 $getTimestampCallCounter++
;
2270 $this->assertEquals( $title, $titleParam );
2271 $this->assertEquals( $oldid, $oldidParam );
2276 $store->resetNotificationTimestamp(
2283 $this->assertEquals( 1, $addUpdateCallCounter );
2284 $this->assertEquals( 1, $getTimestampCallCounter );
2286 ScopedCallback
::consume( $scopedOverrideDeferred );
2287 ScopedCallback
::consume( $scopedOverrideRevision );
2290 public function testResetNotificationTimestamp_notWatchedPageForced() {
2291 $user = $this->getMockNonAnonUserWithId( 1 );
2293 $title = $this->getMockTitle( 'SomeDbKey' );
2294 $title->expects( $this->once() )
2295 ->method( 'getNextRevisionID' )
2297 ->will( $this->returnValue( 33 ) );
2299 $mockDb = $this->getMockDb();
2300 $mockDb->expects( $this->once() )
2301 ->method( 'selectRow' )
2304 'wl_notificationtimestamp',
2307 'wl_namespace' => 0,
2308 'wl_title' => 'SomeDbKey',
2311 ->will( $this->returnValue( false ) );
2313 $mockCache = $this->getMockCache();
2314 $mockCache->expects( $this->never() )->method( 'get' );
2315 $mockCache->expects( $this->never() )->method( 'set' );
2316 $mockCache->expects( $this->once() )
2317 ->method( 'delete' )
2318 ->with( '0:SomeDbKey:1' );
2320 $store = $this->newWatchedItemStore(
2321 $this->getMockLoadBalancer( $mockDb ),
2323 $this->getMockReadOnlyMode()
2326 $callableCallCounter = 0;
2327 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2328 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2329 $callableCallCounter++
;
2330 $this->verifyCallbackJob(
2334 function ( $time ) {
2335 return $time === null;
2342 $store->resetNotificationTimestamp(
2349 $this->assertEquals( 1, $callableCallCounter );
2351 ScopedCallback
::consume( $scopedOverride );
2354 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2355 $user = $this->getMockNonAnonUserWithId( 1 );
2357 $title = $this->getMockTitle( 'SomeDbKey' );
2358 $title->expects( $this->once() )
2359 ->method( 'getNextRevisionID' )
2361 ->will( $this->returnValue( 33 ) );
2363 $mockDb = $this->getMockDb();
2364 $mockDb->expects( $this->once() )
2365 ->method( 'selectRow' )
2368 'wl_notificationtimestamp',
2371 'wl_namespace' => 0,
2372 'wl_title' => 'SomeDbKey',
2375 ->will( $this->returnValue(
2376 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2379 $mockCache = $this->getMockCache();
2380 $mockCache->expects( $this->never() )->method( 'get' );
2381 $mockCache->expects( $this->once() )
2383 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2384 $mockCache->expects( $this->once() )
2385 ->method( 'delete' )
2386 ->with( '0:SomeDbKey:1' );
2388 $store = $this->newWatchedItemStore(
2389 $this->getMockLoadBalancer( $mockDb ),
2391 $this->getMockReadOnlyMode()
2394 $addUpdateCallCounter = 0;
2395 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2396 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2397 $addUpdateCallCounter++
;
2398 $this->verifyCallbackJob(
2402 function ( $time ) {
2403 return $time === '30151212010101';
2409 $getTimestampCallCounter = 0;
2410 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2411 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2412 $getTimestampCallCounter++
;
2413 $this->assertEquals( $title, $titleParam );
2414 $this->assertEquals( $oldid, $oldidParam );
2419 $store->resetNotificationTimestamp(
2426 $this->assertEquals( 1, $addUpdateCallCounter );
2427 $this->assertEquals( 1, $getTimestampCallCounter );
2429 ScopedCallback
::consume( $scopedOverrideDeferred );
2430 ScopedCallback
::consume( $scopedOverrideRevision );
2433 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2434 $user = $this->getMockNonAnonUserWithId( 1 );
2436 $title = $this->getMockTitle( 'SomeDbKey' );
2437 $title->expects( $this->once() )
2438 ->method( 'getNextRevisionID' )
2440 ->will( $this->returnValue( 33 ) );
2442 $mockDb = $this->getMockDb();
2443 $mockDb->expects( $this->once() )
2444 ->method( 'selectRow' )
2447 'wl_notificationtimestamp',
2450 'wl_namespace' => 0,
2451 'wl_title' => 'SomeDbKey',
2454 ->will( $this->returnValue(
2455 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2458 $mockCache = $this->getMockCache();
2459 $mockCache->expects( $this->never() )->method( 'get' );
2460 $mockCache->expects( $this->once() )
2462 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2463 $mockCache->expects( $this->once() )
2464 ->method( 'delete' )
2465 ->with( '0:SomeDbKey:1' );
2467 $store = $this->newWatchedItemStore(
2468 $this->getMockLoadBalancer( $mockDb ),
2470 $this->getMockReadOnlyMode()
2473 $addUpdateCallCounter = 0;
2474 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2475 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2476 $addUpdateCallCounter++
;
2477 $this->verifyCallbackJob(
2481 function ( $time ) {
2482 return $time === false;
2488 $getTimestampCallCounter = 0;
2489 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2490 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2491 $getTimestampCallCounter++
;
2492 $this->assertEquals( $title, $titleParam );
2493 $this->assertEquals( $oldid, $oldidParam );
2498 $store->resetNotificationTimestamp(
2505 $this->assertEquals( 1, $addUpdateCallCounter );
2506 $this->assertEquals( 1, $getTimestampCallCounter );
2508 ScopedCallback
::consume( $scopedOverrideDeferred );
2509 ScopedCallback
::consume( $scopedOverrideRevision );
2512 public function testSetNotificationTimestampsForUser_anonUser() {
2513 $store = $this->newWatchedItemStore(
2514 $this->getMockLoadBalancer( $this->getMockDb() ),
2515 $this->getMockCache(),
2516 $this->getMockReadOnlyMode()
2518 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2521 public function testSetNotificationTimestampsForUser_allRows() {
2522 $user = $this->getMockNonAnonUserWithId( 1 );
2523 $timestamp = '20100101010101';
2525 $mockDb = $this->getMockDb();
2526 $mockDb->expects( $this->once() )
2527 ->method( 'update' )
2530 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2533 ->will( $this->returnValue( true ) );
2534 $mockDb->expects( $this->exactly( 1 ) )
2535 ->method( 'timestamp' )
2536 ->will( $this->returnCallback( function ( $value ) {
2537 return 'TS' . $value . 'TS';
2540 $store = $this->newWatchedItemStore(
2541 $this->getMockLoadBalancer( $mockDb ),
2542 $this->getMockCache(),
2543 $this->getMockReadOnlyMode()
2547 $store->setNotificationTimestampsForUser( $user, $timestamp )
2551 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2552 $user = $this->getMockNonAnonUserWithId( 1 );
2555 $mockDb = $this->getMockDb();
2556 $mockDb->expects( $this->once() )
2557 ->method( 'update' )
2560 [ 'wl_notificationtimestamp' => null ],
2563 ->will( $this->returnValue( true ) );
2564 $mockDb->expects( $this->exactly( 0 ) )
2565 ->method( 'timestamp' )
2566 ->will( $this->returnCallback( function ( $value ) {
2567 return 'TS' . $value . 'TS';
2570 $store = $this->newWatchedItemStore(
2571 $this->getMockLoadBalancer( $mockDb ),
2572 $this->getMockCache(),
2573 $this->getMockReadOnlyMode()
2577 $store->setNotificationTimestampsForUser( $user, $timestamp )
2581 public function testSetNotificationTimestampsForUser_specificTargets() {
2582 $user = $this->getMockNonAnonUserWithId( 1 );
2583 $timestamp = '20100101010101';
2584 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2586 $mockDb = $this->getMockDb();
2587 $mockDb->expects( $this->once() )
2588 ->method( 'update' )
2591 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2592 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2594 ->will( $this->returnValue( true ) );
2595 $mockDb->expects( $this->exactly( 1 ) )
2596 ->method( 'timestamp' )
2597 ->will( $this->returnCallback( function ( $value ) {
2598 return 'TS' . $value . 'TS';
2600 $mockDb->expects( $this->once() )
2601 ->method( 'makeWhereFrom2d' )
2603 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2604 $this->isType( 'string' ),
2605 $this->isType( 'string' )
2607 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2609 $store = $this->newWatchedItemStore(
2610 $this->getMockLoadBalancer( $mockDb ),
2611 $this->getMockCache(),
2612 $this->getMockReadOnlyMode()
2616 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2620 public function testUpdateNotificationTimestamp_watchersExist() {
2621 $mockDb = $this->getMockDb();
2622 $mockDb->expects( $this->once() )
2623 ->method( 'selectFieldValues' )
2629 'wl_namespace' => 0,
2630 'wl_title' => 'SomeDbKey',
2631 'wl_notificationtimestamp IS NULL'
2634 ->will( $this->returnValue( [ '2', '3' ] ) );
2635 $mockDb->expects( $this->once() )
2636 ->method( 'update' )
2639 [ 'wl_notificationtimestamp' => null ],
2641 'wl_user' => [ 2, 3 ],
2642 'wl_namespace' => 0,
2643 'wl_title' => 'SomeDbKey',
2647 $mockCache = $this->getMockCache();
2648 $mockCache->expects( $this->never() )->method( 'set' );
2649 $mockCache->expects( $this->never() )->method( 'get' );
2650 $mockCache->expects( $this->never() )->method( 'delete' );
2652 $store = $this->newWatchedItemStore(
2653 $this->getMockLoadBalancer( $mockDb ),
2655 $this->getMockReadOnlyMode()
2658 $this->assertEquals(
2660 $store->updateNotificationTimestamp(
2661 $this->getMockNonAnonUserWithId( 1 ),
2662 new TitleValue( 0, 'SomeDbKey' ),
2668 public function testUpdateNotificationTimestamp_noWatchers() {
2669 $mockDb = $this->getMockDb();
2670 $mockDb->expects( $this->once() )
2671 ->method( 'selectFieldValues' )
2677 'wl_namespace' => 0,
2678 'wl_title' => 'SomeDbKey',
2679 'wl_notificationtimestamp IS NULL'
2683 $this->returnValue( [] )
2685 $mockDb->expects( $this->never() )
2686 ->method( 'update' );
2688 $mockCache = $this->getMockCache();
2689 $mockCache->expects( $this->never() )->method( 'set' );
2690 $mockCache->expects( $this->never() )->method( 'get' );
2691 $mockCache->expects( $this->never() )->method( 'delete' );
2693 $store = $this->newWatchedItemStore(
2694 $this->getMockLoadBalancer( $mockDb ),
2696 $this->getMockReadOnlyMode()
2699 $watchers = $store->updateNotificationTimestamp(
2700 $this->getMockNonAnonUserWithId( 1 ),
2701 new TitleValue( 0, 'SomeDbKey' ),
2704 $this->assertInternalType( 'array', $watchers );
2705 $this->assertEmpty( $watchers );
2708 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2709 $user = $this->getMockNonAnonUserWithId( 1 );
2710 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2712 $mockDb = $this->getMockDb();
2713 $mockDb->expects( $this->once() )
2714 ->method( 'selectRow' )
2715 ->will( $this->returnValue(
2716 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2718 $mockDb->expects( $this->once() )
2719 ->method( 'selectFieldValues' )
2721 $this->returnValue( [ '2', '3' ] )
2723 $mockDb->expects( $this->once() )
2724 ->method( 'update' );
2726 $mockCache = $this->getMockCache();
2727 $mockCache->expects( $this->once() )
2729 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2730 $mockCache->expects( $this->once() )
2732 ->with( '0:SomeDbKey:1' );
2733 $mockCache->expects( $this->once() )
2734 ->method( 'delete' )
2735 ->with( '0:SomeDbKey:1' );
2737 $store = $this->newWatchedItemStore(
2738 $this->getMockLoadBalancer( $mockDb ),
2740 $this->getMockReadOnlyMode()
2743 // This will add the item to the cache
2744 $store->getWatchedItem( $user, $titleValue );
2746 $store->updateNotificationTimestamp(
2747 $this->getMockNonAnonUserWithId( 1 ),