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