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