2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LoadBalancer
;
4 use Wikimedia\Rdbms\LBFactory
;
5 use Wikimedia\ScopedCallback
;
6 use Wikimedia\TestingAccessWrapper
;
11 * @covers WatchedItemStore
13 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
16 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
18 private function getMockDb() {
19 return $this->createMock( IDatabase
::class );
23 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
25 private function getMockLoadBalancer(
27 $expectedConnectionType = null
29 $mock = $this->getMockBuilder( LoadBalancer
::class )
30 ->disableOriginalConstructor()
32 if ( $expectedConnectionType !== null ) {
33 $mock->expects( $this->any() )
34 ->method( 'getConnectionRef' )
35 ->with( $expectedConnectionType )
36 ->will( $this->returnValue( $mockDb ) );
38 $mock->expects( $this->any() )
39 ->method( 'getConnectionRef' )
40 ->will( $this->returnValue( $mockDb ) );
46 * @return PHPUnit_Framework_MockObject_MockObject|LBFactory
48 private function getMockLBFactory(
50 $expectedConnectionType = null
52 $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
53 $mock = $this->getMockBuilder( LBFactory
::class )
54 ->disableOriginalConstructor()
56 $mock->expects( $this->any() )
57 ->method( 'getMainLB' )
58 ->will( $this->returnValue( $loadBalancer ) );
63 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
65 private function getMockCache() {
66 $mock = $this->getMockBuilder( HashBagOStuff
::class )
67 ->disableOriginalConstructor()
68 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
70 $mock->expects( $this->any() )
72 ->will( $this->returnCallback( function () {
73 return implode( ':', func_get_args() );
79 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
81 private function getMockReadOnlyMode( $readOnly = false ) {
82 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
83 ->disableOriginalConstructor()
85 $mock->expects( $this->any() )
86 ->method( 'isReadOnly' )
87 ->will( $this->returnValue( $readOnly ) );
93 * @return PHPUnit_Framework_MockObject_MockObject|User
95 private function getMockNonAnonUserWithId( $id ) {
96 $mock = $this->createMock( User
::class );
97 $mock->expects( $this->any() )
99 ->will( $this->returnValue( false ) );
100 $mock->expects( $this->any() )
102 ->will( $this->returnValue( $id ) );
109 private function getAnonUser() {
110 return User
::newFromName( 'Anon_User' );
113 private function getFakeRow( array $rowValues ) {
114 $fakeRow = new stdClass();
115 foreach ( $rowValues as $valueName => $value ) {
116 $fakeRow->$valueName = $value;
121 private function newWatchedItemStore( LBFactory
$lbFactory, HashBagOStuff
$cache,
122 ReadOnlyMode
$readOnlyMode
124 return new WatchedItemStore(
132 public function testClearWatchedItems() {
133 $user = $this->getMockNonAnonUserWithId( 7 );
135 $mockDb = $this->getMockDb();
136 $mockDb->expects( $this->once() )
137 ->method( 'selectField' )
142 'wl_user' => $user->getId(),
144 $this->isType( 'string' )
146 ->will( $this->returnValue( 12 ) );
147 $mockDb->expects( $this->once() )
152 $this->isType( 'string' )
155 $mockCache = $this->getMockCache();
156 $mockCache->expects( $this->never() )->method( 'get' );
157 $mockCache->expects( $this->never() )->method( 'set' );
158 $mockCache->expects( $this->once() )
162 $store = $this->newWatchedItemStore(
163 $this->getMockLBFactory( $mockDb ),
165 $this->getMockReadOnlyMode()
167 TestingAccessWrapper
::newFromObject( $store )
168 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
170 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
173 public function testClearWatchedItems_tooManyItemsWatched() {
174 $user = $this->getMockNonAnonUserWithId( 7 );
176 $mockDb = $this->getMockDb();
177 $mockDb->expects( $this->once() )
178 ->method( 'selectField' )
183 'wl_user' => $user->getId(),
185 $this->isType( 'string' )
187 ->will( $this->returnValue( 99999 ) );
189 $mockCache = $this->getMockCache();
190 $mockCache->expects( $this->never() )->method( 'get' );
191 $mockCache->expects( $this->never() )->method( 'set' );
192 $mockCache->expects( $this->never() )->method( 'delete' );
194 $store = $this->newWatchedItemStore(
195 $this->getMockLBFactory( $mockDb ),
197 $this->getMockReadOnlyMode()
200 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
203 public function testCountWatchedItems() {
204 $user = $this->getMockNonAnonUserWithId( 1 );
206 $mockDb = $this->getMockDb();
207 $mockDb->expects( $this->exactly( 1 ) )
208 ->method( 'selectField' )
213 'wl_user' => $user->getId(),
215 $this->isType( 'string' )
217 ->will( $this->returnValue( '12' ) );
219 $mockCache = $this->getMockCache();
220 $mockCache->expects( $this->never() )->method( 'get' );
221 $mockCache->expects( $this->never() )->method( 'set' );
222 $mockCache->expects( $this->never() )->method( 'delete' );
224 $store = $this->newWatchedItemStore(
225 $this->getMockLBFactory( $mockDb ),
227 $this->getMockReadOnlyMode()
230 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
233 public function testCountWatchers() {
234 $titleValue = new TitleValue( 0, 'SomeDbKey' );
236 $mockDb = $this->getMockDb();
237 $mockDb->expects( $this->exactly( 1 ) )
238 ->method( 'selectField' )
243 'wl_namespace' => $titleValue->getNamespace(),
244 'wl_title' => $titleValue->getDBkey(),
246 $this->isType( 'string' )
248 ->will( $this->returnValue( '7' ) );
250 $mockCache = $this->getMockCache();
251 $mockCache->expects( $this->never() )->method( 'get' );
252 $mockCache->expects( $this->never() )->method( 'set' );
253 $mockCache->expects( $this->never() )->method( 'delete' );
255 $store = $this->newWatchedItemStore(
256 $this->getMockLBFactory( $mockDb ),
258 $this->getMockReadOnlyMode()
261 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
264 public function testCountWatchersMultiple() {
266 new TitleValue( 0, 'SomeDbKey' ),
267 new TitleValue( 0, 'OtherDbKey' ),
268 new TitleValue( 1, 'AnotherDbKey' ),
271 $mockDb = $this->getMockDb();
274 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
275 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
276 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
279 $mockDb->expects( $this->once() )
280 ->method( 'makeWhereFrom2d' )
282 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
283 $this->isType( 'string' ),
284 $this->isType( 'string' )
286 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
287 $mockDb->expects( $this->once() )
291 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
292 [ 'makeWhereFrom2d return value' ],
293 $this->isType( 'string' ),
295 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
299 $this->returnValue( $dbResult )
302 $mockCache = $this->getMockCache();
303 $mockCache->expects( $this->never() )->method( 'get' );
304 $mockCache->expects( $this->never() )->method( 'set' );
305 $mockCache->expects( $this->never() )->method( 'delete' );
307 $store = $this->newWatchedItemStore(
308 $this->getMockLBFactory( $mockDb ),
310 $this->getMockReadOnlyMode()
314 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
315 1 => [ 'AnotherDbKey' => 500 ],
317 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
320 public function provideIntWithDbUnsafeVersion() {
323 [ "50; DROP TABLE watchlist;\n--" ],
328 * @dataProvider provideIntWithDbUnsafeVersion
330 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
332 new TitleValue( 0, 'SomeDbKey' ),
333 new TitleValue( 0, 'OtherDbKey' ),
334 new TitleValue( 1, 'AnotherDbKey' ),
337 $mockDb = $this->getMockDb();
340 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
341 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
342 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
345 $mockDb->expects( $this->once() )
346 ->method( 'makeWhereFrom2d' )
348 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
349 $this->isType( 'string' ),
350 $this->isType( 'string' )
352 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
353 $mockDb->expects( $this->once() )
357 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
358 [ 'makeWhereFrom2d return value' ],
359 $this->isType( 'string' ),
361 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
362 'HAVING' => 'COUNT(*) >= 50',
366 $this->returnValue( $dbResult )
369 $mockCache = $this->getMockCache();
370 $mockCache->expects( $this->never() )->method( 'get' );
371 $mockCache->expects( $this->never() )->method( 'set' );
372 $mockCache->expects( $this->never() )->method( 'delete' );
374 $store = $this->newWatchedItemStore(
375 $this->getMockLBFactory( $mockDb ),
377 $this->getMockReadOnlyMode()
381 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
382 1 => [ 'AnotherDbKey' => 500 ],
386 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
390 public function testCountVisitingWatchers() {
391 $titleValue = new TitleValue( 0, 'SomeDbKey' );
393 $mockDb = $this->getMockDb();
394 $mockDb->expects( $this->exactly( 1 ) )
395 ->method( 'selectField' )
400 'wl_namespace' => $titleValue->getNamespace(),
401 'wl_title' => $titleValue->getDBkey(),
402 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
404 $this->isType( 'string' )
406 ->will( $this->returnValue( '7' ) );
407 $mockDb->expects( $this->exactly( 1 ) )
408 ->method( 'addQuotes' )
409 ->will( $this->returnCallback( function ( $value ) {
412 $mockDb->expects( $this->exactly( 1 ) )
413 ->method( 'timestamp' )
414 ->will( $this->returnCallback( function ( $value ) {
415 return 'TS' . $value . 'TS';
418 $mockCache = $this->getMockCache();
419 $mockCache->expects( $this->never() )->method( 'set' );
420 $mockCache->expects( $this->never() )->method( 'get' );
421 $mockCache->expects( $this->never() )->method( 'delete' );
423 $store = $this->newWatchedItemStore(
424 $this->getMockLBFactory( $mockDb ),
426 $this->getMockReadOnlyMode()
429 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
432 public function testCountVisitingWatchersMultiple() {
433 $titleValuesWithThresholds = [
434 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
435 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
436 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
440 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
441 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
443 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
446 $mockDb = $this->getMockDb();
447 $mockDb->expects( $this->exactly( 2 * 3 ) )
448 ->method( 'addQuotes' )
449 ->will( $this->returnCallback( function ( $value ) {
452 $mockDb->expects( $this->exactly( 3 ) )
453 ->method( 'timestamp' )
454 ->will( $this->returnCallback( function ( $value ) {
455 return 'TS' . $value . 'TS';
457 $mockDb->expects( $this->any() )
458 ->method( 'makeList' )
460 $this->isType( 'array' ),
461 $this->isType( 'int' )
463 ->will( $this->returnCallback( function ( $a, $conj ) {
464 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
465 return implode( $sqlConj, array_map( function ( $s ) {
466 return '(' . $s . ')';
470 $mockDb->expects( $this->never() )
471 ->method( 'makeWhereFrom2d' );
474 '((wl_namespace = 0) AND (' .
475 "(((wl_title = 'SomeDbKey') AND (" .
476 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
478 "(wl_title = 'OtherDbKey') AND (" .
479 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
481 ') OR ((wl_namespace = 1) AND (' .
482 "(((wl_title = 'AnotherDbKey') AND (" .
483 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
485 $mockDb->expects( $this->once() )
489 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
491 $this->isType( 'string' ),
493 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
497 $this->returnValue( $dbResult )
500 $mockCache = $this->getMockCache();
501 $mockCache->expects( $this->never() )->method( 'get' );
502 $mockCache->expects( $this->never() )->method( 'set' );
503 $mockCache->expects( $this->never() )->method( 'delete' );
505 $store = $this->newWatchedItemStore(
506 $this->getMockLBFactory( $mockDb ),
508 $this->getMockReadOnlyMode()
512 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
513 1 => [ 'AnotherDbKey' => 500 ],
517 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
521 public function testCountVisitingWatchersMultiple_withMissingTargets() {
522 $titleValuesWithThresholds = [
523 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
524 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
525 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
526 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
527 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
531 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
532 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
534 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
537 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
540 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
543 $mockDb = $this->getMockDb();
544 $mockDb->expects( $this->exactly( 2 * 3 ) )
545 ->method( 'addQuotes' )
546 ->will( $this->returnCallback( function ( $value ) {
549 $mockDb->expects( $this->exactly( 3 ) )
550 ->method( 'timestamp' )
551 ->will( $this->returnCallback( function ( $value ) {
552 return 'TS' . $value . 'TS';
554 $mockDb->expects( $this->any() )
555 ->method( 'makeList' )
557 $this->isType( 'array' ),
558 $this->isType( 'int' )
560 ->will( $this->returnCallback( function ( $a, $conj ) {
561 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
562 return implode( $sqlConj, array_map( function ( $s ) {
563 return '(' . $s . ')';
567 $mockDb->expects( $this->once() )
568 ->method( 'makeWhereFrom2d' )
570 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
571 $this->isType( 'string' ),
572 $this->isType( 'string' )
574 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
577 '((wl_namespace = 0) AND (' .
578 "(((wl_title = 'SomeDbKey') AND (" .
579 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
581 "(wl_title = 'OtherDbKey') AND (" .
582 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
584 ') OR ((wl_namespace = 1) AND (' .
585 "(((wl_title = 'AnotherDbKey') AND (" .
586 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
589 '(makeWhereFrom2d return value)';
590 $mockDb->expects( $this->once() )
594 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
596 $this->isType( 'string' ),
598 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
602 $this->returnValue( $dbResult )
605 $mockCache = $this->getMockCache();
606 $mockCache->expects( $this->never() )->method( 'get' );
607 $mockCache->expects( $this->never() )->method( 'set' );
608 $mockCache->expects( $this->never() )->method( 'delete' );
610 $store = $this->newWatchedItemStore(
611 $this->getMockLBFactory( $mockDb ),
613 $this->getMockReadOnlyMode()
618 'SomeDbKey' => 100, 'OtherDbKey' => 300,
619 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
621 1 => [ 'AnotherDbKey' => 500 ],
625 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
630 * @dataProvider provideIntWithDbUnsafeVersion
632 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
633 $titleValuesWithThresholds = [
634 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
635 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
636 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
639 $mockDb = $this->getMockDb();
640 $mockDb->expects( $this->any() )
641 ->method( 'makeList' )
642 ->will( $this->returnValue( 'makeList return value' ) );
643 $mockDb->expects( $this->once() )
647 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
648 'makeList return value',
649 $this->isType( 'string' ),
651 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
652 'HAVING' => 'COUNT(*) >= 50',
656 $this->returnValue( [] )
659 $mockCache = $this->getMockCache();
660 $mockCache->expects( $this->never() )->method( 'get' );
661 $mockCache->expects( $this->never() )->method( 'set' );
662 $mockCache->expects( $this->never() )->method( 'delete' );
664 $store = $this->newWatchedItemStore(
665 $this->getMockLBFactory( $mockDb ),
667 $this->getMockReadOnlyMode()
671 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
672 1 => [ 'AnotherDbKey' => 0 ],
676 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
680 public function testCountUnreadNotifications() {
681 $user = $this->getMockNonAnonUserWithId( 1 );
683 $mockDb = $this->getMockDb();
684 $mockDb->expects( $this->exactly( 1 ) )
685 ->method( 'selectRowCount' )
690 "wl_notificationtimestamp IS NOT NULL",
693 $this->isType( 'string' )
695 ->will( $this->returnValue( '9' ) );
697 $mockCache = $this->getMockCache();
698 $mockCache->expects( $this->never() )->method( 'set' );
699 $mockCache->expects( $this->never() )->method( 'get' );
700 $mockCache->expects( $this->never() )->method( 'delete' );
702 $store = $this->newWatchedItemStore(
703 $this->getMockLBFactory( $mockDb ),
705 $this->getMockReadOnlyMode()
708 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
712 * @dataProvider provideIntWithDbUnsafeVersion
714 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
715 $user = $this->getMockNonAnonUserWithId( 1 );
717 $mockDb = $this->getMockDb();
718 $mockDb->expects( $this->exactly( 1 ) )
719 ->method( 'selectRowCount' )
724 "wl_notificationtimestamp IS NOT NULL",
727 $this->isType( 'string' ),
730 ->will( $this->returnValue( '50' ) );
732 $mockCache = $this->getMockCache();
733 $mockCache->expects( $this->never() )->method( 'set' );
734 $mockCache->expects( $this->never() )->method( 'get' );
735 $mockCache->expects( $this->never() )->method( 'delete' );
737 $store = $this->newWatchedItemStore(
738 $this->getMockLBFactory( $mockDb ),
740 $this->getMockReadOnlyMode()
745 $store->countUnreadNotifications( $user, $limit )
750 * @dataProvider provideIntWithDbUnsafeVersion
752 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
753 $user = $this->getMockNonAnonUserWithId( 1 );
755 $mockDb = $this->getMockDb();
756 $mockDb->expects( $this->exactly( 1 ) )
757 ->method( 'selectRowCount' )
762 "wl_notificationtimestamp IS NOT NULL",
765 $this->isType( 'string' ),
768 ->will( $this->returnValue( '9' ) );
770 $mockCache = $this->getMockCache();
771 $mockCache->expects( $this->never() )->method( 'set' );
772 $mockCache->expects( $this->never() )->method( 'get' );
773 $mockCache->expects( $this->never() )->method( 'delete' );
775 $store = $this->newWatchedItemStore(
776 $this->getMockLBFactory( $mockDb ),
778 $this->getMockReadOnlyMode()
783 $store->countUnreadNotifications( $user, $limit )
787 public function testDuplicateEntry_nothingToDuplicate() {
788 $mockDb = $this->getMockDb();
789 $mockDb->expects( $this->once() )
795 'wl_notificationtimestamp',
799 'wl_title' => 'Old_Title',
801 'WatchedItemStore::duplicateEntry',
804 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
806 $store = $this->newWatchedItemStore(
807 $this->getMockLBFactory( $mockDb ),
808 $this->getMockCache(),
809 $this->getMockReadOnlyMode()
812 $store->duplicateEntry(
813 Title
::newFromText( 'Old_Title' ),
814 Title
::newFromText( 'New_Title' )
818 public function testDuplicateEntry_somethingToDuplicate() {
820 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
821 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
824 $mockDb = $this->getMockDb();
825 $mockDb->expects( $this->at( 0 ) )
831 'wl_notificationtimestamp',
835 'wl_title' => 'Old_Title',
838 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
839 $mockDb->expects( $this->at( 1 ) )
840 ->method( 'replace' )
843 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
848 'wl_title' => 'New_Title',
849 'wl_notificationtimestamp' => '20151212010101',
854 'wl_title' => 'New_Title',
855 'wl_notificationtimestamp' => null,
858 $this->isType( 'string' )
861 $mockCache = $this->getMockCache();
862 $mockCache->expects( $this->never() )->method( 'get' );
863 $mockCache->expects( $this->never() )->method( 'delete' );
865 $store = $this->newWatchedItemStore(
866 $this->getMockLBFactory( $mockDb ),
868 $this->getMockReadOnlyMode()
871 $store->duplicateEntry(
872 Title
::newFromText( 'Old_Title' ),
873 Title
::newFromText( 'New_Title' )
877 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
878 $mockDb = $this->getMockDb();
879 $mockDb->expects( $this->at( 0 ) )
885 'wl_notificationtimestamp',
889 'wl_title' => 'Old_Title',
892 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
893 $mockDb->expects( $this->at( 1 ) )
899 'wl_notificationtimestamp',
903 'wl_title' => 'Old_Title',
906 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
908 $mockCache = $this->getMockCache();
909 $mockCache->expects( $this->never() )->method( 'get' );
910 $mockCache->expects( $this->never() )->method( 'delete' );
912 $store = $this->newWatchedItemStore(
913 $this->getMockLBFactory( $mockDb ),
915 $this->getMockReadOnlyMode()
918 $store->duplicateAllAssociatedEntries(
919 Title
::newFromText( 'Old_Title' ),
920 Title
::newFromText( 'New_Title' )
924 public function provideLinkTargetPairs() {
926 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
927 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
932 * @dataProvider provideLinkTargetPairs
934 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
935 LinkTarget
$oldTarget,
936 LinkTarget
$newTarget
939 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
942 $mockDb = $this->getMockDb();
943 $mockDb->expects( $this->at( 0 ) )
949 'wl_notificationtimestamp',
952 'wl_namespace' => $oldTarget->getNamespace(),
953 'wl_title' => $oldTarget->getDBkey(),
956 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
957 $mockDb->expects( $this->at( 1 ) )
958 ->method( 'replace' )
961 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
965 'wl_namespace' => $newTarget->getNamespace(),
966 'wl_title' => $newTarget->getDBkey(),
967 'wl_notificationtimestamp' => '20151212010101',
970 $this->isType( 'string' )
972 $mockDb->expects( $this->at( 2 ) )
978 'wl_notificationtimestamp',
981 'wl_namespace' => $oldTarget->getNamespace() +
1,
982 'wl_title' => $oldTarget->getDBkey(),
985 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
986 $mockDb->expects( $this->at( 3 ) )
987 ->method( 'replace' )
990 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
994 'wl_namespace' => $newTarget->getNamespace() +
1,
995 'wl_title' => $newTarget->getDBkey(),
996 'wl_notificationtimestamp' => '20151212010101',
999 $this->isType( 'string' )
1002 $mockCache = $this->getMockCache();
1003 $mockCache->expects( $this->never() )->method( 'get' );
1004 $mockCache->expects( $this->never() )->method( 'delete' );
1006 $store = $this->newWatchedItemStore(
1007 $this->getMockLBFactory( $mockDb ),
1009 $this->getMockReadOnlyMode()
1012 $store->duplicateAllAssociatedEntries(
1018 public function testAddWatch_nonAnonymousUser() {
1019 $mockDb = $this->getMockDb();
1020 $mockDb->expects( $this->once() )
1021 ->method( 'insert' )
1027 'wl_namespace' => 0,
1028 'wl_title' => 'Some_Page',
1029 'wl_notificationtimestamp' => null,
1034 $mockCache = $this->getMockCache();
1035 $mockCache->expects( $this->once() )
1036 ->method( 'delete' )
1037 ->with( '0:Some_Page:1' );
1039 $store = $this->newWatchedItemStore(
1040 $this->getMockLBFactory( $mockDb ),
1042 $this->getMockReadOnlyMode()
1046 $this->getMockNonAnonUserWithId( 1 ),
1047 Title
::newFromText( 'Some_Page' )
1051 public function testAddWatch_anonymousUser() {
1052 $mockDb = $this->getMockDb();
1053 $mockDb->expects( $this->never() )
1054 ->method( 'insert' );
1056 $mockCache = $this->getMockCache();
1057 $mockCache->expects( $this->never() )
1058 ->method( 'delete' );
1060 $store = $this->newWatchedItemStore(
1061 $this->getMockLBFactory( $mockDb ),
1063 $this->getMockReadOnlyMode()
1067 $this->getAnonUser(),
1068 Title
::newFromText( 'Some_Page' )
1072 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1073 $store = $this->newWatchedItemStore(
1074 $this->getMockLBFactory( $this->getMockDb() ),
1075 $this->getMockCache(),
1076 $this->getMockReadOnlyMode( true )
1080 $store->addWatchBatchForUser(
1081 $this->getMockNonAnonUserWithId( 1 ),
1082 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1087 public function testAddWatchBatchForUser_nonAnonymousUser() {
1088 $mockDb = $this->getMockDb();
1089 $mockDb->expects( $this->once() )
1090 ->method( 'insert' )
1096 'wl_namespace' => 0,
1097 'wl_title' => 'Some_Page',
1098 'wl_notificationtimestamp' => null,
1102 'wl_namespace' => 1,
1103 'wl_title' => 'Some_Page',
1104 'wl_notificationtimestamp' => null,
1109 $mockCache = $this->getMockCache();
1110 $mockCache->expects( $this->exactly( 2 ) )
1111 ->method( 'delete' );
1112 $mockCache->expects( $this->at( 1 ) )
1113 ->method( 'delete' )
1114 ->with( '0:Some_Page:1' );
1115 $mockCache->expects( $this->at( 3 ) )
1116 ->method( 'delete' )
1117 ->with( '1:Some_Page:1' );
1119 $store = $this->newWatchedItemStore(
1120 $this->getMockLBFactory( $mockDb ),
1122 $this->getMockReadOnlyMode()
1125 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1128 $store->addWatchBatchForUser(
1130 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1135 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1136 $mockDb = $this->getMockDb();
1137 $mockDb->expects( $this->never() )
1138 ->method( 'insert' );
1140 $mockCache = $this->getMockCache();
1141 $mockCache->expects( $this->never() )
1142 ->method( 'delete' );
1144 $store = $this->newWatchedItemStore(
1145 $this->getMockLBFactory( $mockDb ),
1147 $this->getMockReadOnlyMode()
1151 $store->addWatchBatchForUser(
1152 $this->getAnonUser(),
1153 [ new TitleValue( 0, 'Other_Page' ) ]
1158 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1159 $user = $this->getMockNonAnonUserWithId( 1 );
1160 $mockDb = $this->getMockDb();
1161 $mockDb->expects( $this->never() )
1162 ->method( 'insert' );
1164 $mockCache = $this->getMockCache();
1165 $mockCache->expects( $this->never() )
1166 ->method( 'delete' );
1168 $store = $this->newWatchedItemStore(
1169 $this->getMockLBFactory( $mockDb ),
1171 $this->getMockReadOnlyMode()
1175 $store->addWatchBatchForUser( $user, [] )
1179 public function testLoadWatchedItem_existingItem() {
1180 $mockDb = $this->getMockDb();
1181 $mockDb->expects( $this->once() )
1182 ->method( 'selectRow' )
1185 'wl_notificationtimestamp',
1188 'wl_namespace' => 0,
1189 'wl_title' => 'SomeDbKey',
1192 ->will( $this->returnValue(
1193 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1196 $mockCache = $this->getMockCache();
1197 $mockCache->expects( $this->once() )
1203 $store = $this->newWatchedItemStore(
1204 $this->getMockLBFactory( $mockDb ),
1206 $this->getMockReadOnlyMode()
1209 $watchedItem = $store->loadWatchedItem(
1210 $this->getMockNonAnonUserWithId( 1 ),
1211 new TitleValue( 0, 'SomeDbKey' )
1213 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1214 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1215 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1216 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1219 public function testLoadWatchedItem_noItem() {
1220 $mockDb = $this->getMockDb();
1221 $mockDb->expects( $this->once() )
1222 ->method( 'selectRow' )
1225 'wl_notificationtimestamp',
1228 'wl_namespace' => 0,
1229 'wl_title' => 'SomeDbKey',
1232 ->will( $this->returnValue( [] ) );
1234 $mockCache = $this->getMockCache();
1235 $mockCache->expects( $this->never() )->method( 'get' );
1236 $mockCache->expects( $this->never() )->method( 'delete' );
1238 $store = $this->newWatchedItemStore(
1239 $this->getMockLBFactory( $mockDb ),
1241 $this->getMockReadOnlyMode()
1245 $store->loadWatchedItem(
1246 $this->getMockNonAnonUserWithId( 1 ),
1247 new TitleValue( 0, 'SomeDbKey' )
1252 public function testLoadWatchedItem_anonymousUser() {
1253 $mockDb = $this->getMockDb();
1254 $mockDb->expects( $this->never() )
1255 ->method( 'selectRow' );
1257 $mockCache = $this->getMockCache();
1258 $mockCache->expects( $this->never() )->method( 'get' );
1259 $mockCache->expects( $this->never() )->method( 'delete' );
1261 $store = $this->newWatchedItemStore(
1262 $this->getMockLBFactory( $mockDb ),
1264 $this->getMockReadOnlyMode()
1268 $store->loadWatchedItem(
1269 $this->getAnonUser(),
1270 new TitleValue( 0, 'SomeDbKey' )
1275 public function testRemoveWatch_existingItem() {
1276 $mockDb = $this->getMockDb();
1277 $mockDb->expects( $this->once() )
1278 ->method( 'delete' )
1283 'wl_namespace' => 0,
1284 'wl_title' => 'SomeDbKey',
1287 $mockDb->expects( $this->once() )
1288 ->method( 'affectedRows' )
1289 ->will( $this->returnValue( 1 ) );
1291 $mockCache = $this->getMockCache();
1292 $mockCache->expects( $this->never() )->method( 'get' );
1293 $mockCache->expects( $this->once() )
1294 ->method( 'delete' )
1295 ->with( '0:SomeDbKey:1' );
1297 $store = $this->newWatchedItemStore(
1298 $this->getMockLBFactory( $mockDb ),
1300 $this->getMockReadOnlyMode()
1304 $store->removeWatch(
1305 $this->getMockNonAnonUserWithId( 1 ),
1306 new TitleValue( 0, 'SomeDbKey' )
1311 public function testRemoveWatch_noItem() {
1312 $mockDb = $this->getMockDb();
1313 $mockDb->expects( $this->once() )
1314 ->method( 'delete' )
1319 'wl_namespace' => 0,
1320 'wl_title' => 'SomeDbKey',
1323 $mockDb->expects( $this->once() )
1324 ->method( 'affectedRows' )
1325 ->will( $this->returnValue( 0 ) );
1327 $mockCache = $this->getMockCache();
1328 $mockCache->expects( $this->never() )->method( 'get' );
1329 $mockCache->expects( $this->once() )
1330 ->method( 'delete' )
1331 ->with( '0:SomeDbKey:1' );
1333 $store = $this->newWatchedItemStore(
1334 $this->getMockLBFactory( $mockDb ),
1336 $this->getMockReadOnlyMode()
1340 $store->removeWatch(
1341 $this->getMockNonAnonUserWithId( 1 ),
1342 new TitleValue( 0, 'SomeDbKey' )
1347 public function testRemoveWatch_anonymousUser() {
1348 $mockDb = $this->getMockDb();
1349 $mockDb->expects( $this->never() )
1350 ->method( 'delete' );
1352 $mockCache = $this->getMockCache();
1353 $mockCache->expects( $this->never() )->method( 'get' );
1354 $mockCache->expects( $this->never() )
1355 ->method( 'delete' );
1357 $store = $this->newWatchedItemStore(
1358 $this->getMockLBFactory( $mockDb ),
1360 $this->getMockReadOnlyMode()
1364 $store->removeWatch(
1365 $this->getAnonUser(),
1366 new TitleValue( 0, 'SomeDbKey' )
1371 public function testGetWatchedItem_existingItem() {
1372 $mockDb = $this->getMockDb();
1373 $mockDb->expects( $this->once() )
1374 ->method( 'selectRow' )
1377 'wl_notificationtimestamp',
1380 'wl_namespace' => 0,
1381 'wl_title' => 'SomeDbKey',
1384 ->will( $this->returnValue(
1385 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1388 $mockCache = $this->getMockCache();
1389 $mockCache->expects( $this->never() )->method( 'delete' );
1390 $mockCache->expects( $this->once() )
1395 ->will( $this->returnValue( null ) );
1396 $mockCache->expects( $this->once() )
1402 $store = $this->newWatchedItemStore(
1403 $this->getMockLBFactory( $mockDb ),
1405 $this->getMockReadOnlyMode()
1408 $watchedItem = $store->getWatchedItem(
1409 $this->getMockNonAnonUserWithId( 1 ),
1410 new TitleValue( 0, 'SomeDbKey' )
1412 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1413 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1414 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1415 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1418 public function testGetWatchedItem_cachedItem() {
1419 $mockDb = $this->getMockDb();
1420 $mockDb->expects( $this->never() )
1421 ->method( 'selectRow' );
1423 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1424 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1425 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1427 $mockCache = $this->getMockCache();
1428 $mockCache->expects( $this->never() )->method( 'delete' );
1429 $mockCache->expects( $this->never() )->method( 'set' );
1430 $mockCache->expects( $this->once() )
1435 ->will( $this->returnValue( $cachedItem ) );
1437 $store = $this->newWatchedItemStore(
1438 $this->getMockLBFactory( $mockDb ),
1440 $this->getMockReadOnlyMode()
1443 $this->assertEquals(
1445 $store->getWatchedItem(
1452 public function testGetWatchedItem_noItem() {
1453 $mockDb = $this->getMockDb();
1454 $mockDb->expects( $this->once() )
1455 ->method( 'selectRow' )
1458 'wl_notificationtimestamp',
1461 'wl_namespace' => 0,
1462 'wl_title' => 'SomeDbKey',
1465 ->will( $this->returnValue( [] ) );
1467 $mockCache = $this->getMockCache();
1468 $mockCache->expects( $this->never() )->method( 'set' );
1469 $mockCache->expects( $this->never() )->method( 'delete' );
1470 $mockCache->expects( $this->once() )
1472 ->with( '0:SomeDbKey:1' )
1473 ->will( $this->returnValue( false ) );
1475 $store = $this->newWatchedItemStore(
1476 $this->getMockLBFactory( $mockDb ),
1478 $this->getMockReadOnlyMode()
1482 $store->getWatchedItem(
1483 $this->getMockNonAnonUserWithId( 1 ),
1484 new TitleValue( 0, 'SomeDbKey' )
1489 public function testGetWatchedItem_anonymousUser() {
1490 $mockDb = $this->getMockDb();
1491 $mockDb->expects( $this->never() )
1492 ->method( 'selectRow' );
1494 $mockCache = $this->getMockCache();
1495 $mockCache->expects( $this->never() )->method( 'set' );
1496 $mockCache->expects( $this->never() )->method( 'get' );
1497 $mockCache->expects( $this->never() )->method( 'delete' );
1499 $store = $this->newWatchedItemStore(
1500 $this->getMockLBFactory( $mockDb ),
1502 $this->getMockReadOnlyMode()
1506 $store->getWatchedItem(
1507 $this->getAnonUser(),
1508 new TitleValue( 0, 'SomeDbKey' )
1513 public function testGetWatchedItemsForUser() {
1514 $mockDb = $this->getMockDb();
1515 $mockDb->expects( $this->once() )
1516 ->method( 'select' )
1519 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1522 ->will( $this->returnValue( [
1523 $this->getFakeRow( [
1524 'wl_namespace' => 0,
1525 'wl_title' => 'Foo1',
1526 'wl_notificationtimestamp' => '20151212010101',
1528 $this->getFakeRow( [
1529 'wl_namespace' => 1,
1530 'wl_title' => 'Foo2',
1531 'wl_notificationtimestamp' => null,
1535 $mockCache = $this->getMockCache();
1536 $mockCache->expects( $this->never() )->method( 'delete' );
1537 $mockCache->expects( $this->never() )->method( 'get' );
1538 $mockCache->expects( $this->never() )->method( 'set' );
1540 $store = $this->newWatchedItemStore(
1541 $this->getMockLBFactory( $mockDb ),
1543 $this->getMockReadOnlyMode()
1545 $user = $this->getMockNonAnonUserWithId( 1 );
1547 $watchedItems = $store->getWatchedItemsForUser( $user );
1549 $this->assertInternalType( 'array', $watchedItems );
1550 $this->assertCount( 2, $watchedItems );
1551 foreach ( $watchedItems as $watchedItem ) {
1552 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1554 $this->assertEquals(
1555 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1558 $this->assertEquals(
1559 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1564 public function provideDbTypes() {
1566 [ false, DB_REPLICA
],
1567 [ true, DB_MASTER
],
1572 * @dataProvider provideDbTypes
1574 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1575 $mockDb = $this->getMockDb();
1576 $mockCache = $this->getMockCache();
1577 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1578 $user = $this->getMockNonAnonUserWithId( 1 );
1580 $mockDb->expects( $this->once() )
1581 ->method( 'select' )
1584 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1586 $this->isType( 'string' ),
1587 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1589 ->will( $this->returnValue( [] ) );
1591 $store = $this->newWatchedItemStore(
1594 $this->getMockReadOnlyMode()
1597 $watchedItems = $store->getWatchedItemsForUser(
1599 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1601 $this->assertEquals( [], $watchedItems );
1604 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1605 $store = $this->newWatchedItemStore(
1606 $this->getMockLBFactory( $this->getMockDb() ),
1607 $this->getMockCache(),
1608 $this->getMockReadOnlyMode()
1611 $this->setExpectedException( InvalidArgumentException
::class );
1612 $store->getWatchedItemsForUser(
1613 $this->getMockNonAnonUserWithId( 1 ),
1618 public function testIsWatchedItem_existingItem() {
1619 $mockDb = $this->getMockDb();
1620 $mockDb->expects( $this->once() )
1621 ->method( 'selectRow' )
1624 'wl_notificationtimestamp',
1627 'wl_namespace' => 0,
1628 'wl_title' => 'SomeDbKey',
1631 ->will( $this->returnValue(
1632 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1635 $mockCache = $this->getMockCache();
1636 $mockCache->expects( $this->never() )->method( 'delete' );
1637 $mockCache->expects( $this->once() )
1639 ->with( '0:SomeDbKey:1' )
1640 ->will( $this->returnValue( false ) );
1641 $mockCache->expects( $this->once() )
1647 $store = $this->newWatchedItemStore(
1648 $this->getMockLBFactory( $mockDb ),
1650 $this->getMockReadOnlyMode()
1655 $this->getMockNonAnonUserWithId( 1 ),
1656 new TitleValue( 0, 'SomeDbKey' )
1661 public function testIsWatchedItem_noItem() {
1662 $mockDb = $this->getMockDb();
1663 $mockDb->expects( $this->once() )
1664 ->method( 'selectRow' )
1667 'wl_notificationtimestamp',
1670 'wl_namespace' => 0,
1671 'wl_title' => 'SomeDbKey',
1674 ->will( $this->returnValue( [] ) );
1676 $mockCache = $this->getMockCache();
1677 $mockCache->expects( $this->never() )->method( 'set' );
1678 $mockCache->expects( $this->never() )->method( 'delete' );
1679 $mockCache->expects( $this->once() )
1681 ->with( '0:SomeDbKey:1' )
1682 ->will( $this->returnValue( false ) );
1684 $store = $this->newWatchedItemStore(
1685 $this->getMockLBFactory( $mockDb ),
1687 $this->getMockReadOnlyMode()
1692 $this->getMockNonAnonUserWithId( 1 ),
1693 new TitleValue( 0, 'SomeDbKey' )
1698 public function testIsWatchedItem_anonymousUser() {
1699 $mockDb = $this->getMockDb();
1700 $mockDb->expects( $this->never() )
1701 ->method( 'selectRow' );
1703 $mockCache = $this->getMockCache();
1704 $mockCache->expects( $this->never() )->method( 'set' );
1705 $mockCache->expects( $this->never() )->method( 'get' );
1706 $mockCache->expects( $this->never() )->method( 'delete' );
1708 $store = $this->newWatchedItemStore(
1709 $this->getMockLBFactory( $mockDb ),
1711 $this->getMockReadOnlyMode()
1716 $this->getAnonUser(),
1717 new TitleValue( 0, 'SomeDbKey' )
1722 public function testGetNotificationTimestampsBatch() {
1724 new TitleValue( 0, 'SomeDbKey' ),
1725 new TitleValue( 1, 'AnotherDbKey' ),
1728 $mockDb = $this->getMockDb();
1730 $this->getFakeRow( [
1731 'wl_namespace' => '0',
1732 'wl_title' => 'SomeDbKey',
1733 'wl_notificationtimestamp' => '20151212010101',
1737 'wl_namespace' => '1',
1738 'wl_title' => 'AnotherDbKey',
1739 'wl_notificationtimestamp' => null,
1744 $mockDb->expects( $this->once() )
1745 ->method( 'makeWhereFrom2d' )
1747 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1748 $this->isType( 'string' ),
1749 $this->isType( 'string' )
1751 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1752 $mockDb->expects( $this->once() )
1753 ->method( 'select' )
1756 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1758 'makeWhereFrom2d return value',
1761 $this->isType( 'string' )
1763 ->will( $this->returnValue( $dbResult ) );
1765 $mockCache = $this->getMockCache();
1766 $mockCache->expects( $this->exactly( 2 ) )
1769 [ '0:SomeDbKey:1' ],
1770 [ '1:AnotherDbKey:1' ]
1772 ->will( $this->returnValue( null ) );
1773 $mockCache->expects( $this->never() )->method( 'set' );
1774 $mockCache->expects( $this->never() )->method( 'delete' );
1776 $store = $this->newWatchedItemStore(
1777 $this->getMockLBFactory( $mockDb ),
1779 $this->getMockReadOnlyMode()
1782 $this->assertEquals(
1784 0 => [ 'SomeDbKey' => '20151212010101', ],
1785 1 => [ 'AnotherDbKey' => null, ],
1787 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1791 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1793 new TitleValue( 0, 'OtherDbKey' ),
1796 $mockDb = $this->getMockDb();
1798 $mockDb->expects( $this->once() )
1799 ->method( 'makeWhereFrom2d' )
1801 [ [ 'OtherDbKey' => 1 ] ],
1802 $this->isType( 'string' ),
1803 $this->isType( 'string' )
1805 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1806 $mockDb->expects( $this->once() )
1807 ->method( 'select' )
1810 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1812 'makeWhereFrom2d return value',
1815 $this->isType( 'string' )
1817 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1819 $mockCache = $this->getMockCache();
1820 $mockCache->expects( $this->once() )
1822 ->with( '0:OtherDbKey:1' )
1823 ->will( $this->returnValue( null ) );
1824 $mockCache->expects( $this->never() )->method( 'set' );
1825 $mockCache->expects( $this->never() )->method( 'delete' );
1827 $store = $this->newWatchedItemStore(
1828 $this->getMockLBFactory( $mockDb ),
1830 $this->getMockReadOnlyMode()
1833 $this->assertEquals(
1835 0 => [ 'OtherDbKey' => false, ],
1837 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1841 public function testGetNotificationTimestampsBatch_cachedItem() {
1843 new TitleValue( 0, 'SomeDbKey' ),
1844 new TitleValue( 1, 'AnotherDbKey' ),
1847 $user = $this->getMockNonAnonUserWithId( 1 );
1848 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1850 $mockDb = $this->getMockDb();
1852 $mockDb->expects( $this->once() )
1853 ->method( 'makeWhereFrom2d' )
1855 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1856 $this->isType( 'string' ),
1857 $this->isType( 'string' )
1859 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1860 $mockDb->expects( $this->once() )
1861 ->method( 'select' )
1864 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1866 'makeWhereFrom2d return value',
1869 $this->isType( 'string' )
1871 ->will( $this->returnValue( [
1873 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1877 $mockCache = $this->getMockCache();
1878 $mockCache->expects( $this->at( 1 ) )
1880 ->with( '0:SomeDbKey:1' )
1881 ->will( $this->returnValue( $cachedItem ) );
1882 $mockCache->expects( $this->at( 3 ) )
1884 ->with( '1:AnotherDbKey:1' )
1885 ->will( $this->returnValue( null ) );
1886 $mockCache->expects( $this->never() )->method( 'set' );
1887 $mockCache->expects( $this->never() )->method( 'delete' );
1889 $store = $this->newWatchedItemStore(
1890 $this->getMockLBFactory( $mockDb ),
1892 $this->getMockReadOnlyMode()
1895 $this->assertEquals(
1897 0 => [ 'SomeDbKey' => '20151212010101', ],
1898 1 => [ 'AnotherDbKey' => null, ],
1900 $store->getNotificationTimestampsBatch( $user, $targets )
1904 public function testGetNotificationTimestampsBatch_allItemsCached() {
1906 new TitleValue( 0, 'SomeDbKey' ),
1907 new TitleValue( 1, 'AnotherDbKey' ),
1910 $user = $this->getMockNonAnonUserWithId( 1 );
1912 new WatchedItem( $user, $targets[0], '20151212010101' ),
1913 new WatchedItem( $user, $targets[1], null ),
1915 $mockDb = $this->getMockDb();
1916 $mockDb->expects( $this->never() )->method( $this->anything() );
1918 $mockCache = $this->getMockCache();
1919 $mockCache->expects( $this->at( 1 ) )
1921 ->with( '0:SomeDbKey:1' )
1922 ->will( $this->returnValue( $cachedItems[0] ) );
1923 $mockCache->expects( $this->at( 3 ) )
1925 ->with( '1:AnotherDbKey:1' )
1926 ->will( $this->returnValue( $cachedItems[1] ) );
1927 $mockCache->expects( $this->never() )->method( 'set' );
1928 $mockCache->expects( $this->never() )->method( 'delete' );
1930 $store = $this->newWatchedItemStore(
1931 $this->getMockLBFactory( $mockDb ),
1933 $this->getMockReadOnlyMode()
1936 $this->assertEquals(
1938 0 => [ 'SomeDbKey' => '20151212010101', ],
1939 1 => [ 'AnotherDbKey' => null, ],
1941 $store->getNotificationTimestampsBatch( $user, $targets )
1945 public function testGetNotificationTimestampsBatch_anonymousUser() {
1947 new TitleValue( 0, 'SomeDbKey' ),
1948 new TitleValue( 1, 'AnotherDbKey' ),
1951 $mockDb = $this->getMockDb();
1952 $mockDb->expects( $this->never() )->method( $this->anything() );
1954 $mockCache = $this->getMockCache();
1955 $mockCache->expects( $this->never() )->method( $this->anything() );
1957 $store = $this->newWatchedItemStore(
1958 $this->getMockLBFactory( $mockDb ),
1960 $this->getMockReadOnlyMode()
1963 $this->assertEquals(
1965 0 => [ 'SomeDbKey' => false, ],
1966 1 => [ 'AnotherDbKey' => false, ],
1968 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1972 public function testResetNotificationTimestamp_anonymousUser() {
1973 $mockDb = $this->getMockDb();
1974 $mockDb->expects( $this->never() )
1975 ->method( 'selectRow' );
1977 $mockCache = $this->getMockCache();
1978 $mockCache->expects( $this->never() )->method( 'get' );
1979 $mockCache->expects( $this->never() )->method( 'set' );
1980 $mockCache->expects( $this->never() )->method( 'delete' );
1982 $store = $this->newWatchedItemStore(
1983 $this->getMockLBFactory( $mockDb ),
1985 $this->getMockReadOnlyMode()
1989 $store->resetNotificationTimestamp(
1990 $this->getAnonUser(),
1991 Title
::newFromText( 'SomeDbKey' )
1996 public function testResetNotificationTimestamp_noItem() {
1997 $mockDb = $this->getMockDb();
1998 $mockDb->expects( $this->once() )
1999 ->method( 'selectRow' )
2002 'wl_notificationtimestamp',
2005 'wl_namespace' => 0,
2006 'wl_title' => 'SomeDbKey',
2009 ->will( $this->returnValue( [] ) );
2011 $mockCache = $this->getMockCache();
2012 $mockCache->expects( $this->never() )->method( 'get' );
2013 $mockCache->expects( $this->never() )->method( 'set' );
2014 $mockCache->expects( $this->never() )->method( 'delete' );
2016 $store = $this->newWatchedItemStore(
2017 $this->getMockLBFactory( $mockDb ),
2019 $this->getMockReadOnlyMode()
2023 $store->resetNotificationTimestamp(
2024 $this->getMockNonAnonUserWithId( 1 ),
2025 Title
::newFromText( 'SomeDbKey' )
2030 public function testResetNotificationTimestamp_item() {
2031 $user = $this->getMockNonAnonUserWithId( 1 );
2032 $title = Title
::newFromText( 'SomeDbKey' );
2034 $mockDb = $this->getMockDb();
2035 $mockDb->expects( $this->once() )
2036 ->method( 'selectRow' )
2039 'wl_notificationtimestamp',
2042 'wl_namespace' => 0,
2043 'wl_title' => 'SomeDbKey',
2046 ->will( $this->returnValue(
2047 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2050 $mockCache = $this->getMockCache();
2051 $mockCache->expects( $this->never() )->method( 'get' );
2052 $mockCache->expects( $this->once() )
2056 $this->isInstanceOf( WatchedItem
::class )
2058 $mockCache->expects( $this->once() )
2059 ->method( 'delete' )
2060 ->with( '0:SomeDbKey:1' );
2062 $store = $this->newWatchedItemStore(
2063 $this->getMockLBFactory( $mockDb ),
2065 $this->getMockReadOnlyMode()
2068 // Note: This does not actually assert the job is correct
2069 $callableCallCounter = 0;
2070 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2071 $callableCallCounter++
;
2072 $this->assertInternalType( 'callable', $callable );
2074 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2077 $store->resetNotificationTimestamp(
2082 $this->assertEquals( 1, $callableCallCounter );
2084 ScopedCallback
::consume( $scopedOverride );
2087 public function testResetNotificationTimestamp_noItemForced() {
2088 $user = $this->getMockNonAnonUserWithId( 1 );
2089 $title = Title
::newFromText( 'SomeDbKey' );
2091 $mockDb = $this->getMockDb();
2092 $mockDb->expects( $this->never() )
2093 ->method( 'selectRow' );
2095 $mockCache = $this->getMockCache();
2096 $mockCache->expects( $this->never() )->method( 'get' );
2097 $mockCache->expects( $this->never() )->method( 'set' );
2098 $mockCache->expects( $this->once() )
2099 ->method( 'delete' )
2100 ->with( '0:SomeDbKey:1' );
2102 $store = $this->newWatchedItemStore(
2103 $this->getMockLBFactory( $mockDb ),
2105 $this->getMockReadOnlyMode()
2108 // Note: This does not actually assert the job is correct
2109 $callableCallCounter = 0;
2110 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2111 $callableCallCounter++
;
2112 $this->assertInternalType( 'callable', $callable );
2114 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2117 $store->resetNotificationTimestamp(
2123 $this->assertEquals( 1, $callableCallCounter );
2125 ScopedCallback
::consume( $scopedOverride );
2129 * @param string $text
2132 * @return PHPUnit_Framework_MockObject_MockObject|Title
2134 private function getMockTitle( $text, $ns = 0 ) {
2135 $title = $this->createMock( Title
::class );
2136 $title->expects( $this->any() )
2137 ->method( 'getText' )
2138 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2139 $title->expects( $this->any() )
2140 ->method( 'getDbKey' )
2141 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2142 $title->expects( $this->any() )
2143 ->method( 'getNamespace' )
2144 ->will( $this->returnValue( $ns ) );
2148 private function verifyCallbackJob(
2150 LinkTarget
$expectedTitle,
2152 callable
$notificationTimestampCondition
2154 $this->assertInternalType( 'callable', $callback );
2156 $callbackReflector = new ReflectionFunction( $callback );
2157 $vars = $callbackReflector->getStaticVariables();
2158 $this->assertArrayHasKey( 'job', $vars );
2159 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2161 /** @var ActivityUpdateJob $job */
2162 $job = $vars['job'];
2163 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2164 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2166 $jobParams = $job->getParams();
2167 $this->assertArrayHasKey( 'type', $jobParams );
2168 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2169 $this->assertArrayHasKey( 'userid', $jobParams );
2170 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2171 $this->assertArrayHasKey( 'notifTime', $jobParams );
2172 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2175 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2176 $user = $this->getMockNonAnonUserWithId( 1 );
2178 $title = $this->getMockTitle( 'SomeTitle' );
2179 $title->expects( $this->once() )
2180 ->method( 'getNextRevisionID' )
2182 ->will( $this->returnValue( false ) );
2184 $mockDb = $this->getMockDb();
2185 $mockDb->expects( $this->never() )
2186 ->method( 'selectRow' );
2188 $mockCache = $this->getMockCache();
2189 $mockCache->expects( $this->never() )->method( 'get' );
2190 $mockCache->expects( $this->never() )->method( 'set' );
2191 $mockCache->expects( $this->once() )
2192 ->method( 'delete' )
2193 ->with( '0:SomeTitle:1' );
2195 $store = $this->newWatchedItemStore(
2196 $this->getMockLBFactory( $mockDb ),
2198 $this->getMockReadOnlyMode()
2201 $callableCallCounter = 0;
2202 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2203 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2204 $callableCallCounter++
;
2205 $this->verifyCallbackJob(
2209 function ( $time ) {
2210 return $time === null;
2217 $store->resetNotificationTimestamp(
2224 $this->assertEquals( 1, $callableCallCounter );
2226 ScopedCallback
::consume( $scopedOverride );
2229 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2230 $user = $this->getMockNonAnonUserWithId( 1 );
2232 $title = $this->getMockTitle( 'SomeDbKey' );
2233 $title->expects( $this->once() )
2234 ->method( 'getNextRevisionID' )
2236 ->will( $this->returnValue( 33 ) );
2238 $mockDb = $this->getMockDb();
2239 $mockDb->expects( $this->once() )
2240 ->method( 'selectRow' )
2243 'wl_notificationtimestamp',
2246 'wl_namespace' => 0,
2247 'wl_title' => 'SomeDbKey',
2250 ->will( $this->returnValue(
2251 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2254 $mockCache = $this->getMockCache();
2255 $mockCache->expects( $this->never() )->method( 'get' );
2256 $mockCache->expects( $this->once() )
2258 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2259 $mockCache->expects( $this->once() )
2260 ->method( 'delete' )
2261 ->with( '0:SomeDbKey:1' );
2263 $store = $this->newWatchedItemStore(
2264 $this->getMockLBFactory( $mockDb ),
2266 $this->getMockReadOnlyMode()
2269 $addUpdateCallCounter = 0;
2270 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2271 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2272 $addUpdateCallCounter++
;
2273 $this->verifyCallbackJob(
2277 function ( $time ) {
2278 return $time !== null && $time > '20151212010101';
2284 $getTimestampCallCounter = 0;
2285 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2286 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2287 $getTimestampCallCounter++
;
2288 $this->assertEquals( $title, $titleParam );
2289 $this->assertEquals( $oldid, $oldidParam );
2294 $store->resetNotificationTimestamp(
2301 $this->assertEquals( 1, $addUpdateCallCounter );
2302 $this->assertEquals( 1, $getTimestampCallCounter );
2304 ScopedCallback
::consume( $scopedOverrideDeferred );
2305 ScopedCallback
::consume( $scopedOverrideRevision );
2308 public function testResetNotificationTimestamp_notWatchedPageForced() {
2309 $user = $this->getMockNonAnonUserWithId( 1 );
2311 $title = $this->getMockTitle( 'SomeDbKey' );
2312 $title->expects( $this->once() )
2313 ->method( 'getNextRevisionID' )
2315 ->will( $this->returnValue( 33 ) );
2317 $mockDb = $this->getMockDb();
2318 $mockDb->expects( $this->once() )
2319 ->method( 'selectRow' )
2322 'wl_notificationtimestamp',
2325 'wl_namespace' => 0,
2326 'wl_title' => 'SomeDbKey',
2329 ->will( $this->returnValue( false ) );
2331 $mockCache = $this->getMockCache();
2332 $mockCache->expects( $this->never() )->method( 'get' );
2333 $mockCache->expects( $this->never() )->method( 'set' );
2334 $mockCache->expects( $this->once() )
2335 ->method( 'delete' )
2336 ->with( '0:SomeDbKey:1' );
2338 $store = $this->newWatchedItemStore(
2339 $this->getMockLBFactory( $mockDb ),
2341 $this->getMockReadOnlyMode()
2344 $callableCallCounter = 0;
2345 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2346 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2347 $callableCallCounter++
;
2348 $this->verifyCallbackJob(
2352 function ( $time ) {
2353 return $time === null;
2360 $store->resetNotificationTimestamp(
2367 $this->assertEquals( 1, $callableCallCounter );
2369 ScopedCallback
::consume( $scopedOverride );
2372 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2373 $user = $this->getMockNonAnonUserWithId( 1 );
2375 $title = $this->getMockTitle( 'SomeDbKey' );
2376 $title->expects( $this->once() )
2377 ->method( 'getNextRevisionID' )
2379 ->will( $this->returnValue( 33 ) );
2381 $mockDb = $this->getMockDb();
2382 $mockDb->expects( $this->once() )
2383 ->method( 'selectRow' )
2386 'wl_notificationtimestamp',
2389 'wl_namespace' => 0,
2390 'wl_title' => 'SomeDbKey',
2393 ->will( $this->returnValue(
2394 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2397 $mockCache = $this->getMockCache();
2398 $mockCache->expects( $this->never() )->method( 'get' );
2399 $mockCache->expects( $this->once() )
2401 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2402 $mockCache->expects( $this->once() )
2403 ->method( 'delete' )
2404 ->with( '0:SomeDbKey:1' );
2406 $store = $this->newWatchedItemStore(
2407 $this->getMockLBFactory( $mockDb ),
2409 $this->getMockReadOnlyMode()
2412 $addUpdateCallCounter = 0;
2413 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2414 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2415 $addUpdateCallCounter++
;
2416 $this->verifyCallbackJob(
2420 function ( $time ) {
2421 return $time === '30151212010101';
2427 $getTimestampCallCounter = 0;
2428 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2429 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2430 $getTimestampCallCounter++
;
2431 $this->assertEquals( $title, $titleParam );
2432 $this->assertEquals( $oldid, $oldidParam );
2437 $store->resetNotificationTimestamp(
2444 $this->assertEquals( 1, $addUpdateCallCounter );
2445 $this->assertEquals( 1, $getTimestampCallCounter );
2447 ScopedCallback
::consume( $scopedOverrideDeferred );
2448 ScopedCallback
::consume( $scopedOverrideRevision );
2451 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2452 $user = $this->getMockNonAnonUserWithId( 1 );
2454 $title = $this->getMockTitle( 'SomeDbKey' );
2455 $title->expects( $this->once() )
2456 ->method( 'getNextRevisionID' )
2458 ->will( $this->returnValue( 33 ) );
2460 $mockDb = $this->getMockDb();
2461 $mockDb->expects( $this->once() )
2462 ->method( 'selectRow' )
2465 'wl_notificationtimestamp',
2468 'wl_namespace' => 0,
2469 'wl_title' => 'SomeDbKey',
2472 ->will( $this->returnValue(
2473 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2476 $mockCache = $this->getMockCache();
2477 $mockCache->expects( $this->never() )->method( 'get' );
2478 $mockCache->expects( $this->once() )
2480 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2481 $mockCache->expects( $this->once() )
2482 ->method( 'delete' )
2483 ->with( '0:SomeDbKey:1' );
2485 $store = $this->newWatchedItemStore(
2486 $this->getMockLBFactory( $mockDb ),
2488 $this->getMockReadOnlyMode()
2491 $addUpdateCallCounter = 0;
2492 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2493 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2494 $addUpdateCallCounter++
;
2495 $this->verifyCallbackJob(
2499 function ( $time ) {
2500 return $time === false;
2506 $getTimestampCallCounter = 0;
2507 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2508 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2509 $getTimestampCallCounter++
;
2510 $this->assertEquals( $title, $titleParam );
2511 $this->assertEquals( $oldid, $oldidParam );
2516 $store->resetNotificationTimestamp(
2523 $this->assertEquals( 1, $addUpdateCallCounter );
2524 $this->assertEquals( 1, $getTimestampCallCounter );
2526 ScopedCallback
::consume( $scopedOverrideDeferred );
2527 ScopedCallback
::consume( $scopedOverrideRevision );
2530 public function testSetNotificationTimestampsForUser_anonUser() {
2531 $store = $this->newWatchedItemStore(
2532 $this->getMockLBFactory( $this->getMockDb() ),
2533 $this->getMockCache(),
2534 $this->getMockReadOnlyMode()
2536 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2539 public function testSetNotificationTimestampsForUser_allRows() {
2540 $user = $this->getMockNonAnonUserWithId( 1 );
2541 $timestamp = '20100101010101';
2543 $mockDb = $this->getMockDb();
2544 $mockDb->expects( $this->once() )
2545 ->method( 'update' )
2548 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2551 ->will( $this->returnValue( true ) );
2552 $mockDb->expects( $this->exactly( 1 ) )
2553 ->method( 'timestamp' )
2554 ->will( $this->returnCallback( function ( $value ) {
2555 return 'TS' . $value . 'TS';
2558 $store = $this->newWatchedItemStore(
2559 $this->getMockLBFactory( $mockDb ),
2560 $this->getMockCache(),
2561 $this->getMockReadOnlyMode()
2565 $store->setNotificationTimestampsForUser( $user, $timestamp )
2569 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2570 $user = $this->getMockNonAnonUserWithId( 1 );
2573 $mockDb = $this->getMockDb();
2574 $mockDb->expects( $this->once() )
2575 ->method( 'update' )
2578 [ 'wl_notificationtimestamp' => null ],
2581 ->will( $this->returnValue( true ) );
2582 $mockDb->expects( $this->exactly( 0 ) )
2583 ->method( 'timestamp' )
2584 ->will( $this->returnCallback( function ( $value ) {
2585 return 'TS' . $value . 'TS';
2588 $store = $this->newWatchedItemStore(
2589 $this->getMockLBFactory( $mockDb ),
2590 $this->getMockCache(),
2591 $this->getMockReadOnlyMode()
2595 $store->setNotificationTimestampsForUser( $user, $timestamp )
2599 public function testSetNotificationTimestampsForUser_specificTargets() {
2600 $user = $this->getMockNonAnonUserWithId( 1 );
2601 $timestamp = '20100101010101';
2602 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2604 $mockDb = $this->getMockDb();
2605 $mockDb->expects( $this->once() )
2606 ->method( 'update' )
2609 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2610 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2612 ->will( $this->returnValue( true ) );
2613 $mockDb->expects( $this->exactly( 1 ) )
2614 ->method( 'timestamp' )
2615 ->will( $this->returnCallback( function ( $value ) {
2616 return 'TS' . $value . 'TS';
2618 $mockDb->expects( $this->once() )
2619 ->method( 'makeWhereFrom2d' )
2621 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2622 $this->isType( 'string' ),
2623 $this->isType( 'string' )
2625 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2627 $store = $this->newWatchedItemStore(
2628 $this->getMockLBFactory( $mockDb ),
2629 $this->getMockCache(),
2630 $this->getMockReadOnlyMode()
2634 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2638 public function testUpdateNotificationTimestamp_watchersExist() {
2639 $mockDb = $this->getMockDb();
2640 $mockDb->expects( $this->once() )
2641 ->method( 'selectFieldValues' )
2647 'wl_namespace' => 0,
2648 'wl_title' => 'SomeDbKey',
2649 'wl_notificationtimestamp IS NULL'
2652 ->will( $this->returnValue( [ '2', '3' ] ) );
2653 $mockDb->expects( $this->once() )
2654 ->method( 'update' )
2657 [ 'wl_notificationtimestamp' => null ],
2659 'wl_user' => [ 2, 3 ],
2660 'wl_namespace' => 0,
2661 'wl_title' => 'SomeDbKey',
2665 $mockCache = $this->getMockCache();
2666 $mockCache->expects( $this->never() )->method( 'set' );
2667 $mockCache->expects( $this->never() )->method( 'get' );
2668 $mockCache->expects( $this->never() )->method( 'delete' );
2670 $store = $this->newWatchedItemStore(
2671 $this->getMockLBFactory( $mockDb ),
2673 $this->getMockReadOnlyMode()
2676 $this->assertEquals(
2678 $store->updateNotificationTimestamp(
2679 $this->getMockNonAnonUserWithId( 1 ),
2680 new TitleValue( 0, 'SomeDbKey' ),
2686 public function testUpdateNotificationTimestamp_noWatchers() {
2687 $mockDb = $this->getMockDb();
2688 $mockDb->expects( $this->once() )
2689 ->method( 'selectFieldValues' )
2695 'wl_namespace' => 0,
2696 'wl_title' => 'SomeDbKey',
2697 'wl_notificationtimestamp IS NULL'
2701 $this->returnValue( [] )
2703 $mockDb->expects( $this->never() )
2704 ->method( 'update' );
2706 $mockCache = $this->getMockCache();
2707 $mockCache->expects( $this->never() )->method( 'set' );
2708 $mockCache->expects( $this->never() )->method( 'get' );
2709 $mockCache->expects( $this->never() )->method( 'delete' );
2711 $store = $this->newWatchedItemStore(
2712 $this->getMockLBFactory( $mockDb ),
2714 $this->getMockReadOnlyMode()
2717 $watchers = $store->updateNotificationTimestamp(
2718 $this->getMockNonAnonUserWithId( 1 ),
2719 new TitleValue( 0, 'SomeDbKey' ),
2722 $this->assertInternalType( 'array', $watchers );
2723 $this->assertEmpty( $watchers );
2726 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2727 $user = $this->getMockNonAnonUserWithId( 1 );
2728 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2730 $mockDb = $this->getMockDb();
2731 $mockDb->expects( $this->once() )
2732 ->method( 'selectRow' )
2733 ->will( $this->returnValue(
2734 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2736 $mockDb->expects( $this->once() )
2737 ->method( 'selectFieldValues' )
2739 $this->returnValue( [ '2', '3' ] )
2741 $mockDb->expects( $this->once() )
2742 ->method( 'update' );
2744 $mockCache = $this->getMockCache();
2745 $mockCache->expects( $this->once() )
2747 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2748 $mockCache->expects( $this->once() )
2750 ->with( '0:SomeDbKey:1' );
2751 $mockCache->expects( $this->once() )
2752 ->method( 'delete' )
2753 ->with( '0:SomeDbKey:1' );
2755 $store = $this->newWatchedItemStore(
2756 $this->getMockLBFactory( $mockDb ),
2758 $this->getMockReadOnlyMode()
2761 // This will add the item to the cache
2762 $store->getWatchedItem( $user, $titleValue );
2764 $store->updateNotificationTimestamp(
2765 $this->getMockNonAnonUserWithId( 1 ),