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