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 testCountWatchers() {
85 $titleValue = new TitleValue( 0, 'SomeDbKey' );
87 $mockDb = $this->getMockDb();
88 $mockDb->expects( $this->exactly( 1 ) )
89 ->method( 'selectField' )
94 'wl_namespace' => $titleValue->getNamespace(),
95 'wl_title' => $titleValue->getDBkey(),
97 $this->isType( 'string' )
99 ->will( $this->returnValue( 7 ) );
101 $mockCache = $this->getMockCache();
102 $mockCache->expects( $this->never() )->method( 'get' );
103 $mockCache->expects( $this->never() )->method( 'set' );
104 $mockCache->expects( $this->never() )->method( 'delete' );
106 $store = new WatchedItemStore(
107 $this->getMockLoadBalancer( $mockDb ),
111 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
114 public function testCountWatchersMultiple() {
116 new TitleValue( 0, 'SomeDbKey' ),
117 new TitleValue( 0, 'OtherDbKey' ),
118 new TitleValue( 1, 'AnotherDbKey' ),
121 $mockDb = $this->getMockDb();
124 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
125 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
126 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
129 $mockDb->expects( $this->once() )
130 ->method( 'makeWhereFrom2d' )
132 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
133 $this->isType( 'string' ),
134 $this->isType( 'string' )
136 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
137 $mockDb->expects( $this->once() )
141 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
142 [ 'makeWhereFrom2d return value' ],
143 $this->isType( 'string' ),
145 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
149 $this->returnValue( $dbResult )
152 $mockCache = $this->getMockCache();
153 $mockCache->expects( $this->never() )->method( 'get' );
154 $mockCache->expects( $this->never() )->method( 'set' );
155 $mockCache->expects( $this->never() )->method( 'delete' );
157 $store = new WatchedItemStore(
158 $this->getMockLoadBalancer( $mockDb ),
163 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
164 1 => [ 'AnotherDbKey' => 500 ],
166 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
169 public function provideIntWithDbUnsafeVersion() {
172 [ "50; DROP TABLE watchlist;\n--" ],
177 * @dataProvider provideIntWithDbUnsafeVersion
179 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
181 new TitleValue( 0, 'SomeDbKey' ),
182 new TitleValue( 0, 'OtherDbKey' ),
183 new TitleValue( 1, 'AnotherDbKey' ),
186 $mockDb = $this->getMockDb();
189 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
190 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
191 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
194 $mockDb->expects( $this->once() )
195 ->method( 'makeWhereFrom2d' )
197 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
198 $this->isType( 'string' ),
199 $this->isType( 'string' )
201 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
202 $mockDb->expects( $this->once() )
206 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
207 [ 'makeWhereFrom2d return value' ],
208 $this->isType( 'string' ),
210 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
211 'HAVING' => 'COUNT(*) >= 50',
215 $this->returnValue( $dbResult )
218 $mockCache = $this->getMockCache();
219 $mockCache->expects( $this->never() )->method( 'get' );
220 $mockCache->expects( $this->never() )->method( 'set' );
221 $mockCache->expects( $this->never() )->method( 'delete' );
223 $store = new WatchedItemStore(
224 $this->getMockLoadBalancer( $mockDb ),
229 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
230 1 => [ 'AnotherDbKey' => 500 ],
234 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
238 public function testCountVisitingWatchers() {
239 $titleValue = new TitleValue( 0, 'SomeDbKey' );
241 $mockDb = $this->getMockDb();
242 $mockDb->expects( $this->exactly( 1 ) )
243 ->method( 'selectField' )
248 'wl_namespace' => $titleValue->getNamespace(),
249 'wl_title' => $titleValue->getDBkey(),
250 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
252 $this->isType( 'string' )
254 ->will( $this->returnValue( 7 ) );
255 $mockDb->expects( $this->exactly( 1 ) )
256 ->method( 'addQuotes' )
257 ->will( $this->returnCallback( function( $value ) {
260 $mockDb->expects( $this->exactly( 1 ) )
261 ->method( 'timestamp' )
262 ->will( $this->returnCallback( function( $value ) {
263 return 'TS' . $value . 'TS';
266 $mockCache = $this->getMockCache();
267 $mockCache->expects( $this->never() )->method( 'set' );
268 $mockCache->expects( $this->never() )->method( 'get' );
269 $mockCache->expects( $this->never() )->method( 'delete' );
271 $store = new WatchedItemStore(
272 $this->getMockLoadBalancer( $mockDb ),
276 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
279 public function testCountUnreadNotifications() {
280 $user = $this->getMockNonAnonUserWithId( 1 );
282 $mockDb = $this->getMockDb();
283 $mockDb->expects( $this->exactly( 1 ) )
284 ->method( 'selectRowCount' )
289 "wl_notificationtimestamp IS NOT NULL",
292 $this->isType( 'string' )
294 ->will( $this->returnValue( 9 ) );
296 $mockCache = $this->getMockCache();
297 $mockCache->expects( $this->never() )->method( 'set' );
298 $mockCache->expects( $this->never() )->method( 'get' );
299 $mockCache->expects( $this->never() )->method( 'delete' );
301 $store = new WatchedItemStore(
302 $this->getMockLoadBalancer( $mockDb ),
306 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
310 * @dataProvider provideIntWithDbUnsafeVersion
312 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
313 $user = $this->getMockNonAnonUserWithId( 1 );
315 $mockDb = $this->getMockDb();
316 $mockDb->expects( $this->exactly( 1 ) )
317 ->method( 'selectRowCount' )
322 "wl_notificationtimestamp IS NOT NULL",
325 $this->isType( 'string' ),
328 ->will( $this->returnValue( 50 ) );
330 $mockCache = $this->getMockCache();
331 $mockCache->expects( $this->never() )->method( 'set' );
332 $mockCache->expects( $this->never() )->method( 'get' );
333 $mockCache->expects( $this->never() )->method( 'delete' );
335 $store = new WatchedItemStore(
336 $this->getMockLoadBalancer( $mockDb ),
342 $store->countUnreadNotifications( $user, $limit )
347 * @dataProvider provideIntWithDbUnsafeVersion
349 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
350 $user = $this->getMockNonAnonUserWithId( 1 );
352 $mockDb = $this->getMockDb();
353 $mockDb->expects( $this->exactly( 1 ) )
354 ->method( 'selectRowCount' )
359 "wl_notificationtimestamp IS NOT NULL",
362 $this->isType( 'string' ),
365 ->will( $this->returnValue( 9 ) );
367 $mockCache = $this->getMockCache();
368 $mockCache->expects( $this->never() )->method( 'set' );
369 $mockCache->expects( $this->never() )->method( 'get' );
370 $mockCache->expects( $this->never() )->method( 'delete' );
372 $store = new WatchedItemStore(
373 $this->getMockLoadBalancer( $mockDb ),
379 $store->countUnreadNotifications( $user, $limit )
383 public function testDuplicateEntry_nothingToDuplicate() {
384 $mockDb = $this->getMockDb();
385 $mockDb->expects( $this->once() )
391 'wl_notificationtimestamp',
395 'wl_title' => 'Old_Title',
397 'WatchedItemStore::duplicateEntry',
400 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
402 $store = new WatchedItemStore(
403 $this->getMockLoadBalancer( $mockDb ),
404 $this->getMockCache()
407 $store->duplicateEntry(
408 Title
::newFromText( 'Old_Title' ),
409 Title
::newFromText( 'New_Title' )
413 public function testDuplicateEntry_somethingToDuplicate() {
415 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
416 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
419 $mockDb = $this->getMockDb();
420 $mockDb->expects( $this->at( 0 ) )
426 'wl_notificationtimestamp',
430 'wl_title' => 'Old_Title',
433 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
434 $mockDb->expects( $this->at( 1 ) )
435 ->method( 'replace' )
438 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
443 'wl_title' => 'New_Title',
444 'wl_notificationtimestamp' => '20151212010101',
449 'wl_title' => 'New_Title',
450 'wl_notificationtimestamp' => null,
453 $this->isType( 'string' )
456 $mockCache = $this->getMockCache();
457 $mockCache->expects( $this->never() )->method( 'get' );
458 $mockCache->expects( $this->never() )->method( 'delete' );
460 $store = new WatchedItemStore(
461 $this->getMockLoadBalancer( $mockDb ),
465 $store->duplicateEntry(
466 Title
::newFromText( 'Old_Title' ),
467 Title
::newFromText( 'New_Title' )
471 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
472 $mockDb = $this->getMockDb();
473 $mockDb->expects( $this->at( 0 ) )
479 'wl_notificationtimestamp',
483 'wl_title' => 'Old_Title',
486 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
487 $mockDb->expects( $this->at( 1 ) )
493 'wl_notificationtimestamp',
497 'wl_title' => 'Old_Title',
500 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
502 $mockCache = $this->getMockCache();
503 $mockCache->expects( $this->never() )->method( 'get' );
504 $mockCache->expects( $this->never() )->method( 'delete' );
506 $store = new WatchedItemStore(
507 $this->getMockLoadBalancer( $mockDb ),
511 $store->duplicateAllAssociatedEntries(
512 Title
::newFromText( 'Old_Title' ),
513 Title
::newFromText( 'New_Title' )
517 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
519 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
522 $mockDb = $this->getMockDb();
523 $mockDb->expects( $this->at( 0 ) )
529 'wl_notificationtimestamp',
533 'wl_title' => 'Old_Title',
536 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
537 $mockDb->expects( $this->at( 1 ) )
538 ->method( 'replace' )
541 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
546 'wl_title' => 'New_Title',
547 'wl_notificationtimestamp' => '20151212010101',
550 $this->isType( 'string' )
552 $mockDb->expects( $this->at( 2 ) )
558 'wl_notificationtimestamp',
562 'wl_title' => 'Old_Title',
565 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
566 $mockDb->expects( $this->at( 3 ) )
567 ->method( 'replace' )
570 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
575 'wl_title' => 'New_Title',
576 'wl_notificationtimestamp' => '20151212010101',
579 $this->isType( 'string' )
582 $mockCache = $this->getMockCache();
583 $mockCache->expects( $this->never() )->method( 'get' );
584 $mockCache->expects( $this->never() )->method( 'delete' );
586 $store = new WatchedItemStore(
587 $this->getMockLoadBalancer( $mockDb ),
591 $store->duplicateAllAssociatedEntries(
592 Title
::newFromText( 'Old_Title' ),
593 Title
::newFromText( 'New_Title' )
597 public function testAddWatch_nonAnonymousUser() {
598 $mockDb = $this->getMockDb();
599 $mockDb->expects( $this->once() )
607 'wl_title' => 'Some_Page',
608 'wl_notificationtimestamp' => null,
613 $mockCache = $this->getMockCache();
614 $mockCache->expects( $this->once() )
616 ->with( '0:Some_Page:1' );
618 $store = new WatchedItemStore(
619 $this->getMockLoadBalancer( $mockDb ),
624 $this->getMockNonAnonUserWithId( 1 ),
625 Title
::newFromText( 'Some_Page' )
629 public function testAddWatch_anonymousUser() {
630 $mockDb = $this->getMockDb();
631 $mockDb->expects( $this->never() )
632 ->method( 'insert' );
634 $mockCache = $this->getMockCache();
635 $mockCache->expects( $this->never() )
636 ->method( 'delete' );
638 $store = new WatchedItemStore(
639 $this->getMockLoadBalancer( $mockDb ),
644 $this->getAnonUser(),
645 Title
::newFromText( 'Some_Page' )
649 public function testAddWatchBatch_nonAnonymousUser() {
650 $mockDb = $this->getMockDb();
651 $mockDb->expects( $this->once() )
659 'wl_title' => 'Some_Page',
660 'wl_notificationtimestamp' => null,
665 'wl_title' => 'Some_Page',
666 'wl_notificationtimestamp' => null,
671 $mockCache = $this->getMockCache();
672 $mockCache->expects( $this->exactly( 2 ) )
673 ->method( 'delete' );
674 $mockCache->expects( $this->at( 1 ) )
676 ->with( '0:Some_Page:1' );
677 $mockCache->expects( $this->at( 3 ) )
679 ->with( '1:Some_Page:1' );
681 $store = new WatchedItemStore(
682 $this->getMockLoadBalancer( $mockDb ),
686 $mockUser = $this->getMockNonAnonUserWithId( 1 );
689 $store->addWatchBatch(
691 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
692 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
698 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
699 $mockDb = $this->getMockDb();
700 $mockDb->expects( $this->once() )
708 'wl_title' => 'Some_Page',
709 'wl_notificationtimestamp' => null,
714 $mockCache = $this->getMockCache();
715 $mockCache->expects( $this->once() )
717 ->with( '0:Some_Page:1' );
719 $store = new WatchedItemStore(
720 $this->getMockLoadBalancer( $mockDb ),
725 $store->addWatchBatch(
727 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
728 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
734 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
735 $mockDb = $this->getMockDb();
736 $mockDb->expects( $this->never() )
737 ->method( 'insert' );
739 $mockCache = $this->getMockCache();
740 $mockCache->expects( $this->never() )
741 ->method( 'delete' );
743 $store = new WatchedItemStore(
744 $this->getMockLoadBalancer( $mockDb ),
748 $anonUser = $this->getAnonUser();
750 $store->addWatchBatch(
752 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
753 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
759 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
760 $mockDb = $this->getMockDb();
761 $mockDb->expects( $this->never() )
762 ->method( 'insert' );
764 $mockCache = $this->getMockCache();
765 $mockCache->expects( $this->never() )
766 ->method( 'delete' );
768 $store = new WatchedItemStore(
769 $this->getMockLoadBalancer( $mockDb ),
774 $store->addWatchBatch( [] )
778 public function testLoadWatchedItem_existingItem() {
779 $mockDb = $this->getMockDb();
780 $mockDb->expects( $this->once() )
781 ->method( 'selectRow' )
784 'wl_notificationtimestamp',
788 'wl_title' => 'SomeDbKey',
791 ->will( $this->returnValue(
792 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
795 $mockCache = $this->getMockCache();
796 $mockCache->expects( $this->once() )
802 $store = new WatchedItemStore(
803 $this->getMockLoadBalancer( $mockDb ),
807 $watchedItem = $store->loadWatchedItem(
808 $this->getMockNonAnonUserWithId( 1 ),
809 new TitleValue( 0, 'SomeDbKey' )
811 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
812 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
813 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
814 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
817 public function testLoadWatchedItem_noItem() {
818 $mockDb = $this->getMockDb();
819 $mockDb->expects( $this->once() )
820 ->method( 'selectRow' )
823 'wl_notificationtimestamp',
827 'wl_title' => 'SomeDbKey',
830 ->will( $this->returnValue( [] ) );
832 $mockCache = $this->getMockCache();
833 $mockCache->expects( $this->never() )->method( 'get' );
834 $mockCache->expects( $this->never() )->method( 'delete' );
836 $store = new WatchedItemStore(
837 $this->getMockLoadBalancer( $mockDb ),
842 $store->loadWatchedItem(
843 $this->getMockNonAnonUserWithId( 1 ),
844 new TitleValue( 0, 'SomeDbKey' )
849 public function testLoadWatchedItem_anonymousUser() {
850 $mockDb = $this->getMockDb();
851 $mockDb->expects( $this->never() )
852 ->method( 'selectRow' );
854 $mockCache = $this->getMockCache();
855 $mockCache->expects( $this->never() )->method( 'get' );
856 $mockCache->expects( $this->never() )->method( 'delete' );
858 $store = new WatchedItemStore(
859 $this->getMockLoadBalancer( $mockDb ),
864 $store->loadWatchedItem(
865 $this->getAnonUser(),
866 new TitleValue( 0, 'SomeDbKey' )
871 public function testRemoveWatch_existingItem() {
872 $mockDb = $this->getMockDb();
873 $mockDb->expects( $this->once() )
880 'wl_title' => 'SomeDbKey',
883 $mockDb->expects( $this->once() )
884 ->method( 'affectedRows' )
885 ->will( $this->returnValue( 1 ) );
887 $mockCache = $this->getMockCache();
888 $mockCache->expects( $this->never() )->method( 'get' );
889 $mockCache->expects( $this->once() )
891 ->with( '0:SomeDbKey:1' );
893 $store = new WatchedItemStore(
894 $this->getMockLoadBalancer( $mockDb ),
900 $this->getMockNonAnonUserWithId( 1 ),
901 new TitleValue( 0, 'SomeDbKey' )
906 public function testRemoveWatch_noItem() {
907 $mockDb = $this->getMockDb();
908 $mockDb->expects( $this->once() )
915 'wl_title' => 'SomeDbKey',
918 $mockDb->expects( $this->once() )
919 ->method( 'affectedRows' )
920 ->will( $this->returnValue( 0 ) );
922 $mockCache = $this->getMockCache();
923 $mockCache->expects( $this->never() )->method( 'get' );
924 $mockCache->expects( $this->once() )
926 ->with( '0:SomeDbKey:1' );
928 $store = new WatchedItemStore(
929 $this->getMockLoadBalancer( $mockDb ),
935 $this->getMockNonAnonUserWithId( 1 ),
936 new TitleValue( 0, 'SomeDbKey' )
941 public function testRemoveWatch_anonymousUser() {
942 $mockDb = $this->getMockDb();
943 $mockDb->expects( $this->never() )
944 ->method( 'delete' );
946 $mockCache = $this->getMockCache();
947 $mockCache->expects( $this->never() )->method( 'get' );
948 $mockCache->expects( $this->never() )
949 ->method( 'delete' );
951 $store = new WatchedItemStore(
952 $this->getMockLoadBalancer( $mockDb ),
958 $this->getAnonUser(),
959 new TitleValue( 0, 'SomeDbKey' )
964 public function testGetWatchedItem_existingItem() {
965 $mockDb = $this->getMockDb();
966 $mockDb->expects( $this->once() )
967 ->method( 'selectRow' )
970 'wl_notificationtimestamp',
974 'wl_title' => 'SomeDbKey',
977 ->will( $this->returnValue(
978 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
981 $mockCache = $this->getMockCache();
982 $mockCache->expects( $this->never() )->method( 'delete' );
983 $mockCache->expects( $this->once() )
988 ->will( $this->returnValue( null ) );
989 $mockCache->expects( $this->once() )
995 $store = new WatchedItemStore(
996 $this->getMockLoadBalancer( $mockDb ),
1000 $watchedItem = $store->getWatchedItem(
1001 $this->getMockNonAnonUserWithId( 1 ),
1002 new TitleValue( 0, 'SomeDbKey' )
1004 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1005 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1006 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1007 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1010 public function testGetWatchedItem_cachedItem() {
1011 $mockDb = $this->getMockDb();
1012 $mockDb->expects( $this->never() )
1013 ->method( 'selectRow' );
1015 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1016 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1017 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1019 $mockCache = $this->getMockCache();
1020 $mockCache->expects( $this->never() )->method( 'delete' );
1021 $mockCache->expects( $this->never() )->method( 'set' );
1022 $mockCache->expects( $this->once() )
1027 ->will( $this->returnValue( $cachedItem ) );
1029 $store = new WatchedItemStore(
1030 $this->getMockLoadBalancer( $mockDb ),
1034 $this->assertEquals(
1036 $store->getWatchedItem(
1043 public function testGetWatchedItem_noItem() {
1044 $mockDb = $this->getMockDb();
1045 $mockDb->expects( $this->once() )
1046 ->method( 'selectRow' )
1049 'wl_notificationtimestamp',
1052 'wl_namespace' => 0,
1053 'wl_title' => 'SomeDbKey',
1056 ->will( $this->returnValue( [] ) );
1058 $mockCache = $this->getMockCache();
1059 $mockCache->expects( $this->never() )->method( 'set' );
1060 $mockCache->expects( $this->never() )->method( 'delete' );
1061 $mockCache->expects( $this->once() )
1063 ->with( '0:SomeDbKey:1' )
1064 ->will( $this->returnValue( false ) );
1066 $store = new WatchedItemStore(
1067 $this->getMockLoadBalancer( $mockDb ),
1072 $store->getWatchedItem(
1073 $this->getMockNonAnonUserWithId( 1 ),
1074 new TitleValue( 0, 'SomeDbKey' )
1079 public function testGetWatchedItem_anonymousUser() {
1080 $mockDb = $this->getMockDb();
1081 $mockDb->expects( $this->never() )
1082 ->method( 'selectRow' );
1084 $mockCache = $this->getMockCache();
1085 $mockCache->expects( $this->never() )->method( 'set' );
1086 $mockCache->expects( $this->never() )->method( 'get' );
1087 $mockCache->expects( $this->never() )->method( 'delete' );
1089 $store = new WatchedItemStore(
1090 $this->getMockLoadBalancer( $mockDb ),
1095 $store->getWatchedItem(
1096 $this->getAnonUser(),
1097 new TitleValue( 0, 'SomeDbKey' )
1102 public function testIsWatchedItem_existingItem() {
1103 $mockDb = $this->getMockDb();
1104 $mockDb->expects( $this->once() )
1105 ->method( 'selectRow' )
1108 'wl_notificationtimestamp',
1111 'wl_namespace' => 0,
1112 'wl_title' => 'SomeDbKey',
1115 ->will( $this->returnValue(
1116 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1119 $mockCache = $this->getMockCache();
1120 $mockCache->expects( $this->never() )->method( 'delete' );
1121 $mockCache->expects( $this->once() )
1123 ->with( '0:SomeDbKey:1' )
1124 ->will( $this->returnValue( false ) );
1125 $mockCache->expects( $this->once() )
1131 $store = new WatchedItemStore(
1132 $this->getMockLoadBalancer( $mockDb ),
1138 $this->getMockNonAnonUserWithId( 1 ),
1139 new TitleValue( 0, 'SomeDbKey' )
1144 public function testIsWatchedItem_noItem() {
1145 $mockDb = $this->getMockDb();
1146 $mockDb->expects( $this->once() )
1147 ->method( 'selectRow' )
1150 'wl_notificationtimestamp',
1153 'wl_namespace' => 0,
1154 'wl_title' => 'SomeDbKey',
1157 ->will( $this->returnValue( [] ) );
1159 $mockCache = $this->getMockCache();
1160 $mockCache->expects( $this->never() )->method( 'set' );
1161 $mockCache->expects( $this->never() )->method( 'delete' );
1162 $mockCache->expects( $this->once() )
1164 ->with( '0:SomeDbKey:1' )
1165 ->will( $this->returnValue( false ) );
1167 $store = new WatchedItemStore(
1168 $this->getMockLoadBalancer( $mockDb ),
1174 $this->getMockNonAnonUserWithId( 1 ),
1175 new TitleValue( 0, 'SomeDbKey' )
1180 public function testIsWatchedItem_anonymousUser() {
1181 $mockDb = $this->getMockDb();
1182 $mockDb->expects( $this->never() )
1183 ->method( 'selectRow' );
1185 $mockCache = $this->getMockCache();
1186 $mockCache->expects( $this->never() )->method( 'set' );
1187 $mockCache->expects( $this->never() )->method( 'get' );
1188 $mockCache->expects( $this->never() )->method( 'delete' );
1190 $store = new WatchedItemStore(
1191 $this->getMockLoadBalancer( $mockDb ),
1197 $this->getAnonUser(),
1198 new TitleValue( 0, 'SomeDbKey' )
1203 public function testResetNotificationTimestamp_anonymousUser() {
1204 $mockDb = $this->getMockDb();
1205 $mockDb->expects( $this->never() )
1206 ->method( 'selectRow' );
1208 $mockCache = $this->getMockCache();
1209 $mockCache->expects( $this->never() )->method( 'get' );
1210 $mockCache->expects( $this->never() )->method( 'set' );
1211 $mockCache->expects( $this->never() )->method( 'delete' );
1213 $store = new WatchedItemStore(
1214 $this->getMockLoadBalancer( $mockDb ),
1219 $store->resetNotificationTimestamp(
1220 $this->getAnonUser(),
1221 Title
::newFromText( 'SomeDbKey' )
1226 public function testResetNotificationTimestamp_noItem() {
1227 $mockDb = $this->getMockDb();
1228 $mockDb->expects( $this->once() )
1229 ->method( 'selectRow' )
1232 'wl_notificationtimestamp',
1235 'wl_namespace' => 0,
1236 'wl_title' => 'SomeDbKey',
1239 ->will( $this->returnValue( [] ) );
1241 $mockCache = $this->getMockCache();
1242 $mockCache->expects( $this->never() )->method( 'get' );
1243 $mockCache->expects( $this->never() )->method( 'set' );
1244 $mockCache->expects( $this->never() )->method( 'delete' );
1246 $store = new WatchedItemStore(
1247 $this->getMockLoadBalancer( $mockDb ),
1252 $store->resetNotificationTimestamp(
1253 $this->getMockNonAnonUserWithId( 1 ),
1254 Title
::newFromText( 'SomeDbKey' )
1259 public function testResetNotificationTimestamp_item() {
1260 $user = $this->getMockNonAnonUserWithId( 1 );
1261 $title = Title
::newFromText( 'SomeDbKey' );
1263 $mockDb = $this->getMockDb();
1264 $mockDb->expects( $this->once() )
1265 ->method( 'selectRow' )
1268 'wl_notificationtimestamp',
1271 'wl_namespace' => 0,
1272 'wl_title' => 'SomeDbKey',
1275 ->will( $this->returnValue(
1276 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1279 $mockCache = $this->getMockCache();
1280 $mockCache->expects( $this->never() )->method( 'get' );
1281 $mockCache->expects( $this->once() )
1285 $this->isInstanceOf( WatchedItem
::class )
1287 $mockCache->expects( $this->once() )
1288 ->method( 'delete' )
1289 ->with( '0:SomeDbKey:1' );
1291 $store = new WatchedItemStore(
1292 $this->getMockLoadBalancer( $mockDb ),
1296 // Note: This does not actually assert the job is correct
1297 $callableCallCounter = 0;
1298 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1299 $callableCallCounter++
;
1300 $this->assertInternalType( 'callable', $callable );
1302 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1305 $store->resetNotificationTimestamp(
1310 $this->assertEquals( 1, $callableCallCounter );
1313 public function testResetNotificationTimestamp_noItemForced() {
1314 $user = $this->getMockNonAnonUserWithId( 1 );
1315 $title = Title
::newFromText( 'SomeDbKey' );
1317 $mockDb = $this->getMockDb();
1318 $mockDb->expects( $this->never() )
1319 ->method( 'selectRow' );
1321 $mockCache = $this->getMockCache();
1322 $mockDb->expects( $this->never() )
1324 $mockDb->expects( $this->never() )
1326 $mockDb->expects( $this->never() )
1327 ->method( 'delete' );
1329 $store = new WatchedItemStore(
1330 $this->getMockLoadBalancer( $mockDb ),
1334 // Note: This does not actually assert the job is correct
1335 $callableCallCounter = 0;
1336 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1337 $callableCallCounter++
;
1338 $this->assertInternalType( 'callable', $callable );
1340 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1343 $store->resetNotificationTimestamp(
1349 $this->assertEquals( 1, $callableCallCounter );
1356 * @return PHPUnit_Framework_MockObject_MockObject|Title
1358 private function getMockTitle( $text, $ns = 0 ) {
1359 $title = $this->getMock( Title
::class );
1360 $title->expects( $this->any() )
1361 ->method( 'getText' )
1362 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1363 $title->expects( $this->any() )
1364 ->method( 'getDbKey' )
1365 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1366 $title->expects( $this->any() )
1367 ->method( 'getNamespace' )
1368 ->will( $this->returnValue( $ns ) );
1372 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1373 $user = $this->getMockNonAnonUserWithId( 1 );
1375 $title = $this->getMockTitle( 'SomeTitle' );
1376 $title->expects( $this->once() )
1377 ->method( 'getNextRevisionID' )
1379 ->will( $this->returnValue( false ) );
1381 $mockDb = $this->getMockDb();
1382 $mockDb->expects( $this->never() )
1383 ->method( 'selectRow' );
1385 $mockCache = $this->getMockCache();
1386 $mockDb->expects( $this->never() )
1388 $mockDb->expects( $this->never() )
1390 $mockDb->expects( $this->never() )
1391 ->method( 'delete' );
1393 $store = new WatchedItemStore(
1394 $this->getMockLoadBalancer( $mockDb ),
1398 // Note: This does not actually assert the job is correct
1399 $callableCallCounter = 0;
1400 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1401 function( $callable ) use ( &$callableCallCounter ) {
1402 $callableCallCounter++
;
1403 $this->assertInternalType( 'callable', $callable );
1408 $store->resetNotificationTimestamp(
1415 $this->assertEquals( 1, $callableCallCounter );
1418 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1419 $user = $this->getMockNonAnonUserWithId( 1 );
1421 $title = $this->getMockTitle( 'SomeDbKey' );
1422 $title->expects( $this->once() )
1423 ->method( 'getNextRevisionID' )
1425 ->will( $this->returnValue( 33 ) );
1427 $mockDb = $this->getMockDb();
1428 $mockDb->expects( $this->once() )
1429 ->method( 'selectRow' )
1432 'wl_notificationtimestamp',
1435 'wl_namespace' => 0,
1436 'wl_title' => 'SomeDbKey',
1439 ->will( $this->returnValue(
1440 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1443 $mockCache = $this->getMockCache();
1444 $mockDb->expects( $this->never() )
1446 $mockDb->expects( $this->never() )
1448 $mockDb->expects( $this->never() )
1449 ->method( 'delete' );
1451 $store = new WatchedItemStore(
1452 $this->getMockLoadBalancer( $mockDb ),
1456 // Note: This does not actually assert the job is correct
1457 $addUpdateCallCounter = 0;
1458 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1459 function( $callable ) use ( &$addUpdateCallCounter ) {
1460 $addUpdateCallCounter++
;
1461 $this->assertInternalType( 'callable', $callable );
1465 $getTimestampCallCounter = 0;
1466 $store->overrideRevisionGetTimestampFromIdCallback(
1467 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1468 $getTimestampCallCounter++
;
1469 $this->assertEquals( $title, $titleParam );
1470 $this->assertEquals( $oldid, $oldidParam );
1475 $store->resetNotificationTimestamp(
1482 $this->assertEquals( 1, $addUpdateCallCounter );
1483 $this->assertEquals( 1, $getTimestampCallCounter );
1486 public function testUpdateNotificationTimestamp_watchersExist() {
1487 $mockDb = $this->getMockDb();
1488 $mockDb->expects( $this->once() )
1489 ->method( 'select' )
1495 'wl_namespace' => 0,
1496 'wl_title' => 'SomeDbKey',
1497 'wl_notificationtimestamp IS NULL'
1501 $this->returnValue( [
1502 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1503 $this->getFakeRow( [ 'wl_user' => '3' ] )
1506 $mockDb->expects( $this->once() )
1507 ->method( 'onTransactionIdle' )
1508 ->with( $this->isType( 'callable' ) )
1509 ->will( $this->returnCallback( function( $callable ) {
1512 $mockDb->expects( $this->once() )
1513 ->method( 'update' )
1516 [ 'wl_notificationtimestamp' => null ],
1518 'wl_user' => [ 2, 3 ],
1519 'wl_namespace' => 0,
1520 'wl_title' => 'SomeDbKey',
1524 $mockCache = $this->getMockCache();
1525 $mockCache->expects( $this->never() )->method( 'set' );
1526 $mockCache->expects( $this->never() )->method( 'get' );
1527 $mockCache->expects( $this->never() )->method( 'delete' );
1529 $store = new WatchedItemStore(
1530 $this->getMockLoadBalancer( $mockDb ),
1534 $this->assertEquals(
1536 $store->updateNotificationTimestamp(
1537 $this->getMockNonAnonUserWithId( 1 ),
1538 new TitleValue( 0, 'SomeDbKey' ),
1544 public function testUpdateNotificationTimestamp_noWatchers() {
1545 $mockDb = $this->getMockDb();
1546 $mockDb->expects( $this->once() )
1547 ->method( 'select' )
1553 'wl_namespace' => 0,
1554 'wl_title' => 'SomeDbKey',
1555 'wl_notificationtimestamp IS NULL'
1559 $this->returnValue( [] )
1561 $mockDb->expects( $this->never() )
1562 ->method( 'onTransactionIdle' );
1563 $mockDb->expects( $this->never() )
1564 ->method( 'update' );
1566 $mockCache = $this->getMockCache();
1567 $mockCache->expects( $this->never() )->method( 'set' );
1568 $mockCache->expects( $this->never() )->method( 'get' );
1569 $mockCache->expects( $this->never() )->method( 'delete' );
1571 $store = new WatchedItemStore(
1572 $this->getMockLoadBalancer( $mockDb ),
1576 $watchers = $store->updateNotificationTimestamp(
1577 $this->getMockNonAnonUserWithId( 1 ),
1578 new TitleValue( 0, 'SomeDbKey' ),
1581 $this->assertInternalType( 'array', $watchers );
1582 $this->assertEmpty( $watchers );
1585 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1586 $user = $this->getMockNonAnonUserWithId( 1 );
1587 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1589 $mockDb = $this->getMockDb();
1590 $mockDb->expects( $this->once() )
1591 ->method( 'selectRow' )
1592 ->will( $this->returnValue(
1593 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1595 $mockDb->expects( $this->once() )
1596 ->method( 'select' )
1598 $this->returnValue( [
1599 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1600 $this->getFakeRow( [ 'wl_user' => '3' ] )
1603 $mockDb->expects( $this->once() )
1604 ->method( 'onTransactionIdle' )
1605 ->with( $this->isType( 'callable' ) )
1606 ->will( $this->returnCallback( function( $callable ) {
1609 $mockDb->expects( $this->once() )
1610 ->method( 'update' );
1612 $mockCache = $this->getMockCache();
1613 $mockCache->expects( $this->once() )
1615 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1616 $mockCache->expects( $this->once() )
1618 ->with( '0:SomeDbKey:1' );
1619 $mockCache->expects( $this->once() )
1620 ->method( 'delete' )
1621 ->with( '0:SomeDbKey:1' );
1623 $store = new WatchedItemStore(
1624 $this->getMockLoadBalancer( $mockDb ),
1628 // This will add the item to the cache
1629 $store->getWatchedItem( $user, $titleValue );
1631 $store->updateNotificationTimestamp(
1632 $this->getMockNonAnonUserWithId( 1 ),