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