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