Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / tests / phpunit / includes / WatchedItemQueryServiceUnitTest.php
blobbdec0a50d90ffdf0f1ce8723696e3029498cc584
1 <?php
3 /**
4 * @covers WatchedItemQueryService
5 */
6 class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
8 /**
9 * @return PHPUnit_Framework_MockObject_MockObject|Database
11 private function getMockDb() {
12 $mock = $this->getMockBuilder( Database::class )
13 ->disableOriginalConstructor()
14 ->getMock();
16 $mock->expects( $this->any() )
17 ->method( 'makeList' )
18 ->with(
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 . ')';
26 }, $a
27 ) );
28 } ) );
30 $mock->expects( $this->any() )
31 ->method( 'addQuotes' )
32 ->will( $this->returnCallback( function( $value ) {
33 return "'$value'";
34 } ) );
36 $mock->expects( $this->any() )
37 ->method( 'timestamp' )
38 ->will( $this->returnArgument( 0 ) );
40 $mock->expects( $this->any() )
41 ->method( 'bitAnd' )
42 ->willReturnCallback( function( $a, $b ) {
43 return "($a & $b)";
44 } );
46 return $mock;
49 /**
50 * @param $mockDb
51 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
53 private function getMockLoadBalancer( $mockDb ) {
54 $mock = $this->getMockBuilder( LoadBalancer::class )
55 ->disableOriginalConstructor()
56 ->getMock();
57 $mock->expects( $this->any() )
58 ->method( 'getConnectionRef' )
59 ->with( DB_SLAVE )
60 ->will( $this->returnValue( $mockDb ) );
61 return $mock;
64 /**
65 * @param int $id
66 * @return PHPUnit_Framework_MockObject_MockObject|User
68 private function getMockNonAnonUserWithId( $id ) {
69 $mock = $this->getMock( User::class );
70 $mock->expects( $this->any() )
71 ->method( 'isAnon' )
72 ->will( $this->returnValue( false ) );
73 $mock->expects( $this->any() )
74 ->method( 'getId' )
75 ->will( $this->returnValue( $id ) );
76 return $mock;
79 /**
80 * @param int $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 ) );
94 return $mock;
97 /**
98 * @param int $id
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;
109 } ) );
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 );
115 } ) );
117 return $mock;
121 * @param int $id
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 ) );
141 return $mock;
144 private function getMockAnonUser() {
145 $mock = $this->getMock( User::class );
146 $mock->expects( $this->any() )
147 ->method( 'isAnon' )
148 ->will( $this->returnValue( true ) );
149 return $mock;
152 private function getFakeRow( array $rowValues ) {
153 $fakeRow = new stdClass();
154 foreach ( $rowValues as $valueName => $value ) {
155 $fakeRow->$valueName = $value;
157 return $fakeRow;
160 public function testGetWatchedItemsWithRecentChangeInfo() {
161 $mockDb = $this->getMockDb();
162 $mockDb->expects( $this->once() )
163 ->method( 'select' )
164 ->with(
165 [ 'recentchanges', 'watchlist', 'page' ],
167 'rc_id',
168 'rc_namespace',
169 'rc_title',
170 'rc_timestamp',
171 'rc_type',
172 'rc_deleted',
173 'wl_notificationtimestamp',
174 'rc_cur_id',
175 'rc_this_oldid',
176 'rc_last_oldid',
179 'wl_user' => 1,
180 '(rc_this_oldid=page_latest) OR (rc_type=3)',
182 $this->isType( 'string' ),
184 'LIMIT' => 3,
187 'watchlist' => [
188 'INNER JOIN',
190 'wl_namespace=rc_namespace',
191 'wl_title=rc_title'
194 'page' => [
195 'LEFT JOIN',
196 'rc_cur_id=page_id',
200 ->will( $this->returnValue( [
201 $this->getFakeRow( [
202 'rc_id' => 1,
203 'rc_namespace' => 0,
204 'rc_title' => 'Foo1',
205 'rc_timestamp' => '20151212010101',
206 'rc_type' => RC_NEW,
207 'rc_deleted' => 0,
208 'wl_notificationtimestamp' => '20151212010101',
209 ] ),
210 $this->getFakeRow( [
211 'rc_id' => 2,
212 'rc_namespace' => 1,
213 'rc_title' => 'Foo2',
214 'rc_timestamp' => '20151212010102',
215 'rc_type' => RC_NEW,
216 'rc_deleted' => 0,
217 'wl_notificationtimestamp' => null,
218 ] ),
219 $this->getFakeRow( [
220 'rc_id' => 3,
221 'rc_namespace' => 1,
222 'rc_title' => 'Foo3',
223 'rc_timestamp' => '20151212010103',
224 'rc_type' => RC_NEW,
225 'rc_deleted' => 0,
226 'wl_notificationtimestamp' => null,
227 ] ),
228 ] ) );
230 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
231 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
233 $startFrom = null;
234 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
235 $user, [ 'limit' => 2 ], $startFrom
238 $this->assertInternalType( 'array', $items );
239 $this->assertCount( 2, $items );
241 foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
242 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
243 $this->assertInternalType( 'array', $recentChangeInfo );
246 $this->assertEquals(
247 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
248 $items[0][0]
250 $this->assertEquals(
252 'rc_id' => 1,
253 'rc_namespace' => 0,
254 'rc_title' => 'Foo1',
255 'rc_timestamp' => '20151212010101',
256 'rc_type' => RC_NEW,
257 'rc_deleted' => 0,
259 $items[0][1]
262 $this->assertEquals(
263 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
264 $items[1][0]
266 $this->assertEquals(
268 'rc_id' => 2,
269 'rc_namespace' => 1,
270 'rc_title' => 'Foo2',
271 'rc_timestamp' => '20151212010102',
272 'rc_type' => RC_NEW,
273 'rc_deleted' => 0,
275 $items[1][1]
278 $this->assertEquals( [ '20151212010103', 3 ], $startFrom );
281 public function testGetWatchedItemsWithRecentChangeInfo_extension() {
282 $mockDb = $this->getMockDb();
283 $mockDb->expects( $this->once() )
284 ->method( 'select' )
285 ->with(
286 [ 'recentchanges', 'watchlist', 'page', 'extension_dummy_table' ],
288 'rc_id',
289 'rc_namespace',
290 'rc_title',
291 'rc_timestamp',
292 'rc_type',
293 'rc_deleted',
294 'wl_notificationtimestamp',
295 'rc_cur_id',
296 'rc_this_oldid',
297 'rc_last_oldid',
298 'extension_dummy_field',
301 'wl_user' => 1,
302 '(rc_this_oldid=page_latest) OR (rc_type=3)',
303 'extension_dummy_cond',
305 $this->isType( 'string' ),
307 'extension_dummy_option',
310 'watchlist' => [
311 'INNER JOIN',
313 'wl_namespace=rc_namespace',
314 'wl_title=rc_title'
317 'page' => [
318 'LEFT JOIN',
319 'rc_cur_id=page_id',
321 'extension_dummy_join_cond' => [],
324 ->will( $this->returnValue( [
325 $this->getFakeRow( [
326 'rc_id' => 1,
327 'rc_namespace' => 0,
328 'rc_title' => 'Foo1',
329 'rc_timestamp' => '20151212010101',
330 'rc_type' => RC_NEW,
331 'rc_deleted' => 0,
332 'wl_notificationtimestamp' => '20151212010101',
333 ] ),
334 $this->getFakeRow( [
335 'rc_id' => 2,
336 'rc_namespace' => 1,
337 'rc_title' => 'Foo2',
338 'rc_timestamp' => '20151212010102',
339 'rc_type' => RC_NEW,
340 'rc_deleted' => 0,
341 'wl_notificationtimestamp' => null,
342 ] ),
343 ] ) );
345 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
347 $mockExtension = $this->getMockBuilder( WatchedItemQueryServiceExtension::class )
348 ->getMock();
349 $mockExtension->expects( $this->once() )
350 ->method( 'modifyWatchedItemsWithRCInfoQuery' )
351 ->with(
352 $this->identicalTo( $user ),
353 $this->isType( 'array' ),
354 $this->isInstanceOf( IDatabase::class ),
355 $this->isType( 'array' ),
356 $this->isType( 'array' ),
357 $this->isType( 'array' ),
358 $this->isType( 'array' ),
359 $this->isType( 'array' )
361 ->will( $this->returnCallback( function (
362 $user, $options, $db, &$tables, &$fields, &$conds, &$dbOptions, &$joinConds
364 $tables[] = 'extension_dummy_table';
365 $fields[] = 'extension_dummy_field';
366 $conds[] = 'extension_dummy_cond';
367 $dbOptions[] = 'extension_dummy_option';
368 $joinConds['extension_dummy_join_cond'] = [];
369 } ) );
370 $mockExtension->expects( $this->once() )
371 ->method( 'modifyWatchedItemsWithRCInfo' )
372 ->with(
373 $this->identicalTo( $user ),
374 $this->isType( 'array' ),
375 $this->isInstanceOf( IDatabase::class ),
376 $this->isType( 'array' ),
377 $this->anything(),
378 $this->anything() // Can't test for null here, PHPUnit applies this after the callback
380 ->will( $this->returnCallback( function ( $user, $options, $db, &$items, $res, &$startFrom ) {
381 foreach ( $items as $i => &$item ) {
382 $item[1]['extension_dummy_field'] = $i;
384 unset( $item );
386 $this->assertNull( $startFrom );
387 $startFrom = [ '20160203123456', 42 ];
388 } ) );
390 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
391 TestingAccessWrapper::newFromObject( $queryService )->extensions = [ $mockExtension ];
393 $startFrom = null;
394 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
395 $user, [], $startFrom
398 $this->assertInternalType( 'array', $items );
399 $this->assertCount( 2, $items );
401 foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
402 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
403 $this->assertInternalType( 'array', $recentChangeInfo );
406 $this->assertEquals(
407 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
408 $items[0][0]
410 $this->assertEquals(
412 'rc_id' => 1,
413 'rc_namespace' => 0,
414 'rc_title' => 'Foo1',
415 'rc_timestamp' => '20151212010101',
416 'rc_type' => RC_NEW,
417 'rc_deleted' => 0,
418 'extension_dummy_field' => 0,
420 $items[0][1]
423 $this->assertEquals(
424 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
425 $items[1][0]
427 $this->assertEquals(
429 'rc_id' => 2,
430 'rc_namespace' => 1,
431 'rc_title' => 'Foo2',
432 'rc_timestamp' => '20151212010102',
433 'rc_type' => RC_NEW,
434 'rc_deleted' => 0,
435 'extension_dummy_field' => 1,
437 $items[1][1]
440 $this->assertEquals( [ '20160203123456', 42 ], $startFrom );
443 public function getWatchedItemsWithRecentChangeInfoOptionsProvider() {
444 return [
446 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
447 null,
448 [ 'rc_type', 'rc_minor', 'rc_bot' ],
453 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
454 null,
455 [ 'rc_user_text' ],
460 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
461 null,
462 [ 'rc_user' ],
467 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
468 null,
469 [ 'rc_comment' ],
474 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
475 null,
476 [ 'rc_patrolled', 'rc_log_type' ],
481 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
482 null,
483 [ 'rc_old_len', 'rc_new_len' ],
488 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
489 null,
490 [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
495 [ 'namespaceIds' => [ 0, 1 ] ],
496 null,
498 [ 'wl_namespace' => [ 0, 1 ] ],
502 [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
503 null,
505 [ 'wl_namespace' => [ 0, 1 ] ],
509 [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
510 null,
512 [ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
516 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
517 null,
520 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
523 [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
524 null,
527 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
530 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
531 null,
533 [ "rc_timestamp <= '20151212010101'" ],
534 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
537 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
538 null,
540 [ "rc_timestamp >= '20151212010101'" ],
541 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
545 'dir' => WatchedItemQueryService::DIR_OLDER,
546 'start' => '20151212020101',
547 'end' => '20151212010101'
549 null,
551 [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
552 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
555 [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
556 null,
558 [ "rc_timestamp >= '20151212010101'" ],
559 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
562 [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
563 null,
565 [ "rc_timestamp <= '20151212010101'" ],
566 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
570 'dir' => WatchedItemQueryService::DIR_NEWER,
571 'start' => '20151212010101',
572 'end' => '20151212020101'
574 null,
576 [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
577 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
580 [ 'limit' => 10 ],
581 null,
584 [ 'LIMIT' => 11 ],
587 [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
588 null,
591 [ 'LIMIT' => 11 ],
594 [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
595 null,
597 [ 'rc_minor != 0' ],
601 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
602 null,
604 [ 'rc_minor = 0' ],
608 [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
609 null,
611 [ 'rc_bot != 0' ],
615 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
616 null,
618 [ 'rc_bot = 0' ],
622 [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
623 null,
625 [ 'rc_user = 0' ],
629 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
630 null,
632 [ 'rc_user != 0' ],
636 [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
637 null,
639 [ 'rc_patrolled != 0' ],
643 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ],
644 null,
646 [ 'rc_patrolled = 0' ],
650 [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
651 null,
653 [ 'rc_timestamp >= wl_notificationtimestamp' ],
657 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
658 null,
660 [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
664 [ 'onlyByUser' => 'SomeOtherUser' ],
665 null,
667 [ 'rc_user_text' => 'SomeOtherUser' ],
671 [ 'notByUser' => 'SomeOtherUser' ],
672 null,
674 [ "rc_user_text != 'SomeOtherUser'" ],
678 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
679 [ '20151212010101', 123 ],
682 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
684 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
687 [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
688 [ '20151212010101', 123 ],
691 "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
693 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
696 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
697 [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
700 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
702 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
708 * @dataProvider getWatchedItemsWithRecentChangeInfoOptionsProvider
710 public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
711 array $options,
712 $startFrom,
713 array $expectedExtraFields,
714 array $expectedExtraConds,
715 array $expectedDbOptions
717 $expectedFields = array_merge(
719 'rc_id',
720 'rc_namespace',
721 'rc_title',
722 'rc_timestamp',
723 'rc_type',
724 'rc_deleted',
725 'wl_notificationtimestamp',
727 'rc_cur_id',
728 'rc_this_oldid',
729 'rc_last_oldid',
731 $expectedExtraFields
733 $expectedConds = array_merge(
734 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
735 $expectedExtraConds
738 $mockDb = $this->getMockDb();
739 $mockDb->expects( $this->once() )
740 ->method( 'select' )
741 ->with(
742 [ 'recentchanges', 'watchlist', 'page' ],
743 $expectedFields,
744 $expectedConds,
745 $this->isType( 'string' ),
746 $expectedDbOptions,
748 'watchlist' => [
749 'INNER JOIN',
751 'wl_namespace=rc_namespace',
752 'wl_title=rc_title'
755 'page' => [
756 'LEFT JOIN',
757 'rc_cur_id=page_id',
761 ->will( $this->returnValue( [] ) );
763 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
764 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
766 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
768 $this->assertEmpty( $items );
769 $this->assertNull( $startFrom );
772 public function filterPatrolledOptionProvider() {
773 return [
774 [ WatchedItemQueryService::FILTER_PATROLLED ],
775 [ WatchedItemQueryService::FILTER_NOT_PATROLLED ],
780 * @dataProvider filterPatrolledOptionProvider
782 public function testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights(
783 $filtersOption
785 $mockDb = $this->getMockDb();
786 $mockDb->expects( $this->once() )
787 ->method( 'select' )
788 ->with(
789 [ 'recentchanges', 'watchlist', 'page' ],
790 $this->isType( 'array' ),
791 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
792 $this->isType( 'string' ),
793 $this->isType( 'array' ),
794 $this->isType( 'array' )
796 ->will( $this->returnValue( [] ) );
798 $user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
800 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
801 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
802 $user,
803 [ 'filters' => [ $filtersOption ] ]
806 $this->assertEmpty( $items );
809 public function mysqlIndexOptimizationProvider() {
810 return [
812 'mysql',
814 [ "rc_timestamp > ''" ],
817 'mysql',
818 [ 'start' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
819 [ "rc_timestamp <= '20151212010101'" ],
822 'mysql',
823 [ 'end' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
824 [ "rc_timestamp >= '20151212010101'" ],
827 'postgres',
835 * @dataProvider mysqlIndexOptimizationProvider
837 public function testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization(
838 $dbType,
839 array $options,
840 array $expectedExtraConds
842 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
843 $conds = array_merge( $commonConds, $expectedExtraConds );
845 $mockDb = $this->getMockDb();
846 $mockDb->expects( $this->once() )
847 ->method( 'select' )
848 ->with(
849 [ 'recentchanges', 'watchlist', 'page' ],
850 $this->isType( 'array' ),
851 $conds,
852 $this->isType( 'string' ),
853 $this->isType( 'array' ),
854 $this->isType( 'array' )
856 ->will( $this->returnValue( [] ) );
857 $mockDb->expects( $this->any() )
858 ->method( 'getType' )
859 ->will( $this->returnValue( $dbType ) );
861 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
862 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
864 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
866 $this->assertEmpty( $items );
869 public function userPermissionRelatedExtraChecksProvider() {
870 return [
873 'deletedhistory',
875 '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
876 LogPage::DELETED_ACTION . ')'
881 'suppressrevision',
883 '(rc_type != ' . RC_LOG . ') OR (' .
884 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
885 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
890 'viewsuppressed',
892 '(rc_type != ' . RC_LOG . ') OR (' .
893 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
894 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
898 [ 'onlyByUser' => 'SomeOtherUser' ],
899 'deletedhistory',
901 'rc_user_text' => 'SomeOtherUser',
902 '(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
903 '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
904 LogPage::DELETED_ACTION . ')'
908 [ 'onlyByUser' => 'SomeOtherUser' ],
909 'suppressrevision',
911 'rc_user_text' => 'SomeOtherUser',
912 '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
913 ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
914 '(rc_type != ' . RC_LOG . ') OR (' .
915 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
916 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
920 [ 'onlyByUser' => 'SomeOtherUser' ],
921 'viewsuppressed',
923 'rc_user_text' => 'SomeOtherUser',
924 '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
925 ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
926 '(rc_type != ' . RC_LOG . ') OR (' .
927 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
928 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
935 * @dataProvider userPermissionRelatedExtraChecksProvider
937 public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
938 array $options,
939 $notAllowedAction,
940 array $expectedExtraConds
942 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
943 $conds = array_merge( $commonConds, $expectedExtraConds );
945 $mockDb = $this->getMockDb();
946 $mockDb->expects( $this->once() )
947 ->method( 'select' )
948 ->with(
949 [ 'recentchanges', 'watchlist', 'page' ],
950 $this->isType( 'array' ),
951 $conds,
952 $this->isType( 'string' ),
953 $this->isType( 'array' ),
954 $this->isType( 'array' )
956 ->will( $this->returnValue( [] ) );
958 $user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
960 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
961 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
963 $this->assertEmpty( $items );
966 public function testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult() {
967 $mockDb = $this->getMockDb();
968 $mockDb->expects( $this->once() )
969 ->method( 'select' )
970 ->with(
971 [ 'recentchanges', 'watchlist' ],
973 'rc_id',
974 'rc_namespace',
975 'rc_title',
976 'rc_timestamp',
977 'rc_type',
978 'rc_deleted',
979 'wl_notificationtimestamp',
981 'rc_cur_id',
982 'rc_this_oldid',
983 'rc_last_oldid',
985 [ 'wl_user' => 1, ],
986 $this->isType( 'string' ),
989 'watchlist' => [
990 'INNER JOIN',
992 'wl_namespace=rc_namespace',
993 'wl_title=rc_title'
998 ->will( $this->returnValue( [] ) );
1000 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1001 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1003 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
1005 $this->assertEmpty( $items );
1008 public function getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider() {
1009 return [
1011 [ 'rcTypes' => [ 1337 ] ],
1012 null,
1013 'Bad value for parameter $options[\'rcTypes\']',
1016 [ 'rcTypes' => [ 'edit' ] ],
1017 null,
1018 'Bad value for parameter $options[\'rcTypes\']',
1021 [ 'rcTypes' => [ RC_EDIT, 1337 ] ],
1022 null,
1023 'Bad value for parameter $options[\'rcTypes\']',
1026 [ 'dir' => 'foo' ],
1027 null,
1028 'Bad value for parameter $options[\'dir\']',
1031 [ 'start' => '20151212010101' ],
1032 null,
1033 'Bad value for parameter $options[\'dir\']: must be provided',
1036 [ 'end' => '20151212010101' ],
1037 null,
1038 'Bad value for parameter $options[\'dir\']: must be provided',
1042 [ '20151212010101', 123 ],
1043 'Bad value for parameter $options[\'dir\']: must be provided',
1046 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
1047 '20151212010101',
1048 'Bad value for parameter $startFrom: must be a two-element array',
1051 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
1052 [ '20151212010101' ],
1053 'Bad value for parameter $startFrom: must be a two-element array',
1056 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
1057 [ '20151212010101', 123, 'foo' ],
1058 'Bad value for parameter $startFrom: must be a two-element array',
1061 [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
1062 null,
1063 'Bad value for parameter $options[\'watchlistOwnerToken\']',
1066 [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
1067 null,
1068 'Bad value for parameter $options[\'watchlistOwner\']',
1074 * @dataProvider getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
1076 public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions(
1077 array $options,
1078 $startFrom,
1079 $expectedInExceptionMessage
1081 $mockDb = $this->getMockDb();
1082 $mockDb->expects( $this->never() )
1083 ->method( $this->anything() );
1085 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1086 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1088 $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
1089 $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options, $startFrom );
1092 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() {
1093 $mockDb = $this->getMockDb();
1094 $mockDb->expects( $this->once() )
1095 ->method( 'select' )
1096 ->with(
1097 [ 'recentchanges', 'watchlist', 'page' ],
1099 'rc_id',
1100 'rc_namespace',
1101 'rc_title',
1102 'rc_timestamp',
1103 'rc_type',
1104 'rc_deleted',
1105 'wl_notificationtimestamp',
1106 'rc_cur_id',
1108 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
1109 $this->isType( 'string' ),
1112 'watchlist' => [
1113 'INNER JOIN',
1115 'wl_namespace=rc_namespace',
1116 'wl_title=rc_title'
1119 'page' => [
1120 'LEFT JOIN',
1121 'rc_cur_id=page_id',
1125 ->will( $this->returnValue( [] ) );
1127 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1128 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1130 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1131 $user,
1132 [ 'usedInGenerator' => true ]
1135 $this->assertEmpty( $items );
1138 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions() {
1139 $mockDb = $this->getMockDb();
1140 $mockDb->expects( $this->once() )
1141 ->method( 'select' )
1142 ->with(
1143 [ 'recentchanges', 'watchlist' ],
1145 'rc_id',
1146 'rc_namespace',
1147 'rc_title',
1148 'rc_timestamp',
1149 'rc_type',
1150 'rc_deleted',
1151 'wl_notificationtimestamp',
1152 'rc_this_oldid',
1154 [ 'wl_user' => 1 ],
1155 $this->isType( 'string' ),
1158 'watchlist' => [
1159 'INNER JOIN',
1161 'wl_namespace=rc_namespace',
1162 'wl_title=rc_title'
1167 ->will( $this->returnValue( [] ) );
1169 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1170 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1172 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1173 $user,
1174 [ 'usedInGenerator' => true, 'allRevisions' => true, ]
1177 $this->assertEmpty( $items );
1180 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult() {
1181 $mockDb = $this->getMockDb();
1182 $mockDb->expects( $this->once() )
1183 ->method( 'select' )
1184 ->with(
1185 $this->isType( 'array' ),
1186 $this->isType( 'array' ),
1188 'wl_user' => 2,
1189 '(rc_this_oldid=page_latest) OR (rc_type=3)',
1191 $this->isType( 'string' ),
1192 $this->isType( 'array' ),
1193 $this->isType( 'array' )
1195 ->will( $this->returnValue( [] ) );
1197 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1198 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1199 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1200 $otherUser->expects( $this->once() )
1201 ->method( 'getOption' )
1202 ->with( 'watchlisttoken' )
1203 ->willReturn( '0123456789abcdef' );
1205 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
1206 $user,
1207 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
1210 $this->assertEmpty( $items );
1213 public function invalidWatchlistTokenProvider() {
1214 return [
1215 [ 'wrongToken' ],
1216 [ '' ],
1221 * @dataProvider invalidWatchlistTokenProvider
1223 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken( $token ) {
1224 $mockDb = $this->getMockDb();
1225 $mockDb->expects( $this->never() )
1226 ->method( $this->anything() );
1228 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1229 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1230 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1231 $otherUser->expects( $this->once() )
1232 ->method( 'getOption' )
1233 ->with( 'watchlisttoken' )
1234 ->willReturn( '0123456789abcdef' );
1236 $this->setExpectedException( ApiUsageException::class, 'Incorrect watchlist token provided' );
1237 $queryService->getWatchedItemsWithRecentChangeInfo(
1238 $user,
1239 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
1243 public function testGetWatchedItemsForUser() {
1244 $mockDb = $this->getMockDb();
1245 $mockDb->expects( $this->once() )
1246 ->method( 'select' )
1247 ->with(
1248 'watchlist',
1249 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1250 [ 'wl_user' => 1 ]
1252 ->will( $this->returnValue( [
1253 $this->getFakeRow( [
1254 'wl_namespace' => 0,
1255 'wl_title' => 'Foo1',
1256 'wl_notificationtimestamp' => '20151212010101',
1257 ] ),
1258 $this->getFakeRow( [
1259 'wl_namespace' => 1,
1260 'wl_title' => 'Foo2',
1261 'wl_notificationtimestamp' => null,
1262 ] ),
1263 ] ) );
1265 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1266 $user = $this->getMockNonAnonUserWithId( 1 );
1268 $items = $queryService->getWatchedItemsForUser( $user );
1270 $this->assertInternalType( 'array', $items );
1271 $this->assertCount( 2, $items );
1272 $this->assertContainsOnlyInstancesOf( WatchedItem::class, $items );
1273 $this->assertEquals(
1274 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1275 $items[0]
1277 $this->assertEquals(
1278 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1279 $items[1]
1283 public function provideGetWatchedItemsForUserOptions() {
1284 return [
1286 [ 'namespaceIds' => [ 0, 1 ], ],
1287 [ 'wl_namespace' => [ 0, 1 ], ],
1291 [ 'sort' => WatchedItemQueryService::SORT_ASC, ],
1293 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1297 'namespaceIds' => [ 0 ],
1298 'sort' => WatchedItemQueryService::SORT_ASC,
1300 [ 'wl_namespace' => [ 0 ], ],
1301 [ 'ORDER BY' => 'wl_title ASC' ]
1304 [ 'limit' => 10 ],
1306 [ 'LIMIT' => 10 ]
1310 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ],
1311 'limit' => "10; DROP TABLE watchlist;\n--",
1313 [ 'wl_namespace' => [ 0, 1 ], ],
1314 [ 'LIMIT' => 10 ]
1317 [ 'filter' => WatchedItemQueryService::FILTER_CHANGED ],
1318 [ 'wl_notificationtimestamp IS NOT NULL' ],
1322 [ 'filter' => WatchedItemQueryService::FILTER_NOT_CHANGED ],
1323 [ 'wl_notificationtimestamp IS NULL' ],
1327 [ 'sort' => WatchedItemQueryService::SORT_DESC, ],
1329 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1333 'namespaceIds' => [ 0 ],
1334 'sort' => WatchedItemQueryService::SORT_DESC,
1336 [ 'wl_namespace' => [ 0 ], ],
1337 [ 'ORDER BY' => 'wl_title DESC' ]
1343 * @dataProvider provideGetWatchedItemsForUserOptions
1345 public function testGetWatchedItemsForUser_optionsAndEmptyResult(
1346 array $options,
1347 array $expectedConds,
1348 array $expectedDbOptions
1350 $mockDb = $this->getMockDb();
1351 $user = $this->getMockNonAnonUserWithId( 1 );
1353 $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1354 $mockDb->expects( $this->once() )
1355 ->method( 'select' )
1356 ->with(
1357 'watchlist',
1358 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1359 $expectedConds,
1360 $this->isType( 'string' ),
1361 $expectedDbOptions
1363 ->will( $this->returnValue( [] ) );
1365 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1367 $items = $queryService->getWatchedItemsForUser( $user, $options );
1368 $this->assertEmpty( $items );
1371 public function provideGetWatchedItemsForUser_fromUntilStartFromOptions() {
1372 return [
1375 'from' => new TitleValue( 0, 'SomeDbKey' ),
1376 'sort' => WatchedItemQueryService::SORT_ASC
1378 [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1379 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1383 'from' => new TitleValue( 0, 'SomeDbKey' ),
1384 'sort' => WatchedItemQueryService::SORT_DESC,
1386 [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1387 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1391 'until' => new TitleValue( 0, 'SomeDbKey' ),
1392 'sort' => WatchedItemQueryService::SORT_ASC
1394 [ "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))", ],
1395 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1399 'until' => new TitleValue( 0, 'SomeDbKey' ),
1400 'sort' => WatchedItemQueryService::SORT_DESC
1402 [ "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))", ],
1403 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1407 'from' => new TitleValue( 0, 'AnotherDbKey' ),
1408 'until' => new TitleValue( 0, 'SomeOtherDbKey' ),
1409 'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1410 'sort' => WatchedItemQueryService::SORT_ASC
1413 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1414 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1415 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'SomeDbKey'))",
1417 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1421 'from' => new TitleValue( 0, 'SomeOtherDbKey' ),
1422 'until' => new TitleValue( 0, 'AnotherDbKey' ),
1423 'startFrom' => new TitleValue( 0, 'SomeDbKey' ),
1424 'sort' => WatchedItemQueryService::SORT_DESC
1427 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeOtherDbKey'))",
1428 "(wl_namespace > 0) OR ((wl_namespace = 0) AND (wl_title >= 'AnotherDbKey'))",
1429 "(wl_namespace < 0) OR ((wl_namespace = 0) AND (wl_title <= 'SomeDbKey'))",
1431 [ 'ORDER BY' => [ 'wl_namespace DESC', 'wl_title DESC' ] ]
1437 * @dataProvider provideGetWatchedItemsForUser_fromUntilStartFromOptions
1439 public function testGetWatchedItemsForUser_fromUntilStartFromOptions(
1440 array $options,
1441 array $expectedConds,
1442 array $expectedDbOptions
1444 $user = $this->getMockNonAnonUserWithId( 1 );
1446 $expectedConds = array_merge( [ 'wl_user' => 1 ], $expectedConds );
1448 $mockDb = $this->getMockDb();
1449 $mockDb->expects( $this->any() )
1450 ->method( 'addQuotes' )
1451 ->will( $this->returnCallback( function( $value ) {
1452 return "'$value'";
1453 } ) );
1454 $mockDb->expects( $this->any() )
1455 ->method( 'makeList' )
1456 ->with(
1457 $this->isType( 'array' ),
1458 $this->isType( 'int' )
1460 ->will( $this->returnCallback( function( $a, $conj ) {
1461 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
1462 return join( $sqlConj, array_map( function( $s ) {
1463 return '(' . $s . ')';
1464 }, $a
1465 ) );
1466 } ) );
1467 $mockDb->expects( $this->once() )
1468 ->method( 'select' )
1469 ->with(
1470 'watchlist',
1471 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1472 $expectedConds,
1473 $this->isType( 'string' ),
1474 $expectedDbOptions
1476 ->will( $this->returnValue( [] ) );
1478 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1480 $items = $queryService->getWatchedItemsForUser( $user, $options );
1481 $this->assertEmpty( $items );
1484 public function getWatchedItemsForUserInvalidOptionsProvider() {
1485 return [
1487 [ 'sort' => 'foo' ],
1488 'Bad value for parameter $options[\'sort\']'
1491 [ 'filter' => 'foo' ],
1492 'Bad value for parameter $options[\'filter\']'
1495 [ 'from' => new TitleValue( 0, 'SomeDbKey' ), ],
1496 'Bad value for parameter $options[\'sort\']: must be provided'
1499 [ 'until' => new TitleValue( 0, 'SomeDbKey' ), ],
1500 'Bad value for parameter $options[\'sort\']: must be provided'
1503 [ 'startFrom' => new TitleValue( 0, 'SomeDbKey' ), ],
1504 'Bad value for parameter $options[\'sort\']: must be provided'
1510 * @dataProvider getWatchedItemsForUserInvalidOptionsProvider
1512 public function testGetWatchedItemsForUser_invalidOptionThrowsException(
1513 array $options,
1514 $expectedInExceptionMessage
1516 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $this->getMockDb() ) );
1518 $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
1519 $queryService->getWatchedItemsForUser( $this->getMockNonAnonUserWithId( 1 ), $options );
1522 public function testGetWatchedItemsForUser_userNotAllowedToViewWatchlist() {
1523 $mockDb = $this->getMockDb();
1525 $mockDb->expects( $this->never() )
1526 ->method( $this->anything() );
1528 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
1530 $items = $queryService->getWatchedItemsForUser( $this->getMockAnonUser() );
1531 $this->assertEmpty( $items );