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 ) {
21 $mock = $this->getMockBuilder( LoadBalancer
::class )
22 ->disableOriginalConstructor()
24 $mock->expects( $this->any() )
25 ->method( 'getConnection' )
26 ->will( $this->returnValue( $mockDb ) );
27 $mock->expects( $this->any() )
28 ->method( 'getReadOnlyReason' )
29 ->will( $this->returnValue( false ) );
34 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
36 private function getMockCache() {
37 $mock = $this->getMockBuilder( HashBagOStuff
::class )
38 ->disableOriginalConstructor()
40 $mock->expects( $this->any() )
42 ->will( $this->returnCallback( function() {
43 return implode( ':', func_get_args() );
50 * @return PHPUnit_Framework_MockObject_MockObject|User
52 private function getMockNonAnonUserWithId( $id ) {
53 $mock = $this->getMock( User
::class );
54 $mock->expects( $this->any() )
56 ->will( $this->returnValue( false ) );
57 $mock->expects( $this->any() )
59 ->will( $this->returnValue( $id ) );
66 private function getAnonUser() {
67 return User
::newFromName( 'Anon_User' );
70 private function getFakeRow( array $rowValues ) {
71 $fakeRow = new stdClass();
72 foreach ( $rowValues as $valueName => $value ) {
73 $fakeRow->$valueName = $value;
78 public function testGetDefaultInstance() {
79 $instanceOne = WatchedItemStore
::getDefaultInstance();
80 $instanceTwo = WatchedItemStore
::getDefaultInstance();
81 $this->assertSame( $instanceOne, $instanceTwo );
84 public function testOverrideDefaultInstance() {
85 $instance = WatchedItemStore
::getDefaultInstance();
86 $scopedOverride = $instance->overrideDefaultInstance( null );
88 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
90 unset( $scopedOverride );
92 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
95 public function testCountWatchers() {
96 $titleValue = new TitleValue( 0, 'SomeDbKey' );
98 $mockDb = $this->getMockDb();
99 $mockDb->expects( $this->exactly( 1 ) )
100 ->method( 'selectField' )
105 'wl_namespace' => $titleValue->getNamespace(),
106 'wl_title' => $titleValue->getDBkey(),
108 $this->isType( 'string' )
110 ->will( $this->returnValue( 7 ) );
112 $mockCache = $this->getMockCache();
113 $mockCache->expects( $this->never() )->method( 'get' );
114 $mockCache->expects( $this->never() )->method( 'set' );
115 $mockCache->expects( $this->never() )->method( 'delete' );
117 $store = new WatchedItemStore(
118 $this->getMockLoadBalancer( $mockDb ),
122 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
125 public function testCountWatchersMultiple() {
127 new TitleValue( 0, 'SomeDbKey' ),
128 new TitleValue( 0, 'OtherDbKey' ),
129 new TitleValue( 1, 'AnotherDbKey' ),
132 $mockDb = $this->getMockDb();
135 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
136 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
137 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
140 $mockDb->expects( $this->once() )
141 ->method( 'makeWhereFrom2d' )
143 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
144 $this->isType( 'string' ),
145 $this->isType( 'string' )
147 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
148 $mockDb->expects( $this->once() )
152 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
153 [ 'makeWhereFrom2d return value' ],
154 $this->isType( 'string' ),
156 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
160 $this->returnValue( $dbResult )
163 $mockCache = $this->getMockCache();
164 $mockCache->expects( $this->never() )->method( 'get' );
165 $mockCache->expects( $this->never() )->method( 'set' );
166 $mockCache->expects( $this->never() )->method( 'delete' );
168 $store = new WatchedItemStore(
169 $this->getMockLoadBalancer( $mockDb ),
174 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
175 1 => [ 'AnotherDbKey' => 500 ],
177 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
180 public function provideIntWithDbUnsafeVersion() {
183 [ "50; DROP TABLE watchlist;\n--" ],
188 * @dataProvider provideIntWithDbUnsafeVersion
190 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
192 new TitleValue( 0, 'SomeDbKey' ),
193 new TitleValue( 0, 'OtherDbKey' ),
194 new TitleValue( 1, 'AnotherDbKey' ),
197 $mockDb = $this->getMockDb();
200 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
201 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
202 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
205 $mockDb->expects( $this->once() )
206 ->method( 'makeWhereFrom2d' )
208 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
209 $this->isType( 'string' ),
210 $this->isType( 'string' )
212 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
213 $mockDb->expects( $this->once() )
217 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
218 [ 'makeWhereFrom2d return value' ],
219 $this->isType( 'string' ),
221 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
222 'HAVING' => 'COUNT(*) >= 50',
226 $this->returnValue( $dbResult )
229 $mockCache = $this->getMockCache();
230 $mockCache->expects( $this->never() )->method( 'get' );
231 $mockCache->expects( $this->never() )->method( 'set' );
232 $mockCache->expects( $this->never() )->method( 'delete' );
234 $store = new WatchedItemStore(
235 $this->getMockLoadBalancer( $mockDb ),
240 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
241 1 => [ 'AnotherDbKey' => 500 ],
245 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
249 public function testCountVisitingWatchers() {
250 $titleValue = new TitleValue( 0, 'SomeDbKey' );
252 $mockDb = $this->getMockDb();
253 $mockDb->expects( $this->exactly( 1 ) )
254 ->method( 'selectField' )
259 'wl_namespace' => $titleValue->getNamespace(),
260 'wl_title' => $titleValue->getDBkey(),
261 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
263 $this->isType( 'string' )
265 ->will( $this->returnValue( 7 ) );
266 $mockDb->expects( $this->exactly( 1 ) )
267 ->method( 'addQuotes' )
268 ->will( $this->returnCallback( function( $value ) {
271 $mockDb->expects( $this->exactly( 1 ) )
272 ->method( 'timestamp' )
273 ->will( $this->returnCallback( function( $value ) {
274 return 'TS' . $value . 'TS';
277 $mockCache = $this->getMockCache();
278 $mockCache->expects( $this->never() )->method( 'set' );
279 $mockCache->expects( $this->never() )->method( 'get' );
280 $mockCache->expects( $this->never() )->method( 'delete' );
282 $store = new WatchedItemStore(
283 $this->getMockLoadBalancer( $mockDb ),
287 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
290 public function testCountUnreadNotifications() {
291 $user = $this->getMockNonAnonUserWithId( 1 );
293 $mockDb = $this->getMockDb();
294 $mockDb->expects( $this->exactly( 1 ) )
295 ->method( 'selectRowCount' )
300 "wl_notificationtimestamp IS NOT NULL",
303 $this->isType( 'string' )
305 ->will( $this->returnValue( 9 ) );
307 $mockCache = $this->getMockCache();
308 $mockCache->expects( $this->never() )->method( 'set' );
309 $mockCache->expects( $this->never() )->method( 'get' );
310 $mockCache->expects( $this->never() )->method( 'delete' );
312 $store = new WatchedItemStore(
313 $this->getMockLoadBalancer( $mockDb ),
317 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
321 * @dataProvider provideIntWithDbUnsafeVersion
323 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
324 $user = $this->getMockNonAnonUserWithId( 1 );
326 $mockDb = $this->getMockDb();
327 $mockDb->expects( $this->exactly( 1 ) )
328 ->method( 'selectRowCount' )
333 "wl_notificationtimestamp IS NOT NULL",
336 $this->isType( 'string' ),
339 ->will( $this->returnValue( 50 ) );
341 $mockCache = $this->getMockCache();
342 $mockCache->expects( $this->never() )->method( 'set' );
343 $mockCache->expects( $this->never() )->method( 'get' );
344 $mockCache->expects( $this->never() )->method( 'delete' );
346 $store = new WatchedItemStore(
347 $this->getMockLoadBalancer( $mockDb ),
353 $store->countUnreadNotifications( $user, $limit )
358 * @dataProvider provideIntWithDbUnsafeVersion
360 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
361 $user = $this->getMockNonAnonUserWithId( 1 );
363 $mockDb = $this->getMockDb();
364 $mockDb->expects( $this->exactly( 1 ) )
365 ->method( 'selectRowCount' )
370 "wl_notificationtimestamp IS NOT NULL",
373 $this->isType( 'string' ),
376 ->will( $this->returnValue( 9 ) );
378 $mockCache = $this->getMockCache();
379 $mockCache->expects( $this->never() )->method( 'set' );
380 $mockCache->expects( $this->never() )->method( 'get' );
381 $mockCache->expects( $this->never() )->method( 'delete' );
383 $store = new WatchedItemStore(
384 $this->getMockLoadBalancer( $mockDb ),
390 $store->countUnreadNotifications( $user, $limit )
394 public function testDuplicateEntry_nothingToDuplicate() {
395 $mockDb = $this->getMockDb();
396 $mockDb->expects( $this->once() )
402 'wl_notificationtimestamp',
406 'wl_title' => 'Old_Title',
408 'WatchedItemStore::duplicateEntry',
411 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
413 $store = new WatchedItemStore(
414 $this->getMockLoadBalancer( $mockDb ),
415 $this->getMockCache()
418 $store->duplicateEntry(
419 Title
::newFromText( 'Old_Title' ),
420 Title
::newFromText( 'New_Title' )
424 public function testDuplicateEntry_somethingToDuplicate() {
426 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
427 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
430 $mockDb = $this->getMockDb();
431 $mockDb->expects( $this->at( 0 ) )
437 'wl_notificationtimestamp',
441 'wl_title' => 'Old_Title',
444 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
445 $mockDb->expects( $this->at( 1 ) )
446 ->method( 'replace' )
449 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
454 'wl_title' => 'New_Title',
455 'wl_notificationtimestamp' => '20151212010101',
460 'wl_title' => 'New_Title',
461 'wl_notificationtimestamp' => null,
464 $this->isType( 'string' )
467 $mockCache = $this->getMockCache();
468 $mockCache->expects( $this->never() )->method( 'get' );
469 $mockCache->expects( $this->never() )->method( 'delete' );
471 $store = new WatchedItemStore(
472 $this->getMockLoadBalancer( $mockDb ),
476 $store->duplicateEntry(
477 Title
::newFromText( 'Old_Title' ),
478 Title
::newFromText( 'New_Title' )
482 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
483 $mockDb = $this->getMockDb();
484 $mockDb->expects( $this->at( 0 ) )
490 'wl_notificationtimestamp',
494 'wl_title' => 'Old_Title',
497 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
498 $mockDb->expects( $this->at( 1 ) )
504 'wl_notificationtimestamp',
508 'wl_title' => 'Old_Title',
511 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
513 $mockCache = $this->getMockCache();
514 $mockCache->expects( $this->never() )->method( 'get' );
515 $mockCache->expects( $this->never() )->method( 'delete' );
517 $store = new WatchedItemStore(
518 $this->getMockLoadBalancer( $mockDb ),
522 $store->duplicateAllAssociatedEntries(
523 Title
::newFromText( 'Old_Title' ),
524 Title
::newFromText( 'New_Title' )
528 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
530 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
533 $mockDb = $this->getMockDb();
534 $mockDb->expects( $this->at( 0 ) )
540 'wl_notificationtimestamp',
544 'wl_title' => 'Old_Title',
547 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
548 $mockDb->expects( $this->at( 1 ) )
549 ->method( 'replace' )
552 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
557 'wl_title' => 'New_Title',
558 'wl_notificationtimestamp' => '20151212010101',
561 $this->isType( 'string' )
563 $mockDb->expects( $this->at( 2 ) )
569 'wl_notificationtimestamp',
573 'wl_title' => 'Old_Title',
576 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
577 $mockDb->expects( $this->at( 3 ) )
578 ->method( 'replace' )
581 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
586 'wl_title' => 'New_Title',
587 'wl_notificationtimestamp' => '20151212010101',
590 $this->isType( 'string' )
593 $mockCache = $this->getMockCache();
594 $mockCache->expects( $this->never() )->method( 'get' );
595 $mockCache->expects( $this->never() )->method( 'delete' );
597 $store = new WatchedItemStore(
598 $this->getMockLoadBalancer( $mockDb ),
602 $store->duplicateAllAssociatedEntries(
603 Title
::newFromText( 'Old_Title' ),
604 Title
::newFromText( 'New_Title' )
608 public function testAddWatch_nonAnonymousUser() {
609 $mockDb = $this->getMockDb();
610 $mockDb->expects( $this->once() )
618 'wl_title' => 'Some_Page',
619 'wl_notificationtimestamp' => null,
624 $mockCache = $this->getMockCache();
625 $mockCache->expects( $this->once() )
627 ->with( '0:Some_Page:1' );
629 $store = new WatchedItemStore(
630 $this->getMockLoadBalancer( $mockDb ),
635 $this->getMockNonAnonUserWithId( 1 ),
636 Title
::newFromText( 'Some_Page' )
640 public function testAddWatch_anonymousUser() {
641 $mockDb = $this->getMockDb();
642 $mockDb->expects( $this->never() )
643 ->method( 'insert' );
645 $mockCache = $this->getMockCache();
646 $mockCache->expects( $this->never() )
647 ->method( 'delete' );
649 $store = new WatchedItemStore(
650 $this->getMockLoadBalancer( $mockDb ),
655 $this->getAnonUser(),
656 Title
::newFromText( 'Some_Page' )
660 public function testAddWatchBatch_nonAnonymousUser() {
661 $mockDb = $this->getMockDb();
662 $mockDb->expects( $this->once() )
670 'wl_title' => 'Some_Page',
671 'wl_notificationtimestamp' => null,
676 'wl_title' => 'Some_Page',
677 'wl_notificationtimestamp' => null,
682 $mockCache = $this->getMockCache();
683 $mockCache->expects( $this->exactly( 2 ) )
684 ->method( 'delete' );
685 $mockCache->expects( $this->at( 1 ) )
687 ->with( '0:Some_Page:1' );
688 $mockCache->expects( $this->at( 3 ) )
690 ->with( '1:Some_Page:1' );
692 $store = new WatchedItemStore(
693 $this->getMockLoadBalancer( $mockDb ),
697 $mockUser = $this->getMockNonAnonUserWithId( 1 );
700 $store->addWatchBatch(
702 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
703 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
709 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
710 $mockDb = $this->getMockDb();
711 $mockDb->expects( $this->once() )
719 'wl_title' => 'Some_Page',
720 'wl_notificationtimestamp' => null,
725 $mockCache = $this->getMockCache();
726 $mockCache->expects( $this->once() )
728 ->with( '0:Some_Page:1' );
730 $store = new WatchedItemStore(
731 $this->getMockLoadBalancer( $mockDb ),
736 $store->addWatchBatch(
738 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
739 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
745 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
746 $mockDb = $this->getMockDb();
747 $mockDb->expects( $this->never() )
748 ->method( 'insert' );
750 $mockCache = $this->getMockCache();
751 $mockCache->expects( $this->never() )
752 ->method( 'delete' );
754 $store = new WatchedItemStore(
755 $this->getMockLoadBalancer( $mockDb ),
759 $anonUser = $this->getAnonUser();
761 $store->addWatchBatch(
763 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
764 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
770 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
771 $mockDb = $this->getMockDb();
772 $mockDb->expects( $this->never() )
773 ->method( 'insert' );
775 $mockCache = $this->getMockCache();
776 $mockCache->expects( $this->never() )
777 ->method( 'delete' );
779 $store = new WatchedItemStore(
780 $this->getMockLoadBalancer( $mockDb ),
785 $store->addWatchBatch( [] )
789 public function testLoadWatchedItem_existingItem() {
790 $mockDb = $this->getMockDb();
791 $mockDb->expects( $this->once() )
792 ->method( 'selectRow' )
795 'wl_notificationtimestamp',
799 'wl_title' => 'SomeDbKey',
802 ->will( $this->returnValue(
803 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
806 $mockCache = $this->getMockCache();
807 $mockCache->expects( $this->once() )
813 $store = new WatchedItemStore(
814 $this->getMockLoadBalancer( $mockDb ),
818 $watchedItem = $store->loadWatchedItem(
819 $this->getMockNonAnonUserWithId( 1 ),
820 new TitleValue( 0, 'SomeDbKey' )
822 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
823 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
824 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
825 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
828 public function testLoadWatchedItem_noItem() {
829 $mockDb = $this->getMockDb();
830 $mockDb->expects( $this->once() )
831 ->method( 'selectRow' )
834 'wl_notificationtimestamp',
838 'wl_title' => 'SomeDbKey',
841 ->will( $this->returnValue( [] ) );
843 $mockCache = $this->getMockCache();
844 $mockCache->expects( $this->never() )->method( 'get' );
845 $mockCache->expects( $this->never() )->method( 'delete' );
847 $store = new WatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
853 $store->loadWatchedItem(
854 $this->getMockNonAnonUserWithId( 1 ),
855 new TitleValue( 0, 'SomeDbKey' )
860 public function testLoadWatchedItem_anonymousUser() {
861 $mockDb = $this->getMockDb();
862 $mockDb->expects( $this->never() )
863 ->method( 'selectRow' );
865 $mockCache = $this->getMockCache();
866 $mockCache->expects( $this->never() )->method( 'get' );
867 $mockCache->expects( $this->never() )->method( 'delete' );
869 $store = new WatchedItemStore(
870 $this->getMockLoadBalancer( $mockDb ),
875 $store->loadWatchedItem(
876 $this->getAnonUser(),
877 new TitleValue( 0, 'SomeDbKey' )
882 public function testRemoveWatch_existingItem() {
883 $mockDb = $this->getMockDb();
884 $mockDb->expects( $this->once() )
891 'wl_title' => 'SomeDbKey',
894 $mockDb->expects( $this->once() )
895 ->method( 'affectedRows' )
896 ->will( $this->returnValue( 1 ) );
898 $mockCache = $this->getMockCache();
899 $mockCache->expects( $this->never() )->method( 'get' );
900 $mockCache->expects( $this->once() )
902 ->with( '0:SomeDbKey:1' );
904 $store = new WatchedItemStore(
905 $this->getMockLoadBalancer( $mockDb ),
911 $this->getMockNonAnonUserWithId( 1 ),
912 new TitleValue( 0, 'SomeDbKey' )
917 public function testRemoveWatch_noItem() {
918 $mockDb = $this->getMockDb();
919 $mockDb->expects( $this->once() )
926 'wl_title' => 'SomeDbKey',
929 $mockDb->expects( $this->once() )
930 ->method( 'affectedRows' )
931 ->will( $this->returnValue( 0 ) );
933 $mockCache = $this->getMockCache();
934 $mockCache->expects( $this->never() )->method( 'get' );
935 $mockCache->expects( $this->once() )
937 ->with( '0:SomeDbKey:1' );
939 $store = new WatchedItemStore(
940 $this->getMockLoadBalancer( $mockDb ),
946 $this->getMockNonAnonUserWithId( 1 ),
947 new TitleValue( 0, 'SomeDbKey' )
952 public function testRemoveWatch_anonymousUser() {
953 $mockDb = $this->getMockDb();
954 $mockDb->expects( $this->never() )
955 ->method( 'delete' );
957 $mockCache = $this->getMockCache();
958 $mockCache->expects( $this->never() )->method( 'get' );
959 $mockCache->expects( $this->never() )
960 ->method( 'delete' );
962 $store = new WatchedItemStore(
963 $this->getMockLoadBalancer( $mockDb ),
969 $this->getAnonUser(),
970 new TitleValue( 0, 'SomeDbKey' )
975 public function testGetWatchedItem_existingItem() {
976 $mockDb = $this->getMockDb();
977 $mockDb->expects( $this->once() )
978 ->method( 'selectRow' )
981 'wl_notificationtimestamp',
985 'wl_title' => 'SomeDbKey',
988 ->will( $this->returnValue(
989 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
992 $mockCache = $this->getMockCache();
993 $mockCache->expects( $this->never() )->method( 'delete' );
994 $mockCache->expects( $this->once() )
999 ->will( $this->returnValue( null ) );
1000 $mockCache->expects( $this->once() )
1006 $store = new WatchedItemStore(
1007 $this->getMockLoadBalancer( $mockDb ),
1011 $watchedItem = $store->getWatchedItem(
1012 $this->getMockNonAnonUserWithId( 1 ),
1013 new TitleValue( 0, 'SomeDbKey' )
1015 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1016 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1017 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1018 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1021 public function testGetWatchedItem_cachedItem() {
1022 $mockDb = $this->getMockDb();
1023 $mockDb->expects( $this->never() )
1024 ->method( 'selectRow' );
1026 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1027 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1028 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1030 $mockCache = $this->getMockCache();
1031 $mockCache->expects( $this->never() )->method( 'delete' );
1032 $mockCache->expects( $this->never() )->method( 'set' );
1033 $mockCache->expects( $this->once() )
1038 ->will( $this->returnValue( $cachedItem ) );
1040 $store = new WatchedItemStore(
1041 $this->getMockLoadBalancer( $mockDb ),
1045 $this->assertEquals(
1047 $store->getWatchedItem(
1054 public function testGetWatchedItem_noItem() {
1055 $mockDb = $this->getMockDb();
1056 $mockDb->expects( $this->once() )
1057 ->method( 'selectRow' )
1060 'wl_notificationtimestamp',
1063 'wl_namespace' => 0,
1064 'wl_title' => 'SomeDbKey',
1067 ->will( $this->returnValue( [] ) );
1069 $mockCache = $this->getMockCache();
1070 $mockCache->expects( $this->never() )->method( 'set' );
1071 $mockCache->expects( $this->never() )->method( 'delete' );
1072 $mockCache->expects( $this->once() )
1074 ->with( '0:SomeDbKey:1' )
1075 ->will( $this->returnValue( false ) );
1077 $store = new WatchedItemStore(
1078 $this->getMockLoadBalancer( $mockDb ),
1083 $store->getWatchedItem(
1084 $this->getMockNonAnonUserWithId( 1 ),
1085 new TitleValue( 0, 'SomeDbKey' )
1090 public function testGetWatchedItem_anonymousUser() {
1091 $mockDb = $this->getMockDb();
1092 $mockDb->expects( $this->never() )
1093 ->method( 'selectRow' );
1095 $mockCache = $this->getMockCache();
1096 $mockCache->expects( $this->never() )->method( 'set' );
1097 $mockCache->expects( $this->never() )->method( 'get' );
1098 $mockCache->expects( $this->never() )->method( 'delete' );
1100 $store = new WatchedItemStore(
1101 $this->getMockLoadBalancer( $mockDb ),
1106 $store->getWatchedItem(
1107 $this->getAnonUser(),
1108 new TitleValue( 0, 'SomeDbKey' )
1113 public function testIsWatchedItem_existingItem() {
1114 $mockDb = $this->getMockDb();
1115 $mockDb->expects( $this->once() )
1116 ->method( 'selectRow' )
1119 'wl_notificationtimestamp',
1122 'wl_namespace' => 0,
1123 'wl_title' => 'SomeDbKey',
1126 ->will( $this->returnValue(
1127 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1130 $mockCache = $this->getMockCache();
1131 $mockCache->expects( $this->never() )->method( 'delete' );
1132 $mockCache->expects( $this->once() )
1134 ->with( '0:SomeDbKey:1' )
1135 ->will( $this->returnValue( false ) );
1136 $mockCache->expects( $this->once() )
1142 $store = new WatchedItemStore(
1143 $this->getMockLoadBalancer( $mockDb ),
1149 $this->getMockNonAnonUserWithId( 1 ),
1150 new TitleValue( 0, 'SomeDbKey' )
1155 public function testIsWatchedItem_noItem() {
1156 $mockDb = $this->getMockDb();
1157 $mockDb->expects( $this->once() )
1158 ->method( 'selectRow' )
1161 'wl_notificationtimestamp',
1164 'wl_namespace' => 0,
1165 'wl_title' => 'SomeDbKey',
1168 ->will( $this->returnValue( [] ) );
1170 $mockCache = $this->getMockCache();
1171 $mockCache->expects( $this->never() )->method( 'set' );
1172 $mockCache->expects( $this->never() )->method( 'delete' );
1173 $mockCache->expects( $this->once() )
1175 ->with( '0:SomeDbKey:1' )
1176 ->will( $this->returnValue( false ) );
1178 $store = new WatchedItemStore(
1179 $this->getMockLoadBalancer( $mockDb ),
1185 $this->getMockNonAnonUserWithId( 1 ),
1186 new TitleValue( 0, 'SomeDbKey' )
1191 public function testIsWatchedItem_anonymousUser() {
1192 $mockDb = $this->getMockDb();
1193 $mockDb->expects( $this->never() )
1194 ->method( 'selectRow' );
1196 $mockCache = $this->getMockCache();
1197 $mockCache->expects( $this->never() )->method( 'set' );
1198 $mockCache->expects( $this->never() )->method( 'get' );
1199 $mockCache->expects( $this->never() )->method( 'delete' );
1201 $store = new WatchedItemStore(
1202 $this->getMockLoadBalancer( $mockDb ),
1208 $this->getAnonUser(),
1209 new TitleValue( 0, 'SomeDbKey' )
1214 public function testResetNotificationTimestamp_anonymousUser() {
1215 $mockDb = $this->getMockDb();
1216 $mockDb->expects( $this->never() )
1217 ->method( 'selectRow' );
1219 $mockCache = $this->getMockCache();
1220 $mockCache->expects( $this->never() )->method( 'get' );
1221 $mockCache->expects( $this->never() )->method( 'set' );
1222 $mockCache->expects( $this->never() )->method( 'delete' );
1224 $store = new WatchedItemStore(
1225 $this->getMockLoadBalancer( $mockDb ),
1230 $store->resetNotificationTimestamp(
1231 $this->getAnonUser(),
1232 Title
::newFromText( 'SomeDbKey' )
1237 public function testResetNotificationTimestamp_noItem() {
1238 $mockDb = $this->getMockDb();
1239 $mockDb->expects( $this->once() )
1240 ->method( 'selectRow' )
1243 'wl_notificationtimestamp',
1246 'wl_namespace' => 0,
1247 'wl_title' => 'SomeDbKey',
1250 ->will( $this->returnValue( [] ) );
1252 $mockCache = $this->getMockCache();
1253 $mockCache->expects( $this->never() )->method( 'get' );
1254 $mockCache->expects( $this->never() )->method( 'set' );
1255 $mockCache->expects( $this->never() )->method( 'delete' );
1257 $store = new WatchedItemStore(
1258 $this->getMockLoadBalancer( $mockDb ),
1263 $store->resetNotificationTimestamp(
1264 $this->getMockNonAnonUserWithId( 1 ),
1265 Title
::newFromText( 'SomeDbKey' )
1270 public function testResetNotificationTimestamp_item() {
1271 $user = $this->getMockNonAnonUserWithId( 1 );
1272 $title = Title
::newFromText( 'SomeDbKey' );
1274 $mockDb = $this->getMockDb();
1275 $mockDb->expects( $this->once() )
1276 ->method( 'selectRow' )
1279 'wl_notificationtimestamp',
1282 'wl_namespace' => 0,
1283 'wl_title' => 'SomeDbKey',
1286 ->will( $this->returnValue(
1287 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1290 $mockCache = $this->getMockCache();
1291 $mockCache->expects( $this->never() )->method( 'get' );
1292 $mockCache->expects( $this->once() )
1296 $this->isInstanceOf( WatchedItem
::class )
1298 $mockCache->expects( $this->once() )
1299 ->method( 'delete' )
1300 ->with( '0:SomeDbKey:1' );
1302 $store = new WatchedItemStore(
1303 $this->getMockLoadBalancer( $mockDb ),
1307 // Note: This does not actually assert the job is correct
1308 $callableCallCounter = 0;
1309 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1310 $callableCallCounter++
;
1311 $this->assertInternalType( 'callable', $callable );
1313 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1316 $store->resetNotificationTimestamp(
1321 $this->assertEquals( 1, $callableCallCounter );
1323 ScopedCallback
::consume( $scopedOverride );
1326 public function testResetNotificationTimestamp_noItemForced() {
1327 $user = $this->getMockNonAnonUserWithId( 1 );
1328 $title = Title
::newFromText( 'SomeDbKey' );
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->never() )
1332 ->method( 'selectRow' );
1334 $mockCache = $this->getMockCache();
1335 $mockDb->expects( $this->never() )
1337 $mockDb->expects( $this->never() )
1339 $mockDb->expects( $this->never() )
1340 ->method( 'delete' );
1342 $store = new WatchedItemStore(
1343 $this->getMockLoadBalancer( $mockDb ),
1347 // Note: This does not actually assert the job is correct
1348 $callableCallCounter = 0;
1349 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1350 $callableCallCounter++
;
1351 $this->assertInternalType( 'callable', $callable );
1353 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1356 $store->resetNotificationTimestamp(
1362 $this->assertEquals( 1, $callableCallCounter );
1364 ScopedCallback
::consume( $scopedOverride );
1371 * @return PHPUnit_Framework_MockObject_MockObject|Title
1373 private function getMockTitle( $text, $ns = 0 ) {
1374 $title = $this->getMock( Title
::class );
1375 $title->expects( $this->any() )
1376 ->method( 'getText' )
1377 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1378 $title->expects( $this->any() )
1379 ->method( 'getDbKey' )
1380 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1381 $title->expects( $this->any() )
1382 ->method( 'getNamespace' )
1383 ->will( $this->returnValue( $ns ) );
1387 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1388 $user = $this->getMockNonAnonUserWithId( 1 );
1390 $title = $this->getMockTitle( 'SomeTitle' );
1391 $title->expects( $this->once() )
1392 ->method( 'getNextRevisionID' )
1394 ->will( $this->returnValue( false ) );
1396 $mockDb = $this->getMockDb();
1397 $mockDb->expects( $this->never() )
1398 ->method( 'selectRow' );
1400 $mockCache = $this->getMockCache();
1401 $mockDb->expects( $this->never() )
1403 $mockDb->expects( $this->never() )
1405 $mockDb->expects( $this->never() )
1406 ->method( 'delete' );
1408 $store = new WatchedItemStore(
1409 $this->getMockLoadBalancer( $mockDb ),
1413 // Note: This does not actually assert the job is correct
1414 $callableCallCounter = 0;
1415 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1416 function( $callable ) use ( &$callableCallCounter ) {
1417 $callableCallCounter++
;
1418 $this->assertInternalType( 'callable', $callable );
1423 $store->resetNotificationTimestamp(
1430 $this->assertEquals( 1, $callableCallCounter );
1432 ScopedCallback
::consume( $scopedOverride );
1435 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1436 $user = $this->getMockNonAnonUserWithId( 1 );
1438 $title = $this->getMockTitle( 'SomeDbKey' );
1439 $title->expects( $this->once() )
1440 ->method( 'getNextRevisionID' )
1442 ->will( $this->returnValue( 33 ) );
1444 $mockDb = $this->getMockDb();
1445 $mockDb->expects( $this->once() )
1446 ->method( 'selectRow' )
1449 'wl_notificationtimestamp',
1452 'wl_namespace' => 0,
1453 'wl_title' => 'SomeDbKey',
1456 ->will( $this->returnValue(
1457 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1460 $mockCache = $this->getMockCache();
1461 $mockDb->expects( $this->never() )
1463 $mockDb->expects( $this->never() )
1465 $mockDb->expects( $this->never() )
1466 ->method( 'delete' );
1468 $store = new WatchedItemStore(
1469 $this->getMockLoadBalancer( $mockDb ),
1473 // Note: This does not actually assert the job is correct
1474 $addUpdateCallCounter = 0;
1475 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1476 function( $callable ) use ( &$addUpdateCallCounter ) {
1477 $addUpdateCallCounter++
;
1478 $this->assertInternalType( 'callable', $callable );
1482 $getTimestampCallCounter = 0;
1483 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
1484 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1485 $getTimestampCallCounter++
;
1486 $this->assertEquals( $title, $titleParam );
1487 $this->assertEquals( $oldid, $oldidParam );
1492 $store->resetNotificationTimestamp(
1499 $this->assertEquals( 1, $addUpdateCallCounter );
1500 $this->assertEquals( 1, $getTimestampCallCounter );
1502 ScopedCallback
::consume( $scopedOverrideDeferred );
1503 ScopedCallback
::consume( $scopedOverrideRevision );
1506 public function testUpdateNotificationTimestamp_watchersExist() {
1507 $mockDb = $this->getMockDb();
1508 $mockDb->expects( $this->once() )
1509 ->method( 'select' )
1515 'wl_namespace' => 0,
1516 'wl_title' => 'SomeDbKey',
1517 'wl_notificationtimestamp IS NULL'
1521 $this->returnValue( [
1522 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1523 $this->getFakeRow( [ 'wl_user' => '3' ] )
1526 $mockDb->expects( $this->once() )
1527 ->method( 'onTransactionIdle' )
1528 ->with( $this->isType( 'callable' ) )
1529 ->will( $this->returnCallback( function( $callable ) {
1532 $mockDb->expects( $this->once() )
1533 ->method( 'update' )
1536 [ 'wl_notificationtimestamp' => null ],
1538 'wl_user' => [ 2, 3 ],
1539 'wl_namespace' => 0,
1540 'wl_title' => 'SomeDbKey',
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 = new WatchedItemStore(
1550 $this->getMockLoadBalancer( $mockDb ),
1554 $this->assertEquals(
1556 $store->updateNotificationTimestamp(
1557 $this->getMockNonAnonUserWithId( 1 ),
1558 new TitleValue( 0, 'SomeDbKey' ),
1564 public function testUpdateNotificationTimestamp_noWatchers() {
1565 $mockDb = $this->getMockDb();
1566 $mockDb->expects( $this->once() )
1567 ->method( 'select' )
1573 'wl_namespace' => 0,
1574 'wl_title' => 'SomeDbKey',
1575 'wl_notificationtimestamp IS NULL'
1579 $this->returnValue( [] )
1581 $mockDb->expects( $this->never() )
1582 ->method( 'onTransactionIdle' );
1583 $mockDb->expects( $this->never() )
1584 ->method( 'update' );
1586 $mockCache = $this->getMockCache();
1587 $mockCache->expects( $this->never() )->method( 'set' );
1588 $mockCache->expects( $this->never() )->method( 'get' );
1589 $mockCache->expects( $this->never() )->method( 'delete' );
1591 $store = new WatchedItemStore(
1592 $this->getMockLoadBalancer( $mockDb ),
1596 $watchers = $store->updateNotificationTimestamp(
1597 $this->getMockNonAnonUserWithId( 1 ),
1598 new TitleValue( 0, 'SomeDbKey' ),
1601 $this->assertInternalType( 'array', $watchers );
1602 $this->assertEmpty( $watchers );
1605 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1606 $user = $this->getMockNonAnonUserWithId( 1 );
1607 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1609 $mockDb = $this->getMockDb();
1610 $mockDb->expects( $this->once() )
1611 ->method( 'selectRow' )
1612 ->will( $this->returnValue(
1613 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1615 $mockDb->expects( $this->once() )
1616 ->method( 'select' )
1618 $this->returnValue( [
1619 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1620 $this->getFakeRow( [ 'wl_user' => '3' ] )
1623 $mockDb->expects( $this->once() )
1624 ->method( 'onTransactionIdle' )
1625 ->with( $this->isType( 'callable' ) )
1626 ->will( $this->returnCallback( function( $callable ) {
1629 $mockDb->expects( $this->once() )
1630 ->method( 'update' );
1632 $mockCache = $this->getMockCache();
1633 $mockCache->expects( $this->once() )
1635 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1636 $mockCache->expects( $this->once() )
1638 ->with( '0:SomeDbKey:1' );
1639 $mockCache->expects( $this->once() )
1640 ->method( 'delete' )
1641 ->with( '0:SomeDbKey:1' );
1643 $store = new WatchedItemStore(
1644 $this->getMockLoadBalancer( $mockDb ),
1648 // This will add the item to the cache
1649 $store->getWatchedItem( $user, $titleValue );
1651 $store->updateNotificationTimestamp(
1652 $this->getMockNonAnonUserWithId( 1 ),