2 use MediaWiki\Linker\LinkTarget
;
7 * @covers WatchedItemStore
9 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
12 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
14 private function getMockDb() {
15 return $this->getMock( IDatabase
::class );
19 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
21 private function getMockLoadBalancer(
23 $expectedConnectionType = null,
24 $readOnlyReason = false
26 $mock = $this->getMockBuilder( LoadBalancer
::class )
27 ->disableOriginalConstructor()
29 if ( $expectedConnectionType !== null ) {
30 $mock->expects( $this->any() )
31 ->method( 'getConnection' )
32 ->with( $expectedConnectionType )
33 ->will( $this->returnValue( $mockDb ) );
35 $mock->expects( $this->any() )
36 ->method( 'getConnection' )
37 ->will( $this->returnValue( $mockDb ) );
39 $mock->expects( $this->any() )
40 ->method( 'getReadOnlyReason' )
41 ->will( $this->returnValue( $readOnlyReason ) );
46 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
48 private function getMockCache() {
49 $mock = $this->getMockBuilder( HashBagOStuff
::class )
50 ->disableOriginalConstructor()
52 $mock->expects( $this->any() )
54 ->will( $this->returnCallback( function() {
55 return implode( ':', func_get_args() );
62 * @return PHPUnit_Framework_MockObject_MockObject|User
64 private function getMockNonAnonUserWithId( $id ) {
65 $mock = $this->getMock( User
::class );
66 $mock->expects( $this->any() )
68 ->will( $this->returnValue( false ) );
69 $mock->expects( $this->any() )
71 ->will( $this->returnValue( $id ) );
78 private function getAnonUser() {
79 return User
::newFromName( 'Anon_User' );
82 private function getFakeRow( array $rowValues ) {
83 $fakeRow = new stdClass();
84 foreach ( $rowValues as $valueName => $value ) {
85 $fakeRow->$valueName = $value;
90 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache ) {
91 return new WatchedItemStore(
97 public function testGetDefaultInstance() {
98 $instanceOne = WatchedItemStore
::getDefaultInstance();
99 $instanceTwo = WatchedItemStore
::getDefaultInstance();
100 $this->assertSame( $instanceOne, $instanceTwo );
103 public function testCountWatchedItems() {
104 $user = $this->getMockNonAnonUserWithId( 1 );
106 $mockDb = $this->getMockDb();
107 $mockDb->expects( $this->exactly( 1 ) )
108 ->method( 'selectField' )
113 'wl_user' => $user->getId(),
115 $this->isType( 'string' )
117 ->will( $this->returnValue( 12 ) );
119 $mockCache = $this->getMockCache();
120 $mockCache->expects( $this->never() )->method( 'get' );
121 $mockCache->expects( $this->never() )->method( 'set' );
122 $mockCache->expects( $this->never() )->method( 'delete' );
124 $store = $this->newWatchedItemStore(
125 $this->getMockLoadBalancer( $mockDb ),
129 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
132 public function testCountWatchers() {
133 $titleValue = new TitleValue( 0, 'SomeDbKey' );
135 $mockDb = $this->getMockDb();
136 $mockDb->expects( $this->exactly( 1 ) )
137 ->method( 'selectField' )
142 'wl_namespace' => $titleValue->getNamespace(),
143 'wl_title' => $titleValue->getDBkey(),
145 $this->isType( 'string' )
147 ->will( $this->returnValue( 7 ) );
149 $mockCache = $this->getMockCache();
150 $mockCache->expects( $this->never() )->method( 'get' );
151 $mockCache->expects( $this->never() )->method( 'set' );
152 $mockCache->expects( $this->never() )->method( 'delete' );
154 $store = $this->newWatchedItemStore(
155 $this->getMockLoadBalancer( $mockDb ),
159 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
162 public function testCountWatchersMultiple() {
164 new TitleValue( 0, 'SomeDbKey' ),
165 new TitleValue( 0, 'OtherDbKey' ),
166 new TitleValue( 1, 'AnotherDbKey' ),
169 $mockDb = $this->getMockDb();
172 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
173 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
174 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
177 $mockDb->expects( $this->once() )
178 ->method( 'makeWhereFrom2d' )
180 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
181 $this->isType( 'string' ),
182 $this->isType( 'string' )
184 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
185 $mockDb->expects( $this->once() )
189 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
190 [ 'makeWhereFrom2d return value' ],
191 $this->isType( 'string' ),
193 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
197 $this->returnValue( $dbResult )
200 $mockCache = $this->getMockCache();
201 $mockCache->expects( $this->never() )->method( 'get' );
202 $mockCache->expects( $this->never() )->method( 'set' );
203 $mockCache->expects( $this->never() )->method( 'delete' );
205 $store = $this->newWatchedItemStore(
206 $this->getMockLoadBalancer( $mockDb ),
211 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
212 1 => [ 'AnotherDbKey' => 500 ],
214 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
217 public function provideIntWithDbUnsafeVersion() {
220 [ "50; DROP TABLE watchlist;\n--" ],
225 * @dataProvider provideIntWithDbUnsafeVersion
227 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
229 new TitleValue( 0, 'SomeDbKey' ),
230 new TitleValue( 0, 'OtherDbKey' ),
231 new TitleValue( 1, 'AnotherDbKey' ),
234 $mockDb = $this->getMockDb();
237 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
238 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
239 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
242 $mockDb->expects( $this->once() )
243 ->method( 'makeWhereFrom2d' )
245 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
246 $this->isType( 'string' ),
247 $this->isType( 'string' )
249 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
250 $mockDb->expects( $this->once() )
254 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
255 [ 'makeWhereFrom2d return value' ],
256 $this->isType( 'string' ),
258 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
259 'HAVING' => 'COUNT(*) >= 50',
263 $this->returnValue( $dbResult )
266 $mockCache = $this->getMockCache();
267 $mockCache->expects( $this->never() )->method( 'get' );
268 $mockCache->expects( $this->never() )->method( 'set' );
269 $mockCache->expects( $this->never() )->method( 'delete' );
271 $store = $this->newWatchedItemStore(
272 $this->getMockLoadBalancer( $mockDb ),
277 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
278 1 => [ 'AnotherDbKey' => 500 ],
282 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
286 public function testCountVisitingWatchers() {
287 $titleValue = new TitleValue( 0, 'SomeDbKey' );
289 $mockDb = $this->getMockDb();
290 $mockDb->expects( $this->exactly( 1 ) )
291 ->method( 'selectField' )
296 'wl_namespace' => $titleValue->getNamespace(),
297 'wl_title' => $titleValue->getDBkey(),
298 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
300 $this->isType( 'string' )
302 ->will( $this->returnValue( 7 ) );
303 $mockDb->expects( $this->exactly( 1 ) )
304 ->method( 'addQuotes' )
305 ->will( $this->returnCallback( function( $value ) {
308 $mockDb->expects( $this->exactly( 1 ) )
309 ->method( 'timestamp' )
310 ->will( $this->returnCallback( function( $value ) {
311 return 'TS' . $value . 'TS';
314 $mockCache = $this->getMockCache();
315 $mockCache->expects( $this->never() )->method( 'set' );
316 $mockCache->expects( $this->never() )->method( 'get' );
317 $mockCache->expects( $this->never() )->method( 'delete' );
319 $store = $this->newWatchedItemStore(
320 $this->getMockLoadBalancer( $mockDb ),
324 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
327 public function testCountVisitingWatchersMultiple() {
328 $titleValuesWithThresholds = [
329 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
330 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
331 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
335 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
336 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
337 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
339 $mockDb = $this->getMockDb();
340 $mockDb->expects( $this->exactly( 2 * 3 ) )
341 ->method( 'addQuotes' )
342 ->will( $this->returnCallback( function( $value ) {
345 $mockDb->expects( $this->exactly( 3 ) )
346 ->method( 'timestamp' )
347 ->will( $this->returnCallback( function( $value ) {
348 return 'TS' . $value . 'TS';
350 $mockDb->expects( $this->any() )
351 ->method( 'makeList' )
353 $this->isType( 'array' ),
354 $this->isType( 'int' )
356 ->will( $this->returnCallback( function( $a, $conj ) {
357 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
358 return join( $sqlConj, array_map( function( $s ) {
359 return '(' . $s . ')';
363 $mockDb->expects( $this->never() )
364 ->method( 'makeWhereFrom2d' );
367 '((wl_namespace = 0) AND (' .
368 "(((wl_title = 'SomeDbKey') AND (" .
369 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
371 "(wl_title = 'OtherDbKey') AND (" .
372 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
374 ') OR ((wl_namespace = 1) AND (' .
375 "(((wl_title = 'AnotherDbKey') AND (".
376 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
378 $mockDb->expects( $this->once() )
382 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
384 $this->isType( 'string' ),
386 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
390 $this->returnValue( $dbResult )
393 $mockCache = $this->getMockCache();
394 $mockCache->expects( $this->never() )->method( 'get' );
395 $mockCache->expects( $this->never() )->method( 'set' );
396 $mockCache->expects( $this->never() )->method( 'delete' );
398 $store = $this->newWatchedItemStore(
399 $this->getMockLoadBalancer( $mockDb ),
404 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
405 1 => [ 'AnotherDbKey' => 500 ],
409 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
413 public function testCountVisitingWatchersMultiple_withMissingTargets() {
414 $titleValuesWithThresholds = [
415 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
416 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
417 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
418 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
419 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
423 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
424 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
425 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
427 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
430 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
433 $mockDb = $this->getMockDb();
434 $mockDb->expects( $this->exactly( 2 * 3 ) )
435 ->method( 'addQuotes' )
436 ->will( $this->returnCallback( function( $value ) {
439 $mockDb->expects( $this->exactly( 3 ) )
440 ->method( 'timestamp' )
441 ->will( $this->returnCallback( function( $value ) {
442 return 'TS' . $value . 'TS';
444 $mockDb->expects( $this->any() )
445 ->method( 'makeList' )
447 $this->isType( 'array' ),
448 $this->isType( 'int' )
450 ->will( $this->returnCallback( function( $a, $conj ) {
451 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
452 return join( $sqlConj, array_map( function( $s ) {
453 return '(' . $s . ')';
457 $mockDb->expects( $this->once() )
458 ->method( 'makeWhereFrom2d' )
460 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
461 $this->isType( 'string' ),
462 $this->isType( 'string' )
464 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
467 '((wl_namespace = 0) AND (' .
468 "(((wl_title = 'SomeDbKey') AND (" .
469 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
471 "(wl_title = 'OtherDbKey') AND (" .
472 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
474 ') OR ((wl_namespace = 1) AND (' .
475 "(((wl_title = 'AnotherDbKey') AND (".
476 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
479 '(makeWhereFrom2d return value)';
480 $mockDb->expects( $this->once() )
484 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
486 $this->isType( 'string' ),
488 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
492 $this->returnValue( $dbResult )
495 $mockCache = $this->getMockCache();
496 $mockCache->expects( $this->never() )->method( 'get' );
497 $mockCache->expects( $this->never() )->method( 'set' );
498 $mockCache->expects( $this->never() )->method( 'delete' );
500 $store = $this->newWatchedItemStore(
501 $this->getMockLoadBalancer( $mockDb ),
507 'SomeDbKey' => 100, 'OtherDbKey' => 300,
508 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
510 1 => [ 'AnotherDbKey' => 500 ],
514 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
519 * @dataProvider provideIntWithDbUnsafeVersion
521 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
522 $titleValuesWithThresholds = [
523 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
524 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
525 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
528 $mockDb = $this->getMockDb();
529 $mockDb->expects( $this->any() )
530 ->method( 'makeList' )
531 ->will( $this->returnValue( 'makeList return value' ) );
532 $mockDb->expects( $this->once() )
536 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
537 'makeList return value',
538 $this->isType( 'string' ),
540 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
541 'HAVING' => 'COUNT(*) >= 50',
545 $this->returnValue( [] )
548 $mockCache = $this->getMockCache();
549 $mockCache->expects( $this->never() )->method( 'get' );
550 $mockCache->expects( $this->never() )->method( 'set' );
551 $mockCache->expects( $this->never() )->method( 'delete' );
553 $store = $this->newWatchedItemStore(
554 $this->getMockLoadBalancer( $mockDb ),
559 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
560 1 => [ 'AnotherDbKey' => 0 ],
564 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
568 public function testCountUnreadNotifications() {
569 $user = $this->getMockNonAnonUserWithId( 1 );
571 $mockDb = $this->getMockDb();
572 $mockDb->expects( $this->exactly( 1 ) )
573 ->method( 'selectRowCount' )
578 "wl_notificationtimestamp IS NOT NULL",
581 $this->isType( 'string' )
583 ->will( $this->returnValue( 9 ) );
585 $mockCache = $this->getMockCache();
586 $mockCache->expects( $this->never() )->method( 'set' );
587 $mockCache->expects( $this->never() )->method( 'get' );
588 $mockCache->expects( $this->never() )->method( 'delete' );
590 $store = $this->newWatchedItemStore(
591 $this->getMockLoadBalancer( $mockDb ),
595 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
599 * @dataProvider provideIntWithDbUnsafeVersion
601 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
602 $user = $this->getMockNonAnonUserWithId( 1 );
604 $mockDb = $this->getMockDb();
605 $mockDb->expects( $this->exactly( 1 ) )
606 ->method( 'selectRowCount' )
611 "wl_notificationtimestamp IS NOT NULL",
614 $this->isType( 'string' ),
617 ->will( $this->returnValue( 50 ) );
619 $mockCache = $this->getMockCache();
620 $mockCache->expects( $this->never() )->method( 'set' );
621 $mockCache->expects( $this->never() )->method( 'get' );
622 $mockCache->expects( $this->never() )->method( 'delete' );
624 $store = $this->newWatchedItemStore(
625 $this->getMockLoadBalancer( $mockDb ),
631 $store->countUnreadNotifications( $user, $limit )
636 * @dataProvider provideIntWithDbUnsafeVersion
638 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
639 $user = $this->getMockNonAnonUserWithId( 1 );
641 $mockDb = $this->getMockDb();
642 $mockDb->expects( $this->exactly( 1 ) )
643 ->method( 'selectRowCount' )
648 "wl_notificationtimestamp IS NOT NULL",
651 $this->isType( 'string' ),
654 ->will( $this->returnValue( 9 ) );
656 $mockCache = $this->getMockCache();
657 $mockCache->expects( $this->never() )->method( 'set' );
658 $mockCache->expects( $this->never() )->method( 'get' );
659 $mockCache->expects( $this->never() )->method( 'delete' );
661 $store = $this->newWatchedItemStore(
662 $this->getMockLoadBalancer( $mockDb ),
668 $store->countUnreadNotifications( $user, $limit )
672 public function testDuplicateEntry_nothingToDuplicate() {
673 $mockDb = $this->getMockDb();
674 $mockDb->expects( $this->once() )
680 'wl_notificationtimestamp',
684 'wl_title' => 'Old_Title',
686 'WatchedItemStore::duplicateEntry',
689 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
691 $store = $this->newWatchedItemStore(
692 $this->getMockLoadBalancer( $mockDb ),
693 $this->getMockCache()
696 $store->duplicateEntry(
697 Title
::newFromText( 'Old_Title' ),
698 Title
::newFromText( 'New_Title' )
702 public function testDuplicateEntry_somethingToDuplicate() {
704 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
705 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
708 $mockDb = $this->getMockDb();
709 $mockDb->expects( $this->at( 0 ) )
715 'wl_notificationtimestamp',
719 'wl_title' => 'Old_Title',
722 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
723 $mockDb->expects( $this->at( 1 ) )
724 ->method( 'replace' )
727 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
732 'wl_title' => 'New_Title',
733 'wl_notificationtimestamp' => '20151212010101',
738 'wl_title' => 'New_Title',
739 'wl_notificationtimestamp' => null,
742 $this->isType( 'string' )
745 $mockCache = $this->getMockCache();
746 $mockCache->expects( $this->never() )->method( 'get' );
747 $mockCache->expects( $this->never() )->method( 'delete' );
749 $store = $this->newWatchedItemStore(
750 $this->getMockLoadBalancer( $mockDb ),
754 $store->duplicateEntry(
755 Title
::newFromText( 'Old_Title' ),
756 Title
::newFromText( 'New_Title' )
760 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
761 $mockDb = $this->getMockDb();
762 $mockDb->expects( $this->at( 0 ) )
768 'wl_notificationtimestamp',
772 'wl_title' => 'Old_Title',
775 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
776 $mockDb->expects( $this->at( 1 ) )
782 'wl_notificationtimestamp',
786 'wl_title' => 'Old_Title',
789 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
791 $mockCache = $this->getMockCache();
792 $mockCache->expects( $this->never() )->method( 'get' );
793 $mockCache->expects( $this->never() )->method( 'delete' );
795 $store = $this->newWatchedItemStore(
796 $this->getMockLoadBalancer( $mockDb ),
800 $store->duplicateAllAssociatedEntries(
801 Title
::newFromText( 'Old_Title' ),
802 Title
::newFromText( 'New_Title' )
806 public function provideLinkTargetPairs() {
808 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
809 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
814 * @dataProvider provideLinkTargetPairs
816 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
817 LinkTarget
$oldTarget,
818 LinkTarget
$newTarget
821 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
824 $mockDb = $this->getMockDb();
825 $mockDb->expects( $this->at( 0 ) )
831 'wl_notificationtimestamp',
834 'wl_namespace' => $oldTarget->getNamespace(),
835 'wl_title' => $oldTarget->getDBkey(),
838 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
839 $mockDb->expects( $this->at( 1 ) )
840 ->method( 'replace' )
843 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
847 'wl_namespace' => $newTarget->getNamespace(),
848 'wl_title' => $newTarget->getDBkey(),
849 'wl_notificationtimestamp' => '20151212010101',
852 $this->isType( 'string' )
854 $mockDb->expects( $this->at( 2 ) )
860 'wl_notificationtimestamp',
863 'wl_namespace' => $oldTarget->getNamespace() +
1,
864 'wl_title' => $oldTarget->getDBkey(),
867 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
868 $mockDb->expects( $this->at( 3 ) )
869 ->method( 'replace' )
872 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
876 'wl_namespace' => $newTarget->getNamespace() +
1,
877 'wl_title' => $newTarget->getDBkey(),
878 'wl_notificationtimestamp' => '20151212010101',
881 $this->isType( 'string' )
884 $mockCache = $this->getMockCache();
885 $mockCache->expects( $this->never() )->method( 'get' );
886 $mockCache->expects( $this->never() )->method( 'delete' );
888 $store = $this->newWatchedItemStore(
889 $this->getMockLoadBalancer( $mockDb ),
893 $store->duplicateAllAssociatedEntries(
899 public function testAddWatch_nonAnonymousUser() {
900 $mockDb = $this->getMockDb();
901 $mockDb->expects( $this->once() )
909 'wl_title' => 'Some_Page',
910 'wl_notificationtimestamp' => null,
915 $mockCache = $this->getMockCache();
916 $mockCache->expects( $this->once() )
918 ->with( '0:Some_Page:1' );
920 $store = $this->newWatchedItemStore(
921 $this->getMockLoadBalancer( $mockDb ),
926 $this->getMockNonAnonUserWithId( 1 ),
927 Title
::newFromText( 'Some_Page' )
931 public function testAddWatch_anonymousUser() {
932 $mockDb = $this->getMockDb();
933 $mockDb->expects( $this->never() )
934 ->method( 'insert' );
936 $mockCache = $this->getMockCache();
937 $mockCache->expects( $this->never() )
938 ->method( 'delete' );
940 $store = $this->newWatchedItemStore(
941 $this->getMockLoadBalancer( $mockDb ),
946 $this->getAnonUser(),
947 Title
::newFromText( 'Some_Page' )
951 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
952 $store = $this->newWatchedItemStore(
953 $this->getMockLoadBalancer( $this->getMockDb(), null, 'Some Reason' ),
954 $this->getMockCache()
958 $store->addWatchBatchForUser(
959 $this->getMockNonAnonUserWithId( 1 ),
960 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
965 public function testAddWatchBatchForUser_nonAnonymousUser() {
966 $mockDb = $this->getMockDb();
967 $mockDb->expects( $this->once() )
975 'wl_title' => 'Some_Page',
976 'wl_notificationtimestamp' => null,
981 'wl_title' => 'Some_Page',
982 'wl_notificationtimestamp' => null,
987 $mockCache = $this->getMockCache();
988 $mockCache->expects( $this->exactly( 2 ) )
989 ->method( 'delete' );
990 $mockCache->expects( $this->at( 1 ) )
992 ->with( '0:Some_Page:1' );
993 $mockCache->expects( $this->at( 3 ) )
995 ->with( '1:Some_Page:1' );
997 $store = $this->newWatchedItemStore(
998 $this->getMockLoadBalancer( $mockDb ),
1002 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1005 $store->addWatchBatchForUser(
1007 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1012 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1013 $mockDb = $this->getMockDb();
1014 $mockDb->expects( $this->never() )
1015 ->method( 'insert' );
1017 $mockCache = $this->getMockCache();
1018 $mockCache->expects( $this->never() )
1019 ->method( 'delete' );
1021 $store = $this->newWatchedItemStore(
1022 $this->getMockLoadBalancer( $mockDb ),
1027 $store->addWatchBatchForUser(
1028 $this->getAnonUser(),
1029 [ new TitleValue( 0, 'Other_Page' ) ]
1034 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1035 $user = $this->getMockNonAnonUserWithId( 1 );
1036 $mockDb = $this->getMockDb();
1037 $mockDb->expects( $this->never() )
1038 ->method( 'insert' );
1040 $mockCache = $this->getMockCache();
1041 $mockCache->expects( $this->never() )
1042 ->method( 'delete' );
1044 $store = $this->newWatchedItemStore(
1045 $this->getMockLoadBalancer( $mockDb ),
1050 $store->addWatchBatchForUser( $user, [] )
1054 public function testLoadWatchedItem_existingItem() {
1055 $mockDb = $this->getMockDb();
1056 $mockDb->expects( $this->once() )
1057 ->method( 'selectRow' )
1060 'wl_notificationtimestamp',
1063 'wl_namespace' => 0,
1064 'wl_title' => 'SomeDbKey',
1067 ->will( $this->returnValue(
1068 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1071 $mockCache = $this->getMockCache();
1072 $mockCache->expects( $this->once() )
1078 $store = $this->newWatchedItemStore(
1079 $this->getMockLoadBalancer( $mockDb ),
1083 $watchedItem = $store->loadWatchedItem(
1084 $this->getMockNonAnonUserWithId( 1 ),
1085 new TitleValue( 0, 'SomeDbKey' )
1087 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1088 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1089 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1090 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1093 public function testLoadWatchedItem_noItem() {
1094 $mockDb = $this->getMockDb();
1095 $mockDb->expects( $this->once() )
1096 ->method( 'selectRow' )
1099 'wl_notificationtimestamp',
1102 'wl_namespace' => 0,
1103 'wl_title' => 'SomeDbKey',
1106 ->will( $this->returnValue( [] ) );
1108 $mockCache = $this->getMockCache();
1109 $mockCache->expects( $this->never() )->method( 'get' );
1110 $mockCache->expects( $this->never() )->method( 'delete' );
1112 $store = $this->newWatchedItemStore(
1113 $this->getMockLoadBalancer( $mockDb ),
1118 $store->loadWatchedItem(
1119 $this->getMockNonAnonUserWithId( 1 ),
1120 new TitleValue( 0, 'SomeDbKey' )
1125 public function testLoadWatchedItem_anonymousUser() {
1126 $mockDb = $this->getMockDb();
1127 $mockDb->expects( $this->never() )
1128 ->method( 'selectRow' );
1130 $mockCache = $this->getMockCache();
1131 $mockCache->expects( $this->never() )->method( 'get' );
1132 $mockCache->expects( $this->never() )->method( 'delete' );
1134 $store = $this->newWatchedItemStore(
1135 $this->getMockLoadBalancer( $mockDb ),
1140 $store->loadWatchedItem(
1141 $this->getAnonUser(),
1142 new TitleValue( 0, 'SomeDbKey' )
1147 public function testRemoveWatch_existingItem() {
1148 $mockDb = $this->getMockDb();
1149 $mockDb->expects( $this->once() )
1150 ->method( 'delete' )
1155 'wl_namespace' => 0,
1156 'wl_title' => 'SomeDbKey',
1159 $mockDb->expects( $this->once() )
1160 ->method( 'affectedRows' )
1161 ->will( $this->returnValue( 1 ) );
1163 $mockCache = $this->getMockCache();
1164 $mockCache->expects( $this->never() )->method( 'get' );
1165 $mockCache->expects( $this->once() )
1166 ->method( 'delete' )
1167 ->with( '0:SomeDbKey:1' );
1169 $store = $this->newWatchedItemStore(
1170 $this->getMockLoadBalancer( $mockDb ),
1175 $store->removeWatch(
1176 $this->getMockNonAnonUserWithId( 1 ),
1177 new TitleValue( 0, 'SomeDbKey' )
1182 public function testRemoveWatch_noItem() {
1183 $mockDb = $this->getMockDb();
1184 $mockDb->expects( $this->once() )
1185 ->method( 'delete' )
1190 'wl_namespace' => 0,
1191 'wl_title' => 'SomeDbKey',
1194 $mockDb->expects( $this->once() )
1195 ->method( 'affectedRows' )
1196 ->will( $this->returnValue( 0 ) );
1198 $mockCache = $this->getMockCache();
1199 $mockCache->expects( $this->never() )->method( 'get' );
1200 $mockCache->expects( $this->once() )
1201 ->method( 'delete' )
1202 ->with( '0:SomeDbKey:1' );
1204 $store = $this->newWatchedItemStore(
1205 $this->getMockLoadBalancer( $mockDb ),
1210 $store->removeWatch(
1211 $this->getMockNonAnonUserWithId( 1 ),
1212 new TitleValue( 0, 'SomeDbKey' )
1217 public function testRemoveWatch_anonymousUser() {
1218 $mockDb = $this->getMockDb();
1219 $mockDb->expects( $this->never() )
1220 ->method( 'delete' );
1222 $mockCache = $this->getMockCache();
1223 $mockCache->expects( $this->never() )->method( 'get' );
1224 $mockCache->expects( $this->never() )
1225 ->method( 'delete' );
1227 $store = $this->newWatchedItemStore(
1228 $this->getMockLoadBalancer( $mockDb ),
1233 $store->removeWatch(
1234 $this->getAnonUser(),
1235 new TitleValue( 0, 'SomeDbKey' )
1240 public function testGetWatchedItem_existingItem() {
1241 $mockDb = $this->getMockDb();
1242 $mockDb->expects( $this->once() )
1243 ->method( 'selectRow' )
1246 'wl_notificationtimestamp',
1249 'wl_namespace' => 0,
1250 'wl_title' => 'SomeDbKey',
1253 ->will( $this->returnValue(
1254 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1257 $mockCache = $this->getMockCache();
1258 $mockCache->expects( $this->never() )->method( 'delete' );
1259 $mockCache->expects( $this->once() )
1264 ->will( $this->returnValue( null ) );
1265 $mockCache->expects( $this->once() )
1271 $store = $this->newWatchedItemStore(
1272 $this->getMockLoadBalancer( $mockDb ),
1276 $watchedItem = $store->getWatchedItem(
1277 $this->getMockNonAnonUserWithId( 1 ),
1278 new TitleValue( 0, 'SomeDbKey' )
1280 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1281 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1282 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1283 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1286 public function testGetWatchedItem_cachedItem() {
1287 $mockDb = $this->getMockDb();
1288 $mockDb->expects( $this->never() )
1289 ->method( 'selectRow' );
1291 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1292 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1293 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1295 $mockCache = $this->getMockCache();
1296 $mockCache->expects( $this->never() )->method( 'delete' );
1297 $mockCache->expects( $this->never() )->method( 'set' );
1298 $mockCache->expects( $this->once() )
1303 ->will( $this->returnValue( $cachedItem ) );
1305 $store = $this->newWatchedItemStore(
1306 $this->getMockLoadBalancer( $mockDb ),
1310 $this->assertEquals(
1312 $store->getWatchedItem(
1319 public function testGetWatchedItem_noItem() {
1320 $mockDb = $this->getMockDb();
1321 $mockDb->expects( $this->once() )
1322 ->method( 'selectRow' )
1325 'wl_notificationtimestamp',
1328 'wl_namespace' => 0,
1329 'wl_title' => 'SomeDbKey',
1332 ->will( $this->returnValue( [] ) );
1334 $mockCache = $this->getMockCache();
1335 $mockCache->expects( $this->never() )->method( 'set' );
1336 $mockCache->expects( $this->never() )->method( 'delete' );
1337 $mockCache->expects( $this->once() )
1339 ->with( '0:SomeDbKey:1' )
1340 ->will( $this->returnValue( false ) );
1342 $store = $this->newWatchedItemStore(
1343 $this->getMockLoadBalancer( $mockDb ),
1348 $store->getWatchedItem(
1349 $this->getMockNonAnonUserWithId( 1 ),
1350 new TitleValue( 0, 'SomeDbKey' )
1355 public function testGetWatchedItem_anonymousUser() {
1356 $mockDb = $this->getMockDb();
1357 $mockDb->expects( $this->never() )
1358 ->method( 'selectRow' );
1360 $mockCache = $this->getMockCache();
1361 $mockCache->expects( $this->never() )->method( 'set' );
1362 $mockCache->expects( $this->never() )->method( 'get' );
1363 $mockCache->expects( $this->never() )->method( 'delete' );
1365 $store = $this->newWatchedItemStore(
1366 $this->getMockLoadBalancer( $mockDb ),
1371 $store->getWatchedItem(
1372 $this->getAnonUser(),
1373 new TitleValue( 0, 'SomeDbKey' )
1378 public function testGetWatchedItemsForUser() {
1379 $mockDb = $this->getMockDb();
1380 $mockDb->expects( $this->once() )
1381 ->method( 'select' )
1384 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1387 ->will( $this->returnValue( [
1388 $this->getFakeRow( [
1389 'wl_namespace' => 0,
1390 'wl_title' => 'Foo1',
1391 'wl_notificationtimestamp' => '20151212010101',
1393 $this->getFakeRow( [
1394 'wl_namespace' => 1,
1395 'wl_title' => 'Foo2',
1396 'wl_notificationtimestamp' => null,
1400 $mockCache = $this->getMockCache();
1401 $mockCache->expects( $this->never() )->method( 'delete' );
1402 $mockCache->expects( $this->never() )->method( 'get' );
1403 $mockCache->expects( $this->never() )->method( 'set' );
1405 $store = $this->newWatchedItemStore(
1406 $this->getMockLoadBalancer( $mockDb ),
1409 $user = $this->getMockNonAnonUserWithId( 1 );
1411 $watchedItems = $store->getWatchedItemsForUser( $user );
1413 $this->assertInternalType( 'array', $watchedItems );
1414 $this->assertCount( 2, $watchedItems );
1415 foreach ( $watchedItems as $watchedItem ) {
1416 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1418 $this->assertEquals(
1419 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1422 $this->assertEquals(
1423 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1428 public function provideDbTypes() {
1430 [ false, DB_SLAVE
],
1431 [ true, DB_MASTER
],
1436 * @dataProvider provideDbTypes
1438 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1439 $mockDb = $this->getMockDb();
1440 $mockCache = $this->getMockCache();
1441 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1442 $user = $this->getMockNonAnonUserWithId( 1 );
1444 $mockDb->expects( $this->once() )
1445 ->method( 'select' )
1448 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1450 $this->isType( 'string' ),
1451 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1453 ->will( $this->returnValue( [] ) );
1455 $store = $this->newWatchedItemStore(
1460 $watchedItems = $store->getWatchedItemsForUser(
1462 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1464 $this->assertEquals( [], $watchedItems );
1467 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1468 $store = $this->newWatchedItemStore(
1469 $this->getMockLoadBalancer( $this->getMockDb() ),
1470 $this->getMockCache()
1473 $this->setExpectedException( 'InvalidArgumentException' );
1474 $store->getWatchedItemsForUser(
1475 $this->getMockNonAnonUserWithId( 1 ),
1480 public function testIsWatchedItem_existingItem() {
1481 $mockDb = $this->getMockDb();
1482 $mockDb->expects( $this->once() )
1483 ->method( 'selectRow' )
1486 'wl_notificationtimestamp',
1489 'wl_namespace' => 0,
1490 'wl_title' => 'SomeDbKey',
1493 ->will( $this->returnValue(
1494 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1497 $mockCache = $this->getMockCache();
1498 $mockCache->expects( $this->never() )->method( 'delete' );
1499 $mockCache->expects( $this->once() )
1501 ->with( '0:SomeDbKey:1' )
1502 ->will( $this->returnValue( false ) );
1503 $mockCache->expects( $this->once() )
1509 $store = $this->newWatchedItemStore(
1510 $this->getMockLoadBalancer( $mockDb ),
1516 $this->getMockNonAnonUserWithId( 1 ),
1517 new TitleValue( 0, 'SomeDbKey' )
1522 public function testIsWatchedItem_noItem() {
1523 $mockDb = $this->getMockDb();
1524 $mockDb->expects( $this->once() )
1525 ->method( 'selectRow' )
1528 'wl_notificationtimestamp',
1531 'wl_namespace' => 0,
1532 'wl_title' => 'SomeDbKey',
1535 ->will( $this->returnValue( [] ) );
1537 $mockCache = $this->getMockCache();
1538 $mockCache->expects( $this->never() )->method( 'set' );
1539 $mockCache->expects( $this->never() )->method( 'delete' );
1540 $mockCache->expects( $this->once() )
1542 ->with( '0:SomeDbKey:1' )
1543 ->will( $this->returnValue( false ) );
1545 $store = $this->newWatchedItemStore(
1546 $this->getMockLoadBalancer( $mockDb ),
1552 $this->getMockNonAnonUserWithId( 1 ),
1553 new TitleValue( 0, 'SomeDbKey' )
1558 public function testIsWatchedItem_anonymousUser() {
1559 $mockDb = $this->getMockDb();
1560 $mockDb->expects( $this->never() )
1561 ->method( 'selectRow' );
1563 $mockCache = $this->getMockCache();
1564 $mockCache->expects( $this->never() )->method( 'set' );
1565 $mockCache->expects( $this->never() )->method( 'get' );
1566 $mockCache->expects( $this->never() )->method( 'delete' );
1568 $store = $this->newWatchedItemStore(
1569 $this->getMockLoadBalancer( $mockDb ),
1575 $this->getAnonUser(),
1576 new TitleValue( 0, 'SomeDbKey' )
1581 public function testGetNotificationTimestampsBatch() {
1583 new TitleValue( 0, 'SomeDbKey' ),
1584 new TitleValue( 1, 'AnotherDbKey' ),
1587 $mockDb = $this->getMockDb();
1589 $this->getFakeRow( [
1590 'wl_namespace' => 0,
1591 'wl_title' => 'SomeDbKey',
1592 'wl_notificationtimestamp' => '20151212010101',
1596 'wl_namespace' => 1,
1597 'wl_title' => 'AnotherDbKey',
1598 'wl_notificationtimestamp' => null,
1603 $mockDb->expects( $this->once() )
1604 ->method( 'makeWhereFrom2d' )
1606 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1607 $this->isType( 'string' ),
1608 $this->isType( 'string' )
1610 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1611 $mockDb->expects( $this->once() )
1612 ->method( 'select' )
1615 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1617 'makeWhereFrom2d return value',
1620 $this->isType( 'string' )
1622 ->will( $this->returnValue( $dbResult ) );
1624 $mockCache = $this->getMockCache();
1625 $mockCache->expects( $this->exactly( 2 ) )
1628 [ '0:SomeDbKey:1' ],
1629 [ '1:AnotherDbKey:1' ]
1631 ->will( $this->returnValue( null ) );
1632 $mockCache->expects( $this->never() )->method( 'set' );
1633 $mockCache->expects( $this->never() )->method( 'delete' );
1635 $store = $this->newWatchedItemStore(
1636 $this->getMockLoadBalancer( $mockDb ),
1640 $this->assertEquals(
1642 0 => [ 'SomeDbKey' => '20151212010101', ],
1643 1 => [ 'AnotherDbKey' => null, ],
1645 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1649 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1651 new TitleValue( 0, 'OtherDbKey' ),
1654 $mockDb = $this->getMockDb();
1656 $mockDb->expects( $this->once() )
1657 ->method( 'makeWhereFrom2d' )
1659 [ [ 'OtherDbKey' => 1 ] ],
1660 $this->isType( 'string' ),
1661 $this->isType( 'string' )
1663 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1664 $mockDb->expects( $this->once() )
1665 ->method( 'select' )
1668 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1670 'makeWhereFrom2d return value',
1673 $this->isType( 'string' )
1675 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1677 $mockCache = $this->getMockCache();
1678 $mockCache->expects( $this->once() )
1680 ->with( '0:OtherDbKey:1' )
1681 ->will( $this->returnValue( null ) );
1682 $mockCache->expects( $this->never() )->method( 'set' );
1683 $mockCache->expects( $this->never() )->method( 'delete' );
1685 $store = $this->newWatchedItemStore(
1686 $this->getMockLoadBalancer( $mockDb ),
1690 $this->assertEquals(
1692 0 => [ 'OtherDbKey' => false, ],
1694 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1698 public function testGetNotificationTimestampsBatch_cachedItem() {
1700 new TitleValue( 0, 'SomeDbKey' ),
1701 new TitleValue( 1, 'AnotherDbKey' ),
1704 $user = $this->getMockNonAnonUserWithId( 1 );
1705 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1707 $mockDb = $this->getMockDb();
1709 $mockDb->expects( $this->once() )
1710 ->method( 'makeWhereFrom2d' )
1712 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1713 $this->isType( 'string' ),
1714 $this->isType( 'string' )
1716 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1717 $mockDb->expects( $this->once() )
1718 ->method( 'select' )
1721 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1723 'makeWhereFrom2d return value',
1726 $this->isType( 'string' )
1728 ->will( $this->returnValue( [
1730 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1734 $mockCache = $this->getMockCache();
1735 $mockCache->expects( $this->at( 1 ) )
1737 ->with( '0:SomeDbKey:1' )
1738 ->will( $this->returnValue( $cachedItem ) );
1739 $mockCache->expects( $this->at( 3 ) )
1741 ->with( '1:AnotherDbKey:1' )
1742 ->will( $this->returnValue( null ) );
1743 $mockCache->expects( $this->never() )->method( 'set' );
1744 $mockCache->expects( $this->never() )->method( 'delete' );
1746 $store = $this->newWatchedItemStore(
1747 $this->getMockLoadBalancer( $mockDb ),
1751 $this->assertEquals(
1753 0 => [ 'SomeDbKey' => '20151212010101', ],
1754 1 => [ 'AnotherDbKey' => null, ],
1756 $store->getNotificationTimestampsBatch( $user, $targets )
1760 public function testGetNotificationTimestampsBatch_allItemsCached() {
1762 new TitleValue( 0, 'SomeDbKey' ),
1763 new TitleValue( 1, 'AnotherDbKey' ),
1766 $user = $this->getMockNonAnonUserWithId( 1 );
1768 new WatchedItem( $user, $targets[0], '20151212010101' ),
1769 new WatchedItem( $user, $targets[1], null ),
1771 $mockDb = $this->getMockDb();
1772 $mockDb->expects( $this->never() )->method( $this->anything() );
1774 $mockCache = $this->getMockCache();
1775 $mockCache->expects( $this->at( 1 ) )
1777 ->with( '0:SomeDbKey:1' )
1778 ->will( $this->returnValue( $cachedItems[0] ) );
1779 $mockCache->expects( $this->at( 3 ) )
1781 ->with( '1:AnotherDbKey:1' )
1782 ->will( $this->returnValue( $cachedItems[1] ) );
1783 $mockCache->expects( $this->never() )->method( 'set' );
1784 $mockCache->expects( $this->never() )->method( 'delete' );
1786 $store = $this->newWatchedItemStore(
1787 $this->getMockLoadBalancer( $mockDb ),
1791 $this->assertEquals(
1793 0 => [ 'SomeDbKey' => '20151212010101', ],
1794 1 => [ 'AnotherDbKey' => null, ],
1796 $store->getNotificationTimestampsBatch( $user, $targets )
1800 public function testGetNotificationTimestampsBatch_anonymousUser() {
1802 new TitleValue( 0, 'SomeDbKey' ),
1803 new TitleValue( 1, 'AnotherDbKey' ),
1806 $mockDb = $this->getMockDb();
1807 $mockDb->expects( $this->never() )->method( $this->anything() );
1809 $mockCache = $this->getMockCache();
1810 $mockCache->expects( $this->never() )->method( $this->anything() );
1812 $store = $this->newWatchedItemStore(
1813 $this->getMockLoadBalancer( $mockDb ),
1817 $this->assertEquals(
1819 0 => [ 'SomeDbKey' => false, ],
1820 1 => [ 'AnotherDbKey' => false, ],
1822 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1826 public function testResetNotificationTimestamp_anonymousUser() {
1827 $mockDb = $this->getMockDb();
1828 $mockDb->expects( $this->never() )
1829 ->method( 'selectRow' );
1831 $mockCache = $this->getMockCache();
1832 $mockCache->expects( $this->never() )->method( 'get' );
1833 $mockCache->expects( $this->never() )->method( 'set' );
1834 $mockCache->expects( $this->never() )->method( 'delete' );
1836 $store = $this->newWatchedItemStore(
1837 $this->getMockLoadBalancer( $mockDb ),
1842 $store->resetNotificationTimestamp(
1843 $this->getAnonUser(),
1844 Title
::newFromText( 'SomeDbKey' )
1849 public function testResetNotificationTimestamp_noItem() {
1850 $mockDb = $this->getMockDb();
1851 $mockDb->expects( $this->once() )
1852 ->method( 'selectRow' )
1855 'wl_notificationtimestamp',
1858 'wl_namespace' => 0,
1859 'wl_title' => 'SomeDbKey',
1862 ->will( $this->returnValue( [] ) );
1864 $mockCache = $this->getMockCache();
1865 $mockCache->expects( $this->never() )->method( 'get' );
1866 $mockCache->expects( $this->never() )->method( 'set' );
1867 $mockCache->expects( $this->never() )->method( 'delete' );
1869 $store = $this->newWatchedItemStore(
1870 $this->getMockLoadBalancer( $mockDb ),
1875 $store->resetNotificationTimestamp(
1876 $this->getMockNonAnonUserWithId( 1 ),
1877 Title
::newFromText( 'SomeDbKey' )
1882 public function testResetNotificationTimestamp_item() {
1883 $user = $this->getMockNonAnonUserWithId( 1 );
1884 $title = Title
::newFromText( 'SomeDbKey' );
1886 $mockDb = $this->getMockDb();
1887 $mockDb->expects( $this->once() )
1888 ->method( 'selectRow' )
1891 'wl_notificationtimestamp',
1894 'wl_namespace' => 0,
1895 'wl_title' => 'SomeDbKey',
1898 ->will( $this->returnValue(
1899 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1902 $mockCache = $this->getMockCache();
1903 $mockCache->expects( $this->never() )->method( 'get' );
1904 $mockCache->expects( $this->once() )
1908 $this->isInstanceOf( WatchedItem
::class )
1910 $mockCache->expects( $this->once() )
1911 ->method( 'delete' )
1912 ->with( '0:SomeDbKey:1' );
1914 $store = $this->newWatchedItemStore(
1915 $this->getMockLoadBalancer( $mockDb ),
1919 // Note: This does not actually assert the job is correct
1920 $callableCallCounter = 0;
1921 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1922 $callableCallCounter++
;
1923 $this->assertInternalType( 'callable', $callable );
1925 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1928 $store->resetNotificationTimestamp(
1933 $this->assertEquals( 1, $callableCallCounter );
1935 ScopedCallback
::consume( $scopedOverride );
1938 public function testResetNotificationTimestamp_noItemForced() {
1939 $user = $this->getMockNonAnonUserWithId( 1 );
1940 $title = Title
::newFromText( 'SomeDbKey' );
1942 $mockDb = $this->getMockDb();
1943 $mockDb->expects( $this->never() )
1944 ->method( 'selectRow' );
1946 $mockCache = $this->getMockCache();
1947 $mockDb->expects( $this->never() )
1949 $mockDb->expects( $this->never() )
1951 $mockDb->expects( $this->never() )
1952 ->method( 'delete' );
1954 $store = $this->newWatchedItemStore(
1955 $this->getMockLoadBalancer( $mockDb ),
1959 // Note: This does not actually assert the job is correct
1960 $callableCallCounter = 0;
1961 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1962 $callableCallCounter++
;
1963 $this->assertInternalType( 'callable', $callable );
1965 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1968 $store->resetNotificationTimestamp(
1974 $this->assertEquals( 1, $callableCallCounter );
1976 ScopedCallback
::consume( $scopedOverride );
1983 * @return PHPUnit_Framework_MockObject_MockObject|Title
1985 private function getMockTitle( $text, $ns = 0 ) {
1986 $title = $this->getMock( Title
::class );
1987 $title->expects( $this->any() )
1988 ->method( 'getText' )
1989 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1990 $title->expects( $this->any() )
1991 ->method( 'getDbKey' )
1992 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1993 $title->expects( $this->any() )
1994 ->method( 'getNamespace' )
1995 ->will( $this->returnValue( $ns ) );
1999 private function verifyCallbackJob(
2001 LinkTarget
$expectedTitle,
2003 callable
$notificationTimestampCondition
2005 $this->assertInternalType( 'callable', $callback );
2007 $callbackReflector = new ReflectionFunction( $callback );
2008 $vars = $callbackReflector->getStaticVariables();
2009 $this->assertArrayHasKey( 'job', $vars );
2010 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2012 /** @var ActivityUpdateJob $job */
2013 $job = $vars['job'];
2014 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2015 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2017 $jobParams = $job->getParams();
2018 $this->assertArrayHasKey( 'type', $jobParams );
2019 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2020 $this->assertArrayHasKey( 'userid', $jobParams );
2021 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2022 $this->assertArrayHasKey( 'notifTime', $jobParams );
2023 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2026 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2027 $user = $this->getMockNonAnonUserWithId( 1 );
2029 $title = $this->getMockTitle( 'SomeTitle' );
2030 $title->expects( $this->once() )
2031 ->method( 'getNextRevisionID' )
2033 ->will( $this->returnValue( false ) );
2035 $mockDb = $this->getMockDb();
2036 $mockDb->expects( $this->never() )
2037 ->method( 'selectRow' );
2039 $mockCache = $this->getMockCache();
2040 $mockDb->expects( $this->never() )
2042 $mockDb->expects( $this->never() )
2044 $mockDb->expects( $this->never() )
2045 ->method( 'delete' );
2047 $store = $this->newWatchedItemStore(
2048 $this->getMockLoadBalancer( $mockDb ),
2052 $callableCallCounter = 0;
2053 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2054 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2055 $callableCallCounter++
;
2056 $this->verifyCallbackJob(
2061 return $time === null;
2068 $store->resetNotificationTimestamp(
2075 $this->assertEquals( 1, $callableCallCounter );
2077 ScopedCallback
::consume( $scopedOverride );
2080 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2081 $user = $this->getMockNonAnonUserWithId( 1 );
2083 $title = $this->getMockTitle( 'SomeDbKey' );
2084 $title->expects( $this->once() )
2085 ->method( 'getNextRevisionID' )
2087 ->will( $this->returnValue( 33 ) );
2089 $mockDb = $this->getMockDb();
2090 $mockDb->expects( $this->once() )
2091 ->method( 'selectRow' )
2094 'wl_notificationtimestamp',
2097 'wl_namespace' => 0,
2098 'wl_title' => 'SomeDbKey',
2101 ->will( $this->returnValue(
2102 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2105 $mockCache = $this->getMockCache();
2106 $mockDb->expects( $this->never() )
2108 $mockDb->expects( $this->never() )
2110 $mockDb->expects( $this->never() )
2111 ->method( 'delete' );
2113 $store = $this->newWatchedItemStore(
2114 $this->getMockLoadBalancer( $mockDb ),
2118 $addUpdateCallCounter = 0;
2119 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2120 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2121 $addUpdateCallCounter++
;
2122 $this->verifyCallbackJob(
2127 return $time !== null && $time > '20151212010101';
2133 $getTimestampCallCounter = 0;
2134 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2135 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2136 $getTimestampCallCounter++
;
2137 $this->assertEquals( $title, $titleParam );
2138 $this->assertEquals( $oldid, $oldidParam );
2143 $store->resetNotificationTimestamp(
2150 $this->assertEquals( 1, $addUpdateCallCounter );
2151 $this->assertEquals( 1, $getTimestampCallCounter );
2153 ScopedCallback
::consume( $scopedOverrideDeferred );
2154 ScopedCallback
::consume( $scopedOverrideRevision );
2157 public function testResetNotificationTimestamp_notWatchedPageForced() {
2158 $user = $this->getMockNonAnonUserWithId( 1 );
2160 $title = $this->getMockTitle( 'SomeDbKey' );
2161 $title->expects( $this->once() )
2162 ->method( 'getNextRevisionID' )
2164 ->will( $this->returnValue( 33 ) );
2166 $mockDb = $this->getMockDb();
2167 $mockDb->expects( $this->once() )
2168 ->method( 'selectRow' )
2171 'wl_notificationtimestamp',
2174 'wl_namespace' => 0,
2175 'wl_title' => 'SomeDbKey',
2178 ->will( $this->returnValue( false ) );
2180 $mockCache = $this->getMockCache();
2181 $mockDb->expects( $this->never() )
2183 $mockDb->expects( $this->never() )
2185 $mockDb->expects( $this->never() )
2186 ->method( 'delete' );
2188 $store = $this->newWatchedItemStore(
2189 $this->getMockLoadBalancer( $mockDb ),
2193 $callableCallCounter = 0;
2194 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2195 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2196 $callableCallCounter++
;
2197 $this->verifyCallbackJob(
2202 return $time === null;
2209 $store->resetNotificationTimestamp(
2216 $this->assertEquals( 1, $callableCallCounter );
2218 ScopedCallback
::consume( $scopedOverride );
2221 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2222 $user = $this->getMockNonAnonUserWithId( 1 );
2224 $title = $this->getMockTitle( 'SomeDbKey' );
2225 $title->expects( $this->once() )
2226 ->method( 'getNextRevisionID' )
2228 ->will( $this->returnValue( 33 ) );
2230 $mockDb = $this->getMockDb();
2231 $mockDb->expects( $this->once() )
2232 ->method( 'selectRow' )
2235 'wl_notificationtimestamp',
2238 'wl_namespace' => 0,
2239 'wl_title' => 'SomeDbKey',
2242 ->will( $this->returnValue(
2243 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2246 $mockCache = $this->getMockCache();
2247 $mockDb->expects( $this->never() )
2249 $mockDb->expects( $this->never() )
2251 $mockDb->expects( $this->never() )
2252 ->method( 'delete' );
2254 $store = $this->newWatchedItemStore(
2255 $this->getMockLoadBalancer( $mockDb ),
2259 $addUpdateCallCounter = 0;
2260 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2261 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2262 $addUpdateCallCounter++
;
2263 $this->verifyCallbackJob(
2268 return $time === '30151212010101';
2274 $getTimestampCallCounter = 0;
2275 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2276 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2277 $getTimestampCallCounter++
;
2278 $this->assertEquals( $title, $titleParam );
2279 $this->assertEquals( $oldid, $oldidParam );
2284 $store->resetNotificationTimestamp(
2291 $this->assertEquals( 1, $addUpdateCallCounter );
2292 $this->assertEquals( 1, $getTimestampCallCounter );
2294 ScopedCallback
::consume( $scopedOverrideDeferred );
2295 ScopedCallback
::consume( $scopedOverrideRevision );
2298 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2299 $user = $this->getMockNonAnonUserWithId( 1 );
2301 $title = $this->getMockTitle( 'SomeDbKey' );
2302 $title->expects( $this->once() )
2303 ->method( 'getNextRevisionID' )
2305 ->will( $this->returnValue( 33 ) );
2307 $mockDb = $this->getMockDb();
2308 $mockDb->expects( $this->once() )
2309 ->method( 'selectRow' )
2312 'wl_notificationtimestamp',
2315 'wl_namespace' => 0,
2316 'wl_title' => 'SomeDbKey',
2319 ->will( $this->returnValue(
2320 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2323 $mockCache = $this->getMockCache();
2324 $mockDb->expects( $this->never() )
2326 $mockDb->expects( $this->never() )
2328 $mockDb->expects( $this->never() )
2329 ->method( 'delete' );
2331 $store = $this->newWatchedItemStore(
2332 $this->getMockLoadBalancer( $mockDb ),
2336 $addUpdateCallCounter = 0;
2337 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2338 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2339 $addUpdateCallCounter++
;
2340 $this->verifyCallbackJob(
2345 return $time === false;
2351 $getTimestampCallCounter = 0;
2352 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2353 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2354 $getTimestampCallCounter++
;
2355 $this->assertEquals( $title, $titleParam );
2356 $this->assertEquals( $oldid, $oldidParam );
2361 $store->resetNotificationTimestamp(
2368 $this->assertEquals( 1, $addUpdateCallCounter );
2369 $this->assertEquals( 1, $getTimestampCallCounter );
2371 ScopedCallback
::consume( $scopedOverrideDeferred );
2372 ScopedCallback
::consume( $scopedOverrideRevision );
2375 public function testUpdateNotificationTimestamp_watchersExist() {
2376 $mockDb = $this->getMockDb();
2377 $mockDb->expects( $this->once() )
2378 ->method( 'select' )
2384 'wl_namespace' => 0,
2385 'wl_title' => 'SomeDbKey',
2386 'wl_notificationtimestamp IS NULL'
2390 $this->returnValue( [
2391 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2392 $this->getFakeRow( [ 'wl_user' => '3' ] )
2395 $mockDb->expects( $this->once() )
2396 ->method( 'onTransactionIdle' )
2397 ->with( $this->isType( 'callable' ) )
2398 ->will( $this->returnCallback( function( $callable ) {
2401 $mockDb->expects( $this->once() )
2402 ->method( 'update' )
2405 [ 'wl_notificationtimestamp' => null ],
2407 'wl_user' => [ 2, 3 ],
2408 'wl_namespace' => 0,
2409 'wl_title' => 'SomeDbKey',
2413 $mockCache = $this->getMockCache();
2414 $mockCache->expects( $this->never() )->method( 'set' );
2415 $mockCache->expects( $this->never() )->method( 'get' );
2416 $mockCache->expects( $this->never() )->method( 'delete' );
2418 $store = $this->newWatchedItemStore(
2419 $this->getMockLoadBalancer( $mockDb ),
2423 $this->assertEquals(
2425 $store->updateNotificationTimestamp(
2426 $this->getMockNonAnonUserWithId( 1 ),
2427 new TitleValue( 0, 'SomeDbKey' ),
2433 public function testUpdateNotificationTimestamp_noWatchers() {
2434 $mockDb = $this->getMockDb();
2435 $mockDb->expects( $this->once() )
2436 ->method( 'select' )
2442 'wl_namespace' => 0,
2443 'wl_title' => 'SomeDbKey',
2444 'wl_notificationtimestamp IS NULL'
2448 $this->returnValue( [] )
2450 $mockDb->expects( $this->never() )
2451 ->method( 'onTransactionIdle' );
2452 $mockDb->expects( $this->never() )
2453 ->method( 'update' );
2455 $mockCache = $this->getMockCache();
2456 $mockCache->expects( $this->never() )->method( 'set' );
2457 $mockCache->expects( $this->never() )->method( 'get' );
2458 $mockCache->expects( $this->never() )->method( 'delete' );
2460 $store = $this->newWatchedItemStore(
2461 $this->getMockLoadBalancer( $mockDb ),
2465 $watchers = $store->updateNotificationTimestamp(
2466 $this->getMockNonAnonUserWithId( 1 ),
2467 new TitleValue( 0, 'SomeDbKey' ),
2470 $this->assertInternalType( 'array', $watchers );
2471 $this->assertEmpty( $watchers );
2474 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2475 $user = $this->getMockNonAnonUserWithId( 1 );
2476 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2478 $mockDb = $this->getMockDb();
2479 $mockDb->expects( $this->once() )
2480 ->method( 'selectRow' )
2481 ->will( $this->returnValue(
2482 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2484 $mockDb->expects( $this->once() )
2485 ->method( 'select' )
2487 $this->returnValue( [
2488 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2489 $this->getFakeRow( [ 'wl_user' => '3' ] )
2492 $mockDb->expects( $this->once() )
2493 ->method( 'onTransactionIdle' )
2494 ->with( $this->isType( 'callable' ) )
2495 ->will( $this->returnCallback( function( $callable ) {
2498 $mockDb->expects( $this->once() )
2499 ->method( 'update' );
2501 $mockCache = $this->getMockCache();
2502 $mockCache->expects( $this->once() )
2504 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2505 $mockCache->expects( $this->once() )
2507 ->with( '0:SomeDbKey:1' );
2508 $mockCache->expects( $this->once() )
2509 ->method( 'delete' )
2510 ->with( '0:SomeDbKey:1' );
2512 $store = $this->newWatchedItemStore(
2513 $this->getMockLoadBalancer( $mockDb ),
2517 // This will add the item to the cache
2518 $store->getWatchedItem( $user, $titleValue );
2520 $store->updateNotificationTimestamp(
2521 $this->getMockNonAnonUserWithId( 1 ),