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(
90 $this->getMock( StatsdDataFactory
::class )
94 public function testGetDefaultInstance() {
95 $instanceOne = WatchedItemStore
::getDefaultInstance();
96 $instanceTwo = WatchedItemStore
::getDefaultInstance();
97 $this->assertSame( $instanceOne, $instanceTwo );
100 public function testOverrideDefaultInstance() {
101 $instance = WatchedItemStore
::getDefaultInstance();
102 $scopedOverride = $instance->overrideDefaultInstance( null );
104 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
106 unset( $scopedOverride );
108 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
111 public function testCountWatchedItems() {
112 $user = $this->getMockNonAnonUserWithId( 1 );
114 $mockDb = $this->getMockDb();
115 $mockDb->expects( $this->exactly( 1 ) )
116 ->method( 'selectField' )
121 'wl_user' => $user->getId(),
123 $this->isType( 'string' )
125 ->will( $this->returnValue( 12 ) );
127 $mockCache = $this->getMockCache();
128 $mockCache->expects( $this->never() )->method( 'get' );
129 $mockCache->expects( $this->never() )->method( 'set' );
130 $mockCache->expects( $this->never() )->method( 'delete' );
132 $store = $this->newWatchedItemStore(
133 $this->getMockLoadBalancer( $mockDb ),
137 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
140 public function testCountWatchers() {
141 $titleValue = new TitleValue( 0, 'SomeDbKey' );
143 $mockDb = $this->getMockDb();
144 $mockDb->expects( $this->exactly( 1 ) )
145 ->method( 'selectField' )
150 'wl_namespace' => $titleValue->getNamespace(),
151 'wl_title' => $titleValue->getDBkey(),
153 $this->isType( 'string' )
155 ->will( $this->returnValue( 7 ) );
157 $mockCache = $this->getMockCache();
158 $mockCache->expects( $this->never() )->method( 'get' );
159 $mockCache->expects( $this->never() )->method( 'set' );
160 $mockCache->expects( $this->never() )->method( 'delete' );
162 $store = $this->newWatchedItemStore(
163 $this->getMockLoadBalancer( $mockDb ),
167 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
170 public function testCountWatchersMultiple() {
172 new TitleValue( 0, 'SomeDbKey' ),
173 new TitleValue( 0, 'OtherDbKey' ),
174 new TitleValue( 1, 'AnotherDbKey' ),
177 $mockDb = $this->getMockDb();
180 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
181 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
182 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
185 $mockDb->expects( $this->once() )
186 ->method( 'makeWhereFrom2d' )
188 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
189 $this->isType( 'string' ),
190 $this->isType( 'string' )
192 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
193 $mockDb->expects( $this->once() )
197 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
198 [ 'makeWhereFrom2d return value' ],
199 $this->isType( 'string' ),
201 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
205 $this->returnValue( $dbResult )
208 $mockCache = $this->getMockCache();
209 $mockCache->expects( $this->never() )->method( 'get' );
210 $mockCache->expects( $this->never() )->method( 'set' );
211 $mockCache->expects( $this->never() )->method( 'delete' );
213 $store = $this->newWatchedItemStore(
214 $this->getMockLoadBalancer( $mockDb ),
219 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
220 1 => [ 'AnotherDbKey' => 500 ],
222 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
225 public function provideIntWithDbUnsafeVersion() {
228 [ "50; DROP TABLE watchlist;\n--" ],
233 * @dataProvider provideIntWithDbUnsafeVersion
235 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
237 new TitleValue( 0, 'SomeDbKey' ),
238 new TitleValue( 0, 'OtherDbKey' ),
239 new TitleValue( 1, 'AnotherDbKey' ),
242 $mockDb = $this->getMockDb();
245 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
246 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
247 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
250 $mockDb->expects( $this->once() )
251 ->method( 'makeWhereFrom2d' )
253 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
254 $this->isType( 'string' ),
255 $this->isType( 'string' )
257 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
258 $mockDb->expects( $this->once() )
262 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
263 [ 'makeWhereFrom2d return value' ],
264 $this->isType( 'string' ),
266 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
267 'HAVING' => 'COUNT(*) >= 50',
271 $this->returnValue( $dbResult )
274 $mockCache = $this->getMockCache();
275 $mockCache->expects( $this->never() )->method( 'get' );
276 $mockCache->expects( $this->never() )->method( 'set' );
277 $mockCache->expects( $this->never() )->method( 'delete' );
279 $store = $this->newWatchedItemStore(
280 $this->getMockLoadBalancer( $mockDb ),
285 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
286 1 => [ 'AnotherDbKey' => 500 ],
290 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
294 public function testCountVisitingWatchers() {
295 $titleValue = new TitleValue( 0, 'SomeDbKey' );
297 $mockDb = $this->getMockDb();
298 $mockDb->expects( $this->exactly( 1 ) )
299 ->method( 'selectField' )
304 'wl_namespace' => $titleValue->getNamespace(),
305 'wl_title' => $titleValue->getDBkey(),
306 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
308 $this->isType( 'string' )
310 ->will( $this->returnValue( 7 ) );
311 $mockDb->expects( $this->exactly( 1 ) )
312 ->method( 'addQuotes' )
313 ->will( $this->returnCallback( function( $value ) {
316 $mockDb->expects( $this->exactly( 1 ) )
317 ->method( 'timestamp' )
318 ->will( $this->returnCallback( function( $value ) {
319 return 'TS' . $value . 'TS';
322 $mockCache = $this->getMockCache();
323 $mockCache->expects( $this->never() )->method( 'set' );
324 $mockCache->expects( $this->never() )->method( 'get' );
325 $mockCache->expects( $this->never() )->method( 'delete' );
327 $store = $this->newWatchedItemStore(
328 $this->getMockLoadBalancer( $mockDb ),
332 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
335 public function testCountVisitingWatchersMultiple() {
336 $titleValuesWithThresholds = [
337 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
338 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
339 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
343 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
344 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
345 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
347 $mockDb = $this->getMockDb();
348 $mockDb->expects( $this->exactly( 2 * 3 ) )
349 ->method( 'addQuotes' )
350 ->will( $this->returnCallback( function( $value ) {
353 $mockDb->expects( $this->exactly( 3 ) )
354 ->method( 'timestamp' )
355 ->will( $this->returnCallback( function( $value ) {
356 return 'TS' . $value . 'TS';
358 $mockDb->expects( $this->any() )
359 ->method( 'makeList' )
361 $this->isType( 'array' ),
362 $this->isType( 'int' )
364 ->will( $this->returnCallback( function( $a, $conj ) {
365 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
366 return join( $sqlConj, array_map( function( $s ) {
367 return '(' . $s . ')';
371 $mockDb->expects( $this->never() )
372 ->method( 'makeWhereFrom2d' );
375 '((wl_namespace = 0) AND (' .
376 "(((wl_title = 'SomeDbKey') AND (" .
377 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
379 "(wl_title = 'OtherDbKey') AND (" .
380 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
382 ') OR ((wl_namespace = 1) AND (' .
383 "(((wl_title = 'AnotherDbKey') AND (".
384 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
386 $mockDb->expects( $this->once() )
390 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
392 $this->isType( 'string' ),
394 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
398 $this->returnValue( $dbResult )
401 $mockCache = $this->getMockCache();
402 $mockCache->expects( $this->never() )->method( 'get' );
403 $mockCache->expects( $this->never() )->method( 'set' );
404 $mockCache->expects( $this->never() )->method( 'delete' );
406 $store = $this->newWatchedItemStore(
407 $this->getMockLoadBalancer( $mockDb ),
412 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
413 1 => [ 'AnotherDbKey' => 500 ],
417 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
421 public function testCountVisitingWatchersMultiple_withMissingTargets() {
422 $titleValuesWithThresholds = [
423 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
424 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
425 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
426 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
427 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
431 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
432 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
433 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
435 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
438 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
441 $mockDb = $this->getMockDb();
442 $mockDb->expects( $this->exactly( 2 * 3 ) )
443 ->method( 'addQuotes' )
444 ->will( $this->returnCallback( function( $value ) {
447 $mockDb->expects( $this->exactly( 3 ) )
448 ->method( 'timestamp' )
449 ->will( $this->returnCallback( function( $value ) {
450 return 'TS' . $value . 'TS';
452 $mockDb->expects( $this->any() )
453 ->method( 'makeList' )
455 $this->isType( 'array' ),
456 $this->isType( 'int' )
458 ->will( $this->returnCallback( function( $a, $conj ) {
459 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
460 return join( $sqlConj, array_map( function( $s ) {
461 return '(' . $s . ')';
465 $mockDb->expects( $this->once() )
466 ->method( 'makeWhereFrom2d' )
468 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
469 $this->isType( 'string' ),
470 $this->isType( 'string' )
472 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
475 '((wl_namespace = 0) AND (' .
476 "(((wl_title = 'SomeDbKey') AND (" .
477 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
479 "(wl_title = 'OtherDbKey') AND (" .
480 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
482 ') OR ((wl_namespace = 1) AND (' .
483 "(((wl_title = 'AnotherDbKey') AND (".
484 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
487 '(makeWhereFrom2d return value)';
488 $mockDb->expects( $this->once() )
492 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
494 $this->isType( 'string' ),
496 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
500 $this->returnValue( $dbResult )
503 $mockCache = $this->getMockCache();
504 $mockCache->expects( $this->never() )->method( 'get' );
505 $mockCache->expects( $this->never() )->method( 'set' );
506 $mockCache->expects( $this->never() )->method( 'delete' );
508 $store = $this->newWatchedItemStore(
509 $this->getMockLoadBalancer( $mockDb ),
515 'SomeDbKey' => 100, 'OtherDbKey' => 300,
516 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
518 1 => [ 'AnotherDbKey' => 500 ],
522 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
527 * @dataProvider provideIntWithDbUnsafeVersion
529 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
530 $titleValuesWithThresholds = [
531 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
532 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
533 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
536 $mockDb = $this->getMockDb();
537 $mockDb->expects( $this->any() )
538 ->method( 'makeList' )
539 ->will( $this->returnValue( 'makeList return value' ) );
540 $mockDb->expects( $this->once() )
544 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
545 'makeList return value',
546 $this->isType( 'string' ),
548 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
549 'HAVING' => 'COUNT(*) >= 50',
553 $this->returnValue( [] )
556 $mockCache = $this->getMockCache();
557 $mockCache->expects( $this->never() )->method( 'get' );
558 $mockCache->expects( $this->never() )->method( 'set' );
559 $mockCache->expects( $this->never() )->method( 'delete' );
561 $store = $this->newWatchedItemStore(
562 $this->getMockLoadBalancer( $mockDb ),
567 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
568 1 => [ 'AnotherDbKey' => 0 ],
572 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
576 public function testCountUnreadNotifications() {
577 $user = $this->getMockNonAnonUserWithId( 1 );
579 $mockDb = $this->getMockDb();
580 $mockDb->expects( $this->exactly( 1 ) )
581 ->method( 'selectRowCount' )
586 "wl_notificationtimestamp IS NOT NULL",
589 $this->isType( 'string' )
591 ->will( $this->returnValue( 9 ) );
593 $mockCache = $this->getMockCache();
594 $mockCache->expects( $this->never() )->method( 'set' );
595 $mockCache->expects( $this->never() )->method( 'get' );
596 $mockCache->expects( $this->never() )->method( 'delete' );
598 $store = $this->newWatchedItemStore(
599 $this->getMockLoadBalancer( $mockDb ),
603 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
607 * @dataProvider provideIntWithDbUnsafeVersion
609 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
610 $user = $this->getMockNonAnonUserWithId( 1 );
612 $mockDb = $this->getMockDb();
613 $mockDb->expects( $this->exactly( 1 ) )
614 ->method( 'selectRowCount' )
619 "wl_notificationtimestamp IS NOT NULL",
622 $this->isType( 'string' ),
625 ->will( $this->returnValue( 50 ) );
627 $mockCache = $this->getMockCache();
628 $mockCache->expects( $this->never() )->method( 'set' );
629 $mockCache->expects( $this->never() )->method( 'get' );
630 $mockCache->expects( $this->never() )->method( 'delete' );
632 $store = $this->newWatchedItemStore(
633 $this->getMockLoadBalancer( $mockDb ),
639 $store->countUnreadNotifications( $user, $limit )
644 * @dataProvider provideIntWithDbUnsafeVersion
646 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
647 $user = $this->getMockNonAnonUserWithId( 1 );
649 $mockDb = $this->getMockDb();
650 $mockDb->expects( $this->exactly( 1 ) )
651 ->method( 'selectRowCount' )
656 "wl_notificationtimestamp IS NOT NULL",
659 $this->isType( 'string' ),
662 ->will( $this->returnValue( 9 ) );
664 $mockCache = $this->getMockCache();
665 $mockCache->expects( $this->never() )->method( 'set' );
666 $mockCache->expects( $this->never() )->method( 'get' );
667 $mockCache->expects( $this->never() )->method( 'delete' );
669 $store = $this->newWatchedItemStore(
670 $this->getMockLoadBalancer( $mockDb ),
676 $store->countUnreadNotifications( $user, $limit )
680 public function testDuplicateEntry_nothingToDuplicate() {
681 $mockDb = $this->getMockDb();
682 $mockDb->expects( $this->once() )
688 'wl_notificationtimestamp',
692 'wl_title' => 'Old_Title',
694 'WatchedItemStore::duplicateEntry',
697 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
699 $store = $this->newWatchedItemStore(
700 $this->getMockLoadBalancer( $mockDb ),
701 $this->getMockCache()
704 $store->duplicateEntry(
705 Title
::newFromText( 'Old_Title' ),
706 Title
::newFromText( 'New_Title' )
710 public function testDuplicateEntry_somethingToDuplicate() {
712 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
713 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
716 $mockDb = $this->getMockDb();
717 $mockDb->expects( $this->at( 0 ) )
723 'wl_notificationtimestamp',
727 'wl_title' => 'Old_Title',
730 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
731 $mockDb->expects( $this->at( 1 ) )
732 ->method( 'replace' )
735 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
740 'wl_title' => 'New_Title',
741 'wl_notificationtimestamp' => '20151212010101',
746 'wl_title' => 'New_Title',
747 'wl_notificationtimestamp' => null,
750 $this->isType( 'string' )
753 $mockCache = $this->getMockCache();
754 $mockCache->expects( $this->never() )->method( 'get' );
755 $mockCache->expects( $this->never() )->method( 'delete' );
757 $store = $this->newWatchedItemStore(
758 $this->getMockLoadBalancer( $mockDb ),
762 $store->duplicateEntry(
763 Title
::newFromText( 'Old_Title' ),
764 Title
::newFromText( 'New_Title' )
768 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
769 $mockDb = $this->getMockDb();
770 $mockDb->expects( $this->at( 0 ) )
776 'wl_notificationtimestamp',
780 'wl_title' => 'Old_Title',
783 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
784 $mockDb->expects( $this->at( 1 ) )
790 'wl_notificationtimestamp',
794 'wl_title' => 'Old_Title',
797 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
799 $mockCache = $this->getMockCache();
800 $mockCache->expects( $this->never() )->method( 'get' );
801 $mockCache->expects( $this->never() )->method( 'delete' );
803 $store = $this->newWatchedItemStore(
804 $this->getMockLoadBalancer( $mockDb ),
808 $store->duplicateAllAssociatedEntries(
809 Title
::newFromText( 'Old_Title' ),
810 Title
::newFromText( 'New_Title' )
814 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
816 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
819 $mockDb = $this->getMockDb();
820 $mockDb->expects( $this->at( 0 ) )
826 'wl_notificationtimestamp',
830 'wl_title' => 'Old_Title',
833 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
834 $mockDb->expects( $this->at( 1 ) )
835 ->method( 'replace' )
838 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
843 'wl_title' => 'New_Title',
844 'wl_notificationtimestamp' => '20151212010101',
847 $this->isType( 'string' )
849 $mockDb->expects( $this->at( 2 ) )
855 'wl_notificationtimestamp',
859 'wl_title' => 'Old_Title',
862 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
863 $mockDb->expects( $this->at( 3 ) )
864 ->method( 'replace' )
867 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
872 'wl_title' => 'New_Title',
873 'wl_notificationtimestamp' => '20151212010101',
876 $this->isType( 'string' )
879 $mockCache = $this->getMockCache();
880 $mockCache->expects( $this->never() )->method( 'get' );
881 $mockCache->expects( $this->never() )->method( 'delete' );
883 $store = $this->newWatchedItemStore(
884 $this->getMockLoadBalancer( $mockDb ),
888 $store->duplicateAllAssociatedEntries(
889 Title
::newFromText( 'Old_Title' ),
890 Title
::newFromText( 'New_Title' )
894 public function testAddWatch_nonAnonymousUser() {
895 $mockDb = $this->getMockDb();
896 $mockDb->expects( $this->once() )
904 'wl_title' => 'Some_Page',
905 'wl_notificationtimestamp' => null,
910 $mockCache = $this->getMockCache();
911 $mockCache->expects( $this->once() )
913 ->with( '0:Some_Page:1' );
915 $store = $this->newWatchedItemStore(
916 $this->getMockLoadBalancer( $mockDb ),
921 $this->getMockNonAnonUserWithId( 1 ),
922 Title
::newFromText( 'Some_Page' )
926 public function testAddWatch_anonymousUser() {
927 $mockDb = $this->getMockDb();
928 $mockDb->expects( $this->never() )
929 ->method( 'insert' );
931 $mockCache = $this->getMockCache();
932 $mockCache->expects( $this->never() )
933 ->method( 'delete' );
935 $store = $this->newWatchedItemStore(
936 $this->getMockLoadBalancer( $mockDb ),
941 $this->getAnonUser(),
942 Title
::newFromText( 'Some_Page' )
946 public function testAddWatchBatchForUser_nonAnonymousUser() {
947 $mockDb = $this->getMockDb();
948 $mockDb->expects( $this->once() )
956 'wl_title' => 'Some_Page',
957 'wl_notificationtimestamp' => null,
962 'wl_title' => 'Some_Page',
963 'wl_notificationtimestamp' => null,
968 $mockCache = $this->getMockCache();
969 $mockCache->expects( $this->exactly( 2 ) )
970 ->method( 'delete' );
971 $mockCache->expects( $this->at( 1 ) )
973 ->with( '0:Some_Page:1' );
974 $mockCache->expects( $this->at( 3 ) )
976 ->with( '1:Some_Page:1' );
978 $store = $this->newWatchedItemStore(
979 $this->getMockLoadBalancer( $mockDb ),
983 $mockUser = $this->getMockNonAnonUserWithId( 1 );
986 $store->addWatchBatchForUser(
988 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
993 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
994 $mockDb = $this->getMockDb();
995 $mockDb->expects( $this->never() )
996 ->method( 'insert' );
998 $mockCache = $this->getMockCache();
999 $mockCache->expects( $this->never() )
1000 ->method( 'delete' );
1002 $store = $this->newWatchedItemStore(
1003 $this->getMockLoadBalancer( $mockDb ),
1008 $store->addWatchBatchForUser(
1009 $this->getAnonUser(),
1010 [ new TitleValue( 0, 'Other_Page' ) ]
1015 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1016 $user = $this->getMockNonAnonUserWithId( 1 );
1017 $mockDb = $this->getMockDb();
1018 $mockDb->expects( $this->never() )
1019 ->method( 'insert' );
1021 $mockCache = $this->getMockCache();
1022 $mockCache->expects( $this->never() )
1023 ->method( 'delete' );
1025 $store = $this->newWatchedItemStore(
1026 $this->getMockLoadBalancer( $mockDb ),
1031 $store->addWatchBatchForUser( $user, [] )
1035 public function testLoadWatchedItem_existingItem() {
1036 $mockDb = $this->getMockDb();
1037 $mockDb->expects( $this->once() )
1038 ->method( 'selectRow' )
1041 'wl_notificationtimestamp',
1044 'wl_namespace' => 0,
1045 'wl_title' => 'SomeDbKey',
1048 ->will( $this->returnValue(
1049 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1052 $mockCache = $this->getMockCache();
1053 $mockCache->expects( $this->once() )
1059 $store = $this->newWatchedItemStore(
1060 $this->getMockLoadBalancer( $mockDb ),
1064 $watchedItem = $store->loadWatchedItem(
1065 $this->getMockNonAnonUserWithId( 1 ),
1066 new TitleValue( 0, 'SomeDbKey' )
1068 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1069 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1070 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1071 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1074 public function testLoadWatchedItem_noItem() {
1075 $mockDb = $this->getMockDb();
1076 $mockDb->expects( $this->once() )
1077 ->method( 'selectRow' )
1080 'wl_notificationtimestamp',
1083 'wl_namespace' => 0,
1084 'wl_title' => 'SomeDbKey',
1087 ->will( $this->returnValue( [] ) );
1089 $mockCache = $this->getMockCache();
1090 $mockCache->expects( $this->never() )->method( 'get' );
1091 $mockCache->expects( $this->never() )->method( 'delete' );
1093 $store = $this->newWatchedItemStore(
1094 $this->getMockLoadBalancer( $mockDb ),
1099 $store->loadWatchedItem(
1100 $this->getMockNonAnonUserWithId( 1 ),
1101 new TitleValue( 0, 'SomeDbKey' )
1106 public function testLoadWatchedItem_anonymousUser() {
1107 $mockDb = $this->getMockDb();
1108 $mockDb->expects( $this->never() )
1109 ->method( 'selectRow' );
1111 $mockCache = $this->getMockCache();
1112 $mockCache->expects( $this->never() )->method( 'get' );
1113 $mockCache->expects( $this->never() )->method( 'delete' );
1115 $store = $this->newWatchedItemStore(
1116 $this->getMockLoadBalancer( $mockDb ),
1121 $store->loadWatchedItem(
1122 $this->getAnonUser(),
1123 new TitleValue( 0, 'SomeDbKey' )
1128 public function testRemoveWatch_existingItem() {
1129 $mockDb = $this->getMockDb();
1130 $mockDb->expects( $this->once() )
1131 ->method( 'delete' )
1136 'wl_namespace' => 0,
1137 'wl_title' => 'SomeDbKey',
1140 $mockDb->expects( $this->once() )
1141 ->method( 'affectedRows' )
1142 ->will( $this->returnValue( 1 ) );
1144 $mockCache = $this->getMockCache();
1145 $mockCache->expects( $this->never() )->method( 'get' );
1146 $mockCache->expects( $this->once() )
1147 ->method( 'delete' )
1148 ->with( '0:SomeDbKey:1' );
1150 $store = $this->newWatchedItemStore(
1151 $this->getMockLoadBalancer( $mockDb ),
1156 $store->removeWatch(
1157 $this->getMockNonAnonUserWithId( 1 ),
1158 new TitleValue( 0, 'SomeDbKey' )
1163 public function testRemoveWatch_noItem() {
1164 $mockDb = $this->getMockDb();
1165 $mockDb->expects( $this->once() )
1166 ->method( 'delete' )
1171 'wl_namespace' => 0,
1172 'wl_title' => 'SomeDbKey',
1175 $mockDb->expects( $this->once() )
1176 ->method( 'affectedRows' )
1177 ->will( $this->returnValue( 0 ) );
1179 $mockCache = $this->getMockCache();
1180 $mockCache->expects( $this->never() )->method( 'get' );
1181 $mockCache->expects( $this->once() )
1182 ->method( 'delete' )
1183 ->with( '0:SomeDbKey:1' );
1185 $store = $this->newWatchedItemStore(
1186 $this->getMockLoadBalancer( $mockDb ),
1191 $store->removeWatch(
1192 $this->getMockNonAnonUserWithId( 1 ),
1193 new TitleValue( 0, 'SomeDbKey' )
1198 public function testRemoveWatch_anonymousUser() {
1199 $mockDb = $this->getMockDb();
1200 $mockDb->expects( $this->never() )
1201 ->method( 'delete' );
1203 $mockCache = $this->getMockCache();
1204 $mockCache->expects( $this->never() )->method( 'get' );
1205 $mockCache->expects( $this->never() )
1206 ->method( 'delete' );
1208 $store = $this->newWatchedItemStore(
1209 $this->getMockLoadBalancer( $mockDb ),
1214 $store->removeWatch(
1215 $this->getAnonUser(),
1216 new TitleValue( 0, 'SomeDbKey' )
1221 public function testGetWatchedItem_existingItem() {
1222 $mockDb = $this->getMockDb();
1223 $mockDb->expects( $this->once() )
1224 ->method( 'selectRow' )
1227 'wl_notificationtimestamp',
1230 'wl_namespace' => 0,
1231 'wl_title' => 'SomeDbKey',
1234 ->will( $this->returnValue(
1235 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1238 $mockCache = $this->getMockCache();
1239 $mockCache->expects( $this->never() )->method( 'delete' );
1240 $mockCache->expects( $this->once() )
1245 ->will( $this->returnValue( null ) );
1246 $mockCache->expects( $this->once() )
1252 $store = $this->newWatchedItemStore(
1253 $this->getMockLoadBalancer( $mockDb ),
1257 $watchedItem = $store->getWatchedItem(
1258 $this->getMockNonAnonUserWithId( 1 ),
1259 new TitleValue( 0, 'SomeDbKey' )
1261 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1262 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1263 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1264 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1267 public function testGetWatchedItem_cachedItem() {
1268 $mockDb = $this->getMockDb();
1269 $mockDb->expects( $this->never() )
1270 ->method( 'selectRow' );
1272 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1273 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1274 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1276 $mockCache = $this->getMockCache();
1277 $mockCache->expects( $this->never() )->method( 'delete' );
1278 $mockCache->expects( $this->never() )->method( 'set' );
1279 $mockCache->expects( $this->once() )
1284 ->will( $this->returnValue( $cachedItem ) );
1286 $store = $this->newWatchedItemStore(
1287 $this->getMockLoadBalancer( $mockDb ),
1291 $this->assertEquals(
1293 $store->getWatchedItem(
1300 public function testGetWatchedItem_noItem() {
1301 $mockDb = $this->getMockDb();
1302 $mockDb->expects( $this->once() )
1303 ->method( 'selectRow' )
1306 'wl_notificationtimestamp',
1309 'wl_namespace' => 0,
1310 'wl_title' => 'SomeDbKey',
1313 ->will( $this->returnValue( [] ) );
1315 $mockCache = $this->getMockCache();
1316 $mockCache->expects( $this->never() )->method( 'set' );
1317 $mockCache->expects( $this->never() )->method( 'delete' );
1318 $mockCache->expects( $this->once() )
1320 ->with( '0:SomeDbKey:1' )
1321 ->will( $this->returnValue( false ) );
1323 $store = $this->newWatchedItemStore(
1324 $this->getMockLoadBalancer( $mockDb ),
1329 $store->getWatchedItem(
1330 $this->getMockNonAnonUserWithId( 1 ),
1331 new TitleValue( 0, 'SomeDbKey' )
1336 public function testGetWatchedItem_anonymousUser() {
1337 $mockDb = $this->getMockDb();
1338 $mockDb->expects( $this->never() )
1339 ->method( 'selectRow' );
1341 $mockCache = $this->getMockCache();
1342 $mockCache->expects( $this->never() )->method( 'set' );
1343 $mockCache->expects( $this->never() )->method( 'get' );
1344 $mockCache->expects( $this->never() )->method( 'delete' );
1346 $store = $this->newWatchedItemStore(
1347 $this->getMockLoadBalancer( $mockDb ),
1352 $store->getWatchedItem(
1353 $this->getAnonUser(),
1354 new TitleValue( 0, 'SomeDbKey' )
1359 public function testGetWatchedItemsForUser() {
1360 $mockDb = $this->getMockDb();
1361 $mockDb->expects( $this->once() )
1362 ->method( 'select' )
1365 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1368 ->will( $this->returnValue( [
1369 $this->getFakeRow( [
1370 'wl_namespace' => 0,
1371 'wl_title' => 'Foo1',
1372 'wl_notificationtimestamp' => '20151212010101',
1374 $this->getFakeRow( [
1375 'wl_namespace' => 1,
1376 'wl_title' => 'Foo2',
1377 'wl_notificationtimestamp' => null,
1381 $mockCache = $this->getMockCache();
1382 $mockCache->expects( $this->never() )->method( 'delete' );
1383 $mockCache->expects( $this->never() )->method( 'get' );
1384 $mockCache->expects( $this->never() )->method( 'set' );
1386 $store = $this->newWatchedItemStore(
1387 $this->getMockLoadBalancer( $mockDb ),
1390 $user = $this->getMockNonAnonUserWithId( 1 );
1392 $watchedItems = $store->getWatchedItemsForUser( $user );
1394 $this->assertInternalType( 'array', $watchedItems );
1395 $this->assertCount( 2, $watchedItems );
1396 foreach ( $watchedItems as $watchedItem ) {
1397 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1399 $this->assertEquals(
1400 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1403 $this->assertEquals(
1404 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1409 public function provideDbTypes() {
1411 [ false, DB_SLAVE
],
1412 [ true, DB_MASTER
],
1417 * @dataProvider provideDbTypes
1419 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1420 $mockDb = $this->getMockDb();
1421 $mockCache = $this->getMockCache();
1422 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1423 $user = $this->getMockNonAnonUserWithId( 1 );
1425 $mockDb->expects( $this->once() )
1426 ->method( 'select' )
1429 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1431 $this->isType( 'string' ),
1432 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1434 ->will( $this->returnValue( [] ) );
1436 $store = $this->newWatchedItemStore(
1441 $watchedItems = $store->getWatchedItemsForUser(
1443 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1445 $this->assertEquals( [], $watchedItems );
1448 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1449 $store = $this->newWatchedItemStore(
1450 $this->getMockLoadBalancer( $this->getMockDb() ),
1451 $this->getMockCache()
1454 $this->setExpectedException( 'InvalidArgumentException' );
1455 $store->getWatchedItemsForUser(
1456 $this->getMockNonAnonUserWithId( 1 ),
1461 public function testIsWatchedItem_existingItem() {
1462 $mockDb = $this->getMockDb();
1463 $mockDb->expects( $this->once() )
1464 ->method( 'selectRow' )
1467 'wl_notificationtimestamp',
1470 'wl_namespace' => 0,
1471 'wl_title' => 'SomeDbKey',
1474 ->will( $this->returnValue(
1475 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1478 $mockCache = $this->getMockCache();
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1480 $mockCache->expects( $this->once() )
1482 ->with( '0:SomeDbKey:1' )
1483 ->will( $this->returnValue( false ) );
1484 $mockCache->expects( $this->once() )
1490 $store = $this->newWatchedItemStore(
1491 $this->getMockLoadBalancer( $mockDb ),
1497 $this->getMockNonAnonUserWithId( 1 ),
1498 new TitleValue( 0, 'SomeDbKey' )
1503 public function testIsWatchedItem_noItem() {
1504 $mockDb = $this->getMockDb();
1505 $mockDb->expects( $this->once() )
1506 ->method( 'selectRow' )
1509 'wl_notificationtimestamp',
1512 'wl_namespace' => 0,
1513 'wl_title' => 'SomeDbKey',
1516 ->will( $this->returnValue( [] ) );
1518 $mockCache = $this->getMockCache();
1519 $mockCache->expects( $this->never() )->method( 'set' );
1520 $mockCache->expects( $this->never() )->method( 'delete' );
1521 $mockCache->expects( $this->once() )
1523 ->with( '0:SomeDbKey:1' )
1524 ->will( $this->returnValue( false ) );
1526 $store = $this->newWatchedItemStore(
1527 $this->getMockLoadBalancer( $mockDb ),
1533 $this->getMockNonAnonUserWithId( 1 ),
1534 new TitleValue( 0, 'SomeDbKey' )
1539 public function testIsWatchedItem_anonymousUser() {
1540 $mockDb = $this->getMockDb();
1541 $mockDb->expects( $this->never() )
1542 ->method( 'selectRow' );
1544 $mockCache = $this->getMockCache();
1545 $mockCache->expects( $this->never() )->method( 'set' );
1546 $mockCache->expects( $this->never() )->method( 'get' );
1547 $mockCache->expects( $this->never() )->method( 'delete' );
1549 $store = $this->newWatchedItemStore(
1550 $this->getMockLoadBalancer( $mockDb ),
1556 $this->getAnonUser(),
1557 new TitleValue( 0, 'SomeDbKey' )
1562 public function testGetNotificationTimestampsBatch() {
1564 new TitleValue( 0, 'SomeDbKey' ),
1565 new TitleValue( 1, 'AnotherDbKey' ),
1568 $mockDb = $this->getMockDb();
1570 $this->getFakeRow( [
1571 'wl_namespace' => 0,
1572 'wl_title' => 'SomeDbKey',
1573 'wl_notificationtimestamp' => '20151212010101',
1577 'wl_namespace' => 1,
1578 'wl_title' => 'AnotherDbKey',
1579 'wl_notificationtimestamp' => null,
1584 $mockDb->expects( $this->once() )
1585 ->method( 'makeWhereFrom2d' )
1587 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1588 $this->isType( 'string' ),
1589 $this->isType( 'string' )
1591 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1592 $mockDb->expects( $this->once() )
1593 ->method( 'select' )
1596 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1598 'makeWhereFrom2d return value',
1601 $this->isType( 'string' )
1603 ->will( $this->returnValue( $dbResult ) );
1605 $mockCache = $this->getMockCache();
1606 $mockCache->expects( $this->exactly( 2 ) )
1609 [ '0:SomeDbKey:1' ],
1610 [ '1:AnotherDbKey:1' ]
1612 ->will( $this->returnValue( null ) );
1613 $mockCache->expects( $this->never() )->method( 'set' );
1614 $mockCache->expects( $this->never() )->method( 'delete' );
1616 $store = $this->newWatchedItemStore(
1617 $this->getMockLoadBalancer( $mockDb ),
1621 $this->assertEquals(
1623 0 => [ 'SomeDbKey' => '20151212010101', ],
1624 1 => [ 'AnotherDbKey' => null, ],
1626 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1630 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1632 new TitleValue( 0, 'OtherDbKey' ),
1635 $mockDb = $this->getMockDb();
1637 $mockDb->expects( $this->once() )
1638 ->method( 'makeWhereFrom2d' )
1640 [ [ 'OtherDbKey' => 1 ] ],
1641 $this->isType( 'string' ),
1642 $this->isType( 'string' )
1644 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1645 $mockDb->expects( $this->once() )
1646 ->method( 'select' )
1649 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1651 'makeWhereFrom2d return value',
1654 $this->isType( 'string' )
1656 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1658 $mockCache = $this->getMockCache();
1659 $mockCache->expects( $this->once() )
1661 ->with( '0:OtherDbKey:1' )
1662 ->will( $this->returnValue( null ) );
1663 $mockCache->expects( $this->never() )->method( 'set' );
1664 $mockCache->expects( $this->never() )->method( 'delete' );
1666 $store = $this->newWatchedItemStore(
1667 $this->getMockLoadBalancer( $mockDb ),
1671 $this->assertEquals(
1673 0 => [ 'OtherDbKey' => false, ],
1675 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1679 public function testGetNotificationTimestampsBatch_cachedItem() {
1681 new TitleValue( 0, 'SomeDbKey' ),
1682 new TitleValue( 1, 'AnotherDbKey' ),
1685 $user = $this->getMockNonAnonUserWithId( 1 );
1686 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1688 $mockDb = $this->getMockDb();
1690 $mockDb->expects( $this->once() )
1691 ->method( 'makeWhereFrom2d' )
1693 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1694 $this->isType( 'string' ),
1695 $this->isType( 'string' )
1697 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1698 $mockDb->expects( $this->once() )
1699 ->method( 'select' )
1702 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1704 'makeWhereFrom2d return value',
1707 $this->isType( 'string' )
1709 ->will( $this->returnValue( [
1711 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1715 $mockCache = $this->getMockCache();
1716 $mockCache->expects( $this->at( 1 ) )
1718 ->with( '0:SomeDbKey:1' )
1719 ->will( $this->returnValue( $cachedItem ) );
1720 $mockCache->expects( $this->at( 3 ) )
1722 ->with( '1:AnotherDbKey:1' )
1723 ->will( $this->returnValue( null ) );
1724 $mockCache->expects( $this->never() )->method( 'set' );
1725 $mockCache->expects( $this->never() )->method( 'delete' );
1727 $store = $this->newWatchedItemStore(
1728 $this->getMockLoadBalancer( $mockDb ),
1732 $this->assertEquals(
1734 0 => [ 'SomeDbKey' => '20151212010101', ],
1735 1 => [ 'AnotherDbKey' => null, ],
1737 $store->getNotificationTimestampsBatch( $user, $targets )
1741 public function testGetNotificationTimestampsBatch_allItemsCached() {
1743 new TitleValue( 0, 'SomeDbKey' ),
1744 new TitleValue( 1, 'AnotherDbKey' ),
1747 $user = $this->getMockNonAnonUserWithId( 1 );
1749 new WatchedItem( $user, $targets[0], '20151212010101' ),
1750 new WatchedItem( $user, $targets[1], null ),
1752 $mockDb = $this->getMockDb();
1753 $mockDb->expects( $this->never() )->method( $this->anything() );
1755 $mockCache = $this->getMockCache();
1756 $mockCache->expects( $this->at( 1 ) )
1758 ->with( '0:SomeDbKey:1' )
1759 ->will( $this->returnValue( $cachedItems[0] ) );
1760 $mockCache->expects( $this->at( 3 ) )
1762 ->with( '1:AnotherDbKey:1' )
1763 ->will( $this->returnValue( $cachedItems[1] ) );
1764 $mockCache->expects( $this->never() )->method( 'set' );
1765 $mockCache->expects( $this->never() )->method( 'delete' );
1767 $store = $this->newWatchedItemStore(
1768 $this->getMockLoadBalancer( $mockDb ),
1772 $this->assertEquals(
1774 0 => [ 'SomeDbKey' => '20151212010101', ],
1775 1 => [ 'AnotherDbKey' => null, ],
1777 $store->getNotificationTimestampsBatch( $user, $targets )
1781 public function testGetNotificationTimestampsBatch_anonymousUser() {
1783 new TitleValue( 0, 'SomeDbKey' ),
1784 new TitleValue( 1, 'AnotherDbKey' ),
1787 $mockDb = $this->getMockDb();
1788 $mockDb->expects( $this->never() )->method( $this->anything() );
1790 $mockCache = $this->getMockCache();
1791 $mockCache->expects( $this->never() )->method( $this->anything() );
1793 $store = $this->newWatchedItemStore(
1794 $this->getMockLoadBalancer( $mockDb ),
1798 $this->assertEquals(
1800 0 => [ 'SomeDbKey' => false, ],
1801 1 => [ 'AnotherDbKey' => false, ],
1803 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1807 public function testResetNotificationTimestamp_anonymousUser() {
1808 $mockDb = $this->getMockDb();
1809 $mockDb->expects( $this->never() )
1810 ->method( 'selectRow' );
1812 $mockCache = $this->getMockCache();
1813 $mockCache->expects( $this->never() )->method( 'get' );
1814 $mockCache->expects( $this->never() )->method( 'set' );
1815 $mockCache->expects( $this->never() )->method( 'delete' );
1817 $store = $this->newWatchedItemStore(
1818 $this->getMockLoadBalancer( $mockDb ),
1823 $store->resetNotificationTimestamp(
1824 $this->getAnonUser(),
1825 Title
::newFromText( 'SomeDbKey' )
1830 public function testResetNotificationTimestamp_noItem() {
1831 $mockDb = $this->getMockDb();
1832 $mockDb->expects( $this->once() )
1833 ->method( 'selectRow' )
1836 'wl_notificationtimestamp',
1839 'wl_namespace' => 0,
1840 'wl_title' => 'SomeDbKey',
1843 ->will( $this->returnValue( [] ) );
1845 $mockCache = $this->getMockCache();
1846 $mockCache->expects( $this->never() )->method( 'get' );
1847 $mockCache->expects( $this->never() )->method( 'set' );
1848 $mockCache->expects( $this->never() )->method( 'delete' );
1850 $store = $this->newWatchedItemStore(
1851 $this->getMockLoadBalancer( $mockDb ),
1856 $store->resetNotificationTimestamp(
1857 $this->getMockNonAnonUserWithId( 1 ),
1858 Title
::newFromText( 'SomeDbKey' )
1863 public function testResetNotificationTimestamp_item() {
1864 $user = $this->getMockNonAnonUserWithId( 1 );
1865 $title = Title
::newFromText( 'SomeDbKey' );
1867 $mockDb = $this->getMockDb();
1868 $mockDb->expects( $this->once() )
1869 ->method( 'selectRow' )
1872 'wl_notificationtimestamp',
1875 'wl_namespace' => 0,
1876 'wl_title' => 'SomeDbKey',
1879 ->will( $this->returnValue(
1880 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1883 $mockCache = $this->getMockCache();
1884 $mockCache->expects( $this->never() )->method( 'get' );
1885 $mockCache->expects( $this->once() )
1889 $this->isInstanceOf( WatchedItem
::class )
1891 $mockCache->expects( $this->once() )
1892 ->method( 'delete' )
1893 ->with( '0:SomeDbKey:1' );
1895 $store = $this->newWatchedItemStore(
1896 $this->getMockLoadBalancer( $mockDb ),
1900 // Note: This does not actually assert the job is correct
1901 $callableCallCounter = 0;
1902 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1903 $callableCallCounter++
;
1904 $this->assertInternalType( 'callable', $callable );
1906 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1909 $store->resetNotificationTimestamp(
1914 $this->assertEquals( 1, $callableCallCounter );
1916 ScopedCallback
::consume( $scopedOverride );
1919 public function testResetNotificationTimestamp_noItemForced() {
1920 $user = $this->getMockNonAnonUserWithId( 1 );
1921 $title = Title
::newFromText( 'SomeDbKey' );
1923 $mockDb = $this->getMockDb();
1924 $mockDb->expects( $this->never() )
1925 ->method( 'selectRow' );
1927 $mockCache = $this->getMockCache();
1928 $mockDb->expects( $this->never() )
1930 $mockDb->expects( $this->never() )
1932 $mockDb->expects( $this->never() )
1933 ->method( 'delete' );
1935 $store = $this->newWatchedItemStore(
1936 $this->getMockLoadBalancer( $mockDb ),
1940 // Note: This does not actually assert the job is correct
1941 $callableCallCounter = 0;
1942 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1943 $callableCallCounter++
;
1944 $this->assertInternalType( 'callable', $callable );
1946 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1949 $store->resetNotificationTimestamp(
1955 $this->assertEquals( 1, $callableCallCounter );
1957 ScopedCallback
::consume( $scopedOverride );
1964 * @return PHPUnit_Framework_MockObject_MockObject|Title
1966 private function getMockTitle( $text, $ns = 0 ) {
1967 $title = $this->getMock( Title
::class );
1968 $title->expects( $this->any() )
1969 ->method( 'getText' )
1970 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1971 $title->expects( $this->any() )
1972 ->method( 'getDbKey' )
1973 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1974 $title->expects( $this->any() )
1975 ->method( 'getNamespace' )
1976 ->will( $this->returnValue( $ns ) );
1980 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1981 $user = $this->getMockNonAnonUserWithId( 1 );
1983 $title = $this->getMockTitle( 'SomeTitle' );
1984 $title->expects( $this->once() )
1985 ->method( 'getNextRevisionID' )
1987 ->will( $this->returnValue( false ) );
1989 $mockDb = $this->getMockDb();
1990 $mockDb->expects( $this->never() )
1991 ->method( 'selectRow' );
1993 $mockCache = $this->getMockCache();
1994 $mockDb->expects( $this->never() )
1996 $mockDb->expects( $this->never() )
1998 $mockDb->expects( $this->never() )
1999 ->method( 'delete' );
2001 $store = $this->newWatchedItemStore(
2002 $this->getMockLoadBalancer( $mockDb ),
2006 // Note: This does not actually assert the job is correct
2007 $callableCallCounter = 0;
2008 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2009 function( $callable ) use ( &$callableCallCounter ) {
2010 $callableCallCounter++
;
2011 $this->assertInternalType( 'callable', $callable );
2016 $store->resetNotificationTimestamp(
2023 $this->assertEquals( 1, $callableCallCounter );
2025 ScopedCallback
::consume( $scopedOverride );
2028 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2029 $user = $this->getMockNonAnonUserWithId( 1 );
2031 $title = $this->getMockTitle( 'SomeDbKey' );
2032 $title->expects( $this->once() )
2033 ->method( 'getNextRevisionID' )
2035 ->will( $this->returnValue( 33 ) );
2037 $mockDb = $this->getMockDb();
2038 $mockDb->expects( $this->once() )
2039 ->method( 'selectRow' )
2042 'wl_notificationtimestamp',
2045 'wl_namespace' => 0,
2046 'wl_title' => 'SomeDbKey',
2049 ->will( $this->returnValue(
2050 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2053 $mockCache = $this->getMockCache();
2054 $mockDb->expects( $this->never() )
2056 $mockDb->expects( $this->never() )
2058 $mockDb->expects( $this->never() )
2059 ->method( 'delete' );
2061 $store = $this->newWatchedItemStore(
2062 $this->getMockLoadBalancer( $mockDb ),
2066 // Note: This does not actually assert the job is correct
2067 $addUpdateCallCounter = 0;
2068 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2069 function( $callable ) use ( &$addUpdateCallCounter ) {
2070 $addUpdateCallCounter++
;
2071 $this->assertInternalType( 'callable', $callable );
2075 $getTimestampCallCounter = 0;
2076 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2077 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2078 $getTimestampCallCounter++
;
2079 $this->assertEquals( $title, $titleParam );
2080 $this->assertEquals( $oldid, $oldidParam );
2085 $store->resetNotificationTimestamp(
2092 $this->assertEquals( 1, $addUpdateCallCounter );
2093 $this->assertEquals( 1, $getTimestampCallCounter );
2095 ScopedCallback
::consume( $scopedOverrideDeferred );
2096 ScopedCallback
::consume( $scopedOverrideRevision );
2099 public function testUpdateNotificationTimestamp_watchersExist() {
2100 $mockDb = $this->getMockDb();
2101 $mockDb->expects( $this->once() )
2102 ->method( 'select' )
2108 'wl_namespace' => 0,
2109 'wl_title' => 'SomeDbKey',
2110 'wl_notificationtimestamp IS NULL'
2114 $this->returnValue( [
2115 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2116 $this->getFakeRow( [ 'wl_user' => '3' ] )
2119 $mockDb->expects( $this->once() )
2120 ->method( 'onTransactionIdle' )
2121 ->with( $this->isType( 'callable' ) )
2122 ->will( $this->returnCallback( function( $callable ) {
2125 $mockDb->expects( $this->once() )
2126 ->method( 'update' )
2129 [ 'wl_notificationtimestamp' => null ],
2131 'wl_user' => [ 2, 3 ],
2132 'wl_namespace' => 0,
2133 'wl_title' => 'SomeDbKey',
2137 $mockCache = $this->getMockCache();
2138 $mockCache->expects( $this->never() )->method( 'set' );
2139 $mockCache->expects( $this->never() )->method( 'get' );
2140 $mockCache->expects( $this->never() )->method( 'delete' );
2142 $store = $this->newWatchedItemStore(
2143 $this->getMockLoadBalancer( $mockDb ),
2147 $this->assertEquals(
2149 $store->updateNotificationTimestamp(
2150 $this->getMockNonAnonUserWithId( 1 ),
2151 new TitleValue( 0, 'SomeDbKey' ),
2157 public function testUpdateNotificationTimestamp_noWatchers() {
2158 $mockDb = $this->getMockDb();
2159 $mockDb->expects( $this->once() )
2160 ->method( 'select' )
2166 'wl_namespace' => 0,
2167 'wl_title' => 'SomeDbKey',
2168 'wl_notificationtimestamp IS NULL'
2172 $this->returnValue( [] )
2174 $mockDb->expects( $this->never() )
2175 ->method( 'onTransactionIdle' );
2176 $mockDb->expects( $this->never() )
2177 ->method( 'update' );
2179 $mockCache = $this->getMockCache();
2180 $mockCache->expects( $this->never() )->method( 'set' );
2181 $mockCache->expects( $this->never() )->method( 'get' );
2182 $mockCache->expects( $this->never() )->method( 'delete' );
2184 $store = $this->newWatchedItemStore(
2185 $this->getMockLoadBalancer( $mockDb ),
2189 $watchers = $store->updateNotificationTimestamp(
2190 $this->getMockNonAnonUserWithId( 1 ),
2191 new TitleValue( 0, 'SomeDbKey' ),
2194 $this->assertInternalType( 'array', $watchers );
2195 $this->assertEmpty( $watchers );
2198 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2199 $user = $this->getMockNonAnonUserWithId( 1 );
2200 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2202 $mockDb = $this->getMockDb();
2203 $mockDb->expects( $this->once() )
2204 ->method( 'selectRow' )
2205 ->will( $this->returnValue(
2206 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2208 $mockDb->expects( $this->once() )
2209 ->method( 'select' )
2211 $this->returnValue( [
2212 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2213 $this->getFakeRow( [ 'wl_user' => '3' ] )
2216 $mockDb->expects( $this->once() )
2217 ->method( 'onTransactionIdle' )
2218 ->with( $this->isType( 'callable' ) )
2219 ->will( $this->returnCallback( function( $callable ) {
2222 $mockDb->expects( $this->once() )
2223 ->method( 'update' );
2225 $mockCache = $this->getMockCache();
2226 $mockCache->expects( $this->once() )
2228 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2229 $mockCache->expects( $this->once() )
2231 ->with( '0:SomeDbKey:1' );
2232 $mockCache->expects( $this->once() )
2233 ->method( 'delete' )
2234 ->with( '0:SomeDbKey:1' );
2236 $store = $this->newWatchedItemStore(
2237 $this->getMockLoadBalancer( $mockDb ),
2241 // This will add the item to the cache
2242 $store->getWatchedItem( $user, $titleValue );
2244 $store->updateNotificationTimestamp(
2245 $this->getMockNonAnonUserWithId( 1 ),