Merge "mediawiki.widgets: Remove use of bind() for lexical 'this' binding"
[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 testAddWatchBatchForUser_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->addWatchBatchForUser(
978 $mockUser,
979 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
980 )
981 );
982 }
983
984 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
985 $mockDb = $this->getMockDb();
986 $mockDb->expects( $this->never() )
987 ->method( 'insert' );
988
989 $mockCache = $this->getMockCache();
990 $mockCache->expects( $this->never() )
991 ->method( 'delete' );
992
993 $store = new WatchedItemStore(
994 $this->getMockLoadBalancer( $mockDb ),
995 $mockCache
996 );
997
998 $this->assertFalse(
999 $store->addWatchBatchForUser(
1000 $this->getAnonUser(),
1001 [ new TitleValue( 0, 'Other_Page' ) ]
1002 )
1003 );
1004 }
1005
1006 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1007 $user = $this->getMockNonAnonUserWithId( 1 );
1008 $mockDb = $this->getMockDb();
1009 $mockDb->expects( $this->never() )
1010 ->method( 'insert' );
1011
1012 $mockCache = $this->getMockCache();
1013 $mockCache->expects( $this->never() )
1014 ->method( 'delete' );
1015
1016 $store = new WatchedItemStore(
1017 $this->getMockLoadBalancer( $mockDb ),
1018 $mockCache
1019 );
1020
1021 $this->assertTrue(
1022 $store->addWatchBatchForUser( $user, [] )
1023 );
1024 }
1025
1026 public function testLoadWatchedItem_existingItem() {
1027 $mockDb = $this->getMockDb();
1028 $mockDb->expects( $this->once() )
1029 ->method( 'selectRow' )
1030 ->with(
1031 'watchlist',
1032 'wl_notificationtimestamp',
1033 [
1034 'wl_user' => 1,
1035 'wl_namespace' => 0,
1036 'wl_title' => 'SomeDbKey',
1037 ]
1038 )
1039 ->will( $this->returnValue(
1040 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1041 ) );
1042
1043 $mockCache = $this->getMockCache();
1044 $mockCache->expects( $this->once() )
1045 ->method( 'set' )
1046 ->with(
1047 '0:SomeDbKey:1'
1048 );
1049
1050 $store = new WatchedItemStore(
1051 $this->getMockLoadBalancer( $mockDb ),
1052 $mockCache
1053 );
1054
1055 $watchedItem = $store->loadWatchedItem(
1056 $this->getMockNonAnonUserWithId( 1 ),
1057 new TitleValue( 0, 'SomeDbKey' )
1058 );
1059 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1060 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1061 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1062 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1063 }
1064
1065 public function testLoadWatchedItem_noItem() {
1066 $mockDb = $this->getMockDb();
1067 $mockDb->expects( $this->once() )
1068 ->method( 'selectRow' )
1069 ->with(
1070 'watchlist',
1071 'wl_notificationtimestamp',
1072 [
1073 'wl_user' => 1,
1074 'wl_namespace' => 0,
1075 'wl_title' => 'SomeDbKey',
1076 ]
1077 )
1078 ->will( $this->returnValue( [] ) );
1079
1080 $mockCache = $this->getMockCache();
1081 $mockCache->expects( $this->never() )->method( 'get' );
1082 $mockCache->expects( $this->never() )->method( 'delete' );
1083
1084 $store = new WatchedItemStore(
1085 $this->getMockLoadBalancer( $mockDb ),
1086 $mockCache
1087 );
1088
1089 $this->assertFalse(
1090 $store->loadWatchedItem(
1091 $this->getMockNonAnonUserWithId( 1 ),
1092 new TitleValue( 0, 'SomeDbKey' )
1093 )
1094 );
1095 }
1096
1097 public function testLoadWatchedItem_anonymousUser() {
1098 $mockDb = $this->getMockDb();
1099 $mockDb->expects( $this->never() )
1100 ->method( 'selectRow' );
1101
1102 $mockCache = $this->getMockCache();
1103 $mockCache->expects( $this->never() )->method( 'get' );
1104 $mockCache->expects( $this->never() )->method( 'delete' );
1105
1106 $store = new WatchedItemStore(
1107 $this->getMockLoadBalancer( $mockDb ),
1108 $mockCache
1109 );
1110
1111 $this->assertFalse(
1112 $store->loadWatchedItem(
1113 $this->getAnonUser(),
1114 new TitleValue( 0, 'SomeDbKey' )
1115 )
1116 );
1117 }
1118
1119 public function testRemoveWatch_existingItem() {
1120 $mockDb = $this->getMockDb();
1121 $mockDb->expects( $this->once() )
1122 ->method( 'delete' )
1123 ->with(
1124 'watchlist',
1125 [
1126 'wl_user' => 1,
1127 'wl_namespace' => 0,
1128 'wl_title' => 'SomeDbKey',
1129 ]
1130 );
1131 $mockDb->expects( $this->once() )
1132 ->method( 'affectedRows' )
1133 ->will( $this->returnValue( 1 ) );
1134
1135 $mockCache = $this->getMockCache();
1136 $mockCache->expects( $this->never() )->method( 'get' );
1137 $mockCache->expects( $this->once() )
1138 ->method( 'delete' )
1139 ->with( '0:SomeDbKey:1' );
1140
1141 $store = new WatchedItemStore(
1142 $this->getMockLoadBalancer( $mockDb ),
1143 $mockCache
1144 );
1145
1146 $this->assertTrue(
1147 $store->removeWatch(
1148 $this->getMockNonAnonUserWithId( 1 ),
1149 new TitleValue( 0, 'SomeDbKey' )
1150 )
1151 );
1152 }
1153
1154 public function testRemoveWatch_noItem() {
1155 $mockDb = $this->getMockDb();
1156 $mockDb->expects( $this->once() )
1157 ->method( 'delete' )
1158 ->with(
1159 'watchlist',
1160 [
1161 'wl_user' => 1,
1162 'wl_namespace' => 0,
1163 'wl_title' => 'SomeDbKey',
1164 ]
1165 );
1166 $mockDb->expects( $this->once() )
1167 ->method( 'affectedRows' )
1168 ->will( $this->returnValue( 0 ) );
1169
1170 $mockCache = $this->getMockCache();
1171 $mockCache->expects( $this->never() )->method( 'get' );
1172 $mockCache->expects( $this->once() )
1173 ->method( 'delete' )
1174 ->with( '0:SomeDbKey:1' );
1175
1176 $store = new WatchedItemStore(
1177 $this->getMockLoadBalancer( $mockDb ),
1178 $mockCache
1179 );
1180
1181 $this->assertFalse(
1182 $store->removeWatch(
1183 $this->getMockNonAnonUserWithId( 1 ),
1184 new TitleValue( 0, 'SomeDbKey' )
1185 )
1186 );
1187 }
1188
1189 public function testRemoveWatch_anonymousUser() {
1190 $mockDb = $this->getMockDb();
1191 $mockDb->expects( $this->never() )
1192 ->method( 'delete' );
1193
1194 $mockCache = $this->getMockCache();
1195 $mockCache->expects( $this->never() )->method( 'get' );
1196 $mockCache->expects( $this->never() )
1197 ->method( 'delete' );
1198
1199 $store = new WatchedItemStore(
1200 $this->getMockLoadBalancer( $mockDb ),
1201 $mockCache
1202 );
1203
1204 $this->assertFalse(
1205 $store->removeWatch(
1206 $this->getAnonUser(),
1207 new TitleValue( 0, 'SomeDbKey' )
1208 )
1209 );
1210 }
1211
1212 public function testGetWatchedItem_existingItem() {
1213 $mockDb = $this->getMockDb();
1214 $mockDb->expects( $this->once() )
1215 ->method( 'selectRow' )
1216 ->with(
1217 'watchlist',
1218 'wl_notificationtimestamp',
1219 [
1220 'wl_user' => 1,
1221 'wl_namespace' => 0,
1222 'wl_title' => 'SomeDbKey',
1223 ]
1224 )
1225 ->will( $this->returnValue(
1226 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1227 ) );
1228
1229 $mockCache = $this->getMockCache();
1230 $mockCache->expects( $this->never() )->method( 'delete' );
1231 $mockCache->expects( $this->once() )
1232 ->method( 'get' )
1233 ->with(
1234 '0:SomeDbKey:1'
1235 )
1236 ->will( $this->returnValue( null ) );
1237 $mockCache->expects( $this->once() )
1238 ->method( 'set' )
1239 ->with(
1240 '0:SomeDbKey:1'
1241 );
1242
1243 $store = new WatchedItemStore(
1244 $this->getMockLoadBalancer( $mockDb ),
1245 $mockCache
1246 );
1247
1248 $watchedItem = $store->getWatchedItem(
1249 $this->getMockNonAnonUserWithId( 1 ),
1250 new TitleValue( 0, 'SomeDbKey' )
1251 );
1252 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1253 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1254 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1255 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1256 }
1257
1258 public function testGetWatchedItem_cachedItem() {
1259 $mockDb = $this->getMockDb();
1260 $mockDb->expects( $this->never() )
1261 ->method( 'selectRow' );
1262
1263 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1264 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1265 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1266
1267 $mockCache = $this->getMockCache();
1268 $mockCache->expects( $this->never() )->method( 'delete' );
1269 $mockCache->expects( $this->never() )->method( 'set' );
1270 $mockCache->expects( $this->once() )
1271 ->method( 'get' )
1272 ->with(
1273 '0:SomeDbKey:1'
1274 )
1275 ->will( $this->returnValue( $cachedItem ) );
1276
1277 $store = new WatchedItemStore(
1278 $this->getMockLoadBalancer( $mockDb ),
1279 $mockCache
1280 );
1281
1282 $this->assertEquals(
1283 $cachedItem,
1284 $store->getWatchedItem(
1285 $mockUser,
1286 $linkTarget
1287 )
1288 );
1289 }
1290
1291 public function testGetWatchedItem_noItem() {
1292 $mockDb = $this->getMockDb();
1293 $mockDb->expects( $this->once() )
1294 ->method( 'selectRow' )
1295 ->with(
1296 'watchlist',
1297 'wl_notificationtimestamp',
1298 [
1299 'wl_user' => 1,
1300 'wl_namespace' => 0,
1301 'wl_title' => 'SomeDbKey',
1302 ]
1303 )
1304 ->will( $this->returnValue( [] ) );
1305
1306 $mockCache = $this->getMockCache();
1307 $mockCache->expects( $this->never() )->method( 'set' );
1308 $mockCache->expects( $this->never() )->method( 'delete' );
1309 $mockCache->expects( $this->once() )
1310 ->method( 'get' )
1311 ->with( '0:SomeDbKey:1' )
1312 ->will( $this->returnValue( false ) );
1313
1314 $store = new WatchedItemStore(
1315 $this->getMockLoadBalancer( $mockDb ),
1316 $mockCache
1317 );
1318
1319 $this->assertFalse(
1320 $store->getWatchedItem(
1321 $this->getMockNonAnonUserWithId( 1 ),
1322 new TitleValue( 0, 'SomeDbKey' )
1323 )
1324 );
1325 }
1326
1327 public function testGetWatchedItem_anonymousUser() {
1328 $mockDb = $this->getMockDb();
1329 $mockDb->expects( $this->never() )
1330 ->method( 'selectRow' );
1331
1332 $mockCache = $this->getMockCache();
1333 $mockCache->expects( $this->never() )->method( 'set' );
1334 $mockCache->expects( $this->never() )->method( 'get' );
1335 $mockCache->expects( $this->never() )->method( 'delete' );
1336
1337 $store = new WatchedItemStore(
1338 $this->getMockLoadBalancer( $mockDb ),
1339 $mockCache
1340 );
1341
1342 $this->assertFalse(
1343 $store->getWatchedItem(
1344 $this->getAnonUser(),
1345 new TitleValue( 0, 'SomeDbKey' )
1346 )
1347 );
1348 }
1349
1350 public function testGetWatchedItemsForUser() {
1351 $mockDb = $this->getMockDb();
1352 $mockDb->expects( $this->once() )
1353 ->method( 'select' )
1354 ->with(
1355 'watchlist',
1356 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1357 [ 'wl_user' => 1 ]
1358 )
1359 ->will( $this->returnValue( [
1360 $this->getFakeRow( [
1361 'wl_namespace' => 0,
1362 'wl_title' => 'Foo1',
1363 'wl_notificationtimestamp' => '20151212010101',
1364 ] ),
1365 $this->getFakeRow( [
1366 'wl_namespace' => 1,
1367 'wl_title' => 'Foo2',
1368 'wl_notificationtimestamp' => null,
1369 ] ),
1370 ] ) );
1371
1372 $mockCache = $this->getMockCache();
1373 $mockCache->expects( $this->never() )->method( 'delete' );
1374 $mockCache->expects( $this->never() )->method( 'get' );
1375 $mockCache->expects( $this->never() )->method( 'set' );
1376
1377 $store = new WatchedItemStore(
1378 $this->getMockLoadBalancer( $mockDb ),
1379 $mockCache
1380 );
1381 $user = $this->getMockNonAnonUserWithId( 1 );
1382
1383 $watchedItems = $store->getWatchedItemsForUser( $user );
1384
1385 $this->assertInternalType( 'array', $watchedItems );
1386 $this->assertCount( 2, $watchedItems );
1387 foreach ( $watchedItems as $watchedItem ) {
1388 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1389 }
1390 $this->assertEquals(
1391 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1392 $watchedItems[0]
1393 );
1394 $this->assertEquals(
1395 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1396 $watchedItems[1]
1397 );
1398 }
1399
1400 public function provideDbTypes() {
1401 return [
1402 [ false, DB_SLAVE ],
1403 [ true, DB_MASTER ],
1404 ];
1405 }
1406
1407 /**
1408 * @dataProvider provideDbTypes
1409 */
1410 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1411 $mockDb = $this->getMockDb();
1412 $mockCache = $this->getMockCache();
1413 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1414 $user = $this->getMockNonAnonUserWithId( 1 );
1415
1416 $mockDb->expects( $this->once() )
1417 ->method( 'select' )
1418 ->with(
1419 'watchlist',
1420 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1421 [ 'wl_user' => 1 ],
1422 $this->isType( 'string' ),
1423 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1424 )
1425 ->will( $this->returnValue( [] ) );
1426
1427 $store = new WatchedItemStore(
1428 $mockLoadBalancer,
1429 $mockCache
1430 );
1431
1432 $watchedItems = $store->getWatchedItemsForUser(
1433 $user,
1434 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1435 );
1436 $this->assertEquals( [], $watchedItems );
1437 }
1438
1439 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1440 $store = new WatchedItemStore(
1441 $this->getMockLoadBalancer( $this->getMockDb() ),
1442 $this->getMockCache()
1443 );
1444
1445 $this->setExpectedException( 'InvalidArgumentException' );
1446 $store->getWatchedItemsForUser(
1447 $this->getMockNonAnonUserWithId( 1 ),
1448 [ 'sort' => 'foo' ]
1449 );
1450 }
1451
1452 public function testIsWatchedItem_existingItem() {
1453 $mockDb = $this->getMockDb();
1454 $mockDb->expects( $this->once() )
1455 ->method( 'selectRow' )
1456 ->with(
1457 'watchlist',
1458 'wl_notificationtimestamp',
1459 [
1460 'wl_user' => 1,
1461 'wl_namespace' => 0,
1462 'wl_title' => 'SomeDbKey',
1463 ]
1464 )
1465 ->will( $this->returnValue(
1466 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1467 ) );
1468
1469 $mockCache = $this->getMockCache();
1470 $mockCache->expects( $this->never() )->method( 'delete' );
1471 $mockCache->expects( $this->once() )
1472 ->method( 'get' )
1473 ->with( '0:SomeDbKey:1' )
1474 ->will( $this->returnValue( false ) );
1475 $mockCache->expects( $this->once() )
1476 ->method( 'set' )
1477 ->with(
1478 '0:SomeDbKey:1'
1479 );
1480
1481 $store = new WatchedItemStore(
1482 $this->getMockLoadBalancer( $mockDb ),
1483 $mockCache
1484 );
1485
1486 $this->assertTrue(
1487 $store->isWatched(
1488 $this->getMockNonAnonUserWithId( 1 ),
1489 new TitleValue( 0, 'SomeDbKey' )
1490 )
1491 );
1492 }
1493
1494 public function testIsWatchedItem_noItem() {
1495 $mockDb = $this->getMockDb();
1496 $mockDb->expects( $this->once() )
1497 ->method( 'selectRow' )
1498 ->with(
1499 'watchlist',
1500 'wl_notificationtimestamp',
1501 [
1502 'wl_user' => 1,
1503 'wl_namespace' => 0,
1504 'wl_title' => 'SomeDbKey',
1505 ]
1506 )
1507 ->will( $this->returnValue( [] ) );
1508
1509 $mockCache = $this->getMockCache();
1510 $mockCache->expects( $this->never() )->method( 'set' );
1511 $mockCache->expects( $this->never() )->method( 'delete' );
1512 $mockCache->expects( $this->once() )
1513 ->method( 'get' )
1514 ->with( '0:SomeDbKey:1' )
1515 ->will( $this->returnValue( false ) );
1516
1517 $store = new WatchedItemStore(
1518 $this->getMockLoadBalancer( $mockDb ),
1519 $mockCache
1520 );
1521
1522 $this->assertFalse(
1523 $store->isWatched(
1524 $this->getMockNonAnonUserWithId( 1 ),
1525 new TitleValue( 0, 'SomeDbKey' )
1526 )
1527 );
1528 }
1529
1530 public function testIsWatchedItem_anonymousUser() {
1531 $mockDb = $this->getMockDb();
1532 $mockDb->expects( $this->never() )
1533 ->method( 'selectRow' );
1534
1535 $mockCache = $this->getMockCache();
1536 $mockCache->expects( $this->never() )->method( 'set' );
1537 $mockCache->expects( $this->never() )->method( 'get' );
1538 $mockCache->expects( $this->never() )->method( 'delete' );
1539
1540 $store = new WatchedItemStore(
1541 $this->getMockLoadBalancer( $mockDb ),
1542 $mockCache
1543 );
1544
1545 $this->assertFalse(
1546 $store->isWatched(
1547 $this->getAnonUser(),
1548 new TitleValue( 0, 'SomeDbKey' )
1549 )
1550 );
1551 }
1552
1553 public function testGetNotificationTimestampsBatch() {
1554 $targets = [
1555 new TitleValue( 0, 'SomeDbKey' ),
1556 new TitleValue( 1, 'AnotherDbKey' ),
1557 ];
1558
1559 $mockDb = $this->getMockDb();
1560 $dbResult = [
1561 $this->getFakeRow( [
1562 'wl_namespace' => 0,
1563 'wl_title' => 'SomeDbKey',
1564 'wl_notificationtimestamp' => '20151212010101',
1565 ] ),
1566 $this->getFakeRow(
1567 [
1568 'wl_namespace' => 1,
1569 'wl_title' => 'AnotherDbKey',
1570 'wl_notificationtimestamp' => null,
1571 ]
1572 ),
1573 ];
1574
1575 $mockDb->expects( $this->once() )
1576 ->method( 'makeWhereFrom2d' )
1577 ->with(
1578 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1579 $this->isType( 'string' ),
1580 $this->isType( 'string' )
1581 )
1582 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1583 $mockDb->expects( $this->once() )
1584 ->method( 'select' )
1585 ->with(
1586 'watchlist',
1587 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1588 [
1589 'makeWhereFrom2d return value',
1590 'wl_user' => 1
1591 ],
1592 $this->isType( 'string' )
1593 )
1594 ->will( $this->returnValue( $dbResult ) );
1595
1596 $mockCache = $this->getMockCache();
1597 $mockCache->expects( $this->exactly( 2 ) )
1598 ->method( 'get' )
1599 ->withConsecutive(
1600 [ '0:SomeDbKey:1' ],
1601 [ '1:AnotherDbKey:1' ]
1602 )
1603 ->will( $this->returnValue( null ) );
1604 $mockCache->expects( $this->never() )->method( 'set' );
1605 $mockCache->expects( $this->never() )->method( 'delete' );
1606
1607 $store = new WatchedItemStore(
1608 $this->getMockLoadBalancer( $mockDb ),
1609 $mockCache
1610 );
1611
1612 $this->assertEquals(
1613 [
1614 0 => [ 'SomeDbKey' => '20151212010101', ],
1615 1 => [ 'AnotherDbKey' => null, ],
1616 ],
1617 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1618 );
1619 }
1620
1621 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1622 $targets = [
1623 new TitleValue( 0, 'OtherDbKey' ),
1624 ];
1625
1626 $mockDb = $this->getMockDb();
1627
1628 $mockDb->expects( $this->once() )
1629 ->method( 'makeWhereFrom2d' )
1630 ->with(
1631 [ [ 'OtherDbKey' => 1 ] ],
1632 $this->isType( 'string' ),
1633 $this->isType( 'string' )
1634 )
1635 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1636 $mockDb->expects( $this->once() )
1637 ->method( 'select' )
1638 ->with(
1639 'watchlist',
1640 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1641 [
1642 'makeWhereFrom2d return value',
1643 'wl_user' => 1
1644 ],
1645 $this->isType( 'string' )
1646 )
1647 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1648
1649 $mockCache = $this->getMockCache();
1650 $mockCache->expects( $this->once() )
1651 ->method( 'get' )
1652 ->with( '0:OtherDbKey:1' )
1653 ->will( $this->returnValue( null ) );
1654 $mockCache->expects( $this->never() )->method( 'set' );
1655 $mockCache->expects( $this->never() )->method( 'delete' );
1656
1657 $store = new WatchedItemStore(
1658 $this->getMockLoadBalancer( $mockDb ),
1659 $mockCache
1660 );
1661
1662 $this->assertEquals(
1663 [
1664 0 => [ 'OtherDbKey' => false, ],
1665 ],
1666 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1667 );
1668 }
1669
1670 public function testGetNotificationTimestampsBatch_cachedItem() {
1671 $targets = [
1672 new TitleValue( 0, 'SomeDbKey' ),
1673 new TitleValue( 1, 'AnotherDbKey' ),
1674 ];
1675
1676 $user = $this->getMockNonAnonUserWithId( 1 );
1677 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1678
1679 $mockDb = $this->getMockDb();
1680
1681 $mockDb->expects( $this->once() )
1682 ->method( 'makeWhereFrom2d' )
1683 ->with(
1684 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1685 $this->isType( 'string' ),
1686 $this->isType( 'string' )
1687 )
1688 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1689 $mockDb->expects( $this->once() )
1690 ->method( 'select' )
1691 ->with(
1692 'watchlist',
1693 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1694 [
1695 'makeWhereFrom2d return value',
1696 'wl_user' => 1
1697 ],
1698 $this->isType( 'string' )
1699 )
1700 ->will( $this->returnValue( [
1701 $this->getFakeRow(
1702 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1703 )
1704 ] ) );
1705
1706 $mockCache = $this->getMockCache();
1707 $mockCache->expects( $this->at( 1 ) )
1708 ->method( 'get' )
1709 ->with( '0:SomeDbKey:1' )
1710 ->will( $this->returnValue( $cachedItem ) );
1711 $mockCache->expects( $this->at( 3 ) )
1712 ->method( 'get' )
1713 ->with( '1:AnotherDbKey:1' )
1714 ->will( $this->returnValue( null ) );
1715 $mockCache->expects( $this->never() )->method( 'set' );
1716 $mockCache->expects( $this->never() )->method( 'delete' );
1717
1718 $store = new WatchedItemStore(
1719 $this->getMockLoadBalancer( $mockDb ),
1720 $mockCache
1721 );
1722
1723 $this->assertEquals(
1724 [
1725 0 => [ 'SomeDbKey' => '20151212010101', ],
1726 1 => [ 'AnotherDbKey' => null, ],
1727 ],
1728 $store->getNotificationTimestampsBatch( $user, $targets )
1729 );
1730 }
1731
1732 public function testGetNotificationTimestampsBatch_allItemsCached() {
1733 $targets = [
1734 new TitleValue( 0, 'SomeDbKey' ),
1735 new TitleValue( 1, 'AnotherDbKey' ),
1736 ];
1737
1738 $user = $this->getMockNonAnonUserWithId( 1 );
1739 $cachedItems = [
1740 new WatchedItem( $user, $targets[0], '20151212010101' ),
1741 new WatchedItem( $user, $targets[1], null ),
1742 ];
1743 $mockDb = $this->getMockDb();
1744 $mockDb->expects( $this->never() )->method( $this->anything() );
1745
1746 $mockCache = $this->getMockCache();
1747 $mockCache->expects( $this->at( 1 ) )
1748 ->method( 'get' )
1749 ->with( '0:SomeDbKey:1' )
1750 ->will( $this->returnValue( $cachedItems[0] ) );
1751 $mockCache->expects( $this->at( 3 ) )
1752 ->method( 'get' )
1753 ->with( '1:AnotherDbKey:1' )
1754 ->will( $this->returnValue( $cachedItems[1] ) );
1755 $mockCache->expects( $this->never() )->method( 'set' );
1756 $mockCache->expects( $this->never() )->method( 'delete' );
1757
1758 $store = new WatchedItemStore(
1759 $this->getMockLoadBalancer( $mockDb ),
1760 $mockCache
1761 );
1762
1763 $this->assertEquals(
1764 [
1765 0 => [ 'SomeDbKey' => '20151212010101', ],
1766 1 => [ 'AnotherDbKey' => null, ],
1767 ],
1768 $store->getNotificationTimestampsBatch( $user, $targets )
1769 );
1770 }
1771
1772 public function testGetNotificationTimestampsBatch_anonymousUser() {
1773 $targets = [
1774 new TitleValue( 0, 'SomeDbKey' ),
1775 new TitleValue( 1, 'AnotherDbKey' ),
1776 ];
1777
1778 $mockDb = $this->getMockDb();
1779 $mockDb->expects( $this->never() )->method( $this->anything() );
1780
1781 $mockCache = $this->getMockCache();
1782 $mockCache->expects( $this->never() )->method( $this->anything() );
1783
1784 $store = new WatchedItemStore(
1785 $this->getMockLoadBalancer( $mockDb ),
1786 $mockCache
1787 );
1788
1789 $this->assertEquals(
1790 [
1791 0 => [ 'SomeDbKey' => false, ],
1792 1 => [ 'AnotherDbKey' => false, ],
1793 ],
1794 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1795 );
1796 }
1797
1798 public function testResetNotificationTimestamp_anonymousUser() {
1799 $mockDb = $this->getMockDb();
1800 $mockDb->expects( $this->never() )
1801 ->method( 'selectRow' );
1802
1803 $mockCache = $this->getMockCache();
1804 $mockCache->expects( $this->never() )->method( 'get' );
1805 $mockCache->expects( $this->never() )->method( 'set' );
1806 $mockCache->expects( $this->never() )->method( 'delete' );
1807
1808 $store = new WatchedItemStore(
1809 $this->getMockLoadBalancer( $mockDb ),
1810 $mockCache
1811 );
1812
1813 $this->assertFalse(
1814 $store->resetNotificationTimestamp(
1815 $this->getAnonUser(),
1816 Title::newFromText( 'SomeDbKey' )
1817 )
1818 );
1819 }
1820
1821 public function testResetNotificationTimestamp_noItem() {
1822 $mockDb = $this->getMockDb();
1823 $mockDb->expects( $this->once() )
1824 ->method( 'selectRow' )
1825 ->with(
1826 'watchlist',
1827 'wl_notificationtimestamp',
1828 [
1829 'wl_user' => 1,
1830 'wl_namespace' => 0,
1831 'wl_title' => 'SomeDbKey',
1832 ]
1833 )
1834 ->will( $this->returnValue( [] ) );
1835
1836 $mockCache = $this->getMockCache();
1837 $mockCache->expects( $this->never() )->method( 'get' );
1838 $mockCache->expects( $this->never() )->method( 'set' );
1839 $mockCache->expects( $this->never() )->method( 'delete' );
1840
1841 $store = new WatchedItemStore(
1842 $this->getMockLoadBalancer( $mockDb ),
1843 $mockCache
1844 );
1845
1846 $this->assertFalse(
1847 $store->resetNotificationTimestamp(
1848 $this->getMockNonAnonUserWithId( 1 ),
1849 Title::newFromText( 'SomeDbKey' )
1850 )
1851 );
1852 }
1853
1854 public function testResetNotificationTimestamp_item() {
1855 $user = $this->getMockNonAnonUserWithId( 1 );
1856 $title = Title::newFromText( 'SomeDbKey' );
1857
1858 $mockDb = $this->getMockDb();
1859 $mockDb->expects( $this->once() )
1860 ->method( 'selectRow' )
1861 ->with(
1862 'watchlist',
1863 'wl_notificationtimestamp',
1864 [
1865 'wl_user' => 1,
1866 'wl_namespace' => 0,
1867 'wl_title' => 'SomeDbKey',
1868 ]
1869 )
1870 ->will( $this->returnValue(
1871 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1872 ) );
1873
1874 $mockCache = $this->getMockCache();
1875 $mockCache->expects( $this->never() )->method( 'get' );
1876 $mockCache->expects( $this->once() )
1877 ->method( 'set' )
1878 ->with(
1879 '0:SomeDbKey:1',
1880 $this->isInstanceOf( WatchedItem::class )
1881 );
1882 $mockCache->expects( $this->once() )
1883 ->method( 'delete' )
1884 ->with( '0:SomeDbKey:1' );
1885
1886 $store = new WatchedItemStore(
1887 $this->getMockLoadBalancer( $mockDb ),
1888 $mockCache
1889 );
1890
1891 // Note: This does not actually assert the job is correct
1892 $callableCallCounter = 0;
1893 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1894 $callableCallCounter++;
1895 $this->assertInternalType( 'callable', $callable );
1896 };
1897 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1898
1899 $this->assertTrue(
1900 $store->resetNotificationTimestamp(
1901 $user,
1902 $title
1903 )
1904 );
1905 $this->assertEquals( 1, $callableCallCounter );
1906
1907 ScopedCallback::consume( $scopedOverride );
1908 }
1909
1910 public function testResetNotificationTimestamp_noItemForced() {
1911 $user = $this->getMockNonAnonUserWithId( 1 );
1912 $title = Title::newFromText( 'SomeDbKey' );
1913
1914 $mockDb = $this->getMockDb();
1915 $mockDb->expects( $this->never() )
1916 ->method( 'selectRow' );
1917
1918 $mockCache = $this->getMockCache();
1919 $mockDb->expects( $this->never() )
1920 ->method( 'get' );
1921 $mockDb->expects( $this->never() )
1922 ->method( 'set' );
1923 $mockDb->expects( $this->never() )
1924 ->method( 'delete' );
1925
1926 $store = new WatchedItemStore(
1927 $this->getMockLoadBalancer( $mockDb ),
1928 $mockCache
1929 );
1930
1931 // Note: This does not actually assert the job is correct
1932 $callableCallCounter = 0;
1933 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1934 $callableCallCounter++;
1935 $this->assertInternalType( 'callable', $callable );
1936 };
1937 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1938
1939 $this->assertTrue(
1940 $store->resetNotificationTimestamp(
1941 $user,
1942 $title,
1943 'force'
1944 )
1945 );
1946 $this->assertEquals( 1, $callableCallCounter );
1947
1948 ScopedCallback::consume( $scopedOverride );
1949 }
1950
1951 /**
1952 * @param $text
1953 * @param int $ns
1954 *
1955 * @return PHPUnit_Framework_MockObject_MockObject|Title
1956 */
1957 private function getMockTitle( $text, $ns = 0 ) {
1958 $title = $this->getMock( Title::class );
1959 $title->expects( $this->any() )
1960 ->method( 'getText' )
1961 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1962 $title->expects( $this->any() )
1963 ->method( 'getDbKey' )
1964 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1965 $title->expects( $this->any() )
1966 ->method( 'getNamespace' )
1967 ->will( $this->returnValue( $ns ) );
1968 return $title;
1969 }
1970
1971 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1972 $user = $this->getMockNonAnonUserWithId( 1 );
1973 $oldid = 22;
1974 $title = $this->getMockTitle( 'SomeTitle' );
1975 $title->expects( $this->once() )
1976 ->method( 'getNextRevisionID' )
1977 ->with( $oldid )
1978 ->will( $this->returnValue( false ) );
1979
1980 $mockDb = $this->getMockDb();
1981 $mockDb->expects( $this->never() )
1982 ->method( 'selectRow' );
1983
1984 $mockCache = $this->getMockCache();
1985 $mockDb->expects( $this->never() )
1986 ->method( 'get' );
1987 $mockDb->expects( $this->never() )
1988 ->method( 'set' );
1989 $mockDb->expects( $this->never() )
1990 ->method( 'delete' );
1991
1992 $store = new WatchedItemStore(
1993 $this->getMockLoadBalancer( $mockDb ),
1994 $mockCache
1995 );
1996
1997 // Note: This does not actually assert the job is correct
1998 $callableCallCounter = 0;
1999 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2000 function( $callable ) use ( &$callableCallCounter ) {
2001 $callableCallCounter++;
2002 $this->assertInternalType( 'callable', $callable );
2003 }
2004 );
2005
2006 $this->assertTrue(
2007 $store->resetNotificationTimestamp(
2008 $user,
2009 $title,
2010 'force',
2011 $oldid
2012 )
2013 );
2014 $this->assertEquals( 1, $callableCallCounter );
2015
2016 ScopedCallback::consume( $scopedOverride );
2017 }
2018
2019 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2020 $user = $this->getMockNonAnonUserWithId( 1 );
2021 $oldid = 22;
2022 $title = $this->getMockTitle( 'SomeDbKey' );
2023 $title->expects( $this->once() )
2024 ->method( 'getNextRevisionID' )
2025 ->with( $oldid )
2026 ->will( $this->returnValue( 33 ) );
2027
2028 $mockDb = $this->getMockDb();
2029 $mockDb->expects( $this->once() )
2030 ->method( 'selectRow' )
2031 ->with(
2032 'watchlist',
2033 'wl_notificationtimestamp',
2034 [
2035 'wl_user' => 1,
2036 'wl_namespace' => 0,
2037 'wl_title' => 'SomeDbKey',
2038 ]
2039 )
2040 ->will( $this->returnValue(
2041 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2042 ) );
2043
2044 $mockCache = $this->getMockCache();
2045 $mockDb->expects( $this->never() )
2046 ->method( 'get' );
2047 $mockDb->expects( $this->never() )
2048 ->method( 'set' );
2049 $mockDb->expects( $this->never() )
2050 ->method( 'delete' );
2051
2052 $store = new WatchedItemStore(
2053 $this->getMockLoadBalancer( $mockDb ),
2054 $mockCache
2055 );
2056
2057 // Note: This does not actually assert the job is correct
2058 $addUpdateCallCounter = 0;
2059 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2060 function( $callable ) use ( &$addUpdateCallCounter ) {
2061 $addUpdateCallCounter++;
2062 $this->assertInternalType( 'callable', $callable );
2063 }
2064 );
2065
2066 $getTimestampCallCounter = 0;
2067 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2068 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2069 $getTimestampCallCounter++;
2070 $this->assertEquals( $title, $titleParam );
2071 $this->assertEquals( $oldid, $oldidParam );
2072 }
2073 );
2074
2075 $this->assertTrue(
2076 $store->resetNotificationTimestamp(
2077 $user,
2078 $title,
2079 'force',
2080 $oldid
2081 )
2082 );
2083 $this->assertEquals( 1, $addUpdateCallCounter );
2084 $this->assertEquals( 1, $getTimestampCallCounter );
2085
2086 ScopedCallback::consume( $scopedOverrideDeferred );
2087 ScopedCallback::consume( $scopedOverrideRevision );
2088 }
2089
2090 public function testUpdateNotificationTimestamp_watchersExist() {
2091 $mockDb = $this->getMockDb();
2092 $mockDb->expects( $this->once() )
2093 ->method( 'select' )
2094 ->with(
2095 [ 'watchlist' ],
2096 [ 'wl_user' ],
2097 [
2098 'wl_user != 1',
2099 'wl_namespace' => 0,
2100 'wl_title' => 'SomeDbKey',
2101 'wl_notificationtimestamp IS NULL'
2102 ]
2103 )
2104 ->will(
2105 $this->returnValue( [
2106 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2107 $this->getFakeRow( [ 'wl_user' => '3' ] )
2108 ] )
2109 );
2110 $mockDb->expects( $this->once() )
2111 ->method( 'onTransactionIdle' )
2112 ->with( $this->isType( 'callable' ) )
2113 ->will( $this->returnCallback( function( $callable ) {
2114 $callable();
2115 } ) );
2116 $mockDb->expects( $this->once() )
2117 ->method( 'update' )
2118 ->with(
2119 'watchlist',
2120 [ 'wl_notificationtimestamp' => null ],
2121 [
2122 'wl_user' => [ 2, 3 ],
2123 'wl_namespace' => 0,
2124 'wl_title' => 'SomeDbKey',
2125 ]
2126 );
2127
2128 $mockCache = $this->getMockCache();
2129 $mockCache->expects( $this->never() )->method( 'set' );
2130 $mockCache->expects( $this->never() )->method( 'get' );
2131 $mockCache->expects( $this->never() )->method( 'delete' );
2132
2133 $store = new WatchedItemStore(
2134 $this->getMockLoadBalancer( $mockDb ),
2135 $mockCache
2136 );
2137
2138 $this->assertEquals(
2139 [ 2, 3 ],
2140 $store->updateNotificationTimestamp(
2141 $this->getMockNonAnonUserWithId( 1 ),
2142 new TitleValue( 0, 'SomeDbKey' ),
2143 '20151212010101'
2144 )
2145 );
2146 }
2147
2148 public function testUpdateNotificationTimestamp_noWatchers() {
2149 $mockDb = $this->getMockDb();
2150 $mockDb->expects( $this->once() )
2151 ->method( 'select' )
2152 ->with(
2153 [ 'watchlist' ],
2154 [ 'wl_user' ],
2155 [
2156 'wl_user != 1',
2157 'wl_namespace' => 0,
2158 'wl_title' => 'SomeDbKey',
2159 'wl_notificationtimestamp IS NULL'
2160 ]
2161 )
2162 ->will(
2163 $this->returnValue( [] )
2164 );
2165 $mockDb->expects( $this->never() )
2166 ->method( 'onTransactionIdle' );
2167 $mockDb->expects( $this->never() )
2168 ->method( 'update' );
2169
2170 $mockCache = $this->getMockCache();
2171 $mockCache->expects( $this->never() )->method( 'set' );
2172 $mockCache->expects( $this->never() )->method( 'get' );
2173 $mockCache->expects( $this->never() )->method( 'delete' );
2174
2175 $store = new WatchedItemStore(
2176 $this->getMockLoadBalancer( $mockDb ),
2177 $mockCache
2178 );
2179
2180 $watchers = $store->updateNotificationTimestamp(
2181 $this->getMockNonAnonUserWithId( 1 ),
2182 new TitleValue( 0, 'SomeDbKey' ),
2183 '20151212010101'
2184 );
2185 $this->assertInternalType( 'array', $watchers );
2186 $this->assertEmpty( $watchers );
2187 }
2188
2189 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2190 $user = $this->getMockNonAnonUserWithId( 1 );
2191 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2192
2193 $mockDb = $this->getMockDb();
2194 $mockDb->expects( $this->once() )
2195 ->method( 'selectRow' )
2196 ->will( $this->returnValue(
2197 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2198 ) );
2199 $mockDb->expects( $this->once() )
2200 ->method( 'select' )
2201 ->will(
2202 $this->returnValue( [
2203 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2204 $this->getFakeRow( [ 'wl_user' => '3' ] )
2205 ] )
2206 );
2207 $mockDb->expects( $this->once() )
2208 ->method( 'onTransactionIdle' )
2209 ->with( $this->isType( 'callable' ) )
2210 ->will( $this->returnCallback( function( $callable ) {
2211 $callable();
2212 } ) );
2213 $mockDb->expects( $this->once() )
2214 ->method( 'update' );
2215
2216 $mockCache = $this->getMockCache();
2217 $mockCache->expects( $this->once() )
2218 ->method( 'set' )
2219 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2220 $mockCache->expects( $this->once() )
2221 ->method( 'get' )
2222 ->with( '0:SomeDbKey:1' );
2223 $mockCache->expects( $this->once() )
2224 ->method( 'delete' )
2225 ->with( '0:SomeDbKey:1' );
2226
2227 $store = new WatchedItemStore(
2228 $this->getMockLoadBalancer( $mockDb ),
2229 $mockCache
2230 );
2231
2232 // This will add the item to the cache
2233 $store->getWatchedItem( $user, $titleValue );
2234
2235 $store->updateNotificationTimestamp(
2236 $this->getMockNonAnonUserWithId( 1 ),
2237 $titleValue,
2238 '20151212010101'
2239 );
2240 }
2241
2242 }