Get rid of unnecessary func_get_args() and friends
[lhc/web/wiklou.git] / tests / phpunit / includes / watcheditem / WatchedItemStoreUnitTest.php
1 <?php
2 use MediaWiki\Linker\LinkTarget;
3 use Wikimedia\Rdbms\LBFactory;
4 use Wikimedia\Rdbms\LoadBalancer;
5 use Wikimedia\ScopedCallback;
6 use Wikimedia\TestingAccessWrapper;
7
8 /**
9 * @author Addshore
10 *
11 * @covers WatchedItemStore
12 */
13 class WatchedItemStoreUnitTest extends MediaWikiTestCase {
14
15 /**
16 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
17 */
18 private function getMockDb() {
19 return $this->createMock( IDatabase::class );
20 }
21
22 /**
23 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
24 */
25 private function getMockLoadBalancer(
26 $mockDb,
27 $expectedConnectionType = null
28 ) {
29 $mock = $this->getMockBuilder( LoadBalancer::class )
30 ->disableOriginalConstructor()
31 ->getMock();
32 if ( $expectedConnectionType !== null ) {
33 $mock->expects( $this->any() )
34 ->method( 'getConnectionRef' )
35 ->with( $expectedConnectionType )
36 ->will( $this->returnValue( $mockDb ) );
37 } else {
38 $mock->expects( $this->any() )
39 ->method( 'getConnectionRef' )
40 ->will( $this->returnValue( $mockDb ) );
41 }
42 return $mock;
43 }
44
45 /**
46 * @return PHPUnit_Framework_MockObject_MockObject|LBFactory
47 */
48 private function getMockLBFactory(
49 $mockDb,
50 $expectedConnectionType = null
51 ) {
52 $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
53 $mock = $this->getMockBuilder( LBFactory::class )
54 ->disableOriginalConstructor()
55 ->getMock();
56 $mock->expects( $this->any() )
57 ->method( 'getMainLB' )
58 ->will( $this->returnValue( $loadBalancer ) );
59 return $mock;
60 }
61
62 /**
63 * @return PHPUnit_Framework_MockObject_MockObject|JobQueueGroup
64 */
65 private function getMockJobQueueGroup() {
66 $mock = $this->getMockBuilder( JobQueueGroup::class )
67 ->disableOriginalConstructor()
68 ->getMock();
69 $mock->expects( $this->any() )
70 ->method( 'push' )
71 ->will( $this->returnCallback( function ( Job $job ) {
72 $job->run();
73 } ) );
74 $mock->expects( $this->any() )
75 ->method( 'lazyPush' )
76 ->will( $this->returnCallback( function ( Job $job ) {
77 $job->run();
78 } ) );
79 return $mock;
80 }
81
82 /**
83 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
84 */
85 private function getMockCache() {
86 $mock = $this->getMockBuilder( HashBagOStuff::class )
87 ->disableOriginalConstructor()
88 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
89 ->getMock();
90 $mock->expects( $this->any() )
91 ->method( 'makeKey' )
92 ->will( $this->returnCallback( function ( ...$args ) {
93 return implode( ':', $args );
94 } ) );
95 return $mock;
96 }
97
98 /**
99 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
100 */
101 private function getMockReadOnlyMode( $readOnly = false ) {
102 $mock = $this->getMockBuilder( ReadOnlyMode::class )
103 ->disableOriginalConstructor()
104 ->getMock();
105 $mock->expects( $this->any() )
106 ->method( 'isReadOnly' )
107 ->will( $this->returnValue( $readOnly ) );
108 return $mock;
109 }
110
111 /**
112 * @param int $id
113 * @return PHPUnit_Framework_MockObject_MockObject|User
114 */
115 private function getMockNonAnonUserWithId( $id ) {
116 $mock = $this->createMock( User::class );
117 $mock->expects( $this->any() )
118 ->method( 'isAnon' )
119 ->will( $this->returnValue( false ) );
120 $mock->expects( $this->any() )
121 ->method( 'getId' )
122 ->will( $this->returnValue( $id ) );
123 $mock->expects( $this->any() )
124 ->method( 'getUserPage' )
125 ->will( $this->returnValue( Title::makeTitle( NS_USER, 'MockUser' ) ) );
126 return $mock;
127 }
128
129 /**
130 * @return User
131 */
132 private function getAnonUser() {
133 return User::newFromName( 'Anon_User' );
134 }
135
136 private function getFakeRow( array $rowValues ) {
137 $fakeRow = new stdClass();
138 foreach ( $rowValues as $valueName => $value ) {
139 $fakeRow->$valueName = $value;
140 }
141 return $fakeRow;
142 }
143
144 private function newWatchedItemStore(
145 LBFactory $lbFactory,
146 JobQueueGroup $queueGroup,
147 HashBagOStuff $cache,
148 ReadOnlyMode $readOnlyMode
149 ) {
150 return new WatchedItemStore(
151 $lbFactory,
152 $queueGroup,
153 new HashBagOStuff(),
154 $cache,
155 $readOnlyMode,
156 1000
157 );
158 }
159
160 public function testClearWatchedItems() {
161 $user = $this->getMockNonAnonUserWithId( 7 );
162
163 $mockDb = $this->getMockDb();
164 $mockDb->expects( $this->once() )
165 ->method( 'selectField' )
166 ->with(
167 'watchlist',
168 'COUNT(*)',
169 [
170 'wl_user' => $user->getId(),
171 ],
172 $this->isType( 'string' )
173 )
174 ->will( $this->returnValue( 12 ) );
175 $mockDb->expects( $this->once() )
176 ->method( 'delete' )
177 ->with(
178 'watchlist',
179 [ 'wl_user' => 7 ],
180 $this->isType( 'string' )
181 );
182
183 $mockCache = $this->getMockCache();
184 $mockCache->expects( $this->never() )->method( 'get' );
185 $mockCache->expects( $this->never() )->method( 'set' );
186 $mockCache->expects( $this->once() )
187 ->method( 'delete' )
188 ->with( 'RM-KEY' );
189
190 $store = $this->newWatchedItemStore(
191 $this->getMockLBFactory( $mockDb ),
192 $this->getMockJobQueueGroup(),
193 $mockCache,
194 $this->getMockReadOnlyMode()
195 );
196 TestingAccessWrapper::newFromObject( $store )
197 ->cacheIndex = [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
198
199 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
200 }
201
202 public function testClearWatchedItems_tooManyItemsWatched() {
203 $user = $this->getMockNonAnonUserWithId( 7 );
204
205 $mockDb = $this->getMockDb();
206 $mockDb->expects( $this->once() )
207 ->method( 'selectField' )
208 ->with(
209 'watchlist',
210 'COUNT(*)',
211 [
212 'wl_user' => $user->getId(),
213 ],
214 $this->isType( 'string' )
215 )
216 ->will( $this->returnValue( 99999 ) );
217
218 $mockCache = $this->getMockCache();
219 $mockCache->expects( $this->never() )->method( 'get' );
220 $mockCache->expects( $this->never() )->method( 'set' );
221 $mockCache->expects( $this->never() )->method( 'delete' );
222
223 $store = $this->newWatchedItemStore(
224 $this->getMockLBFactory( $mockDb ),
225 $this->getMockJobQueueGroup(),
226 $mockCache,
227 $this->getMockReadOnlyMode()
228 );
229
230 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
231 }
232
233 public function testCountWatchedItems() {
234 $user = $this->getMockNonAnonUserWithId( 1 );
235
236 $mockDb = $this->getMockDb();
237 $mockDb->expects( $this->exactly( 1 ) )
238 ->method( 'selectField' )
239 ->with(
240 'watchlist',
241 'COUNT(*)',
242 [
243 'wl_user' => $user->getId(),
244 ],
245 $this->isType( 'string' )
246 )
247 ->will( $this->returnValue( '12' ) );
248
249 $mockCache = $this->getMockCache();
250 $mockCache->expects( $this->never() )->method( 'get' );
251 $mockCache->expects( $this->never() )->method( 'set' );
252 $mockCache->expects( $this->never() )->method( 'delete' );
253
254 $store = $this->newWatchedItemStore(
255 $this->getMockLBFactory( $mockDb ),
256 $this->getMockJobQueueGroup(),
257 $mockCache,
258 $this->getMockReadOnlyMode()
259 );
260
261 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
262 }
263
264 public function testCountWatchers() {
265 $titleValue = new TitleValue( 0, 'SomeDbKey' );
266
267 $mockDb = $this->getMockDb();
268 $mockDb->expects( $this->exactly( 1 ) )
269 ->method( 'selectField' )
270 ->with(
271 'watchlist',
272 'COUNT(*)',
273 [
274 'wl_namespace' => $titleValue->getNamespace(),
275 'wl_title' => $titleValue->getDBkey(),
276 ],
277 $this->isType( 'string' )
278 )
279 ->will( $this->returnValue( '7' ) );
280
281 $mockCache = $this->getMockCache();
282 $mockCache->expects( $this->never() )->method( 'get' );
283 $mockCache->expects( $this->never() )->method( 'set' );
284 $mockCache->expects( $this->never() )->method( 'delete' );
285
286 $store = $this->newWatchedItemStore(
287 $this->getMockLBFactory( $mockDb ),
288 $this->getMockJobQueueGroup(),
289 $mockCache,
290 $this->getMockReadOnlyMode()
291 );
292
293 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
294 }
295
296 public function testCountWatchersMultiple() {
297 $titleValues = [
298 new TitleValue( 0, 'SomeDbKey' ),
299 new TitleValue( 0, 'OtherDbKey' ),
300 new TitleValue( 1, 'AnotherDbKey' ),
301 ];
302
303 $mockDb = $this->getMockDb();
304
305 $dbResult = [
306 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
307 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
308 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
309 ),
310 ];
311 $mockDb->expects( $this->once() )
312 ->method( 'makeWhereFrom2d' )
313 ->with(
314 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
315 $this->isType( 'string' ),
316 $this->isType( 'string' )
317 )
318 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
319 $mockDb->expects( $this->once() )
320 ->method( 'select' )
321 ->with(
322 'watchlist',
323 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
324 [ 'makeWhereFrom2d return value' ],
325 $this->isType( 'string' ),
326 [
327 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
328 ]
329 )
330 ->will(
331 $this->returnValue( $dbResult )
332 );
333
334 $mockCache = $this->getMockCache();
335 $mockCache->expects( $this->never() )->method( 'get' );
336 $mockCache->expects( $this->never() )->method( 'set' );
337 $mockCache->expects( $this->never() )->method( 'delete' );
338
339 $store = $this->newWatchedItemStore(
340 $this->getMockLBFactory( $mockDb ),
341 $this->getMockJobQueueGroup(),
342 $mockCache,
343 $this->getMockReadOnlyMode()
344 );
345
346 $expected = [
347 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
348 1 => [ 'AnotherDbKey' => 500 ],
349 ];
350 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
351 }
352
353 public function provideIntWithDbUnsafeVersion() {
354 return [
355 [ 50 ],
356 [ "50; DROP TABLE watchlist;\n--" ],
357 ];
358 }
359
360 /**
361 * @dataProvider provideIntWithDbUnsafeVersion
362 */
363 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
364 $titleValues = [
365 new TitleValue( 0, 'SomeDbKey' ),
366 new TitleValue( 0, 'OtherDbKey' ),
367 new TitleValue( 1, 'AnotherDbKey' ),
368 ];
369
370 $mockDb = $this->getMockDb();
371
372 $dbResult = [
373 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
374 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
375 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
376 ),
377 ];
378 $mockDb->expects( $this->once() )
379 ->method( 'makeWhereFrom2d' )
380 ->with(
381 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
382 $this->isType( 'string' ),
383 $this->isType( 'string' )
384 )
385 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
386 $mockDb->expects( $this->once() )
387 ->method( 'select' )
388 ->with(
389 'watchlist',
390 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
391 [ 'makeWhereFrom2d return value' ],
392 $this->isType( 'string' ),
393 [
394 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
395 'HAVING' => 'COUNT(*) >= 50',
396 ]
397 )
398 ->will(
399 $this->returnValue( $dbResult )
400 );
401
402 $mockCache = $this->getMockCache();
403 $mockCache->expects( $this->never() )->method( 'get' );
404 $mockCache->expects( $this->never() )->method( 'set' );
405 $mockCache->expects( $this->never() )->method( 'delete' );
406
407 $store = $this->newWatchedItemStore(
408 $this->getMockLBFactory( $mockDb ),
409 $this->getMockJobQueueGroup(),
410 $mockCache,
411 $this->getMockReadOnlyMode()
412 );
413
414 $expected = [
415 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
416 1 => [ 'AnotherDbKey' => 500 ],
417 ];
418 $this->assertEquals(
419 $expected,
420 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
421 );
422 }
423
424 public function testCountVisitingWatchers() {
425 $titleValue = new TitleValue( 0, 'SomeDbKey' );
426
427 $mockDb = $this->getMockDb();
428 $mockDb->expects( $this->exactly( 1 ) )
429 ->method( 'selectField' )
430 ->with(
431 'watchlist',
432 'COUNT(*)',
433 [
434 'wl_namespace' => $titleValue->getNamespace(),
435 'wl_title' => $titleValue->getDBkey(),
436 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
437 ],
438 $this->isType( 'string' )
439 )
440 ->will( $this->returnValue( '7' ) );
441 $mockDb->expects( $this->exactly( 1 ) )
442 ->method( 'addQuotes' )
443 ->will( $this->returnCallback( function ( $value ) {
444 return "'$value'";
445 } ) );
446 $mockDb->expects( $this->exactly( 1 ) )
447 ->method( 'timestamp' )
448 ->will( $this->returnCallback( function ( $value ) {
449 return 'TS' . $value . 'TS';
450 } ) );
451
452 $mockCache = $this->getMockCache();
453 $mockCache->expects( $this->never() )->method( 'set' );
454 $mockCache->expects( $this->never() )->method( 'get' );
455 $mockCache->expects( $this->never() )->method( 'delete' );
456
457 $store = $this->newWatchedItemStore(
458 $this->getMockLBFactory( $mockDb ),
459 $this->getMockJobQueueGroup(),
460 $mockCache,
461 $this->getMockReadOnlyMode()
462 );
463
464 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
465 }
466
467 public function testCountVisitingWatchersMultiple() {
468 $titleValuesWithThresholds = [
469 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
470 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
471 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
472 ];
473
474 $dbResult = [
475 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
476 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
477 $this->getFakeRow(
478 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
479 ),
480 ];
481 $mockDb = $this->getMockDb();
482 $mockDb->expects( $this->exactly( 2 * 3 ) )
483 ->method( 'addQuotes' )
484 ->will( $this->returnCallback( function ( $value ) {
485 return "'$value'";
486 } ) );
487 $mockDb->expects( $this->exactly( 3 ) )
488 ->method( 'timestamp' )
489 ->will( $this->returnCallback( function ( $value ) {
490 return 'TS' . $value . 'TS';
491 } ) );
492 $mockDb->expects( $this->any() )
493 ->method( 'makeList' )
494 ->with(
495 $this->isType( 'array' ),
496 $this->isType( 'int' )
497 )
498 ->will( $this->returnCallback( function ( $a, $conj ) {
499 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
500 return implode( $sqlConj, array_map( function ( $s ) {
501 return '(' . $s . ')';
502 }, $a
503 ) );
504 } ) );
505 $mockDb->expects( $this->never() )
506 ->method( 'makeWhereFrom2d' );
507
508 $expectedCond =
509 '((wl_namespace = 0) AND (' .
510 "(((wl_title = 'SomeDbKey') AND (" .
511 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
512 ')) OR (' .
513 "(wl_title = 'OtherDbKey') AND (" .
514 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
515 '))))' .
516 ') OR ((wl_namespace = 1) AND (' .
517 "(((wl_title = 'AnotherDbKey') AND (" .
518 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
519 ')))))';
520 $mockDb->expects( $this->once() )
521 ->method( 'select' )
522 ->with(
523 'watchlist',
524 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
525 $expectedCond,
526 $this->isType( 'string' ),
527 [
528 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
529 ]
530 )
531 ->will(
532 $this->returnValue( $dbResult )
533 );
534
535 $mockCache = $this->getMockCache();
536 $mockCache->expects( $this->never() )->method( 'get' );
537 $mockCache->expects( $this->never() )->method( 'set' );
538 $mockCache->expects( $this->never() )->method( 'delete' );
539
540 $store = $this->newWatchedItemStore(
541 $this->getMockLBFactory( $mockDb ),
542 $this->getMockJobQueueGroup(),
543 $mockCache,
544 $this->getMockReadOnlyMode()
545 );
546
547 $expected = [
548 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
549 1 => [ 'AnotherDbKey' => 500 ],
550 ];
551 $this->assertEquals(
552 $expected,
553 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
554 );
555 }
556
557 public function testCountVisitingWatchersMultiple_withMissingTargets() {
558 $titleValuesWithThresholds = [
559 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
560 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
561 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
562 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
563 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
564 ];
565
566 $dbResult = [
567 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
568 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
569 $this->getFakeRow(
570 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
571 ),
572 $this->getFakeRow(
573 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
574 ),
575 $this->getFakeRow(
576 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
577 ),
578 ];
579 $mockDb = $this->getMockDb();
580 $mockDb->expects( $this->exactly( 2 * 3 ) )
581 ->method( 'addQuotes' )
582 ->will( $this->returnCallback( function ( $value ) {
583 return "'$value'";
584 } ) );
585 $mockDb->expects( $this->exactly( 3 ) )
586 ->method( 'timestamp' )
587 ->will( $this->returnCallback( function ( $value ) {
588 return 'TS' . $value . 'TS';
589 } ) );
590 $mockDb->expects( $this->any() )
591 ->method( 'makeList' )
592 ->with(
593 $this->isType( 'array' ),
594 $this->isType( 'int' )
595 )
596 ->will( $this->returnCallback( function ( $a, $conj ) {
597 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
598 return implode( $sqlConj, array_map( function ( $s ) {
599 return '(' . $s . ')';
600 }, $a
601 ) );
602 } ) );
603 $mockDb->expects( $this->once() )
604 ->method( 'makeWhereFrom2d' )
605 ->with(
606 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
607 $this->isType( 'string' ),
608 $this->isType( 'string' )
609 )
610 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
611
612 $expectedCond =
613 '((wl_namespace = 0) AND (' .
614 "(((wl_title = 'SomeDbKey') AND (" .
615 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
616 ')) OR (' .
617 "(wl_title = 'OtherDbKey') AND (" .
618 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
619 '))))' .
620 ') OR ((wl_namespace = 1) AND (' .
621 "(((wl_title = 'AnotherDbKey') AND (" .
622 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
623 '))))' .
624 ') OR ' .
625 '(makeWhereFrom2d return value)';
626 $mockDb->expects( $this->once() )
627 ->method( 'select' )
628 ->with(
629 'watchlist',
630 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
631 $expectedCond,
632 $this->isType( 'string' ),
633 [
634 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
635 ]
636 )
637 ->will(
638 $this->returnValue( $dbResult )
639 );
640
641 $mockCache = $this->getMockCache();
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'set' );
644 $mockCache->expects( $this->never() )->method( 'delete' );
645
646 $store = $this->newWatchedItemStore(
647 $this->getMockLBFactory( $mockDb ),
648 $this->getMockJobQueueGroup(),
649 $mockCache,
650 $this->getMockReadOnlyMode()
651 );
652
653 $expected = [
654 0 => [
655 'SomeDbKey' => 100, 'OtherDbKey' => 300,
656 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
657 ],
658 1 => [ 'AnotherDbKey' => 500 ],
659 ];
660 $this->assertEquals(
661 $expected,
662 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
663 );
664 }
665
666 /**
667 * @dataProvider provideIntWithDbUnsafeVersion
668 */
669 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
670 $titleValuesWithThresholds = [
671 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
672 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
673 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
674 ];
675
676 $mockDb = $this->getMockDb();
677 $mockDb->expects( $this->any() )
678 ->method( 'makeList' )
679 ->will( $this->returnValue( 'makeList return value' ) );
680 $mockDb->expects( $this->once() )
681 ->method( 'select' )
682 ->with(
683 'watchlist',
684 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
685 'makeList return value',
686 $this->isType( 'string' ),
687 [
688 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
689 'HAVING' => 'COUNT(*) >= 50',
690 ]
691 )
692 ->will(
693 $this->returnValue( [] )
694 );
695
696 $mockCache = $this->getMockCache();
697 $mockCache->expects( $this->never() )->method( 'get' );
698 $mockCache->expects( $this->never() )->method( 'set' );
699 $mockCache->expects( $this->never() )->method( 'delete' );
700
701 $store = $this->newWatchedItemStore(
702 $this->getMockLBFactory( $mockDb ),
703 $this->getMockJobQueueGroup(),
704 $mockCache,
705 $this->getMockReadOnlyMode()
706 );
707
708 $expected = [
709 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
710 1 => [ 'AnotherDbKey' => 0 ],
711 ];
712 $this->assertEquals(
713 $expected,
714 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
715 );
716 }
717
718 public function testCountUnreadNotifications() {
719 $user = $this->getMockNonAnonUserWithId( 1 );
720
721 $mockDb = $this->getMockDb();
722 $mockDb->expects( $this->exactly( 1 ) )
723 ->method( 'selectRowCount' )
724 ->with(
725 'watchlist',
726 '1',
727 [
728 "wl_notificationtimestamp IS NOT NULL",
729 'wl_user' => 1,
730 ],
731 $this->isType( 'string' )
732 )
733 ->will( $this->returnValue( '9' ) );
734
735 $mockCache = $this->getMockCache();
736 $mockCache->expects( $this->never() )->method( 'set' );
737 $mockCache->expects( $this->never() )->method( 'get' );
738 $mockCache->expects( $this->never() )->method( 'delete' );
739
740 $store = $this->newWatchedItemStore(
741 $this->getMockLBFactory( $mockDb ),
742 $this->getMockJobQueueGroup(),
743 $mockCache,
744 $this->getMockReadOnlyMode()
745 );
746
747 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
748 }
749
750 /**
751 * @dataProvider provideIntWithDbUnsafeVersion
752 */
753 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
754 $user = $this->getMockNonAnonUserWithId( 1 );
755
756 $mockDb = $this->getMockDb();
757 $mockDb->expects( $this->exactly( 1 ) )
758 ->method( 'selectRowCount' )
759 ->with(
760 'watchlist',
761 '1',
762 [
763 "wl_notificationtimestamp IS NOT NULL",
764 'wl_user' => 1,
765 ],
766 $this->isType( 'string' ),
767 [ 'LIMIT' => 50 ]
768 )
769 ->will( $this->returnValue( '50' ) );
770
771 $mockCache = $this->getMockCache();
772 $mockCache->expects( $this->never() )->method( 'set' );
773 $mockCache->expects( $this->never() )->method( 'get' );
774 $mockCache->expects( $this->never() )->method( 'delete' );
775
776 $store = $this->newWatchedItemStore(
777 $this->getMockLBFactory( $mockDb ),
778 $this->getMockJobQueueGroup(),
779 $mockCache,
780 $this->getMockReadOnlyMode()
781 );
782
783 $this->assertSame(
784 true,
785 $store->countUnreadNotifications( $user, $limit )
786 );
787 }
788
789 /**
790 * @dataProvider provideIntWithDbUnsafeVersion
791 */
792 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
793 $user = $this->getMockNonAnonUserWithId( 1 );
794
795 $mockDb = $this->getMockDb();
796 $mockDb->expects( $this->exactly( 1 ) )
797 ->method( 'selectRowCount' )
798 ->with(
799 'watchlist',
800 '1',
801 [
802 "wl_notificationtimestamp IS NOT NULL",
803 'wl_user' => 1,
804 ],
805 $this->isType( 'string' ),
806 [ 'LIMIT' => 50 ]
807 )
808 ->will( $this->returnValue( '9' ) );
809
810 $mockCache = $this->getMockCache();
811 $mockCache->expects( $this->never() )->method( 'set' );
812 $mockCache->expects( $this->never() )->method( 'get' );
813 $mockCache->expects( $this->never() )->method( 'delete' );
814
815 $store = $this->newWatchedItemStore(
816 $this->getMockLBFactory( $mockDb ),
817 $this->getMockJobQueueGroup(),
818 $mockCache,
819 $this->getMockReadOnlyMode()
820 );
821
822 $this->assertEquals(
823 9,
824 $store->countUnreadNotifications( $user, $limit )
825 );
826 }
827
828 public function testDuplicateEntry_nothingToDuplicate() {
829 $mockDb = $this->getMockDb();
830 $mockDb->expects( $this->once() )
831 ->method( 'select' )
832 ->with(
833 'watchlist',
834 [
835 'wl_user',
836 'wl_notificationtimestamp',
837 ],
838 [
839 'wl_namespace' => 0,
840 'wl_title' => 'Old_Title',
841 ],
842 'WatchedItemStore::duplicateEntry',
843 [ 'FOR UPDATE' ]
844 )
845 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
846
847 $store = $this->newWatchedItemStore(
848 $this->getMockLBFactory( $mockDb ),
849 $this->getMockJobQueueGroup(),
850 $this->getMockCache(),
851 $this->getMockReadOnlyMode()
852 );
853
854 $store->duplicateEntry(
855 Title::newFromText( 'Old_Title' ),
856 Title::newFromText( 'New_Title' )
857 );
858 }
859
860 public function testDuplicateEntry_somethingToDuplicate() {
861 $fakeRows = [
862 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
863 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
864 ];
865
866 $mockDb = $this->getMockDb();
867 $mockDb->expects( $this->at( 0 ) )
868 ->method( 'select' )
869 ->with(
870 'watchlist',
871 [
872 'wl_user',
873 'wl_notificationtimestamp',
874 ],
875 [
876 'wl_namespace' => 0,
877 'wl_title' => 'Old_Title',
878 ]
879 )
880 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
881 $mockDb->expects( $this->at( 1 ) )
882 ->method( 'replace' )
883 ->with(
884 'watchlist',
885 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
886 [
887 [
888 'wl_user' => 1,
889 'wl_namespace' => 0,
890 'wl_title' => 'New_Title',
891 'wl_notificationtimestamp' => '20151212010101',
892 ],
893 [
894 'wl_user' => 2,
895 'wl_namespace' => 0,
896 'wl_title' => 'New_Title',
897 'wl_notificationtimestamp' => null,
898 ],
899 ],
900 $this->isType( 'string' )
901 );
902
903 $mockCache = $this->getMockCache();
904 $mockCache->expects( $this->never() )->method( 'get' );
905 $mockCache->expects( $this->never() )->method( 'delete' );
906
907 $store = $this->newWatchedItemStore(
908 $this->getMockLBFactory( $mockDb ),
909 $this->getMockJobQueueGroup(),
910 $mockCache,
911 $this->getMockReadOnlyMode()
912 );
913
914 $store->duplicateEntry(
915 Title::newFromText( 'Old_Title' ),
916 Title::newFromText( 'New_Title' )
917 );
918 }
919
920 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
921 $mockDb = $this->getMockDb();
922 $mockDb->expects( $this->at( 0 ) )
923 ->method( 'select' )
924 ->with(
925 'watchlist',
926 [
927 'wl_user',
928 'wl_notificationtimestamp',
929 ],
930 [
931 'wl_namespace' => 0,
932 'wl_title' => 'Old_Title',
933 ]
934 )
935 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
936 $mockDb->expects( $this->at( 1 ) )
937 ->method( 'select' )
938 ->with(
939 'watchlist',
940 [
941 'wl_user',
942 'wl_notificationtimestamp',
943 ],
944 [
945 'wl_namespace' => 1,
946 'wl_title' => 'Old_Title',
947 ]
948 )
949 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
950
951 $mockCache = $this->getMockCache();
952 $mockCache->expects( $this->never() )->method( 'get' );
953 $mockCache->expects( $this->never() )->method( 'delete' );
954
955 $store = $this->newWatchedItemStore(
956 $this->getMockLBFactory( $mockDb ),
957 $this->getMockJobQueueGroup(),
958 $mockCache,
959 $this->getMockReadOnlyMode()
960 );
961
962 $store->duplicateAllAssociatedEntries(
963 Title::newFromText( 'Old_Title' ),
964 Title::newFromText( 'New_Title' )
965 );
966 }
967
968 public function provideLinkTargetPairs() {
969 return [
970 [ Title::newFromText( 'Old_Title' ), Title::newFromText( 'New_Title' ) ],
971 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
972 ];
973 }
974
975 /**
976 * @dataProvider provideLinkTargetPairs
977 */
978 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
979 LinkTarget $oldTarget,
980 LinkTarget $newTarget
981 ) {
982 $fakeRows = [
983 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
984 ];
985
986 $mockDb = $this->getMockDb();
987 $mockDb->expects( $this->at( 0 ) )
988 ->method( 'select' )
989 ->with(
990 'watchlist',
991 [
992 'wl_user',
993 'wl_notificationtimestamp',
994 ],
995 [
996 'wl_namespace' => $oldTarget->getNamespace(),
997 'wl_title' => $oldTarget->getDBkey(),
998 ]
999 )
1000 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1001 $mockDb->expects( $this->at( 1 ) )
1002 ->method( 'replace' )
1003 ->with(
1004 'watchlist',
1005 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1006 [
1007 [
1008 'wl_user' => 1,
1009 'wl_namespace' => $newTarget->getNamespace(),
1010 'wl_title' => $newTarget->getDBkey(),
1011 'wl_notificationtimestamp' => '20151212010101',
1012 ],
1013 ],
1014 $this->isType( 'string' )
1015 );
1016 $mockDb->expects( $this->at( 2 ) )
1017 ->method( 'select' )
1018 ->with(
1019 'watchlist',
1020 [
1021 'wl_user',
1022 'wl_notificationtimestamp',
1023 ],
1024 [
1025 'wl_namespace' => $oldTarget->getNamespace() + 1,
1026 'wl_title' => $oldTarget->getDBkey(),
1027 ]
1028 )
1029 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
1030 $mockDb->expects( $this->at( 3 ) )
1031 ->method( 'replace' )
1032 ->with(
1033 'watchlist',
1034 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
1035 [
1036 [
1037 'wl_user' => 1,
1038 'wl_namespace' => $newTarget->getNamespace() + 1,
1039 'wl_title' => $newTarget->getDBkey(),
1040 'wl_notificationtimestamp' => '20151212010101',
1041 ],
1042 ],
1043 $this->isType( 'string' )
1044 );
1045
1046 $mockCache = $this->getMockCache();
1047 $mockCache->expects( $this->never() )->method( 'get' );
1048 $mockCache->expects( $this->never() )->method( 'delete' );
1049
1050 $store = $this->newWatchedItemStore(
1051 $this->getMockLBFactory( $mockDb ),
1052 $this->getMockJobQueueGroup(),
1053 $mockCache,
1054 $this->getMockReadOnlyMode()
1055 );
1056
1057 $store->duplicateAllAssociatedEntries(
1058 $oldTarget,
1059 $newTarget
1060 );
1061 }
1062
1063 public function testAddWatch_nonAnonymousUser() {
1064 $mockDb = $this->getMockDb();
1065 $mockDb->expects( $this->once() )
1066 ->method( 'insert' )
1067 ->with(
1068 'watchlist',
1069 [
1070 [
1071 'wl_user' => 1,
1072 'wl_namespace' => 0,
1073 'wl_title' => 'Some_Page',
1074 'wl_notificationtimestamp' => null,
1075 ]
1076 ]
1077 );
1078
1079 $mockCache = $this->getMockCache();
1080 $mockCache->expects( $this->once() )
1081 ->method( 'delete' )
1082 ->with( '0:Some_Page:1' );
1083
1084 $store = $this->newWatchedItemStore(
1085 $this->getMockLBFactory( $mockDb ),
1086 $this->getMockJobQueueGroup(),
1087 $mockCache,
1088 $this->getMockReadOnlyMode()
1089 );
1090
1091 $store->addWatch(
1092 $this->getMockNonAnonUserWithId( 1 ),
1093 Title::newFromText( 'Some_Page' )
1094 );
1095 }
1096
1097 public function testAddWatch_anonymousUser() {
1098 $mockDb = $this->getMockDb();
1099 $mockDb->expects( $this->never() )
1100 ->method( 'insert' );
1101
1102 $mockCache = $this->getMockCache();
1103 $mockCache->expects( $this->never() )
1104 ->method( 'delete' );
1105
1106 $store = $this->newWatchedItemStore(
1107 $this->getMockLBFactory( $mockDb ),
1108 $this->getMockJobQueueGroup(),
1109 $mockCache,
1110 $this->getMockReadOnlyMode()
1111 );
1112
1113 $store->addWatch(
1114 $this->getAnonUser(),
1115 Title::newFromText( 'Some_Page' )
1116 );
1117 }
1118
1119 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1120 $store = $this->newWatchedItemStore(
1121 $this->getMockLBFactory( $this->getMockDb() ),
1122 $this->getMockJobQueueGroup(),
1123 $this->getMockCache(),
1124 $this->getMockReadOnlyMode( true )
1125 );
1126
1127 $this->assertFalse(
1128 $store->addWatchBatchForUser(
1129 $this->getMockNonAnonUserWithId( 1 ),
1130 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1131 )
1132 );
1133 }
1134
1135 public function testAddWatchBatchForUser_nonAnonymousUser() {
1136 $mockDb = $this->getMockDb();
1137 $mockDb->expects( $this->once() )
1138 ->method( 'insert' )
1139 ->with(
1140 'watchlist',
1141 [
1142 [
1143 'wl_user' => 1,
1144 'wl_namespace' => 0,
1145 'wl_title' => 'Some_Page',
1146 'wl_notificationtimestamp' => null,
1147 ],
1148 [
1149 'wl_user' => 1,
1150 'wl_namespace' => 1,
1151 'wl_title' => 'Some_Page',
1152 'wl_notificationtimestamp' => null,
1153 ]
1154 ]
1155 );
1156
1157 $mockDb->expects( $this->once() )
1158 ->method( 'affectedRows' )
1159 ->willReturn( 2 );
1160
1161 $mockCache = $this->getMockCache();
1162 $mockCache->expects( $this->exactly( 2 ) )
1163 ->method( 'delete' );
1164 $mockCache->expects( $this->at( 1 ) )
1165 ->method( 'delete' )
1166 ->with( '0:Some_Page:1' );
1167 $mockCache->expects( $this->at( 3 ) )
1168 ->method( 'delete' )
1169 ->with( '1:Some_Page:1' );
1170
1171 $store = $this->newWatchedItemStore(
1172 $this->getMockLBFactory( $mockDb ),
1173 $this->getMockJobQueueGroup(),
1174 $mockCache,
1175 $this->getMockReadOnlyMode()
1176 );
1177
1178 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1179
1180 $this->assertTrue(
1181 $store->addWatchBatchForUser(
1182 $mockUser,
1183 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1184 )
1185 );
1186 }
1187
1188 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1189 $mockDb = $this->getMockDb();
1190 $mockDb->expects( $this->never() )
1191 ->method( 'insert' );
1192
1193 $mockCache = $this->getMockCache();
1194 $mockCache->expects( $this->never() )
1195 ->method( 'delete' );
1196
1197 $store = $this->newWatchedItemStore(
1198 $this->getMockLBFactory( $mockDb ),
1199 $this->getMockJobQueueGroup(),
1200 $mockCache,
1201 $this->getMockReadOnlyMode()
1202 );
1203
1204 $this->assertFalse(
1205 $store->addWatchBatchForUser(
1206 $this->getAnonUser(),
1207 [ new TitleValue( 0, 'Other_Page' ) ]
1208 )
1209 );
1210 }
1211
1212 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1213 $user = $this->getMockNonAnonUserWithId( 1 );
1214 $mockDb = $this->getMockDb();
1215 $mockDb->expects( $this->never() )
1216 ->method( 'insert' );
1217
1218 $mockCache = $this->getMockCache();
1219 $mockCache->expects( $this->never() )
1220 ->method( 'delete' );
1221
1222 $store = $this->newWatchedItemStore(
1223 $this->getMockLBFactory( $mockDb ),
1224 $this->getMockJobQueueGroup(),
1225 $mockCache,
1226 $this->getMockReadOnlyMode()
1227 );
1228
1229 $this->assertTrue(
1230 $store->addWatchBatchForUser( $user, [] )
1231 );
1232 }
1233
1234 public function testLoadWatchedItem_existingItem() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->once() )
1237 ->method( 'selectRow' )
1238 ->with(
1239 'watchlist',
1240 'wl_notificationtimestamp',
1241 [
1242 'wl_user' => 1,
1243 'wl_namespace' => 0,
1244 'wl_title' => 'SomeDbKey',
1245 ]
1246 )
1247 ->will( $this->returnValue(
1248 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1249 ) );
1250
1251 $mockCache = $this->getMockCache();
1252 $mockCache->expects( $this->once() )
1253 ->method( 'set' )
1254 ->with(
1255 '0:SomeDbKey:1'
1256 );
1257
1258 $store = $this->newWatchedItemStore(
1259 $this->getMockLBFactory( $mockDb ),
1260 $this->getMockJobQueueGroup(),
1261 $mockCache,
1262 $this->getMockReadOnlyMode()
1263 );
1264
1265 $watchedItem = $store->loadWatchedItem(
1266 $this->getMockNonAnonUserWithId( 1 ),
1267 new TitleValue( 0, 'SomeDbKey' )
1268 );
1269 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1270 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1271 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1272 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1273 }
1274
1275 public function testLoadWatchedItem_noItem() {
1276 $mockDb = $this->getMockDb();
1277 $mockDb->expects( $this->once() )
1278 ->method( 'selectRow' )
1279 ->with(
1280 'watchlist',
1281 'wl_notificationtimestamp',
1282 [
1283 'wl_user' => 1,
1284 'wl_namespace' => 0,
1285 'wl_title' => 'SomeDbKey',
1286 ]
1287 )
1288 ->will( $this->returnValue( [] ) );
1289
1290 $mockCache = $this->getMockCache();
1291 $mockCache->expects( $this->never() )->method( 'get' );
1292 $mockCache->expects( $this->never() )->method( 'delete' );
1293
1294 $store = $this->newWatchedItemStore(
1295 $this->getMockLBFactory( $mockDb ),
1296 $this->getMockJobQueueGroup(),
1297 $mockCache,
1298 $this->getMockReadOnlyMode()
1299 );
1300
1301 $this->assertFalse(
1302 $store->loadWatchedItem(
1303 $this->getMockNonAnonUserWithId( 1 ),
1304 new TitleValue( 0, 'SomeDbKey' )
1305 )
1306 );
1307 }
1308
1309 public function testLoadWatchedItem_anonymousUser() {
1310 $mockDb = $this->getMockDb();
1311 $mockDb->expects( $this->never() )
1312 ->method( 'selectRow' );
1313
1314 $mockCache = $this->getMockCache();
1315 $mockCache->expects( $this->never() )->method( 'get' );
1316 $mockCache->expects( $this->never() )->method( 'delete' );
1317
1318 $store = $this->newWatchedItemStore(
1319 $this->getMockLBFactory( $mockDb ),
1320 $this->getMockJobQueueGroup(),
1321 $mockCache,
1322 $this->getMockReadOnlyMode()
1323 );
1324
1325 $this->assertFalse(
1326 $store->loadWatchedItem(
1327 $this->getAnonUser(),
1328 new TitleValue( 0, 'SomeDbKey' )
1329 )
1330 );
1331 }
1332
1333 public function testRemoveWatch_existingItem() {
1334 $mockDb = $this->getMockDb();
1335 $mockDb->expects( $this->once() )
1336 ->method( 'delete' )
1337 ->withConsecutive(
1338 [
1339 'watchlist',
1340 [
1341 'wl_user' => 1,
1342 'wl_namespace' => 0,
1343 'wl_title' => [ 'SomeDbKey' ],
1344 ],
1345 ],
1346 [
1347 'watchlist',
1348 [
1349 'wl_user' => 1,
1350 'wl_namespace' => 1,
1351 'wl_title' => [ 'SomeDbKey' ],
1352 ]
1353 ]
1354 );
1355 $mockDb->expects( $this->exactly( 1 ) )
1356 ->method( 'affectedRows' )
1357 ->willReturn( 2 );
1358
1359 $mockCache = $this->getMockCache();
1360 $mockCache->expects( $this->never() )->method( 'get' );
1361 $mockCache->expects( $this->once() )
1362 ->method( 'delete' )
1363 ->withConsecutive(
1364 [ '0:SomeDbKey:1' ],
1365 [ '1:SomeDbKey:1' ]
1366 );
1367
1368 $store = $this->newWatchedItemStore(
1369 $this->getMockLBFactory( $mockDb ),
1370 $this->getMockJobQueueGroup(),
1371 $mockCache,
1372 $this->getMockReadOnlyMode()
1373 );
1374
1375 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1376 $this->assertTrue(
1377 $store->removeWatch(
1378 $this->getMockNonAnonUserWithId( 1 ),
1379 Title::newFromTitleValue( $titleValue )
1380 )
1381 );
1382 }
1383
1384 public function testRemoveWatch_noItem() {
1385 $mockDb = $this->getMockDb();
1386 $mockDb->expects( $this->once() )
1387 ->method( 'delete' )
1388 ->withConsecutive(
1389 [
1390 'watchlist',
1391 [
1392 'wl_user' => 1,
1393 'wl_namespace' => 0,
1394 'wl_title' => [ 'SomeDbKey' ],
1395 ]
1396 ],
1397 [
1398 'watchlist',
1399 [
1400 'wl_user' => 1,
1401 'wl_namespace' => 1,
1402 'wl_title' => [ 'SomeDbKey' ],
1403 ]
1404 ]
1405 );
1406
1407 $mockDb->expects( $this->once() )
1408 ->method( 'affectedRows' )
1409 ->willReturn( 0 );
1410
1411 $mockCache = $this->getMockCache();
1412 $mockCache->expects( $this->never() )->method( 'get' );
1413 $mockCache->expects( $this->once() )
1414 ->method( 'delete' )
1415 ->withConsecutive(
1416 [ '0:SomeDbKey:1' ],
1417 [ '1:SomeDbKey:1' ]
1418 );
1419
1420 $store = $this->newWatchedItemStore(
1421 $this->getMockLBFactory( $mockDb ),
1422 $this->getMockJobQueueGroup(),
1423 $mockCache,
1424 $this->getMockReadOnlyMode()
1425 );
1426
1427 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1428 $this->assertFalse(
1429 $store->removeWatch(
1430 $this->getMockNonAnonUserWithId( 1 ),
1431 Title::newFromTitleValue( $titleValue )
1432 )
1433 );
1434 }
1435
1436 public function testRemoveWatch_anonymousUser() {
1437 $mockDb = $this->getMockDb();
1438 $mockDb->expects( $this->never() )
1439 ->method( 'delete' );
1440
1441 $mockCache = $this->getMockCache();
1442 $mockCache->expects( $this->never() )->method( 'get' );
1443 $mockCache->expects( $this->never() )
1444 ->method( 'delete' );
1445
1446 $store = $this->newWatchedItemStore(
1447 $this->getMockLBFactory( $mockDb ),
1448 $this->getMockJobQueueGroup(),
1449 $mockCache,
1450 $this->getMockReadOnlyMode()
1451 );
1452
1453 $this->assertFalse(
1454 $store->removeWatch(
1455 $this->getAnonUser(),
1456 new TitleValue( 0, 'SomeDbKey' )
1457 )
1458 );
1459 }
1460
1461 public function testGetWatchedItem_existingItem() {
1462 $mockDb = $this->getMockDb();
1463 $mockDb->expects( $this->once() )
1464 ->method( 'selectRow' )
1465 ->with(
1466 'watchlist',
1467 'wl_notificationtimestamp',
1468 [
1469 'wl_user' => 1,
1470 'wl_namespace' => 0,
1471 'wl_title' => 'SomeDbKey',
1472 ]
1473 )
1474 ->will( $this->returnValue(
1475 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1476 ) );
1477
1478 $mockCache = $this->getMockCache();
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1480 $mockCache->expects( $this->once() )
1481 ->method( 'get' )
1482 ->with(
1483 '0:SomeDbKey:1'
1484 )
1485 ->will( $this->returnValue( null ) );
1486 $mockCache->expects( $this->once() )
1487 ->method( 'set' )
1488 ->with(
1489 '0:SomeDbKey:1'
1490 );
1491
1492 $store = $this->newWatchedItemStore(
1493 $this->getMockLBFactory( $mockDb ),
1494 $this->getMockJobQueueGroup(),
1495 $mockCache,
1496 $this->getMockReadOnlyMode()
1497 );
1498
1499 $watchedItem = $store->getWatchedItem(
1500 $this->getMockNonAnonUserWithId( 1 ),
1501 new TitleValue( 0, 'SomeDbKey' )
1502 );
1503 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1504 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1505 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1506 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1507 }
1508
1509 public function testGetWatchedItem_cachedItem() {
1510 $mockDb = $this->getMockDb();
1511 $mockDb->expects( $this->never() )
1512 ->method( 'selectRow' );
1513
1514 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1515 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1516 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1517
1518 $mockCache = $this->getMockCache();
1519 $mockCache->expects( $this->never() )->method( 'delete' );
1520 $mockCache->expects( $this->never() )->method( 'set' );
1521 $mockCache->expects( $this->once() )
1522 ->method( 'get' )
1523 ->with(
1524 '0:SomeDbKey:1'
1525 )
1526 ->will( $this->returnValue( $cachedItem ) );
1527
1528 $store = $this->newWatchedItemStore(
1529 $this->getMockLBFactory( $mockDb ),
1530 $this->getMockJobQueueGroup(),
1531 $mockCache,
1532 $this->getMockReadOnlyMode()
1533 );
1534
1535 $this->assertEquals(
1536 $cachedItem,
1537 $store->getWatchedItem(
1538 $mockUser,
1539 $linkTarget
1540 )
1541 );
1542 }
1543
1544 public function testGetWatchedItem_noItem() {
1545 $mockDb = $this->getMockDb();
1546 $mockDb->expects( $this->once() )
1547 ->method( 'selectRow' )
1548 ->with(
1549 'watchlist',
1550 'wl_notificationtimestamp',
1551 [
1552 'wl_user' => 1,
1553 'wl_namespace' => 0,
1554 'wl_title' => 'SomeDbKey',
1555 ]
1556 )
1557 ->will( $this->returnValue( [] ) );
1558
1559 $mockCache = $this->getMockCache();
1560 $mockCache->expects( $this->never() )->method( 'set' );
1561 $mockCache->expects( $this->never() )->method( 'delete' );
1562 $mockCache->expects( $this->once() )
1563 ->method( 'get' )
1564 ->with( '0:SomeDbKey:1' )
1565 ->will( $this->returnValue( false ) );
1566
1567 $store = $this->newWatchedItemStore(
1568 $this->getMockLBFactory( $mockDb ),
1569 $this->getMockJobQueueGroup(),
1570 $mockCache,
1571 $this->getMockReadOnlyMode()
1572 );
1573
1574 $this->assertFalse(
1575 $store->getWatchedItem(
1576 $this->getMockNonAnonUserWithId( 1 ),
1577 new TitleValue( 0, 'SomeDbKey' )
1578 )
1579 );
1580 }
1581
1582 public function testGetWatchedItem_anonymousUser() {
1583 $mockDb = $this->getMockDb();
1584 $mockDb->expects( $this->never() )
1585 ->method( 'selectRow' );
1586
1587 $mockCache = $this->getMockCache();
1588 $mockCache->expects( $this->never() )->method( 'set' );
1589 $mockCache->expects( $this->never() )->method( 'get' );
1590 $mockCache->expects( $this->never() )->method( 'delete' );
1591
1592 $store = $this->newWatchedItemStore(
1593 $this->getMockLBFactory( $mockDb ),
1594 $this->getMockJobQueueGroup(),
1595 $mockCache,
1596 $this->getMockReadOnlyMode()
1597 );
1598
1599 $this->assertFalse(
1600 $store->getWatchedItem(
1601 $this->getAnonUser(),
1602 new TitleValue( 0, 'SomeDbKey' )
1603 )
1604 );
1605 }
1606
1607 public function testGetWatchedItemsForUser() {
1608 $mockDb = $this->getMockDb();
1609 $mockDb->expects( $this->once() )
1610 ->method( 'select' )
1611 ->with(
1612 'watchlist',
1613 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1614 [ 'wl_user' => 1 ]
1615 )
1616 ->will( $this->returnValue( [
1617 $this->getFakeRow( [
1618 'wl_namespace' => 0,
1619 'wl_title' => 'Foo1',
1620 'wl_notificationtimestamp' => '20151212010101',
1621 ] ),
1622 $this->getFakeRow( [
1623 'wl_namespace' => 1,
1624 'wl_title' => 'Foo2',
1625 'wl_notificationtimestamp' => null,
1626 ] ),
1627 ] ) );
1628
1629 $mockCache = $this->getMockCache();
1630 $mockCache->expects( $this->never() )->method( 'delete' );
1631 $mockCache->expects( $this->never() )->method( 'get' );
1632 $mockCache->expects( $this->never() )->method( 'set' );
1633
1634 $store = $this->newWatchedItemStore(
1635 $this->getMockLBFactory( $mockDb ),
1636 $this->getMockJobQueueGroup(),
1637 $mockCache,
1638 $this->getMockReadOnlyMode()
1639 );
1640 $user = $this->getMockNonAnonUserWithId( 1 );
1641
1642 $watchedItems = $store->getWatchedItemsForUser( $user );
1643
1644 $this->assertInternalType( 'array', $watchedItems );
1645 $this->assertCount( 2, $watchedItems );
1646 foreach ( $watchedItems as $watchedItem ) {
1647 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1648 }
1649 $this->assertEquals(
1650 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1651 $watchedItems[0]
1652 );
1653 $this->assertEquals(
1654 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1655 $watchedItems[1]
1656 );
1657 }
1658
1659 public function provideDbTypes() {
1660 return [
1661 [ false, DB_REPLICA ],
1662 [ true, DB_MASTER ],
1663 ];
1664 }
1665
1666 /**
1667 * @dataProvider provideDbTypes
1668 */
1669 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1670 $mockDb = $this->getMockDb();
1671 $mockCache = $this->getMockCache();
1672 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1673 $user = $this->getMockNonAnonUserWithId( 1 );
1674
1675 $mockDb->expects( $this->once() )
1676 ->method( 'select' )
1677 ->with(
1678 'watchlist',
1679 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1680 [ 'wl_user' => 1 ],
1681 $this->isType( 'string' ),
1682 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1683 )
1684 ->will( $this->returnValue( [] ) );
1685
1686 $store = $this->newWatchedItemStore(
1687 $mockLoadBalancer,
1688 $this->getMockJobQueueGroup(),
1689 $mockCache,
1690 $this->getMockReadOnlyMode()
1691 );
1692
1693 $watchedItems = $store->getWatchedItemsForUser(
1694 $user,
1695 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1696 );
1697 $this->assertEquals( [], $watchedItems );
1698 }
1699
1700 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1701 $store = $this->newWatchedItemStore(
1702 $this->getMockLBFactory( $this->getMockDb() ),
1703 $this->getMockJobQueueGroup(),
1704 $this->getMockCache(),
1705 $this->getMockReadOnlyMode()
1706 );
1707
1708 $this->setExpectedException( InvalidArgumentException::class );
1709 $store->getWatchedItemsForUser(
1710 $this->getMockNonAnonUserWithId( 1 ),
1711 [ 'sort' => 'foo' ]
1712 );
1713 }
1714
1715 public function testIsWatchedItem_existingItem() {
1716 $mockDb = $this->getMockDb();
1717 $mockDb->expects( $this->once() )
1718 ->method( 'selectRow' )
1719 ->with(
1720 'watchlist',
1721 'wl_notificationtimestamp',
1722 [
1723 'wl_user' => 1,
1724 'wl_namespace' => 0,
1725 'wl_title' => 'SomeDbKey',
1726 ]
1727 )
1728 ->will( $this->returnValue(
1729 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1730 ) );
1731
1732 $mockCache = $this->getMockCache();
1733 $mockCache->expects( $this->never() )->method( 'delete' );
1734 $mockCache->expects( $this->once() )
1735 ->method( 'get' )
1736 ->with( '0:SomeDbKey:1' )
1737 ->will( $this->returnValue( false ) );
1738 $mockCache->expects( $this->once() )
1739 ->method( 'set' )
1740 ->with(
1741 '0:SomeDbKey:1'
1742 );
1743
1744 $store = $this->newWatchedItemStore(
1745 $this->getMockLBFactory( $mockDb ),
1746 $this->getMockJobQueueGroup(),
1747 $mockCache,
1748 $this->getMockReadOnlyMode()
1749 );
1750
1751 $this->assertTrue(
1752 $store->isWatched(
1753 $this->getMockNonAnonUserWithId( 1 ),
1754 new TitleValue( 0, 'SomeDbKey' )
1755 )
1756 );
1757 }
1758
1759 public function testIsWatchedItem_noItem() {
1760 $mockDb = $this->getMockDb();
1761 $mockDb->expects( $this->once() )
1762 ->method( 'selectRow' )
1763 ->with(
1764 'watchlist',
1765 'wl_notificationtimestamp',
1766 [
1767 'wl_user' => 1,
1768 'wl_namespace' => 0,
1769 'wl_title' => 'SomeDbKey',
1770 ]
1771 )
1772 ->will( $this->returnValue( [] ) );
1773
1774 $mockCache = $this->getMockCache();
1775 $mockCache->expects( $this->never() )->method( 'set' );
1776 $mockCache->expects( $this->never() )->method( 'delete' );
1777 $mockCache->expects( $this->once() )
1778 ->method( 'get' )
1779 ->with( '0:SomeDbKey:1' )
1780 ->will( $this->returnValue( false ) );
1781
1782 $store = $this->newWatchedItemStore(
1783 $this->getMockLBFactory( $mockDb ),
1784 $this->getMockJobQueueGroup(),
1785 $mockCache,
1786 $this->getMockReadOnlyMode()
1787 );
1788
1789 $this->assertFalse(
1790 $store->isWatched(
1791 $this->getMockNonAnonUserWithId( 1 ),
1792 new TitleValue( 0, 'SomeDbKey' )
1793 )
1794 );
1795 }
1796
1797 public function testIsWatchedItem_anonymousUser() {
1798 $mockDb = $this->getMockDb();
1799 $mockDb->expects( $this->never() )
1800 ->method( 'selectRow' );
1801
1802 $mockCache = $this->getMockCache();
1803 $mockCache->expects( $this->never() )->method( 'set' );
1804 $mockCache->expects( $this->never() )->method( 'get' );
1805 $mockCache->expects( $this->never() )->method( 'delete' );
1806
1807 $store = $this->newWatchedItemStore(
1808 $this->getMockLBFactory( $mockDb ),
1809 $this->getMockJobQueueGroup(),
1810 $mockCache,
1811 $this->getMockReadOnlyMode()
1812 );
1813
1814 $this->assertFalse(
1815 $store->isWatched(
1816 $this->getAnonUser(),
1817 new TitleValue( 0, 'SomeDbKey' )
1818 )
1819 );
1820 }
1821
1822 public function testGetNotificationTimestampsBatch() {
1823 $targets = [
1824 new TitleValue( 0, 'SomeDbKey' ),
1825 new TitleValue( 1, 'AnotherDbKey' ),
1826 ];
1827
1828 $mockDb = $this->getMockDb();
1829 $dbResult = [
1830 $this->getFakeRow( [
1831 'wl_namespace' => '0',
1832 'wl_title' => 'SomeDbKey',
1833 'wl_notificationtimestamp' => '20151212010101',
1834 ] ),
1835 $this->getFakeRow(
1836 [
1837 'wl_namespace' => '1',
1838 'wl_title' => 'AnotherDbKey',
1839 'wl_notificationtimestamp' => null,
1840 ]
1841 ),
1842 ];
1843
1844 $mockDb->expects( $this->once() )
1845 ->method( 'makeWhereFrom2d' )
1846 ->with(
1847 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1848 $this->isType( 'string' ),
1849 $this->isType( 'string' )
1850 )
1851 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1852 $mockDb->expects( $this->once() )
1853 ->method( 'select' )
1854 ->with(
1855 'watchlist',
1856 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1857 [
1858 'makeWhereFrom2d return value',
1859 'wl_user' => 1
1860 ],
1861 $this->isType( 'string' )
1862 )
1863 ->will( $this->returnValue( $dbResult ) );
1864
1865 $mockCache = $this->getMockCache();
1866 $mockCache->expects( $this->exactly( 2 ) )
1867 ->method( 'get' )
1868 ->withConsecutive(
1869 [ '0:SomeDbKey:1' ],
1870 [ '1:AnotherDbKey:1' ]
1871 )
1872 ->will( $this->returnValue( null ) );
1873 $mockCache->expects( $this->never() )->method( 'set' );
1874 $mockCache->expects( $this->never() )->method( 'delete' );
1875
1876 $store = $this->newWatchedItemStore(
1877 $this->getMockLBFactory( $mockDb ),
1878 $this->getMockJobQueueGroup(),
1879 $mockCache,
1880 $this->getMockReadOnlyMode()
1881 );
1882
1883 $this->assertEquals(
1884 [
1885 0 => [ 'SomeDbKey' => '20151212010101', ],
1886 1 => [ 'AnotherDbKey' => null, ],
1887 ],
1888 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1889 );
1890 }
1891
1892 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1893 $targets = [
1894 new TitleValue( 0, 'OtherDbKey' ),
1895 ];
1896
1897 $mockDb = $this->getMockDb();
1898
1899 $mockDb->expects( $this->once() )
1900 ->method( 'makeWhereFrom2d' )
1901 ->with(
1902 [ [ 'OtherDbKey' => 1 ] ],
1903 $this->isType( 'string' ),
1904 $this->isType( 'string' )
1905 )
1906 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1907 $mockDb->expects( $this->once() )
1908 ->method( 'select' )
1909 ->with(
1910 'watchlist',
1911 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1912 [
1913 'makeWhereFrom2d return value',
1914 'wl_user' => 1
1915 ],
1916 $this->isType( 'string' )
1917 )
1918 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1919
1920 $mockCache = $this->getMockCache();
1921 $mockCache->expects( $this->once() )
1922 ->method( 'get' )
1923 ->with( '0:OtherDbKey:1' )
1924 ->will( $this->returnValue( null ) );
1925 $mockCache->expects( $this->never() )->method( 'set' );
1926 $mockCache->expects( $this->never() )->method( 'delete' );
1927
1928 $store = $this->newWatchedItemStore(
1929 $this->getMockLBFactory( $mockDb ),
1930 $this->getMockJobQueueGroup(),
1931 $mockCache,
1932 $this->getMockReadOnlyMode()
1933 );
1934
1935 $this->assertEquals(
1936 [
1937 0 => [ 'OtherDbKey' => false, ],
1938 ],
1939 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1940 );
1941 }
1942
1943 public function testGetNotificationTimestampsBatch_cachedItem() {
1944 $targets = [
1945 new TitleValue( 0, 'SomeDbKey' ),
1946 new TitleValue( 1, 'AnotherDbKey' ),
1947 ];
1948
1949 $user = $this->getMockNonAnonUserWithId( 1 );
1950 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1951
1952 $mockDb = $this->getMockDb();
1953
1954 $mockDb->expects( $this->once() )
1955 ->method( 'makeWhereFrom2d' )
1956 ->with(
1957 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1958 $this->isType( 'string' ),
1959 $this->isType( 'string' )
1960 )
1961 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1962 $mockDb->expects( $this->once() )
1963 ->method( 'select' )
1964 ->with(
1965 'watchlist',
1966 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1967 [
1968 'makeWhereFrom2d return value',
1969 'wl_user' => 1
1970 ],
1971 $this->isType( 'string' )
1972 )
1973 ->will( $this->returnValue( [
1974 $this->getFakeRow(
1975 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1976 )
1977 ] ) );
1978
1979 $mockCache = $this->getMockCache();
1980 $mockCache->expects( $this->at( 1 ) )
1981 ->method( 'get' )
1982 ->with( '0:SomeDbKey:1' )
1983 ->will( $this->returnValue( $cachedItem ) );
1984 $mockCache->expects( $this->at( 3 ) )
1985 ->method( 'get' )
1986 ->with( '1:AnotherDbKey:1' )
1987 ->will( $this->returnValue( null ) );
1988 $mockCache->expects( $this->never() )->method( 'set' );
1989 $mockCache->expects( $this->never() )->method( 'delete' );
1990
1991 $store = $this->newWatchedItemStore(
1992 $this->getMockLBFactory( $mockDb ),
1993 $this->getMockJobQueueGroup(),
1994 $mockCache,
1995 $this->getMockReadOnlyMode()
1996 );
1997
1998 $this->assertEquals(
1999 [
2000 0 => [ 'SomeDbKey' => '20151212010101', ],
2001 1 => [ 'AnotherDbKey' => null, ],
2002 ],
2003 $store->getNotificationTimestampsBatch( $user, $targets )
2004 );
2005 }
2006
2007 public function testGetNotificationTimestampsBatch_allItemsCached() {
2008 $targets = [
2009 new TitleValue( 0, 'SomeDbKey' ),
2010 new TitleValue( 1, 'AnotherDbKey' ),
2011 ];
2012
2013 $user = $this->getMockNonAnonUserWithId( 1 );
2014 $cachedItems = [
2015 new WatchedItem( $user, $targets[0], '20151212010101' ),
2016 new WatchedItem( $user, $targets[1], null ),
2017 ];
2018 $mockDb = $this->getMockDb();
2019 $mockDb->expects( $this->never() )->method( $this->anything() );
2020
2021 $mockCache = $this->getMockCache();
2022 $mockCache->expects( $this->at( 1 ) )
2023 ->method( 'get' )
2024 ->with( '0:SomeDbKey:1' )
2025 ->will( $this->returnValue( $cachedItems[0] ) );
2026 $mockCache->expects( $this->at( 3 ) )
2027 ->method( 'get' )
2028 ->with( '1:AnotherDbKey:1' )
2029 ->will( $this->returnValue( $cachedItems[1] ) );
2030 $mockCache->expects( $this->never() )->method( 'set' );
2031 $mockCache->expects( $this->never() )->method( 'delete' );
2032
2033 $store = $this->newWatchedItemStore(
2034 $this->getMockLBFactory( $mockDb ),
2035 $this->getMockJobQueueGroup(),
2036 $mockCache,
2037 $this->getMockReadOnlyMode()
2038 );
2039
2040 $this->assertEquals(
2041 [
2042 0 => [ 'SomeDbKey' => '20151212010101', ],
2043 1 => [ 'AnotherDbKey' => null, ],
2044 ],
2045 $store->getNotificationTimestampsBatch( $user, $targets )
2046 );
2047 }
2048
2049 public function testGetNotificationTimestampsBatch_anonymousUser() {
2050 $targets = [
2051 new TitleValue( 0, 'SomeDbKey' ),
2052 new TitleValue( 1, 'AnotherDbKey' ),
2053 ];
2054
2055 $mockDb = $this->getMockDb();
2056 $mockDb->expects( $this->never() )->method( $this->anything() );
2057
2058 $mockCache = $this->getMockCache();
2059 $mockCache->expects( $this->never() )->method( $this->anything() );
2060
2061 $store = $this->newWatchedItemStore(
2062 $this->getMockLBFactory( $mockDb ),
2063 $this->getMockJobQueueGroup(),
2064 $mockCache,
2065 $this->getMockReadOnlyMode()
2066 );
2067
2068 $this->assertEquals(
2069 [
2070 0 => [ 'SomeDbKey' => false, ],
2071 1 => [ 'AnotherDbKey' => false, ],
2072 ],
2073 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
2074 );
2075 }
2076
2077 public function testResetNotificationTimestamp_anonymousUser() {
2078 $mockDb = $this->getMockDb();
2079 $mockDb->expects( $this->never() )
2080 ->method( 'selectRow' );
2081
2082 $mockCache = $this->getMockCache();
2083 $mockCache->expects( $this->never() )->method( 'get' );
2084 $mockCache->expects( $this->never() )->method( 'set' );
2085 $mockCache->expects( $this->never() )->method( 'delete' );
2086
2087 $store = $this->newWatchedItemStore(
2088 $this->getMockLBFactory( $mockDb ),
2089 $this->getMockJobQueueGroup(),
2090 $mockCache,
2091 $this->getMockReadOnlyMode()
2092 );
2093
2094 $this->assertFalse(
2095 $store->resetNotificationTimestamp(
2096 $this->getAnonUser(),
2097 Title::newFromText( 'SomeDbKey' )
2098 )
2099 );
2100 }
2101
2102 public function testResetNotificationTimestamp_noItem() {
2103 $mockDb = $this->getMockDb();
2104 $mockDb->expects( $this->once() )
2105 ->method( 'selectRow' )
2106 ->with(
2107 'watchlist',
2108 'wl_notificationtimestamp',
2109 [
2110 'wl_user' => 1,
2111 'wl_namespace' => 0,
2112 'wl_title' => 'SomeDbKey',
2113 ]
2114 )
2115 ->will( $this->returnValue( [] ) );
2116
2117 $mockCache = $this->getMockCache();
2118 $mockCache->expects( $this->never() )->method( 'get' );
2119 $mockCache->expects( $this->never() )->method( 'set' );
2120 $mockCache->expects( $this->never() )->method( 'delete' );
2121
2122 $store = $this->newWatchedItemStore(
2123 $this->getMockLBFactory( $mockDb ),
2124 $this->getMockJobQueueGroup(),
2125 $mockCache,
2126 $this->getMockReadOnlyMode()
2127 );
2128
2129 $this->assertFalse(
2130 $store->resetNotificationTimestamp(
2131 $this->getMockNonAnonUserWithId( 1 ),
2132 Title::newFromText( 'SomeDbKey' )
2133 )
2134 );
2135 }
2136
2137 public function testResetNotificationTimestamp_item() {
2138 $user = $this->getMockNonAnonUserWithId( 1 );
2139 $title = Title::newFromText( 'SomeDbKey' );
2140
2141 $mockDb = $this->getMockDb();
2142 $mockDb->expects( $this->once() )
2143 ->method( 'selectRow' )
2144 ->with(
2145 'watchlist',
2146 'wl_notificationtimestamp',
2147 [
2148 'wl_user' => 1,
2149 'wl_namespace' => 0,
2150 'wl_title' => 'SomeDbKey',
2151 ]
2152 )
2153 ->will( $this->returnValue(
2154 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2155 ) );
2156
2157 $mockCache = $this->getMockCache();
2158 $mockCache->expects( $this->never() )->method( 'get' );
2159 $mockCache->expects( $this->once() )
2160 ->method( 'set' )
2161 ->with(
2162 '0:SomeDbKey:1',
2163 $this->isInstanceOf( WatchedItem::class )
2164 );
2165 $mockCache->expects( $this->once() )
2166 ->method( 'delete' )
2167 ->with( '0:SomeDbKey:1' );
2168
2169 $mockQueueGroup = $this->getMockJobQueueGroup();
2170 $mockQueueGroup->expects( $this->once() )
2171 ->method( 'lazyPush' )
2172 ->willReturnCallback( function ( ActivityUpdateJob $job ) {
2173 // don't run
2174 } );
2175
2176 $store = $this->newWatchedItemStore(
2177 $this->getMockLBFactory( $mockDb ),
2178 $mockQueueGroup,
2179 $mockCache,
2180 $this->getMockReadOnlyMode()
2181 );
2182
2183 $this->assertTrue(
2184 $store->resetNotificationTimestamp(
2185 $user,
2186 $title
2187 )
2188 );
2189 }
2190
2191 public function testResetNotificationTimestamp_noItemForced() {
2192 $user = $this->getMockNonAnonUserWithId( 1 );
2193 $title = Title::newFromText( 'SomeDbKey' );
2194
2195 $mockDb = $this->getMockDb();
2196 $mockDb->expects( $this->never() )
2197 ->method( 'selectRow' );
2198
2199 $mockCache = $this->getMockCache();
2200 $mockCache->expects( $this->never() )->method( 'get' );
2201 $mockCache->expects( $this->never() )->method( 'set' );
2202 $mockCache->expects( $this->once() )
2203 ->method( 'delete' )
2204 ->with( '0:SomeDbKey:1' );
2205
2206 $mockQueueGroup = $this->getMockJobQueueGroup();
2207 $store = $this->newWatchedItemStore(
2208 $this->getMockLBFactory( $mockDb ),
2209 $mockQueueGroup,
2210 $mockCache,
2211 $this->getMockReadOnlyMode()
2212 );
2213
2214 $mockQueueGroup->expects( $this->any() )
2215 ->method( 'lazyPush' )
2216 ->will( $this->returnCallback( function ( ActivityUpdateJob $job ) {
2217 // don't run
2218 } ) );
2219
2220 $this->assertTrue(
2221 $store->resetNotificationTimestamp(
2222 $user,
2223 $title,
2224 'force'
2225 )
2226 );
2227 }
2228
2229 /**
2230 * @param string $text
2231 * @param int $ns
2232 *
2233 * @return PHPUnit_Framework_MockObject_MockObject|Title
2234 */
2235 private function getMockTitle( $text, $ns = 0 ) {
2236 $title = $this->createMock( Title::class );
2237 $title->expects( $this->any() )
2238 ->method( 'getText' )
2239 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2240 $title->expects( $this->any() )
2241 ->method( 'getDbKey' )
2242 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2243 $title->expects( $this->any() )
2244 ->method( 'getNamespace' )
2245 ->will( $this->returnValue( $ns ) );
2246 return $title;
2247 }
2248
2249 private function verifyCallbackJob(
2250 ActivityUpdateJob $job,
2251 LinkTarget $expectedTitle,
2252 $expectedUserId,
2253 callable $notificationTimestampCondition
2254 ) {
2255 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2256 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2257
2258 $jobParams = $job->getParams();
2259 $this->assertArrayHasKey( 'type', $jobParams );
2260 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2261 $this->assertArrayHasKey( 'userid', $jobParams );
2262 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2263 $this->assertArrayHasKey( 'notifTime', $jobParams );
2264 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2265 }
2266
2267 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2268 $user = $this->getMockNonAnonUserWithId( 1 );
2269 $oldid = 22;
2270 $title = $this->getMockTitle( 'SomeTitle' );
2271 $title->expects( $this->once() )
2272 ->method( 'getNextRevisionID' )
2273 ->with( $oldid )
2274 ->will( $this->returnValue( false ) );
2275
2276 $mockDb = $this->getMockDb();
2277 $mockDb->expects( $this->never() )
2278 ->method( 'selectRow' );
2279
2280 $mockCache = $this->getMockCache();
2281 $mockCache->expects( $this->never() )->method( 'get' );
2282 $mockCache->expects( $this->never() )->method( 'set' );
2283 $mockCache->expects( $this->once() )
2284 ->method( 'delete' )
2285 ->with( '0:SomeTitle:1' );
2286
2287 $mockQueueGroup = $this->getMockJobQueueGroup();
2288 $store = $this->newWatchedItemStore(
2289 $this->getMockLBFactory( $mockDb ),
2290 $mockQueueGroup,
2291 $mockCache,
2292 $this->getMockReadOnlyMode()
2293 );
2294
2295 $mockQueueGroup->expects( $this->any() )
2296 ->method( 'lazyPush' )
2297 ->will( $this->returnCallback(
2298 function ( ActivityUpdateJob $job ) use ( $title, $user ) {
2299 $this->verifyCallbackJob(
2300 $job,
2301 $title,
2302 $user->getId(),
2303 function ( $time ) {
2304 return $time === null;
2305 }
2306 );
2307 }
2308 ) );
2309
2310 $this->assertTrue(
2311 $store->resetNotificationTimestamp(
2312 $user,
2313 $title,
2314 'force',
2315 $oldid
2316 )
2317 );
2318 }
2319
2320 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2321 $user = $this->getMockNonAnonUserWithId( 1 );
2322 $oldid = 22;
2323 $title = $this->getMockTitle( 'SomeDbKey' );
2324 $title->expects( $this->once() )
2325 ->method( 'getNextRevisionID' )
2326 ->with( $oldid )
2327 ->will( $this->returnValue( 33 ) );
2328
2329 $mockDb = $this->getMockDb();
2330 $mockDb->expects( $this->once() )
2331 ->method( 'selectRow' )
2332 ->with(
2333 'watchlist',
2334 'wl_notificationtimestamp',
2335 [
2336 'wl_user' => 1,
2337 'wl_namespace' => 0,
2338 'wl_title' => 'SomeDbKey',
2339 ]
2340 )
2341 ->will( $this->returnValue(
2342 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2343 ) );
2344
2345 $mockCache = $this->getMockCache();
2346 $mockCache->expects( $this->never() )->method( 'get' );
2347 $mockCache->expects( $this->once() )
2348 ->method( 'set' )
2349 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2350 $mockCache->expects( $this->once() )
2351 ->method( 'delete' )
2352 ->with( '0:SomeDbKey:1' );
2353
2354 $mockQueueGroup = $this->getMockJobQueueGroup();
2355 $store = $this->newWatchedItemStore(
2356 $this->getMockLBFactory( $mockDb ),
2357 $mockQueueGroup,
2358 $mockCache,
2359 $this->getMockReadOnlyMode()
2360 );
2361
2362 $mockQueueGroup->expects( $this->any() )
2363 ->method( 'lazyPush' )
2364 ->will( $this->returnCallback(
2365 function ( ActivityUpdateJob $job ) use ( $title, $user ) {
2366 $this->verifyCallbackJob(
2367 $job,
2368 $title,
2369 $user->getId(),
2370 function ( $time ) {
2371 return $time !== null && $time > '20151212010101';
2372 }
2373 );
2374 }
2375 ) );
2376
2377 $getTimestampCallCounter = 0;
2378 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2379 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2380 $getTimestampCallCounter++;
2381 $this->assertEquals( $title, $titleParam );
2382 $this->assertEquals( $oldid, $oldidParam );
2383 }
2384 );
2385
2386 $this->assertTrue(
2387 $store->resetNotificationTimestamp(
2388 $user,
2389 $title,
2390 'force',
2391 $oldid
2392 )
2393 );
2394 $this->assertEquals( 2, $getTimestampCallCounter );
2395
2396 ScopedCallback::consume( $scopedOverrideRevision );
2397 }
2398
2399 public function testResetNotificationTimestamp_notWatchedPageForced() {
2400 $user = $this->getMockNonAnonUserWithId( 1 );
2401 $oldid = 22;
2402 $title = $this->getMockTitle( 'SomeDbKey' );
2403 $title->expects( $this->once() )
2404 ->method( 'getNextRevisionID' )
2405 ->with( $oldid )
2406 ->will( $this->returnValue( 33 ) );
2407
2408 $mockDb = $this->getMockDb();
2409 $mockDb->expects( $this->once() )
2410 ->method( 'selectRow' )
2411 ->with(
2412 'watchlist',
2413 'wl_notificationtimestamp',
2414 [
2415 'wl_user' => 1,
2416 'wl_namespace' => 0,
2417 'wl_title' => 'SomeDbKey',
2418 ]
2419 )
2420 ->will( $this->returnValue( false ) );
2421
2422 $mockCache = $this->getMockCache();
2423 $mockCache->expects( $this->never() )->method( 'get' );
2424 $mockCache->expects( $this->never() )->method( 'set' );
2425 $mockCache->expects( $this->once() )
2426 ->method( 'delete' )
2427 ->with( '0:SomeDbKey:1' );
2428
2429 $mockQueueGroup = $this->getMockJobQueueGroup();
2430 $store = $this->newWatchedItemStore(
2431 $this->getMockLBFactory( $mockDb ),
2432 $mockQueueGroup,
2433 $mockCache,
2434 $this->getMockReadOnlyMode()
2435 );
2436
2437 $mockQueueGroup->expects( $this->any() )
2438 ->method( 'lazyPush' )
2439 ->will( $this->returnCallback(
2440 function ( ActivityUpdateJob $job ) use ( $title, $user ) {
2441 $this->verifyCallbackJob(
2442 $job,
2443 $title,
2444 $user->getId(),
2445 function ( $time ) {
2446 return $time === null;
2447 }
2448 );
2449 }
2450 ) );
2451
2452 $this->assertTrue(
2453 $store->resetNotificationTimestamp(
2454 $user,
2455 $title,
2456 'force',
2457 $oldid
2458 )
2459 );
2460 }
2461
2462 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2463 $user = $this->getMockNonAnonUserWithId( 1 );
2464 $oldid = 22;
2465 $title = $this->getMockTitle( 'SomeDbKey' );
2466 $title->expects( $this->once() )
2467 ->method( 'getNextRevisionID' )
2468 ->with( $oldid )
2469 ->will( $this->returnValue( 33 ) );
2470
2471 $mockDb = $this->getMockDb();
2472 $mockDb->expects( $this->once() )
2473 ->method( 'selectRow' )
2474 ->with(
2475 'watchlist',
2476 'wl_notificationtimestamp',
2477 [
2478 'wl_user' => 1,
2479 'wl_namespace' => 0,
2480 'wl_title' => 'SomeDbKey',
2481 ]
2482 )
2483 ->will( $this->returnValue(
2484 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2485 ) );
2486
2487 $mockCache = $this->getMockCache();
2488 $mockCache->expects( $this->never() )->method( 'get' );
2489 $mockCache->expects( $this->once() )
2490 ->method( 'set' )
2491 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2492 $mockCache->expects( $this->once() )
2493 ->method( 'delete' )
2494 ->with( '0:SomeDbKey:1' );
2495
2496 $mockQueueGroup = $this->getMockJobQueueGroup();
2497 $store = $this->newWatchedItemStore(
2498 $this->getMockLBFactory( $mockDb ),
2499 $mockQueueGroup,
2500 $mockCache,
2501 $this->getMockReadOnlyMode()
2502 );
2503
2504 $mockQueueGroup->expects( $this->any() )
2505 ->method( 'lazyPush' )
2506 ->will( $this->returnCallback(
2507 function ( ActivityUpdateJob $job ) use ( $title, $user ) {
2508 $this->verifyCallbackJob(
2509 $job,
2510 $title,
2511 $user->getId(),
2512 function ( $time ) {
2513 return $time === '30151212010101';
2514 }
2515 );
2516 }
2517 ) );
2518
2519 $getTimestampCallCounter = 0;
2520 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2521 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2522 $getTimestampCallCounter++;
2523 $this->assertEquals( $title, $titleParam );
2524 $this->assertEquals( $oldid, $oldidParam );
2525 }
2526 );
2527
2528 $this->assertTrue(
2529 $store->resetNotificationTimestamp(
2530 $user,
2531 $title,
2532 'force',
2533 $oldid
2534 )
2535 );
2536 $this->assertEquals( 2, $getTimestampCallCounter );
2537
2538 ScopedCallback::consume( $scopedOverrideRevision );
2539 }
2540
2541 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2542 $user = $this->getMockNonAnonUserWithId( 1 );
2543 $oldid = 22;
2544 $title = $this->getMockTitle( 'SomeDbKey' );
2545 $title->expects( $this->once() )
2546 ->method( 'getNextRevisionID' )
2547 ->with( $oldid )
2548 ->will( $this->returnValue( 33 ) );
2549
2550 $mockDb = $this->getMockDb();
2551 $mockDb->expects( $this->once() )
2552 ->method( 'selectRow' )
2553 ->with(
2554 'watchlist',
2555 'wl_notificationtimestamp',
2556 [
2557 'wl_user' => 1,
2558 'wl_namespace' => 0,
2559 'wl_title' => 'SomeDbKey',
2560 ]
2561 )
2562 ->will( $this->returnValue(
2563 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2564 ) );
2565
2566 $mockCache = $this->getMockCache();
2567 $mockCache->expects( $this->never() )->method( 'get' );
2568 $mockCache->expects( $this->once() )
2569 ->method( 'set' )
2570 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2571 $mockCache->expects( $this->once() )
2572 ->method( 'delete' )
2573 ->with( '0:SomeDbKey:1' );
2574
2575 $mockQueueGroup = $this->getMockJobQueueGroup();
2576 $store = $this->newWatchedItemStore(
2577 $this->getMockLBFactory( $mockDb ),
2578 $mockQueueGroup,
2579 $mockCache,
2580 $this->getMockReadOnlyMode()
2581 );
2582
2583 $mockQueueGroup->expects( $this->any() )
2584 ->method( 'lazyPush' )
2585 ->will( $this->returnCallback(
2586 function ( ActivityUpdateJob $job ) use ( $title, $user ) {
2587 $this->verifyCallbackJob(
2588 $job,
2589 $title,
2590 $user->getId(),
2591 function ( $time ) {
2592 return $time === false;
2593 }
2594 );
2595 }
2596 ) );
2597
2598 $getTimestampCallCounter = 0;
2599 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2600 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2601 $getTimestampCallCounter++;
2602 $this->assertEquals( $title, $titleParam );
2603 $this->assertEquals( $oldid, $oldidParam );
2604 }
2605 );
2606
2607 $this->assertTrue(
2608 $store->resetNotificationTimestamp(
2609 $user,
2610 $title,
2611 '',
2612 $oldid
2613 )
2614 );
2615 $this->assertEquals( 2, $getTimestampCallCounter );
2616
2617 ScopedCallback::consume( $scopedOverrideRevision );
2618 }
2619
2620 public function testSetNotificationTimestampsForUser_anonUser() {
2621 $store = $this->newWatchedItemStore(
2622 $this->getMockLBFactory( $this->getMockDb() ),
2623 $this->getMockJobQueueGroup(),
2624 $this->getMockCache(),
2625 $this->getMockReadOnlyMode()
2626 );
2627 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2628 }
2629
2630 public function testSetNotificationTimestampsForUser_allRows() {
2631 $user = $this->getMockNonAnonUserWithId( 1 );
2632 $timestamp = '20100101010101';
2633
2634 $store = $this->newWatchedItemStore(
2635 $this->getMockLBFactory( $this->getMockDb() ),
2636 $this->getMockJobQueueGroup(),
2637 $this->getMockCache(),
2638 $this->getMockReadOnlyMode()
2639 );
2640
2641 // Note: This does not actually assert the job is correct
2642 $callableCallCounter = 0;
2643 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2644 $callableCallCounter++;
2645 $this->assertInternalType( 'callable', $callable );
2646 };
2647 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2648
2649 $this->assertTrue(
2650 $store->setNotificationTimestampsForUser( $user, $timestamp )
2651 );
2652 $this->assertEquals( 1, $callableCallCounter );
2653 }
2654
2655 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2656 $user = $this->getMockNonAnonUserWithId( 1 );
2657 $timestamp = null;
2658
2659 $store = $this->newWatchedItemStore(
2660 $this->getMockLBFactory( $this->getMockDb() ),
2661 $this->getMockJobQueueGroup(),
2662 $this->getMockCache(),
2663 $this->getMockReadOnlyMode()
2664 );
2665
2666 // Note: This does not actually assert the job is correct
2667 $callableCallCounter = 0;
2668 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2669 $callableCallCounter++;
2670 $this->assertInternalType( 'callable', $callable );
2671 };
2672 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2673
2674 $this->assertTrue(
2675 $store->setNotificationTimestampsForUser( $user, $timestamp )
2676 );
2677 }
2678
2679 public function testSetNotificationTimestampsForUser_specificTargets() {
2680 $user = $this->getMockNonAnonUserWithId( 1 );
2681 $timestamp = '20100101010101';
2682 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2683
2684 $mockDb = $this->getMockDb();
2685 $mockDb->expects( $this->once() )
2686 ->method( 'update' )
2687 ->with(
2688 'watchlist',
2689 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2690 [ 'wl_user' => 1, 'wl_namespace' => 0, 'wl_title' => [ 'Foo', 'Bar' ] ]
2691 )
2692 ->will( $this->returnValue( true ) );
2693 $mockDb->expects( $this->exactly( 1 ) )
2694 ->method( 'timestamp' )
2695 ->will( $this->returnCallback( function ( $value ) {
2696 return 'TS' . $value . 'TS';
2697 } ) );
2698 $mockDb->expects( $this->once() )
2699 ->method( 'affectedRows' )
2700 ->will( $this->returnValue( 2 ) );
2701
2702 $store = $this->newWatchedItemStore(
2703 $this->getMockLBFactory( $mockDb ),
2704 $this->getMockJobQueueGroup(),
2705 $this->getMockCache(),
2706 $this->getMockReadOnlyMode()
2707 );
2708
2709 $this->assertTrue(
2710 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2711 );
2712 }
2713
2714 public function testUpdateNotificationTimestamp_watchersExist() {
2715 $mockDb = $this->getMockDb();
2716 $mockDb->expects( $this->once() )
2717 ->method( 'selectFieldValues' )
2718 ->with(
2719 'watchlist',
2720 'wl_user',
2721 [
2722 'wl_user != 1',
2723 'wl_namespace' => 0,
2724 'wl_title' => 'SomeDbKey',
2725 'wl_notificationtimestamp IS NULL'
2726 ]
2727 )
2728 ->will( $this->returnValue( [ '2', '3' ] ) );
2729 $mockDb->expects( $this->once() )
2730 ->method( 'update' )
2731 ->with(
2732 'watchlist',
2733 [ 'wl_notificationtimestamp' => null ],
2734 [
2735 'wl_user' => [ 2, 3 ],
2736 'wl_namespace' => 0,
2737 'wl_title' => 'SomeDbKey',
2738 ]
2739 );
2740
2741 $mockCache = $this->getMockCache();
2742 $mockCache->expects( $this->never() )->method( 'set' );
2743 $mockCache->expects( $this->never() )->method( 'get' );
2744 $mockCache->expects( $this->never() )->method( 'delete' );
2745
2746 $store = $this->newWatchedItemStore(
2747 $this->getMockLBFactory( $mockDb ),
2748 $this->getMockJobQueueGroup(),
2749 $mockCache,
2750 $this->getMockReadOnlyMode()
2751 );
2752
2753 $this->assertEquals(
2754 [ 2, 3 ],
2755 $store->updateNotificationTimestamp(
2756 $this->getMockNonAnonUserWithId( 1 ),
2757 new TitleValue( 0, 'SomeDbKey' ),
2758 '20151212010101'
2759 )
2760 );
2761 }
2762
2763 public function testUpdateNotificationTimestamp_noWatchers() {
2764 $mockDb = $this->getMockDb();
2765 $mockDb->expects( $this->once() )
2766 ->method( 'selectFieldValues' )
2767 ->with(
2768 'watchlist',
2769 'wl_user',
2770 [
2771 'wl_user != 1',
2772 'wl_namespace' => 0,
2773 'wl_title' => 'SomeDbKey',
2774 'wl_notificationtimestamp IS NULL'
2775 ]
2776 )
2777 ->will(
2778 $this->returnValue( [] )
2779 );
2780 $mockDb->expects( $this->never() )
2781 ->method( 'update' );
2782
2783 $mockCache = $this->getMockCache();
2784 $mockCache->expects( $this->never() )->method( 'set' );
2785 $mockCache->expects( $this->never() )->method( 'get' );
2786 $mockCache->expects( $this->never() )->method( 'delete' );
2787
2788 $store = $this->newWatchedItemStore(
2789 $this->getMockLBFactory( $mockDb ),
2790 $this->getMockJobQueueGroup(),
2791 $mockCache,
2792 $this->getMockReadOnlyMode()
2793 );
2794
2795 $watchers = $store->updateNotificationTimestamp(
2796 $this->getMockNonAnonUserWithId( 1 ),
2797 new TitleValue( 0, 'SomeDbKey' ),
2798 '20151212010101'
2799 );
2800 $this->assertInternalType( 'array', $watchers );
2801 $this->assertEmpty( $watchers );
2802 }
2803
2804 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2805 $user = $this->getMockNonAnonUserWithId( 1 );
2806 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2807
2808 $mockDb = $this->getMockDb();
2809 $mockDb->expects( $this->once() )
2810 ->method( 'selectRow' )
2811 ->will( $this->returnValue(
2812 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2813 ) );
2814 $mockDb->expects( $this->once() )
2815 ->method( 'selectFieldValues' )
2816 ->will(
2817 $this->returnValue( [ '2', '3' ] )
2818 );
2819 $mockDb->expects( $this->once() )
2820 ->method( 'update' );
2821
2822 $mockCache = $this->getMockCache();
2823 $mockCache->expects( $this->once() )
2824 ->method( 'set' )
2825 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2826 $mockCache->expects( $this->once() )
2827 ->method( 'get' )
2828 ->with( '0:SomeDbKey:1' );
2829 $mockCache->expects( $this->once() )
2830 ->method( 'delete' )
2831 ->with( '0:SomeDbKey:1' );
2832
2833 $store = $this->newWatchedItemStore(
2834 $this->getMockLBFactory( $mockDb ),
2835 $this->getMockJobQueueGroup(),
2836 $mockCache,
2837 $this->getMockReadOnlyMode()
2838 );
2839
2840 // This will add the item to the cache
2841 $store->getWatchedItem( $user, $titleValue );
2842
2843 $store->updateNotificationTimestamp(
2844 $this->getMockNonAnonUserWithId( 1 ),
2845 $titleValue,
2846 '20151212010101'
2847 );
2848 }
2849
2850 }