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