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