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()
51 $mock->expects( $this->any() )
53 ->will( $this->returnCallback( function () {
54 return implode( ':', func_get_args() );
60 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
62 private function getMockReadOnlyMode( $readOnly = false ) {
63 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
64 ->disableOriginalConstructor()
66 $mock->expects( $this->any() )
67 ->method( 'isReadOnly' )
68 ->will( $this->returnValue( $readOnly ) );
74 * @return PHPUnit_Framework_MockObject_MockObject|User
76 private function getMockNonAnonUserWithId( $id ) {
77 $mock = $this->createMock( User
::class );
78 $mock->expects( $this->any() )
80 ->will( $this->returnValue( false ) );
81 $mock->expects( $this->any() )
83 ->will( $this->returnValue( $id ) );
90 private function getAnonUser() {
91 return User
::newFromName( 'Anon_User' );
94 private function getFakeRow( array $rowValues ) {
95 $fakeRow = new stdClass();
96 foreach ( $rowValues as $valueName => $value ) {
97 $fakeRow->$valueName = $value;
102 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache,
103 ReadOnlyMode
$readOnlyMode
105 return new WatchedItemStore(
113 public function testClearWatchedItems() {
114 $user = $this->getMockNonAnonUserWithId( 7 );
116 $mockDb = $this->getMockDb();
117 $mockDb->expects( $this->once() )
118 ->method( 'selectField' )
123 'wl_user' => $user->getId(),
125 $this->isType( 'string' )
127 ->will( $this->returnValue( 12 ) );
128 $mockDb->expects( $this->once() )
133 $this->isType( 'string' )
136 $mockCache = $this->getMockCache();
137 $mockCache->expects( $this->never() )->method( 'get' );
138 $mockCache->expects( $this->never() )->method( 'set' );
139 $mockCache->expects( $this->once() )
143 $store = $this->newWatchedItemStore(
144 $this->getMockLoadBalancer( $mockDb ),
146 $this->getMockReadOnlyMode()
148 TestingAccessWrapper
::newFromObject( $store )
149 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
151 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
154 public function testClearWatchedItems_tooManyItemsWatched() {
155 $user = $this->getMockNonAnonUserWithId( 7 );
157 $mockDb = $this->getMockDb();
158 $mockDb->expects( $this->once() )
159 ->method( 'selectField' )
164 'wl_user' => $user->getId(),
166 $this->isType( 'string' )
168 ->will( $this->returnValue( 99999 ) );
170 $mockCache = $this->getMockCache();
171 $mockCache->expects( $this->never() )->method( 'get' );
172 $mockCache->expects( $this->never() )->method( 'set' );
173 $mockCache->expects( $this->never() )->method( 'delete' );
175 $store = $this->newWatchedItemStore(
176 $this->getMockLoadBalancer( $mockDb ),
178 $this->getMockReadOnlyMode()
181 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
184 public function testCountWatchedItems() {
185 $user = $this->getMockNonAnonUserWithId( 1 );
187 $mockDb = $this->getMockDb();
188 $mockDb->expects( $this->exactly( 1 ) )
189 ->method( 'selectField' )
194 'wl_user' => $user->getId(),
196 $this->isType( 'string' )
198 ->will( $this->returnValue( '12' ) );
200 $mockCache = $this->getMockCache();
201 $mockCache->expects( $this->never() )->method( 'get' );
202 $mockCache->expects( $this->never() )->method( 'set' );
203 $mockCache->expects( $this->never() )->method( 'delete' );
205 $store = $this->newWatchedItemStore(
206 $this->getMockLoadBalancer( $mockDb ),
208 $this->getMockReadOnlyMode()
211 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
214 public function testCountWatchers() {
215 $titleValue = new TitleValue( 0, 'SomeDbKey' );
217 $mockDb = $this->getMockDb();
218 $mockDb->expects( $this->exactly( 1 ) )
219 ->method( 'selectField' )
224 'wl_namespace' => $titleValue->getNamespace(),
225 'wl_title' => $titleValue->getDBkey(),
227 $this->isType( 'string' )
229 ->will( $this->returnValue( '7' ) );
231 $mockCache = $this->getMockCache();
232 $mockCache->expects( $this->never() )->method( 'get' );
233 $mockCache->expects( $this->never() )->method( 'set' );
234 $mockCache->expects( $this->never() )->method( 'delete' );
236 $store = $this->newWatchedItemStore(
237 $this->getMockLoadBalancer( $mockDb ),
239 $this->getMockReadOnlyMode()
242 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
245 public function testCountWatchersMultiple() {
247 new TitleValue( 0, 'SomeDbKey' ),
248 new TitleValue( 0, 'OtherDbKey' ),
249 new TitleValue( 1, 'AnotherDbKey' ),
252 $mockDb = $this->getMockDb();
255 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
256 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
257 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
260 $mockDb->expects( $this->once() )
261 ->method( 'makeWhereFrom2d' )
263 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
264 $this->isType( 'string' ),
265 $this->isType( 'string' )
267 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
268 $mockDb->expects( $this->once() )
272 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
273 [ 'makeWhereFrom2d return value' ],
274 $this->isType( 'string' ),
276 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
280 $this->returnValue( $dbResult )
283 $mockCache = $this->getMockCache();
284 $mockCache->expects( $this->never() )->method( 'get' );
285 $mockCache->expects( $this->never() )->method( 'set' );
286 $mockCache->expects( $this->never() )->method( 'delete' );
288 $store = $this->newWatchedItemStore(
289 $this->getMockLoadBalancer( $mockDb ),
291 $this->getMockReadOnlyMode()
295 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
296 1 => [ 'AnotherDbKey' => 500 ],
298 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
301 public function provideIntWithDbUnsafeVersion() {
304 [ "50; DROP TABLE watchlist;\n--" ],
309 * @dataProvider provideIntWithDbUnsafeVersion
311 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
313 new TitleValue( 0, 'SomeDbKey' ),
314 new TitleValue( 0, 'OtherDbKey' ),
315 new TitleValue( 1, 'AnotherDbKey' ),
318 $mockDb = $this->getMockDb();
321 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
322 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
323 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
326 $mockDb->expects( $this->once() )
327 ->method( 'makeWhereFrom2d' )
329 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
330 $this->isType( 'string' ),
331 $this->isType( 'string' )
333 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
334 $mockDb->expects( $this->once() )
338 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
339 [ 'makeWhereFrom2d return value' ],
340 $this->isType( 'string' ),
342 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
343 'HAVING' => 'COUNT(*) >= 50',
347 $this->returnValue( $dbResult )
350 $mockCache = $this->getMockCache();
351 $mockCache->expects( $this->never() )->method( 'get' );
352 $mockCache->expects( $this->never() )->method( 'set' );
353 $mockCache->expects( $this->never() )->method( 'delete' );
355 $store = $this->newWatchedItemStore(
356 $this->getMockLoadBalancer( $mockDb ),
358 $this->getMockReadOnlyMode()
362 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
363 1 => [ 'AnotherDbKey' => 500 ],
367 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
371 public function testCountVisitingWatchers() {
372 $titleValue = new TitleValue( 0, 'SomeDbKey' );
374 $mockDb = $this->getMockDb();
375 $mockDb->expects( $this->exactly( 1 ) )
376 ->method( 'selectField' )
381 'wl_namespace' => $titleValue->getNamespace(),
382 'wl_title' => $titleValue->getDBkey(),
383 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
385 $this->isType( 'string' )
387 ->will( $this->returnValue( '7' ) );
388 $mockDb->expects( $this->exactly( 1 ) )
389 ->method( 'addQuotes' )
390 ->will( $this->returnCallback( function ( $value ) {
393 $mockDb->expects( $this->exactly( 1 ) )
394 ->method( 'timestamp' )
395 ->will( $this->returnCallback( function ( $value ) {
396 return 'TS' . $value . 'TS';
399 $mockCache = $this->getMockCache();
400 $mockCache->expects( $this->never() )->method( 'set' );
401 $mockCache->expects( $this->never() )->method( 'get' );
402 $mockCache->expects( $this->never() )->method( 'delete' );
404 $store = $this->newWatchedItemStore(
405 $this->getMockLoadBalancer( $mockDb ),
407 $this->getMockReadOnlyMode()
410 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
413 public function testCountVisitingWatchersMultiple() {
414 $titleValuesWithThresholds = [
415 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
416 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
417 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
421 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
422 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
424 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
427 $mockDb = $this->getMockDb();
428 $mockDb->expects( $this->exactly( 2 * 3 ) )
429 ->method( 'addQuotes' )
430 ->will( $this->returnCallback( function ( $value ) {
433 $mockDb->expects( $this->exactly( 3 ) )
434 ->method( 'timestamp' )
435 ->will( $this->returnCallback( function ( $value ) {
436 return 'TS' . $value . 'TS';
438 $mockDb->expects( $this->any() )
439 ->method( 'makeList' )
441 $this->isType( 'array' ),
442 $this->isType( 'int' )
444 ->will( $this->returnCallback( function ( $a, $conj ) {
445 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
446 return implode( $sqlConj, array_map( function ( $s ) {
447 return '(' . $s . ')';
451 $mockDb->expects( $this->never() )
452 ->method( 'makeWhereFrom2d' );
455 '((wl_namespace = 0) AND (' .
456 "(((wl_title = 'SomeDbKey') AND (" .
457 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
459 "(wl_title = 'OtherDbKey') AND (" .
460 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
462 ') OR ((wl_namespace = 1) AND (' .
463 "(((wl_title = 'AnotherDbKey') AND (".
464 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
466 $mockDb->expects( $this->once() )
470 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
472 $this->isType( 'string' ),
474 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
478 $this->returnValue( $dbResult )
481 $mockCache = $this->getMockCache();
482 $mockCache->expects( $this->never() )->method( 'get' );
483 $mockCache->expects( $this->never() )->method( 'set' );
484 $mockCache->expects( $this->never() )->method( 'delete' );
486 $store = $this->newWatchedItemStore(
487 $this->getMockLoadBalancer( $mockDb ),
489 $this->getMockReadOnlyMode()
493 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
494 1 => [ 'AnotherDbKey' => 500 ],
498 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
502 public function testCountVisitingWatchersMultiple_withMissingTargets() {
503 $titleValuesWithThresholds = [
504 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
505 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
506 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
507 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
508 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
512 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
513 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
515 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
518 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
521 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
524 $mockDb = $this->getMockDb();
525 $mockDb->expects( $this->exactly( 2 * 3 ) )
526 ->method( 'addQuotes' )
527 ->will( $this->returnCallback( function ( $value ) {
530 $mockDb->expects( $this->exactly( 3 ) )
531 ->method( 'timestamp' )
532 ->will( $this->returnCallback( function ( $value ) {
533 return 'TS' . $value . 'TS';
535 $mockDb->expects( $this->any() )
536 ->method( 'makeList' )
538 $this->isType( 'array' ),
539 $this->isType( 'int' )
541 ->will( $this->returnCallback( function ( $a, $conj ) {
542 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
543 return implode( $sqlConj, array_map( function ( $s ) {
544 return '(' . $s . ')';
548 $mockDb->expects( $this->once() )
549 ->method( 'makeWhereFrom2d' )
551 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
552 $this->isType( 'string' ),
553 $this->isType( 'string' )
555 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
558 '((wl_namespace = 0) AND (' .
559 "(((wl_title = 'SomeDbKey') AND (" .
560 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
562 "(wl_title = 'OtherDbKey') AND (" .
563 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
565 ') OR ((wl_namespace = 1) AND (' .
566 "(((wl_title = 'AnotherDbKey') AND (".
567 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
570 '(makeWhereFrom2d return value)';
571 $mockDb->expects( $this->once() )
575 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
577 $this->isType( 'string' ),
579 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
583 $this->returnValue( $dbResult )
586 $mockCache = $this->getMockCache();
587 $mockCache->expects( $this->never() )->method( 'get' );
588 $mockCache->expects( $this->never() )->method( 'set' );
589 $mockCache->expects( $this->never() )->method( 'delete' );
591 $store = $this->newWatchedItemStore(
592 $this->getMockLoadBalancer( $mockDb ),
594 $this->getMockReadOnlyMode()
599 'SomeDbKey' => 100, 'OtherDbKey' => 300,
600 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
602 1 => [ 'AnotherDbKey' => 500 ],
606 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
611 * @dataProvider provideIntWithDbUnsafeVersion
613 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
614 $titleValuesWithThresholds = [
615 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
616 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
617 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
620 $mockDb = $this->getMockDb();
621 $mockDb->expects( $this->any() )
622 ->method( 'makeList' )
623 ->will( $this->returnValue( 'makeList return value' ) );
624 $mockDb->expects( $this->once() )
628 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
629 'makeList return value',
630 $this->isType( 'string' ),
632 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
633 'HAVING' => 'COUNT(*) >= 50',
637 $this->returnValue( [] )
640 $mockCache = $this->getMockCache();
641 $mockCache->expects( $this->never() )->method( 'get' );
642 $mockCache->expects( $this->never() )->method( 'set' );
643 $mockCache->expects( $this->never() )->method( 'delete' );
645 $store = $this->newWatchedItemStore(
646 $this->getMockLoadBalancer( $mockDb ),
648 $this->getMockReadOnlyMode()
652 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
653 1 => [ 'AnotherDbKey' => 0 ],
657 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
661 public function testCountUnreadNotifications() {
662 $user = $this->getMockNonAnonUserWithId( 1 );
664 $mockDb = $this->getMockDb();
665 $mockDb->expects( $this->exactly( 1 ) )
666 ->method( 'selectRowCount' )
671 "wl_notificationtimestamp IS NOT NULL",
674 $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()
689 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
693 * @dataProvider provideIntWithDbUnsafeVersion
695 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
696 $user = $this->getMockNonAnonUserWithId( 1 );
698 $mockDb = $this->getMockDb();
699 $mockDb->expects( $this->exactly( 1 ) )
700 ->method( 'selectRowCount' )
705 "wl_notificationtimestamp IS NOT NULL",
708 $this->isType( 'string' ),
711 ->will( $this->returnValue( '50' ) );
713 $mockCache = $this->getMockCache();
714 $mockCache->expects( $this->never() )->method( 'set' );
715 $mockCache->expects( $this->never() )->method( 'get' );
716 $mockCache->expects( $this->never() )->method( 'delete' );
718 $store = $this->newWatchedItemStore(
719 $this->getMockLoadBalancer( $mockDb ),
721 $this->getMockReadOnlyMode()
726 $store->countUnreadNotifications( $user, $limit )
731 * @dataProvider provideIntWithDbUnsafeVersion
733 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
734 $user = $this->getMockNonAnonUserWithId( 1 );
736 $mockDb = $this->getMockDb();
737 $mockDb->expects( $this->exactly( 1 ) )
738 ->method( 'selectRowCount' )
743 "wl_notificationtimestamp IS NOT NULL",
746 $this->isType( 'string' ),
749 ->will( $this->returnValue( '9' ) );
751 $mockCache = $this->getMockCache();
752 $mockCache->expects( $this->never() )->method( 'set' );
753 $mockCache->expects( $this->never() )->method( 'get' );
754 $mockCache->expects( $this->never() )->method( 'delete' );
756 $store = $this->newWatchedItemStore(
757 $this->getMockLoadBalancer( $mockDb ),
759 $this->getMockReadOnlyMode()
764 $store->countUnreadNotifications( $user, $limit )
768 public function testDuplicateEntry_nothingToDuplicate() {
769 $mockDb = $this->getMockDb();
770 $mockDb->expects( $this->once() )
776 'wl_notificationtimestamp',
780 'wl_title' => 'Old_Title',
782 'WatchedItemStore::duplicateEntry',
785 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
787 $store = $this->newWatchedItemStore(
788 $this->getMockLoadBalancer( $mockDb ),
789 $this->getMockCache(),
790 $this->getMockReadOnlyMode()
793 $store->duplicateEntry(
794 Title
::newFromText( 'Old_Title' ),
795 Title
::newFromText( 'New_Title' )
799 public function testDuplicateEntry_somethingToDuplicate() {
801 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
802 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
805 $mockDb = $this->getMockDb();
806 $mockDb->expects( $this->at( 0 ) )
812 'wl_notificationtimestamp',
816 'wl_title' => 'Old_Title',
819 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
820 $mockDb->expects( $this->at( 1 ) )
821 ->method( 'replace' )
824 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
829 'wl_title' => 'New_Title',
830 'wl_notificationtimestamp' => '20151212010101',
835 'wl_title' => 'New_Title',
836 'wl_notificationtimestamp' => null,
839 $this->isType( 'string' )
842 $mockCache = $this->getMockCache();
843 $mockCache->expects( $this->never() )->method( 'get' );
844 $mockCache->expects( $this->never() )->method( 'delete' );
846 $store = $this->newWatchedItemStore(
847 $this->getMockLoadBalancer( $mockDb ),
849 $this->getMockReadOnlyMode()
852 $store->duplicateEntry(
853 Title
::newFromText( 'Old_Title' ),
854 Title
::newFromText( 'New_Title' )
858 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
859 $mockDb = $this->getMockDb();
860 $mockDb->expects( $this->at( 0 ) )
866 'wl_notificationtimestamp',
870 'wl_title' => 'Old_Title',
873 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
874 $mockDb->expects( $this->at( 1 ) )
880 'wl_notificationtimestamp',
884 'wl_title' => 'Old_Title',
887 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
889 $mockCache = $this->getMockCache();
890 $mockCache->expects( $this->never() )->method( 'get' );
891 $mockCache->expects( $this->never() )->method( 'delete' );
893 $store = $this->newWatchedItemStore(
894 $this->getMockLoadBalancer( $mockDb ),
896 $this->getMockReadOnlyMode()
899 $store->duplicateAllAssociatedEntries(
900 Title
::newFromText( 'Old_Title' ),
901 Title
::newFromText( 'New_Title' )
905 public function provideLinkTargetPairs() {
907 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
908 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
913 * @dataProvider provideLinkTargetPairs
915 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
916 LinkTarget
$oldTarget,
917 LinkTarget
$newTarget
920 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
923 $mockDb = $this->getMockDb();
924 $mockDb->expects( $this->at( 0 ) )
930 'wl_notificationtimestamp',
933 'wl_namespace' => $oldTarget->getNamespace(),
934 'wl_title' => $oldTarget->getDBkey(),
937 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
938 $mockDb->expects( $this->at( 1 ) )
939 ->method( 'replace' )
942 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
946 'wl_namespace' => $newTarget->getNamespace(),
947 'wl_title' => $newTarget->getDBkey(),
948 'wl_notificationtimestamp' => '20151212010101',
951 $this->isType( 'string' )
953 $mockDb->expects( $this->at( 2 ) )
959 'wl_notificationtimestamp',
962 'wl_namespace' => $oldTarget->getNamespace() +
1,
963 'wl_title' => $oldTarget->getDBkey(),
966 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
967 $mockDb->expects( $this->at( 3 ) )
968 ->method( 'replace' )
971 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
975 'wl_namespace' => $newTarget->getNamespace() +
1,
976 'wl_title' => $newTarget->getDBkey(),
977 'wl_notificationtimestamp' => '20151212010101',
980 $this->isType( 'string' )
983 $mockCache = $this->getMockCache();
984 $mockCache->expects( $this->never() )->method( 'get' );
985 $mockCache->expects( $this->never() )->method( 'delete' );
987 $store = $this->newWatchedItemStore(
988 $this->getMockLoadBalancer( $mockDb ),
990 $this->getMockReadOnlyMode()
993 $store->duplicateAllAssociatedEntries(
999 public function testAddWatch_nonAnonymousUser() {
1000 $mockDb = $this->getMockDb();
1001 $mockDb->expects( $this->once() )
1002 ->method( 'insert' )
1008 'wl_namespace' => 0,
1009 'wl_title' => 'Some_Page',
1010 'wl_notificationtimestamp' => null,
1015 $mockCache = $this->getMockCache();
1016 $mockCache->expects( $this->once() )
1017 ->method( 'delete' )
1018 ->with( '0:Some_Page:1' );
1020 $store = $this->newWatchedItemStore(
1021 $this->getMockLoadBalancer( $mockDb ),
1023 $this->getMockReadOnlyMode()
1027 $this->getMockNonAnonUserWithId( 1 ),
1028 Title
::newFromText( 'Some_Page' )
1032 public function testAddWatch_anonymousUser() {
1033 $mockDb = $this->getMockDb();
1034 $mockDb->expects( $this->never() )
1035 ->method( 'insert' );
1037 $mockCache = $this->getMockCache();
1038 $mockCache->expects( $this->never() )
1039 ->method( 'delete' );
1041 $store = $this->newWatchedItemStore(
1042 $this->getMockLoadBalancer( $mockDb ),
1044 $this->getMockReadOnlyMode()
1048 $this->getAnonUser(),
1049 Title
::newFromText( 'Some_Page' )
1053 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1054 $store = $this->newWatchedItemStore(
1055 $this->getMockLoadBalancer( $this->getMockDb() ),
1056 $this->getMockCache(),
1057 $this->getMockReadOnlyMode( true )
1061 $store->addWatchBatchForUser(
1062 $this->getMockNonAnonUserWithId( 1 ),
1063 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1068 public function testAddWatchBatchForUser_nonAnonymousUser() {
1069 $mockDb = $this->getMockDb();
1070 $mockDb->expects( $this->once() )
1071 ->method( 'insert' )
1077 'wl_namespace' => 0,
1078 'wl_title' => 'Some_Page',
1079 'wl_notificationtimestamp' => null,
1083 'wl_namespace' => 1,
1084 'wl_title' => 'Some_Page',
1085 'wl_notificationtimestamp' => null,
1090 $mockCache = $this->getMockCache();
1091 $mockCache->expects( $this->exactly( 2 ) )
1092 ->method( 'delete' );
1093 $mockCache->expects( $this->at( 1 ) )
1094 ->method( 'delete' )
1095 ->with( '0:Some_Page:1' );
1096 $mockCache->expects( $this->at( 3 ) )
1097 ->method( 'delete' )
1098 ->with( '1:Some_Page:1' );
1100 $store = $this->newWatchedItemStore(
1101 $this->getMockLoadBalancer( $mockDb ),
1103 $this->getMockReadOnlyMode()
1106 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1109 $store->addWatchBatchForUser(
1111 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1116 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1117 $mockDb = $this->getMockDb();
1118 $mockDb->expects( $this->never() )
1119 ->method( 'insert' );
1121 $mockCache = $this->getMockCache();
1122 $mockCache->expects( $this->never() )
1123 ->method( 'delete' );
1125 $store = $this->newWatchedItemStore(
1126 $this->getMockLoadBalancer( $mockDb ),
1128 $this->getMockReadOnlyMode()
1132 $store->addWatchBatchForUser(
1133 $this->getAnonUser(),
1134 [ new TitleValue( 0, 'Other_Page' ) ]
1139 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1140 $user = $this->getMockNonAnonUserWithId( 1 );
1141 $mockDb = $this->getMockDb();
1142 $mockDb->expects( $this->never() )
1143 ->method( 'insert' );
1145 $mockCache = $this->getMockCache();
1146 $mockCache->expects( $this->never() )
1147 ->method( 'delete' );
1149 $store = $this->newWatchedItemStore(
1150 $this->getMockLoadBalancer( $mockDb ),
1152 $this->getMockReadOnlyMode()
1156 $store->addWatchBatchForUser( $user, [] )
1160 public function testLoadWatchedItem_existingItem() {
1161 $mockDb = $this->getMockDb();
1162 $mockDb->expects( $this->once() )
1163 ->method( 'selectRow' )
1166 'wl_notificationtimestamp',
1169 'wl_namespace' => 0,
1170 'wl_title' => 'SomeDbKey',
1173 ->will( $this->returnValue(
1174 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1177 $mockCache = $this->getMockCache();
1178 $mockCache->expects( $this->once() )
1184 $store = $this->newWatchedItemStore(
1185 $this->getMockLoadBalancer( $mockDb ),
1187 $this->getMockReadOnlyMode()
1190 $watchedItem = $store->loadWatchedItem(
1191 $this->getMockNonAnonUserWithId( 1 ),
1192 new TitleValue( 0, 'SomeDbKey' )
1194 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1195 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1196 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1197 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1200 public function testLoadWatchedItem_noItem() {
1201 $mockDb = $this->getMockDb();
1202 $mockDb->expects( $this->once() )
1203 ->method( 'selectRow' )
1206 'wl_notificationtimestamp',
1209 'wl_namespace' => 0,
1210 'wl_title' => 'SomeDbKey',
1213 ->will( $this->returnValue( [] ) );
1215 $mockCache = $this->getMockCache();
1216 $mockCache->expects( $this->never() )->method( 'get' );
1217 $mockCache->expects( $this->never() )->method( 'delete' );
1219 $store = $this->newWatchedItemStore(
1220 $this->getMockLoadBalancer( $mockDb ),
1222 $this->getMockReadOnlyMode()
1226 $store->loadWatchedItem(
1227 $this->getMockNonAnonUserWithId( 1 ),
1228 new TitleValue( 0, 'SomeDbKey' )
1233 public function testLoadWatchedItem_anonymousUser() {
1234 $mockDb = $this->getMockDb();
1235 $mockDb->expects( $this->never() )
1236 ->method( 'selectRow' );
1238 $mockCache = $this->getMockCache();
1239 $mockCache->expects( $this->never() )->method( 'get' );
1240 $mockCache->expects( $this->never() )->method( 'delete' );
1242 $store = $this->newWatchedItemStore(
1243 $this->getMockLoadBalancer( $mockDb ),
1245 $this->getMockReadOnlyMode()
1249 $store->loadWatchedItem(
1250 $this->getAnonUser(),
1251 new TitleValue( 0, 'SomeDbKey' )
1256 public function testRemoveWatch_existingItem() {
1257 $mockDb = $this->getMockDb();
1258 $mockDb->expects( $this->once() )
1259 ->method( 'delete' )
1264 'wl_namespace' => 0,
1265 'wl_title' => 'SomeDbKey',
1268 $mockDb->expects( $this->once() )
1269 ->method( 'affectedRows' )
1270 ->will( $this->returnValue( 1 ) );
1272 $mockCache = $this->getMockCache();
1273 $mockCache->expects( $this->never() )->method( 'get' );
1274 $mockCache->expects( $this->once() )
1275 ->method( 'delete' )
1276 ->with( '0:SomeDbKey:1' );
1278 $store = $this->newWatchedItemStore(
1279 $this->getMockLoadBalancer( $mockDb ),
1281 $this->getMockReadOnlyMode()
1285 $store->removeWatch(
1286 $this->getMockNonAnonUserWithId( 1 ),
1287 new TitleValue( 0, 'SomeDbKey' )
1292 public function testRemoveWatch_noItem() {
1293 $mockDb = $this->getMockDb();
1294 $mockDb->expects( $this->once() )
1295 ->method( 'delete' )
1300 'wl_namespace' => 0,
1301 'wl_title' => 'SomeDbKey',
1304 $mockDb->expects( $this->once() )
1305 ->method( 'affectedRows' )
1306 ->will( $this->returnValue( 0 ) );
1308 $mockCache = $this->getMockCache();
1309 $mockCache->expects( $this->never() )->method( 'get' );
1310 $mockCache->expects( $this->once() )
1311 ->method( 'delete' )
1312 ->with( '0:SomeDbKey:1' );
1314 $store = $this->newWatchedItemStore(
1315 $this->getMockLoadBalancer( $mockDb ),
1317 $this->getMockReadOnlyMode()
1321 $store->removeWatch(
1322 $this->getMockNonAnonUserWithId( 1 ),
1323 new TitleValue( 0, 'SomeDbKey' )
1328 public function testRemoveWatch_anonymousUser() {
1329 $mockDb = $this->getMockDb();
1330 $mockDb->expects( $this->never() )
1331 ->method( 'delete' );
1333 $mockCache = $this->getMockCache();
1334 $mockCache->expects( $this->never() )->method( 'get' );
1335 $mockCache->expects( $this->never() )
1336 ->method( 'delete' );
1338 $store = $this->newWatchedItemStore(
1339 $this->getMockLoadBalancer( $mockDb ),
1341 $this->getMockReadOnlyMode()
1345 $store->removeWatch(
1346 $this->getAnonUser(),
1347 new TitleValue( 0, 'SomeDbKey' )
1352 public function testGetWatchedItem_existingItem() {
1353 $mockDb = $this->getMockDb();
1354 $mockDb->expects( $this->once() )
1355 ->method( 'selectRow' )
1358 'wl_notificationtimestamp',
1361 'wl_namespace' => 0,
1362 'wl_title' => 'SomeDbKey',
1365 ->will( $this->returnValue(
1366 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1369 $mockCache = $this->getMockCache();
1370 $mockCache->expects( $this->never() )->method( 'delete' );
1371 $mockCache->expects( $this->once() )
1376 ->will( $this->returnValue( null ) );
1377 $mockCache->expects( $this->once() )
1383 $store = $this->newWatchedItemStore(
1384 $this->getMockLoadBalancer( $mockDb ),
1386 $this->getMockReadOnlyMode()
1389 $watchedItem = $store->getWatchedItem(
1390 $this->getMockNonAnonUserWithId( 1 ),
1391 new TitleValue( 0, 'SomeDbKey' )
1393 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1394 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1395 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1396 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1399 public function testGetWatchedItem_cachedItem() {
1400 $mockDb = $this->getMockDb();
1401 $mockDb->expects( $this->never() )
1402 ->method( 'selectRow' );
1404 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1405 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1406 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1408 $mockCache = $this->getMockCache();
1409 $mockCache->expects( $this->never() )->method( 'delete' );
1410 $mockCache->expects( $this->never() )->method( 'set' );
1411 $mockCache->expects( $this->once() )
1416 ->will( $this->returnValue( $cachedItem ) );
1418 $store = $this->newWatchedItemStore(
1419 $this->getMockLoadBalancer( $mockDb ),
1421 $this->getMockReadOnlyMode()
1424 $this->assertEquals(
1426 $store->getWatchedItem(
1433 public function testGetWatchedItem_noItem() {
1434 $mockDb = $this->getMockDb();
1435 $mockDb->expects( $this->once() )
1436 ->method( 'selectRow' )
1439 'wl_notificationtimestamp',
1442 'wl_namespace' => 0,
1443 'wl_title' => 'SomeDbKey',
1446 ->will( $this->returnValue( [] ) );
1448 $mockCache = $this->getMockCache();
1449 $mockCache->expects( $this->never() )->method( 'set' );
1450 $mockCache->expects( $this->never() )->method( 'delete' );
1451 $mockCache->expects( $this->once() )
1453 ->with( '0:SomeDbKey:1' )
1454 ->will( $this->returnValue( false ) );
1456 $store = $this->newWatchedItemStore(
1457 $this->getMockLoadBalancer( $mockDb ),
1459 $this->getMockReadOnlyMode()
1463 $store->getWatchedItem(
1464 $this->getMockNonAnonUserWithId( 1 ),
1465 new TitleValue( 0, 'SomeDbKey' )
1470 public function testGetWatchedItem_anonymousUser() {
1471 $mockDb = $this->getMockDb();
1472 $mockDb->expects( $this->never() )
1473 ->method( 'selectRow' );
1475 $mockCache = $this->getMockCache();
1476 $mockCache->expects( $this->never() )->method( 'set' );
1477 $mockCache->expects( $this->never() )->method( 'get' );
1478 $mockCache->expects( $this->never() )->method( 'delete' );
1480 $store = $this->newWatchedItemStore(
1481 $this->getMockLoadBalancer( $mockDb ),
1483 $this->getMockReadOnlyMode()
1487 $store->getWatchedItem(
1488 $this->getAnonUser(),
1489 new TitleValue( 0, 'SomeDbKey' )
1494 public function testGetWatchedItemsForUser() {
1495 $mockDb = $this->getMockDb();
1496 $mockDb->expects( $this->once() )
1497 ->method( 'select' )
1500 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1503 ->will( $this->returnValue( [
1504 $this->getFakeRow( [
1505 'wl_namespace' => 0,
1506 'wl_title' => 'Foo1',
1507 'wl_notificationtimestamp' => '20151212010101',
1509 $this->getFakeRow( [
1510 'wl_namespace' => 1,
1511 'wl_title' => 'Foo2',
1512 'wl_notificationtimestamp' => null,
1516 $mockCache = $this->getMockCache();
1517 $mockCache->expects( $this->never() )->method( 'delete' );
1518 $mockCache->expects( $this->never() )->method( 'get' );
1519 $mockCache->expects( $this->never() )->method( 'set' );
1521 $store = $this->newWatchedItemStore(
1522 $this->getMockLoadBalancer( $mockDb ),
1524 $this->getMockReadOnlyMode()
1526 $user = $this->getMockNonAnonUserWithId( 1 );
1528 $watchedItems = $store->getWatchedItemsForUser( $user );
1530 $this->assertInternalType( 'array', $watchedItems );
1531 $this->assertCount( 2, $watchedItems );
1532 foreach ( $watchedItems as $watchedItem ) {
1533 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1535 $this->assertEquals(
1536 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1539 $this->assertEquals(
1540 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1545 public function provideDbTypes() {
1547 [ false, DB_REPLICA
],
1548 [ true, DB_MASTER
],
1553 * @dataProvider provideDbTypes
1555 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1556 $mockDb = $this->getMockDb();
1557 $mockCache = $this->getMockCache();
1558 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1559 $user = $this->getMockNonAnonUserWithId( 1 );
1561 $mockDb->expects( $this->once() )
1562 ->method( 'select' )
1565 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1567 $this->isType( 'string' ),
1568 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1570 ->will( $this->returnValue( [] ) );
1572 $store = $this->newWatchedItemStore(
1575 $this->getMockReadOnlyMode()
1578 $watchedItems = $store->getWatchedItemsForUser(
1580 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1582 $this->assertEquals( [], $watchedItems );
1585 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1586 $store = $this->newWatchedItemStore(
1587 $this->getMockLoadBalancer( $this->getMockDb() ),
1588 $this->getMockCache(),
1589 $this->getMockReadOnlyMode()
1592 $this->setExpectedException( InvalidArgumentException
::class );
1593 $store->getWatchedItemsForUser(
1594 $this->getMockNonAnonUserWithId( 1 ),
1599 public function testIsWatchedItem_existingItem() {
1600 $mockDb = $this->getMockDb();
1601 $mockDb->expects( $this->once() )
1602 ->method( 'selectRow' )
1605 'wl_notificationtimestamp',
1608 'wl_namespace' => 0,
1609 'wl_title' => 'SomeDbKey',
1612 ->will( $this->returnValue(
1613 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1616 $mockCache = $this->getMockCache();
1617 $mockCache->expects( $this->never() )->method( 'delete' );
1618 $mockCache->expects( $this->once() )
1620 ->with( '0:SomeDbKey:1' )
1621 ->will( $this->returnValue( false ) );
1622 $mockCache->expects( $this->once() )
1628 $store = $this->newWatchedItemStore(
1629 $this->getMockLoadBalancer( $mockDb ),
1631 $this->getMockReadOnlyMode()
1636 $this->getMockNonAnonUserWithId( 1 ),
1637 new TitleValue( 0, 'SomeDbKey' )
1642 public function testIsWatchedItem_noItem() {
1643 $mockDb = $this->getMockDb();
1644 $mockDb->expects( $this->once() )
1645 ->method( 'selectRow' )
1648 'wl_notificationtimestamp',
1651 'wl_namespace' => 0,
1652 'wl_title' => 'SomeDbKey',
1655 ->will( $this->returnValue( [] ) );
1657 $mockCache = $this->getMockCache();
1658 $mockCache->expects( $this->never() )->method( 'set' );
1659 $mockCache->expects( $this->never() )->method( 'delete' );
1660 $mockCache->expects( $this->once() )
1662 ->with( '0:SomeDbKey:1' )
1663 ->will( $this->returnValue( false ) );
1665 $store = $this->newWatchedItemStore(
1666 $this->getMockLoadBalancer( $mockDb ),
1668 $this->getMockReadOnlyMode()
1673 $this->getMockNonAnonUserWithId( 1 ),
1674 new TitleValue( 0, 'SomeDbKey' )
1679 public function testIsWatchedItem_anonymousUser() {
1680 $mockDb = $this->getMockDb();
1681 $mockDb->expects( $this->never() )
1682 ->method( 'selectRow' );
1684 $mockCache = $this->getMockCache();
1685 $mockCache->expects( $this->never() )->method( 'set' );
1686 $mockCache->expects( $this->never() )->method( 'get' );
1687 $mockCache->expects( $this->never() )->method( 'delete' );
1689 $store = $this->newWatchedItemStore(
1690 $this->getMockLoadBalancer( $mockDb ),
1692 $this->getMockReadOnlyMode()
1697 $this->getAnonUser(),
1698 new TitleValue( 0, 'SomeDbKey' )
1703 public function testGetNotificationTimestampsBatch() {
1705 new TitleValue( 0, 'SomeDbKey' ),
1706 new TitleValue( 1, 'AnotherDbKey' ),
1709 $mockDb = $this->getMockDb();
1711 $this->getFakeRow( [
1712 'wl_namespace' => '0',
1713 'wl_title' => 'SomeDbKey',
1714 'wl_notificationtimestamp' => '20151212010101',
1718 'wl_namespace' => '1',
1719 'wl_title' => 'AnotherDbKey',
1720 'wl_notificationtimestamp' => null,
1725 $mockDb->expects( $this->once() )
1726 ->method( 'makeWhereFrom2d' )
1728 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1729 $this->isType( 'string' ),
1730 $this->isType( 'string' )
1732 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1733 $mockDb->expects( $this->once() )
1734 ->method( 'select' )
1737 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1739 'makeWhereFrom2d return value',
1742 $this->isType( 'string' )
1744 ->will( $this->returnValue( $dbResult ) );
1746 $mockCache = $this->getMockCache();
1747 $mockCache->expects( $this->exactly( 2 ) )
1750 [ '0:SomeDbKey:1' ],
1751 [ '1:AnotherDbKey:1' ]
1753 ->will( $this->returnValue( null ) );
1754 $mockCache->expects( $this->never() )->method( 'set' );
1755 $mockCache->expects( $this->never() )->method( 'delete' );
1757 $store = $this->newWatchedItemStore(
1758 $this->getMockLoadBalancer( $mockDb ),
1760 $this->getMockReadOnlyMode()
1763 $this->assertEquals(
1765 0 => [ 'SomeDbKey' => '20151212010101', ],
1766 1 => [ 'AnotherDbKey' => null, ],
1768 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1772 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1774 new TitleValue( 0, 'OtherDbKey' ),
1777 $mockDb = $this->getMockDb();
1779 $mockDb->expects( $this->once() )
1780 ->method( 'makeWhereFrom2d' )
1782 [ [ 'OtherDbKey' => 1 ] ],
1783 $this->isType( 'string' ),
1784 $this->isType( 'string' )
1786 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1787 $mockDb->expects( $this->once() )
1788 ->method( 'select' )
1791 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1793 'makeWhereFrom2d return value',
1796 $this->isType( 'string' )
1798 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1800 $mockCache = $this->getMockCache();
1801 $mockCache->expects( $this->once() )
1803 ->with( '0:OtherDbKey:1' )
1804 ->will( $this->returnValue( null ) );
1805 $mockCache->expects( $this->never() )->method( 'set' );
1806 $mockCache->expects( $this->never() )->method( 'delete' );
1808 $store = $this->newWatchedItemStore(
1809 $this->getMockLoadBalancer( $mockDb ),
1811 $this->getMockReadOnlyMode()
1814 $this->assertEquals(
1816 0 => [ 'OtherDbKey' => false, ],
1818 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1822 public function testGetNotificationTimestampsBatch_cachedItem() {
1824 new TitleValue( 0, 'SomeDbKey' ),
1825 new TitleValue( 1, 'AnotherDbKey' ),
1828 $user = $this->getMockNonAnonUserWithId( 1 );
1829 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1831 $mockDb = $this->getMockDb();
1833 $mockDb->expects( $this->once() )
1834 ->method( 'makeWhereFrom2d' )
1836 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1837 $this->isType( 'string' ),
1838 $this->isType( 'string' )
1840 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1841 $mockDb->expects( $this->once() )
1842 ->method( 'select' )
1845 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1847 'makeWhereFrom2d return value',
1850 $this->isType( 'string' )
1852 ->will( $this->returnValue( [
1854 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1858 $mockCache = $this->getMockCache();
1859 $mockCache->expects( $this->at( 1 ) )
1861 ->with( '0:SomeDbKey:1' )
1862 ->will( $this->returnValue( $cachedItem ) );
1863 $mockCache->expects( $this->at( 3 ) )
1865 ->with( '1:AnotherDbKey:1' )
1866 ->will( $this->returnValue( null ) );
1867 $mockCache->expects( $this->never() )->method( 'set' );
1868 $mockCache->expects( $this->never() )->method( 'delete' );
1870 $store = $this->newWatchedItemStore(
1871 $this->getMockLoadBalancer( $mockDb ),
1873 $this->getMockReadOnlyMode()
1876 $this->assertEquals(
1878 0 => [ 'SomeDbKey' => '20151212010101', ],
1879 1 => [ 'AnotherDbKey' => null, ],
1881 $store->getNotificationTimestampsBatch( $user, $targets )
1885 public function testGetNotificationTimestampsBatch_allItemsCached() {
1887 new TitleValue( 0, 'SomeDbKey' ),
1888 new TitleValue( 1, 'AnotherDbKey' ),
1891 $user = $this->getMockNonAnonUserWithId( 1 );
1893 new WatchedItem( $user, $targets[0], '20151212010101' ),
1894 new WatchedItem( $user, $targets[1], null ),
1896 $mockDb = $this->getMockDb();
1897 $mockDb->expects( $this->never() )->method( $this->anything() );
1899 $mockCache = $this->getMockCache();
1900 $mockCache->expects( $this->at( 1 ) )
1902 ->with( '0:SomeDbKey:1' )
1903 ->will( $this->returnValue( $cachedItems[0] ) );
1904 $mockCache->expects( $this->at( 3 ) )
1906 ->with( '1:AnotherDbKey:1' )
1907 ->will( $this->returnValue( $cachedItems[1] ) );
1908 $mockCache->expects( $this->never() )->method( 'set' );
1909 $mockCache->expects( $this->never() )->method( 'delete' );
1911 $store = $this->newWatchedItemStore(
1912 $this->getMockLoadBalancer( $mockDb ),
1914 $this->getMockReadOnlyMode()
1917 $this->assertEquals(
1919 0 => [ 'SomeDbKey' => '20151212010101', ],
1920 1 => [ 'AnotherDbKey' => null, ],
1922 $store->getNotificationTimestampsBatch( $user, $targets )
1926 public function testGetNotificationTimestampsBatch_anonymousUser() {
1928 new TitleValue( 0, 'SomeDbKey' ),
1929 new TitleValue( 1, 'AnotherDbKey' ),
1932 $mockDb = $this->getMockDb();
1933 $mockDb->expects( $this->never() )->method( $this->anything() );
1935 $mockCache = $this->getMockCache();
1936 $mockCache->expects( $this->never() )->method( $this->anything() );
1938 $store = $this->newWatchedItemStore(
1939 $this->getMockLoadBalancer( $mockDb ),
1941 $this->getMockReadOnlyMode()
1944 $this->assertEquals(
1946 0 => [ 'SomeDbKey' => false, ],
1947 1 => [ 'AnotherDbKey' => false, ],
1949 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1953 public function testResetNotificationTimestamp_anonymousUser() {
1954 $mockDb = $this->getMockDb();
1955 $mockDb->expects( $this->never() )
1956 ->method( 'selectRow' );
1958 $mockCache = $this->getMockCache();
1959 $mockCache->expects( $this->never() )->method( 'get' );
1960 $mockCache->expects( $this->never() )->method( 'set' );
1961 $mockCache->expects( $this->never() )->method( 'delete' );
1963 $store = $this->newWatchedItemStore(
1964 $this->getMockLoadBalancer( $mockDb ),
1966 $this->getMockReadOnlyMode()
1970 $store->resetNotificationTimestamp(
1971 $this->getAnonUser(),
1972 Title
::newFromText( 'SomeDbKey' )
1977 public function testResetNotificationTimestamp_noItem() {
1978 $mockDb = $this->getMockDb();
1979 $mockDb->expects( $this->once() )
1980 ->method( 'selectRow' )
1983 'wl_notificationtimestamp',
1986 'wl_namespace' => 0,
1987 'wl_title' => 'SomeDbKey',
1990 ->will( $this->returnValue( [] ) );
1992 $mockCache = $this->getMockCache();
1993 $mockCache->expects( $this->never() )->method( 'get' );
1994 $mockCache->expects( $this->never() )->method( 'set' );
1995 $mockCache->expects( $this->never() )->method( 'delete' );
1997 $store = $this->newWatchedItemStore(
1998 $this->getMockLoadBalancer( $mockDb ),
2000 $this->getMockReadOnlyMode()
2004 $store->resetNotificationTimestamp(
2005 $this->getMockNonAnonUserWithId( 1 ),
2006 Title
::newFromText( 'SomeDbKey' )
2011 public function testResetNotificationTimestamp_item() {
2012 $user = $this->getMockNonAnonUserWithId( 1 );
2013 $title = Title
::newFromText( 'SomeDbKey' );
2015 $mockDb = $this->getMockDb();
2016 $mockDb->expects( $this->once() )
2017 ->method( 'selectRow' )
2020 'wl_notificationtimestamp',
2023 'wl_namespace' => 0,
2024 'wl_title' => 'SomeDbKey',
2027 ->will( $this->returnValue(
2028 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2031 $mockCache = $this->getMockCache();
2032 $mockCache->expects( $this->never() )->method( 'get' );
2033 $mockCache->expects( $this->once() )
2037 $this->isInstanceOf( WatchedItem
::class )
2039 $mockCache->expects( $this->once() )
2040 ->method( 'delete' )
2041 ->with( '0:SomeDbKey:1' );
2043 $store = $this->newWatchedItemStore(
2044 $this->getMockLoadBalancer( $mockDb ),
2046 $this->getMockReadOnlyMode()
2049 // Note: This does not actually assert the job is correct
2050 $callableCallCounter = 0;
2051 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2052 $callableCallCounter++
;
2053 $this->assertInternalType( 'callable', $callable );
2055 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2058 $store->resetNotificationTimestamp(
2063 $this->assertEquals( 1, $callableCallCounter );
2065 ScopedCallback
::consume( $scopedOverride );
2068 public function testResetNotificationTimestamp_noItemForced() {
2069 $user = $this->getMockNonAnonUserWithId( 1 );
2070 $title = Title
::newFromText( 'SomeDbKey' );
2072 $mockDb = $this->getMockDb();
2073 $mockDb->expects( $this->never() )
2074 ->method( 'selectRow' );
2076 $mockCache = $this->getMockCache();
2077 $mockDb->expects( $this->never() )
2079 $mockDb->expects( $this->never() )
2081 $mockDb->expects( $this->never() )
2082 ->method( 'delete' );
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 $mockDb->expects( $this->never() )
2173 $mockDb->expects( $this->never() )
2175 $mockDb->expects( $this->never() )
2176 ->method( 'delete' );
2178 $store = $this->newWatchedItemStore(
2179 $this->getMockLoadBalancer( $mockDb ),
2181 $this->getMockReadOnlyMode()
2184 $callableCallCounter = 0;
2185 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2186 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2187 $callableCallCounter++
;
2188 $this->verifyCallbackJob(
2192 function ( $time ) {
2193 return $time === null;
2200 $store->resetNotificationTimestamp(
2207 $this->assertEquals( 1, $callableCallCounter );
2209 ScopedCallback
::consume( $scopedOverride );
2212 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2213 $user = $this->getMockNonAnonUserWithId( 1 );
2215 $title = $this->getMockTitle( 'SomeDbKey' );
2216 $title->expects( $this->once() )
2217 ->method( 'getNextRevisionID' )
2219 ->will( $this->returnValue( 33 ) );
2221 $mockDb = $this->getMockDb();
2222 $mockDb->expects( $this->once() )
2223 ->method( 'selectRow' )
2226 'wl_notificationtimestamp',
2229 'wl_namespace' => 0,
2230 'wl_title' => 'SomeDbKey',
2233 ->will( $this->returnValue(
2234 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2237 $mockCache = $this->getMockCache();
2238 $mockDb->expects( $this->never() )
2240 $mockDb->expects( $this->never() )
2242 $mockDb->expects( $this->never() )
2243 ->method( 'delete' );
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 $mockDb->expects( $this->never() )
2316 $mockDb->expects( $this->never() )
2318 $mockDb->expects( $this->never() )
2319 ->method( 'delete' );
2321 $store = $this->newWatchedItemStore(
2322 $this->getMockLoadBalancer( $mockDb ),
2324 $this->getMockReadOnlyMode()
2327 $callableCallCounter = 0;
2328 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2329 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2330 $callableCallCounter++
;
2331 $this->verifyCallbackJob(
2335 function ( $time ) {
2336 return $time === null;
2343 $store->resetNotificationTimestamp(
2350 $this->assertEquals( 1, $callableCallCounter );
2352 ScopedCallback
::consume( $scopedOverride );
2355 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2356 $user = $this->getMockNonAnonUserWithId( 1 );
2358 $title = $this->getMockTitle( 'SomeDbKey' );
2359 $title->expects( $this->once() )
2360 ->method( 'getNextRevisionID' )
2362 ->will( $this->returnValue( 33 ) );
2364 $mockDb = $this->getMockDb();
2365 $mockDb->expects( $this->once() )
2366 ->method( 'selectRow' )
2369 'wl_notificationtimestamp',
2372 'wl_namespace' => 0,
2373 'wl_title' => 'SomeDbKey',
2376 ->will( $this->returnValue(
2377 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2380 $mockCache = $this->getMockCache();
2381 $mockDb->expects( $this->never() )
2383 $mockDb->expects( $this->never() )
2385 $mockDb->expects( $this->never() )
2386 ->method( 'delete' );
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 $mockDb->expects( $this->never() )
2461 $mockDb->expects( $this->never() )
2463 $mockDb->expects( $this->never() )
2464 ->method( 'delete' );
2466 $store = $this->newWatchedItemStore(
2467 $this->getMockLoadBalancer( $mockDb ),
2469 $this->getMockReadOnlyMode()
2472 $addUpdateCallCounter = 0;
2473 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2474 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2475 $addUpdateCallCounter++
;
2476 $this->verifyCallbackJob(
2480 function ( $time ) {
2481 return $time === false;
2487 $getTimestampCallCounter = 0;
2488 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2489 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2490 $getTimestampCallCounter++
;
2491 $this->assertEquals( $title, $titleParam );
2492 $this->assertEquals( $oldid, $oldidParam );
2497 $store->resetNotificationTimestamp(
2504 $this->assertEquals( 1, $addUpdateCallCounter );
2505 $this->assertEquals( 1, $getTimestampCallCounter );
2507 ScopedCallback
::consume( $scopedOverrideDeferred );
2508 ScopedCallback
::consume( $scopedOverrideRevision );
2511 public function testSetNotificationTimestampsForUser_anonUser() {
2512 $store = $this->newWatchedItemStore(
2513 $this->getMockLoadBalancer( $this->getMockDb() ),
2514 $this->getMockCache(),
2515 $this->getMockReadOnlyMode()
2517 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2520 public function testSetNotificationTimestampsForUser_allRows() {
2521 $user = $this->getMockNonAnonUserWithId( 1 );
2522 $timestamp = '20100101010101';
2524 $mockDb = $this->getMockDb();
2525 $mockDb->expects( $this->once() )
2526 ->method( 'update' )
2529 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2532 ->will( $this->returnValue( true ) );
2533 $mockDb->expects( $this->exactly( 1 ) )
2534 ->method( 'timestamp' )
2535 ->will( $this->returnCallback( function ( $value ) {
2536 return 'TS' . $value . 'TS';
2539 $store = $this->newWatchedItemStore(
2540 $this->getMockLoadBalancer( $mockDb ),
2541 $this->getMockCache(),
2542 $this->getMockReadOnlyMode()
2546 $store->setNotificationTimestampsForUser( $user, $timestamp )
2550 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2551 $user = $this->getMockNonAnonUserWithId( 1 );
2554 $mockDb = $this->getMockDb();
2555 $mockDb->expects( $this->once() )
2556 ->method( 'update' )
2559 [ 'wl_notificationtimestamp' => null ],
2562 ->will( $this->returnValue( true ) );
2563 $mockDb->expects( $this->exactly( 0 ) )
2564 ->method( 'timestamp' )
2565 ->will( $this->returnCallback( function ( $value ) {
2566 return 'TS' . $value . 'TS';
2569 $store = $this->newWatchedItemStore(
2570 $this->getMockLoadBalancer( $mockDb ),
2571 $this->getMockCache(),
2572 $this->getMockReadOnlyMode()
2576 $store->setNotificationTimestampsForUser( $user, $timestamp )
2580 public function testSetNotificationTimestampsForUser_specificTargets() {
2581 $user = $this->getMockNonAnonUserWithId( 1 );
2582 $timestamp = '20100101010101';
2583 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2585 $mockDb = $this->getMockDb();
2586 $mockDb->expects( $this->once() )
2587 ->method( 'update' )
2590 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2591 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2593 ->will( $this->returnValue( true ) );
2594 $mockDb->expects( $this->exactly( 1 ) )
2595 ->method( 'timestamp' )
2596 ->will( $this->returnCallback( function ( $value ) {
2597 return 'TS' . $value . 'TS';
2599 $mockDb->expects( $this->once() )
2600 ->method( 'makeWhereFrom2d' )
2602 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2603 $this->isType( 'string' ),
2604 $this->isType( 'string' )
2606 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2608 $store = $this->newWatchedItemStore(
2609 $this->getMockLoadBalancer( $mockDb ),
2610 $this->getMockCache(),
2611 $this->getMockReadOnlyMode()
2615 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2619 public function testUpdateNotificationTimestamp_watchersExist() {
2620 $mockDb = $this->getMockDb();
2621 $mockDb->expects( $this->once() )
2622 ->method( 'selectFieldValues' )
2628 'wl_namespace' => 0,
2629 'wl_title' => 'SomeDbKey',
2630 'wl_notificationtimestamp IS NULL'
2633 ->will( $this->returnValue( [ '2', '3' ] ) );
2634 $mockDb->expects( $this->once() )
2635 ->method( 'update' )
2638 [ 'wl_notificationtimestamp' => null ],
2640 'wl_user' => [ 2, 3 ],
2641 'wl_namespace' => 0,
2642 'wl_title' => 'SomeDbKey',
2646 $mockCache = $this->getMockCache();
2647 $mockCache->expects( $this->never() )->method( 'set' );
2648 $mockCache->expects( $this->never() )->method( 'get' );
2649 $mockCache->expects( $this->never() )->method( 'delete' );
2651 $store = $this->newWatchedItemStore(
2652 $this->getMockLoadBalancer( $mockDb ),
2654 $this->getMockReadOnlyMode()
2657 $this->assertEquals(
2659 $store->updateNotificationTimestamp(
2660 $this->getMockNonAnonUserWithId( 1 ),
2661 new TitleValue( 0, 'SomeDbKey' ),
2667 public function testUpdateNotificationTimestamp_noWatchers() {
2668 $mockDb = $this->getMockDb();
2669 $mockDb->expects( $this->once() )
2670 ->method( 'selectFieldValues' )
2676 'wl_namespace' => 0,
2677 'wl_title' => 'SomeDbKey',
2678 'wl_notificationtimestamp IS NULL'
2682 $this->returnValue( [] )
2684 $mockDb->expects( $this->never() )
2685 ->method( 'update' );
2687 $mockCache = $this->getMockCache();
2688 $mockCache->expects( $this->never() )->method( 'set' );
2689 $mockCache->expects( $this->never() )->method( 'get' );
2690 $mockCache->expects( $this->never() )->method( 'delete' );
2692 $store = $this->newWatchedItemStore(
2693 $this->getMockLoadBalancer( $mockDb ),
2695 $this->getMockReadOnlyMode()
2698 $watchers = $store->updateNotificationTimestamp(
2699 $this->getMockNonAnonUserWithId( 1 ),
2700 new TitleValue( 0, 'SomeDbKey' ),
2703 $this->assertInternalType( 'array', $watchers );
2704 $this->assertEmpty( $watchers );
2707 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2708 $user = $this->getMockNonAnonUserWithId( 1 );
2709 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2711 $mockDb = $this->getMockDb();
2712 $mockDb->expects( $this->once() )
2713 ->method( 'selectRow' )
2714 ->will( $this->returnValue(
2715 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2717 $mockDb->expects( $this->once() )
2718 ->method( 'selectFieldValues' )
2720 $this->returnValue( [ '2', '3' ] )
2722 $mockDb->expects( $this->once() )
2723 ->method( 'update' );
2725 $mockCache = $this->getMockCache();
2726 $mockCache->expects( $this->once() )
2728 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2729 $mockCache->expects( $this->once() )
2731 ->with( '0:SomeDbKey:1' );
2732 $mockCache->expects( $this->once() )
2733 ->method( 'delete' )
2734 ->with( '0:SomeDbKey:1' );
2736 $store = $this->newWatchedItemStore(
2737 $this->getMockLoadBalancer( $mockDb ),
2739 $this->getMockReadOnlyMode()
2742 // This will add the item to the cache
2743 $store->getWatchedItem( $user, $titleValue );
2745 $store->updateNotificationTimestamp(
2746 $this->getMockNonAnonUserWithId( 1 ),