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