6 * @covers WatchedItemStore
8 class WatchedItemStoreUnitTest
extends PHPUnit_Framework_TestCase
{
11 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
13 private function getMockDb() {
14 return $this->getMock( IDatabase
::class );
18 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
20 private function getMockLoadBalancer( $mockDb, $expectedConnectionType = null ) {
21 $mock = $this->getMockBuilder( LoadBalancer
::class )
22 ->disableOriginalConstructor()
24 if ( $expectedConnectionType !== null ) {
25 $mock->expects( $this->any() )
26 ->method( 'getConnection' )
27 ->with( $expectedConnectionType )
28 ->will( $this->returnValue( $mockDb ) );
30 $mock->expects( $this->any() )
31 ->method( 'getConnection' )
32 ->will( $this->returnValue( $mockDb ) );
34 $mock->expects( $this->any() )
35 ->method( 'getReadOnlyReason' )
36 ->will( $this->returnValue( false ) );
41 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
43 private function getMockCache() {
44 $mock = $this->getMockBuilder( HashBagOStuff
::class )
45 ->disableOriginalConstructor()
47 $mock->expects( $this->any() )
49 ->will( $this->returnCallback( function() {
50 return implode( ':', func_get_args() );
57 * @return PHPUnit_Framework_MockObject_MockObject|User
59 private function getMockNonAnonUserWithId( $id ) {
60 $mock = $this->getMock( User
::class );
61 $mock->expects( $this->any() )
63 ->will( $this->returnValue( false ) );
64 $mock->expects( $this->any() )
66 ->will( $this->returnValue( $id ) );
73 private function getAnonUser() {
74 return User
::newFromName( 'Anon_User' );
77 private function getFakeRow( array $rowValues ) {
78 $fakeRow = new stdClass();
79 foreach ( $rowValues as $valueName => $value ) {
80 $fakeRow->$valueName = $value;
85 public function testGetDefaultInstance() {
86 $instanceOne = WatchedItemStore
::getDefaultInstance();
87 $instanceTwo = WatchedItemStore
::getDefaultInstance();
88 $this->assertSame( $instanceOne, $instanceTwo );
91 public function testOverrideDefaultInstance() {
92 $instance = WatchedItemStore
::getDefaultInstance();
93 $scopedOverride = $instance->overrideDefaultInstance( null );
95 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
97 unset( $scopedOverride );
99 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
102 public function testCountWatchedItems() {
103 $user = $this->getMockNonAnonUserWithId( 1 );
105 $mockDb = $this->getMockDb();
106 $mockDb->expects( $this->exactly( 1 ) )
107 ->method( 'selectField' )
112 'wl_user' => $user->getId(),
114 $this->isType( 'string' )
116 ->will( $this->returnValue( 12 ) );
118 $mockCache = $this->getMockCache();
119 $mockCache->expects( $this->never() )->method( 'get' );
120 $mockCache->expects( $this->never() )->method( 'set' );
121 $mockCache->expects( $this->never() )->method( 'delete' );
123 $store = new WatchedItemStore(
124 $this->getMockLoadBalancer( $mockDb ),
128 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
131 public function testCountWatchers() {
132 $titleValue = new TitleValue( 0, 'SomeDbKey' );
134 $mockDb = $this->getMockDb();
135 $mockDb->expects( $this->exactly( 1 ) )
136 ->method( 'selectField' )
141 'wl_namespace' => $titleValue->getNamespace(),
142 'wl_title' => $titleValue->getDBkey(),
144 $this->isType( 'string' )
146 ->will( $this->returnValue( 7 ) );
148 $mockCache = $this->getMockCache();
149 $mockCache->expects( $this->never() )->method( 'get' );
150 $mockCache->expects( $this->never() )->method( 'set' );
151 $mockCache->expects( $this->never() )->method( 'delete' );
153 $store = new WatchedItemStore(
154 $this->getMockLoadBalancer( $mockDb ),
158 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
161 public function testCountWatchersMultiple() {
163 new TitleValue( 0, 'SomeDbKey' ),
164 new TitleValue( 0, 'OtherDbKey' ),
165 new TitleValue( 1, 'AnotherDbKey' ),
168 $mockDb = $this->getMockDb();
171 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
172 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
173 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
176 $mockDb->expects( $this->once() )
177 ->method( 'makeWhereFrom2d' )
179 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
180 $this->isType( 'string' ),
181 $this->isType( 'string' )
183 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
184 $mockDb->expects( $this->once() )
188 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
189 [ 'makeWhereFrom2d return value' ],
190 $this->isType( 'string' ),
192 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
196 $this->returnValue( $dbResult )
199 $mockCache = $this->getMockCache();
200 $mockCache->expects( $this->never() )->method( 'get' );
201 $mockCache->expects( $this->never() )->method( 'set' );
202 $mockCache->expects( $this->never() )->method( 'delete' );
204 $store = new WatchedItemStore(
205 $this->getMockLoadBalancer( $mockDb ),
210 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
211 1 => [ 'AnotherDbKey' => 500 ],
213 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
216 public function provideIntWithDbUnsafeVersion() {
219 [ "50; DROP TABLE watchlist;\n--" ],
224 * @dataProvider provideIntWithDbUnsafeVersion
226 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
228 new TitleValue( 0, 'SomeDbKey' ),
229 new TitleValue( 0, 'OtherDbKey' ),
230 new TitleValue( 1, 'AnotherDbKey' ),
233 $mockDb = $this->getMockDb();
236 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
237 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
238 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
241 $mockDb->expects( $this->once() )
242 ->method( 'makeWhereFrom2d' )
244 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
245 $this->isType( 'string' ),
246 $this->isType( 'string' )
248 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
249 $mockDb->expects( $this->once() )
253 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
254 [ 'makeWhereFrom2d return value' ],
255 $this->isType( 'string' ),
257 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
258 'HAVING' => 'COUNT(*) >= 50',
262 $this->returnValue( $dbResult )
265 $mockCache = $this->getMockCache();
266 $mockCache->expects( $this->never() )->method( 'get' );
267 $mockCache->expects( $this->never() )->method( 'set' );
268 $mockCache->expects( $this->never() )->method( 'delete' );
270 $store = new WatchedItemStore(
271 $this->getMockLoadBalancer( $mockDb ),
276 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
277 1 => [ 'AnotherDbKey' => 500 ],
281 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
285 public function testCountVisitingWatchers() {
286 $titleValue = new TitleValue( 0, 'SomeDbKey' );
288 $mockDb = $this->getMockDb();
289 $mockDb->expects( $this->exactly( 1 ) )
290 ->method( 'selectField' )
295 'wl_namespace' => $titleValue->getNamespace(),
296 'wl_title' => $titleValue->getDBkey(),
297 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
299 $this->isType( 'string' )
301 ->will( $this->returnValue( 7 ) );
302 $mockDb->expects( $this->exactly( 1 ) )
303 ->method( 'addQuotes' )
304 ->will( $this->returnCallback( function( $value ) {
307 $mockDb->expects( $this->exactly( 1 ) )
308 ->method( 'timestamp' )
309 ->will( $this->returnCallback( function( $value ) {
310 return 'TS' . $value . 'TS';
313 $mockCache = $this->getMockCache();
314 $mockCache->expects( $this->never() )->method( 'set' );
315 $mockCache->expects( $this->never() )->method( 'get' );
316 $mockCache->expects( $this->never() )->method( 'delete' );
318 $store = new WatchedItemStore(
319 $this->getMockLoadBalancer( $mockDb ),
323 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
326 public function testCountVisitingWatchersMultiple() {
327 $titleValuesWithThresholds = [
328 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
329 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
330 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
334 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
335 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
336 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
338 $mockDb = $this->getMockDb();
339 $mockDb->expects( $this->exactly( 2 * 3 ) )
340 ->method( 'addQuotes' )
341 ->will( $this->returnCallback( function( $value ) {
344 $mockDb->expects( $this->exactly( 3 ) )
345 ->method( 'timestamp' )
346 ->will( $this->returnCallback( function( $value ) {
347 return 'TS' . $value . 'TS';
349 $mockDb->expects( $this->any() )
350 ->method( 'makeList' )
352 $this->isType( 'array' ),
353 $this->isType( 'int' )
355 ->will( $this->returnCallback( function( $a, $conj ) {
356 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
357 return join( $sqlConj, array_map( function( $s ) {
358 return '(' . $s . ')';
362 $mockDb->expects( $this->never() )
363 ->method( 'makeWhereFrom2d' );
366 '((wl_namespace = 0) AND (' .
367 "(((wl_title = 'SomeDbKey') AND (" .
368 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
370 "(wl_title = 'OtherDbKey') AND (" .
371 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
373 ') OR ((wl_namespace = 1) AND (' .
374 "(((wl_title = 'AnotherDbKey') AND (".
375 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
377 $mockDb->expects( $this->once() )
381 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
383 $this->isType( 'string' ),
385 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
389 $this->returnValue( $dbResult )
392 $mockCache = $this->getMockCache();
393 $mockCache->expects( $this->never() )->method( 'get' );
394 $mockCache->expects( $this->never() )->method( 'set' );
395 $mockCache->expects( $this->never() )->method( 'delete' );
397 $store = new WatchedItemStore(
398 $this->getMockLoadBalancer( $mockDb ),
403 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
404 1 => [ 'AnotherDbKey' => 500 ],
408 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
412 public function testCountVisitingWatchersMultiple_withMissingTargets() {
413 $titleValuesWithThresholds = [
414 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
415 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
416 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
417 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
418 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
422 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
423 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
424 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
426 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
429 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
432 $mockDb = $this->getMockDb();
433 $mockDb->expects( $this->exactly( 2 * 3 ) )
434 ->method( 'addQuotes' )
435 ->will( $this->returnCallback( function( $value ) {
438 $mockDb->expects( $this->exactly( 3 ) )
439 ->method( 'timestamp' )
440 ->will( $this->returnCallback( function( $value ) {
441 return 'TS' . $value . 'TS';
443 $mockDb->expects( $this->any() )
444 ->method( 'makeList' )
446 $this->isType( 'array' ),
447 $this->isType( 'int' )
449 ->will( $this->returnCallback( function( $a, $conj ) {
450 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
451 return join( $sqlConj, array_map( function( $s ) {
452 return '(' . $s . ')';
456 $mockDb->expects( $this->once() )
457 ->method( 'makeWhereFrom2d' )
459 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
460 $this->isType( 'string' ),
461 $this->isType( 'string' )
463 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
466 '((wl_namespace = 0) AND (' .
467 "(((wl_title = 'SomeDbKey') AND (" .
468 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
470 "(wl_title = 'OtherDbKey') AND (" .
471 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
473 ') OR ((wl_namespace = 1) AND (' .
474 "(((wl_title = 'AnotherDbKey') AND (".
475 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
478 '(makeWhereFrom2d return value)';
479 $mockDb->expects( $this->once() )
483 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
485 $this->isType( 'string' ),
487 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
491 $this->returnValue( $dbResult )
494 $mockCache = $this->getMockCache();
495 $mockCache->expects( $this->never() )->method( 'get' );
496 $mockCache->expects( $this->never() )->method( 'set' );
497 $mockCache->expects( $this->never() )->method( 'delete' );
499 $store = new WatchedItemStore(
500 $this->getMockLoadBalancer( $mockDb ),
506 'SomeDbKey' => 100, 'OtherDbKey' => 300,
507 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
509 1 => [ 'AnotherDbKey' => 500 ],
513 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
518 * @dataProvider provideIntWithDbUnsafeVersion
520 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
521 $titleValuesWithThresholds = [
522 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
523 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
524 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
527 $mockDb = $this->getMockDb();
528 $mockDb->expects( $this->any() )
529 ->method( 'makeList' )
530 ->will( $this->returnValue( 'makeList return value' ) );
531 $mockDb->expects( $this->once() )
535 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
536 'makeList return value',
537 $this->isType( 'string' ),
539 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
540 'HAVING' => 'COUNT(*) >= 50',
544 $this->returnValue( [] )
547 $mockCache = $this->getMockCache();
548 $mockCache->expects( $this->never() )->method( 'get' );
549 $mockCache->expects( $this->never() )->method( 'set' );
550 $mockCache->expects( $this->never() )->method( 'delete' );
552 $store = new WatchedItemStore(
553 $this->getMockLoadBalancer( $mockDb ),
558 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
559 1 => [ 'AnotherDbKey' => 0 ],
563 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
567 public function testCountUnreadNotifications() {
568 $user = $this->getMockNonAnonUserWithId( 1 );
570 $mockDb = $this->getMockDb();
571 $mockDb->expects( $this->exactly( 1 ) )
572 ->method( 'selectRowCount' )
577 "wl_notificationtimestamp IS NOT NULL",
580 $this->isType( 'string' )
582 ->will( $this->returnValue( 9 ) );
584 $mockCache = $this->getMockCache();
585 $mockCache->expects( $this->never() )->method( 'set' );
586 $mockCache->expects( $this->never() )->method( 'get' );
587 $mockCache->expects( $this->never() )->method( 'delete' );
589 $store = new WatchedItemStore(
590 $this->getMockLoadBalancer( $mockDb ),
594 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
598 * @dataProvider provideIntWithDbUnsafeVersion
600 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
601 $user = $this->getMockNonAnonUserWithId( 1 );
603 $mockDb = $this->getMockDb();
604 $mockDb->expects( $this->exactly( 1 ) )
605 ->method( 'selectRowCount' )
610 "wl_notificationtimestamp IS NOT NULL",
613 $this->isType( 'string' ),
616 ->will( $this->returnValue( 50 ) );
618 $mockCache = $this->getMockCache();
619 $mockCache->expects( $this->never() )->method( 'set' );
620 $mockCache->expects( $this->never() )->method( 'get' );
621 $mockCache->expects( $this->never() )->method( 'delete' );
623 $store = new WatchedItemStore(
624 $this->getMockLoadBalancer( $mockDb ),
630 $store->countUnreadNotifications( $user, $limit )
635 * @dataProvider provideIntWithDbUnsafeVersion
637 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
638 $user = $this->getMockNonAnonUserWithId( 1 );
640 $mockDb = $this->getMockDb();
641 $mockDb->expects( $this->exactly( 1 ) )
642 ->method( 'selectRowCount' )
647 "wl_notificationtimestamp IS NOT NULL",
650 $this->isType( 'string' ),
653 ->will( $this->returnValue( 9 ) );
655 $mockCache = $this->getMockCache();
656 $mockCache->expects( $this->never() )->method( 'set' );
657 $mockCache->expects( $this->never() )->method( 'get' );
658 $mockCache->expects( $this->never() )->method( 'delete' );
660 $store = new WatchedItemStore(
661 $this->getMockLoadBalancer( $mockDb ),
667 $store->countUnreadNotifications( $user, $limit )
671 public function testDuplicateEntry_nothingToDuplicate() {
672 $mockDb = $this->getMockDb();
673 $mockDb->expects( $this->once() )
679 'wl_notificationtimestamp',
683 'wl_title' => 'Old_Title',
685 'WatchedItemStore::duplicateEntry',
688 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
690 $store = new WatchedItemStore(
691 $this->getMockLoadBalancer( $mockDb ),
692 $this->getMockCache()
695 $store->duplicateEntry(
696 Title
::newFromText( 'Old_Title' ),
697 Title
::newFromText( 'New_Title' )
701 public function testDuplicateEntry_somethingToDuplicate() {
703 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
704 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
707 $mockDb = $this->getMockDb();
708 $mockDb->expects( $this->at( 0 ) )
714 'wl_notificationtimestamp',
718 'wl_title' => 'Old_Title',
721 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
722 $mockDb->expects( $this->at( 1 ) )
723 ->method( 'replace' )
726 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
731 'wl_title' => 'New_Title',
732 'wl_notificationtimestamp' => '20151212010101',
737 'wl_title' => 'New_Title',
738 'wl_notificationtimestamp' => null,
741 $this->isType( 'string' )
744 $mockCache = $this->getMockCache();
745 $mockCache->expects( $this->never() )->method( 'get' );
746 $mockCache->expects( $this->never() )->method( 'delete' );
748 $store = new WatchedItemStore(
749 $this->getMockLoadBalancer( $mockDb ),
753 $store->duplicateEntry(
754 Title
::newFromText( 'Old_Title' ),
755 Title
::newFromText( 'New_Title' )
759 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
760 $mockDb = $this->getMockDb();
761 $mockDb->expects( $this->at( 0 ) )
767 'wl_notificationtimestamp',
771 'wl_title' => 'Old_Title',
774 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
775 $mockDb->expects( $this->at( 1 ) )
781 'wl_notificationtimestamp',
785 'wl_title' => 'Old_Title',
788 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
790 $mockCache = $this->getMockCache();
791 $mockCache->expects( $this->never() )->method( 'get' );
792 $mockCache->expects( $this->never() )->method( 'delete' );
794 $store = new WatchedItemStore(
795 $this->getMockLoadBalancer( $mockDb ),
799 $store->duplicateAllAssociatedEntries(
800 Title
::newFromText( 'Old_Title' ),
801 Title
::newFromText( 'New_Title' )
805 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
807 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
810 $mockDb = $this->getMockDb();
811 $mockDb->expects( $this->at( 0 ) )
817 'wl_notificationtimestamp',
821 'wl_title' => 'Old_Title',
824 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
825 $mockDb->expects( $this->at( 1 ) )
826 ->method( 'replace' )
829 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
834 'wl_title' => 'New_Title',
835 'wl_notificationtimestamp' => '20151212010101',
838 $this->isType( 'string' )
840 $mockDb->expects( $this->at( 2 ) )
846 'wl_notificationtimestamp',
850 'wl_title' => 'Old_Title',
853 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
854 $mockDb->expects( $this->at( 3 ) )
855 ->method( 'replace' )
858 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
863 'wl_title' => 'New_Title',
864 'wl_notificationtimestamp' => '20151212010101',
867 $this->isType( 'string' )
870 $mockCache = $this->getMockCache();
871 $mockCache->expects( $this->never() )->method( 'get' );
872 $mockCache->expects( $this->never() )->method( 'delete' );
874 $store = new WatchedItemStore(
875 $this->getMockLoadBalancer( $mockDb ),
879 $store->duplicateAllAssociatedEntries(
880 Title
::newFromText( 'Old_Title' ),
881 Title
::newFromText( 'New_Title' )
885 public function testAddWatch_nonAnonymousUser() {
886 $mockDb = $this->getMockDb();
887 $mockDb->expects( $this->once() )
895 'wl_title' => 'Some_Page',
896 'wl_notificationtimestamp' => null,
901 $mockCache = $this->getMockCache();
902 $mockCache->expects( $this->once() )
904 ->with( '0:Some_Page:1' );
906 $store = new WatchedItemStore(
907 $this->getMockLoadBalancer( $mockDb ),
912 $this->getMockNonAnonUserWithId( 1 ),
913 Title
::newFromText( 'Some_Page' )
917 public function testAddWatch_anonymousUser() {
918 $mockDb = $this->getMockDb();
919 $mockDb->expects( $this->never() )
920 ->method( 'insert' );
922 $mockCache = $this->getMockCache();
923 $mockCache->expects( $this->never() )
924 ->method( 'delete' );
926 $store = new WatchedItemStore(
927 $this->getMockLoadBalancer( $mockDb ),
932 $this->getAnonUser(),
933 Title
::newFromText( 'Some_Page' )
937 public function testAddWatchBatch_nonAnonymousUser() {
938 $mockDb = $this->getMockDb();
939 $mockDb->expects( $this->once() )
947 'wl_title' => 'Some_Page',
948 'wl_notificationtimestamp' => null,
953 'wl_title' => 'Some_Page',
954 'wl_notificationtimestamp' => null,
959 $mockCache = $this->getMockCache();
960 $mockCache->expects( $this->exactly( 2 ) )
961 ->method( 'delete' );
962 $mockCache->expects( $this->at( 1 ) )
964 ->with( '0:Some_Page:1' );
965 $mockCache->expects( $this->at( 3 ) )
967 ->with( '1:Some_Page:1' );
969 $store = new WatchedItemStore(
970 $this->getMockLoadBalancer( $mockDb ),
974 $mockUser = $this->getMockNonAnonUserWithId( 1 );
977 $store->addWatchBatch(
979 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
980 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
986 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
987 $mockDb = $this->getMockDb();
988 $mockDb->expects( $this->once() )
996 'wl_title' => 'Some_Page',
997 'wl_notificationtimestamp' => null,
1002 $mockCache = $this->getMockCache();
1003 $mockCache->expects( $this->once() )
1004 ->method( 'delete' )
1005 ->with( '0:Some_Page:1' );
1007 $store = new WatchedItemStore(
1008 $this->getMockLoadBalancer( $mockDb ),
1013 $store->addWatchBatch(
1015 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
1016 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
1022 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
1023 $mockDb = $this->getMockDb();
1024 $mockDb->expects( $this->never() )
1025 ->method( 'insert' );
1027 $mockCache = $this->getMockCache();
1028 $mockCache->expects( $this->never() )
1029 ->method( 'delete' );
1031 $store = new WatchedItemStore(
1032 $this->getMockLoadBalancer( $mockDb ),
1036 $anonUser = $this->getAnonUser();
1038 $store->addWatchBatch(
1040 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
1041 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
1047 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
1048 $mockDb = $this->getMockDb();
1049 $mockDb->expects( $this->never() )
1050 ->method( 'insert' );
1052 $mockCache = $this->getMockCache();
1053 $mockCache->expects( $this->never() )
1054 ->method( 'delete' );
1056 $store = new WatchedItemStore(
1057 $this->getMockLoadBalancer( $mockDb ),
1062 $store->addWatchBatch( [] )
1066 public function testLoadWatchedItem_existingItem() {
1067 $mockDb = $this->getMockDb();
1068 $mockDb->expects( $this->once() )
1069 ->method( 'selectRow' )
1072 'wl_notificationtimestamp',
1075 'wl_namespace' => 0,
1076 'wl_title' => 'SomeDbKey',
1079 ->will( $this->returnValue(
1080 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1083 $mockCache = $this->getMockCache();
1084 $mockCache->expects( $this->once() )
1090 $store = new WatchedItemStore(
1091 $this->getMockLoadBalancer( $mockDb ),
1095 $watchedItem = $store->loadWatchedItem(
1096 $this->getMockNonAnonUserWithId( 1 ),
1097 new TitleValue( 0, 'SomeDbKey' )
1099 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1100 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1101 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1102 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1105 public function testLoadWatchedItem_noItem() {
1106 $mockDb = $this->getMockDb();
1107 $mockDb->expects( $this->once() )
1108 ->method( 'selectRow' )
1111 'wl_notificationtimestamp',
1114 'wl_namespace' => 0,
1115 'wl_title' => 'SomeDbKey',
1118 ->will( $this->returnValue( [] ) );
1120 $mockCache = $this->getMockCache();
1121 $mockCache->expects( $this->never() )->method( 'get' );
1122 $mockCache->expects( $this->never() )->method( 'delete' );
1124 $store = new WatchedItemStore(
1125 $this->getMockLoadBalancer( $mockDb ),
1130 $store->loadWatchedItem(
1131 $this->getMockNonAnonUserWithId( 1 ),
1132 new TitleValue( 0, 'SomeDbKey' )
1137 public function testLoadWatchedItem_anonymousUser() {
1138 $mockDb = $this->getMockDb();
1139 $mockDb->expects( $this->never() )
1140 ->method( 'selectRow' );
1142 $mockCache = $this->getMockCache();
1143 $mockCache->expects( $this->never() )->method( 'get' );
1144 $mockCache->expects( $this->never() )->method( 'delete' );
1146 $store = new WatchedItemStore(
1147 $this->getMockLoadBalancer( $mockDb ),
1152 $store->loadWatchedItem(
1153 $this->getAnonUser(),
1154 new TitleValue( 0, 'SomeDbKey' )
1159 public function testRemoveWatch_existingItem() {
1160 $mockDb = $this->getMockDb();
1161 $mockDb->expects( $this->once() )
1162 ->method( 'delete' )
1167 'wl_namespace' => 0,
1168 'wl_title' => 'SomeDbKey',
1171 $mockDb->expects( $this->once() )
1172 ->method( 'affectedRows' )
1173 ->will( $this->returnValue( 1 ) );
1175 $mockCache = $this->getMockCache();
1176 $mockCache->expects( $this->never() )->method( 'get' );
1177 $mockCache->expects( $this->once() )
1178 ->method( 'delete' )
1179 ->with( '0:SomeDbKey:1' );
1181 $store = new WatchedItemStore(
1182 $this->getMockLoadBalancer( $mockDb ),
1187 $store->removeWatch(
1188 $this->getMockNonAnonUserWithId( 1 ),
1189 new TitleValue( 0, 'SomeDbKey' )
1194 public function testRemoveWatch_noItem() {
1195 $mockDb = $this->getMockDb();
1196 $mockDb->expects( $this->once() )
1197 ->method( 'delete' )
1202 'wl_namespace' => 0,
1203 'wl_title' => 'SomeDbKey',
1206 $mockDb->expects( $this->once() )
1207 ->method( 'affectedRows' )
1208 ->will( $this->returnValue( 0 ) );
1210 $mockCache = $this->getMockCache();
1211 $mockCache->expects( $this->never() )->method( 'get' );
1212 $mockCache->expects( $this->once() )
1213 ->method( 'delete' )
1214 ->with( '0:SomeDbKey:1' );
1216 $store = new WatchedItemStore(
1217 $this->getMockLoadBalancer( $mockDb ),
1222 $store->removeWatch(
1223 $this->getMockNonAnonUserWithId( 1 ),
1224 new TitleValue( 0, 'SomeDbKey' )
1229 public function testRemoveWatch_anonymousUser() {
1230 $mockDb = $this->getMockDb();
1231 $mockDb->expects( $this->never() )
1232 ->method( 'delete' );
1234 $mockCache = $this->getMockCache();
1235 $mockCache->expects( $this->never() )->method( 'get' );
1236 $mockCache->expects( $this->never() )
1237 ->method( 'delete' );
1239 $store = new WatchedItemStore(
1240 $this->getMockLoadBalancer( $mockDb ),
1245 $store->removeWatch(
1246 $this->getAnonUser(),
1247 new TitleValue( 0, 'SomeDbKey' )
1252 public function testGetWatchedItem_existingItem() {
1253 $mockDb = $this->getMockDb();
1254 $mockDb->expects( $this->once() )
1255 ->method( 'selectRow' )
1258 'wl_notificationtimestamp',
1261 'wl_namespace' => 0,
1262 'wl_title' => 'SomeDbKey',
1265 ->will( $this->returnValue(
1266 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1269 $mockCache = $this->getMockCache();
1270 $mockCache->expects( $this->never() )->method( 'delete' );
1271 $mockCache->expects( $this->once() )
1276 ->will( $this->returnValue( null ) );
1277 $mockCache->expects( $this->once() )
1283 $store = new WatchedItemStore(
1284 $this->getMockLoadBalancer( $mockDb ),
1288 $watchedItem = $store->getWatchedItem(
1289 $this->getMockNonAnonUserWithId( 1 ),
1290 new TitleValue( 0, 'SomeDbKey' )
1292 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1293 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1294 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1295 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1298 public function testGetWatchedItem_cachedItem() {
1299 $mockDb = $this->getMockDb();
1300 $mockDb->expects( $this->never() )
1301 ->method( 'selectRow' );
1303 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1304 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1305 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1307 $mockCache = $this->getMockCache();
1308 $mockCache->expects( $this->never() )->method( 'delete' );
1309 $mockCache->expects( $this->never() )->method( 'set' );
1310 $mockCache->expects( $this->once() )
1315 ->will( $this->returnValue( $cachedItem ) );
1317 $store = new WatchedItemStore(
1318 $this->getMockLoadBalancer( $mockDb ),
1322 $this->assertEquals(
1324 $store->getWatchedItem(
1331 public function testGetWatchedItem_noItem() {
1332 $mockDb = $this->getMockDb();
1333 $mockDb->expects( $this->once() )
1334 ->method( 'selectRow' )
1337 'wl_notificationtimestamp',
1340 'wl_namespace' => 0,
1341 'wl_title' => 'SomeDbKey',
1344 ->will( $this->returnValue( [] ) );
1346 $mockCache = $this->getMockCache();
1347 $mockCache->expects( $this->never() )->method( 'set' );
1348 $mockCache->expects( $this->never() )->method( 'delete' );
1349 $mockCache->expects( $this->once() )
1351 ->with( '0:SomeDbKey:1' )
1352 ->will( $this->returnValue( false ) );
1354 $store = new WatchedItemStore(
1355 $this->getMockLoadBalancer( $mockDb ),
1360 $store->getWatchedItem(
1361 $this->getMockNonAnonUserWithId( 1 ),
1362 new TitleValue( 0, 'SomeDbKey' )
1367 public function testGetWatchedItem_anonymousUser() {
1368 $mockDb = $this->getMockDb();
1369 $mockDb->expects( $this->never() )
1370 ->method( 'selectRow' );
1372 $mockCache = $this->getMockCache();
1373 $mockCache->expects( $this->never() )->method( 'set' );
1374 $mockCache->expects( $this->never() )->method( 'get' );
1375 $mockCache->expects( $this->never() )->method( 'delete' );
1377 $store = new WatchedItemStore(
1378 $this->getMockLoadBalancer( $mockDb ),
1383 $store->getWatchedItem(
1384 $this->getAnonUser(),
1385 new TitleValue( 0, 'SomeDbKey' )
1390 public function testGetWatchedItemsForUser() {
1391 $mockDb = $this->getMockDb();
1392 $mockDb->expects( $this->once() )
1393 ->method( 'select' )
1396 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1399 ->will( $this->returnValue( [
1400 $this->getFakeRow( [
1401 'wl_namespace' => 0,
1402 'wl_title' => 'Foo1',
1403 'wl_notificationtimestamp' => '20151212010101',
1405 $this->getFakeRow( [
1406 'wl_namespace' => 1,
1407 'wl_title' => 'Foo2',
1408 'wl_notificationtimestamp' => null,
1412 $mockCache = $this->getMockCache();
1413 $mockCache->expects( $this->never() )->method( 'delete' );
1414 $mockCache->expects( $this->never() )->method( 'get' );
1415 $mockCache->expects( $this->never() )->method( 'set' );
1417 $store = new WatchedItemStore(
1418 $this->getMockLoadBalancer( $mockDb ),
1421 $user = $this->getMockNonAnonUserWithId( 1 );
1423 $watchedItems = $store->getWatchedItemsForUser( $user );
1425 $this->assertInternalType( 'array', $watchedItems );
1426 $this->assertCount( 2, $watchedItems );
1427 foreach ( $watchedItems as $watchedItem ) {
1428 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1430 $this->assertEquals(
1431 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1434 $this->assertEquals(
1435 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1440 public function provideDbTypes() {
1442 [ false, DB_SLAVE
],
1443 [ true, DB_MASTER
],
1448 * @dataProvider provideDbTypes
1450 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1451 $mockDb = $this->getMockDb();
1452 $mockCache = $this->getMockCache();
1453 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1454 $user = $this->getMockNonAnonUserWithId( 1 );
1456 $mockDb->expects( $this->once() )
1457 ->method( 'select' )
1460 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1462 $this->isType( 'string' ),
1463 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1465 ->will( $this->returnValue( [] ) );
1467 $store = new WatchedItemStore(
1472 $watchedItems = $store->getWatchedItemsForUser(
1474 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1476 $this->assertEquals( [], $watchedItems );
1479 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1480 $store = new WatchedItemStore(
1481 $this->getMockLoadBalancer( $this->getMockDb() ),
1482 $this->getMockCache()
1485 $this->setExpectedException( 'InvalidArgumentException' );
1486 $store->getWatchedItemsForUser(
1487 $this->getMockNonAnonUserWithId( 1 ),
1492 public function testIsWatchedItem_existingItem() {
1493 $mockDb = $this->getMockDb();
1494 $mockDb->expects( $this->once() )
1495 ->method( 'selectRow' )
1498 'wl_notificationtimestamp',
1501 'wl_namespace' => 0,
1502 'wl_title' => 'SomeDbKey',
1505 ->will( $this->returnValue(
1506 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1509 $mockCache = $this->getMockCache();
1510 $mockCache->expects( $this->never() )->method( 'delete' );
1511 $mockCache->expects( $this->once() )
1513 ->with( '0:SomeDbKey:1' )
1514 ->will( $this->returnValue( false ) );
1515 $mockCache->expects( $this->once() )
1521 $store = new WatchedItemStore(
1522 $this->getMockLoadBalancer( $mockDb ),
1528 $this->getMockNonAnonUserWithId( 1 ),
1529 new TitleValue( 0, 'SomeDbKey' )
1534 public function testIsWatchedItem_noItem() {
1535 $mockDb = $this->getMockDb();
1536 $mockDb->expects( $this->once() )
1537 ->method( 'selectRow' )
1540 'wl_notificationtimestamp',
1543 'wl_namespace' => 0,
1544 'wl_title' => 'SomeDbKey',
1547 ->will( $this->returnValue( [] ) );
1549 $mockCache = $this->getMockCache();
1550 $mockCache->expects( $this->never() )->method( 'set' );
1551 $mockCache->expects( $this->never() )->method( 'delete' );
1552 $mockCache->expects( $this->once() )
1554 ->with( '0:SomeDbKey:1' )
1555 ->will( $this->returnValue( false ) );
1557 $store = new WatchedItemStore(
1558 $this->getMockLoadBalancer( $mockDb ),
1564 $this->getMockNonAnonUserWithId( 1 ),
1565 new TitleValue( 0, 'SomeDbKey' )
1570 public function testIsWatchedItem_anonymousUser() {
1571 $mockDb = $this->getMockDb();
1572 $mockDb->expects( $this->never() )
1573 ->method( 'selectRow' );
1575 $mockCache = $this->getMockCache();
1576 $mockCache->expects( $this->never() )->method( 'set' );
1577 $mockCache->expects( $this->never() )->method( 'get' );
1578 $mockCache->expects( $this->never() )->method( 'delete' );
1580 $store = new WatchedItemStore(
1581 $this->getMockLoadBalancer( $mockDb ),
1587 $this->getAnonUser(),
1588 new TitleValue( 0, 'SomeDbKey' )
1593 public function testGetNotificationTimestampsBatch() {
1595 new TitleValue( 0, 'SomeDbKey' ),
1596 new TitleValue( 1, 'AnotherDbKey' ),
1599 $mockDb = $this->getMockDb();
1601 $this->getFakeRow( [
1602 'wl_namespace' => 0,
1603 'wl_title' => 'SomeDbKey',
1604 'wl_notificationtimestamp' => '20151212010101',
1608 'wl_namespace' => 1,
1609 'wl_title' => 'AnotherDbKey',
1610 'wl_notificationtimestamp' => null,
1615 $mockDb->expects( $this->once() )
1616 ->method( 'makeWhereFrom2d' )
1618 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1619 $this->isType( 'string' ),
1620 $this->isType( 'string' )
1622 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1623 $mockDb->expects( $this->once() )
1624 ->method( 'select' )
1627 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1629 'makeWhereFrom2d return value',
1632 $this->isType( 'string' )
1634 ->will( $this->returnValue( $dbResult ) );
1636 $mockCache = $this->getMockCache();
1637 $mockCache->expects( $this->exactly( 2 ) )
1640 [ '0:SomeDbKey:1' ],
1641 [ '1:AnotherDbKey:1' ]
1643 ->will( $this->returnValue( null ) );
1644 $mockCache->expects( $this->never() )->method( 'set' );
1645 $mockCache->expects( $this->never() )->method( 'delete' );
1647 $store = new WatchedItemStore(
1648 $this->getMockLoadBalancer( $mockDb ),
1652 $this->assertEquals(
1654 0 => [ 'SomeDbKey' => '20151212010101', ],
1655 1 => [ 'AnotherDbKey' => null, ],
1657 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1661 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1663 new TitleValue( 0, 'OtherDbKey' ),
1666 $mockDb = $this->getMockDb();
1668 $mockDb->expects( $this->once() )
1669 ->method( 'makeWhereFrom2d' )
1671 [ [ 'OtherDbKey' => 1 ] ],
1672 $this->isType( 'string' ),
1673 $this->isType( 'string' )
1675 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1676 $mockDb->expects( $this->once() )
1677 ->method( 'select' )
1680 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1682 'makeWhereFrom2d return value',
1685 $this->isType( 'string' )
1687 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1689 $mockCache = $this->getMockCache();
1690 $mockCache->expects( $this->once() )
1692 ->with( '0:OtherDbKey:1' )
1693 ->will( $this->returnValue( null ) );
1694 $mockCache->expects( $this->never() )->method( 'set' );
1695 $mockCache->expects( $this->never() )->method( 'delete' );
1697 $store = new WatchedItemStore(
1698 $this->getMockLoadBalancer( $mockDb ),
1702 $this->assertEquals(
1704 0 => [ 'OtherDbKey' => false, ],
1706 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1710 public function testGetNotificationTimestampsBatch_cachedItem() {
1712 new TitleValue( 0, 'SomeDbKey' ),
1713 new TitleValue( 1, 'AnotherDbKey' ),
1716 $user = $this->getMockNonAnonUserWithId( 1 );
1717 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1719 $mockDb = $this->getMockDb();
1721 $mockDb->expects( $this->once() )
1722 ->method( 'makeWhereFrom2d' )
1724 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1725 $this->isType( 'string' ),
1726 $this->isType( 'string' )
1728 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1729 $mockDb->expects( $this->once() )
1730 ->method( 'select' )
1733 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1735 'makeWhereFrom2d return value',
1738 $this->isType( 'string' )
1740 ->will( $this->returnValue( [
1742 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1746 $mockCache = $this->getMockCache();
1747 $mockCache->expects( $this->at( 1 ) )
1749 ->with( '0:SomeDbKey:1' )
1750 ->will( $this->returnValue( $cachedItem ) );
1751 $mockCache->expects( $this->at( 3 ) )
1753 ->with( '1:AnotherDbKey:1' )
1754 ->will( $this->returnValue( null ) );
1755 $mockCache->expects( $this->never() )->method( 'set' );
1756 $mockCache->expects( $this->never() )->method( 'delete' );
1758 $store = new WatchedItemStore(
1759 $this->getMockLoadBalancer( $mockDb ),
1763 $this->assertEquals(
1765 0 => [ 'SomeDbKey' => '20151212010101', ],
1766 1 => [ 'AnotherDbKey' => null, ],
1768 $store->getNotificationTimestampsBatch( $user, $targets )
1772 public function testGetNotificationTimestampsBatch_allItemsCached() {
1774 new TitleValue( 0, 'SomeDbKey' ),
1775 new TitleValue( 1, 'AnotherDbKey' ),
1778 $user = $this->getMockNonAnonUserWithId( 1 );
1780 new WatchedItem( $user, $targets[0], '20151212010101' ),
1781 new WatchedItem( $user, $targets[1], null ),
1783 $mockDb = $this->getMockDb();
1784 $mockDb->expects( $this->never() )->method( $this->anything() );
1786 $mockCache = $this->getMockCache();
1787 $mockCache->expects( $this->at( 1 ) )
1789 ->with( '0:SomeDbKey:1' )
1790 ->will( $this->returnValue( $cachedItems[0] ) );
1791 $mockCache->expects( $this->at( 3 ) )
1793 ->with( '1:AnotherDbKey:1' )
1794 ->will( $this->returnValue( $cachedItems[1] ) );
1795 $mockCache->expects( $this->never() )->method( 'set' );
1796 $mockCache->expects( $this->never() )->method( 'delete' );
1798 $store = new WatchedItemStore(
1799 $this->getMockLoadBalancer( $mockDb ),
1803 $this->assertEquals(
1805 0 => [ 'SomeDbKey' => '20151212010101', ],
1806 1 => [ 'AnotherDbKey' => null, ],
1808 $store->getNotificationTimestampsBatch( $user, $targets )
1812 public function testGetNotificationTimestampsBatch_anonymousUser() {
1814 new TitleValue( 0, 'SomeDbKey' ),
1815 new TitleValue( 1, 'AnotherDbKey' ),
1818 $mockDb = $this->getMockDb();
1819 $mockDb->expects( $this->never() )->method( $this->anything() );
1821 $mockCache = $this->getMockCache();
1822 $mockCache->expects( $this->never() )->method( $this->anything() );
1824 $store = new WatchedItemStore(
1825 $this->getMockLoadBalancer( $mockDb ),
1829 $this->assertEquals(
1831 0 => [ 'SomeDbKey' => false, ],
1832 1 => [ 'AnotherDbKey' => false, ],
1834 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1838 public function testResetNotificationTimestamp_anonymousUser() {
1839 $mockDb = $this->getMockDb();
1840 $mockDb->expects( $this->never() )
1841 ->method( 'selectRow' );
1843 $mockCache = $this->getMockCache();
1844 $mockCache->expects( $this->never() )->method( 'get' );
1845 $mockCache->expects( $this->never() )->method( 'set' );
1846 $mockCache->expects( $this->never() )->method( 'delete' );
1848 $store = new WatchedItemStore(
1849 $this->getMockLoadBalancer( $mockDb ),
1854 $store->resetNotificationTimestamp(
1855 $this->getAnonUser(),
1856 Title
::newFromText( 'SomeDbKey' )
1861 public function testResetNotificationTimestamp_noItem() {
1862 $mockDb = $this->getMockDb();
1863 $mockDb->expects( $this->once() )
1864 ->method( 'selectRow' )
1867 'wl_notificationtimestamp',
1870 'wl_namespace' => 0,
1871 'wl_title' => 'SomeDbKey',
1874 ->will( $this->returnValue( [] ) );
1876 $mockCache = $this->getMockCache();
1877 $mockCache->expects( $this->never() )->method( 'get' );
1878 $mockCache->expects( $this->never() )->method( 'set' );
1879 $mockCache->expects( $this->never() )->method( 'delete' );
1881 $store = new WatchedItemStore(
1882 $this->getMockLoadBalancer( $mockDb ),
1887 $store->resetNotificationTimestamp(
1888 $this->getMockNonAnonUserWithId( 1 ),
1889 Title
::newFromText( 'SomeDbKey' )
1894 public function testResetNotificationTimestamp_item() {
1895 $user = $this->getMockNonAnonUserWithId( 1 );
1896 $title = Title
::newFromText( 'SomeDbKey' );
1898 $mockDb = $this->getMockDb();
1899 $mockDb->expects( $this->once() )
1900 ->method( 'selectRow' )
1903 'wl_notificationtimestamp',
1906 'wl_namespace' => 0,
1907 'wl_title' => 'SomeDbKey',
1910 ->will( $this->returnValue(
1911 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1914 $mockCache = $this->getMockCache();
1915 $mockCache->expects( $this->never() )->method( 'get' );
1916 $mockCache->expects( $this->once() )
1920 $this->isInstanceOf( WatchedItem
::class )
1922 $mockCache->expects( $this->once() )
1923 ->method( 'delete' )
1924 ->with( '0:SomeDbKey:1' );
1926 $store = new WatchedItemStore(
1927 $this->getMockLoadBalancer( $mockDb ),
1931 // Note: This does not actually assert the job is correct
1932 $callableCallCounter = 0;
1933 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1934 $callableCallCounter++
;
1935 $this->assertInternalType( 'callable', $callable );
1937 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1940 $store->resetNotificationTimestamp(
1945 $this->assertEquals( 1, $callableCallCounter );
1947 ScopedCallback
::consume( $scopedOverride );
1950 public function testResetNotificationTimestamp_noItemForced() {
1951 $user = $this->getMockNonAnonUserWithId( 1 );
1952 $title = Title
::newFromText( 'SomeDbKey' );
1954 $mockDb = $this->getMockDb();
1955 $mockDb->expects( $this->never() )
1956 ->method( 'selectRow' );
1958 $mockCache = $this->getMockCache();
1959 $mockDb->expects( $this->never() )
1961 $mockDb->expects( $this->never() )
1963 $mockDb->expects( $this->never() )
1964 ->method( 'delete' );
1966 $store = new WatchedItemStore(
1967 $this->getMockLoadBalancer( $mockDb ),
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(
1986 $this->assertEquals( 1, $callableCallCounter );
1988 ScopedCallback
::consume( $scopedOverride );
1995 * @return PHPUnit_Framework_MockObject_MockObject|Title
1997 private function getMockTitle( $text, $ns = 0 ) {
1998 $title = $this->getMock( Title
::class );
1999 $title->expects( $this->any() )
2000 ->method( 'getText' )
2001 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2002 $title->expects( $this->any() )
2003 ->method( 'getDbKey' )
2004 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2005 $title->expects( $this->any() )
2006 ->method( 'getNamespace' )
2007 ->will( $this->returnValue( $ns ) );
2011 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2012 $user = $this->getMockNonAnonUserWithId( 1 );
2014 $title = $this->getMockTitle( 'SomeTitle' );
2015 $title->expects( $this->once() )
2016 ->method( 'getNextRevisionID' )
2018 ->will( $this->returnValue( false ) );
2020 $mockDb = $this->getMockDb();
2021 $mockDb->expects( $this->never() )
2022 ->method( 'selectRow' );
2024 $mockCache = $this->getMockCache();
2025 $mockDb->expects( $this->never() )
2027 $mockDb->expects( $this->never() )
2029 $mockDb->expects( $this->never() )
2030 ->method( 'delete' );
2032 $store = new WatchedItemStore(
2033 $this->getMockLoadBalancer( $mockDb ),
2037 // Note: This does not actually assert the job is correct
2038 $callableCallCounter = 0;
2039 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2040 function( $callable ) use ( &$callableCallCounter ) {
2041 $callableCallCounter++
;
2042 $this->assertInternalType( 'callable', $callable );
2047 $store->resetNotificationTimestamp(
2054 $this->assertEquals( 1, $callableCallCounter );
2056 ScopedCallback
::consume( $scopedOverride );
2059 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2060 $user = $this->getMockNonAnonUserWithId( 1 );
2062 $title = $this->getMockTitle( 'SomeDbKey' );
2063 $title->expects( $this->once() )
2064 ->method( 'getNextRevisionID' )
2066 ->will( $this->returnValue( 33 ) );
2068 $mockDb = $this->getMockDb();
2069 $mockDb->expects( $this->once() )
2070 ->method( 'selectRow' )
2073 'wl_notificationtimestamp',
2076 'wl_namespace' => 0,
2077 'wl_title' => 'SomeDbKey',
2080 ->will( $this->returnValue(
2081 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2084 $mockCache = $this->getMockCache();
2085 $mockDb->expects( $this->never() )
2087 $mockDb->expects( $this->never() )
2089 $mockDb->expects( $this->never() )
2090 ->method( 'delete' );
2092 $store = new WatchedItemStore(
2093 $this->getMockLoadBalancer( $mockDb ),
2097 // Note: This does not actually assert the job is correct
2098 $addUpdateCallCounter = 0;
2099 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2100 function( $callable ) use ( &$addUpdateCallCounter ) {
2101 $addUpdateCallCounter++
;
2102 $this->assertInternalType( 'callable', $callable );
2106 $getTimestampCallCounter = 0;
2107 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2108 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2109 $getTimestampCallCounter++
;
2110 $this->assertEquals( $title, $titleParam );
2111 $this->assertEquals( $oldid, $oldidParam );
2116 $store->resetNotificationTimestamp(
2123 $this->assertEquals( 1, $addUpdateCallCounter );
2124 $this->assertEquals( 1, $getTimestampCallCounter );
2126 ScopedCallback
::consume( $scopedOverrideDeferred );
2127 ScopedCallback
::consume( $scopedOverrideRevision );
2130 public function testUpdateNotificationTimestamp_watchersExist() {
2131 $mockDb = $this->getMockDb();
2132 $mockDb->expects( $this->once() )
2133 ->method( 'select' )
2139 'wl_namespace' => 0,
2140 'wl_title' => 'SomeDbKey',
2141 'wl_notificationtimestamp IS NULL'
2145 $this->returnValue( [
2146 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2147 $this->getFakeRow( [ 'wl_user' => '3' ] )
2150 $mockDb->expects( $this->once() )
2151 ->method( 'onTransactionIdle' )
2152 ->with( $this->isType( 'callable' ) )
2153 ->will( $this->returnCallback( function( $callable ) {
2156 $mockDb->expects( $this->once() )
2157 ->method( 'update' )
2160 [ 'wl_notificationtimestamp' => null ],
2162 'wl_user' => [ 2, 3 ],
2163 'wl_namespace' => 0,
2164 'wl_title' => 'SomeDbKey',
2168 $mockCache = $this->getMockCache();
2169 $mockCache->expects( $this->never() )->method( 'set' );
2170 $mockCache->expects( $this->never() )->method( 'get' );
2171 $mockCache->expects( $this->never() )->method( 'delete' );
2173 $store = new WatchedItemStore(
2174 $this->getMockLoadBalancer( $mockDb ),
2178 $this->assertEquals(
2180 $store->updateNotificationTimestamp(
2181 $this->getMockNonAnonUserWithId( 1 ),
2182 new TitleValue( 0, 'SomeDbKey' ),
2188 public function testUpdateNotificationTimestamp_noWatchers() {
2189 $mockDb = $this->getMockDb();
2190 $mockDb->expects( $this->once() )
2191 ->method( 'select' )
2197 'wl_namespace' => 0,
2198 'wl_title' => 'SomeDbKey',
2199 'wl_notificationtimestamp IS NULL'
2203 $this->returnValue( [] )
2205 $mockDb->expects( $this->never() )
2206 ->method( 'onTransactionIdle' );
2207 $mockDb->expects( $this->never() )
2208 ->method( 'update' );
2210 $mockCache = $this->getMockCache();
2211 $mockCache->expects( $this->never() )->method( 'set' );
2212 $mockCache->expects( $this->never() )->method( 'get' );
2213 $mockCache->expects( $this->never() )->method( 'delete' );
2215 $store = new WatchedItemStore(
2216 $this->getMockLoadBalancer( $mockDb ),
2220 $watchers = $store->updateNotificationTimestamp(
2221 $this->getMockNonAnonUserWithId( 1 ),
2222 new TitleValue( 0, 'SomeDbKey' ),
2225 $this->assertInternalType( 'array', $watchers );
2226 $this->assertEmpty( $watchers );
2229 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2230 $user = $this->getMockNonAnonUserWithId( 1 );
2231 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2233 $mockDb = $this->getMockDb();
2234 $mockDb->expects( $this->once() )
2235 ->method( 'selectRow' )
2236 ->will( $this->returnValue(
2237 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2239 $mockDb->expects( $this->once() )
2240 ->method( 'select' )
2242 $this->returnValue( [
2243 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2244 $this->getFakeRow( [ 'wl_user' => '3' ] )
2247 $mockDb->expects( $this->once() )
2248 ->method( 'onTransactionIdle' )
2249 ->with( $this->isType( 'callable' ) )
2250 ->will( $this->returnCallback( function( $callable ) {
2253 $mockDb->expects( $this->once() )
2254 ->method( 'update' );
2256 $mockCache = $this->getMockCache();
2257 $mockCache->expects( $this->once() )
2259 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2260 $mockCache->expects( $this->once() )
2262 ->with( '0:SomeDbKey:1' );
2263 $mockCache->expects( $this->once() )
2264 ->method( 'delete' )
2265 ->with( '0:SomeDbKey:1' );
2267 $store = new WatchedItemStore(
2268 $this->getMockLoadBalancer( $mockDb ),
2272 // This will add the item to the cache
2273 $store->getWatchedItem( $user, $titleValue );
2275 $store->updateNotificationTimestamp(
2276 $this->getMockNonAnonUserWithId( 1 ),