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 provideMinimumWatchers() {
172 [ "50; DROP TABLE watchlist;\n--" ],
177 * @dataProvider provideMinimumWatchers
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 testDuplicateEntry_nothingToDuplicate() {
280 $mockDb = $this->getMockDb();
281 $mockDb->expects( $this->once() )
287 'wl_notificationtimestamp',
291 'wl_title' => 'Old_Title',
293 'WatchedItemStore::duplicateEntry',
296 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
298 $store = new WatchedItemStore(
299 $this->getMockLoadBalancer( $mockDb ),
300 $this->getMockCache()
303 $store->duplicateEntry(
304 Title
::newFromText( 'Old_Title' ),
305 Title
::newFromText( 'New_Title' )
309 public function testDuplicateEntry_somethingToDuplicate() {
311 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
312 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
315 $mockDb = $this->getMockDb();
316 $mockDb->expects( $this->at( 0 ) )
322 'wl_notificationtimestamp',
326 'wl_title' => 'Old_Title',
329 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
330 $mockDb->expects( $this->at( 1 ) )
331 ->method( 'replace' )
334 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
339 'wl_title' => 'New_Title',
340 'wl_notificationtimestamp' => '20151212010101',
345 'wl_title' => 'New_Title',
346 'wl_notificationtimestamp' => null,
349 $this->isType( 'string' )
352 $mockCache = $this->getMockCache();
353 $mockCache->expects( $this->never() )->method( 'get' );
354 $mockCache->expects( $this->never() )->method( 'delete' );
356 $store = new WatchedItemStore(
357 $this->getMockLoadBalancer( $mockDb ),
361 $store->duplicateEntry(
362 Title
::newFromText( 'Old_Title' ),
363 Title
::newFromText( 'New_Title' )
367 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
368 $mockDb = $this->getMockDb();
369 $mockDb->expects( $this->at( 0 ) )
375 'wl_notificationtimestamp',
379 'wl_title' => 'Old_Title',
382 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
383 $mockDb->expects( $this->at( 1 ) )
389 'wl_notificationtimestamp',
393 'wl_title' => 'Old_Title',
396 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
398 $mockCache = $this->getMockCache();
399 $mockCache->expects( $this->never() )->method( 'get' );
400 $mockCache->expects( $this->never() )->method( 'delete' );
402 $store = new WatchedItemStore(
403 $this->getMockLoadBalancer( $mockDb ),
407 $store->duplicateAllAssociatedEntries(
408 Title
::newFromText( 'Old_Title' ),
409 Title
::newFromText( 'New_Title' )
413 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
415 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
418 $mockDb = $this->getMockDb();
419 $mockDb->expects( $this->at( 0 ) )
425 'wl_notificationtimestamp',
429 'wl_title' => 'Old_Title',
432 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
433 $mockDb->expects( $this->at( 1 ) )
434 ->method( 'replace' )
437 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
442 'wl_title' => 'New_Title',
443 'wl_notificationtimestamp' => '20151212010101',
446 $this->isType( 'string' )
448 $mockDb->expects( $this->at( 2 ) )
454 'wl_notificationtimestamp',
458 'wl_title' => 'Old_Title',
461 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
462 $mockDb->expects( $this->at( 3 ) )
463 ->method( 'replace' )
466 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
471 'wl_title' => 'New_Title',
472 'wl_notificationtimestamp' => '20151212010101',
475 $this->isType( 'string' )
478 $mockCache = $this->getMockCache();
479 $mockCache->expects( $this->never() )->method( 'get' );
480 $mockCache->expects( $this->never() )->method( 'delete' );
482 $store = new WatchedItemStore(
483 $this->getMockLoadBalancer( $mockDb ),
487 $store->duplicateAllAssociatedEntries(
488 Title
::newFromText( 'Old_Title' ),
489 Title
::newFromText( 'New_Title' )
493 public function testAddWatch_nonAnonymousUser() {
494 $mockDb = $this->getMockDb();
495 $mockDb->expects( $this->once() )
503 'wl_title' => 'Some_Page',
504 'wl_notificationtimestamp' => null,
509 $mockCache = $this->getMockCache();
510 $mockCache->expects( $this->once() )
512 ->with( '0:Some_Page:1' );
514 $store = new WatchedItemStore(
515 $this->getMockLoadBalancer( $mockDb ),
520 $this->getMockNonAnonUserWithId( 1 ),
521 Title
::newFromText( 'Some_Page' )
525 public function testAddWatch_anonymousUser() {
526 $mockDb = $this->getMockDb();
527 $mockDb->expects( $this->never() )
528 ->method( 'insert' );
530 $mockCache = $this->getMockCache();
531 $mockCache->expects( $this->never() )
532 ->method( 'delete' );
534 $store = new WatchedItemStore(
535 $this->getMockLoadBalancer( $mockDb ),
540 $this->getAnonUser(),
541 Title
::newFromText( 'Some_Page' )
545 public function testAddWatchBatch_nonAnonymousUser() {
546 $mockDb = $this->getMockDb();
547 $mockDb->expects( $this->once() )
555 'wl_title' => 'Some_Page',
556 'wl_notificationtimestamp' => null,
561 'wl_title' => 'Some_Page',
562 'wl_notificationtimestamp' => null,
567 $mockCache = $this->getMockCache();
568 $mockCache->expects( $this->exactly( 2 ) )
569 ->method( 'delete' );
570 $mockCache->expects( $this->at( 1 ) )
572 ->with( '0:Some_Page:1' );
573 $mockCache->expects( $this->at( 3 ) )
575 ->with( '1:Some_Page:1' );
577 $store = new WatchedItemStore(
578 $this->getMockLoadBalancer( $mockDb ),
582 $mockUser = $this->getMockNonAnonUserWithId( 1 );
585 $store->addWatchBatch(
587 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
588 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
594 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
595 $mockDb = $this->getMockDb();
596 $mockDb->expects( $this->once() )
604 'wl_title' => 'Some_Page',
605 'wl_notificationtimestamp' => null,
610 $mockCache = $this->getMockCache();
611 $mockCache->expects( $this->once() )
613 ->with( '0:Some_Page:1' );
615 $store = new WatchedItemStore(
616 $this->getMockLoadBalancer( $mockDb ),
621 $store->addWatchBatch(
623 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
624 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
630 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
631 $mockDb = $this->getMockDb();
632 $mockDb->expects( $this->never() )
633 ->method( 'insert' );
635 $mockCache = $this->getMockCache();
636 $mockCache->expects( $this->never() )
637 ->method( 'delete' );
639 $store = new WatchedItemStore(
640 $this->getMockLoadBalancer( $mockDb ),
644 $anonUser = $this->getAnonUser();
646 $store->addWatchBatch(
648 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
649 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
655 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
656 $mockDb = $this->getMockDb();
657 $mockDb->expects( $this->never() )
658 ->method( 'insert' );
660 $mockCache = $this->getMockCache();
661 $mockCache->expects( $this->never() )
662 ->method( 'delete' );
664 $store = new WatchedItemStore(
665 $this->getMockLoadBalancer( $mockDb ),
670 $store->addWatchBatch( [] )
674 public function testLoadWatchedItem_existingItem() {
675 $mockDb = $this->getMockDb();
676 $mockDb->expects( $this->once() )
677 ->method( 'selectRow' )
680 'wl_notificationtimestamp',
684 'wl_title' => 'SomeDbKey',
687 ->will( $this->returnValue(
688 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
691 $mockCache = $this->getMockCache();
692 $mockCache->expects( $this->once() )
698 $store = new WatchedItemStore(
699 $this->getMockLoadBalancer( $mockDb ),
703 $watchedItem = $store->loadWatchedItem(
704 $this->getMockNonAnonUserWithId( 1 ),
705 new TitleValue( 0, 'SomeDbKey' )
707 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
708 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
709 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
710 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
713 public function testLoadWatchedItem_noItem() {
714 $mockDb = $this->getMockDb();
715 $mockDb->expects( $this->once() )
716 ->method( 'selectRow' )
719 'wl_notificationtimestamp',
723 'wl_title' => 'SomeDbKey',
726 ->will( $this->returnValue( [] ) );
728 $mockCache = $this->getMockCache();
729 $mockCache->expects( $this->never() )->method( 'get' );
730 $mockCache->expects( $this->never() )->method( 'delete' );
732 $store = new WatchedItemStore(
733 $this->getMockLoadBalancer( $mockDb ),
738 $store->loadWatchedItem(
739 $this->getMockNonAnonUserWithId( 1 ),
740 new TitleValue( 0, 'SomeDbKey' )
745 public function testLoadWatchedItem_anonymousUser() {
746 $mockDb = $this->getMockDb();
747 $mockDb->expects( $this->never() )
748 ->method( 'selectRow' );
750 $mockCache = $this->getMockCache();
751 $mockCache->expects( $this->never() )->method( 'get' );
752 $mockCache->expects( $this->never() )->method( 'delete' );
754 $store = new WatchedItemStore(
755 $this->getMockLoadBalancer( $mockDb ),
760 $store->loadWatchedItem(
761 $this->getAnonUser(),
762 new TitleValue( 0, 'SomeDbKey' )
767 public function testRemoveWatch_existingItem() {
768 $mockDb = $this->getMockDb();
769 $mockDb->expects( $this->once() )
776 'wl_title' => 'SomeDbKey',
779 $mockDb->expects( $this->once() )
780 ->method( 'affectedRows' )
781 ->will( $this->returnValue( 1 ) );
783 $mockCache = $this->getMockCache();
784 $mockCache->expects( $this->never() )->method( 'get' );
785 $mockCache->expects( $this->once() )
787 ->with( '0:SomeDbKey:1' );
789 $store = new WatchedItemStore(
790 $this->getMockLoadBalancer( $mockDb ),
796 $this->getMockNonAnonUserWithId( 1 ),
797 new TitleValue( 0, 'SomeDbKey' )
802 public function testRemoveWatch_noItem() {
803 $mockDb = $this->getMockDb();
804 $mockDb->expects( $this->once() )
811 'wl_title' => 'SomeDbKey',
814 $mockDb->expects( $this->once() )
815 ->method( 'affectedRows' )
816 ->will( $this->returnValue( 0 ) );
818 $mockCache = $this->getMockCache();
819 $mockCache->expects( $this->never() )->method( 'get' );
820 $mockCache->expects( $this->once() )
822 ->with( '0:SomeDbKey:1' );
824 $store = new WatchedItemStore(
825 $this->getMockLoadBalancer( $mockDb ),
831 $this->getMockNonAnonUserWithId( 1 ),
832 new TitleValue( 0, 'SomeDbKey' )
837 public function testRemoveWatch_anonymousUser() {
838 $mockDb = $this->getMockDb();
839 $mockDb->expects( $this->never() )
840 ->method( 'delete' );
842 $mockCache = $this->getMockCache();
843 $mockCache->expects( $this->never() )->method( 'get' );
844 $mockCache->expects( $this->never() )
845 ->method( 'delete' );
847 $store = new WatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
854 $this->getAnonUser(),
855 new TitleValue( 0, 'SomeDbKey' )
860 public function testGetWatchedItem_existingItem() {
861 $mockDb = $this->getMockDb();
862 $mockDb->expects( $this->once() )
863 ->method( 'selectRow' )
866 'wl_notificationtimestamp',
870 'wl_title' => 'SomeDbKey',
873 ->will( $this->returnValue(
874 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
877 $mockCache = $this->getMockCache();
878 $mockCache->expects( $this->never() )->method( 'delete' );
879 $mockCache->expects( $this->once() )
884 ->will( $this->returnValue( null ) );
885 $mockCache->expects( $this->once() )
891 $store = new WatchedItemStore(
892 $this->getMockLoadBalancer( $mockDb ),
896 $watchedItem = $store->getWatchedItem(
897 $this->getMockNonAnonUserWithId( 1 ),
898 new TitleValue( 0, 'SomeDbKey' )
900 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
901 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
902 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
903 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
906 public function testGetWatchedItem_cachedItem() {
907 $mockDb = $this->getMockDb();
908 $mockDb->expects( $this->never() )
909 ->method( 'selectRow' );
911 $mockUser = $this->getMockNonAnonUserWithId( 1 );
912 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
913 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
915 $mockCache = $this->getMockCache();
916 $mockCache->expects( $this->never() )->method( 'delete' );
917 $mockCache->expects( $this->never() )->method( 'set' );
918 $mockCache->expects( $this->once() )
923 ->will( $this->returnValue( $cachedItem ) );
925 $store = new WatchedItemStore(
926 $this->getMockLoadBalancer( $mockDb ),
932 $store->getWatchedItem(
939 public function testGetWatchedItem_noItem() {
940 $mockDb = $this->getMockDb();
941 $mockDb->expects( $this->once() )
942 ->method( 'selectRow' )
945 'wl_notificationtimestamp',
949 'wl_title' => 'SomeDbKey',
952 ->will( $this->returnValue( [] ) );
954 $mockCache = $this->getMockCache();
955 $mockCache->expects( $this->never() )->method( 'set' );
956 $mockCache->expects( $this->never() )->method( 'delete' );
957 $mockCache->expects( $this->once() )
959 ->with( '0:SomeDbKey:1' )
960 ->will( $this->returnValue( false ) );
962 $store = new WatchedItemStore(
963 $this->getMockLoadBalancer( $mockDb ),
968 $store->getWatchedItem(
969 $this->getMockNonAnonUserWithId( 1 ),
970 new TitleValue( 0, 'SomeDbKey' )
975 public function testGetWatchedItem_anonymousUser() {
976 $mockDb = $this->getMockDb();
977 $mockDb->expects( $this->never() )
978 ->method( 'selectRow' );
980 $mockCache = $this->getMockCache();
981 $mockCache->expects( $this->never() )->method( 'set' );
982 $mockCache->expects( $this->never() )->method( 'get' );
983 $mockCache->expects( $this->never() )->method( 'delete' );
985 $store = new WatchedItemStore(
986 $this->getMockLoadBalancer( $mockDb ),
991 $store->getWatchedItem(
992 $this->getAnonUser(),
993 new TitleValue( 0, 'SomeDbKey' )
998 public function testIsWatchedItem_existingItem() {
999 $mockDb = $this->getMockDb();
1000 $mockDb->expects( $this->once() )
1001 ->method( 'selectRow' )
1004 'wl_notificationtimestamp',
1007 'wl_namespace' => 0,
1008 'wl_title' => 'SomeDbKey',
1011 ->will( $this->returnValue(
1012 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1015 $mockCache = $this->getMockCache();
1016 $mockCache->expects( $this->never() )->method( 'delete' );
1017 $mockCache->expects( $this->once() )
1019 ->with( '0:SomeDbKey:1' )
1020 ->will( $this->returnValue( false ) );
1021 $mockCache->expects( $this->once() )
1027 $store = new WatchedItemStore(
1028 $this->getMockLoadBalancer( $mockDb ),
1034 $this->getMockNonAnonUserWithId( 1 ),
1035 new TitleValue( 0, 'SomeDbKey' )
1040 public function testIsWatchedItem_noItem() {
1041 $mockDb = $this->getMockDb();
1042 $mockDb->expects( $this->once() )
1043 ->method( 'selectRow' )
1046 'wl_notificationtimestamp',
1049 'wl_namespace' => 0,
1050 'wl_title' => 'SomeDbKey',
1053 ->will( $this->returnValue( [] ) );
1055 $mockCache = $this->getMockCache();
1056 $mockCache->expects( $this->never() )->method( 'set' );
1057 $mockCache->expects( $this->never() )->method( 'delete' );
1058 $mockCache->expects( $this->once() )
1060 ->with( '0:SomeDbKey:1' )
1061 ->will( $this->returnValue( false ) );
1063 $store = new WatchedItemStore(
1064 $this->getMockLoadBalancer( $mockDb ),
1070 $this->getMockNonAnonUserWithId( 1 ),
1071 new TitleValue( 0, 'SomeDbKey' )
1076 public function testIsWatchedItem_anonymousUser() {
1077 $mockDb = $this->getMockDb();
1078 $mockDb->expects( $this->never() )
1079 ->method( 'selectRow' );
1081 $mockCache = $this->getMockCache();
1082 $mockCache->expects( $this->never() )->method( 'set' );
1083 $mockCache->expects( $this->never() )->method( 'get' );
1084 $mockCache->expects( $this->never() )->method( 'delete' );
1086 $store = new WatchedItemStore(
1087 $this->getMockLoadBalancer( $mockDb ),
1093 $this->getAnonUser(),
1094 new TitleValue( 0, 'SomeDbKey' )
1099 public function testResetNotificationTimestamp_anonymousUser() {
1100 $mockDb = $this->getMockDb();
1101 $mockDb->expects( $this->never() )
1102 ->method( 'selectRow' );
1104 $mockCache = $this->getMockCache();
1105 $mockCache->expects( $this->never() )->method( 'get' );
1106 $mockCache->expects( $this->never() )->method( 'set' );
1107 $mockCache->expects( $this->never() )->method( 'delete' );
1109 $store = new WatchedItemStore(
1110 $this->getMockLoadBalancer( $mockDb ),
1115 $store->resetNotificationTimestamp(
1116 $this->getAnonUser(),
1117 Title
::newFromText( 'SomeDbKey' )
1122 public function testResetNotificationTimestamp_noItem() {
1123 $mockDb = $this->getMockDb();
1124 $mockDb->expects( $this->once() )
1125 ->method( 'selectRow' )
1128 'wl_notificationtimestamp',
1131 'wl_namespace' => 0,
1132 'wl_title' => 'SomeDbKey',
1135 ->will( $this->returnValue( [] ) );
1137 $mockCache = $this->getMockCache();
1138 $mockCache->expects( $this->never() )->method( 'get' );
1139 $mockCache->expects( $this->never() )->method( 'set' );
1140 $mockCache->expects( $this->never() )->method( 'delete' );
1142 $store = new WatchedItemStore(
1143 $this->getMockLoadBalancer( $mockDb ),
1148 $store->resetNotificationTimestamp(
1149 $this->getMockNonAnonUserWithId( 1 ),
1150 Title
::newFromText( 'SomeDbKey' )
1155 public function testResetNotificationTimestamp_item() {
1156 $user = $this->getMockNonAnonUserWithId( 1 );
1157 $title = Title
::newFromText( 'SomeDbKey' );
1159 $mockDb = $this->getMockDb();
1160 $mockDb->expects( $this->once() )
1161 ->method( 'selectRow' )
1164 'wl_notificationtimestamp',
1167 'wl_namespace' => 0,
1168 'wl_title' => 'SomeDbKey',
1171 ->will( $this->returnValue(
1172 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1175 $mockCache = $this->getMockCache();
1176 $mockCache->expects( $this->never() )->method( 'get' );
1177 $mockCache->expects( $this->once() )
1181 $this->isInstanceOf( WatchedItem
::class )
1183 $mockCache->expects( $this->once() )
1184 ->method( 'delete' )
1185 ->with( '0:SomeDbKey:1' );
1187 $store = new WatchedItemStore(
1188 $this->getMockLoadBalancer( $mockDb ),
1192 // Note: This does not actually assert the job is correct
1193 $callableCallCounter = 0;
1194 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1195 $callableCallCounter++
;
1196 $this->assertInternalType( 'callable', $callable );
1198 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1201 $store->resetNotificationTimestamp(
1206 $this->assertEquals( 1, $callableCallCounter );
1209 public function testResetNotificationTimestamp_noItemForced() {
1210 $user = $this->getMockNonAnonUserWithId( 1 );
1211 $title = Title
::newFromText( 'SomeDbKey' );
1213 $mockDb = $this->getMockDb();
1214 $mockDb->expects( $this->never() )
1215 ->method( 'selectRow' );
1217 $mockCache = $this->getMockCache();
1218 $mockDb->expects( $this->never() )
1220 $mockDb->expects( $this->never() )
1222 $mockDb->expects( $this->never() )
1223 ->method( 'delete' );
1225 $store = new WatchedItemStore(
1226 $this->getMockLoadBalancer( $mockDb ),
1230 // Note: This does not actually assert the job is correct
1231 $callableCallCounter = 0;
1232 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1233 $callableCallCounter++
;
1234 $this->assertInternalType( 'callable', $callable );
1236 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1239 $store->resetNotificationTimestamp(
1245 $this->assertEquals( 1, $callableCallCounter );
1252 * @return PHPUnit_Framework_MockObject_MockObject|Title
1254 private function getMockTitle( $text, $ns = 0 ) {
1255 $title = $this->getMock( Title
::class );
1256 $title->expects( $this->any() )
1257 ->method( 'getText' )
1258 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1259 $title->expects( $this->any() )
1260 ->method( 'getDbKey' )
1261 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1262 $title->expects( $this->any() )
1263 ->method( 'getNamespace' )
1264 ->will( $this->returnValue( $ns ) );
1268 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1269 $user = $this->getMockNonAnonUserWithId( 1 );
1271 $title = $this->getMockTitle( 'SomeTitle' );
1272 $title->expects( $this->once() )
1273 ->method( 'getNextRevisionID' )
1275 ->will( $this->returnValue( false ) );
1277 $mockDb = $this->getMockDb();
1278 $mockDb->expects( $this->never() )
1279 ->method( 'selectRow' );
1281 $mockCache = $this->getMockCache();
1282 $mockDb->expects( $this->never() )
1284 $mockDb->expects( $this->never() )
1286 $mockDb->expects( $this->never() )
1287 ->method( 'delete' );
1289 $store = new WatchedItemStore(
1290 $this->getMockLoadBalancer( $mockDb ),
1294 // Note: This does not actually assert the job is correct
1295 $callableCallCounter = 0;
1296 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1297 function( $callable ) use ( &$callableCallCounter ) {
1298 $callableCallCounter++
;
1299 $this->assertInternalType( 'callable', $callable );
1304 $store->resetNotificationTimestamp(
1311 $this->assertEquals( 1, $callableCallCounter );
1314 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1315 $user = $this->getMockNonAnonUserWithId( 1 );
1317 $title = $this->getMockTitle( 'SomeDbKey' );
1318 $title->expects( $this->once() )
1319 ->method( 'getNextRevisionID' )
1321 ->will( $this->returnValue( 33 ) );
1323 $mockDb = $this->getMockDb();
1324 $mockDb->expects( $this->once() )
1325 ->method( 'selectRow' )
1328 'wl_notificationtimestamp',
1331 'wl_namespace' => 0,
1332 'wl_title' => 'SomeDbKey',
1335 ->will( $this->returnValue(
1336 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1339 $mockCache = $this->getMockCache();
1340 $mockDb->expects( $this->never() )
1342 $mockDb->expects( $this->never() )
1344 $mockDb->expects( $this->never() )
1345 ->method( 'delete' );
1347 $store = new WatchedItemStore(
1348 $this->getMockLoadBalancer( $mockDb ),
1352 // Note: This does not actually assert the job is correct
1353 $addUpdateCallCounter = 0;
1354 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1355 function( $callable ) use ( &$addUpdateCallCounter ) {
1356 $addUpdateCallCounter++
;
1357 $this->assertInternalType( 'callable', $callable );
1361 $getTimestampCallCounter = 0;
1362 $store->overrideRevisionGetTimestampFromIdCallback(
1363 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1364 $getTimestampCallCounter++
;
1365 $this->assertEquals( $title, $titleParam );
1366 $this->assertEquals( $oldid, $oldidParam );
1371 $store->resetNotificationTimestamp(
1378 $this->assertEquals( 1, $addUpdateCallCounter );
1379 $this->assertEquals( 1, $getTimestampCallCounter );
1382 public function testUpdateNotificationTimestamp_watchersExist() {
1383 $mockDb = $this->getMockDb();
1384 $mockDb->expects( $this->once() )
1385 ->method( 'select' )
1391 'wl_namespace' => 0,
1392 'wl_title' => 'SomeDbKey',
1393 'wl_notificationtimestamp IS NULL'
1397 $this->returnValue( [
1398 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1399 $this->getFakeRow( [ 'wl_user' => '3' ] )
1402 $mockDb->expects( $this->once() )
1403 ->method( 'onTransactionIdle' )
1404 ->with( $this->isType( 'callable' ) )
1405 ->will( $this->returnCallback( function( $callable ) {
1408 $mockDb->expects( $this->once() )
1409 ->method( 'update' )
1412 [ 'wl_notificationtimestamp' => null ],
1414 'wl_user' => [ 2, 3 ],
1415 'wl_namespace' => 0,
1416 'wl_title' => 'SomeDbKey',
1420 $mockCache = $this->getMockCache();
1421 $mockCache->expects( $this->never() )->method( 'set' );
1422 $mockCache->expects( $this->never() )->method( 'get' );
1423 $mockCache->expects( $this->never() )->method( 'delete' );
1425 $store = new WatchedItemStore(
1426 $this->getMockLoadBalancer( $mockDb ),
1430 $this->assertEquals(
1432 $store->updateNotificationTimestamp(
1433 $this->getMockNonAnonUserWithId( 1 ),
1434 new TitleValue( 0, 'SomeDbKey' ),
1440 public function testUpdateNotificationTimestamp_noWatchers() {
1441 $mockDb = $this->getMockDb();
1442 $mockDb->expects( $this->once() )
1443 ->method( 'select' )
1449 'wl_namespace' => 0,
1450 'wl_title' => 'SomeDbKey',
1451 'wl_notificationtimestamp IS NULL'
1455 $this->returnValue( [] )
1457 $mockDb->expects( $this->never() )
1458 ->method( 'onTransactionIdle' );
1459 $mockDb->expects( $this->never() )
1460 ->method( 'update' );
1462 $mockCache = $this->getMockCache();
1463 $mockCache->expects( $this->never() )->method( 'set' );
1464 $mockCache->expects( $this->never() )->method( 'get' );
1465 $mockCache->expects( $this->never() )->method( 'delete' );
1467 $store = new WatchedItemStore(
1468 $this->getMockLoadBalancer( $mockDb ),
1472 $watchers = $store->updateNotificationTimestamp(
1473 $this->getMockNonAnonUserWithId( 1 ),
1474 new TitleValue( 0, 'SomeDbKey' ),
1477 $this->assertInternalType( 'array', $watchers );
1478 $this->assertEmpty( $watchers );
1481 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1482 $user = $this->getMockNonAnonUserWithId( 1 );
1483 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1485 $mockDb = $this->getMockDb();
1486 $mockDb->expects( $this->once() )
1487 ->method( 'selectRow' )
1488 ->will( $this->returnValue(
1489 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1491 $mockDb->expects( $this->once() )
1492 ->method( 'select' )
1494 $this->returnValue( [
1495 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1496 $this->getFakeRow( [ 'wl_user' => '3' ] )
1499 $mockDb->expects( $this->once() )
1500 ->method( 'onTransactionIdle' )
1501 ->with( $this->isType( 'callable' ) )
1502 ->will( $this->returnCallback( function( $callable ) {
1505 $mockDb->expects( $this->once() )
1506 ->method( 'update' );
1508 $mockCache = $this->getMockCache();
1509 $mockCache->expects( $this->once() )
1511 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1512 $mockCache->expects( $this->once() )
1514 ->with( '0:SomeDbKey:1' );
1515 $mockCache->expects( $this->once() )
1516 ->method( 'delete' )
1517 ->with( '0:SomeDbKey:1' );
1519 $store = new WatchedItemStore(
1520 $this->getMockLoadBalancer( $mockDb ),
1524 // This will add the item to the cache
1525 $store->getWatchedItem( $user, $titleValue );
1527 $store->updateNotificationTimestamp(
1528 $this->getMockNonAnonUserWithId( 1 ),