Merge "ChangesList: Use context language for LogPage::getName() message"
[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|BagOStuff
35 */
36 private function getMockCache() {
37 $mock = $this->getMockBuilder( BagOStuff::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 testDuplicateEntry_nothingToDuplicate() {
85 $mockDb = $this->getMockDb();
86 $mockDb->expects( $this->once() )
87 ->method( 'select' )
88 ->with(
89 'watchlist',
90 [
91 'wl_user',
92 'wl_notificationtimestamp',
93 ],
94 [
95 'wl_namespace' => 0,
96 'wl_title' => 'Old_Title',
97 ],
98 'WatchedItemStore::duplicateEntry',
99 [ 'FOR UPDATE' ]
100 )
101 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
102
103 $store = new WatchedItemStore(
104 $this->getMockLoadBalancer( $mockDb ),
105 new HashBagOStuff( [ 'maxKeys' => 100 ] )
106 );
107
108 $store->duplicateEntry(
109 Title::newFromText( 'Old_Title' ),
110 Title::newFromText( 'New_Title' )
111 );
112 }
113
114 public function testDuplicateEntry_somethingToDuplicate() {
115 $fakeRows = [
116 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
117 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
118 ];
119
120 $mockDb = $this->getMockDb();
121 $mockDb->expects( $this->at( 0 ) )
122 ->method( 'select' )
123 ->with(
124 'watchlist',
125 [
126 'wl_user',
127 'wl_notificationtimestamp',
128 ],
129 [
130 'wl_namespace' => 0,
131 'wl_title' => 'Old_Title',
132 ]
133 )
134 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
135 $mockDb->expects( $this->at( 1 ) )
136 ->method( 'replace' )
137 ->with(
138 'watchlist',
139 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
140 [
141 [
142 'wl_user' => 1,
143 'wl_namespace' => 0,
144 'wl_title' => 'New_Title',
145 'wl_notificationtimestamp' => '20151212010101',
146 ],
147 [
148 'wl_user' => 2,
149 'wl_namespace' => 0,
150 'wl_title' => 'New_Title',
151 'wl_notificationtimestamp' => null,
152 ],
153 ],
154 $this->isType( 'string' )
155 );
156
157 $store = new WatchedItemStore(
158 $this->getMockLoadBalancer( $mockDb ),
159 new HashBagOStuff( [ 'maxKeys' => 100 ] )
160 );
161
162 $store->duplicateEntry(
163 Title::newFromText( 'Old_Title' ),
164 Title::newFromText( 'New_Title' )
165 );
166 }
167
168 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
169 $mockDb = $this->getMockDb();
170 $mockDb->expects( $this->at( 0 ) )
171 ->method( 'select' )
172 ->with(
173 'watchlist',
174 [
175 'wl_user',
176 'wl_notificationtimestamp',
177 ],
178 [
179 'wl_namespace' => 0,
180 'wl_title' => 'Old_Title',
181 ]
182 )
183 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
184 $mockDb->expects( $this->at( 1 ) )
185 ->method( 'select' )
186 ->with(
187 'watchlist',
188 [
189 'wl_user',
190 'wl_notificationtimestamp',
191 ],
192 [
193 'wl_namespace' => 1,
194 'wl_title' => 'Old_Title',
195 ]
196 )
197 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
198
199 $store = new WatchedItemStore(
200 $this->getMockLoadBalancer( $mockDb ),
201 new HashBagOStuff( [ 'maxKeys' => 100 ] )
202 );
203
204 $store->duplicateAllAssociatedEntries(
205 Title::newFromText( 'Old_Title' ),
206 Title::newFromText( 'New_Title' )
207 );
208 }
209
210 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
211 $fakeRows = [
212 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
213 ];
214
215 $mockDb = $this->getMockDb();
216 $mockDb->expects( $this->at( 0 ) )
217 ->method( 'select' )
218 ->with(
219 'watchlist',
220 [
221 'wl_user',
222 'wl_notificationtimestamp',
223 ],
224 [
225 'wl_namespace' => 0,
226 'wl_title' => 'Old_Title',
227 ]
228 )
229 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
230 $mockDb->expects( $this->at( 1 ) )
231 ->method( 'replace' )
232 ->with(
233 'watchlist',
234 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
235 [
236 [
237 'wl_user' => 1,
238 'wl_namespace' => 0,
239 'wl_title' => 'New_Title',
240 'wl_notificationtimestamp' => '20151212010101',
241 ],
242 ],
243 $this->isType( 'string' )
244 );
245 $mockDb->expects( $this->at( 2 ) )
246 ->method( 'select' )
247 ->with(
248 'watchlist',
249 [
250 'wl_user',
251 'wl_notificationtimestamp',
252 ],
253 [
254 'wl_namespace' => 1,
255 'wl_title' => 'Old_Title',
256 ]
257 )
258 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
259 $mockDb->expects( $this->at( 3 ) )
260 ->method( 'replace' )
261 ->with(
262 'watchlist',
263 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
264 [
265 [
266 'wl_user' => 1,
267 'wl_namespace' => 1,
268 'wl_title' => 'New_Title',
269 'wl_notificationtimestamp' => '20151212010101',
270 ],
271 ],
272 $this->isType( 'string' )
273 );
274
275 $store = new WatchedItemStore(
276 $this->getMockLoadBalancer( $mockDb ),
277 new HashBagOStuff( [ 'maxKeys' => 100 ] )
278 );
279
280 $store->duplicateAllAssociatedEntries(
281 Title::newFromText( 'Old_Title' ),
282 Title::newFromText( 'New_Title' )
283 );
284 }
285
286 public function testAddWatch_nonAnonymousUser() {
287 $mockDb = $this->getMockDb();
288 $mockDb->expects( $this->once() )
289 ->method( 'insert' )
290 ->with(
291 'watchlist',
292 [
293 [
294 'wl_user' => 1,
295 'wl_namespace' => 0,
296 'wl_title' => 'Some_Page',
297 'wl_notificationtimestamp' => null,
298 ]
299 ]
300 );
301
302 $mockCache = $this->getMockCache();
303 $mockCache->expects( $this->once() )
304 ->method( 'delete' )
305 ->with( '0:Some_Page:1' );
306
307 $store = new WatchedItemStore(
308 $this->getMockLoadBalancer( $mockDb ),
309 $mockCache
310 );
311
312 $store->addWatch(
313 $this->getMockNonAnonUserWithId( 1 ),
314 Title::newFromText( 'Some_Page' )
315 );
316 }
317
318 public function testAddWatch_anonymousUser() {
319 $mockDb = $this->getMockDb();
320 $mockDb->expects( $this->never() )
321 ->method( 'insert' );
322
323 $mockCache = $this->getMockCache();
324 $mockCache->expects( $this->never() )
325 ->method( 'delete' );
326
327 $store = new WatchedItemStore(
328 $this->getMockLoadBalancer( $mockDb ),
329 $mockCache
330 );
331
332 $store->addWatch(
333 $this->getAnonUser(),
334 Title::newFromText( 'Some_Page' )
335 );
336 }
337
338 public function testAddWatchBatch_nonAnonymousUser() {
339 $mockDb = $this->getMockDb();
340 $mockDb->expects( $this->once() )
341 ->method( 'insert' )
342 ->with(
343 'watchlist',
344 [
345 [
346 'wl_user' => 1,
347 'wl_namespace' => 0,
348 'wl_title' => 'Some_Page',
349 'wl_notificationtimestamp' => null,
350 ],
351 [
352 'wl_user' => 1,
353 'wl_namespace' => 1,
354 'wl_title' => 'Some_Page',
355 'wl_notificationtimestamp' => null,
356 ]
357 ]
358 );
359
360 $mockCache = $this->getMockCache();
361 $mockCache->expects( $this->exactly( 2 ) )
362 ->method( 'delete' );
363 $mockCache->expects( $this->at( 1 ) )
364 ->method( 'delete' )
365 ->with( '0:Some_Page:1' );
366 $mockCache->expects( $this->at( 3 ) )
367 ->method( 'delete' )
368 ->with( '1:Some_Page:1' );
369
370 $store = new WatchedItemStore(
371 $this->getMockLoadBalancer( $mockDb ),
372 $mockCache
373 );
374
375 $mockUser = $this->getMockNonAnonUserWithId( 1 );
376
377 $this->assertTrue(
378 $store->addWatchBatch(
379 [
380 [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
381 [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
382 ]
383 )
384 );
385 }
386
387 public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
388 $mockDb = $this->getMockDb();
389 $mockDb->expects( $this->once() )
390 ->method( 'insert' )
391 ->with(
392 'watchlist',
393 [
394 [
395 'wl_user' => 1,
396 'wl_namespace' => 0,
397 'wl_title' => 'Some_Page',
398 'wl_notificationtimestamp' => null,
399 ]
400 ]
401 );
402
403 $mockCache = $this->getMockCache();
404 $mockCache->expects( $this->once() )
405 ->method( 'delete' )
406 ->with( '0:Some_Page:1' );
407
408 $store = new WatchedItemStore(
409 $this->getMockLoadBalancer( $mockDb ),
410 $mockCache
411 );
412
413 $this->assertTrue(
414 $store->addWatchBatch(
415 [
416 [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
417 [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
418 ]
419 )
420 );
421 }
422
423 public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
424 $mockDb = $this->getMockDb();
425 $mockDb->expects( $this->never() )
426 ->method( 'insert' );
427
428 $mockCache = $this->getMockCache();
429 $mockCache->expects( $this->never() )
430 ->method( 'delete' );
431
432 $store = new WatchedItemStore(
433 $this->getMockLoadBalancer( $mockDb ),
434 $mockCache
435 );
436
437 $anonUser = $this->getAnonUser();
438 $this->assertFalse(
439 $store->addWatchBatch(
440 [
441 [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
442 [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
443 ]
444 )
445 );
446 }
447
448 public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
449 $mockDb = $this->getMockDb();
450 $mockDb->expects( $this->never() )
451 ->method( 'insert' );
452
453 $mockCache = $this->getMockCache();
454 $mockCache->expects( $this->never() )
455 ->method( 'delete' );
456
457 $store = new WatchedItemStore(
458 $this->getMockLoadBalancer( $mockDb ),
459 $mockCache
460 );
461
462 $this->assertFalse(
463 $store->addWatchBatch( [] )
464 );
465 }
466
467 public function testLoadWatchedItem_existingItem() {
468 $mockDb = $this->getMockDb();
469 $mockDb->expects( $this->once() )
470 ->method( 'selectRow' )
471 ->with(
472 'watchlist',
473 'wl_notificationtimestamp',
474 [
475 'wl_user' => 1,
476 'wl_namespace' => 0,
477 'wl_title' => 'SomeDbKey',
478 ]
479 )
480 ->will( $this->returnValue(
481 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
482 ) );
483
484 $mockCache = $this->getMockCache();
485 $mockCache->expects( $this->once() )
486 ->method( 'set' )
487 ->with(
488 '0:SomeDbKey:1'
489 );
490
491 $store = new WatchedItemStore(
492 $this->getMockLoadBalancer( $mockDb ),
493 $mockCache
494 );
495
496 $watchedItem = $store->loadWatchedItem(
497 $this->getMockNonAnonUserWithId( 1 ),
498 new TitleValue( 0, 'SomeDbKey' )
499 );
500 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
501 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
502 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
503 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
504 }
505
506 public function testLoadWatchedItem_noItem() {
507 $mockDb = $this->getMockDb();
508 $mockDb->expects( $this->once() )
509 ->method( 'selectRow' )
510 ->with(
511 'watchlist',
512 'wl_notificationtimestamp',
513 [
514 'wl_user' => 1,
515 'wl_namespace' => 0,
516 'wl_title' => 'SomeDbKey',
517 ]
518 )
519 ->will( $this->returnValue( [] ) );
520
521 $mockCache = $this->getMockCache();
522 $mockCache->expects( $this->never() )
523 ->method( 'delete' );
524
525 $store = new WatchedItemStore(
526 $this->getMockLoadBalancer( $mockDb ),
527 $mockCache
528 );
529
530 $this->assertFalse(
531 $store->loadWatchedItem(
532 $this->getMockNonAnonUserWithId( 1 ),
533 new TitleValue( 0, 'SomeDbKey' )
534 )
535 );
536 }
537
538 public function testLoadWatchedItem_anonymousUser() {
539 $mockDb = $this->getMockDb();
540 $mockDb->expects( $this->never() )
541 ->method( 'selectRow' );
542
543 $mockCache = $this->getMockCache();
544 $mockCache->expects( $this->never() )
545 ->method( 'delete' );
546
547 $store = new WatchedItemStore(
548 $this->getMockLoadBalancer( $mockDb ),
549 $mockCache
550 );
551
552 $this->assertFalse(
553 $store->loadWatchedItem(
554 $this->getAnonUser(),
555 new TitleValue( 0, 'SomeDbKey' )
556 )
557 );
558 }
559
560 public function testRemoveWatch_existingItem() {
561 $mockDb = $this->getMockDb();
562 $mockDb->expects( $this->once() )
563 ->method( 'delete' )
564 ->with(
565 'watchlist',
566 [
567 'wl_user' => 1,
568 'wl_namespace' => 0,
569 'wl_title' => 'SomeDbKey',
570 ]
571 );
572 $mockDb->expects( $this->once() )
573 ->method( 'affectedRows' )
574 ->will( $this->returnValue( 1 ) );
575
576 $mockCache = $this->getMockCache();
577 $mockCache->expects( $this->once() )
578 ->method( 'delete' )
579 ->with( '0:SomeDbKey:1' );
580
581 $store = new WatchedItemStore(
582 $this->getMockLoadBalancer( $mockDb ),
583 $mockCache
584 );
585
586 $this->assertTrue(
587 $store->removeWatch(
588 $this->getMockNonAnonUserWithId( 1 ),
589 new TitleValue( 0, 'SomeDbKey' )
590 )
591 );
592 }
593
594 public function testRemoveWatch_noItem() {
595 $mockDb = $this->getMockDb();
596 $mockDb->expects( $this->once() )
597 ->method( 'delete' )
598 ->with(
599 'watchlist',
600 [
601 'wl_user' => 1,
602 'wl_namespace' => 0,
603 'wl_title' => 'SomeDbKey',
604 ]
605 );
606 $mockDb->expects( $this->once() )
607 ->method( 'affectedRows' )
608 ->will( $this->returnValue( 0 ) );
609
610 $mockCache = $this->getMockCache();
611 $mockCache->expects( $this->once() )
612 ->method( 'delete' )
613 ->with( '0:SomeDbKey:1' );
614
615 $store = new WatchedItemStore(
616 $this->getMockLoadBalancer( $mockDb ),
617 $mockCache
618 );
619
620 $this->assertFalse(
621 $store->removeWatch(
622 $this->getMockNonAnonUserWithId( 1 ),
623 new TitleValue( 0, 'SomeDbKey' )
624 )
625 );
626 }
627
628 public function testRemoveWatch_anonymousUser() {
629 $mockDb = $this->getMockDb();
630 $mockDb->expects( $this->never() )
631 ->method( 'delete' );
632
633 $mockCache = $this->getMockCache();
634 $mockCache->expects( $this->never() )
635 ->method( 'delete' );
636
637 $store = new WatchedItemStore(
638 $this->getMockLoadBalancer( $mockDb ),
639 $mockCache
640 );
641
642 $this->assertFalse(
643 $store->removeWatch(
644 $this->getAnonUser(),
645 new TitleValue( 0, 'SomeDbKey' )
646 )
647 );
648 }
649
650 public function testGetWatchedItem_existingItem() {
651 $mockDb = $this->getMockDb();
652 $mockDb->expects( $this->once() )
653 ->method( 'selectRow' )
654 ->with(
655 'watchlist',
656 'wl_notificationtimestamp',
657 [
658 'wl_user' => 1,
659 'wl_namespace' => 0,
660 'wl_title' => 'SomeDbKey',
661 ]
662 )
663 ->will( $this->returnValue(
664 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
665 ) );
666
667 $mockCache = $this->getMockCache();
668 $mockCache->expects( $this->once() )
669 ->method( 'get' )
670 ->with(
671 '0:SomeDbKey:1'
672 )
673 ->will( $this->returnValue( null ) );
674 $mockCache->expects( $this->once() )
675 ->method( 'set' )
676 ->with(
677 '0:SomeDbKey:1'
678 );
679
680 $store = new WatchedItemStore(
681 $this->getMockLoadBalancer( $mockDb ),
682 $mockCache
683 );
684
685 $watchedItem = $store->getWatchedItem(
686 $this->getMockNonAnonUserWithId( 1 ),
687 new TitleValue( 0, 'SomeDbKey' )
688 );
689 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
690 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
691 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
692 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
693 }
694
695 public function testGetWatchedItem_cachedItem() {
696 $mockDb = $this->getMockDb();
697 $mockDb->expects( $this->never() )
698 ->method( 'selectRow' );
699
700 $mockUser = $this->getMockNonAnonUserWithId( 1 );
701 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
702 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
703
704 $mockCache = $this->getMockCache();
705 $mockCache->expects( $this->once() )
706 ->method( 'get' )
707 ->with(
708 '0:SomeDbKey:1'
709 )
710 ->will( $this->returnValue( $cachedItem ) );
711 $mockCache->expects( $this->never() )
712 ->method( 'set' );
713
714 $store = new WatchedItemStore(
715 $this->getMockLoadBalancer( $mockDb ),
716 $mockCache
717 );
718
719 $this->assertEquals(
720 $cachedItem,
721 $store->getWatchedItem(
722 $mockUser,
723 $linkTarget
724 )
725 );
726 }
727
728 public function testGetWatchedItem_noItem() {
729 $mockDb = $this->getMockDb();
730 $mockDb->expects( $this->once() )
731 ->method( 'selectRow' )
732 ->with(
733 'watchlist',
734 'wl_notificationtimestamp',
735 [
736 'wl_user' => 1,
737 'wl_namespace' => 0,
738 'wl_title' => 'SomeDbKey',
739 ]
740 )
741 ->will( $this->returnValue( [] ) );
742
743 $mockCache = $this->getMockCache();
744 $mockCache->expects( $this->never() )
745 ->method( 'set' );
746
747 $store = new WatchedItemStore(
748 $this->getMockLoadBalancer( $mockDb ),
749 $mockCache
750 );
751
752 $this->assertFalse(
753 $store->getWatchedItem(
754 $this->getMockNonAnonUserWithId( 1 ),
755 new TitleValue( 0, 'SomeDbKey' )
756 )
757 );
758 }
759
760 public function testGetWatchedItem_anonymousUser() {
761 $mockDb = $this->getMockDb();
762 $mockDb->expects( $this->never() )
763 ->method( 'selectRow' );
764
765 $mockCache = $this->getMockCache();
766 $mockCache->expects( $this->never() )
767 ->method( 'set' );
768
769 $store = new WatchedItemStore(
770 $this->getMockLoadBalancer( $mockDb ),
771 $mockCache
772 );
773
774 $this->assertFalse(
775 $store->getWatchedItem(
776 $this->getAnonUser(),
777 new TitleValue( 0, 'SomeDbKey' )
778 )
779 );
780 }
781
782 public function testIsWatchedItem_existingItem() {
783 $mockDb = $this->getMockDb();
784 $mockDb->expects( $this->once() )
785 ->method( 'selectRow' )
786 ->with(
787 'watchlist',
788 'wl_notificationtimestamp',
789 [
790 'wl_user' => 1,
791 'wl_namespace' => 0,
792 'wl_title' => 'SomeDbKey',
793 ]
794 )
795 ->will( $this->returnValue(
796 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
797 ) );
798
799 $mockCache = $this->getMockCache();
800 $mockCache->expects( $this->once() )
801 ->method( 'set' )
802 ->with(
803 '0:SomeDbKey:1'
804 );
805
806 $store = new WatchedItemStore(
807 $this->getMockLoadBalancer( $mockDb ),
808 $mockCache
809 );
810
811 $this->assertTrue(
812 $store->isWatched(
813 $this->getMockNonAnonUserWithId( 1 ),
814 new TitleValue( 0, 'SomeDbKey' )
815 )
816 );
817 }
818
819 public function testIsWatchedItem_noItem() {
820 $mockDb = $this->getMockDb();
821 $mockDb->expects( $this->once() )
822 ->method( 'selectRow' )
823 ->with(
824 'watchlist',
825 'wl_notificationtimestamp',
826 [
827 'wl_user' => 1,
828 'wl_namespace' => 0,
829 'wl_title' => 'SomeDbKey',
830 ]
831 )
832 ->will( $this->returnValue( [] ) );
833
834 $mockCache = $this->getMockCache();
835 $mockCache->expects( $this->never() )
836 ->method( 'set' );
837
838 $store = new WatchedItemStore(
839 $this->getMockLoadBalancer( $mockDb ),
840 $mockCache
841 );
842
843 $this->assertFalse(
844 $store->isWatched(
845 $this->getMockNonAnonUserWithId( 1 ),
846 new TitleValue( 0, 'SomeDbKey' )
847 )
848 );
849 }
850
851 public function testIsWatchedItem_anonymousUser() {
852 $mockDb = $this->getMockDb();
853 $mockDb->expects( $this->never() )
854 ->method( 'selectRow' );
855
856 $mockCache = $this->getMockCache();
857 $mockCache->expects( $this->never() )
858 ->method( 'set' );
859
860 $store = new WatchedItemStore(
861 $this->getMockLoadBalancer( $mockDb ),
862 $mockCache
863 );
864
865 $this->assertFalse(
866 $store->isWatched(
867 $this->getAnonUser(),
868 new TitleValue( 0, 'SomeDbKey' )
869 )
870 );
871 }
872
873 public function testResetNotificationTimestamp_anonymousUser() {
874 $mockDb = $this->getMockDb();
875 $mockDb->expects( $this->never() )
876 ->method( 'selectRow' );
877
878 $mockCache = $this->getMockCache();
879 $mockCache->expects( $this->never() )
880 ->method( 'set' );
881
882 $store = new WatchedItemStore(
883 $this->getMockLoadBalancer( $mockDb ),
884 $mockCache
885 );
886
887 $this->assertFalse(
888 $store->resetNotificationTimestamp(
889 $this->getAnonUser(),
890 Title::newFromText( 'SomeDbKey' )
891 )
892 );
893 }
894
895 public function testResetNotificationTimestamp_noItem() {
896 $mockDb = $this->getMockDb();
897 $mockDb->expects( $this->once() )
898 ->method( 'selectRow' )
899 ->with(
900 'watchlist',
901 'wl_notificationtimestamp',
902 [
903 'wl_user' => 1,
904 'wl_namespace' => 0,
905 'wl_title' => 'SomeDbKey',
906 ]
907 )
908 ->will( $this->returnValue( [] ) );
909
910 $mockCache = $this->getMockCache();
911 $mockCache->expects( $this->never() )
912 ->method( 'set' );
913
914 $store = new WatchedItemStore(
915 $this->getMockLoadBalancer( $mockDb ),
916 $mockCache
917 );
918
919 $this->assertFalse(
920 $store->resetNotificationTimestamp(
921 $this->getMockNonAnonUserWithId( 1 ),
922 Title::newFromText( 'SomeDbKey' )
923 )
924 );
925 }
926
927 public function testResetNotificationTimestamp_item() {
928 $user = $this->getMockNonAnonUserWithId( 1 );
929 $title = Title::newFromText( 'SomeDbKey' );
930
931 $mockDb = $this->getMockDb();
932 $mockDb->expects( $this->once() )
933 ->method( 'selectRow' )
934 ->with(
935 'watchlist',
936 'wl_notificationtimestamp',
937 [
938 'wl_user' => 1,
939 'wl_namespace' => 0,
940 'wl_title' => 'SomeDbKey',
941 ]
942 )
943 ->will( $this->returnValue(
944 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
945 ) );
946
947 $mockCache = $this->getMockCache();
948 $mockCache->expects( $this->once() )
949 ->method( 'set' )
950 ->with(
951 '0:SomeDbKey:1',
952 $this->isInstanceOf( WatchedItem::class )
953 );
954 $mockCache->expects( $this->once() )
955 ->method( 'delete' )
956 ->with( '0:SomeDbKey:1' );
957
958 $store = new WatchedItemStore(
959 $this->getMockLoadBalancer( $mockDb ),
960 $mockCache
961 );
962
963 // Note: This does not actually assert the job is correct
964 $callableCallCounter = 0;
965 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
966 $callableCallCounter++;
967 $this->assertInternalType( 'callable', $callable );
968 };
969 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
970
971 $this->assertTrue(
972 $store->resetNotificationTimestamp(
973 $user,
974 $title
975 )
976 );
977 $this->assertEquals( 1, $callableCallCounter );
978 }
979
980 public function testResetNotificationTimestamp_noItemForced() {
981 $user = $this->getMockNonAnonUserWithId( 1 );
982 $title = Title::newFromText( 'SomeDbKey' );
983
984 $mockDb = $this->getMockDb();
985 $mockDb->expects( $this->never() )
986 ->method( 'selectRow' );
987
988 $mockCache = $this->getMockCache();
989 $mockDb->expects( $this->never() )
990 ->method( 'set' );
991 $mockDb->expects( $this->never() )
992 ->method( 'delete' );
993
994 $store = new WatchedItemStore(
995 $this->getMockLoadBalancer( $mockDb ),
996 $mockCache
997 );
998
999 // Note: This does not actually assert the job is correct
1000 $callableCallCounter = 0;
1001 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1002 $callableCallCounter++;
1003 $this->assertInternalType( 'callable', $callable );
1004 };
1005 $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1006
1007 $this->assertTrue(
1008 $store->resetNotificationTimestamp(
1009 $user,
1010 $title,
1011 'force'
1012 )
1013 );
1014 $this->assertEquals( 1, $callableCallCounter );
1015 }
1016
1017 /**
1018 * @param $text
1019 * @param int $ns
1020 *
1021 * @return PHPUnit_Framework_MockObject_MockObject|Title
1022 */
1023 private function getMockTitle( $text, $ns = 0 ) {
1024 $title = $this->getMock( Title::class );
1025 $title->expects( $this->any() )
1026 ->method( 'getText' )
1027 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1028 $title->expects( $this->any() )
1029 ->method( 'getDbKey' )
1030 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1031 $title->expects( $this->any() )
1032 ->method( 'getNamespace' )
1033 ->will( $this->returnValue( $ns ) );
1034 return $title;
1035 }
1036
1037 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1038 $user = $this->getMockNonAnonUserWithId( 1 );
1039 $oldid = 22;
1040 $title = $this->getMockTitle( 'SomeTitle' );
1041 $title->expects( $this->once() )
1042 ->method( 'getNextRevisionID' )
1043 ->with( $oldid )
1044 ->will( $this->returnValue( false ) );
1045
1046 $mockDb = $this->getMockDb();
1047 $mockDb->expects( $this->never() )
1048 ->method( 'selectRow' );
1049
1050 $mockCache = $this->getMockCache();
1051 $mockDb->expects( $this->never() )
1052 ->method( 'set' );
1053 $mockDb->expects( $this->never() )
1054 ->method( 'delete' );
1055
1056 $store = new WatchedItemStore(
1057 $this->getMockLoadBalancer( $mockDb ),
1058 $mockCache
1059 );
1060
1061 // Note: This does not actually assert the job is correct
1062 $callableCallCounter = 0;
1063 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1064 function( $callable ) use ( &$callableCallCounter ) {
1065 $callableCallCounter++;
1066 $this->assertInternalType( 'callable', $callable );
1067 }
1068 );
1069
1070 $this->assertTrue(
1071 $store->resetNotificationTimestamp(
1072 $user,
1073 $title,
1074 'force',
1075 $oldid
1076 )
1077 );
1078 $this->assertEquals( 1, $callableCallCounter );
1079 }
1080
1081 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
1082 $user = $this->getMockNonAnonUserWithId( 1 );
1083 $oldid = 22;
1084 $title = $this->getMockTitle( 'SomeDbKey' );
1085 $title->expects( $this->once() )
1086 ->method( 'getNextRevisionID' )
1087 ->with( $oldid )
1088 ->will( $this->returnValue( 33 ) );
1089
1090 $mockDb = $this->getMockDb();
1091 $mockDb->expects( $this->once() )
1092 ->method( 'selectRow' )
1093 ->with(
1094 'watchlist',
1095 'wl_notificationtimestamp',
1096 [
1097 'wl_user' => 1,
1098 'wl_namespace' => 0,
1099 'wl_title' => 'SomeDbKey',
1100 ]
1101 )
1102 ->will( $this->returnValue(
1103 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1104 ) );
1105
1106 $mockCache = $this->getMockCache();
1107 $mockDb->expects( $this->never() )
1108 ->method( 'set' );
1109 $mockDb->expects( $this->never() )
1110 ->method( 'delete' );
1111
1112 $store = new WatchedItemStore(
1113 $this->getMockLoadBalancer( $mockDb ),
1114 $mockCache
1115 );
1116
1117 // Note: This does not actually assert the job is correct
1118 $addUpdateCallCounter = 0;
1119 $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1120 function( $callable ) use ( &$addUpdateCallCounter ) {
1121 $addUpdateCallCounter++;
1122 $this->assertInternalType( 'callable', $callable );
1123 }
1124 );
1125
1126 $getTimestampCallCounter = 0;
1127 $store->overrideRevisionGetTimestampFromIdCallback(
1128 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
1129 $getTimestampCallCounter++;
1130 $this->assertEquals( $title, $titleParam );
1131 $this->assertEquals( $oldid, $oldidParam );
1132 }
1133 );
1134
1135 $this->assertTrue(
1136 $store->resetNotificationTimestamp(
1137 $user,
1138 $title,
1139 'force',
1140 $oldid
1141 )
1142 );
1143 $this->assertEquals( 1, $addUpdateCallCounter );
1144 $this->assertEquals( 1, $getTimestampCallCounter );
1145 }
1146
1147 public function testUpdateNotificationTimestamp_watchersExist() {
1148 $mockDb = $this->getMockDb();
1149 $mockDb->expects( $this->once() )
1150 ->method( 'select' )
1151 ->with(
1152 [ 'watchlist' ],
1153 [ 'wl_user' ],
1154 [
1155 'wl_user != 1',
1156 'wl_namespace' => 0,
1157 'wl_title' => 'SomeDbKey',
1158 'wl_notificationtimestamp IS NULL'
1159 ]
1160 )
1161 ->will(
1162 $this->returnValue( [
1163 $this->getFakeRow( [ 'wl_user' => '2' ] ),
1164 $this->getFakeRow( [ 'wl_user' => '3' ] )
1165 ] )
1166 );
1167 $mockDb->expects( $this->once() )
1168 ->method( 'onTransactionIdle' )
1169 ->with( $this->isType( 'callable' ) )
1170 ->will( $this->returnCallback( function( $callable ) {
1171 $callable();
1172 } ) );
1173 $mockDb->expects( $this->once() )
1174 ->method( 'update' )
1175 ->with(
1176 'watchlist',
1177 [ 'wl_notificationtimestamp' => null ],
1178 [
1179 'wl_user' => [ 2, 3 ],
1180 'wl_namespace' => 0,
1181 'wl_title' => 'SomeDbKey',
1182 ]
1183 );
1184
1185 $store = new WatchedItemStore(
1186 $this->getMockLoadBalancer( $mockDb ),
1187 new HashBagOStuff( [ 'maxKeys' => 100 ] )
1188 );
1189
1190 $this->assertEquals(
1191 [ 2, 3 ],
1192 $store->updateNotificationTimestamp(
1193 $this->getMockNonAnonUserWithId( 1 ),
1194 new TitleValue( 0, 'SomeDbKey' ),
1195 '20151212010101'
1196 )
1197 );
1198 }
1199
1200 public function testUpdateNotificationTimestamp_noWatchers() {
1201 $mockDb = $this->getMockDb();
1202 $mockDb->expects( $this->once() )
1203 ->method( 'select' )
1204 ->with(
1205 [ 'watchlist' ],
1206 [ 'wl_user' ],
1207 [
1208 'wl_user != 1',
1209 'wl_namespace' => 0,
1210 'wl_title' => 'SomeDbKey',
1211 'wl_notificationtimestamp IS NULL'
1212 ]
1213 )
1214 ->will(
1215 $this->returnValue( [] )
1216 );
1217 $mockDb->expects( $this->never() )
1218 ->method( 'onTransactionIdle' );
1219 $mockDb->expects( $this->never() )
1220 ->method( 'update' );
1221
1222 $store = new WatchedItemStore(
1223 $this->getMockLoadBalancer( $mockDb ),
1224 new HashBagOStuff( [ 'maxKeys' => 100 ] )
1225 );
1226
1227 $watchers = $store->updateNotificationTimestamp(
1228 $this->getMockNonAnonUserWithId( 1 ),
1229 new TitleValue( 0, 'SomeDbKey' ),
1230 '20151212010101'
1231 );
1232 $this->assertInternalType( 'array', $watchers );
1233 $this->assertEmpty( $watchers );
1234 }
1235
1236 }