Reset WatchedItemStore default instance after tests
[lhc/web/wiklou.git] / tests / phpunit / includes / WatchedItemStoreUnitTest.php
1 <?php
2
3 /**
4 * @author Addshore
5 *
6 * @covers WatchedItemStore
7 */
8 class WatchedItemStoreUnitTest extends PHPUnit_Framework_TestCase {
9
10 /**
11 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
12 */
13 private function getMockDb() {
14 return $this->getMock( IDatabase::class );
15 }
16
17 /**
18 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
19 */
20 private function getMockLoadBalancer( $mockDb ) {
21 $mock = $this->getMockBuilder( LoadBalancer::class )
22 ->disableOriginalConstructor()
23 ->getMock();
24 $mock->expects( $this->any() )
25 ->method( 'getConnection' )
26 ->will( $this->returnValue( $mockDb ) );
27 $mock->expects( $this->any() )
28 ->method( 'getReadOnlyReason' )
29 ->will( $this->returnValue( false ) );
30 return $mock;
31 }
32
33 /**
34 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
35 */
36 private function getMockCache() {
37 $mock = $this->getMockBuilder( HashBagOStuff::class )
38 ->disableOriginalConstructor()
39 ->getMock();
40 $mock->expects( $this->any() )
41 ->method( 'makeKey' )
42 ->will( $this->returnCallback( function() {
43 return implode( ':', func_get_args() );
44 } ) );
45 return $mock;
46 }
47
48 /**
49 * @param int $id
50 * @return PHPUnit_Framework_MockObject_MockObject|User
51 */
52 private function getMockNonAnonUserWithId( $id ) {
53 $mock = $this->getMock( User::class );
54 $mock->expects( $this->any() )
55 ->method( 'isAnon' )
56 ->will( $this->returnValue( false ) );
57 $mock->expects( $this->any() )
58 ->method( 'getId' )
59 ->will( $this->returnValue( $id ) );
60 return $mock;
61 }
62
63 /**
64 * @return User
65 */
66 private function getAnonUser() {
67 return User::newFromName( 'Anon_User' );
68 }
69
70 private function getFakeRow( array $rowValues ) {
71 $fakeRow = new stdClass();
72 foreach ( $rowValues as $valueName => $value ) {
73 $fakeRow->$valueName = $value;
74 }
75 return $fakeRow;
76 }
77
78 public function testGetDefaultInstance() {
79 $instanceOne = WatchedItemStore::getDefaultInstance();
80 $instanceTwo = WatchedItemStore::getDefaultInstance();
81 $this->assertSame( $instanceOne, $instanceTwo );
82 }
83
84 public function testOverrideDefaultInstance() {
85 $instance = WatchedItemStore::getDefaultInstance();
86 $scopedOverride = $instance->overrideDefaultInstance( null );
87
88 $this->assertNotSame( $instance, WatchedItemStore::getDefaultInstance() );
89
90 unset( $scopedOverride );
91
92 $this->assertSame( $instance, WatchedItemStore::getDefaultInstance() );
93 }
94
95 public function testCountWatchers() {
96 $titleValue = new TitleValue( 0, 'SomeDbKey' );
97
98 $mockDb = $this->getMockDb();
99 $mockDb->expects( $this->exactly( 1 ) )
100 ->method( 'selectField' )
101 ->with(
102 'watchlist',
103 'COUNT(*)',
104 [
105 'wl_namespace' => $titleValue->getNamespace(),
106 'wl_title' => $titleValue->getDBkey(),
107 ],
108 $this->isType( 'string' )
109 )
110 ->will( $this->returnValue( 7 ) );
111
112 $mockCache = $this->getMockCache();
113 $mockCache->expects( $this->never() )->method( 'get' );
114 $mockCache->expects( $this->never() )->method( 'set' );
115 $mockCache->expects( $this->never() )->method( 'delete' );
116
117 $store = new WatchedItemStore(
118 $this->getMockLoadBalancer( $mockDb ),
119 $mockCache
120 );
121
122 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
123 }
124
125 public function testCountWatchersMultiple() {
126 $titleValues = [
127 new TitleValue( 0, 'SomeDbKey' ),
128 new TitleValue( 0, 'OtherDbKey' ),
129 new TitleValue( 1, 'AnotherDbKey' ),
130 ];
131
132 $mockDb = $this->getMockDb();
133
134 $dbResult = [
135 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
136 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
137 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
138 ),
139 ];
140 $mockDb->expects( $this->once() )
141 ->method( 'makeWhereFrom2d' )
142 ->with(
143 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
144 $this->isType( 'string' ),
145 $this->isType( 'string' )
146 )
147 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
148 $mockDb->expects( $this->once() )
149 ->method( 'select' )
150 ->with(
151 'watchlist',
152 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
153 [ 'makeWhereFrom2d return value' ],
154 $this->isType( 'string' ),
155 [
156 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
157 ]
158 )
159 ->will(
160 $this->returnValue( $dbResult )
161 );
162
163 $mockCache = $this->getMockCache();
164 $mockCache->expects( $this->never() )->method( 'get' );
165 $mockCache->expects( $this->never() )->method( 'set' );
166 $mockCache->expects( $this->never() )->method( 'delete' );
167
168 $store = new WatchedItemStore(
169 $this->getMockLoadBalancer( $mockDb ),
170 $mockCache
171 );
172
173 $expected = [
174 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
175 1 => [ 'AnotherDbKey' => 500 ],
176 ];
177 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
178 }
179
180 public function provideIntWithDbUnsafeVersion() {
181 return [
182 [ 50 ],
183 [ "50; DROP TABLE watchlist;\n--" ],
184 ];
185 }
186
187 /**
188 * @dataProvider provideIntWithDbUnsafeVersion
189 */
190 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
191 $titleValues = [
192 new TitleValue( 0, 'SomeDbKey' ),
193 new TitleValue( 0, 'OtherDbKey' ),
194 new TitleValue( 1, 'AnotherDbKey' ),
195 ];
196
197 $mockDb = $this->getMockDb();
198
199 $dbResult = [
200 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
201 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
202 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
203 ),
204 ];
205 $mockDb->expects( $this->once() )
206 ->method( 'makeWhereFrom2d' )
207 ->with(
208 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
209 $this->isType( 'string' ),
210 $this->isType( 'string' )
211 )
212 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
213 $mockDb->expects( $this->once() )
214 ->method( 'select' )
215 ->with(
216 'watchlist',
217 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
218 [ 'makeWhereFrom2d return value' ],
219 $this->isType( 'string' ),
220 [
221 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
222 'HAVING' => 'COUNT(*) >= 50',
223 ]
224 )
225 ->will(
226 $this->returnValue( $dbResult )
227 );
228
229 $mockCache = $this->getMockCache();
230 $mockCache->expects( $this->never() )->method( 'get' );
231 $mockCache->expects( $this->never() )->method( 'set' );
232 $mockCache->expects( $this->never() )->method( 'delete' );
233
234 $store = new WatchedItemStore(
235 $this->getMockLoadBalancer( $mockDb ),
236 $mockCache
237 );
238
239 $expected = [
240 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
241 1 => [ 'AnotherDbKey' => 500 ],
242 ];
243 $this->assertEquals(
244 $expected,
245 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
246 );
247 }
248
249 public function testCountVisitingWatchers() {
250 $titleValue = new TitleValue( 0, 'SomeDbKey' );
251
252 $mockDb = $this->getMockDb();
253 $mockDb->expects( $this->exactly( 1 ) )
254 ->method( 'selectField' )
255 ->with(
256 'watchlist',
257 'COUNT(*)',
258 [
259 'wl_namespace' => $titleValue->getNamespace(),
260 'wl_title' => $titleValue->getDBkey(),
261 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
262 ],
263 $this->isType( 'string' )
264 )
265 ->will( $this->returnValue( 7 ) );
266 $mockDb->expects( $this->exactly( 1 ) )
267 ->method( 'addQuotes' )
268 ->will( $this->returnCallback( function( $value ) {
269 return "'$value'";
270 } ) );
271 $mockDb->expects( $this->exactly( 1 ) )
272 ->method( 'timestamp' )
273 ->will( $this->returnCallback( function( $value ) {
274 return 'TS' . $value . 'TS';
275 } ) );
276
277 $mockCache = $this->getMockCache();
278 $mockCache->expects( $this->never() )->method( 'set' );
279 $mockCache->expects( $this->never() )->method( 'get' );
280 $mockCache->expects( $this->never() )->method( 'delete' );
281
282 $store = new WatchedItemStore(
283 $this->getMockLoadBalancer( $mockDb ),
284 $mockCache
285 );
286
287 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
288 }
289
290 public function testCountUnreadNotifications() {
291 $user = $this->getMockNonAnonUserWithId( 1 );
292
293 $mockDb = $this->getMockDb();
294 $mockDb->expects( $this->exactly( 1 ) )
295 ->method( 'selectRowCount' )
296 ->with(
297 'watchlist',
298 '1',
299 [
300 "wl_notificationtimestamp IS NOT NULL",
301 'wl_user' => 1,
302 ],
303 $this->isType( 'string' )
304 )
305 ->will( $this->returnValue( 9 ) );
306
307 $mockCache = $this->getMockCache();
308 $mockCache->expects( $this->never() )->method( 'set' );
309 $mockCache->expects( $this->never() )->method( 'get' );
310 $mockCache->expects( $this->never() )->method( 'delete' );
311
312 $store = new WatchedItemStore(
313 $this->getMockLoadBalancer( $mockDb ),
314 $mockCache
315 );
316
317 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
318 }
319
320 /**
321 * @dataProvider provideIntWithDbUnsafeVersion
322 */
323 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
324 $user = $this->getMockNonAnonUserWithId( 1 );
325
326 $mockDb = $this->getMockDb();
327 $mockDb->expects( $this->exactly( 1 ) )
328 ->method( 'selectRowCount' )
329 ->with(
330 'watchlist',
331 '1',
332 [
333 "wl_notificationtimestamp IS NOT NULL",
334 'wl_user' => 1,
335 ],
336 $this->isType( 'string' ),
337 [ 'LIMIT' => 50 ]
338 )
339 ->will( $this->returnValue( 50 ) );
340
341 $mockCache = $this->getMockCache();
342 $mockCache->expects( $this->never() )->method( 'set' );
343 $mockCache->expects( $this->never() )->method( 'get' );
344 $mockCache->expects( $this->never() )->method( 'delete' );
345
346 $store = new WatchedItemStore(
347 $this->getMockLoadBalancer( $mockDb ),
348 $mockCache
349 );
350
351 $this->assertSame(
352 true,
353 $store->countUnreadNotifications( $user, $limit )
354 );
355 }
356
357 /**
358 * @dataProvider provideIntWithDbUnsafeVersion
359 */
360 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
361 $user = $this->getMockNonAnonUserWithId( 1 );
362
363 $mockDb = $this->getMockDb();
364 $mockDb->expects( $this->exactly( 1 ) )
365 ->method( 'selectRowCount' )
366 ->with(
367 'watchlist',
368 '1',
369 [
370 "wl_notificationtimestamp IS NOT NULL",
371 'wl_user' => 1,
372 ],
373 $this->isType( 'string' ),
374 [ 'LIMIT' => 50 ]
375 )
376 ->will( $this->returnValue( 9 ) );
377
378 $mockCache = $this->getMockCache();
379 $mockCache->expects( $this->never() )->method( 'set' );
380 $mockCache->expects( $this->never() )->method( 'get' );
381 $mockCache->expects( $this->never() )->method( 'delete' );
382
383 $store = new WatchedItemStore(
384 $this->getMockLoadBalancer( $mockDb ),
385 $mockCache
386 );
387
388 $this->assertEquals(
389 9,
390 $store->countUnreadNotifications( $user, $limit )
391 );
392 }
393
394 public function testDuplicateEntry_nothingToDuplicate() {
395 $mockDb = $this->getMockDb();
396 $mockDb->expects( $this->once() )
397 ->method( 'select' )
398 ->with(
399 'watchlist',
400 [
401 'wl_user',
402 'wl_notificationtimestamp',
403 ],
404 [
405 'wl_namespace' => 0,
406 'wl_title' => 'Old_Title',
407 ],
408 'WatchedItemStore::duplicateEntry',
409 [ 'FOR UPDATE' ]
410 )
411 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
412
413 $store = new WatchedItemStore(
414 $this->getMockLoadBalancer( $mockDb ),
415 $this->getMockCache()
416 );
417
418 $store->duplicateEntry(
419 Title::newFromText( 'Old_Title' ),
420 Title::newFromText( 'New_Title' )
421 );
422 }
423
424 public function testDuplicateEntry_somethingToDuplicate() {
425 $fakeRows = [
426 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
427 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
428 ];
429
430 $mockDb = $this->getMockDb();
431 $mockDb->expects( $this->at( 0 ) )
432 ->method( 'select' )
433 ->with(
434 'watchlist',
435 [
436 'wl_user',
437 'wl_notificationtimestamp',
438 ],
439 [
440 'wl_namespace' => 0,
441 'wl_title' => 'Old_Title',
442 ]
443 )
444 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
445 $mockDb->expects( $this->at( 1 ) )
446 ->method( 'replace' )
447 ->with(
448 'watchlist',
449 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
450 [
451 [
452 'wl_user' => 1,
453 'wl_namespace' => 0,
454 'wl_title' => 'New_Title',
455 'wl_notificationtimestamp' => '20151212010101',
456 ],
457 [
458 'wl_user' => 2,
459 'wl_namespace' => 0,
460 'wl_title' => 'New_Title',
461 'wl_notificationtimestamp' => null,
462 ],
463 ],
464 $this->isType( 'string' )
465 );
466
467 $mockCache = $this->getMockCache();
468 $mockCache->expects( $this->never() )->method( 'get' );
469 $mockCache->expects( $this->never() )->method( 'delete' );
470
471 $store = new WatchedItemStore(
472 $this->getMockLoadBalancer( $mockDb ),
473 $mockCache
474 );
475
476 $store->duplicateEntry(
477 Title::newFromText( 'Old_Title' ),
478 Title::newFromText( 'New_Title' )
479 );
480 }
481
482 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
483 $mockDb = $this->getMockDb();
484 $mockDb->expects( $this->at( 0 ) )
485 ->method( 'select' )
486 ->with(
487 'watchlist',
488 [
489 'wl_user',
490 'wl_notificationtimestamp',
491 ],
492 [
493 'wl_namespace' => 0,
494 'wl_title' => 'Old_Title',
495 ]
496 )
497 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
498 $mockDb->expects( $this->at( 1 ) )
499 ->method( 'select' )
500 ->with(
501 'watchlist',
502 [
503 'wl_user',
504 'wl_notificationtimestamp',
505 ],
506 [
507 'wl_namespace' => 1,
508 'wl_title' => 'Old_Title',
509 ]
510 )
511 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
512
513 $mockCache = $this->getMockCache();
514 $mockCache->expects( $this->never() )->method( 'get' );
515 $mockCache->expects( $this->never() )->method( 'delete' );
516
517 $store = new WatchedItemStore(
518 $this->getMockLoadBalancer( $mockDb ),
519 $mockCache
520 );
521
522 $store->duplicateAllAssociatedEntries(
523 Title::newFromText( 'Old_Title' ),
524 Title::newFromText( 'New_Title' )
525 );
526 }
527
528 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
529 $fakeRows = [
530 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
531 ];
532
533 $mockDb = $this->getMockDb();
534 $mockDb->expects( $this->at( 0 ) )
535 ->method( 'select' )
536 ->with(
537 'watchlist',
538 [
539 'wl_user',
540 'wl_notificationtimestamp',
541 ],
542 [
543 'wl_namespace' => 0,
544 'wl_title' => 'Old_Title',
545 ]
546 )
547 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
548 $mockDb->expects( $this->at( 1 ) )
549 ->method( 'replace' )
550 ->with(
551 'watchlist',
552 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
553 [
554 [
555 'wl_user' => 1,
556 'wl_namespace' => 0,
557 'wl_title' => 'New_Title',
558 'wl_notificationtimestamp' => '20151212010101',
559 ],
560 ],
561 $this->isType( 'string' )
562 );
563 $mockDb->expects( $this->at( 2 ) )
564 ->method( 'select' )
565 ->with(
566 'watchlist',
567 [
568 'wl_user',
569 'wl_notificationtimestamp',
570 ],
571 [
572 'wl_namespace' => 1,
573 'wl_title' => 'Old_Title',
574 ]
575 )
576 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
577 $mockDb->expects( $this->at( 3 ) )
578 ->method( 'replace' )
579 ->with(
580 'watchlist',
581 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
582 [
583 [
584 'wl_user' => 1,
585 'wl_namespace' => 1,
586 'wl_title' => 'New_Title',
587 'wl_notificationtimestamp' => '20151212010101',
588 ],
589 ],
590 $this->isType( 'string' )
591 );
592
593 $mockCache = $this->getMockCache();
594 $mockCache->expects( $this->never() )->method( 'get' );
595 $mockCache->expects( $this->never() )->method( 'delete' );
596
597 $store = new WatchedItemStore(
598 $this->getMockLoadBalancer( $mockDb ),
599 $mockCache
600 );
601
602 $store->duplicateAllAssociatedEntries(
603 Title::newFromText( 'Old_Title' ),
604 Title::newFromText( 'New_Title' )
605 );
606 }
607
608 public function testAddWatch_nonAnonymousUser() {
609 $mockDb = $this->getMockDb();
610 $mockDb->expects( $this->once() )
611 ->method( 'insert' )
612 ->with(
613 'watchlist',
614 [
615 [
616 'wl_user' => 1,
617 'wl_namespace' => 0,
618 'wl_title' => 'Some_Page',
619 'wl_notificationtimestamp' => null,
620 ]
621 ]
622 );
623
624 $mockCache = $this->getMockCache();
625 $mockCache->expects( $this->once() )
626 ->method( 'delete' )
627 ->with( '0:Some_Page:1' );
628
629 $store = new WatchedItemStore(
630 $this->getMockLoadBalancer( $mockDb ),
631 $mockCache
632 );
633
634 $store->addWatch(
635 $this->getMockNonAnonUserWithId( 1 ),
636 Title::newFromText( 'Some_Page' )
637 );
638 }
639
640 public function testAddWatch_anonymousUser() {
641 $mockDb = $this->getMockDb();
642 $mockDb->expects( $this->never() )
643 ->method( 'insert' );
644
645 $mockCache = $this->getMockCache();
646 $mockCache->expects( $this->never() )
647 ->method( 'delete' );
648
649 $store = new WatchedItemStore(
650 $this->getMockLoadBalancer( $mockDb ),
651 $mockCache
652 );
653
654 $store->addWatch(
655 $this->getAnonUser(),
656 Title::newFromText( 'Some_Page' )
657 );
658 }
659
660 public function testAddWatchBatch_nonAnonymousUser() {
661 $mockDb = $this->getMockDb();
662 $mockDb->expects( $this->once() )
663 ->method( 'insert' )
664 ->with(
665 'watchlist',
666 [
667 [
668 'wl_user' => 1,
669 'wl_namespace' => 0,
670 'wl_title' => 'Some_Page',
671 'wl_notificationtimestamp' => null,
672 ],
673 [
674 'wl_user' => 1,
675 'wl_namespace' => 1,
676 'wl_title' => 'Some_Page',
677 'wl_notificationtimestamp' => null,
678 ]
679 ]
680 );
681
682 $mockCache = $this->getMockCache();
683 $mockCache->expects( $this->exactly( 2 ) )
684 ->method( 'delete' );
685 $mockCache->expects( $this->at( 1 ) )
686 ->method( 'delete' )
687 ->with( '0:Some_Page:1' );
688 $mockCache->expects( $this->at( 3 ) )
689 ->method( 'delete' )
690 ->with( '1:Some_Page:1' );
691
692 $store = new WatchedItemStore(
693 $this->getMockLoadBalancer( $mockDb ),
694 $mockCache
695 );
696
697 $mockUser = $this->getMockNonAnonUserWithId( 1 );
698
699 $this->assertTrue(
700 $store->addWatchBatch(
701 [
702 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
703 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
704 ]
705 )
706 );
707 }
708
709 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
710 $mockDb = $this->getMockDb();
711 $mockDb->expects( $this->once() )
712 ->method( 'insert' )
713 ->with(
714 'watchlist',
715 [
716 [
717 'wl_user' => 1,
718 'wl_namespace' => 0,
719 'wl_title' => 'Some_Page',
720 'wl_notificationtimestamp' => null,
721 ]
722 ]
723 );
724
725 $mockCache = $this->getMockCache();
726 $mockCache->expects( $this->once() )
727 ->method( 'delete' )
728 ->with( '0:Some_Page:1' );
729
730 $store = new WatchedItemStore(
731 $this->getMockLoadBalancer( $mockDb ),
732 $mockCache
733 );
734
735 $this->assertTrue(
736 $store->addWatchBatch(
737 [
738 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
739 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
740 ]
741 )
742 );
743 }
744
745 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
746 $mockDb = $this->getMockDb();
747 $mockDb->expects( $this->never() )
748 ->method( 'insert' );
749
750 $mockCache = $this->getMockCache();
751 $mockCache->expects( $this->never() )
752 ->method( 'delete' );
753
754 $store = new WatchedItemStore(
755 $this->getMockLoadBalancer( $mockDb ),
756 $mockCache
757 );
758
759 $anonUser = $this->getAnonUser();
760 $this->assertFalse(
761 $store->addWatchBatch(
762 [
763 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
764 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
765 ]
766 )
767 );
768 }
769
770 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
771 $mockDb = $this->getMockDb();
772 $mockDb->expects( $this->never() )
773 ->method( 'insert' );
774
775 $mockCache = $this->getMockCache();
776 $mockCache->expects( $this->never() )
777 ->method( 'delete' );
778
779 $store = new WatchedItemStore(
780 $this->getMockLoadBalancer( $mockDb ),
781 $mockCache
782 );
783
784 $this->assertFalse(
785 $store->addWatchBatch( [] )
786 );
787 }
788
789 public function testLoadWatchedItem_existingItem() {
790 $mockDb = $this->getMockDb();
791 $mockDb->expects( $this->once() )
792 ->method( 'selectRow' )
793 ->with(
794 'watchlist',
795 'wl_notificationtimestamp',
796 [
797 'wl_user' => 1,
798 'wl_namespace' => 0,
799 'wl_title' => 'SomeDbKey',
800 ]
801 )
802 ->will( $this->returnValue(
803 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
804 ) );
805
806 $mockCache = $this->getMockCache();
807 $mockCache->expects( $this->once() )
808 ->method( 'set' )
809 ->with(
810 '0:SomeDbKey:1'
811 );
812
813 $store = new WatchedItemStore(
814 $this->getMockLoadBalancer( $mockDb ),
815 $mockCache
816 );
817
818 $watchedItem = $store->loadWatchedItem(
819 $this->getMockNonAnonUserWithId( 1 ),
820 new TitleValue( 0, 'SomeDbKey' )
821 );
822 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
823 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
824 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
825 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
826 }
827
828 public function testLoadWatchedItem_noItem() {
829 $mockDb = $this->getMockDb();
830 $mockDb->expects( $this->once() )
831 ->method( 'selectRow' )
832 ->with(
833 'watchlist',
834 'wl_notificationtimestamp',
835 [
836 'wl_user' => 1,
837 'wl_namespace' => 0,
838 'wl_title' => 'SomeDbKey',
839 ]
840 )
841 ->will( $this->returnValue( [] ) );
842
843 $mockCache = $this->getMockCache();
844 $mockCache->expects( $this->never() )->method( 'get' );
845 $mockCache->expects( $this->never() )->method( 'delete' );
846
847 $store = new WatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
849 $mockCache
850 );
851
852 $this->assertFalse(
853 $store->loadWatchedItem(
854 $this->getMockNonAnonUserWithId( 1 ),
855 new TitleValue( 0, 'SomeDbKey' )
856 )
857 );
858 }
859
860 public function testLoadWatchedItem_anonymousUser() {
861 $mockDb = $this->getMockDb();
862 $mockDb->expects( $this->never() )
863 ->method( 'selectRow' );
864
865 $mockCache = $this->getMockCache();
866 $mockCache->expects( $this->never() )->method( 'get' );
867 $mockCache->expects( $this->never() )->method( 'delete' );
868
869 $store = new WatchedItemStore(
870 $this->getMockLoadBalancer( $mockDb ),
871 $mockCache
872 );
873
874 $this->assertFalse(
875 $store->loadWatchedItem(
876 $this->getAnonUser(),
877 new TitleValue( 0, 'SomeDbKey' )
878 )
879 );
880 }
881
882 public function testRemoveWatch_existingItem() {
883 $mockDb = $this->getMockDb();
884 $mockDb->expects( $this->once() )
885 ->method( 'delete' )
886 ->with(
887 'watchlist',
888 [
889 'wl_user' => 1,
890 'wl_namespace' => 0,
891 'wl_title' => 'SomeDbKey',
892 ]
893 );
894 $mockDb->expects( $this->once() )
895 ->method( 'affectedRows' )
896 ->will( $this->returnValue( 1 ) );
897
898 $mockCache = $this->getMockCache();
899 $mockCache->expects( $this->never() )->method( 'get' );
900 $mockCache->expects( $this->once() )
901 ->method( 'delete' )
902 ->with( '0:SomeDbKey:1' );
903
904 $store = new WatchedItemStore(
905 $this->getMockLoadBalancer( $mockDb ),
906 $mockCache
907 );
908
909 $this->assertTrue(
910 $store->removeWatch(
911 $this->getMockNonAnonUserWithId( 1 ),
912 new TitleValue( 0, 'SomeDbKey' )
913 )
914 );
915 }
916
917 public function testRemoveWatch_noItem() {
918 $mockDb = $this->getMockDb();
919 $mockDb->expects( $this->once() )
920 ->method( 'delete' )
921 ->with(
922 'watchlist',
923 [
924 'wl_user' => 1,
925 'wl_namespace' => 0,
926 'wl_title' => 'SomeDbKey',
927 ]
928 );
929 $mockDb->expects( $this->once() )
930 ->method( 'affectedRows' )
931 ->will( $this->returnValue( 0 ) );
932
933 $mockCache = $this->getMockCache();
934 $mockCache->expects( $this->never() )->method( 'get' );
935 $mockCache->expects( $this->once() )
936 ->method( 'delete' )
937 ->with( '0:SomeDbKey:1' );
938
939 $store = new WatchedItemStore(
940 $this->getMockLoadBalancer( $mockDb ),
941 $mockCache
942 );
943
944 $this->assertFalse(
945 $store->removeWatch(
946 $this->getMockNonAnonUserWithId( 1 ),
947 new TitleValue( 0, 'SomeDbKey' )
948 )
949 );
950 }
951
952 public function testRemoveWatch_anonymousUser() {
953 $mockDb = $this->getMockDb();
954 $mockDb->expects( $this->never() )
955 ->method( 'delete' );
956
957 $mockCache = $this->getMockCache();
958 $mockCache->expects( $this->never() )->method( 'get' );
959 $mockCache->expects( $this->never() )
960 ->method( 'delete' );
961
962 $store = new WatchedItemStore(
963 $this->getMockLoadBalancer( $mockDb ),
964 $mockCache
965 );
966
967 $this->assertFalse(
968 $store->removeWatch(
969 $this->getAnonUser(),
970 new TitleValue( 0, 'SomeDbKey' )
971 )
972 );
973 }
974
975 public function testGetWatchedItem_existingItem() {
976 $mockDb = $this->getMockDb();
977 $mockDb->expects( $this->once() )
978 ->method( 'selectRow' )
979 ->with(
980 'watchlist',
981 'wl_notificationtimestamp',
982 [
983 'wl_user' => 1,
984 'wl_namespace' => 0,
985 'wl_title' => 'SomeDbKey',
986 ]
987 )
988 ->will( $this->returnValue(
989 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
990 ) );
991
992 $mockCache = $this->getMockCache();
993 $mockCache->expects( $this->never() )->method( 'delete' );
994 $mockCache->expects( $this->once() )
995 ->method( 'get' )
996 ->with(
997 '0:SomeDbKey:1'
998 )
999 ->will( $this->returnValue( null ) );
1000 $mockCache->expects( $this->once() )
1001 ->method( 'set' )
1002 ->with(
1003 '0:SomeDbKey:1'
1004 );
1005
1006 $store = new WatchedItemStore(
1007 $this->getMockLoadBalancer( $mockDb ),
1008 $mockCache
1009 );
1010
1011 $watchedItem = $store->getWatchedItem(
1012 $this->getMockNonAnonUserWithId( 1 ),
1013 new TitleValue( 0, 'SomeDbKey' )
1014 );
1015 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1016 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1017 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1018 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1019 }
1020
1021 public function testGetWatchedItem_cachedItem() {
1022 $mockDb = $this->getMockDb();
1023 $mockDb->expects( $this->never() )
1024 ->method( 'selectRow' );
1025
1026 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1027 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1028 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1029
1030 $mockCache = $this->getMockCache();
1031 $mockCache->expects( $this->never() )->method( 'delete' );
1032 $mockCache->expects( $this->never() )->method( 'set' );
1033 $mockCache->expects( $this->once() )
1034 ->method( 'get' )
1035 ->with(
1036 '0:SomeDbKey:1'
1037 )
1038 ->will( $this->returnValue( $cachedItem ) );
1039
1040 $store = new WatchedItemStore(
1041 $this->getMockLoadBalancer( $mockDb ),
1042 $mockCache
1043 );
1044
1045 $this->assertEquals(
1046 $cachedItem,
1047 $store->getWatchedItem(
1048 $mockUser,
1049 $linkTarget
1050 )
1051 );
1052 }
1053
1054 public function testGetWatchedItem_noItem() {
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
1069 $mockCache = $this->getMockCache();
1070 $mockCache->expects( $this->never() )->method( 'set' );
1071 $mockCache->expects( $this->never() )->method( 'delete' );
1072 $mockCache->expects( $this->once() )
1073 ->method( 'get' )
1074 ->with( '0:SomeDbKey:1' )
1075 ->will( $this->returnValue( false ) );
1076
1077 $store = new WatchedItemStore(
1078 $this->getMockLoadBalancer( $mockDb ),
1079 $mockCache
1080 );
1081
1082 $this->assertFalse(
1083 $store->getWatchedItem(
1084 $this->getMockNonAnonUserWithId( 1 ),
1085 new TitleValue( 0, 'SomeDbKey' )
1086 )
1087 );
1088 }
1089
1090 public function testGetWatchedItem_anonymousUser() {
1091 $mockDb = $this->getMockDb();
1092 $mockDb->expects( $this->never() )
1093 ->method( 'selectRow' );
1094
1095 $mockCache = $this->getMockCache();
1096 $mockCache->expects( $this->never() )->method( 'set' );
1097 $mockCache->expects( $this->never() )->method( 'get' );
1098 $mockCache->expects( $this->never() )->method( 'delete' );
1099
1100 $store = new WatchedItemStore(
1101 $this->getMockLoadBalancer( $mockDb ),
1102 $mockCache
1103 );
1104
1105 $this->assertFalse(
1106 $store->getWatchedItem(
1107 $this->getAnonUser(),
1108 new TitleValue( 0, 'SomeDbKey' )
1109 )
1110 );
1111 }
1112
1113 public function testIsWatchedItem_existingItem() {
1114 $mockDb = $this->getMockDb();
1115 $mockDb->expects( $this->once() )
1116 ->method( 'selectRow' )
1117 ->with(
1118 'watchlist',
1119 'wl_notificationtimestamp',
1120 [
1121 'wl_user' => 1,
1122 'wl_namespace' => 0,
1123 'wl_title' => 'SomeDbKey',
1124 ]
1125 )
1126 ->will( $this->returnValue(
1127 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1128 ) );
1129
1130 $mockCache = $this->getMockCache();
1131 $mockCache->expects( $this->never() )->method( 'delete' );
1132 $mockCache->expects( $this->once() )
1133 ->method( 'get' )
1134 ->with( '0:SomeDbKey:1' )
1135 ->will( $this->returnValue( false ) );
1136 $mockCache->expects( $this->once() )
1137 ->method( 'set' )
1138 ->with(
1139 '0:SomeDbKey:1'
1140 );
1141
1142 $store = new WatchedItemStore(
1143 $this->getMockLoadBalancer( $mockDb ),
1144 $mockCache
1145 );
1146
1147 $this->assertTrue(
1148 $store->isWatched(
1149 $this->getMockNonAnonUserWithId( 1 ),
1150 new TitleValue( 0, 'SomeDbKey' )
1151 )
1152 );
1153 }
1154
1155 public function testIsWatchedItem_noItem() {
1156 $mockDb = $this->getMockDb();
1157 $mockDb->expects( $this->once() )
1158 ->method( 'selectRow' )
1159 ->with(
1160 'watchlist',
1161 'wl_notificationtimestamp',
1162 [
1163 'wl_user' => 1,
1164 'wl_namespace' => 0,
1165 'wl_title' => 'SomeDbKey',
1166 ]
1167 )
1168 ->will( $this->returnValue( [] ) );
1169
1170 $mockCache = $this->getMockCache();
1171 $mockCache->expects( $this->never() )->method( 'set' );
1172 $mockCache->expects( $this->never() )->method( 'delete' );
1173 $mockCache->expects( $this->once() )
1174 ->method( 'get' )
1175 ->with( '0:SomeDbKey:1' )
1176 ->will( $this->returnValue( false ) );
1177
1178 $store = new WatchedItemStore(
1179 $this->getMockLoadBalancer( $mockDb ),
1180 $mockCache
1181 );
1182
1183 $this->assertFalse(
1184 $store->isWatched(
1185 $this->getMockNonAnonUserWithId( 1 ),
1186 new TitleValue( 0, 'SomeDbKey' )
1187 )
1188 );
1189 }
1190
1191 public function testIsWatchedItem_anonymousUser() {
1192 $mockDb = $this->getMockDb();
1193 $mockDb->expects( $this->never() )
1194 ->method( 'selectRow' );
1195
1196 $mockCache = $this->getMockCache();
1197 $mockCache->expects( $this->never() )->method( 'set' );
1198 $mockCache->expects( $this->never() )->method( 'get' );
1199 $mockCache->expects( $this->never() )->method( 'delete' );
1200
1201 $store = new WatchedItemStore(
1202 $this->getMockLoadBalancer( $mockDb ),
1203 $mockCache
1204 );
1205
1206 $this->assertFalse(
1207 $store->isWatched(
1208 $this->getAnonUser(),
1209 new TitleValue( 0, 'SomeDbKey' )
1210 )
1211 );
1212 }
1213
1214 public function testResetNotificationTimestamp_anonymousUser() {
1215 $mockDb = $this->getMockDb();
1216 $mockDb->expects( $this->never() )
1217 ->method( 'selectRow' );
1218
1219 $mockCache = $this->getMockCache();
1220 $mockCache->expects( $this->never() )->method( 'get' );
1221 $mockCache->expects( $this->never() )->method( 'set' );
1222 $mockCache->expects( $this->never() )->method( 'delete' );
1223
1224 $store = new WatchedItemStore(
1225 $this->getMockLoadBalancer( $mockDb ),
1226 $mockCache
1227 );
1228
1229 $this->assertFalse(
1230 $store->resetNotificationTimestamp(
1231 $this->getAnonUser(),
1232 Title::newFromText( 'SomeDbKey' )
1233 )
1234 );
1235 }
1236
1237 public function testResetNotificationTimestamp_noItem() {
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
1252 $mockCache = $this->getMockCache();
1253 $mockCache->expects( $this->never() )->method( 'get' );
1254 $mockCache->expects( $this->never() )->method( 'set' );
1255 $mockCache->expects( $this->never() )->method( 'delete' );
1256
1257 $store = new WatchedItemStore(
1258 $this->getMockLoadBalancer( $mockDb ),
1259 $mockCache
1260 );
1261
1262 $this->assertFalse(
1263 $store->resetNotificationTimestamp(
1264 $this->getMockNonAnonUserWithId( 1 ),
1265 Title::newFromText( 'SomeDbKey' )
1266 )
1267 );
1268 }
1269
1270 public function testResetNotificationTimestamp_item() {
1271 $user = $this->getMockNonAnonUserWithId( 1 );
1272 $title = Title::newFromText( 'SomeDbKey' );
1273
1274 $mockDb = $this->getMockDb();
1275 $mockDb->expects( $this->once() )
1276 ->method( 'selectRow' )
1277 ->with(
1278 'watchlist',
1279 'wl_notificationtimestamp',
1280 [
1281 'wl_user' => 1,
1282 'wl_namespace' => 0,
1283 'wl_title' => 'SomeDbKey',
1284 ]
1285 )
1286 ->will( $this->returnValue(
1287 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1288 ) );
1289
1290 $mockCache = $this->getMockCache();
1291 $mockCache->expects( $this->never() )->method( 'get' );
1292 $mockCache->expects( $this->once() )
1293 ->method( 'set' )
1294 ->with(
1295 '0:SomeDbKey:1',
1296 $this->isInstanceOf( WatchedItem::class )
1297 );
1298 $mockCache->expects( $this->once() )
1299 ->method( 'delete' )
1300 ->with( '0:SomeDbKey:1' );
1301
1302 $store = new WatchedItemStore(
1303 $this->getMockLoadBalancer( $mockDb ),
1304 $mockCache
1305 );
1306
1307 // Note: This does not actually assert the job is correct
1308 $callableCallCounter = 0;
1309 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1310 $callableCallCounter++;
1311 $this->assertInternalType( 'callable', $callable );
1312 };
1313 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1314
1315 $this->assertTrue(
1316 $store->resetNotificationTimestamp(
1317 $user,
1318 $title
1319 )
1320 );
1321 $this->assertEquals( 1, $callableCallCounter );
1322
1323 ScopedCallback::consume( $scopedOverride );
1324 }
1325
1326 public function testResetNotificationTimestamp_noItemForced() {
1327 $user = $this->getMockNonAnonUserWithId( 1 );
1328 $title = Title::newFromText( 'SomeDbKey' );
1329
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->never() )
1332 ->method( 'selectRow' );
1333
1334 $mockCache = $this->getMockCache();
1335 $mockDb->expects( $this->never() )
1336 ->method( 'get' );
1337 $mockDb->expects( $this->never() )
1338 ->method( 'set' );
1339 $mockDb->expects( $this->never() )
1340 ->method( 'delete' );
1341
1342 $store = new WatchedItemStore(
1343 $this->getMockLoadBalancer( $mockDb ),
1344 $mockCache
1345 );
1346
1347 // Note: This does not actually assert the job is correct
1348 $callableCallCounter = 0;
1349 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1350 $callableCallCounter++;
1351 $this->assertInternalType( 'callable', $callable );
1352 };
1353 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1354
1355 $this->assertTrue(
1356 $store->resetNotificationTimestamp(
1357 $user,
1358 $title,
1359 'force'
1360 )
1361 );
1362 $this->assertEquals( 1, $callableCallCounter );
1363
1364 ScopedCallback::consume( $scopedOverride );
1365 }
1366
1367 /**
1368 * @param $text
1369 * @param int $ns
1370 *
1371 * @return PHPUnit_Framework_MockObject_MockObject|Title
1372 */
1373 private function getMockTitle( $text, $ns = 0 ) {
1374 $title = $this->getMock( Title::class );
1375 $title->expects( $this->any() )
1376 ->method( 'getText' )
1377 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1378 $title->expects( $this->any() )
1379 ->method( 'getDbKey' )
1380 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1381 $title->expects( $this->any() )
1382 ->method( 'getNamespace' )
1383 ->will( $this->returnValue( $ns ) );
1384 return $title;
1385 }
1386
1387 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1388 $user = $this->getMockNonAnonUserWithId( 1 );
1389 $oldid = 22;
1390 $title = $this->getMockTitle( 'SomeTitle' );
1391 $title->expects( $this->once() )
1392 ->method( 'getNextRevisionID' )
1393 ->with( $oldid )
1394 ->will( $this->returnValue( false ) );
1395
1396 $mockDb = $this->getMockDb();
1397 $mockDb->expects( $this->never() )
1398 ->method( 'selectRow' );
1399
1400 $mockCache = $this->getMockCache();
1401 $mockDb->expects( $this->never() )
1402 ->method( 'get' );
1403 $mockDb->expects( $this->never() )
1404 ->method( 'set' );
1405 $mockDb->expects( $this->never() )
1406 ->method( 'delete' );
1407
1408 $store = new WatchedItemStore(
1409 $this->getMockLoadBalancer( $mockDb ),
1410 $mockCache
1411 );
1412
1413 // Note: This does not actually assert the job is correct
1414 $callableCallCounter = 0;
1415 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1416 function( $callable ) use ( &$callableCallCounter ) {
1417 $callableCallCounter++;
1418 $this->assertInternalType( 'callable', $callable );
1419 }
1420 );
1421
1422 $this->assertTrue(
1423 $store->resetNotificationTimestamp(
1424 $user,
1425 $title,
1426 'force',
1427 $oldid
1428 )
1429 );
1430 $this->assertEquals( 1, $callableCallCounter );
1431
1432 ScopedCallback::consume( $scopedOverride );
1433 }
1434
1435 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1436 $user = $this->getMockNonAnonUserWithId( 1 );
1437 $oldid = 22;
1438 $title = $this->getMockTitle( 'SomeDbKey' );
1439 $title->expects( $this->once() )
1440 ->method( 'getNextRevisionID' )
1441 ->with( $oldid )
1442 ->will( $this->returnValue( 33 ) );
1443
1444 $mockDb = $this->getMockDb();
1445 $mockDb->expects( $this->once() )
1446 ->method( 'selectRow' )
1447 ->with(
1448 'watchlist',
1449 'wl_notificationtimestamp',
1450 [
1451 'wl_user' => 1,
1452 'wl_namespace' => 0,
1453 'wl_title' => 'SomeDbKey',
1454 ]
1455 )
1456 ->will( $this->returnValue(
1457 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1458 ) );
1459
1460 $mockCache = $this->getMockCache();
1461 $mockDb->expects( $this->never() )
1462 ->method( 'get' );
1463 $mockDb->expects( $this->never() )
1464 ->method( 'set' );
1465 $mockDb->expects( $this->never() )
1466 ->method( 'delete' );
1467
1468 $store = new WatchedItemStore(
1469 $this->getMockLoadBalancer( $mockDb ),
1470 $mockCache
1471 );
1472
1473 // Note: This does not actually assert the job is correct
1474 $addUpdateCallCounter = 0;
1475 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1476 function( $callable ) use ( &$addUpdateCallCounter ) {
1477 $addUpdateCallCounter++;
1478 $this->assertInternalType( 'callable', $callable );
1479 }
1480 );
1481
1482 $getTimestampCallCounter = 0;
1483 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
1484 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1485 $getTimestampCallCounter++;
1486 $this->assertEquals( $title, $titleParam );
1487 $this->assertEquals( $oldid, $oldidParam );
1488 }
1489 );
1490
1491 $this->assertTrue(
1492 $store->resetNotificationTimestamp(
1493 $user,
1494 $title,
1495 'force',
1496 $oldid
1497 )
1498 );
1499 $this->assertEquals( 1, $addUpdateCallCounter );
1500 $this->assertEquals( 1, $getTimestampCallCounter );
1501
1502 ScopedCallback::consume( $scopedOverrideDeferred );
1503 ScopedCallback::consume( $scopedOverrideRevision );
1504 }
1505
1506 public function testUpdateNotificationTimestamp_watchersExist() {
1507 $mockDb = $this->getMockDb();
1508 $mockDb->expects( $this->once() )
1509 ->method( 'select' )
1510 ->with(
1511 [ 'watchlist' ],
1512 [ 'wl_user' ],
1513 [
1514 'wl_user != 1',
1515 'wl_namespace' => 0,
1516 'wl_title' => 'SomeDbKey',
1517 'wl_notificationtimestamp IS NULL'
1518 ]
1519 )
1520 ->will(
1521 $this->returnValue( [
1522 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1523 $this->getFakeRow( [ 'wl_user' => '3' ] )
1524 ] )
1525 );
1526 $mockDb->expects( $this->once() )
1527 ->method( 'onTransactionIdle' )
1528 ->with( $this->isType( 'callable' ) )
1529 ->will( $this->returnCallback( function( $callable ) {
1530 $callable();
1531 } ) );
1532 $mockDb->expects( $this->once() )
1533 ->method( 'update' )
1534 ->with(
1535 'watchlist',
1536 [ 'wl_notificationtimestamp' => null ],
1537 [
1538 'wl_user' => [ 2, 3 ],
1539 'wl_namespace' => 0,
1540 'wl_title' => 'SomeDbKey',
1541 ]
1542 );
1543
1544 $mockCache = $this->getMockCache();
1545 $mockCache->expects( $this->never() )->method( 'set' );
1546 $mockCache->expects( $this->never() )->method( 'get' );
1547 $mockCache->expects( $this->never() )->method( 'delete' );
1548
1549 $store = new WatchedItemStore(
1550 $this->getMockLoadBalancer( $mockDb ),
1551 $mockCache
1552 );
1553
1554 $this->assertEquals(
1555 [ 2, 3 ],
1556 $store->updateNotificationTimestamp(
1557 $this->getMockNonAnonUserWithId( 1 ),
1558 new TitleValue( 0, 'SomeDbKey' ),
1559 '20151212010101'
1560 )
1561 );
1562 }
1563
1564 public function testUpdateNotificationTimestamp_noWatchers() {
1565 $mockDb = $this->getMockDb();
1566 $mockDb->expects( $this->once() )
1567 ->method( 'select' )
1568 ->with(
1569 [ 'watchlist' ],
1570 [ 'wl_user' ],
1571 [
1572 'wl_user != 1',
1573 'wl_namespace' => 0,
1574 'wl_title' => 'SomeDbKey',
1575 'wl_notificationtimestamp IS NULL'
1576 ]
1577 )
1578 ->will(
1579 $this->returnValue( [] )
1580 );
1581 $mockDb->expects( $this->never() )
1582 ->method( 'onTransactionIdle' );
1583 $mockDb->expects( $this->never() )
1584 ->method( 'update' );
1585
1586 $mockCache = $this->getMockCache();
1587 $mockCache->expects( $this->never() )->method( 'set' );
1588 $mockCache->expects( $this->never() )->method( 'get' );
1589 $mockCache->expects( $this->never() )->method( 'delete' );
1590
1591 $store = new WatchedItemStore(
1592 $this->getMockLoadBalancer( $mockDb ),
1593 $mockCache
1594 );
1595
1596 $watchers = $store->updateNotificationTimestamp(
1597 $this->getMockNonAnonUserWithId( 1 ),
1598 new TitleValue( 0, 'SomeDbKey' ),
1599 '20151212010101'
1600 );
1601 $this->assertInternalType( 'array', $watchers );
1602 $this->assertEmpty( $watchers );
1603 }
1604
1605 public function testUpdateNotificationTimestamp_clearsCachedItems() {
1606 $user = $this->getMockNonAnonUserWithId( 1 );
1607 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1608
1609 $mockDb = $this->getMockDb();
1610 $mockDb->expects( $this->once() )
1611 ->method( 'selectRow' )
1612 ->will( $this->returnValue(
1613 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1614 ) );
1615 $mockDb->expects( $this->once() )
1616 ->method( 'select' )
1617 ->will(
1618 $this->returnValue( [
1619 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1620 $this->getFakeRow( [ 'wl_user' => '3' ] )
1621 ] )
1622 );
1623 $mockDb->expects( $this->once() )
1624 ->method( 'onTransactionIdle' )
1625 ->with( $this->isType( 'callable' ) )
1626 ->will( $this->returnCallback( function( $callable ) {
1627 $callable();
1628 } ) );
1629 $mockDb->expects( $this->once() )
1630 ->method( 'update' );
1631
1632 $mockCache = $this->getMockCache();
1633 $mockCache->expects( $this->once() )
1634 ->method( 'set' )
1635 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
1636 $mockCache->expects( $this->once() )
1637 ->method( 'get' )
1638 ->with( '0:SomeDbKey:1' );
1639 $mockCache->expects( $this->once() )
1640 ->method( 'delete' )
1641 ->with( '0:SomeDbKey:1' );
1642
1643 $store = new WatchedItemStore(
1644 $this->getMockLoadBalancer( $mockDb ),
1645 $mockCache
1646 );
1647
1648 // This will add the item to the cache
1649 $store->getWatchedItem( $user, $titleValue );
1650
1651 $store->updateNotificationTimestamp(
1652 $this->getMockNonAnonUserWithId( 1 ),
1653 $titleValue,
1654 '20151212010101'
1655 );
1656 }
1657
1658 }