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 testDuplicateEntry_nothingToDuplicate() {
239 $mockDb = $this->getMockDb();
240 $mockDb->expects( $this->once() )
246 'wl_notificationtimestamp',
250 'wl_title' => 'Old_Title',
252 'WatchedItemStore::duplicateEntry',
255 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
257 $store = new WatchedItemStore(
258 $this->getMockLoadBalancer( $mockDb ),
259 $this->getMockCache()
262 $store->duplicateEntry(
263 Title
::newFromText( 'Old_Title' ),
264 Title
::newFromText( 'New_Title' )
268 public function testDuplicateEntry_somethingToDuplicate() {
270 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
271 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
274 $mockDb = $this->getMockDb();
275 $mockDb->expects( $this->at( 0 ) )
281 'wl_notificationtimestamp',
285 'wl_title' => 'Old_Title',
288 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
289 $mockDb->expects( $this->at( 1 ) )
290 ->method( 'replace' )
293 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
298 'wl_title' => 'New_Title',
299 'wl_notificationtimestamp' => '20151212010101',
304 'wl_title' => 'New_Title',
305 'wl_notificationtimestamp' => null,
308 $this->isType( 'string' )
311 $mockCache = $this->getMockCache();
312 $mockCache->expects( $this->never() )->method( 'get' );
313 $mockCache->expects( $this->never() )->method( 'delete' );
315 $store = new WatchedItemStore(
316 $this->getMockLoadBalancer( $mockDb ),
320 $store->duplicateEntry(
321 Title
::newFromText( 'Old_Title' ),
322 Title
::newFromText( 'New_Title' )
326 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
327 $mockDb = $this->getMockDb();
328 $mockDb->expects( $this->at( 0 ) )
334 'wl_notificationtimestamp',
338 'wl_title' => 'Old_Title',
341 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
342 $mockDb->expects( $this->at( 1 ) )
348 'wl_notificationtimestamp',
352 'wl_title' => 'Old_Title',
355 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
357 $mockCache = $this->getMockCache();
358 $mockCache->expects( $this->never() )->method( 'get' );
359 $mockCache->expects( $this->never() )->method( 'delete' );
361 $store = new WatchedItemStore(
362 $this->getMockLoadBalancer( $mockDb ),
366 $store->duplicateAllAssociatedEntries(
367 Title
::newFromText( 'Old_Title' ),
368 Title
::newFromText( 'New_Title' )
372 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
374 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
377 $mockDb = $this->getMockDb();
378 $mockDb->expects( $this->at( 0 ) )
384 'wl_notificationtimestamp',
388 'wl_title' => 'Old_Title',
391 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
392 $mockDb->expects( $this->at( 1 ) )
393 ->method( 'replace' )
396 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
401 'wl_title' => 'New_Title',
402 'wl_notificationtimestamp' => '20151212010101',
405 $this->isType( 'string' )
407 $mockDb->expects( $this->at( 2 ) )
413 'wl_notificationtimestamp',
417 'wl_title' => 'Old_Title',
420 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
421 $mockDb->expects( $this->at( 3 ) )
422 ->method( 'replace' )
425 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
430 'wl_title' => 'New_Title',
431 'wl_notificationtimestamp' => '20151212010101',
434 $this->isType( 'string' )
437 $mockCache = $this->getMockCache();
438 $mockCache->expects( $this->never() )->method( 'get' );
439 $mockCache->expects( $this->never() )->method( 'delete' );
441 $store = new WatchedItemStore(
442 $this->getMockLoadBalancer( $mockDb ),
446 $store->duplicateAllAssociatedEntries(
447 Title
::newFromText( 'Old_Title' ),
448 Title
::newFromText( 'New_Title' )
452 public function testAddWatch_nonAnonymousUser() {
453 $mockDb = $this->getMockDb();
454 $mockDb->expects( $this->once() )
462 'wl_title' => 'Some_Page',
463 'wl_notificationtimestamp' => null,
468 $mockCache = $this->getMockCache();
469 $mockCache->expects( $this->once() )
471 ->with( '0:Some_Page:1' );
473 $store = new WatchedItemStore(
474 $this->getMockLoadBalancer( $mockDb ),
479 $this->getMockNonAnonUserWithId( 1 ),
480 Title
::newFromText( 'Some_Page' )
484 public function testAddWatch_anonymousUser() {
485 $mockDb = $this->getMockDb();
486 $mockDb->expects( $this->never() )
487 ->method( 'insert' );
489 $mockCache = $this->getMockCache();
490 $mockCache->expects( $this->never() )
491 ->method( 'delete' );
493 $store = new WatchedItemStore(
494 $this->getMockLoadBalancer( $mockDb ),
499 $this->getAnonUser(),
500 Title
::newFromText( 'Some_Page' )
504 public function testAddWatchBatch_nonAnonymousUser() {
505 $mockDb = $this->getMockDb();
506 $mockDb->expects( $this->once() )
514 'wl_title' => 'Some_Page',
515 'wl_notificationtimestamp' => null,
520 'wl_title' => 'Some_Page',
521 'wl_notificationtimestamp' => null,
526 $mockCache = $this->getMockCache();
527 $mockCache->expects( $this->exactly( 2 ) )
528 ->method( 'delete' );
529 $mockCache->expects( $this->at( 1 ) )
531 ->with( '0:Some_Page:1' );
532 $mockCache->expects( $this->at( 3 ) )
534 ->with( '1:Some_Page:1' );
536 $store = new WatchedItemStore(
537 $this->getMockLoadBalancer( $mockDb ),
541 $mockUser = $this->getMockNonAnonUserWithId( 1 );
544 $store->addWatchBatch(
546 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
547 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
553 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
554 $mockDb = $this->getMockDb();
555 $mockDb->expects( $this->once() )
563 'wl_title' => 'Some_Page',
564 'wl_notificationtimestamp' => null,
569 $mockCache = $this->getMockCache();
570 $mockCache->expects( $this->once() )
572 ->with( '0:Some_Page:1' );
574 $store = new WatchedItemStore(
575 $this->getMockLoadBalancer( $mockDb ),
580 $store->addWatchBatch(
582 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
583 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
589 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
590 $mockDb = $this->getMockDb();
591 $mockDb->expects( $this->never() )
592 ->method( 'insert' );
594 $mockCache = $this->getMockCache();
595 $mockCache->expects( $this->never() )
596 ->method( 'delete' );
598 $store = new WatchedItemStore(
599 $this->getMockLoadBalancer( $mockDb ),
603 $anonUser = $this->getAnonUser();
605 $store->addWatchBatch(
607 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
608 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
614 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
615 $mockDb = $this->getMockDb();
616 $mockDb->expects( $this->never() )
617 ->method( 'insert' );
619 $mockCache = $this->getMockCache();
620 $mockCache->expects( $this->never() )
621 ->method( 'delete' );
623 $store = new WatchedItemStore(
624 $this->getMockLoadBalancer( $mockDb ),
629 $store->addWatchBatch( [] )
633 public function testLoadWatchedItem_existingItem() {
634 $mockDb = $this->getMockDb();
635 $mockDb->expects( $this->once() )
636 ->method( 'selectRow' )
639 'wl_notificationtimestamp',
643 'wl_title' => 'SomeDbKey',
646 ->will( $this->returnValue(
647 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
650 $mockCache = $this->getMockCache();
651 $mockCache->expects( $this->once() )
657 $store = new WatchedItemStore(
658 $this->getMockLoadBalancer( $mockDb ),
662 $watchedItem = $store->loadWatchedItem(
663 $this->getMockNonAnonUserWithId( 1 ),
664 new TitleValue( 0, 'SomeDbKey' )
666 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
667 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
668 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
669 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
672 public function testLoadWatchedItem_noItem() {
673 $mockDb = $this->getMockDb();
674 $mockDb->expects( $this->once() )
675 ->method( 'selectRow' )
678 'wl_notificationtimestamp',
682 'wl_title' => 'SomeDbKey',
685 ->will( $this->returnValue( [] ) );
687 $mockCache = $this->getMockCache();
688 $mockCache->expects( $this->never() )->method( 'get' );
689 $mockCache->expects( $this->never() )->method( 'delete' );
691 $store = new WatchedItemStore(
692 $this->getMockLoadBalancer( $mockDb ),
697 $store->loadWatchedItem(
698 $this->getMockNonAnonUserWithId( 1 ),
699 new TitleValue( 0, 'SomeDbKey' )
704 public function testLoadWatchedItem_anonymousUser() {
705 $mockDb = $this->getMockDb();
706 $mockDb->expects( $this->never() )
707 ->method( 'selectRow' );
709 $mockCache = $this->getMockCache();
710 $mockCache->expects( $this->never() )->method( 'get' );
711 $mockCache->expects( $this->never() )->method( 'delete' );
713 $store = new WatchedItemStore(
714 $this->getMockLoadBalancer( $mockDb ),
719 $store->loadWatchedItem(
720 $this->getAnonUser(),
721 new TitleValue( 0, 'SomeDbKey' )
726 public function testRemoveWatch_existingItem() {
727 $mockDb = $this->getMockDb();
728 $mockDb->expects( $this->once() )
735 'wl_title' => 'SomeDbKey',
738 $mockDb->expects( $this->once() )
739 ->method( 'affectedRows' )
740 ->will( $this->returnValue( 1 ) );
742 $mockCache = $this->getMockCache();
743 $mockCache->expects( $this->never() )->method( 'get' );
744 $mockCache->expects( $this->once() )
746 ->with( '0:SomeDbKey:1' );
748 $store = new WatchedItemStore(
749 $this->getMockLoadBalancer( $mockDb ),
755 $this->getMockNonAnonUserWithId( 1 ),
756 new TitleValue( 0, 'SomeDbKey' )
761 public function testRemoveWatch_noItem() {
762 $mockDb = $this->getMockDb();
763 $mockDb->expects( $this->once() )
770 'wl_title' => 'SomeDbKey',
773 $mockDb->expects( $this->once() )
774 ->method( 'affectedRows' )
775 ->will( $this->returnValue( 0 ) );
777 $mockCache = $this->getMockCache();
778 $mockCache->expects( $this->never() )->method( 'get' );
779 $mockCache->expects( $this->once() )
781 ->with( '0:SomeDbKey:1' );
783 $store = new WatchedItemStore(
784 $this->getMockLoadBalancer( $mockDb ),
790 $this->getMockNonAnonUserWithId( 1 ),
791 new TitleValue( 0, 'SomeDbKey' )
796 public function testRemoveWatch_anonymousUser() {
797 $mockDb = $this->getMockDb();
798 $mockDb->expects( $this->never() )
799 ->method( 'delete' );
801 $mockCache = $this->getMockCache();
802 $mockCache->expects( $this->never() )->method( 'get' );
803 $mockCache->expects( $this->never() )
804 ->method( 'delete' );
806 $store = new WatchedItemStore(
807 $this->getMockLoadBalancer( $mockDb ),
813 $this->getAnonUser(),
814 new TitleValue( 0, 'SomeDbKey' )
819 public function testGetWatchedItem_existingItem() {
820 $mockDb = $this->getMockDb();
821 $mockDb->expects( $this->once() )
822 ->method( 'selectRow' )
825 'wl_notificationtimestamp',
829 'wl_title' => 'SomeDbKey',
832 ->will( $this->returnValue(
833 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
836 $mockCache = $this->getMockCache();
837 $mockCache->expects( $this->never() )->method( 'delete' );
838 $mockCache->expects( $this->once() )
843 ->will( $this->returnValue( null ) );
844 $mockCache->expects( $this->once() )
850 $store = new WatchedItemStore(
851 $this->getMockLoadBalancer( $mockDb ),
855 $watchedItem = $store->getWatchedItem(
856 $this->getMockNonAnonUserWithId( 1 ),
857 new TitleValue( 0, 'SomeDbKey' )
859 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
860 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
861 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
862 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
865 public function testGetWatchedItem_cachedItem() {
866 $mockDb = $this->getMockDb();
867 $mockDb->expects( $this->never() )
868 ->method( 'selectRow' );
870 $mockUser = $this->getMockNonAnonUserWithId( 1 );
871 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
872 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
874 $mockCache = $this->getMockCache();
875 $mockCache->expects( $this->never() )->method( 'delete' );
876 $mockCache->expects( $this->never() )->method( 'set' );
877 $mockCache->expects( $this->once() )
882 ->will( $this->returnValue( $cachedItem ) );
884 $store = new WatchedItemStore(
885 $this->getMockLoadBalancer( $mockDb ),
891 $store->getWatchedItem(
898 public function testGetWatchedItem_noItem() {
899 $mockDb = $this->getMockDb();
900 $mockDb->expects( $this->once() )
901 ->method( 'selectRow' )
904 'wl_notificationtimestamp',
908 'wl_title' => 'SomeDbKey',
911 ->will( $this->returnValue( [] ) );
913 $mockCache = $this->getMockCache();
914 $mockCache->expects( $this->never() )->method( 'set' );
915 $mockCache->expects( $this->never() )->method( 'delete' );
916 $mockCache->expects( $this->once() )
918 ->with( '0:SomeDbKey:1' )
919 ->will( $this->returnValue( false ) );
921 $store = new WatchedItemStore(
922 $this->getMockLoadBalancer( $mockDb ),
927 $store->getWatchedItem(
928 $this->getMockNonAnonUserWithId( 1 ),
929 new TitleValue( 0, 'SomeDbKey' )
934 public function testGetWatchedItem_anonymousUser() {
935 $mockDb = $this->getMockDb();
936 $mockDb->expects( $this->never() )
937 ->method( 'selectRow' );
939 $mockCache = $this->getMockCache();
940 $mockCache->expects( $this->never() )->method( 'set' );
941 $mockCache->expects( $this->never() )->method( 'get' );
942 $mockCache->expects( $this->never() )->method( 'delete' );
944 $store = new WatchedItemStore(
945 $this->getMockLoadBalancer( $mockDb ),
950 $store->getWatchedItem(
951 $this->getAnonUser(),
952 new TitleValue( 0, 'SomeDbKey' )
957 public function testIsWatchedItem_existingItem() {
958 $mockDb = $this->getMockDb();
959 $mockDb->expects( $this->once() )
960 ->method( 'selectRow' )
963 'wl_notificationtimestamp',
967 'wl_title' => 'SomeDbKey',
970 ->will( $this->returnValue(
971 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
974 $mockCache = $this->getMockCache();
975 $mockCache->expects( $this->never() )->method( 'delete' );
976 $mockCache->expects( $this->once() )
978 ->with( '0:SomeDbKey:1' )
979 ->will( $this->returnValue( false ) );
980 $mockCache->expects( $this->once() )
986 $store = new WatchedItemStore(
987 $this->getMockLoadBalancer( $mockDb ),
993 $this->getMockNonAnonUserWithId( 1 ),
994 new TitleValue( 0, 'SomeDbKey' )
999 public function testIsWatchedItem_noItem() {
1000 $mockDb = $this->getMockDb();
1001 $mockDb->expects( $this->once() )
1002 ->method( 'selectRow' )
1005 'wl_notificationtimestamp',
1008 'wl_namespace' => 0,
1009 'wl_title' => 'SomeDbKey',
1012 ->will( $this->returnValue( [] ) );
1014 $mockCache = $this->getMockCache();
1015 $mockCache->expects( $this->never() )->method( 'set' );
1016 $mockCache->expects( $this->never() )->method( 'delete' );
1017 $mockCache->expects( $this->once() )
1019 ->with( '0:SomeDbKey:1' )
1020 ->will( $this->returnValue( false ) );
1022 $store = new WatchedItemStore(
1023 $this->getMockLoadBalancer( $mockDb ),
1029 $this->getMockNonAnonUserWithId( 1 ),
1030 new TitleValue( 0, 'SomeDbKey' )
1035 public function testIsWatchedItem_anonymousUser() {
1036 $mockDb = $this->getMockDb();
1037 $mockDb->expects( $this->never() )
1038 ->method( 'selectRow' );
1040 $mockCache = $this->getMockCache();
1041 $mockCache->expects( $this->never() )->method( 'set' );
1042 $mockCache->expects( $this->never() )->method( 'get' );
1043 $mockCache->expects( $this->never() )->method( 'delete' );
1045 $store = new WatchedItemStore(
1046 $this->getMockLoadBalancer( $mockDb ),
1052 $this->getAnonUser(),
1053 new TitleValue( 0, 'SomeDbKey' )
1058 public function testResetNotificationTimestamp_anonymousUser() {
1059 $mockDb = $this->getMockDb();
1060 $mockDb->expects( $this->never() )
1061 ->method( 'selectRow' );
1063 $mockCache = $this->getMockCache();
1064 $mockCache->expects( $this->never() )->method( 'get' );
1065 $mockCache->expects( $this->never() )->method( 'set' );
1066 $mockCache->expects( $this->never() )->method( 'delete' );
1068 $store = new WatchedItemStore(
1069 $this->getMockLoadBalancer( $mockDb ),
1074 $store->resetNotificationTimestamp(
1075 $this->getAnonUser(),
1076 Title
::newFromText( 'SomeDbKey' )
1081 public function testResetNotificationTimestamp_noItem() {
1082 $mockDb = $this->getMockDb();
1083 $mockDb->expects( $this->once() )
1084 ->method( 'selectRow' )
1087 'wl_notificationtimestamp',
1090 'wl_namespace' => 0,
1091 'wl_title' => 'SomeDbKey',
1094 ->will( $this->returnValue( [] ) );
1096 $mockCache = $this->getMockCache();
1097 $mockCache->expects( $this->never() )->method( 'get' );
1098 $mockCache->expects( $this->never() )->method( 'set' );
1099 $mockCache->expects( $this->never() )->method( 'delete' );
1101 $store = new WatchedItemStore(
1102 $this->getMockLoadBalancer( $mockDb ),
1107 $store->resetNotificationTimestamp(
1108 $this->getMockNonAnonUserWithId( 1 ),
1109 Title
::newFromText( 'SomeDbKey' )
1114 public function testResetNotificationTimestamp_item() {
1115 $user = $this->getMockNonAnonUserWithId( 1 );
1116 $title = Title
::newFromText( 'SomeDbKey' );
1118 $mockDb = $this->getMockDb();
1119 $mockDb->expects( $this->once() )
1120 ->method( 'selectRow' )
1123 'wl_notificationtimestamp',
1126 'wl_namespace' => 0,
1127 'wl_title' => 'SomeDbKey',
1130 ->will( $this->returnValue(
1131 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1134 $mockCache = $this->getMockCache();
1135 $mockCache->expects( $this->never() )->method( 'get' );
1136 $mockCache->expects( $this->once() )
1140 $this->isInstanceOf( WatchedItem
::class )
1142 $mockCache->expects( $this->once() )
1143 ->method( 'delete' )
1144 ->with( '0:SomeDbKey:1' );
1146 $store = new WatchedItemStore(
1147 $this->getMockLoadBalancer( $mockDb ),
1151 // Note: This does not actually assert the job is correct
1152 $callableCallCounter = 0;
1153 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1154 $callableCallCounter++
;
1155 $this->assertInternalType( 'callable', $callable );
1157 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1160 $store->resetNotificationTimestamp(
1165 $this->assertEquals( 1, $callableCallCounter );
1168 public function testResetNotificationTimestamp_noItemForced() {
1169 $user = $this->getMockNonAnonUserWithId( 1 );
1170 $title = Title
::newFromText( 'SomeDbKey' );
1172 $mockDb = $this->getMockDb();
1173 $mockDb->expects( $this->never() )
1174 ->method( 'selectRow' );
1176 $mockCache = $this->getMockCache();
1177 $mockDb->expects( $this->never() )
1179 $mockDb->expects( $this->never() )
1181 $mockDb->expects( $this->never() )
1182 ->method( 'delete' );
1184 $store = new WatchedItemStore(
1185 $this->getMockLoadBalancer( $mockDb ),
1189 // Note: This does not actually assert the job is correct
1190 $callableCallCounter = 0;
1191 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1192 $callableCallCounter++
;
1193 $this->assertInternalType( 'callable', $callable );
1195 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1198 $store->resetNotificationTimestamp(
1204 $this->assertEquals( 1, $callableCallCounter );
1211 * @return PHPUnit_Framework_MockObject_MockObject|Title
1213 private function getMockTitle( $text, $ns = 0 ) {
1214 $title = $this->getMock( Title
::class );
1215 $title->expects( $this->any() )
1216 ->method( 'getText' )
1217 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1218 $title->expects( $this->any() )
1219 ->method( 'getDbKey' )
1220 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1221 $title->expects( $this->any() )
1222 ->method( 'getNamespace' )
1223 ->will( $this->returnValue( $ns ) );
1227 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1228 $user = $this->getMockNonAnonUserWithId( 1 );
1230 $title = $this->getMockTitle( 'SomeTitle' );
1231 $title->expects( $this->once() )
1232 ->method( 'getNextRevisionID' )
1234 ->will( $this->returnValue( false ) );
1236 $mockDb = $this->getMockDb();
1237 $mockDb->expects( $this->never() )
1238 ->method( 'selectRow' );
1240 $mockCache = $this->getMockCache();
1241 $mockDb->expects( $this->never() )
1243 $mockDb->expects( $this->never() )
1245 $mockDb->expects( $this->never() )
1246 ->method( 'delete' );
1248 $store = new WatchedItemStore(
1249 $this->getMockLoadBalancer( $mockDb ),
1253 // Note: This does not actually assert the job is correct
1254 $callableCallCounter = 0;
1255 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1256 function( $callable ) use ( &$callableCallCounter ) {
1257 $callableCallCounter++
;
1258 $this->assertInternalType( 'callable', $callable );
1263 $store->resetNotificationTimestamp(
1270 $this->assertEquals( 1, $callableCallCounter );
1273 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1274 $user = $this->getMockNonAnonUserWithId( 1 );
1276 $title = $this->getMockTitle( 'SomeDbKey' );
1277 $title->expects( $this->once() )
1278 ->method( 'getNextRevisionID' )
1280 ->will( $this->returnValue( 33 ) );
1282 $mockDb = $this->getMockDb();
1283 $mockDb->expects( $this->once() )
1284 ->method( 'selectRow' )
1287 'wl_notificationtimestamp',
1290 'wl_namespace' => 0,
1291 'wl_title' => 'SomeDbKey',
1294 ->will( $this->returnValue(
1295 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1298 $mockCache = $this->getMockCache();
1299 $mockDb->expects( $this->never() )
1301 $mockDb->expects( $this->never() )
1303 $mockDb->expects( $this->never() )
1304 ->method( 'delete' );
1306 $store = new WatchedItemStore(
1307 $this->getMockLoadBalancer( $mockDb ),
1311 // Note: This does not actually assert the job is correct
1312 $addUpdateCallCounter = 0;
1313 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1314 function( $callable ) use ( &$addUpdateCallCounter ) {
1315 $addUpdateCallCounter++
;
1316 $this->assertInternalType( 'callable', $callable );
1320 $getTimestampCallCounter = 0;
1321 $store->overrideRevisionGetTimestampFromIdCallback(
1322 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1323 $getTimestampCallCounter++
;
1324 $this->assertEquals( $title, $titleParam );
1325 $this->assertEquals( $oldid, $oldidParam );
1330 $store->resetNotificationTimestamp(
1337 $this->assertEquals( 1, $addUpdateCallCounter );
1338 $this->assertEquals( 1, $getTimestampCallCounter );
1341 public function testUpdateNotificationTimestamp_watchersExist() {
1342 $mockDb = $this->getMockDb();
1343 $mockDb->expects( $this->once() )
1344 ->method( 'select' )
1350 'wl_namespace' => 0,
1351 'wl_title' => 'SomeDbKey',
1352 'wl_notificationtimestamp IS NULL'
1356 $this->returnValue( [
1357 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1358 $this->getFakeRow( [ 'wl_user' => '3' ] )
1361 $mockDb->expects( $this->once() )
1362 ->method( 'onTransactionIdle' )
1363 ->with( $this->isType( 'callable' ) )
1364 ->will( $this->returnCallback( function( $callable ) {
1367 $mockDb->expects( $this->once() )
1368 ->method( 'update' )
1371 [ 'wl_notificationtimestamp' => null ],
1373 'wl_user' => [ 2, 3 ],
1374 'wl_namespace' => 0,
1375 'wl_title' => 'SomeDbKey',
1379 $mockCache = $this->getMockCache();
1380 $mockCache->expects( $this->never() )->method( 'set' );
1381 $mockCache->expects( $this->never() )->method( 'get' );
1382 $mockCache->expects( $this->never() )->method( 'delete' );
1384 $store = new WatchedItemStore(
1385 $this->getMockLoadBalancer( $mockDb ),
1389 $this->assertEquals(
1391 $store->updateNotificationTimestamp(
1392 $this->getMockNonAnonUserWithId( 1 ),
1393 new TitleValue( 0, 'SomeDbKey' ),
1399 public function testUpdateNotificationTimestamp_noWatchers() {
1400 $mockDb = $this->getMockDb();
1401 $mockDb->expects( $this->once() )
1402 ->method( 'select' )
1408 'wl_namespace' => 0,
1409 'wl_title' => 'SomeDbKey',
1410 'wl_notificationtimestamp IS NULL'
1414 $this->returnValue( [] )
1416 $mockDb->expects( $this->never() )
1417 ->method( 'onTransactionIdle' );
1418 $mockDb->expects( $this->never() )
1419 ->method( 'update' );
1421 $mockCache = $this->getMockCache();
1422 $mockCache->expects( $this->never() )->method( 'set' );
1423 $mockCache->expects( $this->never() )->method( 'get' );
1424 $mockCache->expects( $this->never() )->method( 'delete' );
1426 $store = new WatchedItemStore(
1427 $this->getMockLoadBalancer( $mockDb ),
1431 $watchers = $store->updateNotificationTimestamp(
1432 $this->getMockNonAnonUserWithId( 1 ),
1433 new TitleValue( 0, 'SomeDbKey' ),
1436 $this->assertInternalType( 'array', $watchers );
1437 $this->assertEmpty( $watchers );
1440 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1441 $user = $this->getMockNonAnonUserWithId( 1 );
1442 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1444 $mockDb = $this->getMockDb();
1445 $mockDb->expects( $this->once() )
1446 ->method( 'selectRow' )
1447 ->will( $this->returnValue(
1448 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1450 $mockDb->expects( $this->once() )
1451 ->method( 'select' )
1453 $this->returnValue( [
1454 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1455 $this->getFakeRow( [ 'wl_user' => '3' ] )
1458 $mockDb->expects( $this->once() )
1459 ->method( 'onTransactionIdle' )
1460 ->with( $this->isType( 'callable' ) )
1461 ->will( $this->returnCallback( function( $callable ) {
1464 $mockDb->expects( $this->once() )
1465 ->method( 'update' );
1467 $mockCache = $this->getMockCache();
1468 $mockCache->expects( $this->once() )
1470 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1471 $mockCache->expects( $this->once() )
1473 ->with( '0:SomeDbKey:1' );
1474 $mockCache->expects( $this->once() )
1475 ->method( 'delete' )
1476 ->with( '0:SomeDbKey:1' );
1478 $store = new WatchedItemStore(
1479 $this->getMockLoadBalancer( $mockDb ),
1483 // This will add the item to the cache
1484 $store->getWatchedItem( $user, $titleValue );
1486 $store->updateNotificationTimestamp(
1487 $this->getMockNonAnonUserWithId( 1 ),