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( $mockDb, $expectedConnectionType = null ) {
21 $mock = $this->getMockBuilder( LoadBalancer
::class )
22 ->disableOriginalConstructor()
24 if ( $expectedConnectionType !== null ) {
25 $mock->expects( $this->any() )
26 ->method( 'getConnection' )
27 ->with( $expectedConnectionType )
28 ->will( $this->returnValue( $mockDb ) );
30 $mock->expects( $this->any() )
31 ->method( 'getConnection' )
32 ->will( $this->returnValue( $mockDb ) );
34 $mock->expects( $this->any() )
35 ->method( 'getReadOnlyReason' )
36 ->will( $this->returnValue( false ) );
41 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
43 private function getMockCache() {
44 $mock = $this->getMockBuilder( HashBagOStuff
::class )
45 ->disableOriginalConstructor()
47 $mock->expects( $this->any() )
49 ->will( $this->returnCallback( function() {
50 return implode( ':', func_get_args() );
57 * @return PHPUnit_Framework_MockObject_MockObject|User
59 private function getMockNonAnonUserWithId( $id ) {
60 $mock = $this->getMock( User
::class );
61 $mock->expects( $this->any() )
63 ->will( $this->returnValue( false ) );
64 $mock->expects( $this->any() )
66 ->will( $this->returnValue( $id ) );
73 private function getAnonUser() {
74 return User
::newFromName( 'Anon_User' );
77 private function getFakeRow( array $rowValues ) {
78 $fakeRow = new stdClass();
79 foreach ( $rowValues as $valueName => $value ) {
80 $fakeRow->$valueName = $value;
85 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache ) {
86 return new WatchedItemStore(
92 public function testGetDefaultInstance() {
93 $instanceOne = WatchedItemStore
::getDefaultInstance();
94 $instanceTwo = WatchedItemStore
::getDefaultInstance();
95 $this->assertSame( $instanceOne, $instanceTwo );
98 public function testOverrideDefaultInstance() {
99 $instance = WatchedItemStore
::getDefaultInstance();
100 $scopedOverride = $instance->overrideDefaultInstance( null );
102 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
104 unset( $scopedOverride );
106 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
109 public function testCountWatchedItems() {
110 $user = $this->getMockNonAnonUserWithId( 1 );
112 $mockDb = $this->getMockDb();
113 $mockDb->expects( $this->exactly( 1 ) )
114 ->method( 'selectField' )
119 'wl_user' => $user->getId(),
121 $this->isType( 'string' )
123 ->will( $this->returnValue( 12 ) );
125 $mockCache = $this->getMockCache();
126 $mockCache->expects( $this->never() )->method( 'get' );
127 $mockCache->expects( $this->never() )->method( 'set' );
128 $mockCache->expects( $this->never() )->method( 'delete' );
130 $store = $this->newWatchedItemStore(
131 $this->getMockLoadBalancer( $mockDb ),
135 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
138 public function testCountWatchers() {
139 $titleValue = new TitleValue( 0, 'SomeDbKey' );
141 $mockDb = $this->getMockDb();
142 $mockDb->expects( $this->exactly( 1 ) )
143 ->method( 'selectField' )
148 'wl_namespace' => $titleValue->getNamespace(),
149 'wl_title' => $titleValue->getDBkey(),
151 $this->isType( 'string' )
153 ->will( $this->returnValue( 7 ) );
155 $mockCache = $this->getMockCache();
156 $mockCache->expects( $this->never() )->method( 'get' );
157 $mockCache->expects( $this->never() )->method( 'set' );
158 $mockCache->expects( $this->never() )->method( 'delete' );
160 $store = $this->newWatchedItemStore(
161 $this->getMockLoadBalancer( $mockDb ),
165 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
168 public function testCountWatchersMultiple() {
170 new TitleValue( 0, 'SomeDbKey' ),
171 new TitleValue( 0, 'OtherDbKey' ),
172 new TitleValue( 1, 'AnotherDbKey' ),
175 $mockDb = $this->getMockDb();
178 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
179 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
180 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
183 $mockDb->expects( $this->once() )
184 ->method( 'makeWhereFrom2d' )
186 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
187 $this->isType( 'string' ),
188 $this->isType( 'string' )
190 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
191 $mockDb->expects( $this->once() )
195 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
196 [ 'makeWhereFrom2d return value' ],
197 $this->isType( 'string' ),
199 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
203 $this->returnValue( $dbResult )
206 $mockCache = $this->getMockCache();
207 $mockCache->expects( $this->never() )->method( 'get' );
208 $mockCache->expects( $this->never() )->method( 'set' );
209 $mockCache->expects( $this->never() )->method( 'delete' );
211 $store = $this->newWatchedItemStore(
212 $this->getMockLoadBalancer( $mockDb ),
217 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
218 1 => [ 'AnotherDbKey' => 500 ],
220 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
223 public function provideIntWithDbUnsafeVersion() {
226 [ "50; DROP TABLE watchlist;\n--" ],
231 * @dataProvider provideIntWithDbUnsafeVersion
233 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
235 new TitleValue( 0, 'SomeDbKey' ),
236 new TitleValue( 0, 'OtherDbKey' ),
237 new TitleValue( 1, 'AnotherDbKey' ),
240 $mockDb = $this->getMockDb();
243 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
244 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
245 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
248 $mockDb->expects( $this->once() )
249 ->method( 'makeWhereFrom2d' )
251 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
252 $this->isType( 'string' ),
253 $this->isType( 'string' )
255 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
256 $mockDb->expects( $this->once() )
260 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
261 [ 'makeWhereFrom2d return value' ],
262 $this->isType( 'string' ),
264 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
265 'HAVING' => 'COUNT(*) >= 50',
269 $this->returnValue( $dbResult )
272 $mockCache = $this->getMockCache();
273 $mockCache->expects( $this->never() )->method( 'get' );
274 $mockCache->expects( $this->never() )->method( 'set' );
275 $mockCache->expects( $this->never() )->method( 'delete' );
277 $store = $this->newWatchedItemStore(
278 $this->getMockLoadBalancer( $mockDb ),
283 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
284 1 => [ 'AnotherDbKey' => 500 ],
288 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
292 public function testCountVisitingWatchers() {
293 $titleValue = new TitleValue( 0, 'SomeDbKey' );
295 $mockDb = $this->getMockDb();
296 $mockDb->expects( $this->exactly( 1 ) )
297 ->method( 'selectField' )
302 'wl_namespace' => $titleValue->getNamespace(),
303 'wl_title' => $titleValue->getDBkey(),
304 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
306 $this->isType( 'string' )
308 ->will( $this->returnValue( 7 ) );
309 $mockDb->expects( $this->exactly( 1 ) )
310 ->method( 'addQuotes' )
311 ->will( $this->returnCallback( function( $value ) {
314 $mockDb->expects( $this->exactly( 1 ) )
315 ->method( 'timestamp' )
316 ->will( $this->returnCallback( function( $value ) {
317 return 'TS' . $value . 'TS';
320 $mockCache = $this->getMockCache();
321 $mockCache->expects( $this->never() )->method( 'set' );
322 $mockCache->expects( $this->never() )->method( 'get' );
323 $mockCache->expects( $this->never() )->method( 'delete' );
325 $store = $this->newWatchedItemStore(
326 $this->getMockLoadBalancer( $mockDb ),
330 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
333 public function testCountVisitingWatchersMultiple() {
334 $titleValuesWithThresholds = [
335 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
336 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
337 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
341 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
342 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
343 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
345 $mockDb = $this->getMockDb();
346 $mockDb->expects( $this->exactly( 2 * 3 ) )
347 ->method( 'addQuotes' )
348 ->will( $this->returnCallback( function( $value ) {
351 $mockDb->expects( $this->exactly( 3 ) )
352 ->method( 'timestamp' )
353 ->will( $this->returnCallback( function( $value ) {
354 return 'TS' . $value . 'TS';
356 $mockDb->expects( $this->any() )
357 ->method( 'makeList' )
359 $this->isType( 'array' ),
360 $this->isType( 'int' )
362 ->will( $this->returnCallback( function( $a, $conj ) {
363 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
364 return join( $sqlConj, array_map( function( $s ) {
365 return '(' . $s . ')';
369 $mockDb->expects( $this->never() )
370 ->method( 'makeWhereFrom2d' );
373 '((wl_namespace = 0) AND (' .
374 "(((wl_title = 'SomeDbKey') AND (" .
375 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
377 "(wl_title = 'OtherDbKey') AND (" .
378 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
380 ') OR ((wl_namespace = 1) AND (' .
381 "(((wl_title = 'AnotherDbKey') AND (".
382 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
384 $mockDb->expects( $this->once() )
388 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
390 $this->isType( 'string' ),
392 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
396 $this->returnValue( $dbResult )
399 $mockCache = $this->getMockCache();
400 $mockCache->expects( $this->never() )->method( 'get' );
401 $mockCache->expects( $this->never() )->method( 'set' );
402 $mockCache->expects( $this->never() )->method( 'delete' );
404 $store = $this->newWatchedItemStore(
405 $this->getMockLoadBalancer( $mockDb ),
410 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
411 1 => [ 'AnotherDbKey' => 500 ],
415 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
419 public function testCountVisitingWatchersMultiple_withMissingTargets() {
420 $titleValuesWithThresholds = [
421 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
422 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
423 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
424 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
425 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
429 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
430 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
431 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
433 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
436 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
439 $mockDb = $this->getMockDb();
440 $mockDb->expects( $this->exactly( 2 * 3 ) )
441 ->method( 'addQuotes' )
442 ->will( $this->returnCallback( function( $value ) {
445 $mockDb->expects( $this->exactly( 3 ) )
446 ->method( 'timestamp' )
447 ->will( $this->returnCallback( function( $value ) {
448 return 'TS' . $value . 'TS';
450 $mockDb->expects( $this->any() )
451 ->method( 'makeList' )
453 $this->isType( 'array' ),
454 $this->isType( 'int' )
456 ->will( $this->returnCallback( function( $a, $conj ) {
457 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
458 return join( $sqlConj, array_map( function( $s ) {
459 return '(' . $s . ')';
463 $mockDb->expects( $this->once() )
464 ->method( 'makeWhereFrom2d' )
466 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
467 $this->isType( 'string' ),
468 $this->isType( 'string' )
470 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
473 '((wl_namespace = 0) AND (' .
474 "(((wl_title = 'SomeDbKey') AND (" .
475 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
477 "(wl_title = 'OtherDbKey') AND (" .
478 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
480 ') OR ((wl_namespace = 1) AND (' .
481 "(((wl_title = 'AnotherDbKey') AND (".
482 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
485 '(makeWhereFrom2d return value)';
486 $mockDb->expects( $this->once() )
490 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
492 $this->isType( 'string' ),
494 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
498 $this->returnValue( $dbResult )
501 $mockCache = $this->getMockCache();
502 $mockCache->expects( $this->never() )->method( 'get' );
503 $mockCache->expects( $this->never() )->method( 'set' );
504 $mockCache->expects( $this->never() )->method( 'delete' );
506 $store = $this->newWatchedItemStore(
507 $this->getMockLoadBalancer( $mockDb ),
513 'SomeDbKey' => 100, 'OtherDbKey' => 300,
514 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
516 1 => [ 'AnotherDbKey' => 500 ],
520 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
525 * @dataProvider provideIntWithDbUnsafeVersion
527 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
528 $titleValuesWithThresholds = [
529 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
530 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
531 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
534 $mockDb = $this->getMockDb();
535 $mockDb->expects( $this->any() )
536 ->method( 'makeList' )
537 ->will( $this->returnValue( 'makeList return value' ) );
538 $mockDb->expects( $this->once() )
542 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
543 'makeList return value',
544 $this->isType( 'string' ),
546 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
547 'HAVING' => 'COUNT(*) >= 50',
551 $this->returnValue( [] )
554 $mockCache = $this->getMockCache();
555 $mockCache->expects( $this->never() )->method( 'get' );
556 $mockCache->expects( $this->never() )->method( 'set' );
557 $mockCache->expects( $this->never() )->method( 'delete' );
559 $store = $this->newWatchedItemStore(
560 $this->getMockLoadBalancer( $mockDb ),
565 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
566 1 => [ 'AnotherDbKey' => 0 ],
570 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
574 public function testCountUnreadNotifications() {
575 $user = $this->getMockNonAnonUserWithId( 1 );
577 $mockDb = $this->getMockDb();
578 $mockDb->expects( $this->exactly( 1 ) )
579 ->method( 'selectRowCount' )
584 "wl_notificationtimestamp IS NOT NULL",
587 $this->isType( 'string' )
589 ->will( $this->returnValue( 9 ) );
591 $mockCache = $this->getMockCache();
592 $mockCache->expects( $this->never() )->method( 'set' );
593 $mockCache->expects( $this->never() )->method( 'get' );
594 $mockCache->expects( $this->never() )->method( 'delete' );
596 $store = $this->newWatchedItemStore(
597 $this->getMockLoadBalancer( $mockDb ),
601 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
605 * @dataProvider provideIntWithDbUnsafeVersion
607 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
608 $user = $this->getMockNonAnonUserWithId( 1 );
610 $mockDb = $this->getMockDb();
611 $mockDb->expects( $this->exactly( 1 ) )
612 ->method( 'selectRowCount' )
617 "wl_notificationtimestamp IS NOT NULL",
620 $this->isType( 'string' ),
623 ->will( $this->returnValue( 50 ) );
625 $mockCache = $this->getMockCache();
626 $mockCache->expects( $this->never() )->method( 'set' );
627 $mockCache->expects( $this->never() )->method( 'get' );
628 $mockCache->expects( $this->never() )->method( 'delete' );
630 $store = $this->newWatchedItemStore(
631 $this->getMockLoadBalancer( $mockDb ),
637 $store->countUnreadNotifications( $user, $limit )
642 * @dataProvider provideIntWithDbUnsafeVersion
644 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
645 $user = $this->getMockNonAnonUserWithId( 1 );
647 $mockDb = $this->getMockDb();
648 $mockDb->expects( $this->exactly( 1 ) )
649 ->method( 'selectRowCount' )
654 "wl_notificationtimestamp IS NOT NULL",
657 $this->isType( 'string' ),
660 ->will( $this->returnValue( 9 ) );
662 $mockCache = $this->getMockCache();
663 $mockCache->expects( $this->never() )->method( 'set' );
664 $mockCache->expects( $this->never() )->method( 'get' );
665 $mockCache->expects( $this->never() )->method( 'delete' );
667 $store = $this->newWatchedItemStore(
668 $this->getMockLoadBalancer( $mockDb ),
674 $store->countUnreadNotifications( $user, $limit )
678 public function testDuplicateEntry_nothingToDuplicate() {
679 $mockDb = $this->getMockDb();
680 $mockDb->expects( $this->once() )
686 'wl_notificationtimestamp',
690 'wl_title' => 'Old_Title',
692 'WatchedItemStore::duplicateEntry',
695 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
697 $store = $this->newWatchedItemStore(
698 $this->getMockLoadBalancer( $mockDb ),
699 $this->getMockCache()
702 $store->duplicateEntry(
703 Title
::newFromText( 'Old_Title' ),
704 Title
::newFromText( 'New_Title' )
708 public function testDuplicateEntry_somethingToDuplicate() {
710 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
711 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
714 $mockDb = $this->getMockDb();
715 $mockDb->expects( $this->at( 0 ) )
721 'wl_notificationtimestamp',
725 'wl_title' => 'Old_Title',
728 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
729 $mockDb->expects( $this->at( 1 ) )
730 ->method( 'replace' )
733 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
738 'wl_title' => 'New_Title',
739 'wl_notificationtimestamp' => '20151212010101',
744 'wl_title' => 'New_Title',
745 'wl_notificationtimestamp' => null,
748 $this->isType( 'string' )
751 $mockCache = $this->getMockCache();
752 $mockCache->expects( $this->never() )->method( 'get' );
753 $mockCache->expects( $this->never() )->method( 'delete' );
755 $store = $this->newWatchedItemStore(
756 $this->getMockLoadBalancer( $mockDb ),
760 $store->duplicateEntry(
761 Title
::newFromText( 'Old_Title' ),
762 Title
::newFromText( 'New_Title' )
766 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
767 $mockDb = $this->getMockDb();
768 $mockDb->expects( $this->at( 0 ) )
774 'wl_notificationtimestamp',
778 'wl_title' => 'Old_Title',
781 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
782 $mockDb->expects( $this->at( 1 ) )
788 'wl_notificationtimestamp',
792 'wl_title' => 'Old_Title',
795 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
797 $mockCache = $this->getMockCache();
798 $mockCache->expects( $this->never() )->method( 'get' );
799 $mockCache->expects( $this->never() )->method( 'delete' );
801 $store = $this->newWatchedItemStore(
802 $this->getMockLoadBalancer( $mockDb ),
806 $store->duplicateAllAssociatedEntries(
807 Title
::newFromText( 'Old_Title' ),
808 Title
::newFromText( 'New_Title' )
812 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
814 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
817 $mockDb = $this->getMockDb();
818 $mockDb->expects( $this->at( 0 ) )
824 'wl_notificationtimestamp',
828 'wl_title' => 'Old_Title',
831 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
832 $mockDb->expects( $this->at( 1 ) )
833 ->method( 'replace' )
836 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
841 'wl_title' => 'New_Title',
842 'wl_notificationtimestamp' => '20151212010101',
845 $this->isType( 'string' )
847 $mockDb->expects( $this->at( 2 ) )
853 'wl_notificationtimestamp',
857 'wl_title' => 'Old_Title',
860 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
861 $mockDb->expects( $this->at( 3 ) )
862 ->method( 'replace' )
865 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
870 'wl_title' => 'New_Title',
871 'wl_notificationtimestamp' => '20151212010101',
874 $this->isType( 'string' )
877 $mockCache = $this->getMockCache();
878 $mockCache->expects( $this->never() )->method( 'get' );
879 $mockCache->expects( $this->never() )->method( 'delete' );
881 $store = $this->newWatchedItemStore(
882 $this->getMockLoadBalancer( $mockDb ),
886 $store->duplicateAllAssociatedEntries(
887 Title
::newFromText( 'Old_Title' ),
888 Title
::newFromText( 'New_Title' )
892 public function testAddWatch_nonAnonymousUser() {
893 $mockDb = $this->getMockDb();
894 $mockDb->expects( $this->once() )
902 'wl_title' => 'Some_Page',
903 'wl_notificationtimestamp' => null,
908 $mockCache = $this->getMockCache();
909 $mockCache->expects( $this->once() )
911 ->with( '0:Some_Page:1' );
913 $store = $this->newWatchedItemStore(
914 $this->getMockLoadBalancer( $mockDb ),
919 $this->getMockNonAnonUserWithId( 1 ),
920 Title
::newFromText( 'Some_Page' )
924 public function testAddWatch_anonymousUser() {
925 $mockDb = $this->getMockDb();
926 $mockDb->expects( $this->never() )
927 ->method( 'insert' );
929 $mockCache = $this->getMockCache();
930 $mockCache->expects( $this->never() )
931 ->method( 'delete' );
933 $store = $this->newWatchedItemStore(
934 $this->getMockLoadBalancer( $mockDb ),
939 $this->getAnonUser(),
940 Title
::newFromText( 'Some_Page' )
944 public function testAddWatchBatchForUser_nonAnonymousUser() {
945 $mockDb = $this->getMockDb();
946 $mockDb->expects( $this->once() )
954 'wl_title' => 'Some_Page',
955 'wl_notificationtimestamp' => null,
960 'wl_title' => 'Some_Page',
961 'wl_notificationtimestamp' => null,
966 $mockCache = $this->getMockCache();
967 $mockCache->expects( $this->exactly( 2 ) )
968 ->method( 'delete' );
969 $mockCache->expects( $this->at( 1 ) )
971 ->with( '0:Some_Page:1' );
972 $mockCache->expects( $this->at( 3 ) )
974 ->with( '1:Some_Page:1' );
976 $store = $this->newWatchedItemStore(
977 $this->getMockLoadBalancer( $mockDb ),
981 $mockUser = $this->getMockNonAnonUserWithId( 1 );
984 $store->addWatchBatchForUser(
986 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
991 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
992 $mockDb = $this->getMockDb();
993 $mockDb->expects( $this->never() )
994 ->method( 'insert' );
996 $mockCache = $this->getMockCache();
997 $mockCache->expects( $this->never() )
998 ->method( 'delete' );
1000 $store = $this->newWatchedItemStore(
1001 $this->getMockLoadBalancer( $mockDb ),
1006 $store->addWatchBatchForUser(
1007 $this->getAnonUser(),
1008 [ new TitleValue( 0, 'Other_Page' ) ]
1013 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1014 $user = $this->getMockNonAnonUserWithId( 1 );
1015 $mockDb = $this->getMockDb();
1016 $mockDb->expects( $this->never() )
1017 ->method( 'insert' );
1019 $mockCache = $this->getMockCache();
1020 $mockCache->expects( $this->never() )
1021 ->method( 'delete' );
1023 $store = $this->newWatchedItemStore(
1024 $this->getMockLoadBalancer( $mockDb ),
1029 $store->addWatchBatchForUser( $user, [] )
1033 public function testLoadWatchedItem_existingItem() {
1034 $mockDb = $this->getMockDb();
1035 $mockDb->expects( $this->once() )
1036 ->method( 'selectRow' )
1039 'wl_notificationtimestamp',
1042 'wl_namespace' => 0,
1043 'wl_title' => 'SomeDbKey',
1046 ->will( $this->returnValue(
1047 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1050 $mockCache = $this->getMockCache();
1051 $mockCache->expects( $this->once() )
1057 $store = $this->newWatchedItemStore(
1058 $this->getMockLoadBalancer( $mockDb ),
1062 $watchedItem = $store->loadWatchedItem(
1063 $this->getMockNonAnonUserWithId( 1 ),
1064 new TitleValue( 0, 'SomeDbKey' )
1066 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1067 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1068 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1069 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1072 public function testLoadWatchedItem_noItem() {
1073 $mockDb = $this->getMockDb();
1074 $mockDb->expects( $this->once() )
1075 ->method( 'selectRow' )
1078 'wl_notificationtimestamp',
1081 'wl_namespace' => 0,
1082 'wl_title' => 'SomeDbKey',
1085 ->will( $this->returnValue( [] ) );
1087 $mockCache = $this->getMockCache();
1088 $mockCache->expects( $this->never() )->method( 'get' );
1089 $mockCache->expects( $this->never() )->method( 'delete' );
1091 $store = $this->newWatchedItemStore(
1092 $this->getMockLoadBalancer( $mockDb ),
1097 $store->loadWatchedItem(
1098 $this->getMockNonAnonUserWithId( 1 ),
1099 new TitleValue( 0, 'SomeDbKey' )
1104 public function testLoadWatchedItem_anonymousUser() {
1105 $mockDb = $this->getMockDb();
1106 $mockDb->expects( $this->never() )
1107 ->method( 'selectRow' );
1109 $mockCache = $this->getMockCache();
1110 $mockCache->expects( $this->never() )->method( 'get' );
1111 $mockCache->expects( $this->never() )->method( 'delete' );
1113 $store = $this->newWatchedItemStore(
1114 $this->getMockLoadBalancer( $mockDb ),
1119 $store->loadWatchedItem(
1120 $this->getAnonUser(),
1121 new TitleValue( 0, 'SomeDbKey' )
1126 public function testRemoveWatch_existingItem() {
1127 $mockDb = $this->getMockDb();
1128 $mockDb->expects( $this->once() )
1129 ->method( 'delete' )
1134 'wl_namespace' => 0,
1135 'wl_title' => 'SomeDbKey',
1138 $mockDb->expects( $this->once() )
1139 ->method( 'affectedRows' )
1140 ->will( $this->returnValue( 1 ) );
1142 $mockCache = $this->getMockCache();
1143 $mockCache->expects( $this->never() )->method( 'get' );
1144 $mockCache->expects( $this->once() )
1145 ->method( 'delete' )
1146 ->with( '0:SomeDbKey:1' );
1148 $store = $this->newWatchedItemStore(
1149 $this->getMockLoadBalancer( $mockDb ),
1154 $store->removeWatch(
1155 $this->getMockNonAnonUserWithId( 1 ),
1156 new TitleValue( 0, 'SomeDbKey' )
1161 public function testRemoveWatch_noItem() {
1162 $mockDb = $this->getMockDb();
1163 $mockDb->expects( $this->once() )
1164 ->method( 'delete' )
1169 'wl_namespace' => 0,
1170 'wl_title' => 'SomeDbKey',
1173 $mockDb->expects( $this->once() )
1174 ->method( 'affectedRows' )
1175 ->will( $this->returnValue( 0 ) );
1177 $mockCache = $this->getMockCache();
1178 $mockCache->expects( $this->never() )->method( 'get' );
1179 $mockCache->expects( $this->once() )
1180 ->method( 'delete' )
1181 ->with( '0:SomeDbKey:1' );
1183 $store = $this->newWatchedItemStore(
1184 $this->getMockLoadBalancer( $mockDb ),
1189 $store->removeWatch(
1190 $this->getMockNonAnonUserWithId( 1 ),
1191 new TitleValue( 0, 'SomeDbKey' )
1196 public function testRemoveWatch_anonymousUser() {
1197 $mockDb = $this->getMockDb();
1198 $mockDb->expects( $this->never() )
1199 ->method( 'delete' );
1201 $mockCache = $this->getMockCache();
1202 $mockCache->expects( $this->never() )->method( 'get' );
1203 $mockCache->expects( $this->never() )
1204 ->method( 'delete' );
1206 $store = $this->newWatchedItemStore(
1207 $this->getMockLoadBalancer( $mockDb ),
1212 $store->removeWatch(
1213 $this->getAnonUser(),
1214 new TitleValue( 0, 'SomeDbKey' )
1219 public function testGetWatchedItem_existingItem() {
1220 $mockDb = $this->getMockDb();
1221 $mockDb->expects( $this->once() )
1222 ->method( 'selectRow' )
1225 'wl_notificationtimestamp',
1228 'wl_namespace' => 0,
1229 'wl_title' => 'SomeDbKey',
1232 ->will( $this->returnValue(
1233 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1236 $mockCache = $this->getMockCache();
1237 $mockCache->expects( $this->never() )->method( 'delete' );
1238 $mockCache->expects( $this->once() )
1243 ->will( $this->returnValue( null ) );
1244 $mockCache->expects( $this->once() )
1250 $store = $this->newWatchedItemStore(
1251 $this->getMockLoadBalancer( $mockDb ),
1255 $watchedItem = $store->getWatchedItem(
1256 $this->getMockNonAnonUserWithId( 1 ),
1257 new TitleValue( 0, 'SomeDbKey' )
1259 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1260 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1261 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1262 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1265 public function testGetWatchedItem_cachedItem() {
1266 $mockDb = $this->getMockDb();
1267 $mockDb->expects( $this->never() )
1268 ->method( 'selectRow' );
1270 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1271 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1272 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1274 $mockCache = $this->getMockCache();
1275 $mockCache->expects( $this->never() )->method( 'delete' );
1276 $mockCache->expects( $this->never() )->method( 'set' );
1277 $mockCache->expects( $this->once() )
1282 ->will( $this->returnValue( $cachedItem ) );
1284 $store = $this->newWatchedItemStore(
1285 $this->getMockLoadBalancer( $mockDb ),
1289 $this->assertEquals(
1291 $store->getWatchedItem(
1298 public function testGetWatchedItem_noItem() {
1299 $mockDb = $this->getMockDb();
1300 $mockDb->expects( $this->once() )
1301 ->method( 'selectRow' )
1304 'wl_notificationtimestamp',
1307 'wl_namespace' => 0,
1308 'wl_title' => 'SomeDbKey',
1311 ->will( $this->returnValue( [] ) );
1313 $mockCache = $this->getMockCache();
1314 $mockCache->expects( $this->never() )->method( 'set' );
1315 $mockCache->expects( $this->never() )->method( 'delete' );
1316 $mockCache->expects( $this->once() )
1318 ->with( '0:SomeDbKey:1' )
1319 ->will( $this->returnValue( false ) );
1321 $store = $this->newWatchedItemStore(
1322 $this->getMockLoadBalancer( $mockDb ),
1327 $store->getWatchedItem(
1328 $this->getMockNonAnonUserWithId( 1 ),
1329 new TitleValue( 0, 'SomeDbKey' )
1334 public function testGetWatchedItem_anonymousUser() {
1335 $mockDb = $this->getMockDb();
1336 $mockDb->expects( $this->never() )
1337 ->method( 'selectRow' );
1339 $mockCache = $this->getMockCache();
1340 $mockCache->expects( $this->never() )->method( 'set' );
1341 $mockCache->expects( $this->never() )->method( 'get' );
1342 $mockCache->expects( $this->never() )->method( 'delete' );
1344 $store = $this->newWatchedItemStore(
1345 $this->getMockLoadBalancer( $mockDb ),
1350 $store->getWatchedItem(
1351 $this->getAnonUser(),
1352 new TitleValue( 0, 'SomeDbKey' )
1357 public function testGetWatchedItemsForUser() {
1358 $mockDb = $this->getMockDb();
1359 $mockDb->expects( $this->once() )
1360 ->method( 'select' )
1363 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1366 ->will( $this->returnValue( [
1367 $this->getFakeRow( [
1368 'wl_namespace' => 0,
1369 'wl_title' => 'Foo1',
1370 'wl_notificationtimestamp' => '20151212010101',
1372 $this->getFakeRow( [
1373 'wl_namespace' => 1,
1374 'wl_title' => 'Foo2',
1375 'wl_notificationtimestamp' => null,
1379 $mockCache = $this->getMockCache();
1380 $mockCache->expects( $this->never() )->method( 'delete' );
1381 $mockCache->expects( $this->never() )->method( 'get' );
1382 $mockCache->expects( $this->never() )->method( 'set' );
1384 $store = $this->newWatchedItemStore(
1385 $this->getMockLoadBalancer( $mockDb ),
1388 $user = $this->getMockNonAnonUserWithId( 1 );
1390 $watchedItems = $store->getWatchedItemsForUser( $user );
1392 $this->assertInternalType( 'array', $watchedItems );
1393 $this->assertCount( 2, $watchedItems );
1394 foreach ( $watchedItems as $watchedItem ) {
1395 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1397 $this->assertEquals(
1398 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1401 $this->assertEquals(
1402 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1407 public function provideDbTypes() {
1409 [ false, DB_SLAVE
],
1410 [ true, DB_MASTER
],
1415 * @dataProvider provideDbTypes
1417 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1418 $mockDb = $this->getMockDb();
1419 $mockCache = $this->getMockCache();
1420 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1421 $user = $this->getMockNonAnonUserWithId( 1 );
1423 $mockDb->expects( $this->once() )
1424 ->method( 'select' )
1427 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1429 $this->isType( 'string' ),
1430 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1432 ->will( $this->returnValue( [] ) );
1434 $store = $this->newWatchedItemStore(
1439 $watchedItems = $store->getWatchedItemsForUser(
1441 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1443 $this->assertEquals( [], $watchedItems );
1446 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1447 $store = $this->newWatchedItemStore(
1448 $this->getMockLoadBalancer( $this->getMockDb() ),
1449 $this->getMockCache()
1452 $this->setExpectedException( 'InvalidArgumentException' );
1453 $store->getWatchedItemsForUser(
1454 $this->getMockNonAnonUserWithId( 1 ),
1459 public function testIsWatchedItem_existingItem() {
1460 $mockDb = $this->getMockDb();
1461 $mockDb->expects( $this->once() )
1462 ->method( 'selectRow' )
1465 'wl_notificationtimestamp',
1468 'wl_namespace' => 0,
1469 'wl_title' => 'SomeDbKey',
1472 ->will( $this->returnValue(
1473 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1476 $mockCache = $this->getMockCache();
1477 $mockCache->expects( $this->never() )->method( 'delete' );
1478 $mockCache->expects( $this->once() )
1480 ->with( '0:SomeDbKey:1' )
1481 ->will( $this->returnValue( false ) );
1482 $mockCache->expects( $this->once() )
1488 $store = $this->newWatchedItemStore(
1489 $this->getMockLoadBalancer( $mockDb ),
1495 $this->getMockNonAnonUserWithId( 1 ),
1496 new TitleValue( 0, 'SomeDbKey' )
1501 public function testIsWatchedItem_noItem() {
1502 $mockDb = $this->getMockDb();
1503 $mockDb->expects( $this->once() )
1504 ->method( 'selectRow' )
1507 'wl_notificationtimestamp',
1510 'wl_namespace' => 0,
1511 'wl_title' => 'SomeDbKey',
1514 ->will( $this->returnValue( [] ) );
1516 $mockCache = $this->getMockCache();
1517 $mockCache->expects( $this->never() )->method( 'set' );
1518 $mockCache->expects( $this->never() )->method( 'delete' );
1519 $mockCache->expects( $this->once() )
1521 ->with( '0:SomeDbKey:1' )
1522 ->will( $this->returnValue( false ) );
1524 $store = $this->newWatchedItemStore(
1525 $this->getMockLoadBalancer( $mockDb ),
1531 $this->getMockNonAnonUserWithId( 1 ),
1532 new TitleValue( 0, 'SomeDbKey' )
1537 public function testIsWatchedItem_anonymousUser() {
1538 $mockDb = $this->getMockDb();
1539 $mockDb->expects( $this->never() )
1540 ->method( 'selectRow' );
1542 $mockCache = $this->getMockCache();
1543 $mockCache->expects( $this->never() )->method( 'set' );
1544 $mockCache->expects( $this->never() )->method( 'get' );
1545 $mockCache->expects( $this->never() )->method( 'delete' );
1547 $store = $this->newWatchedItemStore(
1548 $this->getMockLoadBalancer( $mockDb ),
1554 $this->getAnonUser(),
1555 new TitleValue( 0, 'SomeDbKey' )
1560 public function testGetNotificationTimestampsBatch() {
1562 new TitleValue( 0, 'SomeDbKey' ),
1563 new TitleValue( 1, 'AnotherDbKey' ),
1566 $mockDb = $this->getMockDb();
1568 $this->getFakeRow( [
1569 'wl_namespace' => 0,
1570 'wl_title' => 'SomeDbKey',
1571 'wl_notificationtimestamp' => '20151212010101',
1575 'wl_namespace' => 1,
1576 'wl_title' => 'AnotherDbKey',
1577 'wl_notificationtimestamp' => null,
1582 $mockDb->expects( $this->once() )
1583 ->method( 'makeWhereFrom2d' )
1585 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1586 $this->isType( 'string' ),
1587 $this->isType( 'string' )
1589 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1590 $mockDb->expects( $this->once() )
1591 ->method( 'select' )
1594 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1596 'makeWhereFrom2d return value',
1599 $this->isType( 'string' )
1601 ->will( $this->returnValue( $dbResult ) );
1603 $mockCache = $this->getMockCache();
1604 $mockCache->expects( $this->exactly( 2 ) )
1607 [ '0:SomeDbKey:1' ],
1608 [ '1:AnotherDbKey:1' ]
1610 ->will( $this->returnValue( null ) );
1611 $mockCache->expects( $this->never() )->method( 'set' );
1612 $mockCache->expects( $this->never() )->method( 'delete' );
1614 $store = $this->newWatchedItemStore(
1615 $this->getMockLoadBalancer( $mockDb ),
1619 $this->assertEquals(
1621 0 => [ 'SomeDbKey' => '20151212010101', ],
1622 1 => [ 'AnotherDbKey' => null, ],
1624 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1628 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1630 new TitleValue( 0, 'OtherDbKey' ),
1633 $mockDb = $this->getMockDb();
1635 $mockDb->expects( $this->once() )
1636 ->method( 'makeWhereFrom2d' )
1638 [ [ 'OtherDbKey' => 1 ] ],
1639 $this->isType( 'string' ),
1640 $this->isType( 'string' )
1642 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1643 $mockDb->expects( $this->once() )
1644 ->method( 'select' )
1647 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1649 'makeWhereFrom2d return value',
1652 $this->isType( 'string' )
1654 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1656 $mockCache = $this->getMockCache();
1657 $mockCache->expects( $this->once() )
1659 ->with( '0:OtherDbKey:1' )
1660 ->will( $this->returnValue( null ) );
1661 $mockCache->expects( $this->never() )->method( 'set' );
1662 $mockCache->expects( $this->never() )->method( 'delete' );
1664 $store = $this->newWatchedItemStore(
1665 $this->getMockLoadBalancer( $mockDb ),
1669 $this->assertEquals(
1671 0 => [ 'OtherDbKey' => false, ],
1673 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1677 public function testGetNotificationTimestampsBatch_cachedItem() {
1679 new TitleValue( 0, 'SomeDbKey' ),
1680 new TitleValue( 1, 'AnotherDbKey' ),
1683 $user = $this->getMockNonAnonUserWithId( 1 );
1684 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1686 $mockDb = $this->getMockDb();
1688 $mockDb->expects( $this->once() )
1689 ->method( 'makeWhereFrom2d' )
1691 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1692 $this->isType( 'string' ),
1693 $this->isType( 'string' )
1695 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1696 $mockDb->expects( $this->once() )
1697 ->method( 'select' )
1700 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1702 'makeWhereFrom2d return value',
1705 $this->isType( 'string' )
1707 ->will( $this->returnValue( [
1709 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1713 $mockCache = $this->getMockCache();
1714 $mockCache->expects( $this->at( 1 ) )
1716 ->with( '0:SomeDbKey:1' )
1717 ->will( $this->returnValue( $cachedItem ) );
1718 $mockCache->expects( $this->at( 3 ) )
1720 ->with( '1:AnotherDbKey:1' )
1721 ->will( $this->returnValue( null ) );
1722 $mockCache->expects( $this->never() )->method( 'set' );
1723 $mockCache->expects( $this->never() )->method( 'delete' );
1725 $store = $this->newWatchedItemStore(
1726 $this->getMockLoadBalancer( $mockDb ),
1730 $this->assertEquals(
1732 0 => [ 'SomeDbKey' => '20151212010101', ],
1733 1 => [ 'AnotherDbKey' => null, ],
1735 $store->getNotificationTimestampsBatch( $user, $targets )
1739 public function testGetNotificationTimestampsBatch_allItemsCached() {
1741 new TitleValue( 0, 'SomeDbKey' ),
1742 new TitleValue( 1, 'AnotherDbKey' ),
1745 $user = $this->getMockNonAnonUserWithId( 1 );
1747 new WatchedItem( $user, $targets[0], '20151212010101' ),
1748 new WatchedItem( $user, $targets[1], null ),
1750 $mockDb = $this->getMockDb();
1751 $mockDb->expects( $this->never() )->method( $this->anything() );
1753 $mockCache = $this->getMockCache();
1754 $mockCache->expects( $this->at( 1 ) )
1756 ->with( '0:SomeDbKey:1' )
1757 ->will( $this->returnValue( $cachedItems[0] ) );
1758 $mockCache->expects( $this->at( 3 ) )
1760 ->with( '1:AnotherDbKey:1' )
1761 ->will( $this->returnValue( $cachedItems[1] ) );
1762 $mockCache->expects( $this->never() )->method( 'set' );
1763 $mockCache->expects( $this->never() )->method( 'delete' );
1765 $store = $this->newWatchedItemStore(
1766 $this->getMockLoadBalancer( $mockDb ),
1770 $this->assertEquals(
1772 0 => [ 'SomeDbKey' => '20151212010101', ],
1773 1 => [ 'AnotherDbKey' => null, ],
1775 $store->getNotificationTimestampsBatch( $user, $targets )
1779 public function testGetNotificationTimestampsBatch_anonymousUser() {
1781 new TitleValue( 0, 'SomeDbKey' ),
1782 new TitleValue( 1, 'AnotherDbKey' ),
1785 $mockDb = $this->getMockDb();
1786 $mockDb->expects( $this->never() )->method( $this->anything() );
1788 $mockCache = $this->getMockCache();
1789 $mockCache->expects( $this->never() )->method( $this->anything() );
1791 $store = $this->newWatchedItemStore(
1792 $this->getMockLoadBalancer( $mockDb ),
1796 $this->assertEquals(
1798 0 => [ 'SomeDbKey' => false, ],
1799 1 => [ 'AnotherDbKey' => false, ],
1801 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1805 public function testResetNotificationTimestamp_anonymousUser() {
1806 $mockDb = $this->getMockDb();
1807 $mockDb->expects( $this->never() )
1808 ->method( 'selectRow' );
1810 $mockCache = $this->getMockCache();
1811 $mockCache->expects( $this->never() )->method( 'get' );
1812 $mockCache->expects( $this->never() )->method( 'set' );
1813 $mockCache->expects( $this->never() )->method( 'delete' );
1815 $store = $this->newWatchedItemStore(
1816 $this->getMockLoadBalancer( $mockDb ),
1821 $store->resetNotificationTimestamp(
1822 $this->getAnonUser(),
1823 Title
::newFromText( 'SomeDbKey' )
1828 public function testResetNotificationTimestamp_noItem() {
1829 $mockDb = $this->getMockDb();
1830 $mockDb->expects( $this->once() )
1831 ->method( 'selectRow' )
1834 'wl_notificationtimestamp',
1837 'wl_namespace' => 0,
1838 'wl_title' => 'SomeDbKey',
1841 ->will( $this->returnValue( [] ) );
1843 $mockCache = $this->getMockCache();
1844 $mockCache->expects( $this->never() )->method( 'get' );
1845 $mockCache->expects( $this->never() )->method( 'set' );
1846 $mockCache->expects( $this->never() )->method( 'delete' );
1848 $store = $this->newWatchedItemStore(
1849 $this->getMockLoadBalancer( $mockDb ),
1854 $store->resetNotificationTimestamp(
1855 $this->getMockNonAnonUserWithId( 1 ),
1856 Title
::newFromText( 'SomeDbKey' )
1861 public function testResetNotificationTimestamp_item() {
1862 $user = $this->getMockNonAnonUserWithId( 1 );
1863 $title = Title
::newFromText( 'SomeDbKey' );
1865 $mockDb = $this->getMockDb();
1866 $mockDb->expects( $this->once() )
1867 ->method( 'selectRow' )
1870 'wl_notificationtimestamp',
1873 'wl_namespace' => 0,
1874 'wl_title' => 'SomeDbKey',
1877 ->will( $this->returnValue(
1878 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1881 $mockCache = $this->getMockCache();
1882 $mockCache->expects( $this->never() )->method( 'get' );
1883 $mockCache->expects( $this->once() )
1887 $this->isInstanceOf( WatchedItem
::class )
1889 $mockCache->expects( $this->once() )
1890 ->method( 'delete' )
1891 ->with( '0:SomeDbKey:1' );
1893 $store = $this->newWatchedItemStore(
1894 $this->getMockLoadBalancer( $mockDb ),
1898 // Note: This does not actually assert the job is correct
1899 $callableCallCounter = 0;
1900 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1901 $callableCallCounter++
;
1902 $this->assertInternalType( 'callable', $callable );
1904 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1907 $store->resetNotificationTimestamp(
1912 $this->assertEquals( 1, $callableCallCounter );
1914 ScopedCallback
::consume( $scopedOverride );
1917 public function testResetNotificationTimestamp_noItemForced() {
1918 $user = $this->getMockNonAnonUserWithId( 1 );
1919 $title = Title
::newFromText( 'SomeDbKey' );
1921 $mockDb = $this->getMockDb();
1922 $mockDb->expects( $this->never() )
1923 ->method( 'selectRow' );
1925 $mockCache = $this->getMockCache();
1926 $mockDb->expects( $this->never() )
1928 $mockDb->expects( $this->never() )
1930 $mockDb->expects( $this->never() )
1931 ->method( 'delete' );
1933 $store = $this->newWatchedItemStore(
1934 $this->getMockLoadBalancer( $mockDb ),
1938 // Note: This does not actually assert the job is correct
1939 $callableCallCounter = 0;
1940 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1941 $callableCallCounter++
;
1942 $this->assertInternalType( 'callable', $callable );
1944 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1947 $store->resetNotificationTimestamp(
1953 $this->assertEquals( 1, $callableCallCounter );
1955 ScopedCallback
::consume( $scopedOverride );
1962 * @return PHPUnit_Framework_MockObject_MockObject|Title
1964 private function getMockTitle( $text, $ns = 0 ) {
1965 $title = $this->getMock( Title
::class );
1966 $title->expects( $this->any() )
1967 ->method( 'getText' )
1968 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1969 $title->expects( $this->any() )
1970 ->method( 'getDbKey' )
1971 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1972 $title->expects( $this->any() )
1973 ->method( 'getNamespace' )
1974 ->will( $this->returnValue( $ns ) );
1978 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1979 $user = $this->getMockNonAnonUserWithId( 1 );
1981 $title = $this->getMockTitle( 'SomeTitle' );
1982 $title->expects( $this->once() )
1983 ->method( 'getNextRevisionID' )
1985 ->will( $this->returnValue( false ) );
1987 $mockDb = $this->getMockDb();
1988 $mockDb->expects( $this->never() )
1989 ->method( 'selectRow' );
1991 $mockCache = $this->getMockCache();
1992 $mockDb->expects( $this->never() )
1994 $mockDb->expects( $this->never() )
1996 $mockDb->expects( $this->never() )
1997 ->method( 'delete' );
1999 $store = $this->newWatchedItemStore(
2000 $this->getMockLoadBalancer( $mockDb ),
2004 // Note: This does not actually assert the job is correct
2005 $callableCallCounter = 0;
2006 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2007 function( $callable ) use ( &$callableCallCounter ) {
2008 $callableCallCounter++
;
2009 $this->assertInternalType( 'callable', $callable );
2014 $store->resetNotificationTimestamp(
2021 $this->assertEquals( 1, $callableCallCounter );
2023 ScopedCallback
::consume( $scopedOverride );
2026 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2027 $user = $this->getMockNonAnonUserWithId( 1 );
2029 $title = $this->getMockTitle( 'SomeDbKey' );
2030 $title->expects( $this->once() )
2031 ->method( 'getNextRevisionID' )
2033 ->will( $this->returnValue( 33 ) );
2035 $mockDb = $this->getMockDb();
2036 $mockDb->expects( $this->once() )
2037 ->method( 'selectRow' )
2040 'wl_notificationtimestamp',
2043 'wl_namespace' => 0,
2044 'wl_title' => 'SomeDbKey',
2047 ->will( $this->returnValue(
2048 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2051 $mockCache = $this->getMockCache();
2052 $mockDb->expects( $this->never() )
2054 $mockDb->expects( $this->never() )
2056 $mockDb->expects( $this->never() )
2057 ->method( 'delete' );
2059 $store = $this->newWatchedItemStore(
2060 $this->getMockLoadBalancer( $mockDb ),
2064 // Note: This does not actually assert the job is correct
2065 $addUpdateCallCounter = 0;
2066 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2067 function( $callable ) use ( &$addUpdateCallCounter ) {
2068 $addUpdateCallCounter++
;
2069 $this->assertInternalType( 'callable', $callable );
2073 $getTimestampCallCounter = 0;
2074 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2075 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2076 $getTimestampCallCounter++
;
2077 $this->assertEquals( $title, $titleParam );
2078 $this->assertEquals( $oldid, $oldidParam );
2083 $store->resetNotificationTimestamp(
2090 $this->assertEquals( 1, $addUpdateCallCounter );
2091 $this->assertEquals( 1, $getTimestampCallCounter );
2093 ScopedCallback
::consume( $scopedOverrideDeferred );
2094 ScopedCallback
::consume( $scopedOverrideRevision );
2097 public function testUpdateNotificationTimestamp_watchersExist() {
2098 $mockDb = $this->getMockDb();
2099 $mockDb->expects( $this->once() )
2100 ->method( 'select' )
2106 'wl_namespace' => 0,
2107 'wl_title' => 'SomeDbKey',
2108 'wl_notificationtimestamp IS NULL'
2112 $this->returnValue( [
2113 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2114 $this->getFakeRow( [ 'wl_user' => '3' ] )
2117 $mockDb->expects( $this->once() )
2118 ->method( 'onTransactionIdle' )
2119 ->with( $this->isType( 'callable' ) )
2120 ->will( $this->returnCallback( function( $callable ) {
2123 $mockDb->expects( $this->once() )
2124 ->method( 'update' )
2127 [ 'wl_notificationtimestamp' => null ],
2129 'wl_user' => [ 2, 3 ],
2130 'wl_namespace' => 0,
2131 'wl_title' => 'SomeDbKey',
2135 $mockCache = $this->getMockCache();
2136 $mockCache->expects( $this->never() )->method( 'set' );
2137 $mockCache->expects( $this->never() )->method( 'get' );
2138 $mockCache->expects( $this->never() )->method( 'delete' );
2140 $store = $this->newWatchedItemStore(
2141 $this->getMockLoadBalancer( $mockDb ),
2145 $this->assertEquals(
2147 $store->updateNotificationTimestamp(
2148 $this->getMockNonAnonUserWithId( 1 ),
2149 new TitleValue( 0, 'SomeDbKey' ),
2155 public function testUpdateNotificationTimestamp_noWatchers() {
2156 $mockDb = $this->getMockDb();
2157 $mockDb->expects( $this->once() )
2158 ->method( 'select' )
2164 'wl_namespace' => 0,
2165 'wl_title' => 'SomeDbKey',
2166 'wl_notificationtimestamp IS NULL'
2170 $this->returnValue( [] )
2172 $mockDb->expects( $this->never() )
2173 ->method( 'onTransactionIdle' );
2174 $mockDb->expects( $this->never() )
2175 ->method( 'update' );
2177 $mockCache = $this->getMockCache();
2178 $mockCache->expects( $this->never() )->method( 'set' );
2179 $mockCache->expects( $this->never() )->method( 'get' );
2180 $mockCache->expects( $this->never() )->method( 'delete' );
2182 $store = $this->newWatchedItemStore(
2183 $this->getMockLoadBalancer( $mockDb ),
2187 $watchers = $store->updateNotificationTimestamp(
2188 $this->getMockNonAnonUserWithId( 1 ),
2189 new TitleValue( 0, 'SomeDbKey' ),
2192 $this->assertInternalType( 'array', $watchers );
2193 $this->assertEmpty( $watchers );
2196 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2197 $user = $this->getMockNonAnonUserWithId( 1 );
2198 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2200 $mockDb = $this->getMockDb();
2201 $mockDb->expects( $this->once() )
2202 ->method( 'selectRow' )
2203 ->will( $this->returnValue(
2204 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2206 $mockDb->expects( $this->once() )
2207 ->method( 'select' )
2209 $this->returnValue( [
2210 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2211 $this->getFakeRow( [ 'wl_user' => '3' ] )
2214 $mockDb->expects( $this->once() )
2215 ->method( 'onTransactionIdle' )
2216 ->with( $this->isType( 'callable' ) )
2217 ->will( $this->returnCallback( function( $callable ) {
2220 $mockDb->expects( $this->once() )
2221 ->method( 'update' );
2223 $mockCache = $this->getMockCache();
2224 $mockCache->expects( $this->once() )
2226 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2227 $mockCache->expects( $this->once() )
2229 ->with( '0:SomeDbKey:1' );
2230 $mockCache->expects( $this->once() )
2231 ->method( 'delete' )
2232 ->with( '0:SomeDbKey:1' );
2234 $store = $this->newWatchedItemStore(
2235 $this->getMockLoadBalancer( $mockDb ),
2239 // This will add the item to the cache
2240 $store->getWatchedItem( $user, $titleValue );
2242 $store->updateNotificationTimestamp(
2243 $this->getMockNonAnonUserWithId( 1 ),