6 * @covers WatchedItemStore
8 class WatchedItemStoreUnitTest
extends PHPUnit_Framework_TestCase
{
11 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
13 private function getMockDb() {
14 return $this->getMock( IDatabase
::class );
18 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
20 private function getMockLoadBalancer(
22 $expectedConnectionType = null,
23 $readOnlyReason = false
25 $mock = $this->getMockBuilder( LoadBalancer
::class )
26 ->disableOriginalConstructor()
28 if ( $expectedConnectionType !== null ) {
29 $mock->expects( $this->any() )
30 ->method( 'getConnection' )
31 ->with( $expectedConnectionType )
32 ->will( $this->returnValue( $mockDb ) );
34 $mock->expects( $this->any() )
35 ->method( 'getConnection' )
36 ->will( $this->returnValue( $mockDb ) );
38 $mock->expects( $this->any() )
39 ->method( 'getReadOnlyReason' )
40 ->will( $this->returnValue( $readOnlyReason ) );
45 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
47 private function getMockCache() {
48 $mock = $this->getMockBuilder( HashBagOStuff
::class )
49 ->disableOriginalConstructor()
51 $mock->expects( $this->any() )
53 ->will( $this->returnCallback( function() {
54 return implode( ':', func_get_args() );
61 * @return PHPUnit_Framework_MockObject_MockObject|User
63 private function getMockNonAnonUserWithId( $id ) {
64 $mock = $this->getMock( User
::class );
65 $mock->expects( $this->any() )
67 ->will( $this->returnValue( false ) );
68 $mock->expects( $this->any() )
70 ->will( $this->returnValue( $id ) );
77 private function getAnonUser() {
78 return User
::newFromName( 'Anon_User' );
81 private function getFakeRow( array $rowValues ) {
82 $fakeRow = new stdClass();
83 foreach ( $rowValues as $valueName => $value ) {
84 $fakeRow->$valueName = $value;
89 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache ) {
90 return new WatchedItemStore(
96 public function testGetDefaultInstance() {
97 $instanceOne = WatchedItemStore
::getDefaultInstance();
98 $instanceTwo = WatchedItemStore
::getDefaultInstance();
99 $this->assertSame( $instanceOne, $instanceTwo );
102 public function testOverrideDefaultInstance() {
103 $instance = WatchedItemStore
::getDefaultInstance();
104 $scopedOverride = $instance->overrideDefaultInstance( null );
106 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
108 unset( $scopedOverride );
110 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
113 public function testCountWatchedItems() {
114 $user = $this->getMockNonAnonUserWithId( 1 );
116 $mockDb = $this->getMockDb();
117 $mockDb->expects( $this->exactly( 1 ) )
118 ->method( 'selectField' )
123 'wl_user' => $user->getId(),
125 $this->isType( 'string' )
127 ->will( $this->returnValue( 12 ) );
129 $mockCache = $this->getMockCache();
130 $mockCache->expects( $this->never() )->method( 'get' );
131 $mockCache->expects( $this->never() )->method( 'set' );
132 $mockCache->expects( $this->never() )->method( 'delete' );
134 $store = $this->newWatchedItemStore(
135 $this->getMockLoadBalancer( $mockDb ),
139 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
142 public function testCountWatchers() {
143 $titleValue = new TitleValue( 0, 'SomeDbKey' );
145 $mockDb = $this->getMockDb();
146 $mockDb->expects( $this->exactly( 1 ) )
147 ->method( 'selectField' )
152 'wl_namespace' => $titleValue->getNamespace(),
153 'wl_title' => $titleValue->getDBkey(),
155 $this->isType( 'string' )
157 ->will( $this->returnValue( 7 ) );
159 $mockCache = $this->getMockCache();
160 $mockCache->expects( $this->never() )->method( 'get' );
161 $mockCache->expects( $this->never() )->method( 'set' );
162 $mockCache->expects( $this->never() )->method( 'delete' );
164 $store = $this->newWatchedItemStore(
165 $this->getMockLoadBalancer( $mockDb ),
169 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
172 public function testCountWatchersMultiple() {
174 new TitleValue( 0, 'SomeDbKey' ),
175 new TitleValue( 0, 'OtherDbKey' ),
176 new TitleValue( 1, 'AnotherDbKey' ),
179 $mockDb = $this->getMockDb();
182 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
183 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
184 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
187 $mockDb->expects( $this->once() )
188 ->method( 'makeWhereFrom2d' )
190 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
191 $this->isType( 'string' ),
192 $this->isType( 'string' )
194 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
195 $mockDb->expects( $this->once() )
199 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
200 [ 'makeWhereFrom2d return value' ],
201 $this->isType( 'string' ),
203 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
207 $this->returnValue( $dbResult )
210 $mockCache = $this->getMockCache();
211 $mockCache->expects( $this->never() )->method( 'get' );
212 $mockCache->expects( $this->never() )->method( 'set' );
213 $mockCache->expects( $this->never() )->method( 'delete' );
215 $store = $this->newWatchedItemStore(
216 $this->getMockLoadBalancer( $mockDb ),
221 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
222 1 => [ 'AnotherDbKey' => 500 ],
224 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
227 public function provideIntWithDbUnsafeVersion() {
230 [ "50; DROP TABLE watchlist;\n--" ],
235 * @dataProvider provideIntWithDbUnsafeVersion
237 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
239 new TitleValue( 0, 'SomeDbKey' ),
240 new TitleValue( 0, 'OtherDbKey' ),
241 new TitleValue( 1, 'AnotherDbKey' ),
244 $mockDb = $this->getMockDb();
247 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
248 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
249 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
252 $mockDb->expects( $this->once() )
253 ->method( 'makeWhereFrom2d' )
255 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
256 $this->isType( 'string' ),
257 $this->isType( 'string' )
259 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
260 $mockDb->expects( $this->once() )
264 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
265 [ 'makeWhereFrom2d return value' ],
266 $this->isType( 'string' ),
268 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
269 'HAVING' => 'COUNT(*) >= 50',
273 $this->returnValue( $dbResult )
276 $mockCache = $this->getMockCache();
277 $mockCache->expects( $this->never() )->method( 'get' );
278 $mockCache->expects( $this->never() )->method( 'set' );
279 $mockCache->expects( $this->never() )->method( 'delete' );
281 $store = $this->newWatchedItemStore(
282 $this->getMockLoadBalancer( $mockDb ),
287 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
288 1 => [ 'AnotherDbKey' => 500 ],
292 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
296 public function testCountVisitingWatchers() {
297 $titleValue = new TitleValue( 0, 'SomeDbKey' );
299 $mockDb = $this->getMockDb();
300 $mockDb->expects( $this->exactly( 1 ) )
301 ->method( 'selectField' )
306 'wl_namespace' => $titleValue->getNamespace(),
307 'wl_title' => $titleValue->getDBkey(),
308 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
310 $this->isType( 'string' )
312 ->will( $this->returnValue( 7 ) );
313 $mockDb->expects( $this->exactly( 1 ) )
314 ->method( 'addQuotes' )
315 ->will( $this->returnCallback( function( $value ) {
318 $mockDb->expects( $this->exactly( 1 ) )
319 ->method( 'timestamp' )
320 ->will( $this->returnCallback( function( $value ) {
321 return 'TS' . $value . 'TS';
324 $mockCache = $this->getMockCache();
325 $mockCache->expects( $this->never() )->method( 'set' );
326 $mockCache->expects( $this->never() )->method( 'get' );
327 $mockCache->expects( $this->never() )->method( 'delete' );
329 $store = $this->newWatchedItemStore(
330 $this->getMockLoadBalancer( $mockDb ),
334 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
337 public function testCountVisitingWatchersMultiple() {
338 $titleValuesWithThresholds = [
339 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
340 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
341 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
345 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
346 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
347 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
349 $mockDb = $this->getMockDb();
350 $mockDb->expects( $this->exactly( 2 * 3 ) )
351 ->method( 'addQuotes' )
352 ->will( $this->returnCallback( function( $value ) {
355 $mockDb->expects( $this->exactly( 3 ) )
356 ->method( 'timestamp' )
357 ->will( $this->returnCallback( function( $value ) {
358 return 'TS' . $value . 'TS';
360 $mockDb->expects( $this->any() )
361 ->method( 'makeList' )
363 $this->isType( 'array' ),
364 $this->isType( 'int' )
366 ->will( $this->returnCallback( function( $a, $conj ) {
367 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
368 return join( $sqlConj, array_map( function( $s ) {
369 return '(' . $s . ')';
373 $mockDb->expects( $this->never() )
374 ->method( 'makeWhereFrom2d' );
377 '((wl_namespace = 0) AND (' .
378 "(((wl_title = 'SomeDbKey') AND (" .
379 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
381 "(wl_title = 'OtherDbKey') AND (" .
382 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
384 ') OR ((wl_namespace = 1) AND (' .
385 "(((wl_title = 'AnotherDbKey') AND (".
386 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
388 $mockDb->expects( $this->once() )
392 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
394 $this->isType( 'string' ),
396 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
400 $this->returnValue( $dbResult )
403 $mockCache = $this->getMockCache();
404 $mockCache->expects( $this->never() )->method( 'get' );
405 $mockCache->expects( $this->never() )->method( 'set' );
406 $mockCache->expects( $this->never() )->method( 'delete' );
408 $store = $this->newWatchedItemStore(
409 $this->getMockLoadBalancer( $mockDb ),
414 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
415 1 => [ 'AnotherDbKey' => 500 ],
419 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
423 public function testCountVisitingWatchersMultiple_withMissingTargets() {
424 $titleValuesWithThresholds = [
425 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
426 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
427 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
428 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
429 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
433 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
434 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
435 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
437 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
440 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
443 $mockDb = $this->getMockDb();
444 $mockDb->expects( $this->exactly( 2 * 3 ) )
445 ->method( 'addQuotes' )
446 ->will( $this->returnCallback( function( $value ) {
449 $mockDb->expects( $this->exactly( 3 ) )
450 ->method( 'timestamp' )
451 ->will( $this->returnCallback( function( $value ) {
452 return 'TS' . $value . 'TS';
454 $mockDb->expects( $this->any() )
455 ->method( 'makeList' )
457 $this->isType( 'array' ),
458 $this->isType( 'int' )
460 ->will( $this->returnCallback( function( $a, $conj ) {
461 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
462 return join( $sqlConj, array_map( function( $s ) {
463 return '(' . $s . ')';
467 $mockDb->expects( $this->once() )
468 ->method( 'makeWhereFrom2d' )
470 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
471 $this->isType( 'string' ),
472 $this->isType( 'string' )
474 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
477 '((wl_namespace = 0) AND (' .
478 "(((wl_title = 'SomeDbKey') AND (" .
479 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
481 "(wl_title = 'OtherDbKey') AND (" .
482 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
484 ') OR ((wl_namespace = 1) AND (' .
485 "(((wl_title = 'AnotherDbKey') AND (".
486 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
489 '(makeWhereFrom2d return value)';
490 $mockDb->expects( $this->once() )
494 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
496 $this->isType( 'string' ),
498 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
502 $this->returnValue( $dbResult )
505 $mockCache = $this->getMockCache();
506 $mockCache->expects( $this->never() )->method( 'get' );
507 $mockCache->expects( $this->never() )->method( 'set' );
508 $mockCache->expects( $this->never() )->method( 'delete' );
510 $store = $this->newWatchedItemStore(
511 $this->getMockLoadBalancer( $mockDb ),
517 'SomeDbKey' => 100, 'OtherDbKey' => 300,
518 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
520 1 => [ 'AnotherDbKey' => 500 ],
524 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
529 * @dataProvider provideIntWithDbUnsafeVersion
531 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
532 $titleValuesWithThresholds = [
533 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
534 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
535 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
538 $mockDb = $this->getMockDb();
539 $mockDb->expects( $this->any() )
540 ->method( 'makeList' )
541 ->will( $this->returnValue( 'makeList return value' ) );
542 $mockDb->expects( $this->once() )
546 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
547 'makeList return value',
548 $this->isType( 'string' ),
550 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
551 'HAVING' => 'COUNT(*) >= 50',
555 $this->returnValue( [] )
558 $mockCache = $this->getMockCache();
559 $mockCache->expects( $this->never() )->method( 'get' );
560 $mockCache->expects( $this->never() )->method( 'set' );
561 $mockCache->expects( $this->never() )->method( 'delete' );
563 $store = $this->newWatchedItemStore(
564 $this->getMockLoadBalancer( $mockDb ),
569 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
570 1 => [ 'AnotherDbKey' => 0 ],
574 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
578 public function testCountUnreadNotifications() {
579 $user = $this->getMockNonAnonUserWithId( 1 );
581 $mockDb = $this->getMockDb();
582 $mockDb->expects( $this->exactly( 1 ) )
583 ->method( 'selectRowCount' )
588 "wl_notificationtimestamp IS NOT NULL",
591 $this->isType( 'string' )
593 ->will( $this->returnValue( 9 ) );
595 $mockCache = $this->getMockCache();
596 $mockCache->expects( $this->never() )->method( 'set' );
597 $mockCache->expects( $this->never() )->method( 'get' );
598 $mockCache->expects( $this->never() )->method( 'delete' );
600 $store = $this->newWatchedItemStore(
601 $this->getMockLoadBalancer( $mockDb ),
605 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
609 * @dataProvider provideIntWithDbUnsafeVersion
611 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
612 $user = $this->getMockNonAnonUserWithId( 1 );
614 $mockDb = $this->getMockDb();
615 $mockDb->expects( $this->exactly( 1 ) )
616 ->method( 'selectRowCount' )
621 "wl_notificationtimestamp IS NOT NULL",
624 $this->isType( 'string' ),
627 ->will( $this->returnValue( 50 ) );
629 $mockCache = $this->getMockCache();
630 $mockCache->expects( $this->never() )->method( 'set' );
631 $mockCache->expects( $this->never() )->method( 'get' );
632 $mockCache->expects( $this->never() )->method( 'delete' );
634 $store = $this->newWatchedItemStore(
635 $this->getMockLoadBalancer( $mockDb ),
641 $store->countUnreadNotifications( $user, $limit )
646 * @dataProvider provideIntWithDbUnsafeVersion
648 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
649 $user = $this->getMockNonAnonUserWithId( 1 );
651 $mockDb = $this->getMockDb();
652 $mockDb->expects( $this->exactly( 1 ) )
653 ->method( 'selectRowCount' )
658 "wl_notificationtimestamp IS NOT NULL",
661 $this->isType( 'string' ),
664 ->will( $this->returnValue( 9 ) );
666 $mockCache = $this->getMockCache();
667 $mockCache->expects( $this->never() )->method( 'set' );
668 $mockCache->expects( $this->never() )->method( 'get' );
669 $mockCache->expects( $this->never() )->method( 'delete' );
671 $store = $this->newWatchedItemStore(
672 $this->getMockLoadBalancer( $mockDb ),
678 $store->countUnreadNotifications( $user, $limit )
682 public function testDuplicateEntry_nothingToDuplicate() {
683 $mockDb = $this->getMockDb();
684 $mockDb->expects( $this->once() )
690 'wl_notificationtimestamp',
694 'wl_title' => 'Old_Title',
696 'WatchedItemStore::duplicateEntry',
699 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
701 $store = $this->newWatchedItemStore(
702 $this->getMockLoadBalancer( $mockDb ),
703 $this->getMockCache()
706 $store->duplicateEntry(
707 Title
::newFromText( 'Old_Title' ),
708 Title
::newFromText( 'New_Title' )
712 public function testDuplicateEntry_somethingToDuplicate() {
714 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
715 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
718 $mockDb = $this->getMockDb();
719 $mockDb->expects( $this->at( 0 ) )
725 'wl_notificationtimestamp',
729 'wl_title' => 'Old_Title',
732 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
733 $mockDb->expects( $this->at( 1 ) )
734 ->method( 'replace' )
737 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
742 'wl_title' => 'New_Title',
743 'wl_notificationtimestamp' => '20151212010101',
748 'wl_title' => 'New_Title',
749 'wl_notificationtimestamp' => null,
752 $this->isType( 'string' )
755 $mockCache = $this->getMockCache();
756 $mockCache->expects( $this->never() )->method( 'get' );
757 $mockCache->expects( $this->never() )->method( 'delete' );
759 $store = $this->newWatchedItemStore(
760 $this->getMockLoadBalancer( $mockDb ),
764 $store->duplicateEntry(
765 Title
::newFromText( 'Old_Title' ),
766 Title
::newFromText( 'New_Title' )
770 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
771 $mockDb = $this->getMockDb();
772 $mockDb->expects( $this->at( 0 ) )
778 'wl_notificationtimestamp',
782 'wl_title' => 'Old_Title',
785 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
786 $mockDb->expects( $this->at( 1 ) )
792 'wl_notificationtimestamp',
796 'wl_title' => 'Old_Title',
799 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
801 $mockCache = $this->getMockCache();
802 $mockCache->expects( $this->never() )->method( 'get' );
803 $mockCache->expects( $this->never() )->method( 'delete' );
805 $store = $this->newWatchedItemStore(
806 $this->getMockLoadBalancer( $mockDb ),
810 $store->duplicateAllAssociatedEntries(
811 Title
::newFromText( 'Old_Title' ),
812 Title
::newFromText( 'New_Title' )
816 public function provideLinkTargetPairs() {
818 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
819 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
824 * @dataProvider provideLinkTargetPairs
826 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
827 LinkTarget
$oldTarget,
828 LinkTarget
$newTarget
831 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
834 $mockDb = $this->getMockDb();
835 $mockDb->expects( $this->at( 0 ) )
841 'wl_notificationtimestamp',
844 'wl_namespace' => $oldTarget->getNamespace(),
845 'wl_title' => $oldTarget->getDBkey(),
848 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
849 $mockDb->expects( $this->at( 1 ) )
850 ->method( 'replace' )
853 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
857 'wl_namespace' => $newTarget->getNamespace(),
858 'wl_title' => $newTarget->getDBkey(),
859 'wl_notificationtimestamp' => '20151212010101',
862 $this->isType( 'string' )
864 $mockDb->expects( $this->at( 2 ) )
870 'wl_notificationtimestamp',
873 'wl_namespace' => $oldTarget->getNamespace() +
1,
874 'wl_title' => $oldTarget->getDBkey(),
877 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
878 $mockDb->expects( $this->at( 3 ) )
879 ->method( 'replace' )
882 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
886 'wl_namespace' => $newTarget->getNamespace() +
1,
887 'wl_title' => $newTarget->getDBkey(),
888 'wl_notificationtimestamp' => '20151212010101',
891 $this->isType( 'string' )
894 $mockCache = $this->getMockCache();
895 $mockCache->expects( $this->never() )->method( 'get' );
896 $mockCache->expects( $this->never() )->method( 'delete' );
898 $store = $this->newWatchedItemStore(
899 $this->getMockLoadBalancer( $mockDb ),
903 $store->duplicateAllAssociatedEntries(
909 public function testAddWatch_nonAnonymousUser() {
910 $mockDb = $this->getMockDb();
911 $mockDb->expects( $this->once() )
919 'wl_title' => 'Some_Page',
920 'wl_notificationtimestamp' => null,
925 $mockCache = $this->getMockCache();
926 $mockCache->expects( $this->once() )
928 ->with( '0:Some_Page:1' );
930 $store = $this->newWatchedItemStore(
931 $this->getMockLoadBalancer( $mockDb ),
936 $this->getMockNonAnonUserWithId( 1 ),
937 Title
::newFromText( 'Some_Page' )
941 public function testAddWatch_anonymousUser() {
942 $mockDb = $this->getMockDb();
943 $mockDb->expects( $this->never() )
944 ->method( 'insert' );
946 $mockCache = $this->getMockCache();
947 $mockCache->expects( $this->never() )
948 ->method( 'delete' );
950 $store = $this->newWatchedItemStore(
951 $this->getMockLoadBalancer( $mockDb ),
956 $this->getAnonUser(),
957 Title
::newFromText( 'Some_Page' )
961 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
962 $store = $this->newWatchedItemStore(
963 $this->getMockLoadBalancer( $this->getMockDb(), null, 'Some Reason' ),
964 $this->getMockCache()
968 $store->addWatchBatchForUser(
969 $this->getMockNonAnonUserWithId( 1 ),
970 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
975 public function testAddWatchBatchForUser_nonAnonymousUser() {
976 $mockDb = $this->getMockDb();
977 $mockDb->expects( $this->once() )
985 'wl_title' => 'Some_Page',
986 'wl_notificationtimestamp' => null,
991 'wl_title' => 'Some_Page',
992 'wl_notificationtimestamp' => null,
997 $mockCache = $this->getMockCache();
998 $mockCache->expects( $this->exactly( 2 ) )
999 ->method( 'delete' );
1000 $mockCache->expects( $this->at( 1 ) )
1001 ->method( 'delete' )
1002 ->with( '0:Some_Page:1' );
1003 $mockCache->expects( $this->at( 3 ) )
1004 ->method( 'delete' )
1005 ->with( '1:Some_Page:1' );
1007 $store = $this->newWatchedItemStore(
1008 $this->getMockLoadBalancer( $mockDb ),
1012 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1015 $store->addWatchBatchForUser(
1017 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1022 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1023 $mockDb = $this->getMockDb();
1024 $mockDb->expects( $this->never() )
1025 ->method( 'insert' );
1027 $mockCache = $this->getMockCache();
1028 $mockCache->expects( $this->never() )
1029 ->method( 'delete' );
1031 $store = $this->newWatchedItemStore(
1032 $this->getMockLoadBalancer( $mockDb ),
1037 $store->addWatchBatchForUser(
1038 $this->getAnonUser(),
1039 [ new TitleValue( 0, 'Other_Page' ) ]
1044 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1045 $user = $this->getMockNonAnonUserWithId( 1 );
1046 $mockDb = $this->getMockDb();
1047 $mockDb->expects( $this->never() )
1048 ->method( 'insert' );
1050 $mockCache = $this->getMockCache();
1051 $mockCache->expects( $this->never() )
1052 ->method( 'delete' );
1054 $store = $this->newWatchedItemStore(
1055 $this->getMockLoadBalancer( $mockDb ),
1060 $store->addWatchBatchForUser( $user, [] )
1064 public function testLoadWatchedItem_existingItem() {
1065 $mockDb = $this->getMockDb();
1066 $mockDb->expects( $this->once() )
1067 ->method( 'selectRow' )
1070 'wl_notificationtimestamp',
1073 'wl_namespace' => 0,
1074 'wl_title' => 'SomeDbKey',
1077 ->will( $this->returnValue(
1078 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1081 $mockCache = $this->getMockCache();
1082 $mockCache->expects( $this->once() )
1088 $store = $this->newWatchedItemStore(
1089 $this->getMockLoadBalancer( $mockDb ),
1093 $watchedItem = $store->loadWatchedItem(
1094 $this->getMockNonAnonUserWithId( 1 ),
1095 new TitleValue( 0, 'SomeDbKey' )
1097 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1098 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1099 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1100 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1103 public function testLoadWatchedItem_noItem() {
1104 $mockDb = $this->getMockDb();
1105 $mockDb->expects( $this->once() )
1106 ->method( 'selectRow' )
1109 'wl_notificationtimestamp',
1112 'wl_namespace' => 0,
1113 'wl_title' => 'SomeDbKey',
1116 ->will( $this->returnValue( [] ) );
1118 $mockCache = $this->getMockCache();
1119 $mockCache->expects( $this->never() )->method( 'get' );
1120 $mockCache->expects( $this->never() )->method( 'delete' );
1122 $store = $this->newWatchedItemStore(
1123 $this->getMockLoadBalancer( $mockDb ),
1128 $store->loadWatchedItem(
1129 $this->getMockNonAnonUserWithId( 1 ),
1130 new TitleValue( 0, 'SomeDbKey' )
1135 public function testLoadWatchedItem_anonymousUser() {
1136 $mockDb = $this->getMockDb();
1137 $mockDb->expects( $this->never() )
1138 ->method( 'selectRow' );
1140 $mockCache = $this->getMockCache();
1141 $mockCache->expects( $this->never() )->method( 'get' );
1142 $mockCache->expects( $this->never() )->method( 'delete' );
1144 $store = $this->newWatchedItemStore(
1145 $this->getMockLoadBalancer( $mockDb ),
1150 $store->loadWatchedItem(
1151 $this->getAnonUser(),
1152 new TitleValue( 0, 'SomeDbKey' )
1157 public function testRemoveWatch_existingItem() {
1158 $mockDb = $this->getMockDb();
1159 $mockDb->expects( $this->once() )
1160 ->method( 'delete' )
1165 'wl_namespace' => 0,
1166 'wl_title' => 'SomeDbKey',
1169 $mockDb->expects( $this->once() )
1170 ->method( 'affectedRows' )
1171 ->will( $this->returnValue( 1 ) );
1173 $mockCache = $this->getMockCache();
1174 $mockCache->expects( $this->never() )->method( 'get' );
1175 $mockCache->expects( $this->once() )
1176 ->method( 'delete' )
1177 ->with( '0:SomeDbKey:1' );
1179 $store = $this->newWatchedItemStore(
1180 $this->getMockLoadBalancer( $mockDb ),
1185 $store->removeWatch(
1186 $this->getMockNonAnonUserWithId( 1 ),
1187 new TitleValue( 0, 'SomeDbKey' )
1192 public function testRemoveWatch_noItem() {
1193 $mockDb = $this->getMockDb();
1194 $mockDb->expects( $this->once() )
1195 ->method( 'delete' )
1200 'wl_namespace' => 0,
1201 'wl_title' => 'SomeDbKey',
1204 $mockDb->expects( $this->once() )
1205 ->method( 'affectedRows' )
1206 ->will( $this->returnValue( 0 ) );
1208 $mockCache = $this->getMockCache();
1209 $mockCache->expects( $this->never() )->method( 'get' );
1210 $mockCache->expects( $this->once() )
1211 ->method( 'delete' )
1212 ->with( '0:SomeDbKey:1' );
1214 $store = $this->newWatchedItemStore(
1215 $this->getMockLoadBalancer( $mockDb ),
1220 $store->removeWatch(
1221 $this->getMockNonAnonUserWithId( 1 ),
1222 new TitleValue( 0, 'SomeDbKey' )
1227 public function testRemoveWatch_anonymousUser() {
1228 $mockDb = $this->getMockDb();
1229 $mockDb->expects( $this->never() )
1230 ->method( 'delete' );
1232 $mockCache = $this->getMockCache();
1233 $mockCache->expects( $this->never() )->method( 'get' );
1234 $mockCache->expects( $this->never() )
1235 ->method( 'delete' );
1237 $store = $this->newWatchedItemStore(
1238 $this->getMockLoadBalancer( $mockDb ),
1243 $store->removeWatch(
1244 $this->getAnonUser(),
1245 new TitleValue( 0, 'SomeDbKey' )
1250 public function testGetWatchedItem_existingItem() {
1251 $mockDb = $this->getMockDb();
1252 $mockDb->expects( $this->once() )
1253 ->method( 'selectRow' )
1256 'wl_notificationtimestamp',
1259 'wl_namespace' => 0,
1260 'wl_title' => 'SomeDbKey',
1263 ->will( $this->returnValue(
1264 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1267 $mockCache = $this->getMockCache();
1268 $mockCache->expects( $this->never() )->method( 'delete' );
1269 $mockCache->expects( $this->once() )
1274 ->will( $this->returnValue( null ) );
1275 $mockCache->expects( $this->once() )
1281 $store = $this->newWatchedItemStore(
1282 $this->getMockLoadBalancer( $mockDb ),
1286 $watchedItem = $store->getWatchedItem(
1287 $this->getMockNonAnonUserWithId( 1 ),
1288 new TitleValue( 0, 'SomeDbKey' )
1290 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1291 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1292 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1293 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1296 public function testGetWatchedItem_cachedItem() {
1297 $mockDb = $this->getMockDb();
1298 $mockDb->expects( $this->never() )
1299 ->method( 'selectRow' );
1301 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1302 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1303 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1305 $mockCache = $this->getMockCache();
1306 $mockCache->expects( $this->never() )->method( 'delete' );
1307 $mockCache->expects( $this->never() )->method( 'set' );
1308 $mockCache->expects( $this->once() )
1313 ->will( $this->returnValue( $cachedItem ) );
1315 $store = $this->newWatchedItemStore(
1316 $this->getMockLoadBalancer( $mockDb ),
1320 $this->assertEquals(
1322 $store->getWatchedItem(
1329 public function testGetWatchedItem_noItem() {
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->once() )
1332 ->method( 'selectRow' )
1335 'wl_notificationtimestamp',
1338 'wl_namespace' => 0,
1339 'wl_title' => 'SomeDbKey',
1342 ->will( $this->returnValue( [] ) );
1344 $mockCache = $this->getMockCache();
1345 $mockCache->expects( $this->never() )->method( 'set' );
1346 $mockCache->expects( $this->never() )->method( 'delete' );
1347 $mockCache->expects( $this->once() )
1349 ->with( '0:SomeDbKey:1' )
1350 ->will( $this->returnValue( false ) );
1352 $store = $this->newWatchedItemStore(
1353 $this->getMockLoadBalancer( $mockDb ),
1358 $store->getWatchedItem(
1359 $this->getMockNonAnonUserWithId( 1 ),
1360 new TitleValue( 0, 'SomeDbKey' )
1365 public function testGetWatchedItem_anonymousUser() {
1366 $mockDb = $this->getMockDb();
1367 $mockDb->expects( $this->never() )
1368 ->method( 'selectRow' );
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'set' );
1372 $mockCache->expects( $this->never() )->method( 'get' );
1373 $mockCache->expects( $this->never() )->method( 'delete' );
1375 $store = $this->newWatchedItemStore(
1376 $this->getMockLoadBalancer( $mockDb ),
1381 $store->getWatchedItem(
1382 $this->getAnonUser(),
1383 new TitleValue( 0, 'SomeDbKey' )
1388 public function testGetWatchedItemsForUser() {
1389 $mockDb = $this->getMockDb();
1390 $mockDb->expects( $this->once() )
1391 ->method( 'select' )
1394 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1397 ->will( $this->returnValue( [
1398 $this->getFakeRow( [
1399 'wl_namespace' => 0,
1400 'wl_title' => 'Foo1',
1401 'wl_notificationtimestamp' => '20151212010101',
1403 $this->getFakeRow( [
1404 'wl_namespace' => 1,
1405 'wl_title' => 'Foo2',
1406 'wl_notificationtimestamp' => null,
1410 $mockCache = $this->getMockCache();
1411 $mockCache->expects( $this->never() )->method( 'delete' );
1412 $mockCache->expects( $this->never() )->method( 'get' );
1413 $mockCache->expects( $this->never() )->method( 'set' );
1415 $store = $this->newWatchedItemStore(
1416 $this->getMockLoadBalancer( $mockDb ),
1419 $user = $this->getMockNonAnonUserWithId( 1 );
1421 $watchedItems = $store->getWatchedItemsForUser( $user );
1423 $this->assertInternalType( 'array', $watchedItems );
1424 $this->assertCount( 2, $watchedItems );
1425 foreach ( $watchedItems as $watchedItem ) {
1426 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1428 $this->assertEquals(
1429 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1432 $this->assertEquals(
1433 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1438 public function provideDbTypes() {
1440 [ false, DB_SLAVE
],
1441 [ true, DB_MASTER
],
1446 * @dataProvider provideDbTypes
1448 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1449 $mockDb = $this->getMockDb();
1450 $mockCache = $this->getMockCache();
1451 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1452 $user = $this->getMockNonAnonUserWithId( 1 );
1454 $mockDb->expects( $this->once() )
1455 ->method( 'select' )
1458 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1460 $this->isType( 'string' ),
1461 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1463 ->will( $this->returnValue( [] ) );
1465 $store = $this->newWatchedItemStore(
1470 $watchedItems = $store->getWatchedItemsForUser(
1472 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1474 $this->assertEquals( [], $watchedItems );
1477 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1478 $store = $this->newWatchedItemStore(
1479 $this->getMockLoadBalancer( $this->getMockDb() ),
1480 $this->getMockCache()
1483 $this->setExpectedException( 'InvalidArgumentException' );
1484 $store->getWatchedItemsForUser(
1485 $this->getMockNonAnonUserWithId( 1 ),
1490 public function testIsWatchedItem_existingItem() {
1491 $mockDb = $this->getMockDb();
1492 $mockDb->expects( $this->once() )
1493 ->method( 'selectRow' )
1496 'wl_notificationtimestamp',
1499 'wl_namespace' => 0,
1500 'wl_title' => 'SomeDbKey',
1503 ->will( $this->returnValue(
1504 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1507 $mockCache = $this->getMockCache();
1508 $mockCache->expects( $this->never() )->method( 'delete' );
1509 $mockCache->expects( $this->once() )
1511 ->with( '0:SomeDbKey:1' )
1512 ->will( $this->returnValue( false ) );
1513 $mockCache->expects( $this->once() )
1519 $store = $this->newWatchedItemStore(
1520 $this->getMockLoadBalancer( $mockDb ),
1526 $this->getMockNonAnonUserWithId( 1 ),
1527 new TitleValue( 0, 'SomeDbKey' )
1532 public function testIsWatchedItem_noItem() {
1533 $mockDb = $this->getMockDb();
1534 $mockDb->expects( $this->once() )
1535 ->method( 'selectRow' )
1538 'wl_notificationtimestamp',
1541 'wl_namespace' => 0,
1542 'wl_title' => 'SomeDbKey',
1545 ->will( $this->returnValue( [] ) );
1547 $mockCache = $this->getMockCache();
1548 $mockCache->expects( $this->never() )->method( 'set' );
1549 $mockCache->expects( $this->never() )->method( 'delete' );
1550 $mockCache->expects( $this->once() )
1552 ->with( '0:SomeDbKey:1' )
1553 ->will( $this->returnValue( false ) );
1555 $store = $this->newWatchedItemStore(
1556 $this->getMockLoadBalancer( $mockDb ),
1562 $this->getMockNonAnonUserWithId( 1 ),
1563 new TitleValue( 0, 'SomeDbKey' )
1568 public function testIsWatchedItem_anonymousUser() {
1569 $mockDb = $this->getMockDb();
1570 $mockDb->expects( $this->never() )
1571 ->method( 'selectRow' );
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' );
1578 $store = $this->newWatchedItemStore(
1579 $this->getMockLoadBalancer( $mockDb ),
1585 $this->getAnonUser(),
1586 new TitleValue( 0, 'SomeDbKey' )
1591 public function testGetNotificationTimestampsBatch() {
1593 new TitleValue( 0, 'SomeDbKey' ),
1594 new TitleValue( 1, 'AnotherDbKey' ),
1597 $mockDb = $this->getMockDb();
1599 $this->getFakeRow( [
1600 'wl_namespace' => 0,
1601 'wl_title' => 'SomeDbKey',
1602 'wl_notificationtimestamp' => '20151212010101',
1606 'wl_namespace' => 1,
1607 'wl_title' => 'AnotherDbKey',
1608 'wl_notificationtimestamp' => null,
1613 $mockDb->expects( $this->once() )
1614 ->method( 'makeWhereFrom2d' )
1616 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1617 $this->isType( 'string' ),
1618 $this->isType( 'string' )
1620 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1621 $mockDb->expects( $this->once() )
1622 ->method( 'select' )
1625 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1627 'makeWhereFrom2d return value',
1630 $this->isType( 'string' )
1632 ->will( $this->returnValue( $dbResult ) );
1634 $mockCache = $this->getMockCache();
1635 $mockCache->expects( $this->exactly( 2 ) )
1638 [ '0:SomeDbKey:1' ],
1639 [ '1:AnotherDbKey:1' ]
1641 ->will( $this->returnValue( null ) );
1642 $mockCache->expects( $this->never() )->method( 'set' );
1643 $mockCache->expects( $this->never() )->method( 'delete' );
1645 $store = $this->newWatchedItemStore(
1646 $this->getMockLoadBalancer( $mockDb ),
1650 $this->assertEquals(
1652 0 => [ 'SomeDbKey' => '20151212010101', ],
1653 1 => [ 'AnotherDbKey' => null, ],
1655 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1659 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1661 new TitleValue( 0, 'OtherDbKey' ),
1664 $mockDb = $this->getMockDb();
1666 $mockDb->expects( $this->once() )
1667 ->method( 'makeWhereFrom2d' )
1669 [ [ 'OtherDbKey' => 1 ] ],
1670 $this->isType( 'string' ),
1671 $this->isType( 'string' )
1673 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1674 $mockDb->expects( $this->once() )
1675 ->method( 'select' )
1678 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1680 'makeWhereFrom2d return value',
1683 $this->isType( 'string' )
1685 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1687 $mockCache = $this->getMockCache();
1688 $mockCache->expects( $this->once() )
1690 ->with( '0:OtherDbKey:1' )
1691 ->will( $this->returnValue( null ) );
1692 $mockCache->expects( $this->never() )->method( 'set' );
1693 $mockCache->expects( $this->never() )->method( 'delete' );
1695 $store = $this->newWatchedItemStore(
1696 $this->getMockLoadBalancer( $mockDb ),
1700 $this->assertEquals(
1702 0 => [ 'OtherDbKey' => false, ],
1704 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1708 public function testGetNotificationTimestampsBatch_cachedItem() {
1710 new TitleValue( 0, 'SomeDbKey' ),
1711 new TitleValue( 1, 'AnotherDbKey' ),
1714 $user = $this->getMockNonAnonUserWithId( 1 );
1715 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1717 $mockDb = $this->getMockDb();
1719 $mockDb->expects( $this->once() )
1720 ->method( 'makeWhereFrom2d' )
1722 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1723 $this->isType( 'string' ),
1724 $this->isType( 'string' )
1726 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1727 $mockDb->expects( $this->once() )
1728 ->method( 'select' )
1731 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1733 'makeWhereFrom2d return value',
1736 $this->isType( 'string' )
1738 ->will( $this->returnValue( [
1740 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1744 $mockCache = $this->getMockCache();
1745 $mockCache->expects( $this->at( 1 ) )
1747 ->with( '0:SomeDbKey:1' )
1748 ->will( $this->returnValue( $cachedItem ) );
1749 $mockCache->expects( $this->at( 3 ) )
1751 ->with( '1:AnotherDbKey:1' )
1752 ->will( $this->returnValue( null ) );
1753 $mockCache->expects( $this->never() )->method( 'set' );
1754 $mockCache->expects( $this->never() )->method( 'delete' );
1756 $store = $this->newWatchedItemStore(
1757 $this->getMockLoadBalancer( $mockDb ),
1761 $this->assertEquals(
1763 0 => [ 'SomeDbKey' => '20151212010101', ],
1764 1 => [ 'AnotherDbKey' => null, ],
1766 $store->getNotificationTimestampsBatch( $user, $targets )
1770 public function testGetNotificationTimestampsBatch_allItemsCached() {
1772 new TitleValue( 0, 'SomeDbKey' ),
1773 new TitleValue( 1, 'AnotherDbKey' ),
1776 $user = $this->getMockNonAnonUserWithId( 1 );
1778 new WatchedItem( $user, $targets[0], '20151212010101' ),
1779 new WatchedItem( $user, $targets[1], null ),
1781 $mockDb = $this->getMockDb();
1782 $mockDb->expects( $this->never() )->method( $this->anything() );
1784 $mockCache = $this->getMockCache();
1785 $mockCache->expects( $this->at( 1 ) )
1787 ->with( '0:SomeDbKey:1' )
1788 ->will( $this->returnValue( $cachedItems[0] ) );
1789 $mockCache->expects( $this->at( 3 ) )
1791 ->with( '1:AnotherDbKey:1' )
1792 ->will( $this->returnValue( $cachedItems[1] ) );
1793 $mockCache->expects( $this->never() )->method( 'set' );
1794 $mockCache->expects( $this->never() )->method( 'delete' );
1796 $store = $this->newWatchedItemStore(
1797 $this->getMockLoadBalancer( $mockDb ),
1801 $this->assertEquals(
1803 0 => [ 'SomeDbKey' => '20151212010101', ],
1804 1 => [ 'AnotherDbKey' => null, ],
1806 $store->getNotificationTimestampsBatch( $user, $targets )
1810 public function testGetNotificationTimestampsBatch_anonymousUser() {
1812 new TitleValue( 0, 'SomeDbKey' ),
1813 new TitleValue( 1, 'AnotherDbKey' ),
1816 $mockDb = $this->getMockDb();
1817 $mockDb->expects( $this->never() )->method( $this->anything() );
1819 $mockCache = $this->getMockCache();
1820 $mockCache->expects( $this->never() )->method( $this->anything() );
1822 $store = $this->newWatchedItemStore(
1823 $this->getMockLoadBalancer( $mockDb ),
1827 $this->assertEquals(
1829 0 => [ 'SomeDbKey' => false, ],
1830 1 => [ 'AnotherDbKey' => false, ],
1832 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1836 public function testResetNotificationTimestamp_anonymousUser() {
1837 $mockDb = $this->getMockDb();
1838 $mockDb->expects( $this->never() )
1839 ->method( 'selectRow' );
1841 $mockCache = $this->getMockCache();
1842 $mockCache->expects( $this->never() )->method( 'get' );
1843 $mockCache->expects( $this->never() )->method( 'set' );
1844 $mockCache->expects( $this->never() )->method( 'delete' );
1846 $store = $this->newWatchedItemStore(
1847 $this->getMockLoadBalancer( $mockDb ),
1852 $store->resetNotificationTimestamp(
1853 $this->getAnonUser(),
1854 Title
::newFromText( 'SomeDbKey' )
1859 public function testResetNotificationTimestamp_noItem() {
1860 $mockDb = $this->getMockDb();
1861 $mockDb->expects( $this->once() )
1862 ->method( 'selectRow' )
1865 'wl_notificationtimestamp',
1868 'wl_namespace' => 0,
1869 'wl_title' => 'SomeDbKey',
1872 ->will( $this->returnValue( [] ) );
1874 $mockCache = $this->getMockCache();
1875 $mockCache->expects( $this->never() )->method( 'get' );
1876 $mockCache->expects( $this->never() )->method( 'set' );
1877 $mockCache->expects( $this->never() )->method( 'delete' );
1879 $store = $this->newWatchedItemStore(
1880 $this->getMockLoadBalancer( $mockDb ),
1885 $store->resetNotificationTimestamp(
1886 $this->getMockNonAnonUserWithId( 1 ),
1887 Title
::newFromText( 'SomeDbKey' )
1892 public function testResetNotificationTimestamp_item() {
1893 $user = $this->getMockNonAnonUserWithId( 1 );
1894 $title = Title
::newFromText( 'SomeDbKey' );
1896 $mockDb = $this->getMockDb();
1897 $mockDb->expects( $this->once() )
1898 ->method( 'selectRow' )
1901 'wl_notificationtimestamp',
1904 'wl_namespace' => 0,
1905 'wl_title' => 'SomeDbKey',
1908 ->will( $this->returnValue(
1909 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1912 $mockCache = $this->getMockCache();
1913 $mockCache->expects( $this->never() )->method( 'get' );
1914 $mockCache->expects( $this->once() )
1918 $this->isInstanceOf( WatchedItem
::class )
1920 $mockCache->expects( $this->once() )
1921 ->method( 'delete' )
1922 ->with( '0:SomeDbKey:1' );
1924 $store = $this->newWatchedItemStore(
1925 $this->getMockLoadBalancer( $mockDb ),
1929 // Note: This does not actually assert the job is correct
1930 $callableCallCounter = 0;
1931 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1932 $callableCallCounter++
;
1933 $this->assertInternalType( 'callable', $callable );
1935 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1938 $store->resetNotificationTimestamp(
1943 $this->assertEquals( 1, $callableCallCounter );
1945 ScopedCallback
::consume( $scopedOverride );
1948 public function testResetNotificationTimestamp_noItemForced() {
1949 $user = $this->getMockNonAnonUserWithId( 1 );
1950 $title = Title
::newFromText( 'SomeDbKey' );
1952 $mockDb = $this->getMockDb();
1953 $mockDb->expects( $this->never() )
1954 ->method( 'selectRow' );
1956 $mockCache = $this->getMockCache();
1957 $mockDb->expects( $this->never() )
1959 $mockDb->expects( $this->never() )
1961 $mockDb->expects( $this->never() )
1962 ->method( 'delete' );
1964 $store = $this->newWatchedItemStore(
1965 $this->getMockLoadBalancer( $mockDb ),
1969 // Note: This does not actually assert the job is correct
1970 $callableCallCounter = 0;
1971 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1972 $callableCallCounter++
;
1973 $this->assertInternalType( 'callable', $callable );
1975 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1978 $store->resetNotificationTimestamp(
1984 $this->assertEquals( 1, $callableCallCounter );
1986 ScopedCallback
::consume( $scopedOverride );
1993 * @return PHPUnit_Framework_MockObject_MockObject|Title
1995 private function getMockTitle( $text, $ns = 0 ) {
1996 $title = $this->getMock( Title
::class );
1997 $title->expects( $this->any() )
1998 ->method( 'getText' )
1999 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2000 $title->expects( $this->any() )
2001 ->method( 'getDbKey' )
2002 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2003 $title->expects( $this->any() )
2004 ->method( 'getNamespace' )
2005 ->will( $this->returnValue( $ns ) );
2009 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2010 $user = $this->getMockNonAnonUserWithId( 1 );
2012 $title = $this->getMockTitle( 'SomeTitle' );
2013 $title->expects( $this->once() )
2014 ->method( 'getNextRevisionID' )
2016 ->will( $this->returnValue( false ) );
2018 $mockDb = $this->getMockDb();
2019 $mockDb->expects( $this->never() )
2020 ->method( 'selectRow' );
2022 $mockCache = $this->getMockCache();
2023 $mockDb->expects( $this->never() )
2025 $mockDb->expects( $this->never() )
2027 $mockDb->expects( $this->never() )
2028 ->method( 'delete' );
2030 $store = $this->newWatchedItemStore(
2031 $this->getMockLoadBalancer( $mockDb ),
2035 // Note: This does not actually assert the job is correct
2036 $callableCallCounter = 0;
2037 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2038 function( $callable ) use ( &$callableCallCounter ) {
2039 $callableCallCounter++
;
2040 $this->assertInternalType( 'callable', $callable );
2045 $store->resetNotificationTimestamp(
2052 $this->assertEquals( 1, $callableCallCounter );
2054 ScopedCallback
::consume( $scopedOverride );
2057 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2058 $user = $this->getMockNonAnonUserWithId( 1 );
2060 $title = $this->getMockTitle( 'SomeDbKey' );
2061 $title->expects( $this->once() )
2062 ->method( 'getNextRevisionID' )
2064 ->will( $this->returnValue( 33 ) );
2066 $mockDb = $this->getMockDb();
2067 $mockDb->expects( $this->once() )
2068 ->method( 'selectRow' )
2071 'wl_notificationtimestamp',
2074 'wl_namespace' => 0,
2075 'wl_title' => 'SomeDbKey',
2078 ->will( $this->returnValue(
2079 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2082 $mockCache = $this->getMockCache();
2083 $mockDb->expects( $this->never() )
2085 $mockDb->expects( $this->never() )
2087 $mockDb->expects( $this->never() )
2088 ->method( 'delete' );
2090 $store = $this->newWatchedItemStore(
2091 $this->getMockLoadBalancer( $mockDb ),
2095 // Note: This does not actually assert the job is correct
2096 $addUpdateCallCounter = 0;
2097 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2098 function( $callable ) use ( &$addUpdateCallCounter ) {
2099 $addUpdateCallCounter++
;
2100 $this->assertInternalType( 'callable', $callable );
2104 $getTimestampCallCounter = 0;
2105 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2106 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2107 $getTimestampCallCounter++
;
2108 $this->assertEquals( $title, $titleParam );
2109 $this->assertEquals( $oldid, $oldidParam );
2114 $store->resetNotificationTimestamp(
2121 $this->assertEquals( 1, $addUpdateCallCounter );
2122 $this->assertEquals( 1, $getTimestampCallCounter );
2124 ScopedCallback
::consume( $scopedOverrideDeferred );
2125 ScopedCallback
::consume( $scopedOverrideRevision );
2128 public function testUpdateNotificationTimestamp_watchersExist() {
2129 $mockDb = $this->getMockDb();
2130 $mockDb->expects( $this->once() )
2131 ->method( 'select' )
2137 'wl_namespace' => 0,
2138 'wl_title' => 'SomeDbKey',
2139 'wl_notificationtimestamp IS NULL'
2143 $this->returnValue( [
2144 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2145 $this->getFakeRow( [ 'wl_user' => '3' ] )
2148 $mockDb->expects( $this->once() )
2149 ->method( 'onTransactionIdle' )
2150 ->with( $this->isType( 'callable' ) )
2151 ->will( $this->returnCallback( function( $callable ) {
2154 $mockDb->expects( $this->once() )
2155 ->method( 'update' )
2158 [ 'wl_notificationtimestamp' => null ],
2160 'wl_user' => [ 2, 3 ],
2161 'wl_namespace' => 0,
2162 'wl_title' => 'SomeDbKey',
2166 $mockCache = $this->getMockCache();
2167 $mockCache->expects( $this->never() )->method( 'set' );
2168 $mockCache->expects( $this->never() )->method( 'get' );
2169 $mockCache->expects( $this->never() )->method( 'delete' );
2171 $store = $this->newWatchedItemStore(
2172 $this->getMockLoadBalancer( $mockDb ),
2176 $this->assertEquals(
2178 $store->updateNotificationTimestamp(
2179 $this->getMockNonAnonUserWithId( 1 ),
2180 new TitleValue( 0, 'SomeDbKey' ),
2186 public function testUpdateNotificationTimestamp_noWatchers() {
2187 $mockDb = $this->getMockDb();
2188 $mockDb->expects( $this->once() )
2189 ->method( 'select' )
2195 'wl_namespace' => 0,
2196 'wl_title' => 'SomeDbKey',
2197 'wl_notificationtimestamp IS NULL'
2201 $this->returnValue( [] )
2203 $mockDb->expects( $this->never() )
2204 ->method( 'onTransactionIdle' );
2205 $mockDb->expects( $this->never() )
2206 ->method( 'update' );
2208 $mockCache = $this->getMockCache();
2209 $mockCache->expects( $this->never() )->method( 'set' );
2210 $mockCache->expects( $this->never() )->method( 'get' );
2211 $mockCache->expects( $this->never() )->method( 'delete' );
2213 $store = $this->newWatchedItemStore(
2214 $this->getMockLoadBalancer( $mockDb ),
2218 $watchers = $store->updateNotificationTimestamp(
2219 $this->getMockNonAnonUserWithId( 1 ),
2220 new TitleValue( 0, 'SomeDbKey' ),
2223 $this->assertInternalType( 'array', $watchers );
2224 $this->assertEmpty( $watchers );
2227 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2228 $user = $this->getMockNonAnonUserWithId( 1 );
2229 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2231 $mockDb = $this->getMockDb();
2232 $mockDb->expects( $this->once() )
2233 ->method( 'selectRow' )
2234 ->will( $this->returnValue(
2235 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2237 $mockDb->expects( $this->once() )
2238 ->method( 'select' )
2240 $this->returnValue( [
2241 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2242 $this->getFakeRow( [ 'wl_user' => '3' ] )
2245 $mockDb->expects( $this->once() )
2246 ->method( 'onTransactionIdle' )
2247 ->with( $this->isType( 'callable' ) )
2248 ->will( $this->returnCallback( function( $callable ) {
2251 $mockDb->expects( $this->once() )
2252 ->method( 'update' );
2254 $mockCache = $this->getMockCache();
2255 $mockCache->expects( $this->once() )
2257 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2258 $mockCache->expects( $this->once() )
2260 ->with( '0:SomeDbKey:1' );
2261 $mockCache->expects( $this->once() )
2262 ->method( 'delete' )
2263 ->with( '0:SomeDbKey:1' );
2265 $store = $this->newWatchedItemStore(
2266 $this->getMockLoadBalancer( $mockDb ),
2270 // This will add the item to the cache
2271 $store->getWatchedItem( $user, $titleValue );
2273 $store->updateNotificationTimestamp(
2274 $this->getMockNonAnonUserWithId( 1 ),