4 * @covers WatchedItemQueryService
6 class WatchedItemQueryServiceUnitTest
extends PHPUnit_Framework_TestCase
{
9 * @return PHPUnit_Framework_MockObject_MockObject|Database
11 private function getMockDb() {
12 $mock = $this->getMockBuilder( Database
::class )
13 ->disableOriginalConstructor()
16 $mock->expects( $this->any() )
17 ->method( 'makeList' )
19 $this->isType( 'array' ),
20 $this->isType( 'int' )
22 ->will( $this->returnCallback( function( $a, $conj ) {
23 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
24 return join( $sqlConj, array_map( function( $s ) {
25 return '(' . $s . ')';
30 $mock->expects( $this->any() )
31 ->method( 'addQuotes' )
32 ->will( $this->returnCallback( function( $value ) {
36 $mock->expects( $this->any() )
37 ->method( 'timestamp' )
38 ->will( $this->returnArgument( 0 ) );
40 $mock->expects( $this->any() )
42 ->willReturnCallback( function( $a, $b ) {
51 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
53 private function getMockLoadBalancer( $mockDb ) {
54 $mock = $this->getMockBuilder( LoadBalancer
::class )
55 ->disableOriginalConstructor()
57 $mock->expects( $this->any() )
58 ->method( 'getConnectionRef' )
60 ->will( $this->returnValue( $mockDb ) );
66 * @return PHPUnit_Framework_MockObject_MockObject|User
68 private function getMockNonAnonUserWithId( $id ) {
69 $mock = $this->getMock( User
::class );
70 $mock->expects( $this->any() )
72 ->will( $this->returnValue( false ) );
73 $mock->expects( $this->any() )
75 ->will( $this->returnValue( $id ) );
81 * @return PHPUnit_Framework_MockObject_MockObject|User
83 private function getMockUnrestrictedNonAnonUserWithId( $id ) {
84 $mock = $this->getMockNonAnonUserWithId( $id );
85 $mock->expects( $this->any() )
86 ->method( 'isAllowed' )
87 ->will( $this->returnValue( true ) );
88 $mock->expects( $this->any() )
89 ->method( 'isAllowedAny' )
90 ->will( $this->returnValue( true ) );
91 $mock->expects( $this->any() )
92 ->method( 'useRCPatrol' )
93 ->will( $this->returnValue( true ) );
99 * @param string $notAllowedAction
100 * @return PHPUnit_Framework_MockObject_MockObject|User
102 private function getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction ) {
103 $mock = $this->getMockNonAnonUserWithId( $id );
105 $mock->expects( $this->any() )
106 ->method( 'isAllowed' )
107 ->will( $this->returnCallback( function( $action ) use ( $notAllowedAction ) {
108 return $action !== $notAllowedAction;
110 $mock->expects( $this->any() )
111 ->method( 'isAllowedAny' )
112 ->will( $this->returnCallback( function() use ( $notAllowedAction ) {
113 $actions = func_get_args();
114 return !in_array( $notAllowedAction, $actions );
122 * @return PHPUnit_Framework_MockObject_MockObject|User
124 private function getMockNonAnonUserWithIdAndNoPatrolRights( $id ) {
125 $mock = $this->getMockNonAnonUserWithId( $id );
127 $mock->expects( $this->any() )
128 ->method( 'isAllowed' )
129 ->will( $this->returnValue( true ) );
130 $mock->expects( $this->any() )
131 ->method( 'isAllowedAny' )
132 ->will( $this->returnValue( true ) );
134 $mock->expects( $this->any() )
135 ->method( 'useRCPatrol' )
136 ->will( $this->returnValue( false ) );
137 $mock->expects( $this->any() )
138 ->method( 'useNPPatrol' )
139 ->will( $this->returnValue( false ) );
144 private function getMockAnonUser() {
145 $mock = $this->getMock( User
::class );
146 $mock->expects( $this->any() )
148 ->will( $this->returnValue( true ) );
152 private function getFakeRow( array $rowValues ) {
153 $fakeRow = new stdClass();
154 foreach ( $rowValues as $valueName => $value ) {
155 $fakeRow->$valueName = $value;
160 public function testGetWatchedItemsWithRecentChangeInfo() {
161 $mockDb = $this->getMockDb();
162 $mockDb->expects( $this->once() )
165 [ 'recentchanges', 'watchlist', 'page' ],
173 'wl_notificationtimestamp',
180 '(rc_this_oldid=page_latest) OR (rc_type=3)',
182 $this->isType( 'string' ),
188 'wl_namespace=rc_namespace',
198 ->will( $this->returnValue( [
202 'rc_title' => 'Foo1',
203 'rc_timestamp' => '20151212010101',
206 'wl_notificationtimestamp' => '20151212010101',
211 'rc_title' => 'Foo2',
212 'rc_timestamp' => '20151212010102',
215 'wl_notificationtimestamp' => null,
219 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
220 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
222 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user );
224 $this->assertInternalType( 'array', $items );
225 $this->assertCount( 2, $items );
227 foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
228 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
229 $this->assertInternalType( 'array', $recentChangeInfo );
233 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
240 'rc_title' => 'Foo1',
241 'rc_timestamp' => '20151212010101',
249 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
256 'rc_title' => 'Foo2',
257 'rc_timestamp' => '20151212010102',
265 public function getWatchedItemsWithRecentChangeInfoOptionsProvider() {
268 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_FLAGS
] ],
269 [ 'rc_type', 'rc_minor', 'rc_bot' ],
274 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_USER
] ],
280 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_USER_ID
] ],
286 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_COMMENT
] ],
292 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_PATROL_INFO
] ],
293 [ 'rc_patrolled', 'rc_log_type' ],
298 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_SIZES
] ],
299 [ 'rc_old_len', 'rc_new_len' ],
304 [ 'includeFields' => [ WatchedItemQueryService
::INCLUDE_LOG_INFO
] ],
305 [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
310 [ 'namespaceIds' => [ 0, 1 ] ],
312 [ 'wl_namespace' => [ 0, 1 ] ],
316 [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
318 [ 'wl_namespace' => [ 0, 1 ] ],
322 [ 'rcTypes' => [ RC_EDIT
, RC_NEW
] ],
324 [ 'rc_type' => [ RC_EDIT
, RC_NEW
] ],
328 [ 'dir' => WatchedItemQueryService
::DIR_OLDER
],
331 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
334 [ 'dir' => WatchedItemQueryService
::DIR_NEWER
],
337 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
340 [ 'dir' => WatchedItemQueryService
::DIR_OLDER
, 'start' => '20151212010101' ],
342 [ "rc_timestamp <= '20151212010101'" ],
343 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
346 [ 'dir' => WatchedItemQueryService
::DIR_OLDER
, 'end' => '20151212010101' ],
348 [ "rc_timestamp >= '20151212010101'" ],
349 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
353 'dir' => WatchedItemQueryService
::DIR_OLDER
,
354 'start' => '20151212020101',
355 'end' => '20151212010101'
358 [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
359 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
362 [ 'dir' => WatchedItemQueryService
::DIR_NEWER
, 'start' => '20151212010101' ],
364 [ "rc_timestamp >= '20151212010101'" ],
365 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
368 [ 'dir' => WatchedItemQueryService
::DIR_NEWER
, 'end' => '20151212010101' ],
370 [ "rc_timestamp <= '20151212010101'" ],
371 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
375 'dir' => WatchedItemQueryService
::DIR_NEWER
,
376 'start' => '20151212010101',
377 'end' => '20151212020101'
380 [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
381 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
390 [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
396 [ 'filters' => [ WatchedItemQueryService
::FILTER_MINOR
] ],
402 [ 'filters' => [ WatchedItemQueryService
::FILTER_NOT_MINOR
] ],
408 [ 'filters' => [ WatchedItemQueryService
::FILTER_BOT
] ],
414 [ 'filters' => [ WatchedItemQueryService
::FILTER_NOT_BOT
] ],
420 [ 'filters' => [ WatchedItemQueryService
::FILTER_ANON
] ],
426 [ 'filters' => [ WatchedItemQueryService
::FILTER_NOT_ANON
] ],
432 [ 'filters' => [ WatchedItemQueryService
::FILTER_PATROLLED
] ],
434 [ 'rc_patrolled != 0' ],
438 [ 'filters' => [ WatchedItemQueryService
::FILTER_NOT_PATROLLED
] ],
440 [ 'rc_patrolled = 0' ],
444 [ 'filters' => [ WatchedItemQueryService
::FILTER_UNREAD
] ],
446 [ 'rc_timestamp >= wl_notificationtimestamp' ],
450 [ 'filters' => [ WatchedItemQueryService
::FILTER_NOT_UNREAD
] ],
452 [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
456 [ 'onlyByUser' => 'SomeOtherUser' ],
458 [ 'rc_user_text' => 'SomeOtherUser' ],
462 [ 'notByUser' => 'SomeOtherUser' ],
464 [ "rc_user_text != 'SomeOtherUser'" ],
468 [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService
::DIR_OLDER
],
471 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
473 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
476 [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService
::DIR_NEWER
],
479 "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
481 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
485 'startFrom' => [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
486 'dir' => WatchedItemQueryService
::DIR_OLDER
490 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
492 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
498 * @dataProvider getWatchedItemsWithRecentChangeInfoOptionsProvider
500 public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
502 array $expectedExtraFields,
503 array $expectedExtraConds,
504 array $expectedDbOptions
506 $expectedFields = array_merge(
514 'wl_notificationtimestamp',
522 $expectedConds = array_merge(
523 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
527 $mockDb = $this->getMockDb();
528 $mockDb->expects( $this->once() )
531 [ 'recentchanges', 'watchlist', 'page' ],
534 $this->isType( 'string' ),
540 'wl_namespace=rc_namespace',
550 ->will( $this->returnValue( [] ) );
552 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
553 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
555 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
557 $this->assertEmpty( $items );
560 public function filterPatrolledOptionProvider() {
562 [ WatchedItemQueryService
::FILTER_PATROLLED
],
563 [ WatchedItemQueryService
::FILTER_NOT_PATROLLED
],
568 * @dataProvider filterPatrolledOptionProvider
570 public function testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights(
573 $mockDb = $this->getMockDb();
574 $mockDb->expects( $this->once() )
577 [ 'recentchanges', 'watchlist', 'page' ],
578 $this->isType( 'array' ),
579 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
580 $this->isType( 'string' ),
581 $this->isType( 'array' ),
582 $this->isType( 'array' )
584 ->will( $this->returnValue( [] ) );
586 $user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
588 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
589 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
591 [ 'filters' => [ $filtersOption ] ]
594 $this->assertEmpty( $items );
597 public function mysqlIndexOptimizationProvider() {
602 [ "rc_timestamp > ''" ],
606 [ 'start' => '20151212010101', 'dir' => WatchedItemQueryService
::DIR_OLDER
],
607 [ "rc_timestamp <= '20151212010101'" ],
611 [ 'end' => '20151212010101', 'dir' => WatchedItemQueryService
::DIR_OLDER
],
612 [ "rc_timestamp >= '20151212010101'" ],
623 * @dataProvider mysqlIndexOptimizationProvider
625 public function testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization(
628 array $expectedExtraConds
630 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
631 $conds = array_merge( $commonConds, $expectedExtraConds );
633 $mockDb = $this->getMockDb();
634 $mockDb->expects( $this->once() )
637 [ 'recentchanges', 'watchlist', 'page' ],
638 $this->isType( 'array' ),
640 $this->isType( 'string' ),
641 $this->isType( 'array' ),
642 $this->isType( 'array' )
644 ->will( $this->returnValue( [] ) );
645 $mockDb->expects( $this->any() )
646 ->method( 'getType' )
647 ->will( $this->returnValue( $dbType ) );
649 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
650 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
652 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
654 $this->assertEmpty( $items );
657 public function userPermissionRelatedExtraChecksProvider() {
663 '(rc_type != ' . RC_LOG
. ') OR ((rc_deleted & ' . LogPage
::DELETED_ACTION
. ') != ' .
664 LogPage
::DELETED_ACTION
. ')'
671 '(rc_type != ' . RC_LOG
. ') OR (' .
672 '(rc_deleted & ' . ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ') != ' .
673 ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ')'
680 '(rc_type != ' . RC_LOG
. ') OR (' .
681 '(rc_deleted & ' . ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ') != ' .
682 ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ')'
686 [ 'onlyByUser' => 'SomeOtherUser' ],
689 'rc_user_text' => 'SomeOtherUser',
690 '(rc_deleted & ' . Revision
::DELETED_USER
. ') != ' . Revision
::DELETED_USER
,
691 '(rc_type != ' . RC_LOG
. ') OR ((rc_deleted & ' . LogPage
::DELETED_ACTION
. ') != ' .
692 LogPage
::DELETED_ACTION
. ')'
696 [ 'onlyByUser' => 'SomeOtherUser' ],
699 'rc_user_text' => 'SomeOtherUser',
700 '(rc_deleted & ' . ( Revision
::DELETED_USER | Revision
::DELETED_RESTRICTED
) . ') != ' .
701 ( Revision
::DELETED_USER | Revision
::DELETED_RESTRICTED
),
702 '(rc_type != ' . RC_LOG
. ') OR (' .
703 '(rc_deleted & ' . ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ') != ' .
704 ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ')'
708 [ 'onlyByUser' => 'SomeOtherUser' ],
711 'rc_user_text' => 'SomeOtherUser',
712 '(rc_deleted & ' . ( Revision
::DELETED_USER | Revision
::DELETED_RESTRICTED
) . ') != ' .
713 ( Revision
::DELETED_USER | Revision
::DELETED_RESTRICTED
),
714 '(rc_type != ' . RC_LOG
. ') OR (' .
715 '(rc_deleted & ' . ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ') != ' .
716 ( LogPage
::DELETED_ACTION | LogPage
::DELETED_RESTRICTED
) . ')'
723 * @dataProvider userPermissionRelatedExtraChecksProvider
725 public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
728 array $expectedExtraConds
730 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
731 $conds = array_merge( $commonConds, $expectedExtraConds );
733 $mockDb = $this->getMockDb();
734 $mockDb->expects( $this->once() )
737 [ 'recentchanges', 'watchlist', 'page' ],
738 $this->isType( 'array' ),
740 $this->isType( 'string' ),
741 $this->isType( 'array' ),
742 $this->isType( 'array' )
744 ->will( $this->returnValue( [] ) );
746 $user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
748 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
749 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
751 $this->assertEmpty( $items );
754 public function testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult() {
755 $mockDb = $this->getMockDb();
756 $mockDb->expects( $this->once() )
759 [ 'recentchanges', 'watchlist' ],
767 'wl_notificationtimestamp',
774 $this->isType( 'string' ),
780 'wl_namespace=rc_namespace',
786 ->will( $this->returnValue( [] ) );
788 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
789 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
791 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
793 $this->assertEmpty( $items );
796 public function getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider() {
799 [ 'rcTypes' => [ 1337 ] ],
800 'Bad value for parameter $options[\'rcTypes\']',
803 [ 'rcTypes' => [ 'edit' ] ],
804 'Bad value for parameter $options[\'rcTypes\']',
807 [ 'rcTypes' => [ RC_EDIT
, 1337 ] ],
808 'Bad value for parameter $options[\'rcTypes\']',
812 'Bad value for parameter $options[\'dir\']',
815 [ 'start' => '20151212010101' ],
816 'Bad value for parameter $options[\'dir\']: must be provided',
819 [ 'end' => '20151212010101' ],
820 'Bad value for parameter $options[\'dir\']: must be provided',
823 [ 'startFrom' => [ '20151212010101', 123 ] ],
824 'Bad value for parameter $options[\'dir\']: must be provided',
827 [ 'dir' => WatchedItemQueryService
::DIR_OLDER
, 'startFrom' => '20151212010101' ],
828 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
831 [ 'dir' => WatchedItemQueryService
::DIR_OLDER
, 'startFrom' => [ '20151212010101' ] ],
832 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
836 'dir' => WatchedItemQueryService
::DIR_OLDER
,
837 'startFrom' => [ '20151212010101', 123, 'foo' ]
839 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
842 [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
843 'Bad value for parameter $options[\'watchlistOwnerToken\']',
846 [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
847 'Bad value for parameter $options[\'watchlistOwner\']',
853 * @dataProvider getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
855 public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions(
857 $expectedInExceptionMessage
859 $mockDb = $this->getMockDb();
860 $mockDb->expects( $this->never() )
861 ->method( $this->anything() );
863 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
864 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
866 $this->setExpectedException( InvalidArgumentException
::class, $expectedInExceptionMessage );
867 $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
870 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() {
871 $mockDb = $this->getMockDb();
872 $mockDb->expects( $this->once() )
875 [ 'recentchanges', 'watchlist', 'page' ],
883 'wl_notificationtimestamp',
886 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
887 $this->isType( 'string' ),
893 'wl_namespace=rc_namespace',
903 ->will( $this->returnValue( [] ) );
905 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
906 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
908 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
910 [ 'usedInGenerator' => true ]
913 $this->assertEmpty( $items );
916 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions() {
917 $mockDb = $this->getMockDb();
918 $mockDb->expects( $this->once() )
921 [ 'recentchanges', 'watchlist' ],
929 'wl_notificationtimestamp',
933 $this->isType( 'string' ),
939 'wl_namespace=rc_namespace',
945 ->will( $this->returnValue( [] ) );
947 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
948 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
950 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
952 [ 'usedInGenerator' => true, 'allRevisions' => true, ]
955 $this->assertEmpty( $items );
958 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult() {
959 $mockDb = $this->getMockDb();
960 $mockDb->expects( $this->once() )
963 $this->isType( 'array' ),
964 $this->isType( 'array' ),
967 '(rc_this_oldid=page_latest) OR (rc_type=3)',
969 $this->isType( 'string' ),
970 $this->isType( 'array' ),
971 $this->isType( 'array' )
973 ->will( $this->returnValue( [] ) );
975 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
976 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
977 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
978 $otherUser->expects( $this->once() )
979 ->method( 'getOption' )
980 ->with( 'watchlisttoken' )
981 ->willReturn( '0123456789abcdef' );
983 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
985 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
988 $this->assertEmpty( $items );
991 public function invalidWatchlistTokenProvider() {
999 * @dataProvider invalidWatchlistTokenProvider
1001 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken( $token ) {
1002 $mockDb = $this->getMockDb();
1003 $mockDb->expects( $this->never() )
1004 ->method( $this->anything() );
1006 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1007 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1008 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1009 $otherUser->expects( $this->once() )
1010 ->method( 'getOption' )
1011 ->with( 'watchlisttoken' )
1012 ->willReturn( '0123456789abcdef' );
1014 $this->setExpectedException( UsageException
::class, 'Incorrect watchlist token provided' );
1015 $queryService->getWatchedItemsWithRecentChangeInfo(
1017 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
1021 public function testGetWatchedItemsForUser() {
1022 $mockDb = $this->getMockDb();
1023 $mockDb->expects( $this->once() )
1024 ->method( 'select' )
1027 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1030 ->will( $this->returnValue( [
1031 $this->getFakeRow( [
1032 'wl_namespace' => 0,
1033 'wl_title' => 'Foo1',
1034 'wl_notificationtimestamp' => '20151212010101',
1036 $this->getFakeRow( [
1037 'wl_namespace' => 1,
1038 'wl_title' => 'Foo2',
1039 'wl_notificationtimestamp' => null,
1043 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1044 $user = $this->getMockNonAnonUserWithId( 1 );
1046 $items = $queryService->getWatchedItemsForUser( $user );
1048 $this->assertInternalType( 'array', $items );
1049 $this->assertCount( 2, $items );
1050 $this->assertContainsOnlyInstancesOf( WatchedItem
::class, $items );
1051 $this->assertEquals(
1052 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1055 $this->assertEquals(
1056 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1061 public function provideGetWatchedItemsForUserOptions() {
1064 [ 'namespaceIds' => [ 0, 1 ], ],
1065 [ 'wl_namespace' => [ 0, 1 ], ],
1069 [ 'sort' => WatchedItemQueryService
::SORT_ASC
, ],
1071 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1075 'namespaceIds' => [ 0 ],
1076 'sort' => WatchedItemQueryService
::SORT_ASC
,
1078 [ 'wl_namespace' => [ 0 ], ],
1079 [ 'ORDER BY' => 'wl_title ASC' ]
1088 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ],
1089 'limit' => "10; DROP TABLE watchlist;\n--",
1091 [ 'wl_namespace' => [ 0, 1 ], ],
1095 [ 'filter' => WatchedItemQueryService
::FILTER_CHANGED
],
1096 [ 'wl_notificationtimestamp IS NOT NULL' ],
1100 [ 'filter' => WatchedItemQueryService
::FILTER_NOT_CHANGED
],
1101 [ 'wl_notificationtimestamp IS NULL' ],
1105 [ 'sort' => WatchedItemQueryService
::SORT_DESC
, ],
1107 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1111 'namespaceIds' => [ 0 ],
1112 'sort' => WatchedItemQueryService
::SORT_DESC
,
1114 [ 'wl_namespace' => [ 0 ], ],
1115 [ 'ORDER BY' => 'wl_title DESC' ]
1121 * @dataProvider provideGetWatchedItemsForUserOptions
1123 public function testGetWatchedItemsForUser_optionsAndEmptyResult(
1125 array $expectedConds,
1126 array $expectedDbOptions
1128 $mockDb = $this->getMockDb();
1129 $user = $this->getMockNonAnonUserWithId( 1 );
1131 $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1132 $mockDb->expects( $this->once() )
1133 ->method( 'select' )
1136 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1138 $this->isType( 'string' ),
1141 ->will( $this->returnValue( [] ) );
1143 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1145 $items = $queryService->getWatchedItemsForUser( $user, $options );
1146 $this->assertEmpty( $items );
1149 public function provideGetWatchedItemsForUser_fromUntilStartFromOptions() {
1153 'from' => new TitleValue( 0, 'SomeDbKey' ),
1154 'sort' => WatchedItemQueryService
::SORT_ASC
1156 [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1157 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1161 'from' => new TitleValue( 0, 'SomeDbKey' ),
1162 'sort' => WatchedItemQueryService
::SORT_DESC
,
1164 [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1165 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1169 'until' => new TitleValue( 0, 'SomeDbKey' ),
1170 'sort' => WatchedItemQueryService
::SORT_ASC
1172 [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1173 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1177 'until' => new TitleValue( 0, 'SomeDbKey' ),
1178 'sort' => WatchedItemQueryService
::SORT_DESC
1180 [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1181 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1185 'from' => new TitleValue( 0, 'AnotherDbKey' ),
1186 'until' => new TitleValue( 0, 'SomeOtherDbKey' ),
1187 'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1188 'sort' => WatchedItemQueryService
::SORT_ASC
1191 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1192 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1193 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))",
1195 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1199 'from' => new TitleValue( 0, 'SomeOtherDbKey' ),
1200 'until' => new TitleValue( 0, 'AnotherDbKey' ),
1201 'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1202 'sort' => WatchedItemQueryService
::SORT_DESC
1205 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1206 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1207 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))",
1209 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1215 * @dataProvider provideGetWatchedItemsForUser_fromUntilStartFromOptions
1217 public function testGetWatchedItemsForUser_fromUntilStartFromOptions(
1219 array $expectedConds,
1220 array $expectedDbOptions
1222 $user = $this->getMockNonAnonUserWithId( 1 );
1224 $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1226 $mockDb = $this->getMockDb();
1227 $mockDb->expects( $this->any() )
1228 ->method( 'addQuotes' )
1229 ->will( $this->returnCallback( function( $value ) {
1232 $mockDb->expects( $this->any() )
1233 ->method( 'makeList' )
1235 $this->isType( 'array' ),
1236 $this->isType( 'int' )
1238 ->will( $this->returnCallback( function( $a, $conj ) {
1239 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
1240 return join( $sqlConj, array_map( function( $s ) {
1241 return '(' . $s . ')';
1245 $mockDb->expects( $this->once() )
1246 ->method( 'select' )
1249 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1251 $this->isType( 'string' ),
1254 ->will( $this->returnValue( [] ) );
1256 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1258 $items = $queryService->getWatchedItemsForUser( $user, $options );
1259 $this->assertEmpty( $items );
1262 public function getWatchedItemsForUserInvalidOptionsProvider() {
1265 [ 'sort' => 'foo' ],
1266 'Bad value for parameter $options[\'sort\']'
1269 [ 'filter' => 'foo' ],
1270 'Bad value for parameter $options[\'filter\']'
1273 [ 'from' => new TitleValue( 0, 'SomeDbKey' ), ],
1274 'Bad value for parameter $options[\'sort\']: must be provided'
1277 [ 'until' => new TitleValue( 0, 'SomeDbKey' ), ],
1278 'Bad value for parameter $options[\'sort\']: must be provided'
1281 [ 'startFrom' => new TitleValue( 0, 'SomeDbKey' ), ],
1282 'Bad value for parameter $options[\'sort\']: must be provided'
1288 * @dataProvider getWatchedItemsForUserInvalidOptionsProvider
1290 public function testGetWatchedItemsForUser_invalidOptionThrowsException(
1292 $expectedInExceptionMessage
1294 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $this->getMockDb() ) );
1296 $this->setExpectedException( InvalidArgumentException
::class, $expectedInExceptionMessage );
1297 $queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
1300 public function testGetWatchedItemsForUser_userNotAllowedToViewWatchlist() {
1301 $mockDb = $this->getMockDb();
1303 $mockDb->expects( $this->never() )
1304 ->method( $this->anything() );
1306 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1308 $items = $queryService->getWatchedItemsForUser( $this->getMockAnonUser() );
1309 $this->assertEmpty( $items );