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 testCountWatchedItems() {
96 $user = $this->getMockNonAnonUserWithId( 1 );
98 $mockDb = $this->getMockDb();
99 $mockDb->expects( $this->exactly( 1 ) )
100 ->method( 'selectField' )
105 'wl_user' => $user->getId(),
107 $this->isType( 'string' )
109 ->will( $this->returnValue( 12 ) );
111 $mockCache = $this->getMockCache();
112 $mockCache->expects( $this->never() )->method( 'get' );
113 $mockCache->expects( $this->never() )->method( 'set' );
114 $mockCache->expects( $this->never() )->method( 'delete' );
116 $store = new WatchedItemStore(
117 $this->getMockLoadBalancer( $mockDb ),
121 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
124 public function testCountWatchers() {
125 $titleValue = new TitleValue( 0, 'SomeDbKey' );
127 $mockDb = $this->getMockDb();
128 $mockDb->expects( $this->exactly( 1 ) )
129 ->method( 'selectField' )
134 'wl_namespace' => $titleValue->getNamespace(),
135 'wl_title' => $titleValue->getDBkey(),
137 $this->isType( 'string' )
139 ->will( $this->returnValue( 7 ) );
141 $mockCache = $this->getMockCache();
142 $mockCache->expects( $this->never() )->method( 'get' );
143 $mockCache->expects( $this->never() )->method( 'set' );
144 $mockCache->expects( $this->never() )->method( 'delete' );
146 $store = new WatchedItemStore(
147 $this->getMockLoadBalancer( $mockDb ),
151 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
154 public function testCountWatchersMultiple() {
156 new TitleValue( 0, 'SomeDbKey' ),
157 new TitleValue( 0, 'OtherDbKey' ),
158 new TitleValue( 1, 'AnotherDbKey' ),
161 $mockDb = $this->getMockDb();
164 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
165 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
166 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
169 $mockDb->expects( $this->once() )
170 ->method( 'makeWhereFrom2d' )
172 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
173 $this->isType( 'string' ),
174 $this->isType( 'string' )
176 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
177 $mockDb->expects( $this->once() )
181 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
182 [ 'makeWhereFrom2d return value' ],
183 $this->isType( 'string' ),
185 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
189 $this->returnValue( $dbResult )
192 $mockCache = $this->getMockCache();
193 $mockCache->expects( $this->never() )->method( 'get' );
194 $mockCache->expects( $this->never() )->method( 'set' );
195 $mockCache->expects( $this->never() )->method( 'delete' );
197 $store = new WatchedItemStore(
198 $this->getMockLoadBalancer( $mockDb ),
203 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
204 1 => [ 'AnotherDbKey' => 500 ],
206 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
209 public function provideIntWithDbUnsafeVersion() {
212 [ "50; DROP TABLE watchlist;\n--" ],
217 * @dataProvider provideIntWithDbUnsafeVersion
219 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
221 new TitleValue( 0, 'SomeDbKey' ),
222 new TitleValue( 0, 'OtherDbKey' ),
223 new TitleValue( 1, 'AnotherDbKey' ),
226 $mockDb = $this->getMockDb();
229 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
230 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
231 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
234 $mockDb->expects( $this->once() )
235 ->method( 'makeWhereFrom2d' )
237 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
238 $this->isType( 'string' ),
239 $this->isType( 'string' )
241 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
242 $mockDb->expects( $this->once() )
246 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
247 [ 'makeWhereFrom2d return value' ],
248 $this->isType( 'string' ),
250 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
251 'HAVING' => 'COUNT(*) >= 50',
255 $this->returnValue( $dbResult )
258 $mockCache = $this->getMockCache();
259 $mockCache->expects( $this->never() )->method( 'get' );
260 $mockCache->expects( $this->never() )->method( 'set' );
261 $mockCache->expects( $this->never() )->method( 'delete' );
263 $store = new WatchedItemStore(
264 $this->getMockLoadBalancer( $mockDb ),
269 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
270 1 => [ 'AnotherDbKey' => 500 ],
274 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
278 public function testCountVisitingWatchers() {
279 $titleValue = new TitleValue( 0, 'SomeDbKey' );
281 $mockDb = $this->getMockDb();
282 $mockDb->expects( $this->exactly( 1 ) )
283 ->method( 'selectField' )
288 'wl_namespace' => $titleValue->getNamespace(),
289 'wl_title' => $titleValue->getDBkey(),
290 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
292 $this->isType( 'string' )
294 ->will( $this->returnValue( 7 ) );
295 $mockDb->expects( $this->exactly( 1 ) )
296 ->method( 'addQuotes' )
297 ->will( $this->returnCallback( function( $value ) {
300 $mockDb->expects( $this->exactly( 1 ) )
301 ->method( 'timestamp' )
302 ->will( $this->returnCallback( function( $value ) {
303 return 'TS' . $value . 'TS';
306 $mockCache = $this->getMockCache();
307 $mockCache->expects( $this->never() )->method( 'set' );
308 $mockCache->expects( $this->never() )->method( 'get' );
309 $mockCache->expects( $this->never() )->method( 'delete' );
311 $store = new WatchedItemStore(
312 $this->getMockLoadBalancer( $mockDb ),
316 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
319 public function testCountUnreadNotifications() {
320 $user = $this->getMockNonAnonUserWithId( 1 );
322 $mockDb = $this->getMockDb();
323 $mockDb->expects( $this->exactly( 1 ) )
324 ->method( 'selectRowCount' )
329 "wl_notificationtimestamp IS NOT NULL",
332 $this->isType( 'string' )
334 ->will( $this->returnValue( 9 ) );
336 $mockCache = $this->getMockCache();
337 $mockCache->expects( $this->never() )->method( 'set' );
338 $mockCache->expects( $this->never() )->method( 'get' );
339 $mockCache->expects( $this->never() )->method( 'delete' );
341 $store = new WatchedItemStore(
342 $this->getMockLoadBalancer( $mockDb ),
346 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
350 * @dataProvider provideIntWithDbUnsafeVersion
352 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
353 $user = $this->getMockNonAnonUserWithId( 1 );
355 $mockDb = $this->getMockDb();
356 $mockDb->expects( $this->exactly( 1 ) )
357 ->method( 'selectRowCount' )
362 "wl_notificationtimestamp IS NOT NULL",
365 $this->isType( 'string' ),
368 ->will( $this->returnValue( 50 ) );
370 $mockCache = $this->getMockCache();
371 $mockCache->expects( $this->never() )->method( 'set' );
372 $mockCache->expects( $this->never() )->method( 'get' );
373 $mockCache->expects( $this->never() )->method( 'delete' );
375 $store = new WatchedItemStore(
376 $this->getMockLoadBalancer( $mockDb ),
382 $store->countUnreadNotifications( $user, $limit )
387 * @dataProvider provideIntWithDbUnsafeVersion
389 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
390 $user = $this->getMockNonAnonUserWithId( 1 );
392 $mockDb = $this->getMockDb();
393 $mockDb->expects( $this->exactly( 1 ) )
394 ->method( 'selectRowCount' )
399 "wl_notificationtimestamp IS NOT NULL",
402 $this->isType( 'string' ),
405 ->will( $this->returnValue( 9 ) );
407 $mockCache = $this->getMockCache();
408 $mockCache->expects( $this->never() )->method( 'set' );
409 $mockCache->expects( $this->never() )->method( 'get' );
410 $mockCache->expects( $this->never() )->method( 'delete' );
412 $store = new WatchedItemStore(
413 $this->getMockLoadBalancer( $mockDb ),
419 $store->countUnreadNotifications( $user, $limit )
423 public function testDuplicateEntry_nothingToDuplicate() {
424 $mockDb = $this->getMockDb();
425 $mockDb->expects( $this->once() )
431 'wl_notificationtimestamp',
435 'wl_title' => 'Old_Title',
437 'WatchedItemStore::duplicateEntry',
440 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
442 $store = new WatchedItemStore(
443 $this->getMockLoadBalancer( $mockDb ),
444 $this->getMockCache()
447 $store->duplicateEntry(
448 Title
::newFromText( 'Old_Title' ),
449 Title
::newFromText( 'New_Title' )
453 public function testDuplicateEntry_somethingToDuplicate() {
455 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
456 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
459 $mockDb = $this->getMockDb();
460 $mockDb->expects( $this->at( 0 ) )
466 'wl_notificationtimestamp',
470 'wl_title' => 'Old_Title',
473 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
474 $mockDb->expects( $this->at( 1 ) )
475 ->method( 'replace' )
478 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
483 'wl_title' => 'New_Title',
484 'wl_notificationtimestamp' => '20151212010101',
489 'wl_title' => 'New_Title',
490 'wl_notificationtimestamp' => null,
493 $this->isType( 'string' )
496 $mockCache = $this->getMockCache();
497 $mockCache->expects( $this->never() )->method( 'get' );
498 $mockCache->expects( $this->never() )->method( 'delete' );
500 $store = new WatchedItemStore(
501 $this->getMockLoadBalancer( $mockDb ),
505 $store->duplicateEntry(
506 Title
::newFromText( 'Old_Title' ),
507 Title
::newFromText( 'New_Title' )
511 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
512 $mockDb = $this->getMockDb();
513 $mockDb->expects( $this->at( 0 ) )
519 'wl_notificationtimestamp',
523 'wl_title' => 'Old_Title',
526 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
527 $mockDb->expects( $this->at( 1 ) )
533 'wl_notificationtimestamp',
537 'wl_title' => 'Old_Title',
540 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
542 $mockCache = $this->getMockCache();
543 $mockCache->expects( $this->never() )->method( 'get' );
544 $mockCache->expects( $this->never() )->method( 'delete' );
546 $store = new WatchedItemStore(
547 $this->getMockLoadBalancer( $mockDb ),
551 $store->duplicateAllAssociatedEntries(
552 Title
::newFromText( 'Old_Title' ),
553 Title
::newFromText( 'New_Title' )
557 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
559 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
562 $mockDb = $this->getMockDb();
563 $mockDb->expects( $this->at( 0 ) )
569 'wl_notificationtimestamp',
573 'wl_title' => 'Old_Title',
576 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
577 $mockDb->expects( $this->at( 1 ) )
578 ->method( 'replace' )
581 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
586 'wl_title' => 'New_Title',
587 'wl_notificationtimestamp' => '20151212010101',
590 $this->isType( 'string' )
592 $mockDb->expects( $this->at( 2 ) )
598 'wl_notificationtimestamp',
602 'wl_title' => 'Old_Title',
605 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
606 $mockDb->expects( $this->at( 3 ) )
607 ->method( 'replace' )
610 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
615 'wl_title' => 'New_Title',
616 'wl_notificationtimestamp' => '20151212010101',
619 $this->isType( 'string' )
622 $mockCache = $this->getMockCache();
623 $mockCache->expects( $this->never() )->method( 'get' );
624 $mockCache->expects( $this->never() )->method( 'delete' );
626 $store = new WatchedItemStore(
627 $this->getMockLoadBalancer( $mockDb ),
631 $store->duplicateAllAssociatedEntries(
632 Title
::newFromText( 'Old_Title' ),
633 Title
::newFromText( 'New_Title' )
637 public function testAddWatch_nonAnonymousUser() {
638 $mockDb = $this->getMockDb();
639 $mockDb->expects( $this->once() )
647 'wl_title' => 'Some_Page',
648 'wl_notificationtimestamp' => null,
653 $mockCache = $this->getMockCache();
654 $mockCache->expects( $this->once() )
656 ->with( '0:Some_Page:1' );
658 $store = new WatchedItemStore(
659 $this->getMockLoadBalancer( $mockDb ),
664 $this->getMockNonAnonUserWithId( 1 ),
665 Title
::newFromText( 'Some_Page' )
669 public function testAddWatch_anonymousUser() {
670 $mockDb = $this->getMockDb();
671 $mockDb->expects( $this->never() )
672 ->method( 'insert' );
674 $mockCache = $this->getMockCache();
675 $mockCache->expects( $this->never() )
676 ->method( 'delete' );
678 $store = new WatchedItemStore(
679 $this->getMockLoadBalancer( $mockDb ),
684 $this->getAnonUser(),
685 Title
::newFromText( 'Some_Page' )
689 public function testAddWatchBatch_nonAnonymousUser() {
690 $mockDb = $this->getMockDb();
691 $mockDb->expects( $this->once() )
699 'wl_title' => 'Some_Page',
700 'wl_notificationtimestamp' => null,
705 'wl_title' => 'Some_Page',
706 'wl_notificationtimestamp' => null,
711 $mockCache = $this->getMockCache();
712 $mockCache->expects( $this->exactly( 2 ) )
713 ->method( 'delete' );
714 $mockCache->expects( $this->at( 1 ) )
716 ->with( '0:Some_Page:1' );
717 $mockCache->expects( $this->at( 3 ) )
719 ->with( '1:Some_Page:1' );
721 $store = new WatchedItemStore(
722 $this->getMockLoadBalancer( $mockDb ),
726 $mockUser = $this->getMockNonAnonUserWithId( 1 );
729 $store->addWatchBatch(
731 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
732 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
738 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
739 $mockDb = $this->getMockDb();
740 $mockDb->expects( $this->once() )
748 'wl_title' => 'Some_Page',
749 'wl_notificationtimestamp' => null,
754 $mockCache = $this->getMockCache();
755 $mockCache->expects( $this->once() )
757 ->with( '0:Some_Page:1' );
759 $store = new WatchedItemStore(
760 $this->getMockLoadBalancer( $mockDb ),
765 $store->addWatchBatch(
767 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
768 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
774 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
775 $mockDb = $this->getMockDb();
776 $mockDb->expects( $this->never() )
777 ->method( 'insert' );
779 $mockCache = $this->getMockCache();
780 $mockCache->expects( $this->never() )
781 ->method( 'delete' );
783 $store = new WatchedItemStore(
784 $this->getMockLoadBalancer( $mockDb ),
788 $anonUser = $this->getAnonUser();
790 $store->addWatchBatch(
792 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
793 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
799 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
800 $mockDb = $this->getMockDb();
801 $mockDb->expects( $this->never() )
802 ->method( 'insert' );
804 $mockCache = $this->getMockCache();
805 $mockCache->expects( $this->never() )
806 ->method( 'delete' );
808 $store = new WatchedItemStore(
809 $this->getMockLoadBalancer( $mockDb ),
814 $store->addWatchBatch( [] )
818 public function testLoadWatchedItem_existingItem() {
819 $mockDb = $this->getMockDb();
820 $mockDb->expects( $this->once() )
821 ->method( 'selectRow' )
824 'wl_notificationtimestamp',
828 'wl_title' => 'SomeDbKey',
831 ->will( $this->returnValue(
832 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
835 $mockCache = $this->getMockCache();
836 $mockCache->expects( $this->once() )
842 $store = new WatchedItemStore(
843 $this->getMockLoadBalancer( $mockDb ),
847 $watchedItem = $store->loadWatchedItem(
848 $this->getMockNonAnonUserWithId( 1 ),
849 new TitleValue( 0, 'SomeDbKey' )
851 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
852 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
853 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
854 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
857 public function testLoadWatchedItem_noItem() {
858 $mockDb = $this->getMockDb();
859 $mockDb->expects( $this->once() )
860 ->method( 'selectRow' )
863 'wl_notificationtimestamp',
867 'wl_title' => 'SomeDbKey',
870 ->will( $this->returnValue( [] ) );
872 $mockCache = $this->getMockCache();
873 $mockCache->expects( $this->never() )->method( 'get' );
874 $mockCache->expects( $this->never() )->method( 'delete' );
876 $store = new WatchedItemStore(
877 $this->getMockLoadBalancer( $mockDb ),
882 $store->loadWatchedItem(
883 $this->getMockNonAnonUserWithId( 1 ),
884 new TitleValue( 0, 'SomeDbKey' )
889 public function testLoadWatchedItem_anonymousUser() {
890 $mockDb = $this->getMockDb();
891 $mockDb->expects( $this->never() )
892 ->method( 'selectRow' );
894 $mockCache = $this->getMockCache();
895 $mockCache->expects( $this->never() )->method( 'get' );
896 $mockCache->expects( $this->never() )->method( 'delete' );
898 $store = new WatchedItemStore(
899 $this->getMockLoadBalancer( $mockDb ),
904 $store->loadWatchedItem(
905 $this->getAnonUser(),
906 new TitleValue( 0, 'SomeDbKey' )
911 public function testRemoveWatch_existingItem() {
912 $mockDb = $this->getMockDb();
913 $mockDb->expects( $this->once() )
920 'wl_title' => 'SomeDbKey',
923 $mockDb->expects( $this->once() )
924 ->method( 'affectedRows' )
925 ->will( $this->returnValue( 1 ) );
927 $mockCache = $this->getMockCache();
928 $mockCache->expects( $this->never() )->method( 'get' );
929 $mockCache->expects( $this->once() )
931 ->with( '0:SomeDbKey:1' );
933 $store = new WatchedItemStore(
934 $this->getMockLoadBalancer( $mockDb ),
940 $this->getMockNonAnonUserWithId( 1 ),
941 new TitleValue( 0, 'SomeDbKey' )
946 public function testRemoveWatch_noItem() {
947 $mockDb = $this->getMockDb();
948 $mockDb->expects( $this->once() )
955 'wl_title' => 'SomeDbKey',
958 $mockDb->expects( $this->once() )
959 ->method( 'affectedRows' )
960 ->will( $this->returnValue( 0 ) );
962 $mockCache = $this->getMockCache();
963 $mockCache->expects( $this->never() )->method( 'get' );
964 $mockCache->expects( $this->once() )
966 ->with( '0:SomeDbKey:1' );
968 $store = new WatchedItemStore(
969 $this->getMockLoadBalancer( $mockDb ),
975 $this->getMockNonAnonUserWithId( 1 ),
976 new TitleValue( 0, 'SomeDbKey' )
981 public function testRemoveWatch_anonymousUser() {
982 $mockDb = $this->getMockDb();
983 $mockDb->expects( $this->never() )
984 ->method( 'delete' );
986 $mockCache = $this->getMockCache();
987 $mockCache->expects( $this->never() )->method( 'get' );
988 $mockCache->expects( $this->never() )
989 ->method( 'delete' );
991 $store = new WatchedItemStore(
992 $this->getMockLoadBalancer( $mockDb ),
998 $this->getAnonUser(),
999 new TitleValue( 0, 'SomeDbKey' )
1004 public function testGetWatchedItem_existingItem() {
1005 $mockDb = $this->getMockDb();
1006 $mockDb->expects( $this->once() )
1007 ->method( 'selectRow' )
1010 'wl_notificationtimestamp',
1013 'wl_namespace' => 0,
1014 'wl_title' => 'SomeDbKey',
1017 ->will( $this->returnValue(
1018 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1021 $mockCache = $this->getMockCache();
1022 $mockCache->expects( $this->never() )->method( 'delete' );
1023 $mockCache->expects( $this->once() )
1028 ->will( $this->returnValue( null ) );
1029 $mockCache->expects( $this->once() )
1035 $store = new WatchedItemStore(
1036 $this->getMockLoadBalancer( $mockDb ),
1040 $watchedItem = $store->getWatchedItem(
1041 $this->getMockNonAnonUserWithId( 1 ),
1042 new TitleValue( 0, 'SomeDbKey' )
1044 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1045 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1046 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1047 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1050 public function testGetWatchedItem_cachedItem() {
1051 $mockDb = $this->getMockDb();
1052 $mockDb->expects( $this->never() )
1053 ->method( 'selectRow' );
1055 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1056 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1057 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1059 $mockCache = $this->getMockCache();
1060 $mockCache->expects( $this->never() )->method( 'delete' );
1061 $mockCache->expects( $this->never() )->method( 'set' );
1062 $mockCache->expects( $this->once() )
1067 ->will( $this->returnValue( $cachedItem ) );
1069 $store = new WatchedItemStore(
1070 $this->getMockLoadBalancer( $mockDb ),
1074 $this->assertEquals(
1076 $store->getWatchedItem(
1083 public function testGetWatchedItem_noItem() {
1084 $mockDb = $this->getMockDb();
1085 $mockDb->expects( $this->once() )
1086 ->method( 'selectRow' )
1089 'wl_notificationtimestamp',
1092 'wl_namespace' => 0,
1093 'wl_title' => 'SomeDbKey',
1096 ->will( $this->returnValue( [] ) );
1098 $mockCache = $this->getMockCache();
1099 $mockCache->expects( $this->never() )->method( 'set' );
1100 $mockCache->expects( $this->never() )->method( 'delete' );
1101 $mockCache->expects( $this->once() )
1103 ->with( '0:SomeDbKey:1' )
1104 ->will( $this->returnValue( false ) );
1106 $store = new WatchedItemStore(
1107 $this->getMockLoadBalancer( $mockDb ),
1112 $store->getWatchedItem(
1113 $this->getMockNonAnonUserWithId( 1 ),
1114 new TitleValue( 0, 'SomeDbKey' )
1119 public function testGetWatchedItem_anonymousUser() {
1120 $mockDb = $this->getMockDb();
1121 $mockDb->expects( $this->never() )
1122 ->method( 'selectRow' );
1124 $mockCache = $this->getMockCache();
1125 $mockCache->expects( $this->never() )->method( 'set' );
1126 $mockCache->expects( $this->never() )->method( 'get' );
1127 $mockCache->expects( $this->never() )->method( 'delete' );
1129 $store = new WatchedItemStore(
1130 $this->getMockLoadBalancer( $mockDb ),
1135 $store->getWatchedItem(
1136 $this->getAnonUser(),
1137 new TitleValue( 0, 'SomeDbKey' )
1142 public function testIsWatchedItem_existingItem() {
1143 $mockDb = $this->getMockDb();
1144 $mockDb->expects( $this->once() )
1145 ->method( 'selectRow' )
1148 'wl_notificationtimestamp',
1151 'wl_namespace' => 0,
1152 'wl_title' => 'SomeDbKey',
1155 ->will( $this->returnValue(
1156 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1159 $mockCache = $this->getMockCache();
1160 $mockCache->expects( $this->never() )->method( 'delete' );
1161 $mockCache->expects( $this->once() )
1163 ->with( '0:SomeDbKey:1' )
1164 ->will( $this->returnValue( false ) );
1165 $mockCache->expects( $this->once() )
1171 $store = new WatchedItemStore(
1172 $this->getMockLoadBalancer( $mockDb ),
1178 $this->getMockNonAnonUserWithId( 1 ),
1179 new TitleValue( 0, 'SomeDbKey' )
1184 public function testIsWatchedItem_noItem() {
1185 $mockDb = $this->getMockDb();
1186 $mockDb->expects( $this->once() )
1187 ->method( 'selectRow' )
1190 'wl_notificationtimestamp',
1193 'wl_namespace' => 0,
1194 'wl_title' => 'SomeDbKey',
1197 ->will( $this->returnValue( [] ) );
1199 $mockCache = $this->getMockCache();
1200 $mockCache->expects( $this->never() )->method( 'set' );
1201 $mockCache->expects( $this->never() )->method( 'delete' );
1202 $mockCache->expects( $this->once() )
1204 ->with( '0:SomeDbKey:1' )
1205 ->will( $this->returnValue( false ) );
1207 $store = new WatchedItemStore(
1208 $this->getMockLoadBalancer( $mockDb ),
1214 $this->getMockNonAnonUserWithId( 1 ),
1215 new TitleValue( 0, 'SomeDbKey' )
1220 public function testIsWatchedItem_anonymousUser() {
1221 $mockDb = $this->getMockDb();
1222 $mockDb->expects( $this->never() )
1223 ->method( 'selectRow' );
1225 $mockCache = $this->getMockCache();
1226 $mockCache->expects( $this->never() )->method( 'set' );
1227 $mockCache->expects( $this->never() )->method( 'get' );
1228 $mockCache->expects( $this->never() )->method( 'delete' );
1230 $store = new WatchedItemStore(
1231 $this->getMockLoadBalancer( $mockDb ),
1237 $this->getAnonUser(),
1238 new TitleValue( 0, 'SomeDbKey' )
1243 public function testResetNotificationTimestamp_anonymousUser() {
1244 $mockDb = $this->getMockDb();
1245 $mockDb->expects( $this->never() )
1246 ->method( 'selectRow' );
1248 $mockCache = $this->getMockCache();
1249 $mockCache->expects( $this->never() )->method( 'get' );
1250 $mockCache->expects( $this->never() )->method( 'set' );
1251 $mockCache->expects( $this->never() )->method( 'delete' );
1253 $store = new WatchedItemStore(
1254 $this->getMockLoadBalancer( $mockDb ),
1259 $store->resetNotificationTimestamp(
1260 $this->getAnonUser(),
1261 Title
::newFromText( 'SomeDbKey' )
1266 public function testResetNotificationTimestamp_noItem() {
1267 $mockDb = $this->getMockDb();
1268 $mockDb->expects( $this->once() )
1269 ->method( 'selectRow' )
1272 'wl_notificationtimestamp',
1275 'wl_namespace' => 0,
1276 'wl_title' => 'SomeDbKey',
1279 ->will( $this->returnValue( [] ) );
1281 $mockCache = $this->getMockCache();
1282 $mockCache->expects( $this->never() )->method( 'get' );
1283 $mockCache->expects( $this->never() )->method( 'set' );
1284 $mockCache->expects( $this->never() )->method( 'delete' );
1286 $store = new WatchedItemStore(
1287 $this->getMockLoadBalancer( $mockDb ),
1292 $store->resetNotificationTimestamp(
1293 $this->getMockNonAnonUserWithId( 1 ),
1294 Title
::newFromText( 'SomeDbKey' )
1299 public function testResetNotificationTimestamp_item() {
1300 $user = $this->getMockNonAnonUserWithId( 1 );
1301 $title = Title
::newFromText( 'SomeDbKey' );
1303 $mockDb = $this->getMockDb();
1304 $mockDb->expects( $this->once() )
1305 ->method( 'selectRow' )
1308 'wl_notificationtimestamp',
1311 'wl_namespace' => 0,
1312 'wl_title' => 'SomeDbKey',
1315 ->will( $this->returnValue(
1316 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1319 $mockCache = $this->getMockCache();
1320 $mockCache->expects( $this->never() )->method( 'get' );
1321 $mockCache->expects( $this->once() )
1325 $this->isInstanceOf( WatchedItem
::class )
1327 $mockCache->expects( $this->once() )
1328 ->method( 'delete' )
1329 ->with( '0:SomeDbKey:1' );
1331 $store = new WatchedItemStore(
1332 $this->getMockLoadBalancer( $mockDb ),
1336 // Note: This does not actually assert the job is correct
1337 $callableCallCounter = 0;
1338 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1339 $callableCallCounter++
;
1340 $this->assertInternalType( 'callable', $callable );
1342 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1345 $store->resetNotificationTimestamp(
1350 $this->assertEquals( 1, $callableCallCounter );
1352 ScopedCallback
::consume( $scopedOverride );
1355 public function testResetNotificationTimestamp_noItemForced() {
1356 $user = $this->getMockNonAnonUserWithId( 1 );
1357 $title = Title
::newFromText( 'SomeDbKey' );
1359 $mockDb = $this->getMockDb();
1360 $mockDb->expects( $this->never() )
1361 ->method( 'selectRow' );
1363 $mockCache = $this->getMockCache();
1364 $mockDb->expects( $this->never() )
1366 $mockDb->expects( $this->never() )
1368 $mockDb->expects( $this->never() )
1369 ->method( 'delete' );
1371 $store = new WatchedItemStore(
1372 $this->getMockLoadBalancer( $mockDb ),
1376 // Note: This does not actually assert the job is correct
1377 $callableCallCounter = 0;
1378 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1379 $callableCallCounter++
;
1380 $this->assertInternalType( 'callable', $callable );
1382 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1385 $store->resetNotificationTimestamp(
1391 $this->assertEquals( 1, $callableCallCounter );
1393 ScopedCallback
::consume( $scopedOverride );
1400 * @return PHPUnit_Framework_MockObject_MockObject|Title
1402 private function getMockTitle( $text, $ns = 0 ) {
1403 $title = $this->getMock( Title
::class );
1404 $title->expects( $this->any() )
1405 ->method( 'getText' )
1406 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1407 $title->expects( $this->any() )
1408 ->method( 'getDbKey' )
1409 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1410 $title->expects( $this->any() )
1411 ->method( 'getNamespace' )
1412 ->will( $this->returnValue( $ns ) );
1416 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1417 $user = $this->getMockNonAnonUserWithId( 1 );
1419 $title = $this->getMockTitle( 'SomeTitle' );
1420 $title->expects( $this->once() )
1421 ->method( 'getNextRevisionID' )
1423 ->will( $this->returnValue( false ) );
1425 $mockDb = $this->getMockDb();
1426 $mockDb->expects( $this->never() )
1427 ->method( 'selectRow' );
1429 $mockCache = $this->getMockCache();
1430 $mockDb->expects( $this->never() )
1432 $mockDb->expects( $this->never() )
1434 $mockDb->expects( $this->never() )
1435 ->method( 'delete' );
1437 $store = new WatchedItemStore(
1438 $this->getMockLoadBalancer( $mockDb ),
1442 // Note: This does not actually assert the job is correct
1443 $callableCallCounter = 0;
1444 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1445 function( $callable ) use ( &$callableCallCounter ) {
1446 $callableCallCounter++
;
1447 $this->assertInternalType( 'callable', $callable );
1452 $store->resetNotificationTimestamp(
1459 $this->assertEquals( 1, $callableCallCounter );
1461 ScopedCallback
::consume( $scopedOverride );
1464 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1465 $user = $this->getMockNonAnonUserWithId( 1 );
1467 $title = $this->getMockTitle( 'SomeDbKey' );
1468 $title->expects( $this->once() )
1469 ->method( 'getNextRevisionID' )
1471 ->will( $this->returnValue( 33 ) );
1473 $mockDb = $this->getMockDb();
1474 $mockDb->expects( $this->once() )
1475 ->method( 'selectRow' )
1478 'wl_notificationtimestamp',
1481 'wl_namespace' => 0,
1482 'wl_title' => 'SomeDbKey',
1485 ->will( $this->returnValue(
1486 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1489 $mockCache = $this->getMockCache();
1490 $mockDb->expects( $this->never() )
1492 $mockDb->expects( $this->never() )
1494 $mockDb->expects( $this->never() )
1495 ->method( 'delete' );
1497 $store = new WatchedItemStore(
1498 $this->getMockLoadBalancer( $mockDb ),
1502 // Note: This does not actually assert the job is correct
1503 $addUpdateCallCounter = 0;
1504 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1505 function( $callable ) use ( &$addUpdateCallCounter ) {
1506 $addUpdateCallCounter++
;
1507 $this->assertInternalType( 'callable', $callable );
1511 $getTimestampCallCounter = 0;
1512 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
1513 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1514 $getTimestampCallCounter++
;
1515 $this->assertEquals( $title, $titleParam );
1516 $this->assertEquals( $oldid, $oldidParam );
1521 $store->resetNotificationTimestamp(
1528 $this->assertEquals( 1, $addUpdateCallCounter );
1529 $this->assertEquals( 1, $getTimestampCallCounter );
1531 ScopedCallback
::consume( $scopedOverrideDeferred );
1532 ScopedCallback
::consume( $scopedOverrideRevision );
1535 public function testUpdateNotificationTimestamp_watchersExist() {
1536 $mockDb = $this->getMockDb();
1537 $mockDb->expects( $this->once() )
1538 ->method( 'select' )
1544 'wl_namespace' => 0,
1545 'wl_title' => 'SomeDbKey',
1546 'wl_notificationtimestamp IS NULL'
1550 $this->returnValue( [
1551 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1552 $this->getFakeRow( [ 'wl_user' => '3' ] )
1555 $mockDb->expects( $this->once() )
1556 ->method( 'onTransactionIdle' )
1557 ->with( $this->isType( 'callable' ) )
1558 ->will( $this->returnCallback( function( $callable ) {
1561 $mockDb->expects( $this->once() )
1562 ->method( 'update' )
1565 [ 'wl_notificationtimestamp' => null ],
1567 'wl_user' => [ 2, 3 ],
1568 'wl_namespace' => 0,
1569 'wl_title' => 'SomeDbKey',
1573 $mockCache = $this->getMockCache();
1574 $mockCache->expects( $this->never() )->method( 'set' );
1575 $mockCache->expects( $this->never() )->method( 'get' );
1576 $mockCache->expects( $this->never() )->method( 'delete' );
1578 $store = new WatchedItemStore(
1579 $this->getMockLoadBalancer( $mockDb ),
1583 $this->assertEquals(
1585 $store->updateNotificationTimestamp(
1586 $this->getMockNonAnonUserWithId( 1 ),
1587 new TitleValue( 0, 'SomeDbKey' ),
1593 public function testUpdateNotificationTimestamp_noWatchers() {
1594 $mockDb = $this->getMockDb();
1595 $mockDb->expects( $this->once() )
1596 ->method( 'select' )
1602 'wl_namespace' => 0,
1603 'wl_title' => 'SomeDbKey',
1604 'wl_notificationtimestamp IS NULL'
1608 $this->returnValue( [] )
1610 $mockDb->expects( $this->never() )
1611 ->method( 'onTransactionIdle' );
1612 $mockDb->expects( $this->never() )
1613 ->method( 'update' );
1615 $mockCache = $this->getMockCache();
1616 $mockCache->expects( $this->never() )->method( 'set' );
1617 $mockCache->expects( $this->never() )->method( 'get' );
1618 $mockCache->expects( $this->never() )->method( 'delete' );
1620 $store = new WatchedItemStore(
1621 $this->getMockLoadBalancer( $mockDb ),
1625 $watchers = $store->updateNotificationTimestamp(
1626 $this->getMockNonAnonUserWithId( 1 ),
1627 new TitleValue( 0, 'SomeDbKey' ),
1630 $this->assertInternalType( 'array', $watchers );
1631 $this->assertEmpty( $watchers );
1634 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1635 $user = $this->getMockNonAnonUserWithId( 1 );
1636 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1638 $mockDb = $this->getMockDb();
1639 $mockDb->expects( $this->once() )
1640 ->method( 'selectRow' )
1641 ->will( $this->returnValue(
1642 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1644 $mockDb->expects( $this->once() )
1645 ->method( 'select' )
1647 $this->returnValue( [
1648 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1649 $this->getFakeRow( [ 'wl_user' => '3' ] )
1652 $mockDb->expects( $this->once() )
1653 ->method( 'onTransactionIdle' )
1654 ->with( $this->isType( 'callable' ) )
1655 ->will( $this->returnCallback( function( $callable ) {
1658 $mockDb->expects( $this->once() )
1659 ->method( 'update' );
1661 $mockCache = $this->getMockCache();
1662 $mockCache->expects( $this->once() )
1664 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1665 $mockCache->expects( $this->once() )
1667 ->with( '0:SomeDbKey:1' );
1668 $mockCache->expects( $this->once() )
1669 ->method( 'delete' )
1670 ->with( '0:SomeDbKey:1' );
1672 $store = new WatchedItemStore(
1673 $this->getMockLoadBalancer( $mockDb ),
1677 // This will add the item to the cache
1678 $store->getWatchedItem( $user, $titleValue );
1680 $store->updateNotificationTimestamp(
1681 $this->getMockNonAnonUserWithId( 1 ),