2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\Rdbms\LBFactory
;
4 use Wikimedia\Rdbms\LoadBalancer
;
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 $mockDb->expects( $this->once() )
1110 ->method( 'affectedRows' )
1113 $mockCache = $this->getMockCache();
1114 $mockCache->expects( $this->exactly( 2 ) )
1115 ->method( 'delete' );
1116 $mockCache->expects( $this->at( 1 ) )
1117 ->method( 'delete' )
1118 ->with( '0:Some_Page:1' );
1119 $mockCache->expects( $this->at( 3 ) )
1120 ->method( 'delete' )
1121 ->with( '1:Some_Page:1' );
1123 $store = $this->newWatchedItemStore(
1124 $this->getMockLBFactory( $mockDb ),
1126 $this->getMockReadOnlyMode()
1129 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1132 $store->addWatchBatchForUser(
1134 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1139 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1140 $mockDb = $this->getMockDb();
1141 $mockDb->expects( $this->never() )
1142 ->method( 'insert' );
1144 $mockCache = $this->getMockCache();
1145 $mockCache->expects( $this->never() )
1146 ->method( 'delete' );
1148 $store = $this->newWatchedItemStore(
1149 $this->getMockLBFactory( $mockDb ),
1151 $this->getMockReadOnlyMode()
1155 $store->addWatchBatchForUser(
1156 $this->getAnonUser(),
1157 [ new TitleValue( 0, 'Other_Page' ) ]
1162 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1163 $user = $this->getMockNonAnonUserWithId( 1 );
1164 $mockDb = $this->getMockDb();
1165 $mockDb->expects( $this->never() )
1166 ->method( 'insert' );
1168 $mockCache = $this->getMockCache();
1169 $mockCache->expects( $this->never() )
1170 ->method( 'delete' );
1172 $store = $this->newWatchedItemStore(
1173 $this->getMockLBFactory( $mockDb ),
1175 $this->getMockReadOnlyMode()
1179 $store->addWatchBatchForUser( $user, [] )
1183 public function testLoadWatchedItem_existingItem() {
1184 $mockDb = $this->getMockDb();
1185 $mockDb->expects( $this->once() )
1186 ->method( 'selectRow' )
1189 'wl_notificationtimestamp',
1192 'wl_namespace' => 0,
1193 'wl_title' => 'SomeDbKey',
1196 ->will( $this->returnValue(
1197 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1200 $mockCache = $this->getMockCache();
1201 $mockCache->expects( $this->once() )
1207 $store = $this->newWatchedItemStore(
1208 $this->getMockLBFactory( $mockDb ),
1210 $this->getMockReadOnlyMode()
1213 $watchedItem = $store->loadWatchedItem(
1214 $this->getMockNonAnonUserWithId( 1 ),
1215 new TitleValue( 0, 'SomeDbKey' )
1217 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1218 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1219 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1220 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1223 public function testLoadWatchedItem_noItem() {
1224 $mockDb = $this->getMockDb();
1225 $mockDb->expects( $this->once() )
1226 ->method( 'selectRow' )
1229 'wl_notificationtimestamp',
1232 'wl_namespace' => 0,
1233 'wl_title' => 'SomeDbKey',
1236 ->will( $this->returnValue( [] ) );
1238 $mockCache = $this->getMockCache();
1239 $mockCache->expects( $this->never() )->method( 'get' );
1240 $mockCache->expects( $this->never() )->method( 'delete' );
1242 $store = $this->newWatchedItemStore(
1243 $this->getMockLBFactory( $mockDb ),
1245 $this->getMockReadOnlyMode()
1249 $store->loadWatchedItem(
1250 $this->getMockNonAnonUserWithId( 1 ),
1251 new TitleValue( 0, 'SomeDbKey' )
1256 public function testLoadWatchedItem_anonymousUser() {
1257 $mockDb = $this->getMockDb();
1258 $mockDb->expects( $this->never() )
1259 ->method( 'selectRow' );
1261 $mockCache = $this->getMockCache();
1262 $mockCache->expects( $this->never() )->method( 'get' );
1263 $mockCache->expects( $this->never() )->method( 'delete' );
1265 $store = $this->newWatchedItemStore(
1266 $this->getMockLBFactory( $mockDb ),
1268 $this->getMockReadOnlyMode()
1272 $store->loadWatchedItem(
1273 $this->getAnonUser(),
1274 new TitleValue( 0, 'SomeDbKey' )
1279 public function testRemoveWatch_existingItem() {
1280 $mockDb = $this->getMockDb();
1281 $mockDb->expects( $this->once() )
1282 ->method( 'delete' )
1288 'wl_namespace' => 0,
1289 'wl_title' => [ 'SomeDbKey' ],
1296 'wl_namespace' => 1,
1297 'wl_title' => [ 'SomeDbKey' ],
1301 $mockDb->expects( $this->exactly( 1 ) )
1302 ->method( 'affectedRows' )
1305 $mockCache = $this->getMockCache();
1306 $mockCache->expects( $this->never() )->method( 'get' );
1307 $mockCache->expects( $this->once() )
1308 ->method( 'delete' )
1310 [ '0:SomeDbKey:1' ],
1314 $store = $this->newWatchedItemStore(
1315 $this->getMockLBFactory( $mockDb ),
1317 $this->getMockReadOnlyMode()
1320 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1322 $store->removeWatch(
1323 $this->getMockNonAnonUserWithId( 1 ),
1324 Title
::newFromTitleValue( $titleValue )
1329 public function testRemoveWatch_noItem() {
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->once() )
1332 ->method( 'delete' )
1338 'wl_namespace' => 0,
1339 'wl_title' => [ 'SomeDbKey' ],
1346 'wl_namespace' => 1,
1347 'wl_title' => [ 'SomeDbKey' ],
1352 $mockDb->expects( $this->once() )
1353 ->method( 'affectedRows' )
1356 $mockCache = $this->getMockCache();
1357 $mockCache->expects( $this->never() )->method( 'get' );
1358 $mockCache->expects( $this->once() )
1359 ->method( 'delete' )
1361 [ '0:SomeDbKey:1' ],
1365 $store = $this->newWatchedItemStore(
1366 $this->getMockLBFactory( $mockDb ),
1368 $this->getMockReadOnlyMode()
1371 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1373 $store->removeWatch(
1374 $this->getMockNonAnonUserWithId( 1 ),
1375 Title
::newFromTitleValue( $titleValue )
1380 public function testRemoveWatch_anonymousUser() {
1381 $mockDb = $this->getMockDb();
1382 $mockDb->expects( $this->never() )
1383 ->method( 'delete' );
1385 $mockCache = $this->getMockCache();
1386 $mockCache->expects( $this->never() )->method( 'get' );
1387 $mockCache->expects( $this->never() )
1388 ->method( 'delete' );
1390 $store = $this->newWatchedItemStore(
1391 $this->getMockLBFactory( $mockDb ),
1393 $this->getMockReadOnlyMode()
1397 $store->removeWatch(
1398 $this->getAnonUser(),
1399 new TitleValue( 0, 'SomeDbKey' )
1404 public function testGetWatchedItem_existingItem() {
1405 $mockDb = $this->getMockDb();
1406 $mockDb->expects( $this->once() )
1407 ->method( 'selectRow' )
1410 'wl_notificationtimestamp',
1413 'wl_namespace' => 0,
1414 'wl_title' => 'SomeDbKey',
1417 ->will( $this->returnValue(
1418 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1421 $mockCache = $this->getMockCache();
1422 $mockCache->expects( $this->never() )->method( 'delete' );
1423 $mockCache->expects( $this->once() )
1428 ->will( $this->returnValue( null ) );
1429 $mockCache->expects( $this->once() )
1435 $store = $this->newWatchedItemStore(
1436 $this->getMockLBFactory( $mockDb ),
1438 $this->getMockReadOnlyMode()
1441 $watchedItem = $store->getWatchedItem(
1442 $this->getMockNonAnonUserWithId( 1 ),
1443 new TitleValue( 0, 'SomeDbKey' )
1445 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1446 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1447 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1448 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1451 public function testGetWatchedItem_cachedItem() {
1452 $mockDb = $this->getMockDb();
1453 $mockDb->expects( $this->never() )
1454 ->method( 'selectRow' );
1456 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1457 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1458 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1460 $mockCache = $this->getMockCache();
1461 $mockCache->expects( $this->never() )->method( 'delete' );
1462 $mockCache->expects( $this->never() )->method( 'set' );
1463 $mockCache->expects( $this->once() )
1468 ->will( $this->returnValue( $cachedItem ) );
1470 $store = $this->newWatchedItemStore(
1471 $this->getMockLBFactory( $mockDb ),
1473 $this->getMockReadOnlyMode()
1476 $this->assertEquals(
1478 $store->getWatchedItem(
1485 public function testGetWatchedItem_noItem() {
1486 $mockDb = $this->getMockDb();
1487 $mockDb->expects( $this->once() )
1488 ->method( 'selectRow' )
1491 'wl_notificationtimestamp',
1494 'wl_namespace' => 0,
1495 'wl_title' => 'SomeDbKey',
1498 ->will( $this->returnValue( [] ) );
1500 $mockCache = $this->getMockCache();
1501 $mockCache->expects( $this->never() )->method( 'set' );
1502 $mockCache->expects( $this->never() )->method( 'delete' );
1503 $mockCache->expects( $this->once() )
1505 ->with( '0:SomeDbKey:1' )
1506 ->will( $this->returnValue( false ) );
1508 $store = $this->newWatchedItemStore(
1509 $this->getMockLBFactory( $mockDb ),
1511 $this->getMockReadOnlyMode()
1515 $store->getWatchedItem(
1516 $this->getMockNonAnonUserWithId( 1 ),
1517 new TitleValue( 0, 'SomeDbKey' )
1522 public function testGetWatchedItem_anonymousUser() {
1523 $mockDb = $this->getMockDb();
1524 $mockDb->expects( $this->never() )
1525 ->method( 'selectRow' );
1527 $mockCache = $this->getMockCache();
1528 $mockCache->expects( $this->never() )->method( 'set' );
1529 $mockCache->expects( $this->never() )->method( 'get' );
1530 $mockCache->expects( $this->never() )->method( 'delete' );
1532 $store = $this->newWatchedItemStore(
1533 $this->getMockLBFactory( $mockDb ),
1535 $this->getMockReadOnlyMode()
1539 $store->getWatchedItem(
1540 $this->getAnonUser(),
1541 new TitleValue( 0, 'SomeDbKey' )
1546 public function testGetWatchedItemsForUser() {
1547 $mockDb = $this->getMockDb();
1548 $mockDb->expects( $this->once() )
1549 ->method( 'select' )
1552 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1555 ->will( $this->returnValue( [
1556 $this->getFakeRow( [
1557 'wl_namespace' => 0,
1558 'wl_title' => 'Foo1',
1559 'wl_notificationtimestamp' => '20151212010101',
1561 $this->getFakeRow( [
1562 'wl_namespace' => 1,
1563 'wl_title' => 'Foo2',
1564 'wl_notificationtimestamp' => null,
1568 $mockCache = $this->getMockCache();
1569 $mockCache->expects( $this->never() )->method( 'delete' );
1570 $mockCache->expects( $this->never() )->method( 'get' );
1571 $mockCache->expects( $this->never() )->method( 'set' );
1573 $store = $this->newWatchedItemStore(
1574 $this->getMockLBFactory( $mockDb ),
1576 $this->getMockReadOnlyMode()
1578 $user = $this->getMockNonAnonUserWithId( 1 );
1580 $watchedItems = $store->getWatchedItemsForUser( $user );
1582 $this->assertInternalType( 'array', $watchedItems );
1583 $this->assertCount( 2, $watchedItems );
1584 foreach ( $watchedItems as $watchedItem ) {
1585 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1587 $this->assertEquals(
1588 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1591 $this->assertEquals(
1592 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1597 public function provideDbTypes() {
1599 [ false, DB_REPLICA
],
1600 [ true, DB_MASTER
],
1605 * @dataProvider provideDbTypes
1607 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1608 $mockDb = $this->getMockDb();
1609 $mockCache = $this->getMockCache();
1610 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1611 $user = $this->getMockNonAnonUserWithId( 1 );
1613 $mockDb->expects( $this->once() )
1614 ->method( 'select' )
1617 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1619 $this->isType( 'string' ),
1620 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1622 ->will( $this->returnValue( [] ) );
1624 $store = $this->newWatchedItemStore(
1627 $this->getMockReadOnlyMode()
1630 $watchedItems = $store->getWatchedItemsForUser(
1632 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1634 $this->assertEquals( [], $watchedItems );
1637 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1638 $store = $this->newWatchedItemStore(
1639 $this->getMockLBFactory( $this->getMockDb() ),
1640 $this->getMockCache(),
1641 $this->getMockReadOnlyMode()
1644 $this->setExpectedException( InvalidArgumentException
::class );
1645 $store->getWatchedItemsForUser(
1646 $this->getMockNonAnonUserWithId( 1 ),
1651 public function testIsWatchedItem_existingItem() {
1652 $mockDb = $this->getMockDb();
1653 $mockDb->expects( $this->once() )
1654 ->method( 'selectRow' )
1657 'wl_notificationtimestamp',
1660 'wl_namespace' => 0,
1661 'wl_title' => 'SomeDbKey',
1664 ->will( $this->returnValue(
1665 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1668 $mockCache = $this->getMockCache();
1669 $mockCache->expects( $this->never() )->method( 'delete' );
1670 $mockCache->expects( $this->once() )
1672 ->with( '0:SomeDbKey:1' )
1673 ->will( $this->returnValue( false ) );
1674 $mockCache->expects( $this->once() )
1680 $store = $this->newWatchedItemStore(
1681 $this->getMockLBFactory( $mockDb ),
1683 $this->getMockReadOnlyMode()
1688 $this->getMockNonAnonUserWithId( 1 ),
1689 new TitleValue( 0, 'SomeDbKey' )
1694 public function testIsWatchedItem_noItem() {
1695 $mockDb = $this->getMockDb();
1696 $mockDb->expects( $this->once() )
1697 ->method( 'selectRow' )
1700 'wl_notificationtimestamp',
1703 'wl_namespace' => 0,
1704 'wl_title' => 'SomeDbKey',
1707 ->will( $this->returnValue( [] ) );
1709 $mockCache = $this->getMockCache();
1710 $mockCache->expects( $this->never() )->method( 'set' );
1711 $mockCache->expects( $this->never() )->method( 'delete' );
1712 $mockCache->expects( $this->once() )
1714 ->with( '0:SomeDbKey:1' )
1715 ->will( $this->returnValue( false ) );
1717 $store = $this->newWatchedItemStore(
1718 $this->getMockLBFactory( $mockDb ),
1720 $this->getMockReadOnlyMode()
1725 $this->getMockNonAnonUserWithId( 1 ),
1726 new TitleValue( 0, 'SomeDbKey' )
1731 public function testIsWatchedItem_anonymousUser() {
1732 $mockDb = $this->getMockDb();
1733 $mockDb->expects( $this->never() )
1734 ->method( 'selectRow' );
1736 $mockCache = $this->getMockCache();
1737 $mockCache->expects( $this->never() )->method( 'set' );
1738 $mockCache->expects( $this->never() )->method( 'get' );
1739 $mockCache->expects( $this->never() )->method( 'delete' );
1741 $store = $this->newWatchedItemStore(
1742 $this->getMockLBFactory( $mockDb ),
1744 $this->getMockReadOnlyMode()
1749 $this->getAnonUser(),
1750 new TitleValue( 0, 'SomeDbKey' )
1755 public function testGetNotificationTimestampsBatch() {
1757 new TitleValue( 0, 'SomeDbKey' ),
1758 new TitleValue( 1, 'AnotherDbKey' ),
1761 $mockDb = $this->getMockDb();
1763 $this->getFakeRow( [
1764 'wl_namespace' => '0',
1765 'wl_title' => 'SomeDbKey',
1766 'wl_notificationtimestamp' => '20151212010101',
1770 'wl_namespace' => '1',
1771 'wl_title' => 'AnotherDbKey',
1772 'wl_notificationtimestamp' => null,
1777 $mockDb->expects( $this->once() )
1778 ->method( 'makeWhereFrom2d' )
1780 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1781 $this->isType( 'string' ),
1782 $this->isType( 'string' )
1784 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1785 $mockDb->expects( $this->once() )
1786 ->method( 'select' )
1789 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1791 'makeWhereFrom2d return value',
1794 $this->isType( 'string' )
1796 ->will( $this->returnValue( $dbResult ) );
1798 $mockCache = $this->getMockCache();
1799 $mockCache->expects( $this->exactly( 2 ) )
1802 [ '0:SomeDbKey:1' ],
1803 [ '1:AnotherDbKey:1' ]
1805 ->will( $this->returnValue( null ) );
1806 $mockCache->expects( $this->never() )->method( 'set' );
1807 $mockCache->expects( $this->never() )->method( 'delete' );
1809 $store = $this->newWatchedItemStore(
1810 $this->getMockLBFactory( $mockDb ),
1812 $this->getMockReadOnlyMode()
1815 $this->assertEquals(
1817 0 => [ 'SomeDbKey' => '20151212010101', ],
1818 1 => [ 'AnotherDbKey' => null, ],
1820 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1824 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1826 new TitleValue( 0, 'OtherDbKey' ),
1829 $mockDb = $this->getMockDb();
1831 $mockDb->expects( $this->once() )
1832 ->method( 'makeWhereFrom2d' )
1834 [ [ 'OtherDbKey' => 1 ] ],
1835 $this->isType( 'string' ),
1836 $this->isType( 'string' )
1838 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1839 $mockDb->expects( $this->once() )
1840 ->method( 'select' )
1843 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1845 'makeWhereFrom2d return value',
1848 $this->isType( 'string' )
1850 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1852 $mockCache = $this->getMockCache();
1853 $mockCache->expects( $this->once() )
1855 ->with( '0:OtherDbKey:1' )
1856 ->will( $this->returnValue( null ) );
1857 $mockCache->expects( $this->never() )->method( 'set' );
1858 $mockCache->expects( $this->never() )->method( 'delete' );
1860 $store = $this->newWatchedItemStore(
1861 $this->getMockLBFactory( $mockDb ),
1863 $this->getMockReadOnlyMode()
1866 $this->assertEquals(
1868 0 => [ 'OtherDbKey' => false, ],
1870 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1874 public function testGetNotificationTimestampsBatch_cachedItem() {
1876 new TitleValue( 0, 'SomeDbKey' ),
1877 new TitleValue( 1, 'AnotherDbKey' ),
1880 $user = $this->getMockNonAnonUserWithId( 1 );
1881 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1883 $mockDb = $this->getMockDb();
1885 $mockDb->expects( $this->once() )
1886 ->method( 'makeWhereFrom2d' )
1888 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1889 $this->isType( 'string' ),
1890 $this->isType( 'string' )
1892 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1893 $mockDb->expects( $this->once() )
1894 ->method( 'select' )
1897 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1899 'makeWhereFrom2d return value',
1902 $this->isType( 'string' )
1904 ->will( $this->returnValue( [
1906 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1910 $mockCache = $this->getMockCache();
1911 $mockCache->expects( $this->at( 1 ) )
1913 ->with( '0:SomeDbKey:1' )
1914 ->will( $this->returnValue( $cachedItem ) );
1915 $mockCache->expects( $this->at( 3 ) )
1917 ->with( '1:AnotherDbKey:1' )
1918 ->will( $this->returnValue( null ) );
1919 $mockCache->expects( $this->never() )->method( 'set' );
1920 $mockCache->expects( $this->never() )->method( 'delete' );
1922 $store = $this->newWatchedItemStore(
1923 $this->getMockLBFactory( $mockDb ),
1925 $this->getMockReadOnlyMode()
1928 $this->assertEquals(
1930 0 => [ 'SomeDbKey' => '20151212010101', ],
1931 1 => [ 'AnotherDbKey' => null, ],
1933 $store->getNotificationTimestampsBatch( $user, $targets )
1937 public function testGetNotificationTimestampsBatch_allItemsCached() {
1939 new TitleValue( 0, 'SomeDbKey' ),
1940 new TitleValue( 1, 'AnotherDbKey' ),
1943 $user = $this->getMockNonAnonUserWithId( 1 );
1945 new WatchedItem( $user, $targets[0], '20151212010101' ),
1946 new WatchedItem( $user, $targets[1], null ),
1948 $mockDb = $this->getMockDb();
1949 $mockDb->expects( $this->never() )->method( $this->anything() );
1951 $mockCache = $this->getMockCache();
1952 $mockCache->expects( $this->at( 1 ) )
1954 ->with( '0:SomeDbKey:1' )
1955 ->will( $this->returnValue( $cachedItems[0] ) );
1956 $mockCache->expects( $this->at( 3 ) )
1958 ->with( '1:AnotherDbKey:1' )
1959 ->will( $this->returnValue( $cachedItems[1] ) );
1960 $mockCache->expects( $this->never() )->method( 'set' );
1961 $mockCache->expects( $this->never() )->method( 'delete' );
1963 $store = $this->newWatchedItemStore(
1964 $this->getMockLBFactory( $mockDb ),
1966 $this->getMockReadOnlyMode()
1969 $this->assertEquals(
1971 0 => [ 'SomeDbKey' => '20151212010101', ],
1972 1 => [ 'AnotherDbKey' => null, ],
1974 $store->getNotificationTimestampsBatch( $user, $targets )
1978 public function testGetNotificationTimestampsBatch_anonymousUser() {
1980 new TitleValue( 0, 'SomeDbKey' ),
1981 new TitleValue( 1, 'AnotherDbKey' ),
1984 $mockDb = $this->getMockDb();
1985 $mockDb->expects( $this->never() )->method( $this->anything() );
1987 $mockCache = $this->getMockCache();
1988 $mockCache->expects( $this->never() )->method( $this->anything() );
1990 $store = $this->newWatchedItemStore(
1991 $this->getMockLBFactory( $mockDb ),
1993 $this->getMockReadOnlyMode()
1996 $this->assertEquals(
1998 0 => [ 'SomeDbKey' => false, ],
1999 1 => [ 'AnotherDbKey' => false, ],
2001 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
2005 public function testResetNotificationTimestamp_anonymousUser() {
2006 $mockDb = $this->getMockDb();
2007 $mockDb->expects( $this->never() )
2008 ->method( 'selectRow' );
2010 $mockCache = $this->getMockCache();
2011 $mockCache->expects( $this->never() )->method( 'get' );
2012 $mockCache->expects( $this->never() )->method( 'set' );
2013 $mockCache->expects( $this->never() )->method( 'delete' );
2015 $store = $this->newWatchedItemStore(
2016 $this->getMockLBFactory( $mockDb ),
2018 $this->getMockReadOnlyMode()
2022 $store->resetNotificationTimestamp(
2023 $this->getAnonUser(),
2024 Title
::newFromText( 'SomeDbKey' )
2029 public function testResetNotificationTimestamp_noItem() {
2030 $mockDb = $this->getMockDb();
2031 $mockDb->expects( $this->once() )
2032 ->method( 'selectRow' )
2035 'wl_notificationtimestamp',
2038 'wl_namespace' => 0,
2039 'wl_title' => 'SomeDbKey',
2042 ->will( $this->returnValue( [] ) );
2044 $mockCache = $this->getMockCache();
2045 $mockCache->expects( $this->never() )->method( 'get' );
2046 $mockCache->expects( $this->never() )->method( 'set' );
2047 $mockCache->expects( $this->never() )->method( 'delete' );
2049 $store = $this->newWatchedItemStore(
2050 $this->getMockLBFactory( $mockDb ),
2052 $this->getMockReadOnlyMode()
2056 $store->resetNotificationTimestamp(
2057 $this->getMockNonAnonUserWithId( 1 ),
2058 Title
::newFromText( 'SomeDbKey' )
2063 public function testResetNotificationTimestamp_item() {
2064 $user = $this->getMockNonAnonUserWithId( 1 );
2065 $title = Title
::newFromText( 'SomeDbKey' );
2067 $mockDb = $this->getMockDb();
2068 $mockDb->expects( $this->once() )
2069 ->method( 'selectRow' )
2072 'wl_notificationtimestamp',
2075 'wl_namespace' => 0,
2076 'wl_title' => 'SomeDbKey',
2079 ->will( $this->returnValue(
2080 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2083 $mockCache = $this->getMockCache();
2084 $mockCache->expects( $this->never() )->method( 'get' );
2085 $mockCache->expects( $this->once() )
2089 $this->isInstanceOf( WatchedItem
::class )
2091 $mockCache->expects( $this->once() )
2092 ->method( 'delete' )
2093 ->with( '0:SomeDbKey:1' );
2095 $store = $this->newWatchedItemStore(
2096 $this->getMockLBFactory( $mockDb ),
2098 $this->getMockReadOnlyMode()
2101 // Note: This does not actually assert the job is correct
2102 $callableCallCounter = 0;
2103 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2104 $callableCallCounter++
;
2105 $this->assertInternalType( 'callable', $callable );
2107 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2110 $store->resetNotificationTimestamp(
2115 $this->assertEquals( 1, $callableCallCounter );
2117 ScopedCallback
::consume( $scopedOverride );
2120 public function testResetNotificationTimestamp_noItemForced() {
2121 $user = $this->getMockNonAnonUserWithId( 1 );
2122 $title = Title
::newFromText( 'SomeDbKey' );
2124 $mockDb = $this->getMockDb();
2125 $mockDb->expects( $this->never() )
2126 ->method( 'selectRow' );
2128 $mockCache = $this->getMockCache();
2129 $mockCache->expects( $this->never() )->method( 'get' );
2130 $mockCache->expects( $this->never() )->method( 'set' );
2131 $mockCache->expects( $this->once() )
2132 ->method( 'delete' )
2133 ->with( '0:SomeDbKey:1' );
2135 $store = $this->newWatchedItemStore(
2136 $this->getMockLBFactory( $mockDb ),
2138 $this->getMockReadOnlyMode()
2141 // Note: This does not actually assert the job is correct
2142 $callableCallCounter = 0;
2143 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2144 $callableCallCounter++
;
2145 $this->assertInternalType( 'callable', $callable );
2147 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2150 $store->resetNotificationTimestamp(
2156 $this->assertEquals( 1, $callableCallCounter );
2158 ScopedCallback
::consume( $scopedOverride );
2162 * @param string $text
2165 * @return PHPUnit_Framework_MockObject_MockObject|Title
2167 private function getMockTitle( $text, $ns = 0 ) {
2168 $title = $this->createMock( Title
::class );
2169 $title->expects( $this->any() )
2170 ->method( 'getText' )
2171 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2172 $title->expects( $this->any() )
2173 ->method( 'getDbKey' )
2174 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2175 $title->expects( $this->any() )
2176 ->method( 'getNamespace' )
2177 ->will( $this->returnValue( $ns ) );
2181 private function verifyCallbackJob(
2183 LinkTarget
$expectedTitle,
2185 callable
$notificationTimestampCondition
2187 $this->assertInternalType( 'callable', $callback );
2189 $callbackReflector = new ReflectionFunction( $callback );
2190 $vars = $callbackReflector->getStaticVariables();
2191 $this->assertArrayHasKey( 'job', $vars );
2192 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2194 /** @var ActivityUpdateJob $job */
2195 $job = $vars['job'];
2196 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2197 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2199 $jobParams = $job->getParams();
2200 $this->assertArrayHasKey( 'type', $jobParams );
2201 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2202 $this->assertArrayHasKey( 'userid', $jobParams );
2203 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2204 $this->assertArrayHasKey( 'notifTime', $jobParams );
2205 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2208 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2209 $user = $this->getMockNonAnonUserWithId( 1 );
2211 $title = $this->getMockTitle( 'SomeTitle' );
2212 $title->expects( $this->once() )
2213 ->method( 'getNextRevisionID' )
2215 ->will( $this->returnValue( false ) );
2217 $mockDb = $this->getMockDb();
2218 $mockDb->expects( $this->never() )
2219 ->method( 'selectRow' );
2221 $mockCache = $this->getMockCache();
2222 $mockCache->expects( $this->never() )->method( 'get' );
2223 $mockCache->expects( $this->never() )->method( 'set' );
2224 $mockCache->expects( $this->once() )
2225 ->method( 'delete' )
2226 ->with( '0:SomeTitle:1' );
2228 $store = $this->newWatchedItemStore(
2229 $this->getMockLBFactory( $mockDb ),
2231 $this->getMockReadOnlyMode()
2234 $callableCallCounter = 0;
2235 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2236 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2237 $callableCallCounter++
;
2238 $this->verifyCallbackJob(
2242 function ( $time ) {
2243 return $time === null;
2250 $store->resetNotificationTimestamp(
2257 $this->assertEquals( 1, $callableCallCounter );
2259 ScopedCallback
::consume( $scopedOverride );
2262 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2263 $user = $this->getMockNonAnonUserWithId( 1 );
2265 $title = $this->getMockTitle( 'SomeDbKey' );
2266 $title->expects( $this->once() )
2267 ->method( 'getNextRevisionID' )
2269 ->will( $this->returnValue( 33 ) );
2271 $mockDb = $this->getMockDb();
2272 $mockDb->expects( $this->once() )
2273 ->method( 'selectRow' )
2276 'wl_notificationtimestamp',
2279 'wl_namespace' => 0,
2280 'wl_title' => 'SomeDbKey',
2283 ->will( $this->returnValue(
2284 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2287 $mockCache = $this->getMockCache();
2288 $mockCache->expects( $this->never() )->method( 'get' );
2289 $mockCache->expects( $this->once() )
2291 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2292 $mockCache->expects( $this->once() )
2293 ->method( 'delete' )
2294 ->with( '0:SomeDbKey:1' );
2296 $store = $this->newWatchedItemStore(
2297 $this->getMockLBFactory( $mockDb ),
2299 $this->getMockReadOnlyMode()
2302 $addUpdateCallCounter = 0;
2303 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2304 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2305 $addUpdateCallCounter++
;
2306 $this->verifyCallbackJob(
2310 function ( $time ) {
2311 return $time !== null && $time > '20151212010101';
2317 $getTimestampCallCounter = 0;
2318 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2319 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2320 $getTimestampCallCounter++
;
2321 $this->assertEquals( $title, $titleParam );
2322 $this->assertEquals( $oldid, $oldidParam );
2327 $store->resetNotificationTimestamp(
2334 $this->assertEquals( 1, $addUpdateCallCounter );
2335 $this->assertEquals( 1, $getTimestampCallCounter );
2337 ScopedCallback
::consume( $scopedOverrideDeferred );
2338 ScopedCallback
::consume( $scopedOverrideRevision );
2341 public function testResetNotificationTimestamp_notWatchedPageForced() {
2342 $user = $this->getMockNonAnonUserWithId( 1 );
2344 $title = $this->getMockTitle( 'SomeDbKey' );
2345 $title->expects( $this->once() )
2346 ->method( 'getNextRevisionID' )
2348 ->will( $this->returnValue( 33 ) );
2350 $mockDb = $this->getMockDb();
2351 $mockDb->expects( $this->once() )
2352 ->method( 'selectRow' )
2355 'wl_notificationtimestamp',
2358 'wl_namespace' => 0,
2359 'wl_title' => 'SomeDbKey',
2362 ->will( $this->returnValue( false ) );
2364 $mockCache = $this->getMockCache();
2365 $mockCache->expects( $this->never() )->method( 'get' );
2366 $mockCache->expects( $this->never() )->method( 'set' );
2367 $mockCache->expects( $this->once() )
2368 ->method( 'delete' )
2369 ->with( '0:SomeDbKey:1' );
2371 $store = $this->newWatchedItemStore(
2372 $this->getMockLBFactory( $mockDb ),
2374 $this->getMockReadOnlyMode()
2377 $callableCallCounter = 0;
2378 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2379 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2380 $callableCallCounter++
;
2381 $this->verifyCallbackJob(
2385 function ( $time ) {
2386 return $time === null;
2393 $store->resetNotificationTimestamp(
2400 $this->assertEquals( 1, $callableCallCounter );
2402 ScopedCallback
::consume( $scopedOverride );
2405 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2406 $user = $this->getMockNonAnonUserWithId( 1 );
2408 $title = $this->getMockTitle( 'SomeDbKey' );
2409 $title->expects( $this->once() )
2410 ->method( 'getNextRevisionID' )
2412 ->will( $this->returnValue( 33 ) );
2414 $mockDb = $this->getMockDb();
2415 $mockDb->expects( $this->once() )
2416 ->method( 'selectRow' )
2419 'wl_notificationtimestamp',
2422 'wl_namespace' => 0,
2423 'wl_title' => 'SomeDbKey',
2426 ->will( $this->returnValue(
2427 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2430 $mockCache = $this->getMockCache();
2431 $mockCache->expects( $this->never() )->method( 'get' );
2432 $mockCache->expects( $this->once() )
2434 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2435 $mockCache->expects( $this->once() )
2436 ->method( 'delete' )
2437 ->with( '0:SomeDbKey:1' );
2439 $store = $this->newWatchedItemStore(
2440 $this->getMockLBFactory( $mockDb ),
2442 $this->getMockReadOnlyMode()
2445 $addUpdateCallCounter = 0;
2446 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2447 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2448 $addUpdateCallCounter++
;
2449 $this->verifyCallbackJob(
2453 function ( $time ) {
2454 return $time === '30151212010101';
2460 $getTimestampCallCounter = 0;
2461 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2462 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2463 $getTimestampCallCounter++
;
2464 $this->assertEquals( $title, $titleParam );
2465 $this->assertEquals( $oldid, $oldidParam );
2470 $store->resetNotificationTimestamp(
2477 $this->assertEquals( 1, $addUpdateCallCounter );
2478 $this->assertEquals( 1, $getTimestampCallCounter );
2480 ScopedCallback
::consume( $scopedOverrideDeferred );
2481 ScopedCallback
::consume( $scopedOverrideRevision );
2484 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2485 $user = $this->getMockNonAnonUserWithId( 1 );
2487 $title = $this->getMockTitle( 'SomeDbKey' );
2488 $title->expects( $this->once() )
2489 ->method( 'getNextRevisionID' )
2491 ->will( $this->returnValue( 33 ) );
2493 $mockDb = $this->getMockDb();
2494 $mockDb->expects( $this->once() )
2495 ->method( 'selectRow' )
2498 'wl_notificationtimestamp',
2501 'wl_namespace' => 0,
2502 'wl_title' => 'SomeDbKey',
2505 ->will( $this->returnValue(
2506 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2509 $mockCache = $this->getMockCache();
2510 $mockCache->expects( $this->never() )->method( 'get' );
2511 $mockCache->expects( $this->once() )
2513 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2514 $mockCache->expects( $this->once() )
2515 ->method( 'delete' )
2516 ->with( '0:SomeDbKey:1' );
2518 $store = $this->newWatchedItemStore(
2519 $this->getMockLBFactory( $mockDb ),
2521 $this->getMockReadOnlyMode()
2524 $addUpdateCallCounter = 0;
2525 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2526 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2527 $addUpdateCallCounter++
;
2528 $this->verifyCallbackJob(
2532 function ( $time ) {
2533 return $time === false;
2539 $getTimestampCallCounter = 0;
2540 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2541 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2542 $getTimestampCallCounter++
;
2543 $this->assertEquals( $title, $titleParam );
2544 $this->assertEquals( $oldid, $oldidParam );
2549 $store->resetNotificationTimestamp(
2556 $this->assertEquals( 1, $addUpdateCallCounter );
2557 $this->assertEquals( 1, $getTimestampCallCounter );
2559 ScopedCallback
::consume( $scopedOverrideDeferred );
2560 ScopedCallback
::consume( $scopedOverrideRevision );
2563 public function testSetNotificationTimestampsForUser_anonUser() {
2564 $store = $this->newWatchedItemStore(
2565 $this->getMockLBFactory( $this->getMockDb() ),
2566 $this->getMockCache(),
2567 $this->getMockReadOnlyMode()
2569 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2572 public function testSetNotificationTimestampsForUser_allRows() {
2573 $user = $this->getMockNonAnonUserWithId( 1 );
2574 $timestamp = '20100101010101';
2576 $mockDb = $this->getMockDb();
2577 $mockDb->expects( $this->once() )
2578 ->method( 'update' )
2581 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2584 ->will( $this->returnValue( true ) );
2585 $mockDb->expects( $this->exactly( 1 ) )
2586 ->method( 'timestamp' )
2587 ->will( $this->returnCallback( function ( $value ) {
2588 return 'TS' . $value . 'TS';
2591 $store = $this->newWatchedItemStore(
2592 $this->getMockLBFactory( $mockDb ),
2593 $this->getMockCache(),
2594 $this->getMockReadOnlyMode()
2598 $store->setNotificationTimestampsForUser( $user, $timestamp )
2602 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2603 $user = $this->getMockNonAnonUserWithId( 1 );
2606 $mockDb = $this->getMockDb();
2607 $mockDb->expects( $this->once() )
2608 ->method( 'update' )
2611 [ 'wl_notificationtimestamp' => null ],
2614 ->will( $this->returnValue( true ) );
2615 $mockDb->expects( $this->exactly( 0 ) )
2616 ->method( 'timestamp' )
2617 ->will( $this->returnCallback( function ( $value ) {
2618 return 'TS' . $value . 'TS';
2621 $store = $this->newWatchedItemStore(
2622 $this->getMockLBFactory( $mockDb ),
2623 $this->getMockCache(),
2624 $this->getMockReadOnlyMode()
2628 $store->setNotificationTimestampsForUser( $user, $timestamp )
2632 public function testSetNotificationTimestampsForUser_specificTargets() {
2633 $user = $this->getMockNonAnonUserWithId( 1 );
2634 $timestamp = '20100101010101';
2635 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2637 $mockDb = $this->getMockDb();
2638 $mockDb->expects( $this->once() )
2639 ->method( 'update' )
2642 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2643 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2645 ->will( $this->returnValue( true ) );
2646 $mockDb->expects( $this->exactly( 1 ) )
2647 ->method( 'timestamp' )
2648 ->will( $this->returnCallback( function ( $value ) {
2649 return 'TS' . $value . 'TS';
2651 $mockDb->expects( $this->once() )
2652 ->method( 'makeWhereFrom2d' )
2654 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2655 $this->isType( 'string' ),
2656 $this->isType( 'string' )
2658 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2660 $store = $this->newWatchedItemStore(
2661 $this->getMockLBFactory( $mockDb ),
2662 $this->getMockCache(),
2663 $this->getMockReadOnlyMode()
2667 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2671 public function testUpdateNotificationTimestamp_watchersExist() {
2672 $mockDb = $this->getMockDb();
2673 $mockDb->expects( $this->once() )
2674 ->method( 'selectFieldValues' )
2680 'wl_namespace' => 0,
2681 'wl_title' => 'SomeDbKey',
2682 'wl_notificationtimestamp IS NULL'
2685 ->will( $this->returnValue( [ '2', '3' ] ) );
2686 $mockDb->expects( $this->once() )
2687 ->method( 'update' )
2690 [ 'wl_notificationtimestamp' => null ],
2692 'wl_user' => [ 2, 3 ],
2693 'wl_namespace' => 0,
2694 'wl_title' => 'SomeDbKey',
2698 $mockCache = $this->getMockCache();
2699 $mockCache->expects( $this->never() )->method( 'set' );
2700 $mockCache->expects( $this->never() )->method( 'get' );
2701 $mockCache->expects( $this->never() )->method( 'delete' );
2703 $store = $this->newWatchedItemStore(
2704 $this->getMockLBFactory( $mockDb ),
2706 $this->getMockReadOnlyMode()
2709 $this->assertEquals(
2711 $store->updateNotificationTimestamp(
2712 $this->getMockNonAnonUserWithId( 1 ),
2713 new TitleValue( 0, 'SomeDbKey' ),
2719 public function testUpdateNotificationTimestamp_noWatchers() {
2720 $mockDb = $this->getMockDb();
2721 $mockDb->expects( $this->once() )
2722 ->method( 'selectFieldValues' )
2728 'wl_namespace' => 0,
2729 'wl_title' => 'SomeDbKey',
2730 'wl_notificationtimestamp IS NULL'
2734 $this->returnValue( [] )
2736 $mockDb->expects( $this->never() )
2737 ->method( 'update' );
2739 $mockCache = $this->getMockCache();
2740 $mockCache->expects( $this->never() )->method( 'set' );
2741 $mockCache->expects( $this->never() )->method( 'get' );
2742 $mockCache->expects( $this->never() )->method( 'delete' );
2744 $store = $this->newWatchedItemStore(
2745 $this->getMockLBFactory( $mockDb ),
2747 $this->getMockReadOnlyMode()
2750 $watchers = $store->updateNotificationTimestamp(
2751 $this->getMockNonAnonUserWithId( 1 ),
2752 new TitleValue( 0, 'SomeDbKey' ),
2755 $this->assertInternalType( 'array', $watchers );
2756 $this->assertEmpty( $watchers );
2759 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2760 $user = $this->getMockNonAnonUserWithId( 1 );
2761 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2763 $mockDb = $this->getMockDb();
2764 $mockDb->expects( $this->once() )
2765 ->method( 'selectRow' )
2766 ->will( $this->returnValue(
2767 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2769 $mockDb->expects( $this->once() )
2770 ->method( 'selectFieldValues' )
2772 $this->returnValue( [ '2', '3' ] )
2774 $mockDb->expects( $this->once() )
2775 ->method( 'update' );
2777 $mockCache = $this->getMockCache();
2778 $mockCache->expects( $this->once() )
2780 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2781 $mockCache->expects( $this->once() )
2783 ->with( '0:SomeDbKey:1' );
2784 $mockCache->expects( $this->once() )
2785 ->method( 'delete' )
2786 ->with( '0:SomeDbKey:1' );
2788 $store = $this->newWatchedItemStore(
2789 $this->getMockLBFactory( $mockDb ),
2791 $this->getMockReadOnlyMode()
2794 // This will add the item to the cache
2795 $store->getWatchedItem( $user, $titleValue );
2797 $store->updateNotificationTimestamp(
2798 $this->getMockNonAnonUserWithId( 1 ),