Merge "Move action filter logic to LogPager"
[lhc/web/wiklou.git] / tests / phpunit / includes / WatchedItemStoreUnitTest.php
1 <?php
2 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
3
4 /**
5 * @author Addshore
6 *
7 * @covers WatchedItemStore
8 */
9 class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
10
11 /**
12 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
13 */
14 private function getMockDb() {
15 return $this->getMock( IDatabase::class );
16 }
17
18 /**
19 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
20 */
21 private function getMockLoadBalancer( $mockDb, $expectedConnectionType = null ) {
22 $mock = $this->getMockBuilder( LoadBalancer::class )
23 ->disableOriginalConstructor()
24 ->getMock();
25 if ( $expectedConnectionType !== null ) {
26 $mock->expects( $this->any() )
27 ->method( 'getConnection' )
28 ->with( $expectedConnectionType )
29 ->will( $this->returnValue( $mockDb ) );
30 } else {
31 $mock->expects( $this->any() )
32 ->method( 'getConnection' )
33 ->will( $this->returnValue( $mockDb ) );
34 }
35 $mock->expects( $this->any() )
36 ->method( 'getReadOnlyReason' )
37 ->will( $this->returnValue( false ) );
38 return $mock;
39 }
40
41 /**
42 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
43 */
44 private function getMockCache() {
45 $mock = $this->getMockBuilder( HashBagOStuff::class )
46 ->disableOriginalConstructor()
47 ->getMock();
48 $mock->expects( $this->any() )
49 ->method( 'makeKey' )
50 ->will( $this->returnCallback( function() {
51 return implode( ':', func_get_args() );
52 } ) );
53 return $mock;
54 }
55
56 /**
57 * @param int $id
58 * @return PHPUnit_Framework_MockObject_MockObject|User
59 */
60 private function getMockNonAnonUserWithId( $id ) {
61 $mock = $this->getMock( User::class );
62 $mock->expects( $this->any() )
63 ->method( 'isAnon' )
64 ->will( $this->returnValue( false ) );
65 $mock->expects( $this->any() )
66 ->method( 'getId' )
67 ->will( $this->returnValue( $id ) );
68 return $mock;
69 }
70
71 /**
72 * @return User
73 */
74 private function getAnonUser() {
75 return User::newFromName( 'Anon_User' );
76 }
77
78 private function getFakeRow( array $rowValues ) {
79 $fakeRow = new stdClass();
80 foreach ( $rowValues as $valueName => $value ) {
81 $fakeRow->$valueName = $value;
82 }
83 return $fakeRow;
84 }
85
86 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache ) {
87 return new WatchedItemStore(
88 $loadBalancer,
89 $cache
90 );
91 }
92
93 public function testGetDefaultInstance() {
94 $instanceOne = WatchedItemStore::getDefaultInstance();
95 $instanceTwo = WatchedItemStore::getDefaultInstance();
96 $this->assertSame( $instanceOne, $instanceTwo );
97 }
98
99 public function testOverrideDefaultInstance() {
100 $instance = WatchedItemStore::getDefaultInstance();
101 $scopedOverride = $instance->overrideDefaultInstance( null );
102
103 $this->assertNotSame( $instance, WatchedItemStore::getDefaultInstance() );
104
105 unset( $scopedOverride );
106
107 $this->assertSame( $instance, WatchedItemStore::getDefaultInstance() );
108 }
109
110 public function testCountWatchedItems() {
111 $user = $this->getMockNonAnonUserWithId( 1 );
112
113 $mockDb = $this->getMockDb();
114 $mockDb->expects( $this->exactly( 1 ) )
115 ->method( 'selectField' )
116 ->with(
117 'watchlist',
118 'COUNT(*)',
119 [
120 'wl_user' => $user->getId(),
121 ],
122 $this->isType( 'string' )
123 )
124 ->will( $this->returnValue( 12 ) );
125
126 $mockCache = $this->getMockCache();
127 $mockCache->expects( $this->never() )->method( 'get' );
128 $mockCache->expects( $this->never() )->method( 'set' );
129 $mockCache->expects( $this->never() )->method( 'delete' );
130
131 $store = $this->newWatchedItemStore(
132 $this->getMockLoadBalancer( $mockDb ),
133 $mockCache
134 );
135
136 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
137 }
138
139 public function testCountWatchers() {
140 $titleValue = new TitleValue( 0, 'SomeDbKey' );
141
142 $mockDb = $this->getMockDb();
143 $mockDb->expects( $this->exactly( 1 ) )
144 ->method( 'selectField' )
145 ->with(
146 'watchlist',
147 'COUNT(*)',
148 [
149 'wl_namespace' => $titleValue->getNamespace(),
150 'wl_title' => $titleValue->getDBkey(),
151 ],
152 $this->isType( 'string' )
153 )
154 ->will( $this->returnValue( 7 ) );
155
156 $mockCache = $this->getMockCache();
157 $mockCache->expects( $this->never() )->method( 'get' );
158 $mockCache->expects( $this->never() )->method( 'set' );
159 $mockCache->expects( $this->never() )->method( 'delete' );
160
161 $store = $this->newWatchedItemStore(
162 $this->getMockLoadBalancer( $mockDb ),
163 $mockCache
164 );
165
166 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
167 }
168
169 public function testCountWatchersMultiple() {
170 $titleValues = [
171 new TitleValue( 0, 'SomeDbKey' ),
172 new TitleValue( 0, 'OtherDbKey' ),
173 new TitleValue( 1, 'AnotherDbKey' ),
174 ];
175
176 $mockDb = $this->getMockDb();
177
178 $dbResult = [
179 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
180 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
181 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
182 ),
183 ];
184 $mockDb->expects( $this->once() )
185 ->method( 'makeWhereFrom2d' )
186 ->with(
187 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
188 $this->isType( 'string' ),
189 $this->isType( 'string' )
190 )
191 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
192 $mockDb->expects( $this->once() )
193 ->method( 'select' )
194 ->with(
195 'watchlist',
196 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
197 [ 'makeWhereFrom2d return value' ],
198 $this->isType( 'string' ),
199 [
200 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
201 ]
202 )
203 ->will(
204 $this->returnValue( $dbResult )
205 );
206
207 $mockCache = $this->getMockCache();
208 $mockCache->expects( $this->never() )->method( 'get' );
209 $mockCache->expects( $this->never() )->method( 'set' );
210 $mockCache->expects( $this->never() )->method( 'delete' );
211
212 $store = $this->newWatchedItemStore(
213 $this->getMockLoadBalancer( $mockDb ),
214 $mockCache
215 );
216
217 $expected = [
218 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
219 1 => [ 'AnotherDbKey' => 500 ],
220 ];
221 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
222 }
223
224 public function provideIntWithDbUnsafeVersion() {
225 return [
226 [ 50 ],
227 [ "50; DROP TABLE watchlist;\n--" ],
228 ];
229 }
230
231 /**
232 * @dataProvider provideIntWithDbUnsafeVersion
233 */
234 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
235 $titleValues = [
236 new TitleValue( 0, 'SomeDbKey' ),
237 new TitleValue( 0, 'OtherDbKey' ),
238 new TitleValue( 1, 'AnotherDbKey' ),
239 ];
240
241 $mockDb = $this->getMockDb();
242
243 $dbResult = [
244 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
245 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
246 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
247 ),
248 ];
249 $mockDb->expects( $this->once() )
250 ->method( 'makeWhereFrom2d' )
251 ->with(
252 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
253 $this->isType( 'string' ),
254 $this->isType( 'string' )
255 )
256 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
257 $mockDb->expects( $this->once() )
258 ->method( 'select' )
259 ->with(
260 'watchlist',
261 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
262 [ 'makeWhereFrom2d return value' ],
263 $this->isType( 'string' ),
264 [
265 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
266 'HAVING' => 'COUNT(*) >= 50',
267 ]
268 )
269 ->will(
270 $this->returnValue( $dbResult )
271 );
272
273 $mockCache = $this->getMockCache();
274 $mockCache->expects( $this->never() )->method( 'get' );
275 $mockCache->expects( $this->never() )->method( 'set' );
276 $mockCache->expects( $this->never() )->method( 'delete' );
277
278 $store = $this->newWatchedItemStore(
279 $this->getMockLoadBalancer( $mockDb ),
280 $mockCache
281 );
282
283 $expected = [
284 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
285 1 => [ 'AnotherDbKey' => 500 ],
286 ];
287 $this->assertEquals(
288 $expected,
289 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
290 );
291 }
292
293 public function testCountVisitingWatchers() {
294 $titleValue = new TitleValue( 0, 'SomeDbKey' );
295
296 $mockDb = $this->getMockDb();
297 $mockDb->expects( $this->exactly( 1 ) )
298 ->method( 'selectField' )
299 ->with(
300 'watchlist',
301 'COUNT(*)',
302 [
303 'wl_namespace' => $titleValue->getNamespace(),
304 'wl_title' => $titleValue->getDBkey(),
305 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
306 ],
307 $this->isType( 'string' )
308 )
309 ->will( $this->returnValue( 7 ) );
310 $mockDb->expects( $this->exactly( 1 ) )
311 ->method( 'addQuotes' )
312 ->will( $this->returnCallback( function( $value ) {
313 return "'$value'";
314 } ) );
315 $mockDb->expects( $this->exactly( 1 ) )
316 ->method( 'timestamp' )
317 ->will( $this->returnCallback( function( $value ) {
318 return 'TS' . $value . 'TS';
319 } ) );
320
321 $mockCache = $this->getMockCache();
322 $mockCache->expects( $this->never() )->method( 'set' );
323 $mockCache->expects( $this->never() )->method( 'get' );
324 $mockCache->expects( $this->never() )->method( 'delete' );
325
326 $store = $this->newWatchedItemStore(
327 $this->getMockLoadBalancer( $mockDb ),
328 $mockCache
329 );
330
331 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
332 }
333
334 public function testCountVisitingWatchersMultiple() {
335 $titleValuesWithThresholds = [
336 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
337 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
338 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
339 ];
340
341 $dbResult = [
342 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
343 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
344 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
345 ];
346 $mockDb = $this->getMockDb();
347 $mockDb->expects( $this->exactly( 2 * 3 ) )
348 ->method( 'addQuotes' )
349 ->will( $this->returnCallback( function( $value ) {
350 return "'$value'";
351 } ) );
352 $mockDb->expects( $this->exactly( 3 ) )
353 ->method( 'timestamp' )
354 ->will( $this->returnCallback( function( $value ) {
355 return 'TS' . $value . 'TS';
356 } ) );
357 $mockDb->expects( $this->any() )
358 ->method( 'makeList' )
359 ->with(
360 $this->isType( 'array' ),
361 $this->isType( 'int' )
362 )
363 ->will( $this->returnCallback( function( $a, $conj ) {
364 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
365 return join( $sqlConj, array_map( function( $s ) {
366 return '(' . $s . ')';
367 }, $a
368 ) );
369 } ) );
370 $mockDb->expects( $this->never() )
371 ->method( 'makeWhereFrom2d' );
372
373 $expectedCond =
374 '((wl_namespace = 0) AND (' .
375 "(((wl_title = 'SomeDbKey') AND (" .
376 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
377 ')) OR (' .
378 "(wl_title = 'OtherDbKey') AND (" .
379 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
380 '))))' .
381 ') OR ((wl_namespace = 1) AND (' .
382 "(((wl_title = 'AnotherDbKey') AND (".
383 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
384 ')))))';
385 $mockDb->expects( $this->once() )
386 ->method( 'select' )
387 ->with(
388 'watchlist',
389 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
390 $expectedCond,
391 $this->isType( 'string' ),
392 [
393 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
394 ]
395 )
396 ->will(
397 $this->returnValue( $dbResult )
398 );
399
400 $mockCache = $this->getMockCache();
401 $mockCache->expects( $this->never() )->method( 'get' );
402 $mockCache->expects( $this->never() )->method( 'set' );
403 $mockCache->expects( $this->never() )->method( 'delete' );
404
405 $store = $this->newWatchedItemStore(
406 $this->getMockLoadBalancer( $mockDb ),
407 $mockCache
408 );
409
410 $expected = [
411 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
412 1 => [ 'AnotherDbKey' => 500 ],
413 ];
414 $this->assertEquals(
415 $expected,
416 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
417 );
418 }
419
420 public function testCountVisitingWatchersMultiple_withMissingTargets() {
421 $titleValuesWithThresholds = [
422 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
423 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
424 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
425 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
426 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
427 ];
428
429 $dbResult = [
430 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
431 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
432 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
433 $this->getFakeRow(
434 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
435 ),
436 $this->getFakeRow(
437 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
438 ),
439 ];
440 $mockDb = $this->getMockDb();
441 $mockDb->expects( $this->exactly( 2 * 3 ) )
442 ->method( 'addQuotes' )
443 ->will( $this->returnCallback( function( $value ) {
444 return "'$value'";
445 } ) );
446 $mockDb->expects( $this->exactly( 3 ) )
447 ->method( 'timestamp' )
448 ->will( $this->returnCallback( function( $value ) {
449 return 'TS' . $value . 'TS';
450 } ) );
451 $mockDb->expects( $this->any() )
452 ->method( 'makeList' )
453 ->with(
454 $this->isType( 'array' ),
455 $this->isType( 'int' )
456 )
457 ->will( $this->returnCallback( function( $a, $conj ) {
458 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
459 return join( $sqlConj, array_map( function( $s ) {
460 return '(' . $s . ')';
461 }, $a
462 ) );
463 } ) );
464 $mockDb->expects( $this->once() )
465 ->method( 'makeWhereFrom2d' )
466 ->with(
467 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
468 $this->isType( 'string' ),
469 $this->isType( 'string' )
470 )
471 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
472
473 $expectedCond =
474 '((wl_namespace = 0) AND (' .
475 "(((wl_title = 'SomeDbKey') AND (" .
476 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
477 ')) OR (' .
478 "(wl_title = 'OtherDbKey') AND (" .
479 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
480 '))))' .
481 ') OR ((wl_namespace = 1) AND (' .
482 "(((wl_title = 'AnotherDbKey') AND (".
483 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
484 '))))' .
485 ') OR ' .
486 '(makeWhereFrom2d return value)';
487 $mockDb->expects( $this->once() )
488 ->method( 'select' )
489 ->with(
490 'watchlist',
491 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
492 $expectedCond,
493 $this->isType( 'string' ),
494 [
495 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
496 ]
497 )
498 ->will(
499 $this->returnValue( $dbResult )
500 );
501
502 $mockCache = $this->getMockCache();
503 $mockCache->expects( $this->never() )->method( 'get' );
504 $mockCache->expects( $this->never() )->method( 'set' );
505 $mockCache->expects( $this->never() )->method( 'delete' );
506
507 $store = $this->newWatchedItemStore(
508 $this->getMockLoadBalancer( $mockDb ),
509 $mockCache
510 );
511
512 $expected = [
513 0 => [
514 'SomeDbKey' => 100, 'OtherDbKey' => 300,
515 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
516 ],
517 1 => [ 'AnotherDbKey' => 500 ],
518 ];
519 $this->assertEquals(
520 $expected,
521 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
522 );
523 }
524
525 /**
526 * @dataProvider provideIntWithDbUnsafeVersion
527 */
528 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
529 $titleValuesWithThresholds = [
530 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
531 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
532 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
533 ];
534
535 $mockDb = $this->getMockDb();
536 $mockDb->expects( $this->any() )
537 ->method( 'makeList' )
538 ->will( $this->returnValue( 'makeList return value' ) );
539 $mockDb->expects( $this->once() )
540 ->method( 'select' )
541 ->with(
542 'watchlist',
543 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
544 'makeList return value',
545 $this->isType( 'string' ),
546 [
547 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
548 'HAVING' => 'COUNT(*) >= 50',
549 ]
550 )
551 ->will(
552 $this->returnValue( [] )
553 );
554
555 $mockCache = $this->getMockCache();
556 $mockCache->expects( $this->never() )->method( 'get' );
557 $mockCache->expects( $this->never() )->method( 'set' );
558 $mockCache->expects( $this->never() )->method( 'delete' );
559
560 $store = $this->newWatchedItemStore(
561 $this->getMockLoadBalancer( $mockDb ),
562 $mockCache
563 );
564
565 $expected = [
566 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
567 1 => [ 'AnotherDbKey' => 0 ],
568 ];
569 $this->assertEquals(
570 $expected,
571 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
572 );
573 }
574
575 public function testCountUnreadNotifications() {
576 $user = $this->getMockNonAnonUserWithId( 1 );
577
578 $mockDb = $this->getMockDb();
579 $mockDb->expects( $this->exactly( 1 ) )
580 ->method( 'selectRowCount' )
581 ->with(
582 'watchlist',
583 '1',
584 [
585 "wl_notificationtimestamp IS NOT NULL",
586 'wl_user' => 1,
587 ],
588 $this->isType( 'string' )
589 )
590 ->will( $this->returnValue( 9 ) );
591
592 $mockCache = $this->getMockCache();
593 $mockCache->expects( $this->never() )->method( 'set' );
594 $mockCache->expects( $this->never() )->method( 'get' );
595 $mockCache->expects( $this->never() )->method( 'delete' );
596
597 $store = $this->newWatchedItemStore(
598 $this->getMockLoadBalancer( $mockDb ),
599 $mockCache
600 );
601
602 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
603 }
604
605 /**
606 * @dataProvider provideIntWithDbUnsafeVersion
607 */
608 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
609 $user = $this->getMockNonAnonUserWithId( 1 );
610
611 $mockDb = $this->getMockDb();
612 $mockDb->expects( $this->exactly( 1 ) )
613 ->method( 'selectRowCount' )
614 ->with(
615 'watchlist',
616 '1',
617 [
618 "wl_notificationtimestamp IS NOT NULL",
619 'wl_user' => 1,
620 ],
621 $this->isType( 'string' ),
622 [ 'LIMIT' => 50 ]
623 )
624 ->will( $this->returnValue( 50 ) );
625
626 $mockCache = $this->getMockCache();
627 $mockCache->expects( $this->never() )->method( 'set' );
628 $mockCache->expects( $this->never() )->method( 'get' );
629 $mockCache->expects( $this->never() )->method( 'delete' );
630
631 $store = $this->newWatchedItemStore(
632 $this->getMockLoadBalancer( $mockDb ),
633 $mockCache
634 );
635
636 $this->assertSame(
637 true,
638 $store->countUnreadNotifications( $user, $limit )
639 );
640 }
641
642 /**
643 * @dataProvider provideIntWithDbUnsafeVersion
644 */
645 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
646 $user = $this->getMockNonAnonUserWithId( 1 );
647
648 $mockDb = $this->getMockDb();
649 $mockDb->expects( $this->exactly( 1 ) )
650 ->method( 'selectRowCount' )
651 ->with(
652 'watchlist',
653 '1',
654 [
655 "wl_notificationtimestamp IS NOT NULL",
656 'wl_user' => 1,
657 ],
658 $this->isType( 'string' ),
659 [ 'LIMIT' => 50 ]
660 )
661 ->will( $this->returnValue( 9 ) );
662
663 $mockCache = $this->getMockCache();
664 $mockCache->expects( $this->never() )->method( 'set' );
665 $mockCache->expects( $this->never() )->method( 'get' );
666 $mockCache->expects( $this->never() )->method( 'delete' );
667
668 $store = $this->newWatchedItemStore(
669 $this->getMockLoadBalancer( $mockDb ),
670 $mockCache
671 );
672
673 $this->assertEquals(
674 9,
675 $store->countUnreadNotifications( $user, $limit )
676 );
677 }
678
679 public function testDuplicateEntry_nothingToDuplicate() {
680 $mockDb = $this->getMockDb();
681 $mockDb->expects( $this->once() )
682 ->method( 'select' )
683 ->with(
684 'watchlist',
685 [
686 'wl_user',
687 'wl_notificationtimestamp',
688 ],
689 [
690 'wl_namespace' => 0,
691 'wl_title' => 'Old_Title',
692 ],
693 'WatchedItemStore::duplicateEntry',
694 [ 'FOR UPDATE' ]
695 )
696 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
697
698 $store = $this->newWatchedItemStore(
699 $this->getMockLoadBalancer( $mockDb ),
700 $this->getMockCache()
701 );
702
703 $store->duplicateEntry(
704 Title::newFromText( 'Old_Title' ),
705 Title::newFromText( 'New_Title' )
706 );
707 }
708
709 public function testDuplicateEntry_somethingToDuplicate() {
710 $fakeRows = [
711 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
712 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
713 ];
714
715 $mockDb = $this->getMockDb();
716 $mockDb->expects( $this->at( 0 ) )
717 ->method( 'select' )
718 ->with(
719 'watchlist',
720 [
721 'wl_user',
722 'wl_notificationtimestamp',
723 ],
724 [
725 'wl_namespace' => 0,
726 'wl_title' => 'Old_Title',
727 ]
728 )
729 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
730 $mockDb->expects( $this->at( 1 ) )
731 ->method( 'replace' )
732 ->with(
733 'watchlist',
734 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
735 [
736 [
737 'wl_user' => 1,
738 'wl_namespace' => 0,
739 'wl_title' => 'New_Title',
740 'wl_notificationtimestamp' => '20151212010101',
741 ],
742 [
743 'wl_user' => 2,
744 'wl_namespace' => 0,
745 'wl_title' => 'New_Title',
746 'wl_notificationtimestamp' => null,
747 ],
748 ],
749 $this->isType( 'string' )
750 );
751
752 $mockCache = $this->getMockCache();
753 $mockCache->expects( $this->never() )->method( 'get' );
754 $mockCache->expects( $this->never() )->method( 'delete' );
755
756 $store = $this->newWatchedItemStore(
757 $this->getMockLoadBalancer( $mockDb ),
758 $mockCache
759 );
760
761 $store->duplicateEntry(
762 Title::newFromText( 'Old_Title' ),
763 Title::newFromText( 'New_Title' )
764 );
765 }
766
767 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
768 $mockDb = $this->getMockDb();
769 $mockDb->expects( $this->at( 0 ) )
770 ->method( 'select' )
771 ->with(
772 'watchlist',
773 [
774 'wl_user',
775 'wl_notificationtimestamp',
776 ],
777 [
778 'wl_namespace' => 0,
779 'wl_title' => 'Old_Title',
780 ]
781 )
782 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
783 $mockDb->expects( $this->at( 1 ) )
784 ->method( 'select' )
785 ->with(
786 'watchlist',
787 [
788 'wl_user',
789 'wl_notificationtimestamp',
790 ],
791 [
792 'wl_namespace' => 1,
793 'wl_title' => 'Old_Title',
794 ]
795 )
796 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
797
798 $mockCache = $this->getMockCache();
799 $mockCache->expects( $this->never() )->method( 'get' );
800 $mockCache->expects( $this->never() )->method( 'delete' );
801
802 $store = $this->newWatchedItemStore(
803 $this->getMockLoadBalancer( $mockDb ),
804 $mockCache
805 );
806
807 $store->duplicateAllAssociatedEntries(
808 Title::newFromText( 'Old_Title' ),
809 Title::newFromText( 'New_Title' )
810 );
811 }
812
813 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
814 $fakeRows = [
815 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
816 ];
817
818 $mockDb = $this->getMockDb();
819 $mockDb->expects( $this->at( 0 ) )
820 ->method( 'select' )
821 ->with(
822 'watchlist',
823 [
824 'wl_user',
825 'wl_notificationtimestamp',
826 ],
827 [
828 'wl_namespace' => 0,
829 'wl_title' => 'Old_Title',
830 ]
831 )
832 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
833 $mockDb->expects( $this->at( 1 ) )
834 ->method( 'replace' )
835 ->with(
836 'watchlist',
837 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
838 [
839 [
840 'wl_user' => 1,
841 'wl_namespace' => 0,
842 'wl_title' => 'New_Title',
843 'wl_notificationtimestamp' => '20151212010101',
844 ],
845 ],
846 $this->isType( 'string' )
847 );
848 $mockDb->expects( $this->at( 2 ) )
849 ->method( 'select' )
850 ->with(
851 'watchlist',
852 [
853 'wl_user',
854 'wl_notificationtimestamp',
855 ],
856 [
857 'wl_namespace' => 1,
858 'wl_title' => 'Old_Title',
859 ]
860 )
861 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
862 $mockDb->expects( $this->at( 3 ) )
863 ->method( 'replace' )
864 ->with(
865 'watchlist',
866 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
867 [
868 [
869 'wl_user' => 1,
870 'wl_namespace' => 1,
871 'wl_title' => 'New_Title',
872 'wl_notificationtimestamp' => '20151212010101',
873 ],
874 ],
875 $this->isType( 'string' )
876 );
877
878 $mockCache = $this->getMockCache();
879 $mockCache->expects( $this->never() )->method( 'get' );
880 $mockCache->expects( $this->never() )->method( 'delete' );
881
882 $store = $this->newWatchedItemStore(
883 $this->getMockLoadBalancer( $mockDb ),
884 $mockCache
885 );
886
887 $store->duplicateAllAssociatedEntries(
888 Title::newFromText( 'Old_Title' ),
889 Title::newFromText( 'New_Title' )
890 );
891 }
892
893 public function testAddWatch_nonAnonymousUser() {
894 $mockDb = $this->getMockDb();
895 $mockDb->expects( $this->once() )
896 ->method( 'insert' )
897 ->with(
898 'watchlist',
899 [
900 [
901 'wl_user' => 1,
902 'wl_namespace' => 0,
903 'wl_title' => 'Some_Page',
904 'wl_notificationtimestamp' => null,
905 ]
906 ]
907 );
908
909 $mockCache = $this->getMockCache();
910 $mockCache->expects( $this->once() )
911 ->method( 'delete' )
912 ->with( '0:Some_Page:1' );
913
914 $store = $this->newWatchedItemStore(
915 $this->getMockLoadBalancer( $mockDb ),
916 $mockCache
917 );
918
919 $store->addWatch(
920 $this->getMockNonAnonUserWithId( 1 ),
921 Title::newFromText( 'Some_Page' )
922 );
923 }
924
925 public function testAddWatch_anonymousUser() {
926 $mockDb = $this->getMockDb();
927 $mockDb->expects( $this->never() )
928 ->method( 'insert' );
929
930 $mockCache = $this->getMockCache();
931 $mockCache->expects( $this->never() )
932 ->method( 'delete' );
933
934 $store = $this->newWatchedItemStore(
935 $this->getMockLoadBalancer( $mockDb ),
936 $mockCache
937 );
938
939 $store->addWatch(
940 $this->getAnonUser(),
941 Title::newFromText( 'Some_Page' )
942 );
943 }
944
945 public function testAddWatchBatchForUser_nonAnonymousUser() {
946 $mockDb = $this->getMockDb();
947 $mockDb->expects( $this->once() )
948 ->method( 'insert' )
949 ->with(
950 'watchlist',
951 [
952 [
953 'wl_user' => 1,
954 'wl_namespace' => 0,
955 'wl_title' => 'Some_Page',
956 'wl_notificationtimestamp' => null,
957 ],
958 [
959 'wl_user' => 1,
960 'wl_namespace' => 1,
961 'wl_title' => 'Some_Page',
962 'wl_notificationtimestamp' => null,
963 ]
964 ]
965 );
966
967 $mockCache = $this->getMockCache();
968 $mockCache->expects( $this->exactly( 2 ) )
969 ->method( 'delete' );
970 $mockCache->expects( $this->at( 1 ) )
971 ->method( 'delete' )
972 ->with( '0:Some_Page:1' );
973 $mockCache->expects( $this->at( 3 ) )
974 ->method( 'delete' )
975 ->with( '1:Some_Page:1' );
976
977 $store = $this->newWatchedItemStore(
978 $this->getMockLoadBalancer( $mockDb ),
979 $mockCache
980 );
981
982 $mockUser = $this->getMockNonAnonUserWithId( 1 );
983
984 $this->assertTrue(
985 $store->addWatchBatchForUser(
986 $mockUser,
987 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
988 )
989 );
990 }
991
992 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
993 $mockDb = $this->getMockDb();
994 $mockDb->expects( $this->never() )
995 ->method( 'insert' );
996
997 $mockCache = $this->getMockCache();
998 $mockCache->expects( $this->never() )
999 ->method( 'delete' );
1000
1001 $store = $this->newWatchedItemStore(
1002 $this->getMockLoadBalancer( $mockDb ),
1003 $mockCache
1004 );
1005
1006 $this->assertFalse(
1007 $store->addWatchBatchForUser(
1008 $this->getAnonUser(),
1009 [ new TitleValue( 0, 'Other_Page' ) ]
1010 )
1011 );
1012 }
1013
1014 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1015 $user = $this->getMockNonAnonUserWithId( 1 );
1016 $mockDb = $this->getMockDb();
1017 $mockDb->expects( $this->never() )
1018 ->method( 'insert' );
1019
1020 $mockCache = $this->getMockCache();
1021 $mockCache->expects( $this->never() )
1022 ->method( 'delete' );
1023
1024 $store = $this->newWatchedItemStore(
1025 $this->getMockLoadBalancer( $mockDb ),
1026 $mockCache
1027 );
1028
1029 $this->assertTrue(
1030 $store->addWatchBatchForUser( $user, [] )
1031 );
1032 }
1033
1034 public function testLoadWatchedItem_existingItem() {
1035 $mockDb = $this->getMockDb();
1036 $mockDb->expects( $this->once() )
1037 ->method( 'selectRow' )
1038 ->with(
1039 'watchlist',
1040 'wl_notificationtimestamp',
1041 [
1042 'wl_user' => 1,
1043 'wl_namespace' => 0,
1044 'wl_title' => 'SomeDbKey',
1045 ]
1046 )
1047 ->will( $this->returnValue(
1048 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1049 ) );
1050
1051 $mockCache = $this->getMockCache();
1052 $mockCache->expects( $this->once() )
1053 ->method( 'set' )
1054 ->with(
1055 '0:SomeDbKey:1'
1056 );
1057
1058 $store = $this->newWatchedItemStore(
1059 $this->getMockLoadBalancer( $mockDb ),
1060 $mockCache
1061 );
1062
1063 $watchedItem = $store->loadWatchedItem(
1064 $this->getMockNonAnonUserWithId( 1 ),
1065 new TitleValue( 0, 'SomeDbKey' )
1066 );
1067 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1068 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1069 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1070 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1071 }
1072
1073 public function testLoadWatchedItem_noItem() {
1074 $mockDb = $this->getMockDb();
1075 $mockDb->expects( $this->once() )
1076 ->method( 'selectRow' )
1077 ->with(
1078 'watchlist',
1079 'wl_notificationtimestamp',
1080 [
1081 'wl_user' => 1,
1082 'wl_namespace' => 0,
1083 'wl_title' => 'SomeDbKey',
1084 ]
1085 )
1086 ->will( $this->returnValue( [] ) );
1087
1088 $mockCache = $this->getMockCache();
1089 $mockCache->expects( $this->never() )->method( 'get' );
1090 $mockCache->expects( $this->never() )->method( 'delete' );
1091
1092 $store = $this->newWatchedItemStore(
1093 $this->getMockLoadBalancer( $mockDb ),
1094 $mockCache
1095 );
1096
1097 $this->assertFalse(
1098 $store->loadWatchedItem(
1099 $this->getMockNonAnonUserWithId( 1 ),
1100 new TitleValue( 0, 'SomeDbKey' )
1101 )
1102 );
1103 }
1104
1105 public function testLoadWatchedItem_anonymousUser() {
1106 $mockDb = $this->getMockDb();
1107 $mockDb->expects( $this->never() )
1108 ->method( 'selectRow' );
1109
1110 $mockCache = $this->getMockCache();
1111 $mockCache->expects( $this->never() )->method( 'get' );
1112 $mockCache->expects( $this->never() )->method( 'delete' );
1113
1114 $store = $this->newWatchedItemStore(
1115 $this->getMockLoadBalancer( $mockDb ),
1116 $mockCache
1117 );
1118
1119 $this->assertFalse(
1120 $store->loadWatchedItem(
1121 $this->getAnonUser(),
1122 new TitleValue( 0, 'SomeDbKey' )
1123 )
1124 );
1125 }
1126
1127 public function testRemoveWatch_existingItem() {
1128 $mockDb = $this->getMockDb();
1129 $mockDb->expects( $this->once() )
1130 ->method( 'delete' )
1131 ->with(
1132 'watchlist',
1133 [
1134 'wl_user' => 1,
1135 'wl_namespace' => 0,
1136 'wl_title' => 'SomeDbKey',
1137 ]
1138 );
1139 $mockDb->expects( $this->once() )
1140 ->method( 'affectedRows' )
1141 ->will( $this->returnValue( 1 ) );
1142
1143 $mockCache = $this->getMockCache();
1144 $mockCache->expects( $this->never() )->method( 'get' );
1145 $mockCache->expects( $this->once() )
1146 ->method( 'delete' )
1147 ->with( '0:SomeDbKey:1' );
1148
1149 $store = $this->newWatchedItemStore(
1150 $this->getMockLoadBalancer( $mockDb ),
1151 $mockCache
1152 );
1153
1154 $this->assertTrue(
1155 $store->removeWatch(
1156 $this->getMockNonAnonUserWithId( 1 ),
1157 new TitleValue( 0, 'SomeDbKey' )
1158 )
1159 );
1160 }
1161
1162 public function testRemoveWatch_noItem() {
1163 $mockDb = $this->getMockDb();
1164 $mockDb->expects( $this->once() )
1165 ->method( 'delete' )
1166 ->with(
1167 'watchlist',
1168 [
1169 'wl_user' => 1,
1170 'wl_namespace' => 0,
1171 'wl_title' => 'SomeDbKey',
1172 ]
1173 );
1174 $mockDb->expects( $this->once() )
1175 ->method( 'affectedRows' )
1176 ->will( $this->returnValue( 0 ) );
1177
1178 $mockCache = $this->getMockCache();
1179 $mockCache->expects( $this->never() )->method( 'get' );
1180 $mockCache->expects( $this->once() )
1181 ->method( 'delete' )
1182 ->with( '0:SomeDbKey:1' );
1183
1184 $store = $this->newWatchedItemStore(
1185 $this->getMockLoadBalancer( $mockDb ),
1186 $mockCache
1187 );
1188
1189 $this->assertFalse(
1190 $store->removeWatch(
1191 $this->getMockNonAnonUserWithId( 1 ),
1192 new TitleValue( 0, 'SomeDbKey' )
1193 )
1194 );
1195 }
1196
1197 public function testRemoveWatch_anonymousUser() {
1198 $mockDb = $this->getMockDb();
1199 $mockDb->expects( $this->never() )
1200 ->method( 'delete' );
1201
1202 $mockCache = $this->getMockCache();
1203 $mockCache->expects( $this->never() )->method( 'get' );
1204 $mockCache->expects( $this->never() )
1205 ->method( 'delete' );
1206
1207 $store = $this->newWatchedItemStore(
1208 $this->getMockLoadBalancer( $mockDb ),
1209 $mockCache
1210 );
1211
1212 $this->assertFalse(
1213 $store->removeWatch(
1214 $this->getAnonUser(),
1215 new TitleValue( 0, 'SomeDbKey' )
1216 )
1217 );
1218 }
1219
1220 public function testGetWatchedItem_existingItem() {
1221 $mockDb = $this->getMockDb();
1222 $mockDb->expects( $this->once() )
1223 ->method( 'selectRow' )
1224 ->with(
1225 'watchlist',
1226 'wl_notificationtimestamp',
1227 [
1228 'wl_user' => 1,
1229 'wl_namespace' => 0,
1230 'wl_title' => 'SomeDbKey',
1231 ]
1232 )
1233 ->will( $this->returnValue(
1234 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1235 ) );
1236
1237 $mockCache = $this->getMockCache();
1238 $mockCache->expects( $this->never() )->method( 'delete' );
1239 $mockCache->expects( $this->once() )
1240 ->method( 'get' )
1241 ->with(
1242 '0:SomeDbKey:1'
1243 )
1244 ->will( $this->returnValue( null ) );
1245 $mockCache->expects( $this->once() )
1246 ->method( 'set' )
1247 ->with(
1248 '0:SomeDbKey:1'
1249 );
1250
1251 $store = $this->newWatchedItemStore(
1252 $this->getMockLoadBalancer( $mockDb ),
1253 $mockCache
1254 );
1255
1256 $watchedItem = $store->getWatchedItem(
1257 $this->getMockNonAnonUserWithId( 1 ),
1258 new TitleValue( 0, 'SomeDbKey' )
1259 );
1260 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1261 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1262 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1263 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1264 }
1265
1266 public function testGetWatchedItem_cachedItem() {
1267 $mockDb = $this->getMockDb();
1268 $mockDb->expects( $this->never() )
1269 ->method( 'selectRow' );
1270
1271 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1272 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1273 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1274
1275 $mockCache = $this->getMockCache();
1276 $mockCache->expects( $this->never() )->method( 'delete' );
1277 $mockCache->expects( $this->never() )->method( 'set' );
1278 $mockCache->expects( $this->once() )
1279 ->method( 'get' )
1280 ->with(
1281 '0:SomeDbKey:1'
1282 )
1283 ->will( $this->returnValue( $cachedItem ) );
1284
1285 $store = $this->newWatchedItemStore(
1286 $this->getMockLoadBalancer( $mockDb ),
1287 $mockCache
1288 );
1289
1290 $this->assertEquals(
1291 $cachedItem,
1292 $store->getWatchedItem(
1293 $mockUser,
1294 $linkTarget
1295 )
1296 );
1297 }
1298
1299 public function testGetWatchedItem_noItem() {
1300 $mockDb = $this->getMockDb();
1301 $mockDb->expects( $this->once() )
1302 ->method( 'selectRow' )
1303 ->with(
1304 'watchlist',
1305 'wl_notificationtimestamp',
1306 [
1307 'wl_user' => 1,
1308 'wl_namespace' => 0,
1309 'wl_title' => 'SomeDbKey',
1310 ]
1311 )
1312 ->will( $this->returnValue( [] ) );
1313
1314 $mockCache = $this->getMockCache();
1315 $mockCache->expects( $this->never() )->method( 'set' );
1316 $mockCache->expects( $this->never() )->method( 'delete' );
1317 $mockCache->expects( $this->once() )
1318 ->method( 'get' )
1319 ->with( '0:SomeDbKey:1' )
1320 ->will( $this->returnValue( false ) );
1321
1322 $store = $this->newWatchedItemStore(
1323 $this->getMockLoadBalancer( $mockDb ),
1324 $mockCache
1325 );
1326
1327 $this->assertFalse(
1328 $store->getWatchedItem(
1329 $this->getMockNonAnonUserWithId( 1 ),
1330 new TitleValue( 0, 'SomeDbKey' )
1331 )
1332 );
1333 }
1334
1335 public function testGetWatchedItem_anonymousUser() {
1336 $mockDb = $this->getMockDb();
1337 $mockDb->expects( $this->never() )
1338 ->method( 'selectRow' );
1339
1340 $mockCache = $this->getMockCache();
1341 $mockCache->expects( $this->never() )->method( 'set' );
1342 $mockCache->expects( $this->never() )->method( 'get' );
1343 $mockCache->expects( $this->never() )->method( 'delete' );
1344
1345 $store = $this->newWatchedItemStore(
1346 $this->getMockLoadBalancer( $mockDb ),
1347 $mockCache
1348 );
1349
1350 $this->assertFalse(
1351 $store->getWatchedItem(
1352 $this->getAnonUser(),
1353 new TitleValue( 0, 'SomeDbKey' )
1354 )
1355 );
1356 }
1357
1358 public function testGetWatchedItemsForUser() {
1359 $mockDb = $this->getMockDb();
1360 $mockDb->expects( $this->once() )
1361 ->method( 'select' )
1362 ->with(
1363 'watchlist',
1364 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1365 [ 'wl_user' => 1 ]
1366 )
1367 ->will( $this->returnValue( [
1368 $this->getFakeRow( [
1369 'wl_namespace' => 0,
1370 'wl_title' => 'Foo1',
1371 'wl_notificationtimestamp' => '20151212010101',
1372 ] ),
1373 $this->getFakeRow( [
1374 'wl_namespace' => 1,
1375 'wl_title' => 'Foo2',
1376 'wl_notificationtimestamp' => null,
1377 ] ),
1378 ] ) );
1379
1380 $mockCache = $this->getMockCache();
1381 $mockCache->expects( $this->never() )->method( 'delete' );
1382 $mockCache->expects( $this->never() )->method( 'get' );
1383 $mockCache->expects( $this->never() )->method( 'set' );
1384
1385 $store = $this->newWatchedItemStore(
1386 $this->getMockLoadBalancer( $mockDb ),
1387 $mockCache
1388 );
1389 $user = $this->getMockNonAnonUserWithId( 1 );
1390
1391 $watchedItems = $store->getWatchedItemsForUser( $user );
1392
1393 $this->assertInternalType( 'array', $watchedItems );
1394 $this->assertCount( 2, $watchedItems );
1395 foreach ( $watchedItems as $watchedItem ) {
1396 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1397 }
1398 $this->assertEquals(
1399 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1400 $watchedItems[0]
1401 );
1402 $this->assertEquals(
1403 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1404 $watchedItems[1]
1405 );
1406 }
1407
1408 public function provideDbTypes() {
1409 return [
1410 [ false, DB_SLAVE ],
1411 [ true, DB_MASTER ],
1412 ];
1413 }
1414
1415 /**
1416 * @dataProvider provideDbTypes
1417 */
1418 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1419 $mockDb = $this->getMockDb();
1420 $mockCache = $this->getMockCache();
1421 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1422 $user = $this->getMockNonAnonUserWithId( 1 );
1423
1424 $mockDb->expects( $this->once() )
1425 ->method( 'select' )
1426 ->with(
1427 'watchlist',
1428 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1429 [ 'wl_user' => 1 ],
1430 $this->isType( 'string' ),
1431 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1432 )
1433 ->will( $this->returnValue( [] ) );
1434
1435 $store = $this->newWatchedItemStore(
1436 $mockLoadBalancer,
1437 $mockCache
1438 );
1439
1440 $watchedItems = $store->getWatchedItemsForUser(
1441 $user,
1442 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1443 );
1444 $this->assertEquals( [], $watchedItems );
1445 }
1446
1447 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1448 $store = $this->newWatchedItemStore(
1449 $this->getMockLoadBalancer( $this->getMockDb() ),
1450 $this->getMockCache()
1451 );
1452
1453 $this->setExpectedException( 'InvalidArgumentException' );
1454 $store->getWatchedItemsForUser(
1455 $this->getMockNonAnonUserWithId( 1 ),
1456 [ 'sort' => 'foo' ]
1457 );
1458 }
1459
1460 public function testIsWatchedItem_existingItem() {
1461 $mockDb = $this->getMockDb();
1462 $mockDb->expects( $this->once() )
1463 ->method( 'selectRow' )
1464 ->with(
1465 'watchlist',
1466 'wl_notificationtimestamp',
1467 [
1468 'wl_user' => 1,
1469 'wl_namespace' => 0,
1470 'wl_title' => 'SomeDbKey',
1471 ]
1472 )
1473 ->will( $this->returnValue(
1474 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1475 ) );
1476
1477 $mockCache = $this->getMockCache();
1478 $mockCache->expects( $this->never() )->method( 'delete' );
1479 $mockCache->expects( $this->once() )
1480 ->method( 'get' )
1481 ->with( '0:SomeDbKey:1' )
1482 ->will( $this->returnValue( false ) );
1483 $mockCache->expects( $this->once() )
1484 ->method( 'set' )
1485 ->with(
1486 '0:SomeDbKey:1'
1487 );
1488
1489 $store = $this->newWatchedItemStore(
1490 $this->getMockLoadBalancer( $mockDb ),
1491 $mockCache
1492 );
1493
1494 $this->assertTrue(
1495 $store->isWatched(
1496 $this->getMockNonAnonUserWithId( 1 ),
1497 new TitleValue( 0, 'SomeDbKey' )
1498 )
1499 );
1500 }
1501
1502 public function testIsWatchedItem_noItem() {
1503 $mockDb = $this->getMockDb();
1504 $mockDb->expects( $this->once() )
1505 ->method( 'selectRow' )
1506 ->with(
1507 'watchlist',
1508 'wl_notificationtimestamp',
1509 [
1510 'wl_user' => 1,
1511 'wl_namespace' => 0,
1512 'wl_title' => 'SomeDbKey',
1513 ]
1514 )
1515 ->will( $this->returnValue( [] ) );
1516
1517 $mockCache = $this->getMockCache();
1518 $mockCache->expects( $this->never() )->method( 'set' );
1519 $mockCache->expects( $this->never() )->method( 'delete' );
1520 $mockCache->expects( $this->once() )
1521 ->method( 'get' )
1522 ->with( '0:SomeDbKey:1' )
1523 ->will( $this->returnValue( false ) );
1524
1525 $store = $this->newWatchedItemStore(
1526 $this->getMockLoadBalancer( $mockDb ),
1527 $mockCache
1528 );
1529
1530 $this->assertFalse(
1531 $store->isWatched(
1532 $this->getMockNonAnonUserWithId( 1 ),
1533 new TitleValue( 0, 'SomeDbKey' )
1534 )
1535 );
1536 }
1537
1538 public function testIsWatchedItem_anonymousUser() {
1539 $mockDb = $this->getMockDb();
1540 $mockDb->expects( $this->never() )
1541 ->method( 'selectRow' );
1542
1543 $mockCache = $this->getMockCache();
1544 $mockCache->expects( $this->never() )->method( 'set' );
1545 $mockCache->expects( $this->never() )->method( 'get' );
1546 $mockCache->expects( $this->never() )->method( 'delete' );
1547
1548 $store = $this->newWatchedItemStore(
1549 $this->getMockLoadBalancer( $mockDb ),
1550 $mockCache
1551 );
1552
1553 $this->assertFalse(
1554 $store->isWatched(
1555 $this->getAnonUser(),
1556 new TitleValue( 0, 'SomeDbKey' )
1557 )
1558 );
1559 }
1560
1561 public function testGetNotificationTimestampsBatch() {
1562 $targets = [
1563 new TitleValue( 0, 'SomeDbKey' ),
1564 new TitleValue( 1, 'AnotherDbKey' ),
1565 ];
1566
1567 $mockDb = $this->getMockDb();
1568 $dbResult = [
1569 $this->getFakeRow( [
1570 'wl_namespace' => 0,
1571 'wl_title' => 'SomeDbKey',
1572 'wl_notificationtimestamp' => '20151212010101',
1573 ] ),
1574 $this->getFakeRow(
1575 [
1576 'wl_namespace' => 1,
1577 'wl_title' => 'AnotherDbKey',
1578 'wl_notificationtimestamp' => null,
1579 ]
1580 ),
1581 ];
1582
1583 $mockDb->expects( $this->once() )
1584 ->method( 'makeWhereFrom2d' )
1585 ->with(
1586 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1587 $this->isType( 'string' ),
1588 $this->isType( 'string' )
1589 )
1590 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1591 $mockDb->expects( $this->once() )
1592 ->method( 'select' )
1593 ->with(
1594 'watchlist',
1595 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1596 [
1597 'makeWhereFrom2d return value',
1598 'wl_user' => 1
1599 ],
1600 $this->isType( 'string' )
1601 )
1602 ->will( $this->returnValue( $dbResult ) );
1603
1604 $mockCache = $this->getMockCache();
1605 $mockCache->expects( $this->exactly( 2 ) )
1606 ->method( 'get' )
1607 ->withConsecutive(
1608 [ '0:SomeDbKey:1' ],
1609 [ '1:AnotherDbKey:1' ]
1610 )
1611 ->will( $this->returnValue( null ) );
1612 $mockCache->expects( $this->never() )->method( 'set' );
1613 $mockCache->expects( $this->never() )->method( 'delete' );
1614
1615 $store = $this->newWatchedItemStore(
1616 $this->getMockLoadBalancer( $mockDb ),
1617 $mockCache
1618 );
1619
1620 $this->assertEquals(
1621 [
1622 0 => [ 'SomeDbKey' => '20151212010101', ],
1623 1 => [ 'AnotherDbKey' => null, ],
1624 ],
1625 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1626 );
1627 }
1628
1629 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1630 $targets = [
1631 new TitleValue( 0, 'OtherDbKey' ),
1632 ];
1633
1634 $mockDb = $this->getMockDb();
1635
1636 $mockDb->expects( $this->once() )
1637 ->method( 'makeWhereFrom2d' )
1638 ->with(
1639 [ [ 'OtherDbKey' => 1 ] ],
1640 $this->isType( 'string' ),
1641 $this->isType( 'string' )
1642 )
1643 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1644 $mockDb->expects( $this->once() )
1645 ->method( 'select' )
1646 ->with(
1647 'watchlist',
1648 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1649 [
1650 'makeWhereFrom2d return value',
1651 'wl_user' => 1
1652 ],
1653 $this->isType( 'string' )
1654 )
1655 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1656
1657 $mockCache = $this->getMockCache();
1658 $mockCache->expects( $this->once() )
1659 ->method( 'get' )
1660 ->with( '0:OtherDbKey:1' )
1661 ->will( $this->returnValue( null ) );
1662 $mockCache->expects( $this->never() )->method( 'set' );
1663 $mockCache->expects( $this->never() )->method( 'delete' );
1664
1665 $store = $this->newWatchedItemStore(
1666 $this->getMockLoadBalancer( $mockDb ),
1667 $mockCache
1668 );
1669
1670 $this->assertEquals(
1671 [
1672 0 => [ 'OtherDbKey' => false, ],
1673 ],
1674 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1675 );
1676 }
1677
1678 public function testGetNotificationTimestampsBatch_cachedItem() {
1679 $targets = [
1680 new TitleValue( 0, 'SomeDbKey' ),
1681 new TitleValue( 1, 'AnotherDbKey' ),
1682 ];
1683
1684 $user = $this->getMockNonAnonUserWithId( 1 );
1685 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1686
1687 $mockDb = $this->getMockDb();
1688
1689 $mockDb->expects( $this->once() )
1690 ->method( 'makeWhereFrom2d' )
1691 ->with(
1692 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1693 $this->isType( 'string' ),
1694 $this->isType( 'string' )
1695 )
1696 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1697 $mockDb->expects( $this->once() )
1698 ->method( 'select' )
1699 ->with(
1700 'watchlist',
1701 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1702 [
1703 'makeWhereFrom2d return value',
1704 'wl_user' => 1
1705 ],
1706 $this->isType( 'string' )
1707 )
1708 ->will( $this->returnValue( [
1709 $this->getFakeRow(
1710 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1711 )
1712 ] ) );
1713
1714 $mockCache = $this->getMockCache();
1715 $mockCache->expects( $this->at( 1 ) )
1716 ->method( 'get' )
1717 ->with( '0:SomeDbKey:1' )
1718 ->will( $this->returnValue( $cachedItem ) );
1719 $mockCache->expects( $this->at( 3 ) )
1720 ->method( 'get' )
1721 ->with( '1:AnotherDbKey:1' )
1722 ->will( $this->returnValue( null ) );
1723 $mockCache->expects( $this->never() )->method( 'set' );
1724 $mockCache->expects( $this->never() )->method( 'delete' );
1725
1726 $store = $this->newWatchedItemStore(
1727 $this->getMockLoadBalancer( $mockDb ),
1728 $mockCache
1729 );
1730
1731 $this->assertEquals(
1732 [
1733 0 => [ 'SomeDbKey' => '20151212010101', ],
1734 1 => [ 'AnotherDbKey' => null, ],
1735 ],
1736 $store->getNotificationTimestampsBatch( $user, $targets )
1737 );
1738 }
1739
1740 public function testGetNotificationTimestampsBatch_allItemsCached() {
1741 $targets = [
1742 new TitleValue( 0, 'SomeDbKey' ),
1743 new TitleValue( 1, 'AnotherDbKey' ),
1744 ];
1745
1746 $user = $this->getMockNonAnonUserWithId( 1 );
1747 $cachedItems = [
1748 new WatchedItem( $user, $targets[0], '20151212010101' ),
1749 new WatchedItem( $user, $targets[1], null ),
1750 ];
1751 $mockDb = $this->getMockDb();
1752 $mockDb->expects( $this->never() )->method( $this->anything() );
1753
1754 $mockCache = $this->getMockCache();
1755 $mockCache->expects( $this->at( 1 ) )
1756 ->method( 'get' )
1757 ->with( '0:SomeDbKey:1' )
1758 ->will( $this->returnValue( $cachedItems[0] ) );
1759 $mockCache->expects( $this->at( 3 ) )
1760 ->method( 'get' )
1761 ->with( '1:AnotherDbKey:1' )
1762 ->will( $this->returnValue( $cachedItems[1] ) );
1763 $mockCache->expects( $this->never() )->method( 'set' );
1764 $mockCache->expects( $this->never() )->method( 'delete' );
1765
1766 $store = $this->newWatchedItemStore(
1767 $this->getMockLoadBalancer( $mockDb ),
1768 $mockCache
1769 );
1770
1771 $this->assertEquals(
1772 [
1773 0 => [ 'SomeDbKey' => '20151212010101', ],
1774 1 => [ 'AnotherDbKey' => null, ],
1775 ],
1776 $store->getNotificationTimestampsBatch( $user, $targets )
1777 );
1778 }
1779
1780 public function testGetNotificationTimestampsBatch_anonymousUser() {
1781 $targets = [
1782 new TitleValue( 0, 'SomeDbKey' ),
1783 new TitleValue( 1, 'AnotherDbKey' ),
1784 ];
1785
1786 $mockDb = $this->getMockDb();
1787 $mockDb->expects( $this->never() )->method( $this->anything() );
1788
1789 $mockCache = $this->getMockCache();
1790 $mockCache->expects( $this->never() )->method( $this->anything() );
1791
1792 $store = $this->newWatchedItemStore(
1793 $this->getMockLoadBalancer( $mockDb ),
1794 $mockCache
1795 );
1796
1797 $this->assertEquals(
1798 [
1799 0 => [ 'SomeDbKey' => false, ],
1800 1 => [ 'AnotherDbKey' => false, ],
1801 ],
1802 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1803 );
1804 }
1805
1806 public function testResetNotificationTimestamp_anonymousUser() {
1807 $mockDb = $this->getMockDb();
1808 $mockDb->expects( $this->never() )
1809 ->method( 'selectRow' );
1810
1811 $mockCache = $this->getMockCache();
1812 $mockCache->expects( $this->never() )->method( 'get' );
1813 $mockCache->expects( $this->never() )->method( 'set' );
1814 $mockCache->expects( $this->never() )->method( 'delete' );
1815
1816 $store = $this->newWatchedItemStore(
1817 $this->getMockLoadBalancer( $mockDb ),
1818 $mockCache
1819 );
1820
1821 $this->assertFalse(
1822 $store->resetNotificationTimestamp(
1823 $this->getAnonUser(),
1824 Title::newFromText( 'SomeDbKey' )
1825 )
1826 );
1827 }
1828
1829 public function testResetNotificationTimestamp_noItem() {
1830 $mockDb = $this->getMockDb();
1831 $mockDb->expects( $this->once() )
1832 ->method( 'selectRow' )
1833 ->with(
1834 'watchlist',
1835 'wl_notificationtimestamp',
1836 [
1837 'wl_user' => 1,
1838 'wl_namespace' => 0,
1839 'wl_title' => 'SomeDbKey',
1840 ]
1841 )
1842 ->will( $this->returnValue( [] ) );
1843
1844 $mockCache = $this->getMockCache();
1845 $mockCache->expects( $this->never() )->method( 'get' );
1846 $mockCache->expects( $this->never() )->method( 'set' );
1847 $mockCache->expects( $this->never() )->method( 'delete' );
1848
1849 $store = $this->newWatchedItemStore(
1850 $this->getMockLoadBalancer( $mockDb ),
1851 $mockCache
1852 );
1853
1854 $this->assertFalse(
1855 $store->resetNotificationTimestamp(
1856 $this->getMockNonAnonUserWithId( 1 ),
1857 Title::newFromText( 'SomeDbKey' )
1858 )
1859 );
1860 }
1861
1862 public function testResetNotificationTimestamp_item() {
1863 $user = $this->getMockNonAnonUserWithId( 1 );
1864 $title = Title::newFromText( 'SomeDbKey' );
1865
1866 $mockDb = $this->getMockDb();
1867 $mockDb->expects( $this->once() )
1868 ->method( 'selectRow' )
1869 ->with(
1870 'watchlist',
1871 'wl_notificationtimestamp',
1872 [
1873 'wl_user' => 1,
1874 'wl_namespace' => 0,
1875 'wl_title' => 'SomeDbKey',
1876 ]
1877 )
1878 ->will( $this->returnValue(
1879 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1880 ) );
1881
1882 $mockCache = $this->getMockCache();
1883 $mockCache->expects( $this->never() )->method( 'get' );
1884 $mockCache->expects( $this->once() )
1885 ->method( 'set' )
1886 ->with(
1887 '0:SomeDbKey:1',
1888 $this->isInstanceOf( WatchedItem::class )
1889 );
1890 $mockCache->expects( $this->once() )
1891 ->method( 'delete' )
1892 ->with( '0:SomeDbKey:1' );
1893
1894 $store = $this->newWatchedItemStore(
1895 $this->getMockLoadBalancer( $mockDb ),
1896 $mockCache
1897 );
1898
1899 // Note: This does not actually assert the job is correct
1900 $callableCallCounter = 0;
1901 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1902 $callableCallCounter++;
1903 $this->assertInternalType( 'callable', $callable );
1904 };
1905 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1906
1907 $this->assertTrue(
1908 $store->resetNotificationTimestamp(
1909 $user,
1910 $title
1911 )
1912 );
1913 $this->assertEquals( 1, $callableCallCounter );
1914
1915 ScopedCallback::consume( $scopedOverride );
1916 }
1917
1918 public function testResetNotificationTimestamp_noItemForced() {
1919 $user = $this->getMockNonAnonUserWithId( 1 );
1920 $title = Title::newFromText( 'SomeDbKey' );
1921
1922 $mockDb = $this->getMockDb();
1923 $mockDb->expects( $this->never() )
1924 ->method( 'selectRow' );
1925
1926 $mockCache = $this->getMockCache();
1927 $mockDb->expects( $this->never() )
1928 ->method( 'get' );
1929 $mockDb->expects( $this->never() )
1930 ->method( 'set' );
1931 $mockDb->expects( $this->never() )
1932 ->method( 'delete' );
1933
1934 $store = $this->newWatchedItemStore(
1935 $this->getMockLoadBalancer( $mockDb ),
1936 $mockCache
1937 );
1938
1939 // Note: This does not actually assert the job is correct
1940 $callableCallCounter = 0;
1941 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1942 $callableCallCounter++;
1943 $this->assertInternalType( 'callable', $callable );
1944 };
1945 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1946
1947 $this->assertTrue(
1948 $store->resetNotificationTimestamp(
1949 $user,
1950 $title,
1951 'force'
1952 )
1953 );
1954 $this->assertEquals( 1, $callableCallCounter );
1955
1956 ScopedCallback::consume( $scopedOverride );
1957 }
1958
1959 /**
1960 * @param $text
1961 * @param int $ns
1962 *
1963 * @return PHPUnit_Framework_MockObject_MockObject|Title
1964 */
1965 private function getMockTitle( $text, $ns = 0 ) {
1966 $title = $this->getMock( Title::class );
1967 $title->expects( $this->any() )
1968 ->method( 'getText' )
1969 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1970 $title->expects( $this->any() )
1971 ->method( 'getDbKey' )
1972 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1973 $title->expects( $this->any() )
1974 ->method( 'getNamespace' )
1975 ->will( $this->returnValue( $ns ) );
1976 return $title;
1977 }
1978
1979 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1980 $user = $this->getMockNonAnonUserWithId( 1 );
1981 $oldid = 22;
1982 $title = $this->getMockTitle( 'SomeTitle' );
1983 $title->expects( $this->once() )
1984 ->method( 'getNextRevisionID' )
1985 ->with( $oldid )
1986 ->will( $this->returnValue( false ) );
1987
1988 $mockDb = $this->getMockDb();
1989 $mockDb->expects( $this->never() )
1990 ->method( 'selectRow' );
1991
1992 $mockCache = $this->getMockCache();
1993 $mockDb->expects( $this->never() )
1994 ->method( 'get' );
1995 $mockDb->expects( $this->never() )
1996 ->method( 'set' );
1997 $mockDb->expects( $this->never() )
1998 ->method( 'delete' );
1999
2000 $store = $this->newWatchedItemStore(
2001 $this->getMockLoadBalancer( $mockDb ),
2002 $mockCache
2003 );
2004
2005 // Note: This does not actually assert the job is correct
2006 $callableCallCounter = 0;
2007 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2008 function( $callable ) use ( &$callableCallCounter ) {
2009 $callableCallCounter++;
2010 $this->assertInternalType( 'callable', $callable );
2011 }
2012 );
2013
2014 $this->assertTrue(
2015 $store->resetNotificationTimestamp(
2016 $user,
2017 $title,
2018 'force',
2019 $oldid
2020 )
2021 );
2022 $this->assertEquals( 1, $callableCallCounter );
2023
2024 ScopedCallback::consume( $scopedOverride );
2025 }
2026
2027 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2028 $user = $this->getMockNonAnonUserWithId( 1 );
2029 $oldid = 22;
2030 $title = $this->getMockTitle( 'SomeDbKey' );
2031 $title->expects( $this->once() )
2032 ->method( 'getNextRevisionID' )
2033 ->with( $oldid )
2034 ->will( $this->returnValue( 33 ) );
2035
2036 $mockDb = $this->getMockDb();
2037 $mockDb->expects( $this->once() )
2038 ->method( 'selectRow' )
2039 ->with(
2040 'watchlist',
2041 'wl_notificationtimestamp',
2042 [
2043 'wl_user' => 1,
2044 'wl_namespace' => 0,
2045 'wl_title' => 'SomeDbKey',
2046 ]
2047 )
2048 ->will( $this->returnValue(
2049 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2050 ) );
2051
2052 $mockCache = $this->getMockCache();
2053 $mockDb->expects( $this->never() )
2054 ->method( 'get' );
2055 $mockDb->expects( $this->never() )
2056 ->method( 'set' );
2057 $mockDb->expects( $this->never() )
2058 ->method( 'delete' );
2059
2060 $store = $this->newWatchedItemStore(
2061 $this->getMockLoadBalancer( $mockDb ),
2062 $mockCache
2063 );
2064
2065 // Note: This does not actually assert the job is correct
2066 $addUpdateCallCounter = 0;
2067 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2068 function( $callable ) use ( &$addUpdateCallCounter ) {
2069 $addUpdateCallCounter++;
2070 $this->assertInternalType( 'callable', $callable );
2071 }
2072 );
2073
2074 $getTimestampCallCounter = 0;
2075 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2076 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2077 $getTimestampCallCounter++;
2078 $this->assertEquals( $title, $titleParam );
2079 $this->assertEquals( $oldid, $oldidParam );
2080 }
2081 );
2082
2083 $this->assertTrue(
2084 $store->resetNotificationTimestamp(
2085 $user,
2086 $title,
2087 'force',
2088 $oldid
2089 )
2090 );
2091 $this->assertEquals( 1, $addUpdateCallCounter );
2092 $this->assertEquals( 1, $getTimestampCallCounter );
2093
2094 ScopedCallback::consume( $scopedOverrideDeferred );
2095 ScopedCallback::consume( $scopedOverrideRevision );
2096 }
2097
2098 public function testUpdateNotificationTimestamp_watchersExist() {
2099 $mockDb = $this->getMockDb();
2100 $mockDb->expects( $this->once() )
2101 ->method( 'select' )
2102 ->with(
2103 [ 'watchlist' ],
2104 [ 'wl_user' ],
2105 [
2106 'wl_user != 1',
2107 'wl_namespace' => 0,
2108 'wl_title' => 'SomeDbKey',
2109 'wl_notificationtimestamp IS NULL'
2110 ]
2111 )
2112 ->will(
2113 $this->returnValue( [
2114 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2115 $this->getFakeRow( [ 'wl_user' => '3' ] )
2116 ] )
2117 );
2118 $mockDb->expects( $this->once() )
2119 ->method( 'onTransactionIdle' )
2120 ->with( $this->isType( 'callable' ) )
2121 ->will( $this->returnCallback( function( $callable ) {
2122 $callable();
2123 } ) );
2124 $mockDb->expects( $this->once() )
2125 ->method( 'update' )
2126 ->with(
2127 'watchlist',
2128 [ 'wl_notificationtimestamp' => null ],
2129 [
2130 'wl_user' => [ 2, 3 ],
2131 'wl_namespace' => 0,
2132 'wl_title' => 'SomeDbKey',
2133 ]
2134 );
2135
2136 $mockCache = $this->getMockCache();
2137 $mockCache->expects( $this->never() )->method( 'set' );
2138 $mockCache->expects( $this->never() )->method( 'get' );
2139 $mockCache->expects( $this->never() )->method( 'delete' );
2140
2141 $store = $this->newWatchedItemStore(
2142 $this->getMockLoadBalancer( $mockDb ),
2143 $mockCache
2144 );
2145
2146 $this->assertEquals(
2147 [ 2, 3 ],
2148 $store->updateNotificationTimestamp(
2149 $this->getMockNonAnonUserWithId( 1 ),
2150 new TitleValue( 0, 'SomeDbKey' ),
2151 '20151212010101'
2152 )
2153 );
2154 }
2155
2156 public function testUpdateNotificationTimestamp_noWatchers() {
2157 $mockDb = $this->getMockDb();
2158 $mockDb->expects( $this->once() )
2159 ->method( 'select' )
2160 ->with(
2161 [ 'watchlist' ],
2162 [ 'wl_user' ],
2163 [
2164 'wl_user != 1',
2165 'wl_namespace' => 0,
2166 'wl_title' => 'SomeDbKey',
2167 'wl_notificationtimestamp IS NULL'
2168 ]
2169 )
2170 ->will(
2171 $this->returnValue( [] )
2172 );
2173 $mockDb->expects( $this->never() )
2174 ->method( 'onTransactionIdle' );
2175 $mockDb->expects( $this->never() )
2176 ->method( 'update' );
2177
2178 $mockCache = $this->getMockCache();
2179 $mockCache->expects( $this->never() )->method( 'set' );
2180 $mockCache->expects( $this->never() )->method( 'get' );
2181 $mockCache->expects( $this->never() )->method( 'delete' );
2182
2183 $store = $this->newWatchedItemStore(
2184 $this->getMockLoadBalancer( $mockDb ),
2185 $mockCache
2186 );
2187
2188 $watchers = $store->updateNotificationTimestamp(
2189 $this->getMockNonAnonUserWithId( 1 ),
2190 new TitleValue( 0, 'SomeDbKey' ),
2191 '20151212010101'
2192 );
2193 $this->assertInternalType( 'array', $watchers );
2194 $this->assertEmpty( $watchers );
2195 }
2196
2197 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2198 $user = $this->getMockNonAnonUserWithId( 1 );
2199 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2200
2201 $mockDb = $this->getMockDb();
2202 $mockDb->expects( $this->once() )
2203 ->method( 'selectRow' )
2204 ->will( $this->returnValue(
2205 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2206 ) );
2207 $mockDb->expects( $this->once() )
2208 ->method( 'select' )
2209 ->will(
2210 $this->returnValue( [
2211 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2212 $this->getFakeRow( [ 'wl_user' => '3' ] )
2213 ] )
2214 );
2215 $mockDb->expects( $this->once() )
2216 ->method( 'onTransactionIdle' )
2217 ->with( $this->isType( 'callable' ) )
2218 ->will( $this->returnCallback( function( $callable ) {
2219 $callable();
2220 } ) );
2221 $mockDb->expects( $this->once() )
2222 ->method( 'update' );
2223
2224 $mockCache = $this->getMockCache();
2225 $mockCache->expects( $this->once() )
2226 ->method( 'set' )
2227 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2228 $mockCache->expects( $this->once() )
2229 ->method( 'get' )
2230 ->with( '0:SomeDbKey:1' );
2231 $mockCache->expects( $this->once() )
2232 ->method( 'delete' )
2233 ->with( '0:SomeDbKey:1' );
2234
2235 $store = $this->newWatchedItemStore(
2236 $this->getMockLoadBalancer( $mockDb ),
2237 $mockCache
2238 );
2239
2240 // This will add the item to the cache
2241 $store->getWatchedItem( $user, $titleValue );
2242
2243 $store->updateNotificationTimestamp(
2244 $this->getMockNonAnonUserWithId( 1 ),
2245 $titleValue,
2246 '20151212010101'
2247 );
2248 }
2249
2250 }