2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LoadBalancer
;
4 use Wikimedia\ScopedCallback
;
9 * @covers WatchedItemStore
11 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
14 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
16 private function getMockDb() {
17 return $this->createMock( IDatabase
::class );
21 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
23 private function getMockLoadBalancer(
25 $expectedConnectionType = null
27 $mock = $this->getMockBuilder( LoadBalancer
::class )
28 ->disableOriginalConstructor()
30 if ( $expectedConnectionType !== null ) {
31 $mock->expects( $this->any() )
32 ->method( 'getConnectionRef' )
33 ->with( $expectedConnectionType )
34 ->will( $this->returnValue( $mockDb ) );
36 $mock->expects( $this->any() )
37 ->method( 'getConnectionRef' )
38 ->will( $this->returnValue( $mockDb ) );
44 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
46 private function getMockCache() {
47 $mock = $this->getMockBuilder( HashBagOStuff
::class )
48 ->disableOriginalConstructor()
50 $mock->expects( $this->any() )
52 ->will( $this->returnCallback( function () {
53 return implode( ':', func_get_args() );
59 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
61 private function getMockReadOnlyMode( $readOnly = false ) {
62 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
63 ->disableOriginalConstructor()
65 $mock->expects( $this->any() )
66 ->method( 'isReadOnly' )
67 ->will( $this->returnValue( $readOnly ) );
73 * @return PHPUnit_Framework_MockObject_MockObject|User
75 private function getMockNonAnonUserWithId( $id ) {
76 $mock = $this->createMock( User
::class );
77 $mock->expects( $this->any() )
79 ->will( $this->returnValue( false ) );
80 $mock->expects( $this->any() )
82 ->will( $this->returnValue( $id ) );
89 private function getAnonUser() {
90 return User
::newFromName( 'Anon_User' );
93 private function getFakeRow( array $rowValues ) {
94 $fakeRow = new stdClass();
95 foreach ( $rowValues as $valueName => $value ) {
96 $fakeRow->$valueName = $value;
101 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache,
102 ReadOnlyMode
$readOnlyMode
104 return new WatchedItemStore(
111 public function testCountWatchedItems() {
112 $user = $this->getMockNonAnonUserWithId( 1 );
114 $mockDb = $this->getMockDb();
115 $mockDb->expects( $this->exactly( 1 ) )
116 ->method( 'selectField' )
121 'wl_user' => $user->getId(),
123 $this->isType( 'string' )
125 ->will( $this->returnValue( 12 ) );
127 $mockCache = $this->getMockCache();
128 $mockCache->expects( $this->never() )->method( 'get' );
129 $mockCache->expects( $this->never() )->method( 'set' );
130 $mockCache->expects( $this->never() )->method( 'delete' );
132 $store = $this->newWatchedItemStore(
133 $this->getMockLoadBalancer( $mockDb ),
135 $this->getMockReadOnlyMode()
138 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
141 public function testCountWatchers() {
142 $titleValue = new TitleValue( 0, 'SomeDbKey' );
144 $mockDb = $this->getMockDb();
145 $mockDb->expects( $this->exactly( 1 ) )
146 ->method( 'selectField' )
151 'wl_namespace' => $titleValue->getNamespace(),
152 'wl_title' => $titleValue->getDBkey(),
154 $this->isType( 'string' )
156 ->will( $this->returnValue( 7 ) );
158 $mockCache = $this->getMockCache();
159 $mockCache->expects( $this->never() )->method( 'get' );
160 $mockCache->expects( $this->never() )->method( 'set' );
161 $mockCache->expects( $this->never() )->method( 'delete' );
163 $store = $this->newWatchedItemStore(
164 $this->getMockLoadBalancer( $mockDb ),
166 $this->getMockReadOnlyMode()
169 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
172 public function testCountWatchersMultiple() {
174 new TitleValue( 0, 'SomeDbKey' ),
175 new TitleValue( 0, 'OtherDbKey' ),
176 new TitleValue( 1, 'AnotherDbKey' ),
179 $mockDb = $this->getMockDb();
182 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
183 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
184 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
187 $mockDb->expects( $this->once() )
188 ->method( 'makeWhereFrom2d' )
190 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
191 $this->isType( 'string' ),
192 $this->isType( 'string' )
194 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
195 $mockDb->expects( $this->once() )
199 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
200 [ 'makeWhereFrom2d return value' ],
201 $this->isType( 'string' ),
203 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
207 $this->returnValue( $dbResult )
210 $mockCache = $this->getMockCache();
211 $mockCache->expects( $this->never() )->method( 'get' );
212 $mockCache->expects( $this->never() )->method( 'set' );
213 $mockCache->expects( $this->never() )->method( 'delete' );
215 $store = $this->newWatchedItemStore(
216 $this->getMockLoadBalancer( $mockDb ),
218 $this->getMockReadOnlyMode()
222 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
223 1 => [ 'AnotherDbKey' => 500 ],
225 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
228 public function provideIntWithDbUnsafeVersion() {
231 [ "50; DROP TABLE watchlist;\n--" ],
236 * @dataProvider provideIntWithDbUnsafeVersion
238 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
240 new TitleValue( 0, 'SomeDbKey' ),
241 new TitleValue( 0, 'OtherDbKey' ),
242 new TitleValue( 1, 'AnotherDbKey' ),
245 $mockDb = $this->getMockDb();
248 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
249 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
250 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
253 $mockDb->expects( $this->once() )
254 ->method( 'makeWhereFrom2d' )
256 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
257 $this->isType( 'string' ),
258 $this->isType( 'string' )
260 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
261 $mockDb->expects( $this->once() )
265 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
266 [ 'makeWhereFrom2d return value' ],
267 $this->isType( 'string' ),
269 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
270 'HAVING' => 'COUNT(*) >= 50',
274 $this->returnValue( $dbResult )
277 $mockCache = $this->getMockCache();
278 $mockCache->expects( $this->never() )->method( 'get' );
279 $mockCache->expects( $this->never() )->method( 'set' );
280 $mockCache->expects( $this->never() )->method( 'delete' );
282 $store = $this->newWatchedItemStore(
283 $this->getMockLoadBalancer( $mockDb ),
285 $this->getMockReadOnlyMode()
289 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
290 1 => [ 'AnotherDbKey' => 500 ],
294 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
298 public function testCountVisitingWatchers() {
299 $titleValue = new TitleValue( 0, 'SomeDbKey' );
301 $mockDb = $this->getMockDb();
302 $mockDb->expects( $this->exactly( 1 ) )
303 ->method( 'selectField' )
308 'wl_namespace' => $titleValue->getNamespace(),
309 'wl_title' => $titleValue->getDBkey(),
310 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
312 $this->isType( 'string' )
314 ->will( $this->returnValue( 7 ) );
315 $mockDb->expects( $this->exactly( 1 ) )
316 ->method( 'addQuotes' )
317 ->will( $this->returnCallback( function ( $value ) {
320 $mockDb->expects( $this->exactly( 1 ) )
321 ->method( 'timestamp' )
322 ->will( $this->returnCallback( function ( $value ) {
323 return 'TS' . $value . 'TS';
326 $mockCache = $this->getMockCache();
327 $mockCache->expects( $this->never() )->method( 'set' );
328 $mockCache->expects( $this->never() )->method( 'get' );
329 $mockCache->expects( $this->never() )->method( 'delete' );
331 $store = $this->newWatchedItemStore(
332 $this->getMockLoadBalancer( $mockDb ),
334 $this->getMockReadOnlyMode()
337 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
340 public function testCountVisitingWatchersMultiple() {
341 $titleValuesWithThresholds = [
342 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
343 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
344 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
348 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
349 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
350 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
352 $mockDb = $this->getMockDb();
353 $mockDb->expects( $this->exactly( 2 * 3 ) )
354 ->method( 'addQuotes' )
355 ->will( $this->returnCallback( function ( $value ) {
358 $mockDb->expects( $this->exactly( 3 ) )
359 ->method( 'timestamp' )
360 ->will( $this->returnCallback( function ( $value ) {
361 return 'TS' . $value . 'TS';
363 $mockDb->expects( $this->any() )
364 ->method( 'makeList' )
366 $this->isType( 'array' ),
367 $this->isType( 'int' )
369 ->will( $this->returnCallback( function ( $a, $conj ) {
370 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
371 return join( $sqlConj, array_map( function ( $s ) {
372 return '(' . $s . ')';
376 $mockDb->expects( $this->never() )
377 ->method( 'makeWhereFrom2d' );
380 '((wl_namespace = 0) AND (' .
381 "(((wl_title = 'SomeDbKey') AND (" .
382 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
384 "(wl_title = 'OtherDbKey') AND (" .
385 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
387 ') OR ((wl_namespace = 1) AND (' .
388 "(((wl_title = 'AnotherDbKey') AND (".
389 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
391 $mockDb->expects( $this->once() )
395 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
397 $this->isType( 'string' ),
399 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
403 $this->returnValue( $dbResult )
406 $mockCache = $this->getMockCache();
407 $mockCache->expects( $this->never() )->method( 'get' );
408 $mockCache->expects( $this->never() )->method( 'set' );
409 $mockCache->expects( $this->never() )->method( 'delete' );
411 $store = $this->newWatchedItemStore(
412 $this->getMockLoadBalancer( $mockDb ),
414 $this->getMockReadOnlyMode()
418 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
419 1 => [ 'AnotherDbKey' => 500 ],
423 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
427 public function testCountVisitingWatchersMultiple_withMissingTargets() {
428 $titleValuesWithThresholds = [
429 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
430 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
431 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
432 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
433 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
437 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
438 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
439 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
441 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
444 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
447 $mockDb = $this->getMockDb();
448 $mockDb->expects( $this->exactly( 2 * 3 ) )
449 ->method( 'addQuotes' )
450 ->will( $this->returnCallback( function ( $value ) {
453 $mockDb->expects( $this->exactly( 3 ) )
454 ->method( 'timestamp' )
455 ->will( $this->returnCallback( function ( $value ) {
456 return 'TS' . $value . 'TS';
458 $mockDb->expects( $this->any() )
459 ->method( 'makeList' )
461 $this->isType( 'array' ),
462 $this->isType( 'int' )
464 ->will( $this->returnCallback( function ( $a, $conj ) {
465 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
466 return join( $sqlConj, array_map( function ( $s ) {
467 return '(' . $s . ')';
471 $mockDb->expects( $this->once() )
472 ->method( 'makeWhereFrom2d' )
474 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
475 $this->isType( 'string' ),
476 $this->isType( 'string' )
478 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
481 '((wl_namespace = 0) AND (' .
482 "(((wl_title = 'SomeDbKey') AND (" .
483 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
485 "(wl_title = 'OtherDbKey') AND (" .
486 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
488 ') OR ((wl_namespace = 1) AND (' .
489 "(((wl_title = 'AnotherDbKey') AND (".
490 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
493 '(makeWhereFrom2d return value)';
494 $mockDb->expects( $this->once() )
498 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
500 $this->isType( 'string' ),
502 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
506 $this->returnValue( $dbResult )
509 $mockCache = $this->getMockCache();
510 $mockCache->expects( $this->never() )->method( 'get' );
511 $mockCache->expects( $this->never() )->method( 'set' );
512 $mockCache->expects( $this->never() )->method( 'delete' );
514 $store = $this->newWatchedItemStore(
515 $this->getMockLoadBalancer( $mockDb ),
517 $this->getMockReadOnlyMode()
522 'SomeDbKey' => 100, 'OtherDbKey' => 300,
523 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
525 1 => [ 'AnotherDbKey' => 500 ],
529 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
534 * @dataProvider provideIntWithDbUnsafeVersion
536 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
537 $titleValuesWithThresholds = [
538 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
539 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
540 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
543 $mockDb = $this->getMockDb();
544 $mockDb->expects( $this->any() )
545 ->method( 'makeList' )
546 ->will( $this->returnValue( 'makeList return value' ) );
547 $mockDb->expects( $this->once() )
551 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
552 'makeList return value',
553 $this->isType( 'string' ),
555 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
556 'HAVING' => 'COUNT(*) >= 50',
560 $this->returnValue( [] )
563 $mockCache = $this->getMockCache();
564 $mockCache->expects( $this->never() )->method( 'get' );
565 $mockCache->expects( $this->never() )->method( 'set' );
566 $mockCache->expects( $this->never() )->method( 'delete' );
568 $store = $this->newWatchedItemStore(
569 $this->getMockLoadBalancer( $mockDb ),
571 $this->getMockReadOnlyMode()
575 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
576 1 => [ 'AnotherDbKey' => 0 ],
580 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
584 public function testCountUnreadNotifications() {
585 $user = $this->getMockNonAnonUserWithId( 1 );
587 $mockDb = $this->getMockDb();
588 $mockDb->expects( $this->exactly( 1 ) )
589 ->method( 'selectRowCount' )
594 "wl_notificationtimestamp IS NOT NULL",
597 $this->isType( 'string' )
599 ->will( $this->returnValue( 9 ) );
601 $mockCache = $this->getMockCache();
602 $mockCache->expects( $this->never() )->method( 'set' );
603 $mockCache->expects( $this->never() )->method( 'get' );
604 $mockCache->expects( $this->never() )->method( 'delete' );
606 $store = $this->newWatchedItemStore(
607 $this->getMockLoadBalancer( $mockDb ),
609 $this->getMockReadOnlyMode()
612 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
616 * @dataProvider provideIntWithDbUnsafeVersion
618 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
619 $user = $this->getMockNonAnonUserWithId( 1 );
621 $mockDb = $this->getMockDb();
622 $mockDb->expects( $this->exactly( 1 ) )
623 ->method( 'selectRowCount' )
628 "wl_notificationtimestamp IS NOT NULL",
631 $this->isType( 'string' ),
634 ->will( $this->returnValue( 50 ) );
636 $mockCache = $this->getMockCache();
637 $mockCache->expects( $this->never() )->method( 'set' );
638 $mockCache->expects( $this->never() )->method( 'get' );
639 $mockCache->expects( $this->never() )->method( 'delete' );
641 $store = $this->newWatchedItemStore(
642 $this->getMockLoadBalancer( $mockDb ),
644 $this->getMockReadOnlyMode()
649 $store->countUnreadNotifications( $user, $limit )
654 * @dataProvider provideIntWithDbUnsafeVersion
656 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
657 $user = $this->getMockNonAnonUserWithId( 1 );
659 $mockDb = $this->getMockDb();
660 $mockDb->expects( $this->exactly( 1 ) )
661 ->method( 'selectRowCount' )
666 "wl_notificationtimestamp IS NOT NULL",
669 $this->isType( 'string' ),
672 ->will( $this->returnValue( 9 ) );
674 $mockCache = $this->getMockCache();
675 $mockCache->expects( $this->never() )->method( 'set' );
676 $mockCache->expects( $this->never() )->method( 'get' );
677 $mockCache->expects( $this->never() )->method( 'delete' );
679 $store = $this->newWatchedItemStore(
680 $this->getMockLoadBalancer( $mockDb ),
682 $this->getMockReadOnlyMode()
687 $store->countUnreadNotifications( $user, $limit )
691 public function testDuplicateEntry_nothingToDuplicate() {
692 $mockDb = $this->getMockDb();
693 $mockDb->expects( $this->once() )
699 'wl_notificationtimestamp',
703 'wl_title' => 'Old_Title',
705 'WatchedItemStore::duplicateEntry',
708 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
710 $store = $this->newWatchedItemStore(
711 $this->getMockLoadBalancer( $mockDb ),
712 $this->getMockCache(),
713 $this->getMockReadOnlyMode()
716 $store->duplicateEntry(
717 Title
::newFromText( 'Old_Title' ),
718 Title
::newFromText( 'New_Title' )
722 public function testDuplicateEntry_somethingToDuplicate() {
724 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
725 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
728 $mockDb = $this->getMockDb();
729 $mockDb->expects( $this->at( 0 ) )
735 'wl_notificationtimestamp',
739 'wl_title' => 'Old_Title',
742 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
743 $mockDb->expects( $this->at( 1 ) )
744 ->method( 'replace' )
747 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
752 'wl_title' => 'New_Title',
753 'wl_notificationtimestamp' => '20151212010101',
758 'wl_title' => 'New_Title',
759 'wl_notificationtimestamp' => null,
762 $this->isType( 'string' )
765 $mockCache = $this->getMockCache();
766 $mockCache->expects( $this->never() )->method( 'get' );
767 $mockCache->expects( $this->never() )->method( 'delete' );
769 $store = $this->newWatchedItemStore(
770 $this->getMockLoadBalancer( $mockDb ),
772 $this->getMockReadOnlyMode()
775 $store->duplicateEntry(
776 Title
::newFromText( 'Old_Title' ),
777 Title
::newFromText( 'New_Title' )
781 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
782 $mockDb = $this->getMockDb();
783 $mockDb->expects( $this->at( 0 ) )
789 'wl_notificationtimestamp',
793 'wl_title' => 'Old_Title',
796 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
797 $mockDb->expects( $this->at( 1 ) )
803 'wl_notificationtimestamp',
807 'wl_title' => 'Old_Title',
810 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
812 $mockCache = $this->getMockCache();
813 $mockCache->expects( $this->never() )->method( 'get' );
814 $mockCache->expects( $this->never() )->method( 'delete' );
816 $store = $this->newWatchedItemStore(
817 $this->getMockLoadBalancer( $mockDb ),
819 $this->getMockReadOnlyMode()
822 $store->duplicateAllAssociatedEntries(
823 Title
::newFromText( 'Old_Title' ),
824 Title
::newFromText( 'New_Title' )
828 public function provideLinkTargetPairs() {
830 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
831 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
836 * @dataProvider provideLinkTargetPairs
838 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
839 LinkTarget
$oldTarget,
840 LinkTarget
$newTarget
843 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
846 $mockDb = $this->getMockDb();
847 $mockDb->expects( $this->at( 0 ) )
853 'wl_notificationtimestamp',
856 'wl_namespace' => $oldTarget->getNamespace(),
857 'wl_title' => $oldTarget->getDBkey(),
860 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
861 $mockDb->expects( $this->at( 1 ) )
862 ->method( 'replace' )
865 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
869 'wl_namespace' => $newTarget->getNamespace(),
870 'wl_title' => $newTarget->getDBkey(),
871 'wl_notificationtimestamp' => '20151212010101',
874 $this->isType( 'string' )
876 $mockDb->expects( $this->at( 2 ) )
882 'wl_notificationtimestamp',
885 'wl_namespace' => $oldTarget->getNamespace() +
1,
886 'wl_title' => $oldTarget->getDBkey(),
889 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
890 $mockDb->expects( $this->at( 3 ) )
891 ->method( 'replace' )
894 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
898 'wl_namespace' => $newTarget->getNamespace() +
1,
899 'wl_title' => $newTarget->getDBkey(),
900 'wl_notificationtimestamp' => '20151212010101',
903 $this->isType( 'string' )
906 $mockCache = $this->getMockCache();
907 $mockCache->expects( $this->never() )->method( 'get' );
908 $mockCache->expects( $this->never() )->method( 'delete' );
910 $store = $this->newWatchedItemStore(
911 $this->getMockLoadBalancer( $mockDb ),
913 $this->getMockReadOnlyMode()
916 $store->duplicateAllAssociatedEntries(
922 public function testAddWatch_nonAnonymousUser() {
923 $mockDb = $this->getMockDb();
924 $mockDb->expects( $this->once() )
932 'wl_title' => 'Some_Page',
933 'wl_notificationtimestamp' => null,
938 $mockCache = $this->getMockCache();
939 $mockCache->expects( $this->once() )
941 ->with( '0:Some_Page:1' );
943 $store = $this->newWatchedItemStore(
944 $this->getMockLoadBalancer( $mockDb ),
946 $this->getMockReadOnlyMode()
950 $this->getMockNonAnonUserWithId( 1 ),
951 Title
::newFromText( 'Some_Page' )
955 public function testAddWatch_anonymousUser() {
956 $mockDb = $this->getMockDb();
957 $mockDb->expects( $this->never() )
958 ->method( 'insert' );
960 $mockCache = $this->getMockCache();
961 $mockCache->expects( $this->never() )
962 ->method( 'delete' );
964 $store = $this->newWatchedItemStore(
965 $this->getMockLoadBalancer( $mockDb ),
967 $this->getMockReadOnlyMode()
971 $this->getAnonUser(),
972 Title
::newFromText( 'Some_Page' )
976 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
977 $store = $this->newWatchedItemStore(
978 $this->getMockLoadBalancer( $this->getMockDb() ),
979 $this->getMockCache(),
980 $this->getMockReadOnlyMode( true )
984 $store->addWatchBatchForUser(
985 $this->getMockNonAnonUserWithId( 1 ),
986 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
991 public function testAddWatchBatchForUser_nonAnonymousUser() {
992 $mockDb = $this->getMockDb();
993 $mockDb->expects( $this->once() )
1000 'wl_namespace' => 0,
1001 'wl_title' => 'Some_Page',
1002 'wl_notificationtimestamp' => null,
1006 'wl_namespace' => 1,
1007 'wl_title' => 'Some_Page',
1008 'wl_notificationtimestamp' => null,
1013 $mockCache = $this->getMockCache();
1014 $mockCache->expects( $this->exactly( 2 ) )
1015 ->method( 'delete' );
1016 $mockCache->expects( $this->at( 1 ) )
1017 ->method( 'delete' )
1018 ->with( '0:Some_Page:1' );
1019 $mockCache->expects( $this->at( 3 ) )
1020 ->method( 'delete' )
1021 ->with( '1:Some_Page:1' );
1023 $store = $this->newWatchedItemStore(
1024 $this->getMockLoadBalancer( $mockDb ),
1026 $this->getMockReadOnlyMode()
1029 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1032 $store->addWatchBatchForUser(
1034 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1039 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1040 $mockDb = $this->getMockDb();
1041 $mockDb->expects( $this->never() )
1042 ->method( 'insert' );
1044 $mockCache = $this->getMockCache();
1045 $mockCache->expects( $this->never() )
1046 ->method( 'delete' );
1048 $store = $this->newWatchedItemStore(
1049 $this->getMockLoadBalancer( $mockDb ),
1051 $this->getMockReadOnlyMode()
1055 $store->addWatchBatchForUser(
1056 $this->getAnonUser(),
1057 [ new TitleValue( 0, 'Other_Page' ) ]
1062 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1063 $user = $this->getMockNonAnonUserWithId( 1 );
1064 $mockDb = $this->getMockDb();
1065 $mockDb->expects( $this->never() )
1066 ->method( 'insert' );
1068 $mockCache = $this->getMockCache();
1069 $mockCache->expects( $this->never() )
1070 ->method( 'delete' );
1072 $store = $this->newWatchedItemStore(
1073 $this->getMockLoadBalancer( $mockDb ),
1075 $this->getMockReadOnlyMode()
1079 $store->addWatchBatchForUser( $user, [] )
1083 public function testLoadWatchedItem_existingItem() {
1084 $mockDb = $this->getMockDb();
1085 $mockDb->expects( $this->once() )
1086 ->method( 'selectRow' )
1089 'wl_notificationtimestamp',
1092 'wl_namespace' => 0,
1093 'wl_title' => 'SomeDbKey',
1096 ->will( $this->returnValue(
1097 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1100 $mockCache = $this->getMockCache();
1101 $mockCache->expects( $this->once() )
1107 $store = $this->newWatchedItemStore(
1108 $this->getMockLoadBalancer( $mockDb ),
1110 $this->getMockReadOnlyMode()
1113 $watchedItem = $store->loadWatchedItem(
1114 $this->getMockNonAnonUserWithId( 1 ),
1115 new TitleValue( 0, 'SomeDbKey' )
1117 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1118 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1119 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1120 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1123 public function testLoadWatchedItem_noItem() {
1124 $mockDb = $this->getMockDb();
1125 $mockDb->expects( $this->once() )
1126 ->method( 'selectRow' )
1129 'wl_notificationtimestamp',
1132 'wl_namespace' => 0,
1133 'wl_title' => 'SomeDbKey',
1136 ->will( $this->returnValue( [] ) );
1138 $mockCache = $this->getMockCache();
1139 $mockCache->expects( $this->never() )->method( 'get' );
1140 $mockCache->expects( $this->never() )->method( 'delete' );
1142 $store = $this->newWatchedItemStore(
1143 $this->getMockLoadBalancer( $mockDb ),
1145 $this->getMockReadOnlyMode()
1149 $store->loadWatchedItem(
1150 $this->getMockNonAnonUserWithId( 1 ),
1151 new TitleValue( 0, 'SomeDbKey' )
1156 public function testLoadWatchedItem_anonymousUser() {
1157 $mockDb = $this->getMockDb();
1158 $mockDb->expects( $this->never() )
1159 ->method( 'selectRow' );
1161 $mockCache = $this->getMockCache();
1162 $mockCache->expects( $this->never() )->method( 'get' );
1163 $mockCache->expects( $this->never() )->method( 'delete' );
1165 $store = $this->newWatchedItemStore(
1166 $this->getMockLoadBalancer( $mockDb ),
1168 $this->getMockReadOnlyMode()
1172 $store->loadWatchedItem(
1173 $this->getAnonUser(),
1174 new TitleValue( 0, 'SomeDbKey' )
1179 public function testRemoveWatch_existingItem() {
1180 $mockDb = $this->getMockDb();
1181 $mockDb->expects( $this->once() )
1182 ->method( 'delete' )
1187 'wl_namespace' => 0,
1188 'wl_title' => 'SomeDbKey',
1191 $mockDb->expects( $this->once() )
1192 ->method( 'affectedRows' )
1193 ->will( $this->returnValue( 1 ) );
1195 $mockCache = $this->getMockCache();
1196 $mockCache->expects( $this->never() )->method( 'get' );
1197 $mockCache->expects( $this->once() )
1198 ->method( 'delete' )
1199 ->with( '0:SomeDbKey:1' );
1201 $store = $this->newWatchedItemStore(
1202 $this->getMockLoadBalancer( $mockDb ),
1204 $this->getMockReadOnlyMode()
1208 $store->removeWatch(
1209 $this->getMockNonAnonUserWithId( 1 ),
1210 new TitleValue( 0, 'SomeDbKey' )
1215 public function testRemoveWatch_noItem() {
1216 $mockDb = $this->getMockDb();
1217 $mockDb->expects( $this->once() )
1218 ->method( 'delete' )
1223 'wl_namespace' => 0,
1224 'wl_title' => 'SomeDbKey',
1227 $mockDb->expects( $this->once() )
1228 ->method( 'affectedRows' )
1229 ->will( $this->returnValue( 0 ) );
1231 $mockCache = $this->getMockCache();
1232 $mockCache->expects( $this->never() )->method( 'get' );
1233 $mockCache->expects( $this->once() )
1234 ->method( 'delete' )
1235 ->with( '0:SomeDbKey:1' );
1237 $store = $this->newWatchedItemStore(
1238 $this->getMockLoadBalancer( $mockDb ),
1240 $this->getMockReadOnlyMode()
1244 $store->removeWatch(
1245 $this->getMockNonAnonUserWithId( 1 ),
1246 new TitleValue( 0, 'SomeDbKey' )
1251 public function testRemoveWatch_anonymousUser() {
1252 $mockDb = $this->getMockDb();
1253 $mockDb->expects( $this->never() )
1254 ->method( 'delete' );
1256 $mockCache = $this->getMockCache();
1257 $mockCache->expects( $this->never() )->method( 'get' );
1258 $mockCache->expects( $this->never() )
1259 ->method( 'delete' );
1261 $store = $this->newWatchedItemStore(
1262 $this->getMockLoadBalancer( $mockDb ),
1264 $this->getMockReadOnlyMode()
1268 $store->removeWatch(
1269 $this->getAnonUser(),
1270 new TitleValue( 0, 'SomeDbKey' )
1275 public function testGetWatchedItem_existingItem() {
1276 $mockDb = $this->getMockDb();
1277 $mockDb->expects( $this->once() )
1278 ->method( 'selectRow' )
1281 'wl_notificationtimestamp',
1284 'wl_namespace' => 0,
1285 'wl_title' => 'SomeDbKey',
1288 ->will( $this->returnValue(
1289 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1292 $mockCache = $this->getMockCache();
1293 $mockCache->expects( $this->never() )->method( 'delete' );
1294 $mockCache->expects( $this->once() )
1299 ->will( $this->returnValue( null ) );
1300 $mockCache->expects( $this->once() )
1306 $store = $this->newWatchedItemStore(
1307 $this->getMockLoadBalancer( $mockDb ),
1309 $this->getMockReadOnlyMode()
1312 $watchedItem = $store->getWatchedItem(
1313 $this->getMockNonAnonUserWithId( 1 ),
1314 new TitleValue( 0, 'SomeDbKey' )
1316 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1317 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1318 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1319 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1322 public function testGetWatchedItem_cachedItem() {
1323 $mockDb = $this->getMockDb();
1324 $mockDb->expects( $this->never() )
1325 ->method( 'selectRow' );
1327 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1328 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1329 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1331 $mockCache = $this->getMockCache();
1332 $mockCache->expects( $this->never() )->method( 'delete' );
1333 $mockCache->expects( $this->never() )->method( 'set' );
1334 $mockCache->expects( $this->once() )
1339 ->will( $this->returnValue( $cachedItem ) );
1341 $store = $this->newWatchedItemStore(
1342 $this->getMockLoadBalancer( $mockDb ),
1344 $this->getMockReadOnlyMode()
1347 $this->assertEquals(
1349 $store->getWatchedItem(
1356 public function testGetWatchedItem_noItem() {
1357 $mockDb = $this->getMockDb();
1358 $mockDb->expects( $this->once() )
1359 ->method( 'selectRow' )
1362 'wl_notificationtimestamp',
1365 'wl_namespace' => 0,
1366 'wl_title' => 'SomeDbKey',
1369 ->will( $this->returnValue( [] ) );
1371 $mockCache = $this->getMockCache();
1372 $mockCache->expects( $this->never() )->method( 'set' );
1373 $mockCache->expects( $this->never() )->method( 'delete' );
1374 $mockCache->expects( $this->once() )
1376 ->with( '0:SomeDbKey:1' )
1377 ->will( $this->returnValue( false ) );
1379 $store = $this->newWatchedItemStore(
1380 $this->getMockLoadBalancer( $mockDb ),
1382 $this->getMockReadOnlyMode()
1386 $store->getWatchedItem(
1387 $this->getMockNonAnonUserWithId( 1 ),
1388 new TitleValue( 0, 'SomeDbKey' )
1393 public function testGetWatchedItem_anonymousUser() {
1394 $mockDb = $this->getMockDb();
1395 $mockDb->expects( $this->never() )
1396 ->method( 'selectRow' );
1398 $mockCache = $this->getMockCache();
1399 $mockCache->expects( $this->never() )->method( 'set' );
1400 $mockCache->expects( $this->never() )->method( 'get' );
1401 $mockCache->expects( $this->never() )->method( 'delete' );
1403 $store = $this->newWatchedItemStore(
1404 $this->getMockLoadBalancer( $mockDb ),
1406 $this->getMockReadOnlyMode()
1410 $store->getWatchedItem(
1411 $this->getAnonUser(),
1412 new TitleValue( 0, 'SomeDbKey' )
1417 public function testGetWatchedItemsForUser() {
1418 $mockDb = $this->getMockDb();
1419 $mockDb->expects( $this->once() )
1420 ->method( 'select' )
1423 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1426 ->will( $this->returnValue( [
1427 $this->getFakeRow( [
1428 'wl_namespace' => 0,
1429 'wl_title' => 'Foo1',
1430 'wl_notificationtimestamp' => '20151212010101',
1432 $this->getFakeRow( [
1433 'wl_namespace' => 1,
1434 'wl_title' => 'Foo2',
1435 'wl_notificationtimestamp' => null,
1439 $mockCache = $this->getMockCache();
1440 $mockCache->expects( $this->never() )->method( 'delete' );
1441 $mockCache->expects( $this->never() )->method( 'get' );
1442 $mockCache->expects( $this->never() )->method( 'set' );
1444 $store = $this->newWatchedItemStore(
1445 $this->getMockLoadBalancer( $mockDb ),
1447 $this->getMockReadOnlyMode()
1449 $user = $this->getMockNonAnonUserWithId( 1 );
1451 $watchedItems = $store->getWatchedItemsForUser( $user );
1453 $this->assertInternalType( 'array', $watchedItems );
1454 $this->assertCount( 2, $watchedItems );
1455 foreach ( $watchedItems as $watchedItem ) {
1456 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1458 $this->assertEquals(
1459 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1462 $this->assertEquals(
1463 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1468 public function provideDbTypes() {
1470 [ false, DB_REPLICA
],
1471 [ true, DB_MASTER
],
1476 * @dataProvider provideDbTypes
1478 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1479 $mockDb = $this->getMockDb();
1480 $mockCache = $this->getMockCache();
1481 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1482 $user = $this->getMockNonAnonUserWithId( 1 );
1484 $mockDb->expects( $this->once() )
1485 ->method( 'select' )
1488 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1490 $this->isType( 'string' ),
1491 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1493 ->will( $this->returnValue( [] ) );
1495 $store = $this->newWatchedItemStore(
1498 $this->getMockReadOnlyMode()
1501 $watchedItems = $store->getWatchedItemsForUser(
1503 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1505 $this->assertEquals( [], $watchedItems );
1508 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1509 $store = $this->newWatchedItemStore(
1510 $this->getMockLoadBalancer( $this->getMockDb() ),
1511 $this->getMockCache(),
1512 $this->getMockReadOnlyMode()
1515 $this->setExpectedException( 'InvalidArgumentException' );
1516 $store->getWatchedItemsForUser(
1517 $this->getMockNonAnonUserWithId( 1 ),
1522 public function testIsWatchedItem_existingItem() {
1523 $mockDb = $this->getMockDb();
1524 $mockDb->expects( $this->once() )
1525 ->method( 'selectRow' )
1528 'wl_notificationtimestamp',
1531 'wl_namespace' => 0,
1532 'wl_title' => 'SomeDbKey',
1535 ->will( $this->returnValue(
1536 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1539 $mockCache = $this->getMockCache();
1540 $mockCache->expects( $this->never() )->method( 'delete' );
1541 $mockCache->expects( $this->once() )
1543 ->with( '0:SomeDbKey:1' )
1544 ->will( $this->returnValue( false ) );
1545 $mockCache->expects( $this->once() )
1551 $store = $this->newWatchedItemStore(
1552 $this->getMockLoadBalancer( $mockDb ),
1554 $this->getMockReadOnlyMode()
1559 $this->getMockNonAnonUserWithId( 1 ),
1560 new TitleValue( 0, 'SomeDbKey' )
1565 public function testIsWatchedItem_noItem() {
1566 $mockDb = $this->getMockDb();
1567 $mockDb->expects( $this->once() )
1568 ->method( 'selectRow' )
1571 'wl_notificationtimestamp',
1574 'wl_namespace' => 0,
1575 'wl_title' => 'SomeDbKey',
1578 ->will( $this->returnValue( [] ) );
1580 $mockCache = $this->getMockCache();
1581 $mockCache->expects( $this->never() )->method( 'set' );
1582 $mockCache->expects( $this->never() )->method( 'delete' );
1583 $mockCache->expects( $this->once() )
1585 ->with( '0:SomeDbKey:1' )
1586 ->will( $this->returnValue( false ) );
1588 $store = $this->newWatchedItemStore(
1589 $this->getMockLoadBalancer( $mockDb ),
1591 $this->getMockReadOnlyMode()
1596 $this->getMockNonAnonUserWithId( 1 ),
1597 new TitleValue( 0, 'SomeDbKey' )
1602 public function testIsWatchedItem_anonymousUser() {
1603 $mockDb = $this->getMockDb();
1604 $mockDb->expects( $this->never() )
1605 ->method( 'selectRow' );
1607 $mockCache = $this->getMockCache();
1608 $mockCache->expects( $this->never() )->method( 'set' );
1609 $mockCache->expects( $this->never() )->method( 'get' );
1610 $mockCache->expects( $this->never() )->method( 'delete' );
1612 $store = $this->newWatchedItemStore(
1613 $this->getMockLoadBalancer( $mockDb ),
1615 $this->getMockReadOnlyMode()
1620 $this->getAnonUser(),
1621 new TitleValue( 0, 'SomeDbKey' )
1626 public function testGetNotificationTimestampsBatch() {
1628 new TitleValue( 0, 'SomeDbKey' ),
1629 new TitleValue( 1, 'AnotherDbKey' ),
1632 $mockDb = $this->getMockDb();
1634 $this->getFakeRow( [
1635 'wl_namespace' => 0,
1636 'wl_title' => 'SomeDbKey',
1637 'wl_notificationtimestamp' => '20151212010101',
1641 'wl_namespace' => 1,
1642 'wl_title' => 'AnotherDbKey',
1643 'wl_notificationtimestamp' => null,
1648 $mockDb->expects( $this->once() )
1649 ->method( 'makeWhereFrom2d' )
1651 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1652 $this->isType( 'string' ),
1653 $this->isType( 'string' )
1655 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1656 $mockDb->expects( $this->once() )
1657 ->method( 'select' )
1660 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1662 'makeWhereFrom2d return value',
1665 $this->isType( 'string' )
1667 ->will( $this->returnValue( $dbResult ) );
1669 $mockCache = $this->getMockCache();
1670 $mockCache->expects( $this->exactly( 2 ) )
1673 [ '0:SomeDbKey:1' ],
1674 [ '1:AnotherDbKey:1' ]
1676 ->will( $this->returnValue( null ) );
1677 $mockCache->expects( $this->never() )->method( 'set' );
1678 $mockCache->expects( $this->never() )->method( 'delete' );
1680 $store = $this->newWatchedItemStore(
1681 $this->getMockLoadBalancer( $mockDb ),
1683 $this->getMockReadOnlyMode()
1686 $this->assertEquals(
1688 0 => [ 'SomeDbKey' => '20151212010101', ],
1689 1 => [ 'AnotherDbKey' => null, ],
1691 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1695 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1697 new TitleValue( 0, 'OtherDbKey' ),
1700 $mockDb = $this->getMockDb();
1702 $mockDb->expects( $this->once() )
1703 ->method( 'makeWhereFrom2d' )
1705 [ [ 'OtherDbKey' => 1 ] ],
1706 $this->isType( 'string' ),
1707 $this->isType( 'string' )
1709 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1710 $mockDb->expects( $this->once() )
1711 ->method( 'select' )
1714 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1716 'makeWhereFrom2d return value',
1719 $this->isType( 'string' )
1721 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1723 $mockCache = $this->getMockCache();
1724 $mockCache->expects( $this->once() )
1726 ->with( '0:OtherDbKey:1' )
1727 ->will( $this->returnValue( null ) );
1728 $mockCache->expects( $this->never() )->method( 'set' );
1729 $mockCache->expects( $this->never() )->method( 'delete' );
1731 $store = $this->newWatchedItemStore(
1732 $this->getMockLoadBalancer( $mockDb ),
1734 $this->getMockReadOnlyMode()
1737 $this->assertEquals(
1739 0 => [ 'OtherDbKey' => false, ],
1741 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1745 public function testGetNotificationTimestampsBatch_cachedItem() {
1747 new TitleValue( 0, 'SomeDbKey' ),
1748 new TitleValue( 1, 'AnotherDbKey' ),
1751 $user = $this->getMockNonAnonUserWithId( 1 );
1752 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1754 $mockDb = $this->getMockDb();
1756 $mockDb->expects( $this->once() )
1757 ->method( 'makeWhereFrom2d' )
1759 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1760 $this->isType( 'string' ),
1761 $this->isType( 'string' )
1763 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1764 $mockDb->expects( $this->once() )
1765 ->method( 'select' )
1768 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1770 'makeWhereFrom2d return value',
1773 $this->isType( 'string' )
1775 ->will( $this->returnValue( [
1777 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1781 $mockCache = $this->getMockCache();
1782 $mockCache->expects( $this->at( 1 ) )
1784 ->with( '0:SomeDbKey:1' )
1785 ->will( $this->returnValue( $cachedItem ) );
1786 $mockCache->expects( $this->at( 3 ) )
1788 ->with( '1:AnotherDbKey:1' )
1789 ->will( $this->returnValue( null ) );
1790 $mockCache->expects( $this->never() )->method( 'set' );
1791 $mockCache->expects( $this->never() )->method( 'delete' );
1793 $store = $this->newWatchedItemStore(
1794 $this->getMockLoadBalancer( $mockDb ),
1796 $this->getMockReadOnlyMode()
1799 $this->assertEquals(
1801 0 => [ 'SomeDbKey' => '20151212010101', ],
1802 1 => [ 'AnotherDbKey' => null, ],
1804 $store->getNotificationTimestampsBatch( $user, $targets )
1808 public function testGetNotificationTimestampsBatch_allItemsCached() {
1810 new TitleValue( 0, 'SomeDbKey' ),
1811 new TitleValue( 1, 'AnotherDbKey' ),
1814 $user = $this->getMockNonAnonUserWithId( 1 );
1816 new WatchedItem( $user, $targets[0], '20151212010101' ),
1817 new WatchedItem( $user, $targets[1], null ),
1819 $mockDb = $this->getMockDb();
1820 $mockDb->expects( $this->never() )->method( $this->anything() );
1822 $mockCache = $this->getMockCache();
1823 $mockCache->expects( $this->at( 1 ) )
1825 ->with( '0:SomeDbKey:1' )
1826 ->will( $this->returnValue( $cachedItems[0] ) );
1827 $mockCache->expects( $this->at( 3 ) )
1829 ->with( '1:AnotherDbKey:1' )
1830 ->will( $this->returnValue( $cachedItems[1] ) );
1831 $mockCache->expects( $this->never() )->method( 'set' );
1832 $mockCache->expects( $this->never() )->method( 'delete' );
1834 $store = $this->newWatchedItemStore(
1835 $this->getMockLoadBalancer( $mockDb ),
1837 $this->getMockReadOnlyMode()
1840 $this->assertEquals(
1842 0 => [ 'SomeDbKey' => '20151212010101', ],
1843 1 => [ 'AnotherDbKey' => null, ],
1845 $store->getNotificationTimestampsBatch( $user, $targets )
1849 public function testGetNotificationTimestampsBatch_anonymousUser() {
1851 new TitleValue( 0, 'SomeDbKey' ),
1852 new TitleValue( 1, 'AnotherDbKey' ),
1855 $mockDb = $this->getMockDb();
1856 $mockDb->expects( $this->never() )->method( $this->anything() );
1858 $mockCache = $this->getMockCache();
1859 $mockCache->expects( $this->never() )->method( $this->anything() );
1861 $store = $this->newWatchedItemStore(
1862 $this->getMockLoadBalancer( $mockDb ),
1864 $this->getMockReadOnlyMode()
1867 $this->assertEquals(
1869 0 => [ 'SomeDbKey' => false, ],
1870 1 => [ 'AnotherDbKey' => false, ],
1872 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1876 public function testResetNotificationTimestamp_anonymousUser() {
1877 $mockDb = $this->getMockDb();
1878 $mockDb->expects( $this->never() )
1879 ->method( 'selectRow' );
1881 $mockCache = $this->getMockCache();
1882 $mockCache->expects( $this->never() )->method( 'get' );
1883 $mockCache->expects( $this->never() )->method( 'set' );
1884 $mockCache->expects( $this->never() )->method( 'delete' );
1886 $store = $this->newWatchedItemStore(
1887 $this->getMockLoadBalancer( $mockDb ),
1889 $this->getMockReadOnlyMode()
1893 $store->resetNotificationTimestamp(
1894 $this->getAnonUser(),
1895 Title
::newFromText( 'SomeDbKey' )
1900 public function testResetNotificationTimestamp_noItem() {
1901 $mockDb = $this->getMockDb();
1902 $mockDb->expects( $this->once() )
1903 ->method( 'selectRow' )
1906 'wl_notificationtimestamp',
1909 'wl_namespace' => 0,
1910 'wl_title' => 'SomeDbKey',
1913 ->will( $this->returnValue( [] ) );
1915 $mockCache = $this->getMockCache();
1916 $mockCache->expects( $this->never() )->method( 'get' );
1917 $mockCache->expects( $this->never() )->method( 'set' );
1918 $mockCache->expects( $this->never() )->method( 'delete' );
1920 $store = $this->newWatchedItemStore(
1921 $this->getMockLoadBalancer( $mockDb ),
1923 $this->getMockReadOnlyMode()
1927 $store->resetNotificationTimestamp(
1928 $this->getMockNonAnonUserWithId( 1 ),
1929 Title
::newFromText( 'SomeDbKey' )
1934 public function testResetNotificationTimestamp_item() {
1935 $user = $this->getMockNonAnonUserWithId( 1 );
1936 $title = Title
::newFromText( 'SomeDbKey' );
1938 $mockDb = $this->getMockDb();
1939 $mockDb->expects( $this->once() )
1940 ->method( 'selectRow' )
1943 'wl_notificationtimestamp',
1946 'wl_namespace' => 0,
1947 'wl_title' => 'SomeDbKey',
1950 ->will( $this->returnValue(
1951 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1954 $mockCache = $this->getMockCache();
1955 $mockCache->expects( $this->never() )->method( 'get' );
1956 $mockCache->expects( $this->once() )
1960 $this->isInstanceOf( WatchedItem
::class )
1962 $mockCache->expects( $this->once() )
1963 ->method( 'delete' )
1964 ->with( '0:SomeDbKey:1' );
1966 $store = $this->newWatchedItemStore(
1967 $this->getMockLoadBalancer( $mockDb ),
1969 $this->getMockReadOnlyMode()
1972 // Note: This does not actually assert the job is correct
1973 $callableCallCounter = 0;
1974 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
1975 $callableCallCounter++
;
1976 $this->assertInternalType( 'callable', $callable );
1978 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1981 $store->resetNotificationTimestamp(
1986 $this->assertEquals( 1, $callableCallCounter );
1988 ScopedCallback
::consume( $scopedOverride );
1991 public function testResetNotificationTimestamp_noItemForced() {
1992 $user = $this->getMockNonAnonUserWithId( 1 );
1993 $title = Title
::newFromText( 'SomeDbKey' );
1995 $mockDb = $this->getMockDb();
1996 $mockDb->expects( $this->never() )
1997 ->method( 'selectRow' );
1999 $mockCache = $this->getMockCache();
2000 $mockDb->expects( $this->never() )
2002 $mockDb->expects( $this->never() )
2004 $mockDb->expects( $this->never() )
2005 ->method( 'delete' );
2007 $store = $this->newWatchedItemStore(
2008 $this->getMockLoadBalancer( $mockDb ),
2010 $this->getMockReadOnlyMode()
2013 // Note: This does not actually assert the job is correct
2014 $callableCallCounter = 0;
2015 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2016 $callableCallCounter++
;
2017 $this->assertInternalType( 'callable', $callable );
2019 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2022 $store->resetNotificationTimestamp(
2028 $this->assertEquals( 1, $callableCallCounter );
2030 ScopedCallback
::consume( $scopedOverride );
2034 * @param string $text
2037 * @return PHPUnit_Framework_MockObject_MockObject|Title
2039 private function getMockTitle( $text, $ns = 0 ) {
2040 $title = $this->createMock( Title
::class );
2041 $title->expects( $this->any() )
2042 ->method( 'getText' )
2043 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2044 $title->expects( $this->any() )
2045 ->method( 'getDbKey' )
2046 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2047 $title->expects( $this->any() )
2048 ->method( 'getNamespace' )
2049 ->will( $this->returnValue( $ns ) );
2053 private function verifyCallbackJob(
2055 LinkTarget
$expectedTitle,
2057 callable
$notificationTimestampCondition
2059 $this->assertInternalType( 'callable', $callback );
2061 $callbackReflector = new ReflectionFunction( $callback );
2062 $vars = $callbackReflector->getStaticVariables();
2063 $this->assertArrayHasKey( 'job', $vars );
2064 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2066 /** @var ActivityUpdateJob $job */
2067 $job = $vars['job'];
2068 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2069 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2071 $jobParams = $job->getParams();
2072 $this->assertArrayHasKey( 'type', $jobParams );
2073 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2074 $this->assertArrayHasKey( 'userid', $jobParams );
2075 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2076 $this->assertArrayHasKey( 'notifTime', $jobParams );
2077 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2080 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2081 $user = $this->getMockNonAnonUserWithId( 1 );
2083 $title = $this->getMockTitle( 'SomeTitle' );
2084 $title->expects( $this->once() )
2085 ->method( 'getNextRevisionID' )
2087 ->will( $this->returnValue( false ) );
2089 $mockDb = $this->getMockDb();
2090 $mockDb->expects( $this->never() )
2091 ->method( 'selectRow' );
2093 $mockCache = $this->getMockCache();
2094 $mockDb->expects( $this->never() )
2096 $mockDb->expects( $this->never() )
2098 $mockDb->expects( $this->never() )
2099 ->method( 'delete' );
2101 $store = $this->newWatchedItemStore(
2102 $this->getMockLoadBalancer( $mockDb ),
2104 $this->getMockReadOnlyMode()
2107 $callableCallCounter = 0;
2108 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2109 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2110 $callableCallCounter++
;
2111 $this->verifyCallbackJob(
2115 function ( $time ) {
2116 return $time === null;
2123 $store->resetNotificationTimestamp(
2130 $this->assertEquals( 1, $callableCallCounter );
2132 ScopedCallback
::consume( $scopedOverride );
2135 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2136 $user = $this->getMockNonAnonUserWithId( 1 );
2138 $title = $this->getMockTitle( 'SomeDbKey' );
2139 $title->expects( $this->once() )
2140 ->method( 'getNextRevisionID' )
2142 ->will( $this->returnValue( 33 ) );
2144 $mockDb = $this->getMockDb();
2145 $mockDb->expects( $this->once() )
2146 ->method( 'selectRow' )
2149 'wl_notificationtimestamp',
2152 'wl_namespace' => 0,
2153 'wl_title' => 'SomeDbKey',
2156 ->will( $this->returnValue(
2157 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2160 $mockCache = $this->getMockCache();
2161 $mockDb->expects( $this->never() )
2163 $mockDb->expects( $this->never() )
2165 $mockDb->expects( $this->never() )
2166 ->method( 'delete' );
2168 $store = $this->newWatchedItemStore(
2169 $this->getMockLoadBalancer( $mockDb ),
2171 $this->getMockReadOnlyMode()
2174 $addUpdateCallCounter = 0;
2175 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2176 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2177 $addUpdateCallCounter++
;
2178 $this->verifyCallbackJob(
2182 function ( $time ) {
2183 return $time !== null && $time > '20151212010101';
2189 $getTimestampCallCounter = 0;
2190 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2191 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2192 $getTimestampCallCounter++
;
2193 $this->assertEquals( $title, $titleParam );
2194 $this->assertEquals( $oldid, $oldidParam );
2199 $store->resetNotificationTimestamp(
2206 $this->assertEquals( 1, $addUpdateCallCounter );
2207 $this->assertEquals( 1, $getTimestampCallCounter );
2209 ScopedCallback
::consume( $scopedOverrideDeferred );
2210 ScopedCallback
::consume( $scopedOverrideRevision );
2213 public function testResetNotificationTimestamp_notWatchedPageForced() {
2214 $user = $this->getMockNonAnonUserWithId( 1 );
2216 $title = $this->getMockTitle( 'SomeDbKey' );
2217 $title->expects( $this->once() )
2218 ->method( 'getNextRevisionID' )
2220 ->will( $this->returnValue( 33 ) );
2222 $mockDb = $this->getMockDb();
2223 $mockDb->expects( $this->once() )
2224 ->method( 'selectRow' )
2227 'wl_notificationtimestamp',
2230 'wl_namespace' => 0,
2231 'wl_title' => 'SomeDbKey',
2234 ->will( $this->returnValue( false ) );
2236 $mockCache = $this->getMockCache();
2237 $mockDb->expects( $this->never() )
2239 $mockDb->expects( $this->never() )
2241 $mockDb->expects( $this->never() )
2242 ->method( 'delete' );
2244 $store = $this->newWatchedItemStore(
2245 $this->getMockLoadBalancer( $mockDb ),
2247 $this->getMockReadOnlyMode()
2250 $callableCallCounter = 0;
2251 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2252 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2253 $callableCallCounter++
;
2254 $this->verifyCallbackJob(
2258 function ( $time ) {
2259 return $time === null;
2266 $store->resetNotificationTimestamp(
2273 $this->assertEquals( 1, $callableCallCounter );
2275 ScopedCallback
::consume( $scopedOverride );
2278 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2279 $user = $this->getMockNonAnonUserWithId( 1 );
2281 $title = $this->getMockTitle( 'SomeDbKey' );
2282 $title->expects( $this->once() )
2283 ->method( 'getNextRevisionID' )
2285 ->will( $this->returnValue( 33 ) );
2287 $mockDb = $this->getMockDb();
2288 $mockDb->expects( $this->once() )
2289 ->method( 'selectRow' )
2292 'wl_notificationtimestamp',
2295 'wl_namespace' => 0,
2296 'wl_title' => 'SomeDbKey',
2299 ->will( $this->returnValue(
2300 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2303 $mockCache = $this->getMockCache();
2304 $mockDb->expects( $this->never() )
2306 $mockDb->expects( $this->never() )
2308 $mockDb->expects( $this->never() )
2309 ->method( 'delete' );
2311 $store = $this->newWatchedItemStore(
2312 $this->getMockLoadBalancer( $mockDb ),
2314 $this->getMockReadOnlyMode()
2317 $addUpdateCallCounter = 0;
2318 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2319 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2320 $addUpdateCallCounter++
;
2321 $this->verifyCallbackJob(
2325 function ( $time ) {
2326 return $time === '30151212010101';
2332 $getTimestampCallCounter = 0;
2333 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2334 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2335 $getTimestampCallCounter++
;
2336 $this->assertEquals( $title, $titleParam );
2337 $this->assertEquals( $oldid, $oldidParam );
2342 $store->resetNotificationTimestamp(
2349 $this->assertEquals( 1, $addUpdateCallCounter );
2350 $this->assertEquals( 1, $getTimestampCallCounter );
2352 ScopedCallback
::consume( $scopedOverrideDeferred );
2353 ScopedCallback
::consume( $scopedOverrideRevision );
2356 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2357 $user = $this->getMockNonAnonUserWithId( 1 );
2359 $title = $this->getMockTitle( 'SomeDbKey' );
2360 $title->expects( $this->once() )
2361 ->method( 'getNextRevisionID' )
2363 ->will( $this->returnValue( 33 ) );
2365 $mockDb = $this->getMockDb();
2366 $mockDb->expects( $this->once() )
2367 ->method( 'selectRow' )
2370 'wl_notificationtimestamp',
2373 'wl_namespace' => 0,
2374 'wl_title' => 'SomeDbKey',
2377 ->will( $this->returnValue(
2378 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2381 $mockCache = $this->getMockCache();
2382 $mockDb->expects( $this->never() )
2384 $mockDb->expects( $this->never() )
2386 $mockDb->expects( $this->never() )
2387 ->method( 'delete' );
2389 $store = $this->newWatchedItemStore(
2390 $this->getMockLoadBalancer( $mockDb ),
2392 $this->getMockReadOnlyMode()
2395 $addUpdateCallCounter = 0;
2396 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2397 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2398 $addUpdateCallCounter++
;
2399 $this->verifyCallbackJob(
2403 function ( $time ) {
2404 return $time === false;
2410 $getTimestampCallCounter = 0;
2411 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2412 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2413 $getTimestampCallCounter++
;
2414 $this->assertEquals( $title, $titleParam );
2415 $this->assertEquals( $oldid, $oldidParam );
2420 $store->resetNotificationTimestamp(
2427 $this->assertEquals( 1, $addUpdateCallCounter );
2428 $this->assertEquals( 1, $getTimestampCallCounter );
2430 ScopedCallback
::consume( $scopedOverrideDeferred );
2431 ScopedCallback
::consume( $scopedOverrideRevision );
2434 public function testSetNotificationTimestampsForUser_anonUser() {
2435 $store = $this->newWatchedItemStore(
2436 $this->getMockLoadBalancer( $this->getMockDb() ),
2437 $this->getMockCache(),
2438 $this->getMockReadOnlyMode()
2440 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2443 public function testSetNotificationTimestampsForUser_allRows() {
2444 $user = $this->getMockNonAnonUserWithId( 1 );
2445 $timestamp = '20100101010101';
2447 $mockDb = $this->getMockDb();
2448 $mockDb->expects( $this->once() )
2449 ->method( 'update' )
2452 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2455 ->will( $this->returnValue( true ) );
2456 $mockDb->expects( $this->exactly( 1 ) )
2457 ->method( 'timestamp' )
2458 ->will( $this->returnCallback( function ( $value ) {
2459 return 'TS' . $value . 'TS';
2462 $store = $this->newWatchedItemStore(
2463 $this->getMockLoadBalancer( $mockDb ),
2464 $this->getMockCache(),
2465 $this->getMockReadOnlyMode()
2469 $store->setNotificationTimestampsForUser( $user, $timestamp )
2473 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2474 $user = $this->getMockNonAnonUserWithId( 1 );
2477 $mockDb = $this->getMockDb();
2478 $mockDb->expects( $this->once() )
2479 ->method( 'update' )
2482 [ 'wl_notificationtimestamp' => null ],
2485 ->will( $this->returnValue( true ) );
2486 $mockDb->expects( $this->exactly( 0 ) )
2487 ->method( 'timestamp' )
2488 ->will( $this->returnCallback( function ( $value ) {
2489 return 'TS' . $value . 'TS';
2492 $store = $this->newWatchedItemStore(
2493 $this->getMockLoadBalancer( $mockDb ),
2494 $this->getMockCache(),
2495 $this->getMockReadOnlyMode()
2499 $store->setNotificationTimestampsForUser( $user, $timestamp )
2503 public function testSetNotificationTimestampsForUser_specificTargets() {
2504 $user = $this->getMockNonAnonUserWithId( 1 );
2505 $timestamp = '20100101010101';
2506 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2508 $mockDb = $this->getMockDb();
2509 $mockDb->expects( $this->once() )
2510 ->method( 'update' )
2513 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2514 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2516 ->will( $this->returnValue( true ) );
2517 $mockDb->expects( $this->exactly( 1 ) )
2518 ->method( 'timestamp' )
2519 ->will( $this->returnCallback( function ( $value ) {
2520 return 'TS' . $value . 'TS';
2522 $mockDb->expects( $this->once() )
2523 ->method( 'makeWhereFrom2d' )
2525 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2526 $this->isType( 'string' ),
2527 $this->isType( 'string' )
2529 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2531 $store = $this->newWatchedItemStore(
2532 $this->getMockLoadBalancer( $mockDb ),
2533 $this->getMockCache(),
2534 $this->getMockReadOnlyMode()
2538 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2542 public function testUpdateNotificationTimestamp_watchersExist() {
2543 $mockDb = $this->getMockDb();
2544 $mockDb->expects( $this->once() )
2545 ->method( 'selectFieldValues' )
2551 'wl_namespace' => 0,
2552 'wl_title' => 'SomeDbKey',
2553 'wl_notificationtimestamp IS NULL'
2556 ->will( $this->returnValue( [ '2', '3' ] ) );
2557 $mockDb->expects( $this->once() )
2558 ->method( 'update' )
2561 [ 'wl_notificationtimestamp' => null ],
2563 'wl_user' => [ 2, 3 ],
2564 'wl_namespace' => 0,
2565 'wl_title' => 'SomeDbKey',
2569 $mockCache = $this->getMockCache();
2570 $mockCache->expects( $this->never() )->method( 'set' );
2571 $mockCache->expects( $this->never() )->method( 'get' );
2572 $mockCache->expects( $this->never() )->method( 'delete' );
2574 $store = $this->newWatchedItemStore(
2575 $this->getMockLoadBalancer( $mockDb ),
2577 $this->getMockReadOnlyMode()
2580 $this->assertEquals(
2582 $store->updateNotificationTimestamp(
2583 $this->getMockNonAnonUserWithId( 1 ),
2584 new TitleValue( 0, 'SomeDbKey' ),
2590 public function testUpdateNotificationTimestamp_noWatchers() {
2591 $mockDb = $this->getMockDb();
2592 $mockDb->expects( $this->once() )
2593 ->method( 'selectFieldValues' )
2599 'wl_namespace' => 0,
2600 'wl_title' => 'SomeDbKey',
2601 'wl_notificationtimestamp IS NULL'
2605 $this->returnValue( [] )
2607 $mockDb->expects( $this->never() )
2608 ->method( 'update' );
2610 $mockCache = $this->getMockCache();
2611 $mockCache->expects( $this->never() )->method( 'set' );
2612 $mockCache->expects( $this->never() )->method( 'get' );
2613 $mockCache->expects( $this->never() )->method( 'delete' );
2615 $store = $this->newWatchedItemStore(
2616 $this->getMockLoadBalancer( $mockDb ),
2618 $this->getMockReadOnlyMode()
2621 $watchers = $store->updateNotificationTimestamp(
2622 $this->getMockNonAnonUserWithId( 1 ),
2623 new TitleValue( 0, 'SomeDbKey' ),
2626 $this->assertInternalType( 'array', $watchers );
2627 $this->assertEmpty( $watchers );
2630 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2631 $user = $this->getMockNonAnonUserWithId( 1 );
2632 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2634 $mockDb = $this->getMockDb();
2635 $mockDb->expects( $this->once() )
2636 ->method( 'selectRow' )
2637 ->will( $this->returnValue(
2638 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2640 $mockDb->expects( $this->once() )
2641 ->method( 'selectFieldValues' )
2643 $this->returnValue( [ '2', '3' ] )
2645 $mockDb->expects( $this->once() )
2646 ->method( 'update' );
2648 $mockCache = $this->getMockCache();
2649 $mockCache->expects( $this->once() )
2651 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2652 $mockCache->expects( $this->once() )
2654 ->with( '0:SomeDbKey:1' );
2655 $mockCache->expects( $this->once() )
2656 ->method( 'delete' )
2657 ->with( '0:SomeDbKey:1' );
2659 $store = $this->newWatchedItemStore(
2660 $this->getMockLoadBalancer( $mockDb ),
2662 $this->getMockReadOnlyMode()
2665 // This will add the item to the cache
2666 $store->getWatchedItem( $user, $titleValue );
2668 $store->updateNotificationTimestamp(
2669 $this->getMockNonAnonUserWithId( 1 ),