Merge "registration: Add schema validation ResourceLoaderWikiModule"
[lhc/web/wiklou.git] / tests / phpunit / includes / WatchedItemQueryServiceUnitTest.php
1 <?php
2
3 /**
4 * @covers WatchedItemQueryService
5 */
6 class WatchedItemQueryServiceUnitTest extends PHPUnit_Framework_TestCase {
7
8 /**
9 * @return PHPUnit_Framework_MockObject_MockObject|DatabaseBase
10 */
11 private function getMockDb() {
12 $mock = $this->getMockBuilder( DatabaseBase::class )
13 ->disableOriginalConstructor()
14 ->getMock();
15
16 $mock->expects( $this->any() )
17 ->method( 'makeList' )
18 ->with(
19 $this->isType( 'array' ),
20 $this->isType( 'int' )
21 )
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 } ) );
29
30 $mock->expects( $this->any() )
31 ->method( 'addQuotes' )
32 ->will( $this->returnCallback( function( $value ) {
33 return "'$value'";
34 } ) );
35
36 $mock->expects( $this->any() )
37 ->method( 'timestamp' )
38 ->will( $this->returnArgument( 0 ) );
39
40 $mock->expects( $this->any() )
41 ->method( 'bitAnd' )
42 ->willReturnCallback( function( $a, $b ) {
43 return "($a & $b)";
44 } );
45
46 return $mock;
47 }
48
49 /**
50 * @param $mockDb
51 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
52 */
53 private function getMockLoadBalancer( $mockDb ) {
54 $mock = $this->getMockBuilder( LoadBalancer::class )
55 ->disableOriginalConstructor()
56 ->getMock();
57 $mock->expects( $this->any() )
58 ->method( 'getConnection' )
59 ->with( DB_SLAVE )
60 ->will( $this->returnValue( $mockDb ) );
61 return $mock;
62 }
63
64 /**
65 * @param int $id
66 * @return PHPUnit_Framework_MockObject_MockObject|User
67 */
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;
77 }
78
79 /**
80 * @param int $id
81 * @return PHPUnit_Framework_MockObject_MockObject|User
82 */
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;
95 }
96
97 /**
98 * @param int $id
99 * @param string $notAllowedAction
100 * @return PHPUnit_Framework_MockObject_MockObject|User
101 */
102 private function getMockNonAnonUserWithIdAndRestrictedPermissions( $id, $notAllowedAction ) {
103 $mock = $this->getMockNonAnonUserWithId( $id );
104
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 } ) );
116
117 return $mock;
118 }
119
120 /**
121 * @param int $id
122 * @return PHPUnit_Framework_MockObject_MockObject|User
123 */
124 private function getMockNonAnonUserWithIdAndNoPatrolRights( $id ) {
125 $mock = $this->getMockNonAnonUserWithId( $id );
126
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 ) );
133
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 ) );
140
141 return $mock;
142 }
143
144 private function getFakeRow( array $rowValues ) {
145 $fakeRow = new stdClass();
146 foreach ( $rowValues as $valueName => $value ) {
147 $fakeRow->$valueName = $value;
148 }
149 return $fakeRow;
150 }
151
152 public function testGetWatchedItemsWithRecentChangeInfo() {
153 $mockDb = $this->getMockDb();
154 $mockDb->expects( $this->once() )
155 ->method( 'select' )
156 ->with(
157 [ 'recentchanges', 'watchlist', 'page' ],
158 [
159 'rc_id',
160 'rc_namespace',
161 'rc_title',
162 'rc_timestamp',
163 'rc_type',
164 'rc_deleted',
165 'wl_notificationtimestamp',
166 'rc_cur_id',
167 'rc_this_oldid',
168 'rc_last_oldid',
169 ],
170 [
171 'wl_user' => 1,
172 '(rc_this_oldid=page_latest) OR (rc_type=3)',
173 ],
174 $this->isType( 'string' ),
175 [],
176 [
177 'watchlist' => [
178 'INNER JOIN',
179 [
180 'wl_namespace=rc_namespace',
181 'wl_title=rc_title'
182 ]
183 ],
184 'page' => [
185 'LEFT JOIN',
186 'rc_cur_id=page_id',
187 ],
188 ]
189 )
190 ->will( $this->returnValue( [
191 $this->getFakeRow( [
192 'rc_id' => 1,
193 'rc_namespace' => 0,
194 'rc_title' => 'Foo1',
195 'rc_timestamp' => '20151212010101',
196 'rc_type' => RC_NEW,
197 'rc_deleted' => 0,
198 'wl_notificationtimestamp' => '20151212010101',
199 ] ),
200 $this->getFakeRow( [
201 'rc_id' => 2,
202 'rc_namespace' => 1,
203 'rc_title' => 'Foo2',
204 'rc_timestamp' => '20151212010102',
205 'rc_type' => RC_NEW,
206 'rc_deleted' => 0,
207 'wl_notificationtimestamp' => null,
208 ] ),
209 ] ) );
210
211 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
212 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
213
214 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user );
215
216 $this->assertInternalType( 'array', $items );
217 $this->assertCount( 2, $items );
218
219 foreach ( $items as list( $watchedItem, $recentChangeInfo ) ) {
220 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
221 $this->assertInternalType( 'array', $recentChangeInfo );
222 }
223
224 $this->assertEquals(
225 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
226 $items[0][0]
227 );
228 $this->assertEquals(
229 [
230 'rc_id' => 1,
231 'rc_namespace' => 0,
232 'rc_title' => 'Foo1',
233 'rc_timestamp' => '20151212010101',
234 'rc_type' => RC_NEW,
235 'rc_deleted' => 0,
236 ],
237 $items[0][1]
238 );
239
240 $this->assertEquals(
241 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
242 $items[1][0]
243 );
244 $this->assertEquals(
245 [
246 'rc_id' => 2,
247 'rc_namespace' => 1,
248 'rc_title' => 'Foo2',
249 'rc_timestamp' => '20151212010102',
250 'rc_type' => RC_NEW,
251 'rc_deleted' => 0,
252 ],
253 $items[1][1]
254 );
255 }
256
257 public function getWatchedItemsWithRecentChangeInfoOptionsProvider() {
258 return [
259 [
260 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_FLAGS ] ],
261 [ 'rc_type', 'rc_minor', 'rc_bot' ],
262 [],
263 [],
264 ],
265 [
266 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
267 [ 'rc_user_text' ],
268 [],
269 [],
270 ],
271 [
272 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
273 [ 'rc_user' ],
274 [],
275 [],
276 ],
277 [
278 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
279 [ 'rc_comment' ],
280 [],
281 [],
282 ],
283 [
284 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_PATROL_INFO ] ],
285 [ 'rc_patrolled', 'rc_log_type' ],
286 [],
287 [],
288 ],
289 [
290 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_SIZES ] ],
291 [ 'rc_old_len', 'rc_new_len' ],
292 [],
293 [],
294 ],
295 [
296 [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_LOG_INFO ] ],
297 [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
298 [],
299 [],
300 ],
301 [
302 [ 'namespaceIds' => [ 0, 1 ] ],
303 [],
304 [ 'wl_namespace' => [ 0, 1 ] ],
305 [],
306 ],
307 [
308 [ 'namespaceIds' => [ 0, "1; DROP TABLE watchlist;\n--" ] ],
309 [],
310 [ 'wl_namespace' => [ 0, 1 ] ],
311 [],
312 ],
313 [
314 [ 'rcTypes' => [ RC_EDIT, RC_NEW ] ],
315 [],
316 [ 'rc_type' => [ RC_EDIT, RC_NEW ] ],
317 [],
318 ],
319 [
320 [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
321 [],
322 [],
323 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
324 ],
325 [
326 [ 'dir' => WatchedItemQueryService::DIR_NEWER ],
327 [],
328 [],
329 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
330 ],
331 [
332 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'start' => '20151212010101' ],
333 [],
334 [ "rc_timestamp <= '20151212010101'" ],
335 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
336 ],
337 [
338 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'end' => '20151212010101' ],
339 [],
340 [ "rc_timestamp >= '20151212010101'" ],
341 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
342 ],
343 [
344 [
345 'dir' => WatchedItemQueryService::DIR_OLDER,
346 'start' => '20151212020101',
347 'end' => '20151212010101'
348 ],
349 [],
350 [ "rc_timestamp <= '20151212020101'", "rc_timestamp >= '20151212010101'" ],
351 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ]
352 ],
353 [
354 [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'start' => '20151212010101' ],
355 [],
356 [ "rc_timestamp >= '20151212010101'" ],
357 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
358 ],
359 [
360 [ 'dir' => WatchedItemQueryService::DIR_NEWER, 'end' => '20151212010101' ],
361 [],
362 [ "rc_timestamp <= '20151212010101'" ],
363 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
364 ],
365 [
366 [
367 'dir' => WatchedItemQueryService::DIR_NEWER,
368 'start' => '20151212010101',
369 'end' => '20151212020101'
370 ],
371 [],
372 [ "rc_timestamp >= '20151212010101'", "rc_timestamp <= '20151212020101'" ],
373 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ]
374 ],
375 [
376 [ 'limit' => 10 ],
377 [],
378 [],
379 [ 'LIMIT' => 10 ],
380 ],
381 [
382 [ 'limit' => "10; DROP TABLE watchlist;\n--" ],
383 [],
384 [],
385 [ 'LIMIT' => 10 ],
386 ],
387 [
388 [ 'filters' => [ WatchedItemQueryService::FILTER_MINOR ] ],
389 [],
390 [ 'rc_minor != 0' ],
391 [],
392 ],
393 [
394 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_MINOR ] ],
395 [],
396 [ 'rc_minor = 0' ],
397 [],
398 ],
399 [
400 [ 'filters' => [ WatchedItemQueryService::FILTER_BOT ] ],
401 [],
402 [ 'rc_bot != 0' ],
403 [],
404 ],
405 [
406 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_BOT ] ],
407 [],
408 [ 'rc_bot = 0' ],
409 [],
410 ],
411 [
412 [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
413 [],
414 [ 'rc_user = 0' ],
415 [],
416 ],
417 [
418 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
419 [],
420 [ 'rc_user != 0' ],
421 [],
422 ],
423 [
424 [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
425 [],
426 [ 'rc_patrolled != 0' ],
427 [],
428 ],
429 [
430 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_PATROLLED ] ],
431 [],
432 [ 'rc_patrolled = 0' ],
433 [],
434 ],
435 [
436 [ 'filters' => [ WatchedItemQueryService::FILTER_UNREAD ] ],
437 [],
438 [ 'rc_timestamp >= wl_notificationtimestamp' ],
439 [],
440 ],
441 [
442 [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_UNREAD ] ],
443 [],
444 [ 'wl_notificationtimestamp IS NULL OR rc_timestamp < wl_notificationtimestamp' ],
445 [],
446 ],
447 [
448 [ 'onlyByUser' => 'SomeOtherUser' ],
449 [],
450 [ 'rc_user_text' => 'SomeOtherUser' ],
451 [],
452 ],
453 [
454 [ 'notByUser' => 'SomeOtherUser' ],
455 [],
456 [ "rc_user_text != 'SomeOtherUser'" ],
457 [],
458 ],
459 [
460 [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_OLDER ],
461 [],
462 [
463 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
464 ],
465 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
466 ],
467 [
468 [ 'startFrom' => [ '20151212010101', 123 ], 'dir' => WatchedItemQueryService::DIR_NEWER ],
469 [],
470 [
471 "(rc_timestamp > '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id >= 123))"
472 ],
473 [ 'ORDER BY' => [ 'rc_timestamp', 'rc_id' ] ],
474 ],
475 [
476 [
477 'startFrom' => [ '20151212010101', "123; DROP TABLE watchlist;\n--" ],
478 'dir' => WatchedItemQueryService::DIR_OLDER
479 ],
480 [],
481 [
482 "(rc_timestamp < '20151212010101') OR ((rc_timestamp = '20151212010101') AND (rc_id <= 123))"
483 ],
484 [ 'ORDER BY' => [ 'rc_timestamp DESC', 'rc_id DESC' ] ],
485 ],
486 ];
487 }
488
489 /**
490 * @dataProvider getWatchedItemsWithRecentChangeInfoOptionsProvider
491 */
492 public function testGetWatchedItemsWithRecentChangeInfo_optionsAndEmptyResult(
493 array $options,
494 array $expectedExtraFields,
495 array $expectedExtraConds,
496 array $expectedDbOptions
497 ) {
498 $expectedFields = array_merge(
499 [
500 'rc_id',
501 'rc_namespace',
502 'rc_title',
503 'rc_timestamp',
504 'rc_type',
505 'rc_deleted',
506 'wl_notificationtimestamp',
507
508 'rc_cur_id',
509 'rc_this_oldid',
510 'rc_last_oldid',
511 ],
512 $expectedExtraFields
513 );
514 $expectedConds = array_merge(
515 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)', ],
516 $expectedExtraConds
517 );
518
519 $mockDb = $this->getMockDb();
520 $mockDb->expects( $this->once() )
521 ->method( 'select' )
522 ->with(
523 [ 'recentchanges', 'watchlist', 'page' ],
524 $expectedFields,
525 $expectedConds,
526 $this->isType( 'string' ),
527 $expectedDbOptions,
528 [
529 'watchlist' => [
530 'INNER JOIN',
531 [
532 'wl_namespace=rc_namespace',
533 'wl_title=rc_title'
534 ]
535 ],
536 'page' => [
537 'LEFT JOIN',
538 'rc_cur_id=page_id',
539 ],
540 ]
541 )
542 ->will( $this->returnValue( [] ) );
543
544 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
545 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
546
547 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
548
549 $this->assertEmpty( $items );
550 }
551
552 public function filterPatrolledOptionProvider() {
553 return [
554 [ WatchedItemQueryService::FILTER_PATROLLED ],
555 [ WatchedItemQueryService::FILTER_NOT_PATROLLED ],
556 ];
557 }
558
559 /**
560 * @dataProvider filterPatrolledOptionProvider
561 */
562 public function testGetWatchedItemsWithRecentChangeInfo_filterPatrolledAndUserWithNoPatrolRights(
563 $filtersOption
564 ) {
565 $mockDb = $this->getMockDb();
566 $mockDb->expects( $this->once() )
567 ->method( 'select' )
568 ->with(
569 [ 'recentchanges', 'watchlist', 'page' ],
570 $this->isType( 'array' ),
571 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
572 $this->isType( 'string' ),
573 $this->isType( 'array' ),
574 $this->isType( 'array' )
575 )
576 ->will( $this->returnValue( [] ) );
577
578 $user = $this->getMockNonAnonUserWithIdAndNoPatrolRights( 1 );
579
580 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
581 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
582 $user,
583 [ 'filters' => [ $filtersOption ] ]
584 );
585
586 $this->assertEmpty( $items );
587 }
588
589 public function mysqlIndexOptimizationProvider() {
590 return [
591 [
592 'mysql',
593 [],
594 [ "rc_timestamp > ''" ],
595 ],
596 [
597 'mysql',
598 [ 'start' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
599 [ "rc_timestamp <= '20151212010101'" ],
600 ],
601 [
602 'mysql',
603 [ 'end' => '20151212010101', 'dir' => WatchedItemQueryService::DIR_OLDER ],
604 [ "rc_timestamp >= '20151212010101'" ],
605 ],
606 [
607 'postgres',
608 [],
609 [],
610 ],
611 ];
612 }
613
614 /**
615 * @dataProvider mysqlIndexOptimizationProvider
616 */
617 public function testGetWatchedItemsWithRecentChangeInfo_mysqlIndexOptimization(
618 $dbType,
619 array $options,
620 array $expectedExtraConds
621 ) {
622 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
623 $conds = array_merge( $commonConds, $expectedExtraConds );
624
625 $mockDb = $this->getMockDb();
626 $mockDb->expects( $this->once() )
627 ->method( 'select' )
628 ->with(
629 [ 'recentchanges', 'watchlist', 'page' ],
630 $this->isType( 'array' ),
631 $conds,
632 $this->isType( 'string' ),
633 $this->isType( 'array' ),
634 $this->isType( 'array' )
635 )
636 ->will( $this->returnValue( [] ) );
637 $mockDb->expects( $this->any() )
638 ->method( 'getType' )
639 ->will( $this->returnValue( $dbType ) );
640
641 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
642 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
643
644 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
645
646 $this->assertEmpty( $items );
647 }
648
649 public function userPermissionRelatedExtraChecksProvider() {
650 return [
651 [
652 [],
653 'deletedhistory',
654 [
655 '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
656 LogPage::DELETED_ACTION . ')'
657 ],
658 ],
659 [
660 [],
661 'suppressrevision',
662 [
663 '(rc_type != ' . RC_LOG . ') OR (' .
664 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
665 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
666 ],
667 ],
668 [
669 [],
670 'viewsuppressed',
671 [
672 '(rc_type != ' . RC_LOG . ') OR (' .
673 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
674 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
675 ],
676 ],
677 [
678 [ 'onlyByUser' => 'SomeOtherUser' ],
679 'deletedhistory',
680 [
681 'rc_user_text' => 'SomeOtherUser',
682 '(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
683 '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
684 LogPage::DELETED_ACTION . ')'
685 ],
686 ],
687 [
688 [ 'onlyByUser' => 'SomeOtherUser' ],
689 'suppressrevision',
690 [
691 'rc_user_text' => 'SomeOtherUser',
692 '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
693 ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
694 '(rc_type != ' . RC_LOG . ') OR (' .
695 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
696 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
697 ],
698 ],
699 [
700 [ 'onlyByUser' => 'SomeOtherUser' ],
701 'viewsuppressed',
702 [
703 'rc_user_text' => 'SomeOtherUser',
704 '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
705 ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
706 '(rc_type != ' . RC_LOG . ') OR (' .
707 '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
708 ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
709 ],
710 ],
711 ];
712 }
713
714 /**
715 * @dataProvider userPermissionRelatedExtraChecksProvider
716 */
717 public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
718 array $options,
719 $notAllowedAction,
720 array $expectedExtraConds
721 ) {
722 $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
723 $conds = array_merge( $commonConds, $expectedExtraConds );
724
725 $mockDb = $this->getMockDb();
726 $mockDb->expects( $this->once() )
727 ->method( 'select' )
728 ->with(
729 [ 'recentchanges', 'watchlist', 'page' ],
730 $this->isType( 'array' ),
731 $conds,
732 $this->isType( 'string' ),
733 $this->isType( 'array' ),
734 $this->isType( 'array' )
735 )
736 ->will( $this->returnValue( [] ) );
737
738 $user = $this->getMockNonAnonUserWithIdAndRestrictedPermissions( 1, $notAllowedAction );
739
740 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
741 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
742
743 $this->assertEmpty( $items );
744 }
745
746 public function testGetWatchedItemsWithRecentChangeInfo_allRevisionsOptionAndEmptyResult() {
747 $mockDb = $this->getMockDb();
748 $mockDb->expects( $this->once() )
749 ->method( 'select' )
750 ->with(
751 [ 'recentchanges', 'watchlist' ],
752 [
753 'rc_id',
754 'rc_namespace',
755 'rc_title',
756 'rc_timestamp',
757 'rc_type',
758 'rc_deleted',
759 'wl_notificationtimestamp',
760
761 'rc_cur_id',
762 'rc_this_oldid',
763 'rc_last_oldid',
764 ],
765 [ 'wl_user' => 1, ],
766 $this->isType( 'string' ),
767 [],
768 [
769 'watchlist' => [
770 'INNER JOIN',
771 [
772 'wl_namespace=rc_namespace',
773 'wl_title=rc_title'
774 ]
775 ],
776 ]
777 )
778 ->will( $this->returnValue( [] ) );
779
780 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
781 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
782
783 $items = $queryService->getWatchedItemsWithRecentChangeInfo( $user, [ 'allRevisions' => true ] );
784
785 $this->assertEmpty( $items );
786 }
787
788 public function getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider() {
789 return [
790 [
791 [ 'rcTypes' => [ 1337 ] ],
792 'Bad value for parameter $options[\'rcTypes\']',
793 ],
794 [
795 [ 'rcTypes' => [ 'edit' ] ],
796 'Bad value for parameter $options[\'rcTypes\']',
797 ],
798 [
799 [ 'rcTypes' => [ RC_EDIT, 1337 ] ],
800 'Bad value for parameter $options[\'rcTypes\']',
801 ],
802 [
803 [ 'dir' => 'foo' ],
804 'Bad value for parameter $options[\'dir\']',
805 ],
806 [
807 [ 'start' => '20151212010101' ],
808 'Bad value for parameter $options[\'dir\']: must be provided',
809 ],
810 [
811 [ 'end' => '20151212010101' ],
812 'Bad value for parameter $options[\'dir\']: must be provided',
813 ],
814 [
815 [ 'startFrom' => [ '20151212010101', 123 ] ],
816 'Bad value for parameter $options[\'dir\']: must be provided',
817 ],
818 [
819 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => '20151212010101' ],
820 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
821 ],
822 [
823 [ 'dir' => WatchedItemQueryService::DIR_OLDER, 'startFrom' => [ '20151212010101' ] ],
824 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
825 ],
826 [
827 [
828 'dir' => WatchedItemQueryService::DIR_OLDER,
829 'startFrom' => [ '20151212010101', 123, 'foo' ]
830 ],
831 'Bad value for parameter $options[\'startFrom\']: must be a two-element array',
832 ],
833 [
834 [ 'watchlistOwner' => $this->getMockUnrestrictedNonAnonUserWithId( 2 ) ],
835 'Bad value for parameter $options[\'watchlistOwnerToken\']',
836 ],
837 [
838 [ 'watchlistOwner' => 'Other User', 'watchlistOwnerToken' => 'some-token' ],
839 'Bad value for parameter $options[\'watchlistOwner\']',
840 ],
841 ];
842 }
843
844 /**
845 * @dataProvider getWatchedItemsWithRecentChangeInfoInvalidOptionsProvider
846 */
847 public function testGetWatchedItemsWithRecentChangeInfo_invalidOptions(
848 array $options,
849 $expectedInExceptionMessage
850 ) {
851 $mockDb = $this->getMockDb();
852 $mockDb->expects( $this->never() )
853 ->method( $this->anything() );
854
855 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
856 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
857
858 $this->setExpectedException( InvalidArgumentException::class, $expectedInExceptionMessage );
859 $queryService->getWatchedItemsWithRecentChangeInfo( $user, $options );
860 }
861
862 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorOptionAndEmptyResult() {
863 $mockDb = $this->getMockDb();
864 $mockDb->expects( $this->once() )
865 ->method( 'select' )
866 ->with(
867 [ 'recentchanges', 'watchlist', 'page' ],
868 [
869 'rc_id',
870 'rc_namespace',
871 'rc_title',
872 'rc_timestamp',
873 'rc_type',
874 'rc_deleted',
875 'wl_notificationtimestamp',
876 'rc_cur_id',
877 ],
878 [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ],
879 $this->isType( 'string' ),
880 [],
881 [
882 'watchlist' => [
883 'INNER JOIN',
884 [
885 'wl_namespace=rc_namespace',
886 'wl_title=rc_title'
887 ]
888 ],
889 'page' => [
890 'LEFT JOIN',
891 'rc_cur_id=page_id',
892 ],
893 ]
894 )
895 ->will( $this->returnValue( [] ) );
896
897 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
898 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
899
900 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
901 $user,
902 [ 'usedInGenerator' => true ]
903 );
904
905 $this->assertEmpty( $items );
906 }
907
908 public function testGetWatchedItemsWithRecentChangeInfo_usedInGeneratorAllRevisionsOptions() {
909 $mockDb = $this->getMockDb();
910 $mockDb->expects( $this->once() )
911 ->method( 'select' )
912 ->with(
913 [ 'recentchanges', 'watchlist' ],
914 [
915 'rc_id',
916 'rc_namespace',
917 'rc_title',
918 'rc_timestamp',
919 'rc_type',
920 'rc_deleted',
921 'wl_notificationtimestamp',
922 'rc_this_oldid',
923 ],
924 [ 'wl_user' => 1 ],
925 $this->isType( 'string' ),
926 [],
927 [
928 'watchlist' => [
929 'INNER JOIN',
930 [
931 'wl_namespace=rc_namespace',
932 'wl_title=rc_title'
933 ]
934 ],
935 ]
936 )
937 ->will( $this->returnValue( [] ) );
938
939 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
940 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
941
942 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
943 $user,
944 [ 'usedInGenerator' => true, 'allRevisions' => true, ]
945 );
946
947 $this->assertEmpty( $items );
948 }
949
950 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerOptionAndEmptyResult() {
951 $mockDb = $this->getMockDb();
952 $mockDb->expects( $this->once() )
953 ->method( 'select' )
954 ->with(
955 $this->isType( 'array' ),
956 $this->isType( 'array' ),
957 [
958 'wl_user' => 2,
959 '(rc_this_oldid=page_latest) OR (rc_type=3)',
960 ],
961 $this->isType( 'string' ),
962 $this->isType( 'array' ),
963 $this->isType( 'array' )
964 )
965 ->will( $this->returnValue( [] ) );
966
967 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
968 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
969 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
970 $otherUser->expects( $this->once() )
971 ->method( 'getOption' )
972 ->with( 'watchlisttoken' )
973 ->willReturn( '0123456789abcdef' );
974
975 $items = $queryService->getWatchedItemsWithRecentChangeInfo(
976 $user,
977 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => '0123456789abcdef' ]
978 );
979
980 $this->assertEmpty( $items );
981 }
982
983 public function invalidWatchlistTokenProvider() {
984 return [
985 [ 'wrongToken' ],
986 [ '' ],
987 ];
988 }
989
990 /**
991 * @dataProvider invalidWatchlistTokenProvider
992 */
993 public function testGetWatchedItemsWithRecentChangeInfo_watchlistOwnerAndInvalidToken( $token ) {
994 $mockDb = $this->getMockDb();
995 $mockDb->expects( $this->never() )
996 ->method( $this->anything() );
997
998 $queryService = new WatchedItemQueryService( $this->getMockLoadBalancer( $mockDb ) );
999 $user = $this->getMockUnrestrictedNonAnonUserWithId( 1 );
1000 $otherUser = $this->getMockUnrestrictedNonAnonUserWithId( 2 );
1001 $otherUser->expects( $this->once() )
1002 ->method( 'getOption' )
1003 ->with( 'watchlisttoken' )
1004 ->willReturn( '0123456789abcdef' );
1005
1006 $this->setExpectedException( UsageException::class, 'Incorrect watchlist token provided' );
1007 $queryService->getWatchedItemsWithRecentChangeInfo(
1008 $user,
1009 [ 'watchlistOwner' => $otherUser, 'watchlistOwnerToken' => $token ]
1010 );
1011 }
1012
1013 }