2 use Liuggio\StatsdClient\Factory\StatsdDataFactory
;
7 * @covers WatchedItemStore
9 class WatchedItemStoreUnitTest
extends PHPUnit_Framework_TestCase
{
12 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
14 private function getMockDb() {
15 return $this->getMock( IDatabase
::class );
19 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
21 private function getMockLoadBalancer( $mockDb, $expectedConnectionType = null ) {
22 $mock = $this->getMockBuilder( LoadBalancer
::class )
23 ->disableOriginalConstructor()
25 if ( $expectedConnectionType !== null ) {
26 $mock->expects( $this->any() )
27 ->method( 'getConnection' )
28 ->with( $expectedConnectionType )
29 ->will( $this->returnValue( $mockDb ) );
31 $mock->expects( $this->any() )
32 ->method( 'getConnection' )
33 ->will( $this->returnValue( $mockDb ) );
35 $mock->expects( $this->any() )
36 ->method( 'getReadOnlyReason' )
37 ->will( $this->returnValue( false ) );
42 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
44 private function getMockCache() {
45 $mock = $this->getMockBuilder( HashBagOStuff
::class )
46 ->disableOriginalConstructor()
48 $mock->expects( $this->any() )
50 ->will( $this->returnCallback( function() {
51 return implode( ':', func_get_args() );
58 * @return PHPUnit_Framework_MockObject_MockObject|User
60 private function getMockNonAnonUserWithId( $id ) {
61 $mock = $this->getMock( User
::class );
62 $mock->expects( $this->any() )
64 ->will( $this->returnValue( false ) );
65 $mock->expects( $this->any() )
67 ->will( $this->returnValue( $id ) );
74 private function getAnonUser() {
75 return User
::newFromName( 'Anon_User' );
78 private function getFakeRow( array $rowValues ) {
79 $fakeRow = new stdClass();
80 foreach ( $rowValues as $valueName => $value ) {
81 $fakeRow->$valueName = $value;
86 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache ) {
87 return new WatchedItemStore(
93 public function testGetDefaultInstance() {
94 $instanceOne = WatchedItemStore
::getDefaultInstance();
95 $instanceTwo = WatchedItemStore
::getDefaultInstance();
96 $this->assertSame( $instanceOne, $instanceTwo );
99 public function testOverrideDefaultInstance() {
100 $instance = WatchedItemStore
::getDefaultInstance();
101 $scopedOverride = $instance->overrideDefaultInstance( null );
103 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
105 unset( $scopedOverride );
107 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
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 ),
136 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
139 public function testCountWatchers() {
140 $titleValue = new TitleValue( 0, 'SomeDbKey' );
142 $mockDb = $this->getMockDb();
143 $mockDb->expects( $this->exactly( 1 ) )
144 ->method( 'selectField' )
149 'wl_namespace' => $titleValue->getNamespace(),
150 'wl_title' => $titleValue->getDBkey(),
152 $this->isType( 'string' )
154 ->will( $this->returnValue( 7 ) );
156 $mockCache = $this->getMockCache();
157 $mockCache->expects( $this->never() )->method( 'get' );
158 $mockCache->expects( $this->never() )->method( 'set' );
159 $mockCache->expects( $this->never() )->method( 'delete' );
161 $store = $this->newWatchedItemStore(
162 $this->getMockLoadBalancer( $mockDb ),
166 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
169 public function testCountWatchersMultiple() {
171 new TitleValue( 0, 'SomeDbKey' ),
172 new TitleValue( 0, 'OtherDbKey' ),
173 new TitleValue( 1, 'AnotherDbKey' ),
176 $mockDb = $this->getMockDb();
179 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
180 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
181 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
184 $mockDb->expects( $this->once() )
185 ->method( 'makeWhereFrom2d' )
187 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
188 $this->isType( 'string' ),
189 $this->isType( 'string' )
191 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
192 $mockDb->expects( $this->once() )
196 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
197 [ 'makeWhereFrom2d return value' ],
198 $this->isType( 'string' ),
200 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
204 $this->returnValue( $dbResult )
207 $mockCache = $this->getMockCache();
208 $mockCache->expects( $this->never() )->method( 'get' );
209 $mockCache->expects( $this->never() )->method( 'set' );
210 $mockCache->expects( $this->never() )->method( 'delete' );
212 $store = $this->newWatchedItemStore(
213 $this->getMockLoadBalancer( $mockDb ),
218 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
219 1 => [ 'AnotherDbKey' => 500 ],
221 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
224 public function provideIntWithDbUnsafeVersion() {
227 [ "50; DROP TABLE watchlist;\n--" ],
232 * @dataProvider provideIntWithDbUnsafeVersion
234 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
236 new TitleValue( 0, 'SomeDbKey' ),
237 new TitleValue( 0, 'OtherDbKey' ),
238 new TitleValue( 1, 'AnotherDbKey' ),
241 $mockDb = $this->getMockDb();
244 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
245 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
246 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
249 $mockDb->expects( $this->once() )
250 ->method( 'makeWhereFrom2d' )
252 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
253 $this->isType( 'string' ),
254 $this->isType( 'string' )
256 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
257 $mockDb->expects( $this->once() )
261 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
262 [ 'makeWhereFrom2d return value' ],
263 $this->isType( 'string' ),
265 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
266 'HAVING' => 'COUNT(*) >= 50',
270 $this->returnValue( $dbResult )
273 $mockCache = $this->getMockCache();
274 $mockCache->expects( $this->never() )->method( 'get' );
275 $mockCache->expects( $this->never() )->method( 'set' );
276 $mockCache->expects( $this->never() )->method( 'delete' );
278 $store = $this->newWatchedItemStore(
279 $this->getMockLoadBalancer( $mockDb ),
284 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
285 1 => [ 'AnotherDbKey' => 500 ],
289 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
293 public function testCountVisitingWatchers() {
294 $titleValue = new TitleValue( 0, 'SomeDbKey' );
296 $mockDb = $this->getMockDb();
297 $mockDb->expects( $this->exactly( 1 ) )
298 ->method( 'selectField' )
303 'wl_namespace' => $titleValue->getNamespace(),
304 'wl_title' => $titleValue->getDBkey(),
305 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
307 $this->isType( 'string' )
309 ->will( $this->returnValue( 7 ) );
310 $mockDb->expects( $this->exactly( 1 ) )
311 ->method( 'addQuotes' )
312 ->will( $this->returnCallback( function( $value ) {
315 $mockDb->expects( $this->exactly( 1 ) )
316 ->method( 'timestamp' )
317 ->will( $this->returnCallback( function( $value ) {
318 return 'TS' . $value . 'TS';
321 $mockCache = $this->getMockCache();
322 $mockCache->expects( $this->never() )->method( 'set' );
323 $mockCache->expects( $this->never() )->method( 'get' );
324 $mockCache->expects( $this->never() )->method( 'delete' );
326 $store = $this->newWatchedItemStore(
327 $this->getMockLoadBalancer( $mockDb ),
331 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
334 public function testCountVisitingWatchersMultiple() {
335 $titleValuesWithThresholds = [
336 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
337 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
338 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
342 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
343 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
344 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
346 $mockDb = $this->getMockDb();
347 $mockDb->expects( $this->exactly( 2 * 3 ) )
348 ->method( 'addQuotes' )
349 ->will( $this->returnCallback( function( $value ) {
352 $mockDb->expects( $this->exactly( 3 ) )
353 ->method( 'timestamp' )
354 ->will( $this->returnCallback( function( $value ) {
355 return 'TS' . $value . 'TS';
357 $mockDb->expects( $this->any() )
358 ->method( 'makeList' )
360 $this->isType( 'array' ),
361 $this->isType( 'int' )
363 ->will( $this->returnCallback( function( $a, $conj ) {
364 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
365 return join( $sqlConj, array_map( function( $s ) {
366 return '(' . $s . ')';
370 $mockDb->expects( $this->never() )
371 ->method( 'makeWhereFrom2d' );
374 '((wl_namespace = 0) AND (' .
375 "(((wl_title = 'SomeDbKey') AND (" .
376 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
378 "(wl_title = 'OtherDbKey') AND (" .
379 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
381 ') OR ((wl_namespace = 1) AND (' .
382 "(((wl_title = 'AnotherDbKey') AND (".
383 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
385 $mockDb->expects( $this->once() )
389 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
391 $this->isType( 'string' ),
393 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
397 $this->returnValue( $dbResult )
400 $mockCache = $this->getMockCache();
401 $mockCache->expects( $this->never() )->method( 'get' );
402 $mockCache->expects( $this->never() )->method( 'set' );
403 $mockCache->expects( $this->never() )->method( 'delete' );
405 $store = $this->newWatchedItemStore(
406 $this->getMockLoadBalancer( $mockDb ),
411 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
412 1 => [ 'AnotherDbKey' => 500 ],
416 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
420 public function testCountVisitingWatchersMultiple_withMissingTargets() {
421 $titleValuesWithThresholds = [
422 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
423 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
424 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
425 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
426 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
430 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
431 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
432 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
434 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
437 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
440 $mockDb = $this->getMockDb();
441 $mockDb->expects( $this->exactly( 2 * 3 ) )
442 ->method( 'addQuotes' )
443 ->will( $this->returnCallback( function( $value ) {
446 $mockDb->expects( $this->exactly( 3 ) )
447 ->method( 'timestamp' )
448 ->will( $this->returnCallback( function( $value ) {
449 return 'TS' . $value . 'TS';
451 $mockDb->expects( $this->any() )
452 ->method( 'makeList' )
454 $this->isType( 'array' ),
455 $this->isType( 'int' )
457 ->will( $this->returnCallback( function( $a, $conj ) {
458 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
459 return join( $sqlConj, array_map( function( $s ) {
460 return '(' . $s . ')';
464 $mockDb->expects( $this->once() )
465 ->method( 'makeWhereFrom2d' )
467 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
468 $this->isType( 'string' ),
469 $this->isType( 'string' )
471 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
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)" .
486 '(makeWhereFrom2d return value)';
487 $mockDb->expects( $this->once() )
491 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
493 $this->isType( 'string' ),
495 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
499 $this->returnValue( $dbResult )
502 $mockCache = $this->getMockCache();
503 $mockCache->expects( $this->never() )->method( 'get' );
504 $mockCache->expects( $this->never() )->method( 'set' );
505 $mockCache->expects( $this->never() )->method( 'delete' );
507 $store = $this->newWatchedItemStore(
508 $this->getMockLoadBalancer( $mockDb ),
514 'SomeDbKey' => 100, 'OtherDbKey' => 300,
515 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
517 1 => [ 'AnotherDbKey' => 500 ],
521 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
526 * @dataProvider provideIntWithDbUnsafeVersion
528 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
529 $titleValuesWithThresholds = [
530 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
531 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
532 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
535 $mockDb = $this->getMockDb();
536 $mockDb->expects( $this->any() )
537 ->method( 'makeList' )
538 ->will( $this->returnValue( 'makeList return value' ) );
539 $mockDb->expects( $this->once() )
543 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
544 'makeList return value',
545 $this->isType( 'string' ),
547 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
548 'HAVING' => 'COUNT(*) >= 50',
552 $this->returnValue( [] )
555 $mockCache = $this->getMockCache();
556 $mockCache->expects( $this->never() )->method( 'get' );
557 $mockCache->expects( $this->never() )->method( 'set' );
558 $mockCache->expects( $this->never() )->method( 'delete' );
560 $store = $this->newWatchedItemStore(
561 $this->getMockLoadBalancer( $mockDb ),
566 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
567 1 => [ 'AnotherDbKey' => 0 ],
571 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
575 public function testCountUnreadNotifications() {
576 $user = $this->getMockNonAnonUserWithId( 1 );
578 $mockDb = $this->getMockDb();
579 $mockDb->expects( $this->exactly( 1 ) )
580 ->method( 'selectRowCount' )
585 "wl_notificationtimestamp IS NOT NULL",
588 $this->isType( 'string' )
590 ->will( $this->returnValue( 9 ) );
592 $mockCache = $this->getMockCache();
593 $mockCache->expects( $this->never() )->method( 'set' );
594 $mockCache->expects( $this->never() )->method( 'get' );
595 $mockCache->expects( $this->never() )->method( 'delete' );
597 $store = $this->newWatchedItemStore(
598 $this->getMockLoadBalancer( $mockDb ),
602 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
606 * @dataProvider provideIntWithDbUnsafeVersion
608 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
609 $user = $this->getMockNonAnonUserWithId( 1 );
611 $mockDb = $this->getMockDb();
612 $mockDb->expects( $this->exactly( 1 ) )
613 ->method( 'selectRowCount' )
618 "wl_notificationtimestamp IS NOT NULL",
621 $this->isType( 'string' ),
624 ->will( $this->returnValue( 50 ) );
626 $mockCache = $this->getMockCache();
627 $mockCache->expects( $this->never() )->method( 'set' );
628 $mockCache->expects( $this->never() )->method( 'get' );
629 $mockCache->expects( $this->never() )->method( 'delete' );
631 $store = $this->newWatchedItemStore(
632 $this->getMockLoadBalancer( $mockDb ),
638 $store->countUnreadNotifications( $user, $limit )
643 * @dataProvider provideIntWithDbUnsafeVersion
645 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
646 $user = $this->getMockNonAnonUserWithId( 1 );
648 $mockDb = $this->getMockDb();
649 $mockDb->expects( $this->exactly( 1 ) )
650 ->method( 'selectRowCount' )
655 "wl_notificationtimestamp IS NOT NULL",
658 $this->isType( 'string' ),
661 ->will( $this->returnValue( 9 ) );
663 $mockCache = $this->getMockCache();
664 $mockCache->expects( $this->never() )->method( 'set' );
665 $mockCache->expects( $this->never() )->method( 'get' );
666 $mockCache->expects( $this->never() )->method( 'delete' );
668 $store = $this->newWatchedItemStore(
669 $this->getMockLoadBalancer( $mockDb ),
675 $store->countUnreadNotifications( $user, $limit )
679 public function testDuplicateEntry_nothingToDuplicate() {
680 $mockDb = $this->getMockDb();
681 $mockDb->expects( $this->once() )
687 'wl_notificationtimestamp',
691 'wl_title' => 'Old_Title',
693 'WatchedItemStore::duplicateEntry',
696 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
698 $store = $this->newWatchedItemStore(
699 $this->getMockLoadBalancer( $mockDb ),
700 $this->getMockCache()
703 $store->duplicateEntry(
704 Title
::newFromText( 'Old_Title' ),
705 Title
::newFromText( 'New_Title' )
709 public function testDuplicateEntry_somethingToDuplicate() {
711 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
712 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
715 $mockDb = $this->getMockDb();
716 $mockDb->expects( $this->at( 0 ) )
722 'wl_notificationtimestamp',
726 'wl_title' => 'Old_Title',
729 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
730 $mockDb->expects( $this->at( 1 ) )
731 ->method( 'replace' )
734 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
739 'wl_title' => 'New_Title',
740 'wl_notificationtimestamp' => '20151212010101',
745 'wl_title' => 'New_Title',
746 'wl_notificationtimestamp' => null,
749 $this->isType( 'string' )
752 $mockCache = $this->getMockCache();
753 $mockCache->expects( $this->never() )->method( 'get' );
754 $mockCache->expects( $this->never() )->method( 'delete' );
756 $store = $this->newWatchedItemStore(
757 $this->getMockLoadBalancer( $mockDb ),
761 $store->duplicateEntry(
762 Title
::newFromText( 'Old_Title' ),
763 Title
::newFromText( 'New_Title' )
767 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
768 $mockDb = $this->getMockDb();
769 $mockDb->expects( $this->at( 0 ) )
775 'wl_notificationtimestamp',
779 'wl_title' => 'Old_Title',
782 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
783 $mockDb->expects( $this->at( 1 ) )
789 'wl_notificationtimestamp',
793 'wl_title' => 'Old_Title',
796 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
798 $mockCache = $this->getMockCache();
799 $mockCache->expects( $this->never() )->method( 'get' );
800 $mockCache->expects( $this->never() )->method( 'delete' );
802 $store = $this->newWatchedItemStore(
803 $this->getMockLoadBalancer( $mockDb ),
807 $store->duplicateAllAssociatedEntries(
808 Title
::newFromText( 'Old_Title' ),
809 Title
::newFromText( 'New_Title' )
813 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
815 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
818 $mockDb = $this->getMockDb();
819 $mockDb->expects( $this->at( 0 ) )
825 'wl_notificationtimestamp',
829 'wl_title' => 'Old_Title',
832 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
833 $mockDb->expects( $this->at( 1 ) )
834 ->method( 'replace' )
837 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
842 'wl_title' => 'New_Title',
843 'wl_notificationtimestamp' => '20151212010101',
846 $this->isType( 'string' )
848 $mockDb->expects( $this->at( 2 ) )
854 'wl_notificationtimestamp',
858 'wl_title' => 'Old_Title',
861 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
862 $mockDb->expects( $this->at( 3 ) )
863 ->method( 'replace' )
866 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
871 'wl_title' => 'New_Title',
872 'wl_notificationtimestamp' => '20151212010101',
875 $this->isType( 'string' )
878 $mockCache = $this->getMockCache();
879 $mockCache->expects( $this->never() )->method( 'get' );
880 $mockCache->expects( $this->never() )->method( 'delete' );
882 $store = $this->newWatchedItemStore(
883 $this->getMockLoadBalancer( $mockDb ),
887 $store->duplicateAllAssociatedEntries(
888 Title
::newFromText( 'Old_Title' ),
889 Title
::newFromText( 'New_Title' )
893 public function testAddWatch_nonAnonymousUser() {
894 $mockDb = $this->getMockDb();
895 $mockDb->expects( $this->once() )
903 'wl_title' => 'Some_Page',
904 'wl_notificationtimestamp' => null,
909 $mockCache = $this->getMockCache();
910 $mockCache->expects( $this->once() )
912 ->with( '0:Some_Page:1' );
914 $store = $this->newWatchedItemStore(
915 $this->getMockLoadBalancer( $mockDb ),
920 $this->getMockNonAnonUserWithId( 1 ),
921 Title
::newFromText( 'Some_Page' )
925 public function testAddWatch_anonymousUser() {
926 $mockDb = $this->getMockDb();
927 $mockDb->expects( $this->never() )
928 ->method( 'insert' );
930 $mockCache = $this->getMockCache();
931 $mockCache->expects( $this->never() )
932 ->method( 'delete' );
934 $store = $this->newWatchedItemStore(
935 $this->getMockLoadBalancer( $mockDb ),
940 $this->getAnonUser(),
941 Title
::newFromText( 'Some_Page' )
945 public function testAddWatchBatchForUser_nonAnonymousUser() {
946 $mockDb = $this->getMockDb();
947 $mockDb->expects( $this->once() )
955 'wl_title' => 'Some_Page',
956 'wl_notificationtimestamp' => null,
961 'wl_title' => 'Some_Page',
962 'wl_notificationtimestamp' => null,
967 $mockCache = $this->getMockCache();
968 $mockCache->expects( $this->exactly( 2 ) )
969 ->method( 'delete' );
970 $mockCache->expects( $this->at( 1 ) )
972 ->with( '0:Some_Page:1' );
973 $mockCache->expects( $this->at( 3 ) )
975 ->with( '1:Some_Page:1' );
977 $store = $this->newWatchedItemStore(
978 $this->getMockLoadBalancer( $mockDb ),
982 $mockUser = $this->getMockNonAnonUserWithId( 1 );
985 $store->addWatchBatchForUser(
987 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
992 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
993 $mockDb = $this->getMockDb();
994 $mockDb->expects( $this->never() )
995 ->method( 'insert' );
997 $mockCache = $this->getMockCache();
998 $mockCache->expects( $this->never() )
999 ->method( 'delete' );
1001 $store = $this->newWatchedItemStore(
1002 $this->getMockLoadBalancer( $mockDb ),
1007 $store->addWatchBatchForUser(
1008 $this->getAnonUser(),
1009 [ new TitleValue( 0, 'Other_Page' ) ]
1014 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1015 $user = $this->getMockNonAnonUserWithId( 1 );
1016 $mockDb = $this->getMockDb();
1017 $mockDb->expects( $this->never() )
1018 ->method( 'insert' );
1020 $mockCache = $this->getMockCache();
1021 $mockCache->expects( $this->never() )
1022 ->method( 'delete' );
1024 $store = $this->newWatchedItemStore(
1025 $this->getMockLoadBalancer( $mockDb ),
1030 $store->addWatchBatchForUser( $user, [] )
1034 public function testLoadWatchedItem_existingItem() {
1035 $mockDb = $this->getMockDb();
1036 $mockDb->expects( $this->once() )
1037 ->method( 'selectRow' )
1040 'wl_notificationtimestamp',
1043 'wl_namespace' => 0,
1044 'wl_title' => 'SomeDbKey',
1047 ->will( $this->returnValue(
1048 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1051 $mockCache = $this->getMockCache();
1052 $mockCache->expects( $this->once() )
1058 $store = $this->newWatchedItemStore(
1059 $this->getMockLoadBalancer( $mockDb ),
1063 $watchedItem = $store->loadWatchedItem(
1064 $this->getMockNonAnonUserWithId( 1 ),
1065 new TitleValue( 0, 'SomeDbKey' )
1067 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1068 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1069 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1070 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1073 public function testLoadWatchedItem_noItem() {
1074 $mockDb = $this->getMockDb();
1075 $mockDb->expects( $this->once() )
1076 ->method( 'selectRow' )
1079 'wl_notificationtimestamp',
1082 'wl_namespace' => 0,
1083 'wl_title' => 'SomeDbKey',
1086 ->will( $this->returnValue( [] ) );
1088 $mockCache = $this->getMockCache();
1089 $mockCache->expects( $this->never() )->method( 'get' );
1090 $mockCache->expects( $this->never() )->method( 'delete' );
1092 $store = $this->newWatchedItemStore(
1093 $this->getMockLoadBalancer( $mockDb ),
1098 $store->loadWatchedItem(
1099 $this->getMockNonAnonUserWithId( 1 ),
1100 new TitleValue( 0, 'SomeDbKey' )
1105 public function testLoadWatchedItem_anonymousUser() {
1106 $mockDb = $this->getMockDb();
1107 $mockDb->expects( $this->never() )
1108 ->method( 'selectRow' );
1110 $mockCache = $this->getMockCache();
1111 $mockCache->expects( $this->never() )->method( 'get' );
1112 $mockCache->expects( $this->never() )->method( 'delete' );
1114 $store = $this->newWatchedItemStore(
1115 $this->getMockLoadBalancer( $mockDb ),
1120 $store->loadWatchedItem(
1121 $this->getAnonUser(),
1122 new TitleValue( 0, 'SomeDbKey' )
1127 public function testRemoveWatch_existingItem() {
1128 $mockDb = $this->getMockDb();
1129 $mockDb->expects( $this->once() )
1130 ->method( 'delete' )
1135 'wl_namespace' => 0,
1136 'wl_title' => 'SomeDbKey',
1139 $mockDb->expects( $this->once() )
1140 ->method( 'affectedRows' )
1141 ->will( $this->returnValue( 1 ) );
1143 $mockCache = $this->getMockCache();
1144 $mockCache->expects( $this->never() )->method( 'get' );
1145 $mockCache->expects( $this->once() )
1146 ->method( 'delete' )
1147 ->with( '0:SomeDbKey:1' );
1149 $store = $this->newWatchedItemStore(
1150 $this->getMockLoadBalancer( $mockDb ),
1155 $store->removeWatch(
1156 $this->getMockNonAnonUserWithId( 1 ),
1157 new TitleValue( 0, 'SomeDbKey' )
1162 public function testRemoveWatch_noItem() {
1163 $mockDb = $this->getMockDb();
1164 $mockDb->expects( $this->once() )
1165 ->method( 'delete' )
1170 'wl_namespace' => 0,
1171 'wl_title' => 'SomeDbKey',
1174 $mockDb->expects( $this->once() )
1175 ->method( 'affectedRows' )
1176 ->will( $this->returnValue( 0 ) );
1178 $mockCache = $this->getMockCache();
1179 $mockCache->expects( $this->never() )->method( 'get' );
1180 $mockCache->expects( $this->once() )
1181 ->method( 'delete' )
1182 ->with( '0:SomeDbKey:1' );
1184 $store = $this->newWatchedItemStore(
1185 $this->getMockLoadBalancer( $mockDb ),
1190 $store->removeWatch(
1191 $this->getMockNonAnonUserWithId( 1 ),
1192 new TitleValue( 0, 'SomeDbKey' )
1197 public function testRemoveWatch_anonymousUser() {
1198 $mockDb = $this->getMockDb();
1199 $mockDb->expects( $this->never() )
1200 ->method( 'delete' );
1202 $mockCache = $this->getMockCache();
1203 $mockCache->expects( $this->never() )->method( 'get' );
1204 $mockCache->expects( $this->never() )
1205 ->method( 'delete' );
1207 $store = $this->newWatchedItemStore(
1208 $this->getMockLoadBalancer( $mockDb ),
1213 $store->removeWatch(
1214 $this->getAnonUser(),
1215 new TitleValue( 0, 'SomeDbKey' )
1220 public function testGetWatchedItem_existingItem() {
1221 $mockDb = $this->getMockDb();
1222 $mockDb->expects( $this->once() )
1223 ->method( 'selectRow' )
1226 'wl_notificationtimestamp',
1229 'wl_namespace' => 0,
1230 'wl_title' => 'SomeDbKey',
1233 ->will( $this->returnValue(
1234 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1237 $mockCache = $this->getMockCache();
1238 $mockCache->expects( $this->never() )->method( 'delete' );
1239 $mockCache->expects( $this->once() )
1244 ->will( $this->returnValue( null ) );
1245 $mockCache->expects( $this->once() )
1251 $store = $this->newWatchedItemStore(
1252 $this->getMockLoadBalancer( $mockDb ),
1256 $watchedItem = $store->getWatchedItem(
1257 $this->getMockNonAnonUserWithId( 1 ),
1258 new TitleValue( 0, 'SomeDbKey' )
1260 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1261 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1262 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1263 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1266 public function testGetWatchedItem_cachedItem() {
1267 $mockDb = $this->getMockDb();
1268 $mockDb->expects( $this->never() )
1269 ->method( 'selectRow' );
1271 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1272 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1273 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1275 $mockCache = $this->getMockCache();
1276 $mockCache->expects( $this->never() )->method( 'delete' );
1277 $mockCache->expects( $this->never() )->method( 'set' );
1278 $mockCache->expects( $this->once() )
1283 ->will( $this->returnValue( $cachedItem ) );
1285 $store = $this->newWatchedItemStore(
1286 $this->getMockLoadBalancer( $mockDb ),
1290 $this->assertEquals(
1292 $store->getWatchedItem(
1299 public function testGetWatchedItem_noItem() {
1300 $mockDb = $this->getMockDb();
1301 $mockDb->expects( $this->once() )
1302 ->method( 'selectRow' )
1305 'wl_notificationtimestamp',
1308 'wl_namespace' => 0,
1309 'wl_title' => 'SomeDbKey',
1312 ->will( $this->returnValue( [] ) );
1314 $mockCache = $this->getMockCache();
1315 $mockCache->expects( $this->never() )->method( 'set' );
1316 $mockCache->expects( $this->never() )->method( 'delete' );
1317 $mockCache->expects( $this->once() )
1319 ->with( '0:SomeDbKey:1' )
1320 ->will( $this->returnValue( false ) );
1322 $store = $this->newWatchedItemStore(
1323 $this->getMockLoadBalancer( $mockDb ),
1328 $store->getWatchedItem(
1329 $this->getMockNonAnonUserWithId( 1 ),
1330 new TitleValue( 0, 'SomeDbKey' )
1335 public function testGetWatchedItem_anonymousUser() {
1336 $mockDb = $this->getMockDb();
1337 $mockDb->expects( $this->never() )
1338 ->method( 'selectRow' );
1340 $mockCache = $this->getMockCache();
1341 $mockCache->expects( $this->never() )->method( 'set' );
1342 $mockCache->expects( $this->never() )->method( 'get' );
1343 $mockCache->expects( $this->never() )->method( 'delete' );
1345 $store = $this->newWatchedItemStore(
1346 $this->getMockLoadBalancer( $mockDb ),
1351 $store->getWatchedItem(
1352 $this->getAnonUser(),
1353 new TitleValue( 0, 'SomeDbKey' )
1358 public function testGetWatchedItemsForUser() {
1359 $mockDb = $this->getMockDb();
1360 $mockDb->expects( $this->once() )
1361 ->method( 'select' )
1364 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1367 ->will( $this->returnValue( [
1368 $this->getFakeRow( [
1369 'wl_namespace' => 0,
1370 'wl_title' => 'Foo1',
1371 'wl_notificationtimestamp' => '20151212010101',
1373 $this->getFakeRow( [
1374 'wl_namespace' => 1,
1375 'wl_title' => 'Foo2',
1376 'wl_notificationtimestamp' => null,
1380 $mockCache = $this->getMockCache();
1381 $mockCache->expects( $this->never() )->method( 'delete' );
1382 $mockCache->expects( $this->never() )->method( 'get' );
1383 $mockCache->expects( $this->never() )->method( 'set' );
1385 $store = $this->newWatchedItemStore(
1386 $this->getMockLoadBalancer( $mockDb ),
1389 $user = $this->getMockNonAnonUserWithId( 1 );
1391 $watchedItems = $store->getWatchedItemsForUser( $user );
1393 $this->assertInternalType( 'array', $watchedItems );
1394 $this->assertCount( 2, $watchedItems );
1395 foreach ( $watchedItems as $watchedItem ) {
1396 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1398 $this->assertEquals(
1399 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1402 $this->assertEquals(
1403 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1408 public function provideDbTypes() {
1410 [ false, DB_SLAVE
],
1411 [ true, DB_MASTER
],
1416 * @dataProvider provideDbTypes
1418 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1419 $mockDb = $this->getMockDb();
1420 $mockCache = $this->getMockCache();
1421 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1422 $user = $this->getMockNonAnonUserWithId( 1 );
1424 $mockDb->expects( $this->once() )
1425 ->method( 'select' )
1428 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1430 $this->isType( 'string' ),
1431 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1433 ->will( $this->returnValue( [] ) );
1435 $store = $this->newWatchedItemStore(
1440 $watchedItems = $store->getWatchedItemsForUser(
1442 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1444 $this->assertEquals( [], $watchedItems );
1447 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1448 $store = $this->newWatchedItemStore(
1449 $this->getMockLoadBalancer( $this->getMockDb() ),
1450 $this->getMockCache()
1453 $this->setExpectedException( 'InvalidArgumentException' );
1454 $store->getWatchedItemsForUser(
1455 $this->getMockNonAnonUserWithId( 1 ),
1460 public function testIsWatchedItem_existingItem() {
1461 $mockDb = $this->getMockDb();
1462 $mockDb->expects( $this->once() )
1463 ->method( 'selectRow' )
1466 'wl_notificationtimestamp',
1469 'wl_namespace' => 0,
1470 'wl_title' => 'SomeDbKey',
1473 ->will( $this->returnValue(
1474 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1477 $mockCache = $this->getMockCache();
1478 $mockCache->expects( $this->never() )->method( 'delete' );
1479 $mockCache->expects( $this->once() )
1481 ->with( '0:SomeDbKey:1' )
1482 ->will( $this->returnValue( false ) );
1483 $mockCache->expects( $this->once() )
1489 $store = $this->newWatchedItemStore(
1490 $this->getMockLoadBalancer( $mockDb ),
1496 $this->getMockNonAnonUserWithId( 1 ),
1497 new TitleValue( 0, 'SomeDbKey' )
1502 public function testIsWatchedItem_noItem() {
1503 $mockDb = $this->getMockDb();
1504 $mockDb->expects( $this->once() )
1505 ->method( 'selectRow' )
1508 'wl_notificationtimestamp',
1511 'wl_namespace' => 0,
1512 'wl_title' => 'SomeDbKey',
1515 ->will( $this->returnValue( [] ) );
1517 $mockCache = $this->getMockCache();
1518 $mockCache->expects( $this->never() )->method( 'set' );
1519 $mockCache->expects( $this->never() )->method( 'delete' );
1520 $mockCache->expects( $this->once() )
1522 ->with( '0:SomeDbKey:1' )
1523 ->will( $this->returnValue( false ) );
1525 $store = $this->newWatchedItemStore(
1526 $this->getMockLoadBalancer( $mockDb ),
1532 $this->getMockNonAnonUserWithId( 1 ),
1533 new TitleValue( 0, 'SomeDbKey' )
1538 public function testIsWatchedItem_anonymousUser() {
1539 $mockDb = $this->getMockDb();
1540 $mockDb->expects( $this->never() )
1541 ->method( 'selectRow' );
1543 $mockCache = $this->getMockCache();
1544 $mockCache->expects( $this->never() )->method( 'set' );
1545 $mockCache->expects( $this->never() )->method( 'get' );
1546 $mockCache->expects( $this->never() )->method( 'delete' );
1548 $store = $this->newWatchedItemStore(
1549 $this->getMockLoadBalancer( $mockDb ),
1555 $this->getAnonUser(),
1556 new TitleValue( 0, 'SomeDbKey' )
1561 public function testGetNotificationTimestampsBatch() {
1563 new TitleValue( 0, 'SomeDbKey' ),
1564 new TitleValue( 1, 'AnotherDbKey' ),
1567 $mockDb = $this->getMockDb();
1569 $this->getFakeRow( [
1570 'wl_namespace' => 0,
1571 'wl_title' => 'SomeDbKey',
1572 'wl_notificationtimestamp' => '20151212010101',
1576 'wl_namespace' => 1,
1577 'wl_title' => 'AnotherDbKey',
1578 'wl_notificationtimestamp' => null,
1583 $mockDb->expects( $this->once() )
1584 ->method( 'makeWhereFrom2d' )
1586 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1587 $this->isType( 'string' ),
1588 $this->isType( 'string' )
1590 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1591 $mockDb->expects( $this->once() )
1592 ->method( 'select' )
1595 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1597 'makeWhereFrom2d return value',
1600 $this->isType( 'string' )
1602 ->will( $this->returnValue( $dbResult ) );
1604 $mockCache = $this->getMockCache();
1605 $mockCache->expects( $this->exactly( 2 ) )
1608 [ '0:SomeDbKey:1' ],
1609 [ '1:AnotherDbKey:1' ]
1611 ->will( $this->returnValue( null ) );
1612 $mockCache->expects( $this->never() )->method( 'set' );
1613 $mockCache->expects( $this->never() )->method( 'delete' );
1615 $store = $this->newWatchedItemStore(
1616 $this->getMockLoadBalancer( $mockDb ),
1620 $this->assertEquals(
1622 0 => [ 'SomeDbKey' => '20151212010101', ],
1623 1 => [ 'AnotherDbKey' => null, ],
1625 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1629 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1631 new TitleValue( 0, 'OtherDbKey' ),
1634 $mockDb = $this->getMockDb();
1636 $mockDb->expects( $this->once() )
1637 ->method( 'makeWhereFrom2d' )
1639 [ [ 'OtherDbKey' => 1 ] ],
1640 $this->isType( 'string' ),
1641 $this->isType( 'string' )
1643 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1644 $mockDb->expects( $this->once() )
1645 ->method( 'select' )
1648 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1650 'makeWhereFrom2d return value',
1653 $this->isType( 'string' )
1655 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1657 $mockCache = $this->getMockCache();
1658 $mockCache->expects( $this->once() )
1660 ->with( '0:OtherDbKey:1' )
1661 ->will( $this->returnValue( null ) );
1662 $mockCache->expects( $this->never() )->method( 'set' );
1663 $mockCache->expects( $this->never() )->method( 'delete' );
1665 $store = $this->newWatchedItemStore(
1666 $this->getMockLoadBalancer( $mockDb ),
1670 $this->assertEquals(
1672 0 => [ 'OtherDbKey' => false, ],
1674 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1678 public function testGetNotificationTimestampsBatch_cachedItem() {
1680 new TitleValue( 0, 'SomeDbKey' ),
1681 new TitleValue( 1, 'AnotherDbKey' ),
1684 $user = $this->getMockNonAnonUserWithId( 1 );
1685 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1687 $mockDb = $this->getMockDb();
1689 $mockDb->expects( $this->once() )
1690 ->method( 'makeWhereFrom2d' )
1692 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1693 $this->isType( 'string' ),
1694 $this->isType( 'string' )
1696 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1697 $mockDb->expects( $this->once() )
1698 ->method( 'select' )
1701 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1703 'makeWhereFrom2d return value',
1706 $this->isType( 'string' )
1708 ->will( $this->returnValue( [
1710 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1714 $mockCache = $this->getMockCache();
1715 $mockCache->expects( $this->at( 1 ) )
1717 ->with( '0:SomeDbKey:1' )
1718 ->will( $this->returnValue( $cachedItem ) );
1719 $mockCache->expects( $this->at( 3 ) )
1721 ->with( '1:AnotherDbKey:1' )
1722 ->will( $this->returnValue( null ) );
1723 $mockCache->expects( $this->never() )->method( 'set' );
1724 $mockCache->expects( $this->never() )->method( 'delete' );
1726 $store = $this->newWatchedItemStore(
1727 $this->getMockLoadBalancer( $mockDb ),
1731 $this->assertEquals(
1733 0 => [ 'SomeDbKey' => '20151212010101', ],
1734 1 => [ 'AnotherDbKey' => null, ],
1736 $store->getNotificationTimestampsBatch( $user, $targets )
1740 public function testGetNotificationTimestampsBatch_allItemsCached() {
1742 new TitleValue( 0, 'SomeDbKey' ),
1743 new TitleValue( 1, 'AnotherDbKey' ),
1746 $user = $this->getMockNonAnonUserWithId( 1 );
1748 new WatchedItem( $user, $targets[0], '20151212010101' ),
1749 new WatchedItem( $user, $targets[1], null ),
1751 $mockDb = $this->getMockDb();
1752 $mockDb->expects( $this->never() )->method( $this->anything() );
1754 $mockCache = $this->getMockCache();
1755 $mockCache->expects( $this->at( 1 ) )
1757 ->with( '0:SomeDbKey:1' )
1758 ->will( $this->returnValue( $cachedItems[0] ) );
1759 $mockCache->expects( $this->at( 3 ) )
1761 ->with( '1:AnotherDbKey:1' )
1762 ->will( $this->returnValue( $cachedItems[1] ) );
1763 $mockCache->expects( $this->never() )->method( 'set' );
1764 $mockCache->expects( $this->never() )->method( 'delete' );
1766 $store = $this->newWatchedItemStore(
1767 $this->getMockLoadBalancer( $mockDb ),
1771 $this->assertEquals(
1773 0 => [ 'SomeDbKey' => '20151212010101', ],
1774 1 => [ 'AnotherDbKey' => null, ],
1776 $store->getNotificationTimestampsBatch( $user, $targets )
1780 public function testGetNotificationTimestampsBatch_anonymousUser() {
1782 new TitleValue( 0, 'SomeDbKey' ),
1783 new TitleValue( 1, 'AnotherDbKey' ),
1786 $mockDb = $this->getMockDb();
1787 $mockDb->expects( $this->never() )->method( $this->anything() );
1789 $mockCache = $this->getMockCache();
1790 $mockCache->expects( $this->never() )->method( $this->anything() );
1792 $store = $this->newWatchedItemStore(
1793 $this->getMockLoadBalancer( $mockDb ),
1797 $this->assertEquals(
1799 0 => [ 'SomeDbKey' => false, ],
1800 1 => [ 'AnotherDbKey' => false, ],
1802 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1806 public function testResetNotificationTimestamp_anonymousUser() {
1807 $mockDb = $this->getMockDb();
1808 $mockDb->expects( $this->never() )
1809 ->method( 'selectRow' );
1811 $mockCache = $this->getMockCache();
1812 $mockCache->expects( $this->never() )->method( 'get' );
1813 $mockCache->expects( $this->never() )->method( 'set' );
1814 $mockCache->expects( $this->never() )->method( 'delete' );
1816 $store = $this->newWatchedItemStore(
1817 $this->getMockLoadBalancer( $mockDb ),
1822 $store->resetNotificationTimestamp(
1823 $this->getAnonUser(),
1824 Title
::newFromText( 'SomeDbKey' )
1829 public function testResetNotificationTimestamp_noItem() {
1830 $mockDb = $this->getMockDb();
1831 $mockDb->expects( $this->once() )
1832 ->method( 'selectRow' )
1835 'wl_notificationtimestamp',
1838 'wl_namespace' => 0,
1839 'wl_title' => 'SomeDbKey',
1842 ->will( $this->returnValue( [] ) );
1844 $mockCache = $this->getMockCache();
1845 $mockCache->expects( $this->never() )->method( 'get' );
1846 $mockCache->expects( $this->never() )->method( 'set' );
1847 $mockCache->expects( $this->never() )->method( 'delete' );
1849 $store = $this->newWatchedItemStore(
1850 $this->getMockLoadBalancer( $mockDb ),
1855 $store->resetNotificationTimestamp(
1856 $this->getMockNonAnonUserWithId( 1 ),
1857 Title
::newFromText( 'SomeDbKey' )
1862 public function testResetNotificationTimestamp_item() {
1863 $user = $this->getMockNonAnonUserWithId( 1 );
1864 $title = Title
::newFromText( 'SomeDbKey' );
1866 $mockDb = $this->getMockDb();
1867 $mockDb->expects( $this->once() )
1868 ->method( 'selectRow' )
1871 'wl_notificationtimestamp',
1874 'wl_namespace' => 0,
1875 'wl_title' => 'SomeDbKey',
1878 ->will( $this->returnValue(
1879 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1882 $mockCache = $this->getMockCache();
1883 $mockCache->expects( $this->never() )->method( 'get' );
1884 $mockCache->expects( $this->once() )
1888 $this->isInstanceOf( WatchedItem
::class )
1890 $mockCache->expects( $this->once() )
1891 ->method( 'delete' )
1892 ->with( '0:SomeDbKey:1' );
1894 $store = $this->newWatchedItemStore(
1895 $this->getMockLoadBalancer( $mockDb ),
1899 // Note: This does not actually assert the job is correct
1900 $callableCallCounter = 0;
1901 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1902 $callableCallCounter++
;
1903 $this->assertInternalType( 'callable', $callable );
1905 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1908 $store->resetNotificationTimestamp(
1913 $this->assertEquals( 1, $callableCallCounter );
1915 ScopedCallback
::consume( $scopedOverride );
1918 public function testResetNotificationTimestamp_noItemForced() {
1919 $user = $this->getMockNonAnonUserWithId( 1 );
1920 $title = Title
::newFromText( 'SomeDbKey' );
1922 $mockDb = $this->getMockDb();
1923 $mockDb->expects( $this->never() )
1924 ->method( 'selectRow' );
1926 $mockCache = $this->getMockCache();
1927 $mockDb->expects( $this->never() )
1929 $mockDb->expects( $this->never() )
1931 $mockDb->expects( $this->never() )
1932 ->method( 'delete' );
1934 $store = $this->newWatchedItemStore(
1935 $this->getMockLoadBalancer( $mockDb ),
1939 // Note: This does not actually assert the job is correct
1940 $callableCallCounter = 0;
1941 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1942 $callableCallCounter++
;
1943 $this->assertInternalType( 'callable', $callable );
1945 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1948 $store->resetNotificationTimestamp(
1954 $this->assertEquals( 1, $callableCallCounter );
1956 ScopedCallback
::consume( $scopedOverride );
1963 * @return PHPUnit_Framework_MockObject_MockObject|Title
1965 private function getMockTitle( $text, $ns = 0 ) {
1966 $title = $this->getMock( Title
::class );
1967 $title->expects( $this->any() )
1968 ->method( 'getText' )
1969 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1970 $title->expects( $this->any() )
1971 ->method( 'getDbKey' )
1972 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1973 $title->expects( $this->any() )
1974 ->method( 'getNamespace' )
1975 ->will( $this->returnValue( $ns ) );
1979 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1980 $user = $this->getMockNonAnonUserWithId( 1 );
1982 $title = $this->getMockTitle( 'SomeTitle' );
1983 $title->expects( $this->once() )
1984 ->method( 'getNextRevisionID' )
1986 ->will( $this->returnValue( false ) );
1988 $mockDb = $this->getMockDb();
1989 $mockDb->expects( $this->never() )
1990 ->method( 'selectRow' );
1992 $mockCache = $this->getMockCache();
1993 $mockDb->expects( $this->never() )
1995 $mockDb->expects( $this->never() )
1997 $mockDb->expects( $this->never() )
1998 ->method( 'delete' );
2000 $store = $this->newWatchedItemStore(
2001 $this->getMockLoadBalancer( $mockDb ),
2005 // Note: This does not actually assert the job is correct
2006 $callableCallCounter = 0;
2007 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2008 function( $callable ) use ( &$callableCallCounter ) {
2009 $callableCallCounter++
;
2010 $this->assertInternalType( 'callable', $callable );
2015 $store->resetNotificationTimestamp(
2022 $this->assertEquals( 1, $callableCallCounter );
2024 ScopedCallback
::consume( $scopedOverride );
2027 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2028 $user = $this->getMockNonAnonUserWithId( 1 );
2030 $title = $this->getMockTitle( 'SomeDbKey' );
2031 $title->expects( $this->once() )
2032 ->method( 'getNextRevisionID' )
2034 ->will( $this->returnValue( 33 ) );
2036 $mockDb = $this->getMockDb();
2037 $mockDb->expects( $this->once() )
2038 ->method( 'selectRow' )
2041 'wl_notificationtimestamp',
2044 'wl_namespace' => 0,
2045 'wl_title' => 'SomeDbKey',
2048 ->will( $this->returnValue(
2049 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2052 $mockCache = $this->getMockCache();
2053 $mockDb->expects( $this->never() )
2055 $mockDb->expects( $this->never() )
2057 $mockDb->expects( $this->never() )
2058 ->method( 'delete' );
2060 $store = $this->newWatchedItemStore(
2061 $this->getMockLoadBalancer( $mockDb ),
2065 // Note: This does not actually assert the job is correct
2066 $addUpdateCallCounter = 0;
2067 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2068 function( $callable ) use ( &$addUpdateCallCounter ) {
2069 $addUpdateCallCounter++
;
2070 $this->assertInternalType( 'callable', $callable );
2074 $getTimestampCallCounter = 0;
2075 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2076 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2077 $getTimestampCallCounter++
;
2078 $this->assertEquals( $title, $titleParam );
2079 $this->assertEquals( $oldid, $oldidParam );
2084 $store->resetNotificationTimestamp(
2091 $this->assertEquals( 1, $addUpdateCallCounter );
2092 $this->assertEquals( 1, $getTimestampCallCounter );
2094 ScopedCallback
::consume( $scopedOverrideDeferred );
2095 ScopedCallback
::consume( $scopedOverrideRevision );
2098 public function testUpdateNotificationTimestamp_watchersExist() {
2099 $mockDb = $this->getMockDb();
2100 $mockDb->expects( $this->once() )
2101 ->method( 'select' )
2107 'wl_namespace' => 0,
2108 'wl_title' => 'SomeDbKey',
2109 'wl_notificationtimestamp IS NULL'
2113 $this->returnValue( [
2114 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2115 $this->getFakeRow( [ 'wl_user' => '3' ] )
2118 $mockDb->expects( $this->once() )
2119 ->method( 'onTransactionIdle' )
2120 ->with( $this->isType( 'callable' ) )
2121 ->will( $this->returnCallback( function( $callable ) {
2124 $mockDb->expects( $this->once() )
2125 ->method( 'update' )
2128 [ 'wl_notificationtimestamp' => null ],
2130 'wl_user' => [ 2, 3 ],
2131 'wl_namespace' => 0,
2132 'wl_title' => 'SomeDbKey',
2136 $mockCache = $this->getMockCache();
2137 $mockCache->expects( $this->never() )->method( 'set' );
2138 $mockCache->expects( $this->never() )->method( 'get' );
2139 $mockCache->expects( $this->never() )->method( 'delete' );
2141 $store = $this->newWatchedItemStore(
2142 $this->getMockLoadBalancer( $mockDb ),
2146 $this->assertEquals(
2148 $store->updateNotificationTimestamp(
2149 $this->getMockNonAnonUserWithId( 1 ),
2150 new TitleValue( 0, 'SomeDbKey' ),
2156 public function testUpdateNotificationTimestamp_noWatchers() {
2157 $mockDb = $this->getMockDb();
2158 $mockDb->expects( $this->once() )
2159 ->method( 'select' )
2165 'wl_namespace' => 0,
2166 'wl_title' => 'SomeDbKey',
2167 'wl_notificationtimestamp IS NULL'
2171 $this->returnValue( [] )
2173 $mockDb->expects( $this->never() )
2174 ->method( 'onTransactionIdle' );
2175 $mockDb->expects( $this->never() )
2176 ->method( 'update' );
2178 $mockCache = $this->getMockCache();
2179 $mockCache->expects( $this->never() )->method( 'set' );
2180 $mockCache->expects( $this->never() )->method( 'get' );
2181 $mockCache->expects( $this->never() )->method( 'delete' );
2183 $store = $this->newWatchedItemStore(
2184 $this->getMockLoadBalancer( $mockDb ),
2188 $watchers = $store->updateNotificationTimestamp(
2189 $this->getMockNonAnonUserWithId( 1 ),
2190 new TitleValue( 0, 'SomeDbKey' ),
2193 $this->assertInternalType( 'array', $watchers );
2194 $this->assertEmpty( $watchers );
2197 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2198 $user = $this->getMockNonAnonUserWithId( 1 );
2199 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2201 $mockDb = $this->getMockDb();
2202 $mockDb->expects( $this->once() )
2203 ->method( 'selectRow' )
2204 ->will( $this->returnValue(
2205 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2207 $mockDb->expects( $this->once() )
2208 ->method( 'select' )
2210 $this->returnValue( [
2211 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2212 $this->getFakeRow( [ 'wl_user' => '3' ] )
2215 $mockDb->expects( $this->once() )
2216 ->method( 'onTransactionIdle' )
2217 ->with( $this->isType( 'callable' ) )
2218 ->will( $this->returnCallback( function( $callable ) {
2221 $mockDb->expects( $this->once() )
2222 ->method( 'update' );
2224 $mockCache = $this->getMockCache();
2225 $mockCache->expects( $this->once() )
2227 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2228 $mockCache->expects( $this->once() )
2230 ->with( '0:SomeDbKey:1' );
2231 $mockCache->expects( $this->once() )
2232 ->method( 'delete' )
2233 ->with( '0:SomeDbKey:1' );
2235 $store = $this->newWatchedItemStore(
2236 $this->getMockLoadBalancer( $mockDb ),
2240 // This will add the item to the cache
2241 $store->getWatchedItem( $user, $titleValue );
2243 $store->updateNotificationTimestamp(
2244 $this->getMockNonAnonUserWithId( 1 ),