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