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