2 use MediaWiki\Linker\LinkTarget
;
3 use Wikimedia\ScopedCallback
;
8 * @covers WatchedItemStore
10 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
13 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
15 private function getMockDb() {
16 return $this->createMock( IDatabase
::class );
20 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
22 private function getMockLoadBalancer(
24 $expectedConnectionType = null
26 $mock = $this->getMockBuilder( LoadBalancer
::class )
27 ->disableOriginalConstructor()
29 if ( $expectedConnectionType !== null ) {
30 $mock->expects( $this->any() )
31 ->method( 'getConnectionRef' )
32 ->with( $expectedConnectionType )
33 ->will( $this->returnValue( $mockDb ) );
35 $mock->expects( $this->any() )
36 ->method( 'getConnectionRef' )
37 ->will( $this->returnValue( $mockDb ) );
43 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
45 private function getMockCache() {
46 $mock = $this->getMockBuilder( HashBagOStuff
::class )
47 ->disableOriginalConstructor()
49 $mock->expects( $this->any() )
51 ->will( $this->returnCallback( function () {
52 return implode( ':', func_get_args() );
58 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
60 private function getMockReadOnlyMode( $readOnly = false ) {
61 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
62 ->disableOriginalConstructor()
64 $mock->expects( $this->any() )
65 ->method( 'isReadOnly' )
66 ->will( $this->returnValue( $readOnly ) );
72 * @return PHPUnit_Framework_MockObject_MockObject|User
74 private function getMockNonAnonUserWithId( $id ) {
75 $mock = $this->createMock( User
::class );
76 $mock->expects( $this->any() )
78 ->will( $this->returnValue( false ) );
79 $mock->expects( $this->any() )
81 ->will( $this->returnValue( $id ) );
88 private function getAnonUser() {
89 return User
::newFromName( 'Anon_User' );
92 private function getFakeRow( array $rowValues ) {
93 $fakeRow = new stdClass();
94 foreach ( $rowValues as $valueName => $value ) {
95 $fakeRow->$valueName = $value;
100 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache,
101 ReadOnlyMode
$readOnlyMode
103 return new WatchedItemStore(
110 public function testCountWatchedItems() {
111 $user = $this->getMockNonAnonUserWithId( 1 );
113 $mockDb = $this->getMockDb();
114 $mockDb->expects( $this->exactly( 1 ) )
115 ->method( 'selectField' )
120 'wl_user' => $user->getId(),
122 $this->isType( 'string' )
124 ->will( $this->returnValue( 12 ) );
126 $mockCache = $this->getMockCache();
127 $mockCache->expects( $this->never() )->method( 'get' );
128 $mockCache->expects( $this->never() )->method( 'set' );
129 $mockCache->expects( $this->never() )->method( 'delete' );
131 $store = $this->newWatchedItemStore(
132 $this->getMockLoadBalancer( $mockDb ),
134 $this->getMockReadOnlyMode()
137 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
140 public function testCountWatchers() {
141 $titleValue = new TitleValue( 0, 'SomeDbKey' );
143 $mockDb = $this->getMockDb();
144 $mockDb->expects( $this->exactly( 1 ) )
145 ->method( 'selectField' )
150 'wl_namespace' => $titleValue->getNamespace(),
151 'wl_title' => $titleValue->getDBkey(),
153 $this->isType( 'string' )
155 ->will( $this->returnValue( 7 ) );
157 $mockCache = $this->getMockCache();
158 $mockCache->expects( $this->never() )->method( 'get' );
159 $mockCache->expects( $this->never() )->method( 'set' );
160 $mockCache->expects( $this->never() )->method( 'delete' );
162 $store = $this->newWatchedItemStore(
163 $this->getMockLoadBalancer( $mockDb ),
165 $this->getMockReadOnlyMode()
168 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
171 public function testCountWatchersMultiple() {
173 new TitleValue( 0, 'SomeDbKey' ),
174 new TitleValue( 0, 'OtherDbKey' ),
175 new TitleValue( 1, 'AnotherDbKey' ),
178 $mockDb = $this->getMockDb();
181 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
182 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
183 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
186 $mockDb->expects( $this->once() )
187 ->method( 'makeWhereFrom2d' )
189 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
190 $this->isType( 'string' ),
191 $this->isType( 'string' )
193 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
194 $mockDb->expects( $this->once() )
198 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
199 [ 'makeWhereFrom2d return value' ],
200 $this->isType( 'string' ),
202 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
206 $this->returnValue( $dbResult )
209 $mockCache = $this->getMockCache();
210 $mockCache->expects( $this->never() )->method( 'get' );
211 $mockCache->expects( $this->never() )->method( 'set' );
212 $mockCache->expects( $this->never() )->method( 'delete' );
214 $store = $this->newWatchedItemStore(
215 $this->getMockLoadBalancer( $mockDb ),
217 $this->getMockReadOnlyMode()
221 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
222 1 => [ 'AnotherDbKey' => 500 ],
224 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
227 public function provideIntWithDbUnsafeVersion() {
230 [ "50; DROP TABLE watchlist;\n--" ],
235 * @dataProvider provideIntWithDbUnsafeVersion
237 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
239 new TitleValue( 0, 'SomeDbKey' ),
240 new TitleValue( 0, 'OtherDbKey' ),
241 new TitleValue( 1, 'AnotherDbKey' ),
244 $mockDb = $this->getMockDb();
247 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
248 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
249 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
252 $mockDb->expects( $this->once() )
253 ->method( 'makeWhereFrom2d' )
255 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
256 $this->isType( 'string' ),
257 $this->isType( 'string' )
259 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
260 $mockDb->expects( $this->once() )
264 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
265 [ 'makeWhereFrom2d return value' ],
266 $this->isType( 'string' ),
268 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
269 'HAVING' => 'COUNT(*) >= 50',
273 $this->returnValue( $dbResult )
276 $mockCache = $this->getMockCache();
277 $mockCache->expects( $this->never() )->method( 'get' );
278 $mockCache->expects( $this->never() )->method( 'set' );
279 $mockCache->expects( $this->never() )->method( 'delete' );
281 $store = $this->newWatchedItemStore(
282 $this->getMockLoadBalancer( $mockDb ),
284 $this->getMockReadOnlyMode()
288 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
289 1 => [ 'AnotherDbKey' => 500 ],
293 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
297 public function testCountVisitingWatchers() {
298 $titleValue = new TitleValue( 0, 'SomeDbKey' );
300 $mockDb = $this->getMockDb();
301 $mockDb->expects( $this->exactly( 1 ) )
302 ->method( 'selectField' )
307 'wl_namespace' => $titleValue->getNamespace(),
308 'wl_title' => $titleValue->getDBkey(),
309 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
311 $this->isType( 'string' )
313 ->will( $this->returnValue( 7 ) );
314 $mockDb->expects( $this->exactly( 1 ) )
315 ->method( 'addQuotes' )
316 ->will( $this->returnCallback( function ( $value ) {
319 $mockDb->expects( $this->exactly( 1 ) )
320 ->method( 'timestamp' )
321 ->will( $this->returnCallback( function ( $value ) {
322 return 'TS' . $value . 'TS';
325 $mockCache = $this->getMockCache();
326 $mockCache->expects( $this->never() )->method( 'set' );
327 $mockCache->expects( $this->never() )->method( 'get' );
328 $mockCache->expects( $this->never() )->method( 'delete' );
330 $store = $this->newWatchedItemStore(
331 $this->getMockLoadBalancer( $mockDb ),
333 $this->getMockReadOnlyMode()
336 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
339 public function testCountVisitingWatchersMultiple() {
340 $titleValuesWithThresholds = [
341 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
342 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
343 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
347 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
348 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
349 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
351 $mockDb = $this->getMockDb();
352 $mockDb->expects( $this->exactly( 2 * 3 ) )
353 ->method( 'addQuotes' )
354 ->will( $this->returnCallback( function ( $value ) {
357 $mockDb->expects( $this->exactly( 3 ) )
358 ->method( 'timestamp' )
359 ->will( $this->returnCallback( function ( $value ) {
360 return 'TS' . $value . 'TS';
362 $mockDb->expects( $this->any() )
363 ->method( 'makeList' )
365 $this->isType( 'array' ),
366 $this->isType( 'int' )
368 ->will( $this->returnCallback( function ( $a, $conj ) {
369 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
370 return join( $sqlConj, array_map( function ( $s ) {
371 return '(' . $s . ')';
375 $mockDb->expects( $this->never() )
376 ->method( 'makeWhereFrom2d' );
379 '((wl_namespace = 0) AND (' .
380 "(((wl_title = 'SomeDbKey') AND (" .
381 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
383 "(wl_title = 'OtherDbKey') AND (" .
384 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
386 ') OR ((wl_namespace = 1) AND (' .
387 "(((wl_title = 'AnotherDbKey') AND (".
388 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
390 $mockDb->expects( $this->once() )
394 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
396 $this->isType( 'string' ),
398 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
402 $this->returnValue( $dbResult )
405 $mockCache = $this->getMockCache();
406 $mockCache->expects( $this->never() )->method( 'get' );
407 $mockCache->expects( $this->never() )->method( 'set' );
408 $mockCache->expects( $this->never() )->method( 'delete' );
410 $store = $this->newWatchedItemStore(
411 $this->getMockLoadBalancer( $mockDb ),
413 $this->getMockReadOnlyMode()
417 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
418 1 => [ 'AnotherDbKey' => 500 ],
422 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
426 public function testCountVisitingWatchersMultiple_withMissingTargets() {
427 $titleValuesWithThresholds = [
428 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
429 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
430 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
431 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
432 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
436 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
437 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
438 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
440 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
443 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
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 join( $sqlConj, array_map( function ( $s ) {
466 return '(' . $s . ')';
470 $mockDb->expects( $this->once() )
471 ->method( 'makeWhereFrom2d' )
473 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
474 $this->isType( 'string' ),
475 $this->isType( 'string' )
477 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
480 '((wl_namespace = 0) AND (' .
481 "(((wl_title = 'SomeDbKey') AND (" .
482 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
484 "(wl_title = 'OtherDbKey') AND (" .
485 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
487 ') OR ((wl_namespace = 1) AND (' .
488 "(((wl_title = 'AnotherDbKey') AND (".
489 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
492 '(makeWhereFrom2d return value)';
493 $mockDb->expects( $this->once() )
497 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
499 $this->isType( 'string' ),
501 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
505 $this->returnValue( $dbResult )
508 $mockCache = $this->getMockCache();
509 $mockCache->expects( $this->never() )->method( 'get' );
510 $mockCache->expects( $this->never() )->method( 'set' );
511 $mockCache->expects( $this->never() )->method( 'delete' );
513 $store = $this->newWatchedItemStore(
514 $this->getMockLoadBalancer( $mockDb ),
516 $this->getMockReadOnlyMode()
521 'SomeDbKey' => 100, 'OtherDbKey' => 300,
522 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
524 1 => [ 'AnotherDbKey' => 500 ],
528 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
533 * @dataProvider provideIntWithDbUnsafeVersion
535 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
536 $titleValuesWithThresholds = [
537 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
538 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
539 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
542 $mockDb = $this->getMockDb();
543 $mockDb->expects( $this->any() )
544 ->method( 'makeList' )
545 ->will( $this->returnValue( 'makeList return value' ) );
546 $mockDb->expects( $this->once() )
550 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
551 'makeList return value',
552 $this->isType( 'string' ),
554 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
555 'HAVING' => 'COUNT(*) >= 50',
559 $this->returnValue( [] )
562 $mockCache = $this->getMockCache();
563 $mockCache->expects( $this->never() )->method( 'get' );
564 $mockCache->expects( $this->never() )->method( 'set' );
565 $mockCache->expects( $this->never() )->method( 'delete' );
567 $store = $this->newWatchedItemStore(
568 $this->getMockLoadBalancer( $mockDb ),
570 $this->getMockReadOnlyMode()
574 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
575 1 => [ 'AnotherDbKey' => 0 ],
579 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
583 public function testCountUnreadNotifications() {
584 $user = $this->getMockNonAnonUserWithId( 1 );
586 $mockDb = $this->getMockDb();
587 $mockDb->expects( $this->exactly( 1 ) )
588 ->method( 'selectRowCount' )
593 "wl_notificationtimestamp IS NOT NULL",
596 $this->isType( 'string' )
598 ->will( $this->returnValue( 9 ) );
600 $mockCache = $this->getMockCache();
601 $mockCache->expects( $this->never() )->method( 'set' );
602 $mockCache->expects( $this->never() )->method( 'get' );
603 $mockCache->expects( $this->never() )->method( 'delete' );
605 $store = $this->newWatchedItemStore(
606 $this->getMockLoadBalancer( $mockDb ),
608 $this->getMockReadOnlyMode()
611 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
615 * @dataProvider provideIntWithDbUnsafeVersion
617 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
618 $user = $this->getMockNonAnonUserWithId( 1 );
620 $mockDb = $this->getMockDb();
621 $mockDb->expects( $this->exactly( 1 ) )
622 ->method( 'selectRowCount' )
627 "wl_notificationtimestamp IS NOT NULL",
630 $this->isType( 'string' ),
633 ->will( $this->returnValue( 50 ) );
635 $mockCache = $this->getMockCache();
636 $mockCache->expects( $this->never() )->method( 'set' );
637 $mockCache->expects( $this->never() )->method( 'get' );
638 $mockCache->expects( $this->never() )->method( 'delete' );
640 $store = $this->newWatchedItemStore(
641 $this->getMockLoadBalancer( $mockDb ),
643 $this->getMockReadOnlyMode()
648 $store->countUnreadNotifications( $user, $limit )
653 * @dataProvider provideIntWithDbUnsafeVersion
655 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
656 $user = $this->getMockNonAnonUserWithId( 1 );
658 $mockDb = $this->getMockDb();
659 $mockDb->expects( $this->exactly( 1 ) )
660 ->method( 'selectRowCount' )
665 "wl_notificationtimestamp IS NOT NULL",
668 $this->isType( 'string' ),
671 ->will( $this->returnValue( 9 ) );
673 $mockCache = $this->getMockCache();
674 $mockCache->expects( $this->never() )->method( 'set' );
675 $mockCache->expects( $this->never() )->method( 'get' );
676 $mockCache->expects( $this->never() )->method( 'delete' );
678 $store = $this->newWatchedItemStore(
679 $this->getMockLoadBalancer( $mockDb ),
681 $this->getMockReadOnlyMode()
686 $store->countUnreadNotifications( $user, $limit )
690 public function testDuplicateEntry_nothingToDuplicate() {
691 $mockDb = $this->getMockDb();
692 $mockDb->expects( $this->once() )
698 'wl_notificationtimestamp',
702 'wl_title' => 'Old_Title',
704 'WatchedItemStore::duplicateEntry',
707 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
709 $store = $this->newWatchedItemStore(
710 $this->getMockLoadBalancer( $mockDb ),
711 $this->getMockCache(),
712 $this->getMockReadOnlyMode()
715 $store->duplicateEntry(
716 Title
::newFromText( 'Old_Title' ),
717 Title
::newFromText( 'New_Title' )
721 public function testDuplicateEntry_somethingToDuplicate() {
723 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
724 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
727 $mockDb = $this->getMockDb();
728 $mockDb->expects( $this->at( 0 ) )
734 'wl_notificationtimestamp',
738 'wl_title' => 'Old_Title',
741 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
742 $mockDb->expects( $this->at( 1 ) )
743 ->method( 'replace' )
746 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
751 'wl_title' => 'New_Title',
752 'wl_notificationtimestamp' => '20151212010101',
757 'wl_title' => 'New_Title',
758 'wl_notificationtimestamp' => null,
761 $this->isType( 'string' )
764 $mockCache = $this->getMockCache();
765 $mockCache->expects( $this->never() )->method( 'get' );
766 $mockCache->expects( $this->never() )->method( 'delete' );
768 $store = $this->newWatchedItemStore(
769 $this->getMockLoadBalancer( $mockDb ),
771 $this->getMockReadOnlyMode()
774 $store->duplicateEntry(
775 Title
::newFromText( 'Old_Title' ),
776 Title
::newFromText( 'New_Title' )
780 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
781 $mockDb = $this->getMockDb();
782 $mockDb->expects( $this->at( 0 ) )
788 'wl_notificationtimestamp',
792 'wl_title' => 'Old_Title',
795 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
796 $mockDb->expects( $this->at( 1 ) )
802 'wl_notificationtimestamp',
806 'wl_title' => 'Old_Title',
809 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
811 $mockCache = $this->getMockCache();
812 $mockCache->expects( $this->never() )->method( 'get' );
813 $mockCache->expects( $this->never() )->method( 'delete' );
815 $store = $this->newWatchedItemStore(
816 $this->getMockLoadBalancer( $mockDb ),
818 $this->getMockReadOnlyMode()
821 $store->duplicateAllAssociatedEntries(
822 Title
::newFromText( 'Old_Title' ),
823 Title
::newFromText( 'New_Title' )
827 public function provideLinkTargetPairs() {
829 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
830 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
835 * @dataProvider provideLinkTargetPairs
837 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
838 LinkTarget
$oldTarget,
839 LinkTarget
$newTarget
842 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
845 $mockDb = $this->getMockDb();
846 $mockDb->expects( $this->at( 0 ) )
852 'wl_notificationtimestamp',
855 'wl_namespace' => $oldTarget->getNamespace(),
856 'wl_title' => $oldTarget->getDBkey(),
859 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
860 $mockDb->expects( $this->at( 1 ) )
861 ->method( 'replace' )
864 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
868 'wl_namespace' => $newTarget->getNamespace(),
869 'wl_title' => $newTarget->getDBkey(),
870 'wl_notificationtimestamp' => '20151212010101',
873 $this->isType( 'string' )
875 $mockDb->expects( $this->at( 2 ) )
881 'wl_notificationtimestamp',
884 'wl_namespace' => $oldTarget->getNamespace() +
1,
885 'wl_title' => $oldTarget->getDBkey(),
888 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
889 $mockDb->expects( $this->at( 3 ) )
890 ->method( 'replace' )
893 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
897 'wl_namespace' => $newTarget->getNamespace() +
1,
898 'wl_title' => $newTarget->getDBkey(),
899 'wl_notificationtimestamp' => '20151212010101',
902 $this->isType( 'string' )
905 $mockCache = $this->getMockCache();
906 $mockCache->expects( $this->never() )->method( 'get' );
907 $mockCache->expects( $this->never() )->method( 'delete' );
909 $store = $this->newWatchedItemStore(
910 $this->getMockLoadBalancer( $mockDb ),
912 $this->getMockReadOnlyMode()
915 $store->duplicateAllAssociatedEntries(
921 public function testAddWatch_nonAnonymousUser() {
922 $mockDb = $this->getMockDb();
923 $mockDb->expects( $this->once() )
931 'wl_title' => 'Some_Page',
932 'wl_notificationtimestamp' => null,
937 $mockCache = $this->getMockCache();
938 $mockCache->expects( $this->once() )
940 ->with( '0:Some_Page:1' );
942 $store = $this->newWatchedItemStore(
943 $this->getMockLoadBalancer( $mockDb ),
945 $this->getMockReadOnlyMode()
949 $this->getMockNonAnonUserWithId( 1 ),
950 Title
::newFromText( 'Some_Page' )
954 public function testAddWatch_anonymousUser() {
955 $mockDb = $this->getMockDb();
956 $mockDb->expects( $this->never() )
957 ->method( 'insert' );
959 $mockCache = $this->getMockCache();
960 $mockCache->expects( $this->never() )
961 ->method( 'delete' );
963 $store = $this->newWatchedItemStore(
964 $this->getMockLoadBalancer( $mockDb ),
966 $this->getMockReadOnlyMode()
970 $this->getAnonUser(),
971 Title
::newFromText( 'Some_Page' )
975 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
976 $store = $this->newWatchedItemStore(
977 $this->getMockLoadBalancer( $this->getMockDb() ),
978 $this->getMockCache(),
979 $this->getMockReadOnlyMode( true )
983 $store->addWatchBatchForUser(
984 $this->getMockNonAnonUserWithId( 1 ),
985 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
990 public function testAddWatchBatchForUser_nonAnonymousUser() {
991 $mockDb = $this->getMockDb();
992 $mockDb->expects( $this->once() )
1000 'wl_title' => 'Some_Page',
1001 'wl_notificationtimestamp' => null,
1005 'wl_namespace' => 1,
1006 'wl_title' => 'Some_Page',
1007 'wl_notificationtimestamp' => null,
1012 $mockCache = $this->getMockCache();
1013 $mockCache->expects( $this->exactly( 2 ) )
1014 ->method( 'delete' );
1015 $mockCache->expects( $this->at( 1 ) )
1016 ->method( 'delete' )
1017 ->with( '0:Some_Page:1' );
1018 $mockCache->expects( $this->at( 3 ) )
1019 ->method( 'delete' )
1020 ->with( '1:Some_Page:1' );
1022 $store = $this->newWatchedItemStore(
1023 $this->getMockLoadBalancer( $mockDb ),
1025 $this->getMockReadOnlyMode()
1028 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1031 $store->addWatchBatchForUser(
1033 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1038 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1039 $mockDb = $this->getMockDb();
1040 $mockDb->expects( $this->never() )
1041 ->method( 'insert' );
1043 $mockCache = $this->getMockCache();
1044 $mockCache->expects( $this->never() )
1045 ->method( 'delete' );
1047 $store = $this->newWatchedItemStore(
1048 $this->getMockLoadBalancer( $mockDb ),
1050 $this->getMockReadOnlyMode()
1054 $store->addWatchBatchForUser(
1055 $this->getAnonUser(),
1056 [ new TitleValue( 0, 'Other_Page' ) ]
1061 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1062 $user = $this->getMockNonAnonUserWithId( 1 );
1063 $mockDb = $this->getMockDb();
1064 $mockDb->expects( $this->never() )
1065 ->method( 'insert' );
1067 $mockCache = $this->getMockCache();
1068 $mockCache->expects( $this->never() )
1069 ->method( 'delete' );
1071 $store = $this->newWatchedItemStore(
1072 $this->getMockLoadBalancer( $mockDb ),
1074 $this->getMockReadOnlyMode()
1078 $store->addWatchBatchForUser( $user, [] )
1082 public function testLoadWatchedItem_existingItem() {
1083 $mockDb = $this->getMockDb();
1084 $mockDb->expects( $this->once() )
1085 ->method( 'selectRow' )
1088 'wl_notificationtimestamp',
1091 'wl_namespace' => 0,
1092 'wl_title' => 'SomeDbKey',
1095 ->will( $this->returnValue(
1096 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1099 $mockCache = $this->getMockCache();
1100 $mockCache->expects( $this->once() )
1106 $store = $this->newWatchedItemStore(
1107 $this->getMockLoadBalancer( $mockDb ),
1109 $this->getMockReadOnlyMode()
1112 $watchedItem = $store->loadWatchedItem(
1113 $this->getMockNonAnonUserWithId( 1 ),
1114 new TitleValue( 0, 'SomeDbKey' )
1116 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1117 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1118 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1119 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1122 public function testLoadWatchedItem_noItem() {
1123 $mockDb = $this->getMockDb();
1124 $mockDb->expects( $this->once() )
1125 ->method( 'selectRow' )
1128 'wl_notificationtimestamp',
1131 'wl_namespace' => 0,
1132 'wl_title' => 'SomeDbKey',
1135 ->will( $this->returnValue( [] ) );
1137 $mockCache = $this->getMockCache();
1138 $mockCache->expects( $this->never() )->method( 'get' );
1139 $mockCache->expects( $this->never() )->method( 'delete' );
1141 $store = $this->newWatchedItemStore(
1142 $this->getMockLoadBalancer( $mockDb ),
1144 $this->getMockReadOnlyMode()
1148 $store->loadWatchedItem(
1149 $this->getMockNonAnonUserWithId( 1 ),
1150 new TitleValue( 0, 'SomeDbKey' )
1155 public function testLoadWatchedItem_anonymousUser() {
1156 $mockDb = $this->getMockDb();
1157 $mockDb->expects( $this->never() )
1158 ->method( 'selectRow' );
1160 $mockCache = $this->getMockCache();
1161 $mockCache->expects( $this->never() )->method( 'get' );
1162 $mockCache->expects( $this->never() )->method( 'delete' );
1164 $store = $this->newWatchedItemStore(
1165 $this->getMockLoadBalancer( $mockDb ),
1167 $this->getMockReadOnlyMode()
1171 $store->loadWatchedItem(
1172 $this->getAnonUser(),
1173 new TitleValue( 0, 'SomeDbKey' )
1178 public function testRemoveWatch_existingItem() {
1179 $mockDb = $this->getMockDb();
1180 $mockDb->expects( $this->once() )
1181 ->method( 'delete' )
1186 'wl_namespace' => 0,
1187 'wl_title' => 'SomeDbKey',
1190 $mockDb->expects( $this->once() )
1191 ->method( 'affectedRows' )
1192 ->will( $this->returnValue( 1 ) );
1194 $mockCache = $this->getMockCache();
1195 $mockCache->expects( $this->never() )->method( 'get' );
1196 $mockCache->expects( $this->once() )
1197 ->method( 'delete' )
1198 ->with( '0:SomeDbKey:1' );
1200 $store = $this->newWatchedItemStore(
1201 $this->getMockLoadBalancer( $mockDb ),
1203 $this->getMockReadOnlyMode()
1207 $store->removeWatch(
1208 $this->getMockNonAnonUserWithId( 1 ),
1209 new TitleValue( 0, 'SomeDbKey' )
1214 public function testRemoveWatch_noItem() {
1215 $mockDb = $this->getMockDb();
1216 $mockDb->expects( $this->once() )
1217 ->method( 'delete' )
1222 'wl_namespace' => 0,
1223 'wl_title' => 'SomeDbKey',
1226 $mockDb->expects( $this->once() )
1227 ->method( 'affectedRows' )
1228 ->will( $this->returnValue( 0 ) );
1230 $mockCache = $this->getMockCache();
1231 $mockCache->expects( $this->never() )->method( 'get' );
1232 $mockCache->expects( $this->once() )
1233 ->method( 'delete' )
1234 ->with( '0:SomeDbKey:1' );
1236 $store = $this->newWatchedItemStore(
1237 $this->getMockLoadBalancer( $mockDb ),
1239 $this->getMockReadOnlyMode()
1243 $store->removeWatch(
1244 $this->getMockNonAnonUserWithId( 1 ),
1245 new TitleValue( 0, 'SomeDbKey' )
1250 public function testRemoveWatch_anonymousUser() {
1251 $mockDb = $this->getMockDb();
1252 $mockDb->expects( $this->never() )
1253 ->method( 'delete' );
1255 $mockCache = $this->getMockCache();
1256 $mockCache->expects( $this->never() )->method( 'get' );
1257 $mockCache->expects( $this->never() )
1258 ->method( 'delete' );
1260 $store = $this->newWatchedItemStore(
1261 $this->getMockLoadBalancer( $mockDb ),
1263 $this->getMockReadOnlyMode()
1267 $store->removeWatch(
1268 $this->getAnonUser(),
1269 new TitleValue( 0, 'SomeDbKey' )
1274 public function testGetWatchedItem_existingItem() {
1275 $mockDb = $this->getMockDb();
1276 $mockDb->expects( $this->once() )
1277 ->method( 'selectRow' )
1280 'wl_notificationtimestamp',
1283 'wl_namespace' => 0,
1284 'wl_title' => 'SomeDbKey',
1287 ->will( $this->returnValue(
1288 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1291 $mockCache = $this->getMockCache();
1292 $mockCache->expects( $this->never() )->method( 'delete' );
1293 $mockCache->expects( $this->once() )
1298 ->will( $this->returnValue( null ) );
1299 $mockCache->expects( $this->once() )
1305 $store = $this->newWatchedItemStore(
1306 $this->getMockLoadBalancer( $mockDb ),
1308 $this->getMockReadOnlyMode()
1311 $watchedItem = $store->getWatchedItem(
1312 $this->getMockNonAnonUserWithId( 1 ),
1313 new TitleValue( 0, 'SomeDbKey' )
1315 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1316 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1317 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1318 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1321 public function testGetWatchedItem_cachedItem() {
1322 $mockDb = $this->getMockDb();
1323 $mockDb->expects( $this->never() )
1324 ->method( 'selectRow' );
1326 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1327 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1328 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1330 $mockCache = $this->getMockCache();
1331 $mockCache->expects( $this->never() )->method( 'delete' );
1332 $mockCache->expects( $this->never() )->method( 'set' );
1333 $mockCache->expects( $this->once() )
1338 ->will( $this->returnValue( $cachedItem ) );
1340 $store = $this->newWatchedItemStore(
1341 $this->getMockLoadBalancer( $mockDb ),
1343 $this->getMockReadOnlyMode()
1346 $this->assertEquals(
1348 $store->getWatchedItem(
1355 public function testGetWatchedItem_noItem() {
1356 $mockDb = $this->getMockDb();
1357 $mockDb->expects( $this->once() )
1358 ->method( 'selectRow' )
1361 'wl_notificationtimestamp',
1364 'wl_namespace' => 0,
1365 'wl_title' => 'SomeDbKey',
1368 ->will( $this->returnValue( [] ) );
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'set' );
1372 $mockCache->expects( $this->never() )->method( 'delete' );
1373 $mockCache->expects( $this->once() )
1375 ->with( '0:SomeDbKey:1' )
1376 ->will( $this->returnValue( false ) );
1378 $store = $this->newWatchedItemStore(
1379 $this->getMockLoadBalancer( $mockDb ),
1381 $this->getMockReadOnlyMode()
1385 $store->getWatchedItem(
1386 $this->getMockNonAnonUserWithId( 1 ),
1387 new TitleValue( 0, 'SomeDbKey' )
1392 public function testGetWatchedItem_anonymousUser() {
1393 $mockDb = $this->getMockDb();
1394 $mockDb->expects( $this->never() )
1395 ->method( 'selectRow' );
1397 $mockCache = $this->getMockCache();
1398 $mockCache->expects( $this->never() )->method( 'set' );
1399 $mockCache->expects( $this->never() )->method( 'get' );
1400 $mockCache->expects( $this->never() )->method( 'delete' );
1402 $store = $this->newWatchedItemStore(
1403 $this->getMockLoadBalancer( $mockDb ),
1405 $this->getMockReadOnlyMode()
1409 $store->getWatchedItem(
1410 $this->getAnonUser(),
1411 new TitleValue( 0, 'SomeDbKey' )
1416 public function testGetWatchedItemsForUser() {
1417 $mockDb = $this->getMockDb();
1418 $mockDb->expects( $this->once() )
1419 ->method( 'select' )
1422 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1425 ->will( $this->returnValue( [
1426 $this->getFakeRow( [
1427 'wl_namespace' => 0,
1428 'wl_title' => 'Foo1',
1429 'wl_notificationtimestamp' => '20151212010101',
1431 $this->getFakeRow( [
1432 'wl_namespace' => 1,
1433 'wl_title' => 'Foo2',
1434 'wl_notificationtimestamp' => null,
1438 $mockCache = $this->getMockCache();
1439 $mockCache->expects( $this->never() )->method( 'delete' );
1440 $mockCache->expects( $this->never() )->method( 'get' );
1441 $mockCache->expects( $this->never() )->method( 'set' );
1443 $store = $this->newWatchedItemStore(
1444 $this->getMockLoadBalancer( $mockDb ),
1446 $this->getMockReadOnlyMode()
1448 $user = $this->getMockNonAnonUserWithId( 1 );
1450 $watchedItems = $store->getWatchedItemsForUser( $user );
1452 $this->assertInternalType( 'array', $watchedItems );
1453 $this->assertCount( 2, $watchedItems );
1454 foreach ( $watchedItems as $watchedItem ) {
1455 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1457 $this->assertEquals(
1458 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1461 $this->assertEquals(
1462 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1467 public function provideDbTypes() {
1469 [ false, DB_REPLICA
],
1470 [ true, DB_MASTER
],
1475 * @dataProvider provideDbTypes
1477 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1478 $mockDb = $this->getMockDb();
1479 $mockCache = $this->getMockCache();
1480 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1481 $user = $this->getMockNonAnonUserWithId( 1 );
1483 $mockDb->expects( $this->once() )
1484 ->method( 'select' )
1487 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1489 $this->isType( 'string' ),
1490 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1492 ->will( $this->returnValue( [] ) );
1494 $store = $this->newWatchedItemStore(
1497 $this->getMockReadOnlyMode()
1500 $watchedItems = $store->getWatchedItemsForUser(
1502 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1504 $this->assertEquals( [], $watchedItems );
1507 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1508 $store = $this->newWatchedItemStore(
1509 $this->getMockLoadBalancer( $this->getMockDb() ),
1510 $this->getMockCache(),
1511 $this->getMockReadOnlyMode()
1514 $this->setExpectedException( 'InvalidArgumentException' );
1515 $store->getWatchedItemsForUser(
1516 $this->getMockNonAnonUserWithId( 1 ),
1521 public function testIsWatchedItem_existingItem() {
1522 $mockDb = $this->getMockDb();
1523 $mockDb->expects( $this->once() )
1524 ->method( 'selectRow' )
1527 'wl_notificationtimestamp',
1530 'wl_namespace' => 0,
1531 'wl_title' => 'SomeDbKey',
1534 ->will( $this->returnValue(
1535 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1538 $mockCache = $this->getMockCache();
1539 $mockCache->expects( $this->never() )->method( 'delete' );
1540 $mockCache->expects( $this->once() )
1542 ->with( '0:SomeDbKey:1' )
1543 ->will( $this->returnValue( false ) );
1544 $mockCache->expects( $this->once() )
1550 $store = $this->newWatchedItemStore(
1551 $this->getMockLoadBalancer( $mockDb ),
1553 $this->getMockReadOnlyMode()
1558 $this->getMockNonAnonUserWithId( 1 ),
1559 new TitleValue( 0, 'SomeDbKey' )
1564 public function testIsWatchedItem_noItem() {
1565 $mockDb = $this->getMockDb();
1566 $mockDb->expects( $this->once() )
1567 ->method( 'selectRow' )
1570 'wl_notificationtimestamp',
1573 'wl_namespace' => 0,
1574 'wl_title' => 'SomeDbKey',
1577 ->will( $this->returnValue( [] ) );
1579 $mockCache = $this->getMockCache();
1580 $mockCache->expects( $this->never() )->method( 'set' );
1581 $mockCache->expects( $this->never() )->method( 'delete' );
1582 $mockCache->expects( $this->once() )
1584 ->with( '0:SomeDbKey:1' )
1585 ->will( $this->returnValue( false ) );
1587 $store = $this->newWatchedItemStore(
1588 $this->getMockLoadBalancer( $mockDb ),
1590 $this->getMockReadOnlyMode()
1595 $this->getMockNonAnonUserWithId( 1 ),
1596 new TitleValue( 0, 'SomeDbKey' )
1601 public function testIsWatchedItem_anonymousUser() {
1602 $mockDb = $this->getMockDb();
1603 $mockDb->expects( $this->never() )
1604 ->method( 'selectRow' );
1606 $mockCache = $this->getMockCache();
1607 $mockCache->expects( $this->never() )->method( 'set' );
1608 $mockCache->expects( $this->never() )->method( 'get' );
1609 $mockCache->expects( $this->never() )->method( 'delete' );
1611 $store = $this->newWatchedItemStore(
1612 $this->getMockLoadBalancer( $mockDb ),
1614 $this->getMockReadOnlyMode()
1619 $this->getAnonUser(),
1620 new TitleValue( 0, 'SomeDbKey' )
1625 public function testGetNotificationTimestampsBatch() {
1627 new TitleValue( 0, 'SomeDbKey' ),
1628 new TitleValue( 1, 'AnotherDbKey' ),
1631 $mockDb = $this->getMockDb();
1633 $this->getFakeRow( [
1634 'wl_namespace' => 0,
1635 'wl_title' => 'SomeDbKey',
1636 'wl_notificationtimestamp' => '20151212010101',
1640 'wl_namespace' => 1,
1641 'wl_title' => 'AnotherDbKey',
1642 'wl_notificationtimestamp' => null,
1647 $mockDb->expects( $this->once() )
1648 ->method( 'makeWhereFrom2d' )
1650 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1651 $this->isType( 'string' ),
1652 $this->isType( 'string' )
1654 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1655 $mockDb->expects( $this->once() )
1656 ->method( 'select' )
1659 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1661 'makeWhereFrom2d return value',
1664 $this->isType( 'string' )
1666 ->will( $this->returnValue( $dbResult ) );
1668 $mockCache = $this->getMockCache();
1669 $mockCache->expects( $this->exactly( 2 ) )
1672 [ '0:SomeDbKey:1' ],
1673 [ '1:AnotherDbKey:1' ]
1675 ->will( $this->returnValue( null ) );
1676 $mockCache->expects( $this->never() )->method( 'set' );
1677 $mockCache->expects( $this->never() )->method( 'delete' );
1679 $store = $this->newWatchedItemStore(
1680 $this->getMockLoadBalancer( $mockDb ),
1682 $this->getMockReadOnlyMode()
1685 $this->assertEquals(
1687 0 => [ 'SomeDbKey' => '20151212010101', ],
1688 1 => [ 'AnotherDbKey' => null, ],
1690 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1694 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1696 new TitleValue( 0, 'OtherDbKey' ),
1699 $mockDb = $this->getMockDb();
1701 $mockDb->expects( $this->once() )
1702 ->method( 'makeWhereFrom2d' )
1704 [ [ 'OtherDbKey' => 1 ] ],
1705 $this->isType( 'string' ),
1706 $this->isType( 'string' )
1708 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1709 $mockDb->expects( $this->once() )
1710 ->method( 'select' )
1713 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1715 'makeWhereFrom2d return value',
1718 $this->isType( 'string' )
1720 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1722 $mockCache = $this->getMockCache();
1723 $mockCache->expects( $this->once() )
1725 ->with( '0:OtherDbKey:1' )
1726 ->will( $this->returnValue( null ) );
1727 $mockCache->expects( $this->never() )->method( 'set' );
1728 $mockCache->expects( $this->never() )->method( 'delete' );
1730 $store = $this->newWatchedItemStore(
1731 $this->getMockLoadBalancer( $mockDb ),
1733 $this->getMockReadOnlyMode()
1736 $this->assertEquals(
1738 0 => [ 'OtherDbKey' => false, ],
1740 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1744 public function testGetNotificationTimestampsBatch_cachedItem() {
1746 new TitleValue( 0, 'SomeDbKey' ),
1747 new TitleValue( 1, 'AnotherDbKey' ),
1750 $user = $this->getMockNonAnonUserWithId( 1 );
1751 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1753 $mockDb = $this->getMockDb();
1755 $mockDb->expects( $this->once() )
1756 ->method( 'makeWhereFrom2d' )
1758 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1759 $this->isType( 'string' ),
1760 $this->isType( 'string' )
1762 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1763 $mockDb->expects( $this->once() )
1764 ->method( 'select' )
1767 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1769 'makeWhereFrom2d return value',
1772 $this->isType( 'string' )
1774 ->will( $this->returnValue( [
1776 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1780 $mockCache = $this->getMockCache();
1781 $mockCache->expects( $this->at( 1 ) )
1783 ->with( '0:SomeDbKey:1' )
1784 ->will( $this->returnValue( $cachedItem ) );
1785 $mockCache->expects( $this->at( 3 ) )
1787 ->with( '1:AnotherDbKey:1' )
1788 ->will( $this->returnValue( null ) );
1789 $mockCache->expects( $this->never() )->method( 'set' );
1790 $mockCache->expects( $this->never() )->method( 'delete' );
1792 $store = $this->newWatchedItemStore(
1793 $this->getMockLoadBalancer( $mockDb ),
1795 $this->getMockReadOnlyMode()
1798 $this->assertEquals(
1800 0 => [ 'SomeDbKey' => '20151212010101', ],
1801 1 => [ 'AnotherDbKey' => null, ],
1803 $store->getNotificationTimestampsBatch( $user, $targets )
1807 public function testGetNotificationTimestampsBatch_allItemsCached() {
1809 new TitleValue( 0, 'SomeDbKey' ),
1810 new TitleValue( 1, 'AnotherDbKey' ),
1813 $user = $this->getMockNonAnonUserWithId( 1 );
1815 new WatchedItem( $user, $targets[0], '20151212010101' ),
1816 new WatchedItem( $user, $targets[1], null ),
1818 $mockDb = $this->getMockDb();
1819 $mockDb->expects( $this->never() )->method( $this->anything() );
1821 $mockCache = $this->getMockCache();
1822 $mockCache->expects( $this->at( 1 ) )
1824 ->with( '0:SomeDbKey:1' )
1825 ->will( $this->returnValue( $cachedItems[0] ) );
1826 $mockCache->expects( $this->at( 3 ) )
1828 ->with( '1:AnotherDbKey:1' )
1829 ->will( $this->returnValue( $cachedItems[1] ) );
1830 $mockCache->expects( $this->never() )->method( 'set' );
1831 $mockCache->expects( $this->never() )->method( 'delete' );
1833 $store = $this->newWatchedItemStore(
1834 $this->getMockLoadBalancer( $mockDb ),
1836 $this->getMockReadOnlyMode()
1839 $this->assertEquals(
1841 0 => [ 'SomeDbKey' => '20151212010101', ],
1842 1 => [ 'AnotherDbKey' => null, ],
1844 $store->getNotificationTimestampsBatch( $user, $targets )
1848 public function testGetNotificationTimestampsBatch_anonymousUser() {
1850 new TitleValue( 0, 'SomeDbKey' ),
1851 new TitleValue( 1, 'AnotherDbKey' ),
1854 $mockDb = $this->getMockDb();
1855 $mockDb->expects( $this->never() )->method( $this->anything() );
1857 $mockCache = $this->getMockCache();
1858 $mockCache->expects( $this->never() )->method( $this->anything() );
1860 $store = $this->newWatchedItemStore(
1861 $this->getMockLoadBalancer( $mockDb ),
1863 $this->getMockReadOnlyMode()
1866 $this->assertEquals(
1868 0 => [ 'SomeDbKey' => false, ],
1869 1 => [ 'AnotherDbKey' => false, ],
1871 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1875 public function testResetNotificationTimestamp_anonymousUser() {
1876 $mockDb = $this->getMockDb();
1877 $mockDb->expects( $this->never() )
1878 ->method( 'selectRow' );
1880 $mockCache = $this->getMockCache();
1881 $mockCache->expects( $this->never() )->method( 'get' );
1882 $mockCache->expects( $this->never() )->method( 'set' );
1883 $mockCache->expects( $this->never() )->method( 'delete' );
1885 $store = $this->newWatchedItemStore(
1886 $this->getMockLoadBalancer( $mockDb ),
1888 $this->getMockReadOnlyMode()
1892 $store->resetNotificationTimestamp(
1893 $this->getAnonUser(),
1894 Title
::newFromText( 'SomeDbKey' )
1899 public function testResetNotificationTimestamp_noItem() {
1900 $mockDb = $this->getMockDb();
1901 $mockDb->expects( $this->once() )
1902 ->method( 'selectRow' )
1905 'wl_notificationtimestamp',
1908 'wl_namespace' => 0,
1909 'wl_title' => 'SomeDbKey',
1912 ->will( $this->returnValue( [] ) );
1914 $mockCache = $this->getMockCache();
1915 $mockCache->expects( $this->never() )->method( 'get' );
1916 $mockCache->expects( $this->never() )->method( 'set' );
1917 $mockCache->expects( $this->never() )->method( 'delete' );
1919 $store = $this->newWatchedItemStore(
1920 $this->getMockLoadBalancer( $mockDb ),
1922 $this->getMockReadOnlyMode()
1926 $store->resetNotificationTimestamp(
1927 $this->getMockNonAnonUserWithId( 1 ),
1928 Title
::newFromText( 'SomeDbKey' )
1933 public function testResetNotificationTimestamp_item() {
1934 $user = $this->getMockNonAnonUserWithId( 1 );
1935 $title = Title
::newFromText( 'SomeDbKey' );
1937 $mockDb = $this->getMockDb();
1938 $mockDb->expects( $this->once() )
1939 ->method( 'selectRow' )
1942 'wl_notificationtimestamp',
1945 'wl_namespace' => 0,
1946 'wl_title' => 'SomeDbKey',
1949 ->will( $this->returnValue(
1950 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1953 $mockCache = $this->getMockCache();
1954 $mockCache->expects( $this->never() )->method( 'get' );
1955 $mockCache->expects( $this->once() )
1959 $this->isInstanceOf( WatchedItem
::class )
1961 $mockCache->expects( $this->once() )
1962 ->method( 'delete' )
1963 ->with( '0:SomeDbKey:1' );
1965 $store = $this->newWatchedItemStore(
1966 $this->getMockLoadBalancer( $mockDb ),
1968 $this->getMockReadOnlyMode()
1971 // Note: This does not actually assert the job is correct
1972 $callableCallCounter = 0;
1973 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
1974 $callableCallCounter++
;
1975 $this->assertInternalType( 'callable', $callable );
1977 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1980 $store->resetNotificationTimestamp(
1985 $this->assertEquals( 1, $callableCallCounter );
1987 ScopedCallback
::consume( $scopedOverride );
1990 public function testResetNotificationTimestamp_noItemForced() {
1991 $user = $this->getMockNonAnonUserWithId( 1 );
1992 $title = Title
::newFromText( 'SomeDbKey' );
1994 $mockDb = $this->getMockDb();
1995 $mockDb->expects( $this->never() )
1996 ->method( 'selectRow' );
1998 $mockCache = $this->getMockCache();
1999 $mockDb->expects( $this->never() )
2001 $mockDb->expects( $this->never() )
2003 $mockDb->expects( $this->never() )
2004 ->method( 'delete' );
2006 $store = $this->newWatchedItemStore(
2007 $this->getMockLoadBalancer( $mockDb ),
2009 $this->getMockReadOnlyMode()
2012 // Note: This does not actually assert the job is correct
2013 $callableCallCounter = 0;
2014 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2015 $callableCallCounter++
;
2016 $this->assertInternalType( 'callable', $callable );
2018 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2021 $store->resetNotificationTimestamp(
2027 $this->assertEquals( 1, $callableCallCounter );
2029 ScopedCallback
::consume( $scopedOverride );
2033 * @param string $text
2036 * @return PHPUnit_Framework_MockObject_MockObject|Title
2038 private function getMockTitle( $text, $ns = 0 ) {
2039 $title = $this->createMock( Title
::class );
2040 $title->expects( $this->any() )
2041 ->method( 'getText' )
2042 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2043 $title->expects( $this->any() )
2044 ->method( 'getDbKey' )
2045 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2046 $title->expects( $this->any() )
2047 ->method( 'getNamespace' )
2048 ->will( $this->returnValue( $ns ) );
2052 private function verifyCallbackJob(
2054 LinkTarget
$expectedTitle,
2056 callable
$notificationTimestampCondition
2058 $this->assertInternalType( 'callable', $callback );
2060 $callbackReflector = new ReflectionFunction( $callback );
2061 $vars = $callbackReflector->getStaticVariables();
2062 $this->assertArrayHasKey( 'job', $vars );
2063 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2065 /** @var ActivityUpdateJob $job */
2066 $job = $vars['job'];
2067 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2068 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2070 $jobParams = $job->getParams();
2071 $this->assertArrayHasKey( 'type', $jobParams );
2072 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2073 $this->assertArrayHasKey( 'userid', $jobParams );
2074 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2075 $this->assertArrayHasKey( 'notifTime', $jobParams );
2076 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2079 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2080 $user = $this->getMockNonAnonUserWithId( 1 );
2082 $title = $this->getMockTitle( 'SomeTitle' );
2083 $title->expects( $this->once() )
2084 ->method( 'getNextRevisionID' )
2086 ->will( $this->returnValue( false ) );
2088 $mockDb = $this->getMockDb();
2089 $mockDb->expects( $this->never() )
2090 ->method( 'selectRow' );
2092 $mockCache = $this->getMockCache();
2093 $mockDb->expects( $this->never() )
2095 $mockDb->expects( $this->never() )
2097 $mockDb->expects( $this->never() )
2098 ->method( 'delete' );
2100 $store = $this->newWatchedItemStore(
2101 $this->getMockLoadBalancer( $mockDb ),
2103 $this->getMockReadOnlyMode()
2106 $callableCallCounter = 0;
2107 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2108 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2109 $callableCallCounter++
;
2110 $this->verifyCallbackJob(
2114 function ( $time ) {
2115 return $time === null;
2122 $store->resetNotificationTimestamp(
2129 $this->assertEquals( 1, $callableCallCounter );
2131 ScopedCallback
::consume( $scopedOverride );
2134 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2135 $user = $this->getMockNonAnonUserWithId( 1 );
2137 $title = $this->getMockTitle( 'SomeDbKey' );
2138 $title->expects( $this->once() )
2139 ->method( 'getNextRevisionID' )
2141 ->will( $this->returnValue( 33 ) );
2143 $mockDb = $this->getMockDb();
2144 $mockDb->expects( $this->once() )
2145 ->method( 'selectRow' )
2148 'wl_notificationtimestamp',
2151 'wl_namespace' => 0,
2152 'wl_title' => 'SomeDbKey',
2155 ->will( $this->returnValue(
2156 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2159 $mockCache = $this->getMockCache();
2160 $mockDb->expects( $this->never() )
2162 $mockDb->expects( $this->never() )
2164 $mockDb->expects( $this->never() )
2165 ->method( 'delete' );
2167 $store = $this->newWatchedItemStore(
2168 $this->getMockLoadBalancer( $mockDb ),
2170 $this->getMockReadOnlyMode()
2173 $addUpdateCallCounter = 0;
2174 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2175 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2176 $addUpdateCallCounter++
;
2177 $this->verifyCallbackJob(
2181 function ( $time ) {
2182 return $time !== null && $time > '20151212010101';
2188 $getTimestampCallCounter = 0;
2189 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2190 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2191 $getTimestampCallCounter++
;
2192 $this->assertEquals( $title, $titleParam );
2193 $this->assertEquals( $oldid, $oldidParam );
2198 $store->resetNotificationTimestamp(
2205 $this->assertEquals( 1, $addUpdateCallCounter );
2206 $this->assertEquals( 1, $getTimestampCallCounter );
2208 ScopedCallback
::consume( $scopedOverrideDeferred );
2209 ScopedCallback
::consume( $scopedOverrideRevision );
2212 public function testResetNotificationTimestamp_notWatchedPageForced() {
2213 $user = $this->getMockNonAnonUserWithId( 1 );
2215 $title = $this->getMockTitle( 'SomeDbKey' );
2216 $title->expects( $this->once() )
2217 ->method( 'getNextRevisionID' )
2219 ->will( $this->returnValue( 33 ) );
2221 $mockDb = $this->getMockDb();
2222 $mockDb->expects( $this->once() )
2223 ->method( 'selectRow' )
2226 'wl_notificationtimestamp',
2229 'wl_namespace' => 0,
2230 'wl_title' => 'SomeDbKey',
2233 ->will( $this->returnValue( false ) );
2235 $mockCache = $this->getMockCache();
2236 $mockDb->expects( $this->never() )
2238 $mockDb->expects( $this->never() )
2240 $mockDb->expects( $this->never() )
2241 ->method( 'delete' );
2243 $store = $this->newWatchedItemStore(
2244 $this->getMockLoadBalancer( $mockDb ),
2246 $this->getMockReadOnlyMode()
2249 $callableCallCounter = 0;
2250 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2251 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2252 $callableCallCounter++
;
2253 $this->verifyCallbackJob(
2257 function ( $time ) {
2258 return $time === null;
2265 $store->resetNotificationTimestamp(
2272 $this->assertEquals( 1, $callableCallCounter );
2274 ScopedCallback
::consume( $scopedOverride );
2277 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2278 $user = $this->getMockNonAnonUserWithId( 1 );
2280 $title = $this->getMockTitle( 'SomeDbKey' );
2281 $title->expects( $this->once() )
2282 ->method( 'getNextRevisionID' )
2284 ->will( $this->returnValue( 33 ) );
2286 $mockDb = $this->getMockDb();
2287 $mockDb->expects( $this->once() )
2288 ->method( 'selectRow' )
2291 'wl_notificationtimestamp',
2294 'wl_namespace' => 0,
2295 'wl_title' => 'SomeDbKey',
2298 ->will( $this->returnValue(
2299 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2302 $mockCache = $this->getMockCache();
2303 $mockDb->expects( $this->never() )
2305 $mockDb->expects( $this->never() )
2307 $mockDb->expects( $this->never() )
2308 ->method( 'delete' );
2310 $store = $this->newWatchedItemStore(
2311 $this->getMockLoadBalancer( $mockDb ),
2313 $this->getMockReadOnlyMode()
2316 $addUpdateCallCounter = 0;
2317 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2318 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2319 $addUpdateCallCounter++
;
2320 $this->verifyCallbackJob(
2324 function ( $time ) {
2325 return $time === '30151212010101';
2331 $getTimestampCallCounter = 0;
2332 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2333 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2334 $getTimestampCallCounter++
;
2335 $this->assertEquals( $title, $titleParam );
2336 $this->assertEquals( $oldid, $oldidParam );
2341 $store->resetNotificationTimestamp(
2348 $this->assertEquals( 1, $addUpdateCallCounter );
2349 $this->assertEquals( 1, $getTimestampCallCounter );
2351 ScopedCallback
::consume( $scopedOverrideDeferred );
2352 ScopedCallback
::consume( $scopedOverrideRevision );
2355 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2356 $user = $this->getMockNonAnonUserWithId( 1 );
2358 $title = $this->getMockTitle( 'SomeDbKey' );
2359 $title->expects( $this->once() )
2360 ->method( 'getNextRevisionID' )
2362 ->will( $this->returnValue( 33 ) );
2364 $mockDb = $this->getMockDb();
2365 $mockDb->expects( $this->once() )
2366 ->method( 'selectRow' )
2369 'wl_notificationtimestamp',
2372 'wl_namespace' => 0,
2373 'wl_title' => 'SomeDbKey',
2376 ->will( $this->returnValue(
2377 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2380 $mockCache = $this->getMockCache();
2381 $mockDb->expects( $this->never() )
2383 $mockDb->expects( $this->never() )
2385 $mockDb->expects( $this->never() )
2386 ->method( 'delete' );
2388 $store = $this->newWatchedItemStore(
2389 $this->getMockLoadBalancer( $mockDb ),
2391 $this->getMockReadOnlyMode()
2394 $addUpdateCallCounter = 0;
2395 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2396 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2397 $addUpdateCallCounter++
;
2398 $this->verifyCallbackJob(
2402 function ( $time ) {
2403 return $time === false;
2409 $getTimestampCallCounter = 0;
2410 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2411 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2412 $getTimestampCallCounter++
;
2413 $this->assertEquals( $title, $titleParam );
2414 $this->assertEquals( $oldid, $oldidParam );
2419 $store->resetNotificationTimestamp(
2426 $this->assertEquals( 1, $addUpdateCallCounter );
2427 $this->assertEquals( 1, $getTimestampCallCounter );
2429 ScopedCallback
::consume( $scopedOverrideDeferred );
2430 ScopedCallback
::consume( $scopedOverrideRevision );
2433 public function testSetNotificationTimestampsForUser_anonUser() {
2434 $store = $this->newWatchedItemStore(
2435 $this->getMockLoadBalancer( $this->getMockDb() ),
2436 $this->getMockCache(),
2437 $this->getMockReadOnlyMode()
2439 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2442 public function testSetNotificationTimestampsForUser_allRows() {
2443 $user = $this->getMockNonAnonUserWithId( 1 );
2444 $timestamp = '20100101010101';
2446 $mockDb = $this->getMockDb();
2447 $mockDb->expects( $this->once() )
2448 ->method( 'update' )
2451 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2454 ->will( $this->returnValue( true ) );
2455 $mockDb->expects( $this->exactly( 1 ) )
2456 ->method( 'timestamp' )
2457 ->will( $this->returnCallback( function ( $value ) {
2458 return 'TS' . $value . 'TS';
2461 $store = $this->newWatchedItemStore(
2462 $this->getMockLoadBalancer( $mockDb ),
2463 $this->getMockCache(),
2464 $this->getMockReadOnlyMode()
2468 $store->setNotificationTimestampsForUser( $user, $timestamp )
2472 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2473 $user = $this->getMockNonAnonUserWithId( 1 );
2476 $mockDb = $this->getMockDb();
2477 $mockDb->expects( $this->once() )
2478 ->method( 'update' )
2481 [ 'wl_notificationtimestamp' => null ],
2484 ->will( $this->returnValue( true ) );
2485 $mockDb->expects( $this->exactly( 0 ) )
2486 ->method( 'timestamp' )
2487 ->will( $this->returnCallback( function ( $value ) {
2488 return 'TS' . $value . 'TS';
2491 $store = $this->newWatchedItemStore(
2492 $this->getMockLoadBalancer( $mockDb ),
2493 $this->getMockCache(),
2494 $this->getMockReadOnlyMode()
2498 $store->setNotificationTimestampsForUser( $user, $timestamp )
2502 public function testSetNotificationTimestampsForUser_specificTargets() {
2503 $user = $this->getMockNonAnonUserWithId( 1 );
2504 $timestamp = '20100101010101';
2505 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2507 $mockDb = $this->getMockDb();
2508 $mockDb->expects( $this->once() )
2509 ->method( 'update' )
2512 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2513 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2515 ->will( $this->returnValue( true ) );
2516 $mockDb->expects( $this->exactly( 1 ) )
2517 ->method( 'timestamp' )
2518 ->will( $this->returnCallback( function ( $value ) {
2519 return 'TS' . $value . 'TS';
2521 $mockDb->expects( $this->once() )
2522 ->method( 'makeWhereFrom2d' )
2524 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2525 $this->isType( 'string' ),
2526 $this->isType( 'string' )
2528 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2530 $store = $this->newWatchedItemStore(
2531 $this->getMockLoadBalancer( $mockDb ),
2532 $this->getMockCache(),
2533 $this->getMockReadOnlyMode()
2537 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2541 public function testUpdateNotificationTimestamp_watchersExist() {
2542 $mockDb = $this->getMockDb();
2543 $mockDb->expects( $this->once() )
2544 ->method( 'selectFieldValues' )
2550 'wl_namespace' => 0,
2551 'wl_title' => 'SomeDbKey',
2552 'wl_notificationtimestamp IS NULL'
2555 ->will( $this->returnValue( [ '2', '3' ] ) );
2556 $mockDb->expects( $this->once() )
2557 ->method( 'update' )
2560 [ 'wl_notificationtimestamp' => null ],
2562 'wl_user' => [ 2, 3 ],
2563 'wl_namespace' => 0,
2564 'wl_title' => 'SomeDbKey',
2568 $mockCache = $this->getMockCache();
2569 $mockCache->expects( $this->never() )->method( 'set' );
2570 $mockCache->expects( $this->never() )->method( 'get' );
2571 $mockCache->expects( $this->never() )->method( 'delete' );
2573 $store = $this->newWatchedItemStore(
2574 $this->getMockLoadBalancer( $mockDb ),
2576 $this->getMockReadOnlyMode()
2579 $this->assertEquals(
2581 $store->updateNotificationTimestamp(
2582 $this->getMockNonAnonUserWithId( 1 ),
2583 new TitleValue( 0, 'SomeDbKey' ),
2589 public function testUpdateNotificationTimestamp_noWatchers() {
2590 $mockDb = $this->getMockDb();
2591 $mockDb->expects( $this->once() )
2592 ->method( 'selectFieldValues' )
2598 'wl_namespace' => 0,
2599 'wl_title' => 'SomeDbKey',
2600 'wl_notificationtimestamp IS NULL'
2604 $this->returnValue( [] )
2606 $mockDb->expects( $this->never() )
2607 ->method( 'update' );
2609 $mockCache = $this->getMockCache();
2610 $mockCache->expects( $this->never() )->method( 'set' );
2611 $mockCache->expects( $this->never() )->method( 'get' );
2612 $mockCache->expects( $this->never() )->method( 'delete' );
2614 $store = $this->newWatchedItemStore(
2615 $this->getMockLoadBalancer( $mockDb ),
2617 $this->getMockReadOnlyMode()
2620 $watchers = $store->updateNotificationTimestamp(
2621 $this->getMockNonAnonUserWithId( 1 ),
2622 new TitleValue( 0, 'SomeDbKey' ),
2625 $this->assertInternalType( 'array', $watchers );
2626 $this->assertEmpty( $watchers );
2629 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2630 $user = $this->getMockNonAnonUserWithId( 1 );
2631 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2633 $mockDb = $this->getMockDb();
2634 $mockDb->expects( $this->once() )
2635 ->method( 'selectRow' )
2636 ->will( $this->returnValue(
2637 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2639 $mockDb->expects( $this->once() )
2640 ->method( 'selectFieldValues' )
2642 $this->returnValue( [ '2', '3' ] )
2644 $mockDb->expects( $this->once() )
2645 ->method( 'update' );
2647 $mockCache = $this->getMockCache();
2648 $mockCache->expects( $this->once() )
2650 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2651 $mockCache->expects( $this->once() )
2653 ->with( '0:SomeDbKey:1' );
2654 $mockCache->expects( $this->once() )
2655 ->method( 'delete' )
2656 ->with( '0:SomeDbKey:1' );
2658 $store = $this->newWatchedItemStore(
2659 $this->getMockLoadBalancer( $mockDb ),
2661 $this->getMockReadOnlyMode()
2664 // This will add the item to the cache
2665 $store->getWatchedItem( $user, $titleValue );
2667 $store->updateNotificationTimestamp(
2668 $this->getMockNonAnonUserWithId( 1 ),