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( 'getConnectionRef' )
32 ->with( $expectedConnectionType )
33 ->will( $this->returnValue( $mockDb ) );
35 $mock->expects( $this->any() )
36 ->method( 'getConnectionRef' )
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 testCountWatchedItems() {
98 $user = $this->getMockNonAnonUserWithId( 1 );
100 $mockDb = $this->getMockDb();
101 $mockDb->expects( $this->exactly( 1 ) )
102 ->method( 'selectField' )
107 'wl_user' => $user->getId(),
109 $this->isType( 'string' )
111 ->will( $this->returnValue( 12 ) );
113 $mockCache = $this->getMockCache();
114 $mockCache->expects( $this->never() )->method( 'get' );
115 $mockCache->expects( $this->never() )->method( 'set' );
116 $mockCache->expects( $this->never() )->method( 'delete' );
118 $store = $this->newWatchedItemStore(
119 $this->getMockLoadBalancer( $mockDb ),
123 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
126 public function testCountWatchers() {
127 $titleValue = new TitleValue( 0, 'SomeDbKey' );
129 $mockDb = $this->getMockDb();
130 $mockDb->expects( $this->exactly( 1 ) )
131 ->method( 'selectField' )
136 'wl_namespace' => $titleValue->getNamespace(),
137 'wl_title' => $titleValue->getDBkey(),
139 $this->isType( 'string' )
141 ->will( $this->returnValue( 7 ) );
143 $mockCache = $this->getMockCache();
144 $mockCache->expects( $this->never() )->method( 'get' );
145 $mockCache->expects( $this->never() )->method( 'set' );
146 $mockCache->expects( $this->never() )->method( 'delete' );
148 $store = $this->newWatchedItemStore(
149 $this->getMockLoadBalancer( $mockDb ),
153 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
156 public function testCountWatchersMultiple() {
158 new TitleValue( 0, 'SomeDbKey' ),
159 new TitleValue( 0, 'OtherDbKey' ),
160 new TitleValue( 1, 'AnotherDbKey' ),
163 $mockDb = $this->getMockDb();
166 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
167 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
168 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
171 $mockDb->expects( $this->once() )
172 ->method( 'makeWhereFrom2d' )
174 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
175 $this->isType( 'string' ),
176 $this->isType( 'string' )
178 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
179 $mockDb->expects( $this->once() )
183 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
184 [ 'makeWhereFrom2d return value' ],
185 $this->isType( 'string' ),
187 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
191 $this->returnValue( $dbResult )
194 $mockCache = $this->getMockCache();
195 $mockCache->expects( $this->never() )->method( 'get' );
196 $mockCache->expects( $this->never() )->method( 'set' );
197 $mockCache->expects( $this->never() )->method( 'delete' );
199 $store = $this->newWatchedItemStore(
200 $this->getMockLoadBalancer( $mockDb ),
205 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
206 1 => [ 'AnotherDbKey' => 500 ],
208 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
211 public function provideIntWithDbUnsafeVersion() {
214 [ "50; DROP TABLE watchlist;\n--" ],
219 * @dataProvider provideIntWithDbUnsafeVersion
221 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
223 new TitleValue( 0, 'SomeDbKey' ),
224 new TitleValue( 0, 'OtherDbKey' ),
225 new TitleValue( 1, 'AnotherDbKey' ),
228 $mockDb = $this->getMockDb();
231 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
232 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
233 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
236 $mockDb->expects( $this->once() )
237 ->method( 'makeWhereFrom2d' )
239 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
240 $this->isType( 'string' ),
241 $this->isType( 'string' )
243 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
244 $mockDb->expects( $this->once() )
248 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
249 [ 'makeWhereFrom2d return value' ],
250 $this->isType( 'string' ),
252 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
253 'HAVING' => 'COUNT(*) >= 50',
257 $this->returnValue( $dbResult )
260 $mockCache = $this->getMockCache();
261 $mockCache->expects( $this->never() )->method( 'get' );
262 $mockCache->expects( $this->never() )->method( 'set' );
263 $mockCache->expects( $this->never() )->method( 'delete' );
265 $store = $this->newWatchedItemStore(
266 $this->getMockLoadBalancer( $mockDb ),
271 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
272 1 => [ 'AnotherDbKey' => 500 ],
276 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
280 public function testCountVisitingWatchers() {
281 $titleValue = new TitleValue( 0, 'SomeDbKey' );
283 $mockDb = $this->getMockDb();
284 $mockDb->expects( $this->exactly( 1 ) )
285 ->method( 'selectField' )
290 'wl_namespace' => $titleValue->getNamespace(),
291 'wl_title' => $titleValue->getDBkey(),
292 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
294 $this->isType( 'string' )
296 ->will( $this->returnValue( 7 ) );
297 $mockDb->expects( $this->exactly( 1 ) )
298 ->method( 'addQuotes' )
299 ->will( $this->returnCallback( function( $value ) {
302 $mockDb->expects( $this->exactly( 1 ) )
303 ->method( 'timestamp' )
304 ->will( $this->returnCallback( function( $value ) {
305 return 'TS' . $value . 'TS';
308 $mockCache = $this->getMockCache();
309 $mockCache->expects( $this->never() )->method( 'set' );
310 $mockCache->expects( $this->never() )->method( 'get' );
311 $mockCache->expects( $this->never() )->method( 'delete' );
313 $store = $this->newWatchedItemStore(
314 $this->getMockLoadBalancer( $mockDb ),
318 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
321 public function testCountVisitingWatchersMultiple() {
322 $titleValuesWithThresholds = [
323 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
324 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
325 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
329 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
330 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
331 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
333 $mockDb = $this->getMockDb();
334 $mockDb->expects( $this->exactly( 2 * 3 ) )
335 ->method( 'addQuotes' )
336 ->will( $this->returnCallback( function( $value ) {
339 $mockDb->expects( $this->exactly( 3 ) )
340 ->method( 'timestamp' )
341 ->will( $this->returnCallback( function( $value ) {
342 return 'TS' . $value . 'TS';
344 $mockDb->expects( $this->any() )
345 ->method( 'makeList' )
347 $this->isType( 'array' ),
348 $this->isType( 'int' )
350 ->will( $this->returnCallback( function( $a, $conj ) {
351 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
352 return join( $sqlConj, array_map( function( $s ) {
353 return '(' . $s . ')';
357 $mockDb->expects( $this->never() )
358 ->method( 'makeWhereFrom2d' );
361 '((wl_namespace = 0) AND (' .
362 "(((wl_title = 'SomeDbKey') AND (" .
363 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
365 "(wl_title = 'OtherDbKey') AND (" .
366 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
368 ') OR ((wl_namespace = 1) AND (' .
369 "(((wl_title = 'AnotherDbKey') AND (".
370 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
372 $mockDb->expects( $this->once() )
376 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
378 $this->isType( 'string' ),
380 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
384 $this->returnValue( $dbResult )
387 $mockCache = $this->getMockCache();
388 $mockCache->expects( $this->never() )->method( 'get' );
389 $mockCache->expects( $this->never() )->method( 'set' );
390 $mockCache->expects( $this->never() )->method( 'delete' );
392 $store = $this->newWatchedItemStore(
393 $this->getMockLoadBalancer( $mockDb ),
398 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
399 1 => [ 'AnotherDbKey' => 500 ],
403 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
407 public function testCountVisitingWatchersMultiple_withMissingTargets() {
408 $titleValuesWithThresholds = [
409 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
410 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
411 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
412 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
413 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
417 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
418 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
419 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
421 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
424 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
427 $mockDb = $this->getMockDb();
428 $mockDb->expects( $this->exactly( 2 * 3 ) )
429 ->method( 'addQuotes' )
430 ->will( $this->returnCallback( function( $value ) {
433 $mockDb->expects( $this->exactly( 3 ) )
434 ->method( 'timestamp' )
435 ->will( $this->returnCallback( function( $value ) {
436 return 'TS' . $value . 'TS';
438 $mockDb->expects( $this->any() )
439 ->method( 'makeList' )
441 $this->isType( 'array' ),
442 $this->isType( 'int' )
444 ->will( $this->returnCallback( function( $a, $conj ) {
445 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
446 return join( $sqlConj, array_map( function( $s ) {
447 return '(' . $s . ')';
451 $mockDb->expects( $this->once() )
452 ->method( 'makeWhereFrom2d' )
454 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
455 $this->isType( 'string' ),
456 $this->isType( 'string' )
458 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
461 '((wl_namespace = 0) AND (' .
462 "(((wl_title = 'SomeDbKey') AND (" .
463 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
465 "(wl_title = 'OtherDbKey') AND (" .
466 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
468 ') OR ((wl_namespace = 1) AND (' .
469 "(((wl_title = 'AnotherDbKey') AND (".
470 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
473 '(makeWhereFrom2d return value)';
474 $mockDb->expects( $this->once() )
478 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
480 $this->isType( 'string' ),
482 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
486 $this->returnValue( $dbResult )
489 $mockCache = $this->getMockCache();
490 $mockCache->expects( $this->never() )->method( 'get' );
491 $mockCache->expects( $this->never() )->method( 'set' );
492 $mockCache->expects( $this->never() )->method( 'delete' );
494 $store = $this->newWatchedItemStore(
495 $this->getMockLoadBalancer( $mockDb ),
501 'SomeDbKey' => 100, 'OtherDbKey' => 300,
502 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
504 1 => [ 'AnotherDbKey' => 500 ],
508 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
513 * @dataProvider provideIntWithDbUnsafeVersion
515 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
516 $titleValuesWithThresholds = [
517 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
518 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
519 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
522 $mockDb = $this->getMockDb();
523 $mockDb->expects( $this->any() )
524 ->method( 'makeList' )
525 ->will( $this->returnValue( 'makeList return value' ) );
526 $mockDb->expects( $this->once() )
530 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
531 'makeList return value',
532 $this->isType( 'string' ),
534 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
535 'HAVING' => 'COUNT(*) >= 50',
539 $this->returnValue( [] )
542 $mockCache = $this->getMockCache();
543 $mockCache->expects( $this->never() )->method( 'get' );
544 $mockCache->expects( $this->never() )->method( 'set' );
545 $mockCache->expects( $this->never() )->method( 'delete' );
547 $store = $this->newWatchedItemStore(
548 $this->getMockLoadBalancer( $mockDb ),
553 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
554 1 => [ 'AnotherDbKey' => 0 ],
558 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
562 public function testCountUnreadNotifications() {
563 $user = $this->getMockNonAnonUserWithId( 1 );
565 $mockDb = $this->getMockDb();
566 $mockDb->expects( $this->exactly( 1 ) )
567 ->method( 'selectRowCount' )
572 "wl_notificationtimestamp IS NOT NULL",
575 $this->isType( 'string' )
577 ->will( $this->returnValue( 9 ) );
579 $mockCache = $this->getMockCache();
580 $mockCache->expects( $this->never() )->method( 'set' );
581 $mockCache->expects( $this->never() )->method( 'get' );
582 $mockCache->expects( $this->never() )->method( 'delete' );
584 $store = $this->newWatchedItemStore(
585 $this->getMockLoadBalancer( $mockDb ),
589 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
593 * @dataProvider provideIntWithDbUnsafeVersion
595 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
596 $user = $this->getMockNonAnonUserWithId( 1 );
598 $mockDb = $this->getMockDb();
599 $mockDb->expects( $this->exactly( 1 ) )
600 ->method( 'selectRowCount' )
605 "wl_notificationtimestamp IS NOT NULL",
608 $this->isType( 'string' ),
611 ->will( $this->returnValue( 50 ) );
613 $mockCache = $this->getMockCache();
614 $mockCache->expects( $this->never() )->method( 'set' );
615 $mockCache->expects( $this->never() )->method( 'get' );
616 $mockCache->expects( $this->never() )->method( 'delete' );
618 $store = $this->newWatchedItemStore(
619 $this->getMockLoadBalancer( $mockDb ),
625 $store->countUnreadNotifications( $user, $limit )
630 * @dataProvider provideIntWithDbUnsafeVersion
632 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
633 $user = $this->getMockNonAnonUserWithId( 1 );
635 $mockDb = $this->getMockDb();
636 $mockDb->expects( $this->exactly( 1 ) )
637 ->method( 'selectRowCount' )
642 "wl_notificationtimestamp IS NOT NULL",
645 $this->isType( 'string' ),
648 ->will( $this->returnValue( 9 ) );
650 $mockCache = $this->getMockCache();
651 $mockCache->expects( $this->never() )->method( 'set' );
652 $mockCache->expects( $this->never() )->method( 'get' );
653 $mockCache->expects( $this->never() )->method( 'delete' );
655 $store = $this->newWatchedItemStore(
656 $this->getMockLoadBalancer( $mockDb ),
662 $store->countUnreadNotifications( $user, $limit )
666 public function testDuplicateEntry_nothingToDuplicate() {
667 $mockDb = $this->getMockDb();
668 $mockDb->expects( $this->once() )
674 'wl_notificationtimestamp',
678 'wl_title' => 'Old_Title',
680 'WatchedItemStore::duplicateEntry',
683 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
685 $store = $this->newWatchedItemStore(
686 $this->getMockLoadBalancer( $mockDb ),
687 $this->getMockCache()
690 $store->duplicateEntry(
691 Title
::newFromText( 'Old_Title' ),
692 Title
::newFromText( 'New_Title' )
696 public function testDuplicateEntry_somethingToDuplicate() {
698 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
699 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
702 $mockDb = $this->getMockDb();
703 $mockDb->expects( $this->at( 0 ) )
709 'wl_notificationtimestamp',
713 'wl_title' => 'Old_Title',
716 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
717 $mockDb->expects( $this->at( 1 ) )
718 ->method( 'replace' )
721 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
726 'wl_title' => 'New_Title',
727 'wl_notificationtimestamp' => '20151212010101',
732 'wl_title' => 'New_Title',
733 'wl_notificationtimestamp' => null,
736 $this->isType( 'string' )
739 $mockCache = $this->getMockCache();
740 $mockCache->expects( $this->never() )->method( 'get' );
741 $mockCache->expects( $this->never() )->method( 'delete' );
743 $store = $this->newWatchedItemStore(
744 $this->getMockLoadBalancer( $mockDb ),
748 $store->duplicateEntry(
749 Title
::newFromText( 'Old_Title' ),
750 Title
::newFromText( 'New_Title' )
754 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
755 $mockDb = $this->getMockDb();
756 $mockDb->expects( $this->at( 0 ) )
762 'wl_notificationtimestamp',
766 'wl_title' => 'Old_Title',
769 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
770 $mockDb->expects( $this->at( 1 ) )
776 'wl_notificationtimestamp',
780 'wl_title' => 'Old_Title',
783 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
785 $mockCache = $this->getMockCache();
786 $mockCache->expects( $this->never() )->method( 'get' );
787 $mockCache->expects( $this->never() )->method( 'delete' );
789 $store = $this->newWatchedItemStore(
790 $this->getMockLoadBalancer( $mockDb ),
794 $store->duplicateAllAssociatedEntries(
795 Title
::newFromText( 'Old_Title' ),
796 Title
::newFromText( 'New_Title' )
800 public function provideLinkTargetPairs() {
802 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
803 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
808 * @dataProvider provideLinkTargetPairs
810 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
811 LinkTarget
$oldTarget,
812 LinkTarget
$newTarget
815 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
818 $mockDb = $this->getMockDb();
819 $mockDb->expects( $this->at( 0 ) )
825 'wl_notificationtimestamp',
828 'wl_namespace' => $oldTarget->getNamespace(),
829 'wl_title' => $oldTarget->getDBkey(),
832 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
833 $mockDb->expects( $this->at( 1 ) )
834 ->method( 'replace' )
837 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
841 'wl_namespace' => $newTarget->getNamespace(),
842 'wl_title' => $newTarget->getDBkey(),
843 'wl_notificationtimestamp' => '20151212010101',
846 $this->isType( 'string' )
848 $mockDb->expects( $this->at( 2 ) )
854 'wl_notificationtimestamp',
857 'wl_namespace' => $oldTarget->getNamespace() +
1,
858 'wl_title' => $oldTarget->getDBkey(),
861 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
862 $mockDb->expects( $this->at( 3 ) )
863 ->method( 'replace' )
866 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
870 'wl_namespace' => $newTarget->getNamespace() +
1,
871 'wl_title' => $newTarget->getDBkey(),
872 'wl_notificationtimestamp' => '20151212010101',
875 $this->isType( 'string' )
878 $mockCache = $this->getMockCache();
879 $mockCache->expects( $this->never() )->method( 'get' );
880 $mockCache->expects( $this->never() )->method( 'delete' );
882 $store = $this->newWatchedItemStore(
883 $this->getMockLoadBalancer( $mockDb ),
887 $store->duplicateAllAssociatedEntries(
893 public function testAddWatch_nonAnonymousUser() {
894 $mockDb = $this->getMockDb();
895 $mockDb->expects( $this->once() )
903 'wl_title' => 'Some_Page',
904 'wl_notificationtimestamp' => null,
909 $mockCache = $this->getMockCache();
910 $mockCache->expects( $this->once() )
912 ->with( '0:Some_Page:1' );
914 $store = $this->newWatchedItemStore(
915 $this->getMockLoadBalancer( $mockDb ),
920 $this->getMockNonAnonUserWithId( 1 ),
921 Title
::newFromText( 'Some_Page' )
925 public function testAddWatch_anonymousUser() {
926 $mockDb = $this->getMockDb();
927 $mockDb->expects( $this->never() )
928 ->method( 'insert' );
930 $mockCache = $this->getMockCache();
931 $mockCache->expects( $this->never() )
932 ->method( 'delete' );
934 $store = $this->newWatchedItemStore(
935 $this->getMockLoadBalancer( $mockDb ),
940 $this->getAnonUser(),
941 Title
::newFromText( 'Some_Page' )
945 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
946 $store = $this->newWatchedItemStore(
947 $this->getMockLoadBalancer( $this->getMockDb(), null, 'Some Reason' ),
948 $this->getMockCache()
952 $store->addWatchBatchForUser(
953 $this->getMockNonAnonUserWithId( 1 ),
954 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
959 public function testAddWatchBatchForUser_nonAnonymousUser() {
960 $mockDb = $this->getMockDb();
961 $mockDb->expects( $this->once() )
969 'wl_title' => 'Some_Page',
970 'wl_notificationtimestamp' => null,
975 'wl_title' => 'Some_Page',
976 'wl_notificationtimestamp' => null,
981 $mockCache = $this->getMockCache();
982 $mockCache->expects( $this->exactly( 2 ) )
983 ->method( 'delete' );
984 $mockCache->expects( $this->at( 1 ) )
986 ->with( '0:Some_Page:1' );
987 $mockCache->expects( $this->at( 3 ) )
989 ->with( '1:Some_Page:1' );
991 $store = $this->newWatchedItemStore(
992 $this->getMockLoadBalancer( $mockDb ),
996 $mockUser = $this->getMockNonAnonUserWithId( 1 );
999 $store->addWatchBatchForUser(
1001 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1006 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1007 $mockDb = $this->getMockDb();
1008 $mockDb->expects( $this->never() )
1009 ->method( 'insert' );
1011 $mockCache = $this->getMockCache();
1012 $mockCache->expects( $this->never() )
1013 ->method( 'delete' );
1015 $store = $this->newWatchedItemStore(
1016 $this->getMockLoadBalancer( $mockDb ),
1021 $store->addWatchBatchForUser(
1022 $this->getAnonUser(),
1023 [ new TitleValue( 0, 'Other_Page' ) ]
1028 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1029 $user = $this->getMockNonAnonUserWithId( 1 );
1030 $mockDb = $this->getMockDb();
1031 $mockDb->expects( $this->never() )
1032 ->method( 'insert' );
1034 $mockCache = $this->getMockCache();
1035 $mockCache->expects( $this->never() )
1036 ->method( 'delete' );
1038 $store = $this->newWatchedItemStore(
1039 $this->getMockLoadBalancer( $mockDb ),
1044 $store->addWatchBatchForUser( $user, [] )
1048 public function testLoadWatchedItem_existingItem() {
1049 $mockDb = $this->getMockDb();
1050 $mockDb->expects( $this->once() )
1051 ->method( 'selectRow' )
1054 'wl_notificationtimestamp',
1057 'wl_namespace' => 0,
1058 'wl_title' => 'SomeDbKey',
1061 ->will( $this->returnValue(
1062 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1065 $mockCache = $this->getMockCache();
1066 $mockCache->expects( $this->once() )
1072 $store = $this->newWatchedItemStore(
1073 $this->getMockLoadBalancer( $mockDb ),
1077 $watchedItem = $store->loadWatchedItem(
1078 $this->getMockNonAnonUserWithId( 1 ),
1079 new TitleValue( 0, 'SomeDbKey' )
1081 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1082 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1083 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1084 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1087 public function testLoadWatchedItem_noItem() {
1088 $mockDb = $this->getMockDb();
1089 $mockDb->expects( $this->once() )
1090 ->method( 'selectRow' )
1093 'wl_notificationtimestamp',
1096 'wl_namespace' => 0,
1097 'wl_title' => 'SomeDbKey',
1100 ->will( $this->returnValue( [] ) );
1102 $mockCache = $this->getMockCache();
1103 $mockCache->expects( $this->never() )->method( 'get' );
1104 $mockCache->expects( $this->never() )->method( 'delete' );
1106 $store = $this->newWatchedItemStore(
1107 $this->getMockLoadBalancer( $mockDb ),
1112 $store->loadWatchedItem(
1113 $this->getMockNonAnonUserWithId( 1 ),
1114 new TitleValue( 0, 'SomeDbKey' )
1119 public function testLoadWatchedItem_anonymousUser() {
1120 $mockDb = $this->getMockDb();
1121 $mockDb->expects( $this->never() )
1122 ->method( 'selectRow' );
1124 $mockCache = $this->getMockCache();
1125 $mockCache->expects( $this->never() )->method( 'get' );
1126 $mockCache->expects( $this->never() )->method( 'delete' );
1128 $store = $this->newWatchedItemStore(
1129 $this->getMockLoadBalancer( $mockDb ),
1134 $store->loadWatchedItem(
1135 $this->getAnonUser(),
1136 new TitleValue( 0, 'SomeDbKey' )
1141 public function testRemoveWatch_existingItem() {
1142 $mockDb = $this->getMockDb();
1143 $mockDb->expects( $this->once() )
1144 ->method( 'delete' )
1149 'wl_namespace' => 0,
1150 'wl_title' => 'SomeDbKey',
1153 $mockDb->expects( $this->once() )
1154 ->method( 'affectedRows' )
1155 ->will( $this->returnValue( 1 ) );
1157 $mockCache = $this->getMockCache();
1158 $mockCache->expects( $this->never() )->method( 'get' );
1159 $mockCache->expects( $this->once() )
1160 ->method( 'delete' )
1161 ->with( '0:SomeDbKey:1' );
1163 $store = $this->newWatchedItemStore(
1164 $this->getMockLoadBalancer( $mockDb ),
1169 $store->removeWatch(
1170 $this->getMockNonAnonUserWithId( 1 ),
1171 new TitleValue( 0, 'SomeDbKey' )
1176 public function testRemoveWatch_noItem() {
1177 $mockDb = $this->getMockDb();
1178 $mockDb->expects( $this->once() )
1179 ->method( 'delete' )
1184 'wl_namespace' => 0,
1185 'wl_title' => 'SomeDbKey',
1188 $mockDb->expects( $this->once() )
1189 ->method( 'affectedRows' )
1190 ->will( $this->returnValue( 0 ) );
1192 $mockCache = $this->getMockCache();
1193 $mockCache->expects( $this->never() )->method( 'get' );
1194 $mockCache->expects( $this->once() )
1195 ->method( 'delete' )
1196 ->with( '0:SomeDbKey:1' );
1198 $store = $this->newWatchedItemStore(
1199 $this->getMockLoadBalancer( $mockDb ),
1204 $store->removeWatch(
1205 $this->getMockNonAnonUserWithId( 1 ),
1206 new TitleValue( 0, 'SomeDbKey' )
1211 public function testRemoveWatch_anonymousUser() {
1212 $mockDb = $this->getMockDb();
1213 $mockDb->expects( $this->never() )
1214 ->method( 'delete' );
1216 $mockCache = $this->getMockCache();
1217 $mockCache->expects( $this->never() )->method( 'get' );
1218 $mockCache->expects( $this->never() )
1219 ->method( 'delete' );
1221 $store = $this->newWatchedItemStore(
1222 $this->getMockLoadBalancer( $mockDb ),
1227 $store->removeWatch(
1228 $this->getAnonUser(),
1229 new TitleValue( 0, 'SomeDbKey' )
1234 public function testGetWatchedItem_existingItem() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->once() )
1237 ->method( 'selectRow' )
1240 'wl_notificationtimestamp',
1243 'wl_namespace' => 0,
1244 'wl_title' => 'SomeDbKey',
1247 ->will( $this->returnValue(
1248 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1251 $mockCache = $this->getMockCache();
1252 $mockCache->expects( $this->never() )->method( 'delete' );
1253 $mockCache->expects( $this->once() )
1258 ->will( $this->returnValue( null ) );
1259 $mockCache->expects( $this->once() )
1265 $store = $this->newWatchedItemStore(
1266 $this->getMockLoadBalancer( $mockDb ),
1270 $watchedItem = $store->getWatchedItem(
1271 $this->getMockNonAnonUserWithId( 1 ),
1272 new TitleValue( 0, 'SomeDbKey' )
1274 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1275 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1276 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1277 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1280 public function testGetWatchedItem_cachedItem() {
1281 $mockDb = $this->getMockDb();
1282 $mockDb->expects( $this->never() )
1283 ->method( 'selectRow' );
1285 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1286 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1287 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1289 $mockCache = $this->getMockCache();
1290 $mockCache->expects( $this->never() )->method( 'delete' );
1291 $mockCache->expects( $this->never() )->method( 'set' );
1292 $mockCache->expects( $this->once() )
1297 ->will( $this->returnValue( $cachedItem ) );
1299 $store = $this->newWatchedItemStore(
1300 $this->getMockLoadBalancer( $mockDb ),
1304 $this->assertEquals(
1306 $store->getWatchedItem(
1313 public function testGetWatchedItem_noItem() {
1314 $mockDb = $this->getMockDb();
1315 $mockDb->expects( $this->once() )
1316 ->method( 'selectRow' )
1319 'wl_notificationtimestamp',
1322 'wl_namespace' => 0,
1323 'wl_title' => 'SomeDbKey',
1326 ->will( $this->returnValue( [] ) );
1328 $mockCache = $this->getMockCache();
1329 $mockCache->expects( $this->never() )->method( 'set' );
1330 $mockCache->expects( $this->never() )->method( 'delete' );
1331 $mockCache->expects( $this->once() )
1333 ->with( '0:SomeDbKey:1' )
1334 ->will( $this->returnValue( false ) );
1336 $store = $this->newWatchedItemStore(
1337 $this->getMockLoadBalancer( $mockDb ),
1342 $store->getWatchedItem(
1343 $this->getMockNonAnonUserWithId( 1 ),
1344 new TitleValue( 0, 'SomeDbKey' )
1349 public function testGetWatchedItem_anonymousUser() {
1350 $mockDb = $this->getMockDb();
1351 $mockDb->expects( $this->never() )
1352 ->method( 'selectRow' );
1354 $mockCache = $this->getMockCache();
1355 $mockCache->expects( $this->never() )->method( 'set' );
1356 $mockCache->expects( $this->never() )->method( 'get' );
1357 $mockCache->expects( $this->never() )->method( 'delete' );
1359 $store = $this->newWatchedItemStore(
1360 $this->getMockLoadBalancer( $mockDb ),
1365 $store->getWatchedItem(
1366 $this->getAnonUser(),
1367 new TitleValue( 0, 'SomeDbKey' )
1372 public function testGetWatchedItemsForUser() {
1373 $mockDb = $this->getMockDb();
1374 $mockDb->expects( $this->once() )
1375 ->method( 'select' )
1378 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1381 ->will( $this->returnValue( [
1382 $this->getFakeRow( [
1383 'wl_namespace' => 0,
1384 'wl_title' => 'Foo1',
1385 'wl_notificationtimestamp' => '20151212010101',
1387 $this->getFakeRow( [
1388 'wl_namespace' => 1,
1389 'wl_title' => 'Foo2',
1390 'wl_notificationtimestamp' => null,
1394 $mockCache = $this->getMockCache();
1395 $mockCache->expects( $this->never() )->method( 'delete' );
1396 $mockCache->expects( $this->never() )->method( 'get' );
1397 $mockCache->expects( $this->never() )->method( 'set' );
1399 $store = $this->newWatchedItemStore(
1400 $this->getMockLoadBalancer( $mockDb ),
1403 $user = $this->getMockNonAnonUserWithId( 1 );
1405 $watchedItems = $store->getWatchedItemsForUser( $user );
1407 $this->assertInternalType( 'array', $watchedItems );
1408 $this->assertCount( 2, $watchedItems );
1409 foreach ( $watchedItems as $watchedItem ) {
1410 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1412 $this->assertEquals(
1413 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1416 $this->assertEquals(
1417 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1422 public function provideDbTypes() {
1424 [ false, DB_SLAVE
],
1425 [ true, DB_MASTER
],
1430 * @dataProvider provideDbTypes
1432 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1433 $mockDb = $this->getMockDb();
1434 $mockCache = $this->getMockCache();
1435 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1436 $user = $this->getMockNonAnonUserWithId( 1 );
1438 $mockDb->expects( $this->once() )
1439 ->method( 'select' )
1442 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1444 $this->isType( 'string' ),
1445 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1447 ->will( $this->returnValue( [] ) );
1449 $store = $this->newWatchedItemStore(
1454 $watchedItems = $store->getWatchedItemsForUser(
1456 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1458 $this->assertEquals( [], $watchedItems );
1461 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1462 $store = $this->newWatchedItemStore(
1463 $this->getMockLoadBalancer( $this->getMockDb() ),
1464 $this->getMockCache()
1467 $this->setExpectedException( 'InvalidArgumentException' );
1468 $store->getWatchedItemsForUser(
1469 $this->getMockNonAnonUserWithId( 1 ),
1474 public function testIsWatchedItem_existingItem() {
1475 $mockDb = $this->getMockDb();
1476 $mockDb->expects( $this->once() )
1477 ->method( 'selectRow' )
1480 'wl_notificationtimestamp',
1483 'wl_namespace' => 0,
1484 'wl_title' => 'SomeDbKey',
1487 ->will( $this->returnValue(
1488 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1491 $mockCache = $this->getMockCache();
1492 $mockCache->expects( $this->never() )->method( 'delete' );
1493 $mockCache->expects( $this->once() )
1495 ->with( '0:SomeDbKey:1' )
1496 ->will( $this->returnValue( false ) );
1497 $mockCache->expects( $this->once() )
1503 $store = $this->newWatchedItemStore(
1504 $this->getMockLoadBalancer( $mockDb ),
1510 $this->getMockNonAnonUserWithId( 1 ),
1511 new TitleValue( 0, 'SomeDbKey' )
1516 public function testIsWatchedItem_noItem() {
1517 $mockDb = $this->getMockDb();
1518 $mockDb->expects( $this->once() )
1519 ->method( 'selectRow' )
1522 'wl_notificationtimestamp',
1525 'wl_namespace' => 0,
1526 'wl_title' => 'SomeDbKey',
1529 ->will( $this->returnValue( [] ) );
1531 $mockCache = $this->getMockCache();
1532 $mockCache->expects( $this->never() )->method( 'set' );
1533 $mockCache->expects( $this->never() )->method( 'delete' );
1534 $mockCache->expects( $this->once() )
1536 ->with( '0:SomeDbKey:1' )
1537 ->will( $this->returnValue( false ) );
1539 $store = $this->newWatchedItemStore(
1540 $this->getMockLoadBalancer( $mockDb ),
1546 $this->getMockNonAnonUserWithId( 1 ),
1547 new TitleValue( 0, 'SomeDbKey' )
1552 public function testIsWatchedItem_anonymousUser() {
1553 $mockDb = $this->getMockDb();
1554 $mockDb->expects( $this->never() )
1555 ->method( 'selectRow' );
1557 $mockCache = $this->getMockCache();
1558 $mockCache->expects( $this->never() )->method( 'set' );
1559 $mockCache->expects( $this->never() )->method( 'get' );
1560 $mockCache->expects( $this->never() )->method( 'delete' );
1562 $store = $this->newWatchedItemStore(
1563 $this->getMockLoadBalancer( $mockDb ),
1569 $this->getAnonUser(),
1570 new TitleValue( 0, 'SomeDbKey' )
1575 public function testGetNotificationTimestampsBatch() {
1577 new TitleValue( 0, 'SomeDbKey' ),
1578 new TitleValue( 1, 'AnotherDbKey' ),
1581 $mockDb = $this->getMockDb();
1583 $this->getFakeRow( [
1584 'wl_namespace' => 0,
1585 'wl_title' => 'SomeDbKey',
1586 'wl_notificationtimestamp' => '20151212010101',
1590 'wl_namespace' => 1,
1591 'wl_title' => 'AnotherDbKey',
1592 'wl_notificationtimestamp' => null,
1597 $mockDb->expects( $this->once() )
1598 ->method( 'makeWhereFrom2d' )
1600 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1601 $this->isType( 'string' ),
1602 $this->isType( 'string' )
1604 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1605 $mockDb->expects( $this->once() )
1606 ->method( 'select' )
1609 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1611 'makeWhereFrom2d return value',
1614 $this->isType( 'string' )
1616 ->will( $this->returnValue( $dbResult ) );
1618 $mockCache = $this->getMockCache();
1619 $mockCache->expects( $this->exactly( 2 ) )
1622 [ '0:SomeDbKey:1' ],
1623 [ '1:AnotherDbKey:1' ]
1625 ->will( $this->returnValue( null ) );
1626 $mockCache->expects( $this->never() )->method( 'set' );
1627 $mockCache->expects( $this->never() )->method( 'delete' );
1629 $store = $this->newWatchedItemStore(
1630 $this->getMockLoadBalancer( $mockDb ),
1634 $this->assertEquals(
1636 0 => [ 'SomeDbKey' => '20151212010101', ],
1637 1 => [ 'AnotherDbKey' => null, ],
1639 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1643 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1645 new TitleValue( 0, 'OtherDbKey' ),
1648 $mockDb = $this->getMockDb();
1650 $mockDb->expects( $this->once() )
1651 ->method( 'makeWhereFrom2d' )
1653 [ [ 'OtherDbKey' => 1 ] ],
1654 $this->isType( 'string' ),
1655 $this->isType( 'string' )
1657 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1658 $mockDb->expects( $this->once() )
1659 ->method( 'select' )
1662 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1664 'makeWhereFrom2d return value',
1667 $this->isType( 'string' )
1669 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1671 $mockCache = $this->getMockCache();
1672 $mockCache->expects( $this->once() )
1674 ->with( '0:OtherDbKey:1' )
1675 ->will( $this->returnValue( null ) );
1676 $mockCache->expects( $this->never() )->method( 'set' );
1677 $mockCache->expects( $this->never() )->method( 'delete' );
1679 $store = $this->newWatchedItemStore(
1680 $this->getMockLoadBalancer( $mockDb ),
1684 $this->assertEquals(
1686 0 => [ 'OtherDbKey' => false, ],
1688 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1692 public function testGetNotificationTimestampsBatch_cachedItem() {
1694 new TitleValue( 0, 'SomeDbKey' ),
1695 new TitleValue( 1, 'AnotherDbKey' ),
1698 $user = $this->getMockNonAnonUserWithId( 1 );
1699 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1701 $mockDb = $this->getMockDb();
1703 $mockDb->expects( $this->once() )
1704 ->method( 'makeWhereFrom2d' )
1706 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1707 $this->isType( 'string' ),
1708 $this->isType( 'string' )
1710 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1711 $mockDb->expects( $this->once() )
1712 ->method( 'select' )
1715 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1717 'makeWhereFrom2d return value',
1720 $this->isType( 'string' )
1722 ->will( $this->returnValue( [
1724 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1728 $mockCache = $this->getMockCache();
1729 $mockCache->expects( $this->at( 1 ) )
1731 ->with( '0:SomeDbKey:1' )
1732 ->will( $this->returnValue( $cachedItem ) );
1733 $mockCache->expects( $this->at( 3 ) )
1735 ->with( '1:AnotherDbKey:1' )
1736 ->will( $this->returnValue( null ) );
1737 $mockCache->expects( $this->never() )->method( 'set' );
1738 $mockCache->expects( $this->never() )->method( 'delete' );
1740 $store = $this->newWatchedItemStore(
1741 $this->getMockLoadBalancer( $mockDb ),
1745 $this->assertEquals(
1747 0 => [ 'SomeDbKey' => '20151212010101', ],
1748 1 => [ 'AnotherDbKey' => null, ],
1750 $store->getNotificationTimestampsBatch( $user, $targets )
1754 public function testGetNotificationTimestampsBatch_allItemsCached() {
1756 new TitleValue( 0, 'SomeDbKey' ),
1757 new TitleValue( 1, 'AnotherDbKey' ),
1760 $user = $this->getMockNonAnonUserWithId( 1 );
1762 new WatchedItem( $user, $targets[0], '20151212010101' ),
1763 new WatchedItem( $user, $targets[1], null ),
1765 $mockDb = $this->getMockDb();
1766 $mockDb->expects( $this->never() )->method( $this->anything() );
1768 $mockCache = $this->getMockCache();
1769 $mockCache->expects( $this->at( 1 ) )
1771 ->with( '0:SomeDbKey:1' )
1772 ->will( $this->returnValue( $cachedItems[0] ) );
1773 $mockCache->expects( $this->at( 3 ) )
1775 ->with( '1:AnotherDbKey:1' )
1776 ->will( $this->returnValue( $cachedItems[1] ) );
1777 $mockCache->expects( $this->never() )->method( 'set' );
1778 $mockCache->expects( $this->never() )->method( 'delete' );
1780 $store = $this->newWatchedItemStore(
1781 $this->getMockLoadBalancer( $mockDb ),
1785 $this->assertEquals(
1787 0 => [ 'SomeDbKey' => '20151212010101', ],
1788 1 => [ 'AnotherDbKey' => null, ],
1790 $store->getNotificationTimestampsBatch( $user, $targets )
1794 public function testGetNotificationTimestampsBatch_anonymousUser() {
1796 new TitleValue( 0, 'SomeDbKey' ),
1797 new TitleValue( 1, 'AnotherDbKey' ),
1800 $mockDb = $this->getMockDb();
1801 $mockDb->expects( $this->never() )->method( $this->anything() );
1803 $mockCache = $this->getMockCache();
1804 $mockCache->expects( $this->never() )->method( $this->anything() );
1806 $store = $this->newWatchedItemStore(
1807 $this->getMockLoadBalancer( $mockDb ),
1811 $this->assertEquals(
1813 0 => [ 'SomeDbKey' => false, ],
1814 1 => [ 'AnotherDbKey' => false, ],
1816 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1820 public function testResetNotificationTimestamp_anonymousUser() {
1821 $mockDb = $this->getMockDb();
1822 $mockDb->expects( $this->never() )
1823 ->method( 'selectRow' );
1825 $mockCache = $this->getMockCache();
1826 $mockCache->expects( $this->never() )->method( 'get' );
1827 $mockCache->expects( $this->never() )->method( 'set' );
1828 $mockCache->expects( $this->never() )->method( 'delete' );
1830 $store = $this->newWatchedItemStore(
1831 $this->getMockLoadBalancer( $mockDb ),
1836 $store->resetNotificationTimestamp(
1837 $this->getAnonUser(),
1838 Title
::newFromText( 'SomeDbKey' )
1843 public function testResetNotificationTimestamp_noItem() {
1844 $mockDb = $this->getMockDb();
1845 $mockDb->expects( $this->once() )
1846 ->method( 'selectRow' )
1849 'wl_notificationtimestamp',
1852 'wl_namespace' => 0,
1853 'wl_title' => 'SomeDbKey',
1856 ->will( $this->returnValue( [] ) );
1858 $mockCache = $this->getMockCache();
1859 $mockCache->expects( $this->never() )->method( 'get' );
1860 $mockCache->expects( $this->never() )->method( 'set' );
1861 $mockCache->expects( $this->never() )->method( 'delete' );
1863 $store = $this->newWatchedItemStore(
1864 $this->getMockLoadBalancer( $mockDb ),
1869 $store->resetNotificationTimestamp(
1870 $this->getMockNonAnonUserWithId( 1 ),
1871 Title
::newFromText( 'SomeDbKey' )
1876 public function testResetNotificationTimestamp_item() {
1877 $user = $this->getMockNonAnonUserWithId( 1 );
1878 $title = Title
::newFromText( 'SomeDbKey' );
1880 $mockDb = $this->getMockDb();
1881 $mockDb->expects( $this->once() )
1882 ->method( 'selectRow' )
1885 'wl_notificationtimestamp',
1888 'wl_namespace' => 0,
1889 'wl_title' => 'SomeDbKey',
1892 ->will( $this->returnValue(
1893 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1896 $mockCache = $this->getMockCache();
1897 $mockCache->expects( $this->never() )->method( 'get' );
1898 $mockCache->expects( $this->once() )
1902 $this->isInstanceOf( WatchedItem
::class )
1904 $mockCache->expects( $this->once() )
1905 ->method( 'delete' )
1906 ->with( '0:SomeDbKey:1' );
1908 $store = $this->newWatchedItemStore(
1909 $this->getMockLoadBalancer( $mockDb ),
1913 // Note: This does not actually assert the job is correct
1914 $callableCallCounter = 0;
1915 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1916 $callableCallCounter++
;
1917 $this->assertInternalType( 'callable', $callable );
1919 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1922 $store->resetNotificationTimestamp(
1927 $this->assertEquals( 1, $callableCallCounter );
1929 ScopedCallback
::consume( $scopedOverride );
1932 public function testResetNotificationTimestamp_noItemForced() {
1933 $user = $this->getMockNonAnonUserWithId( 1 );
1934 $title = Title
::newFromText( 'SomeDbKey' );
1936 $mockDb = $this->getMockDb();
1937 $mockDb->expects( $this->never() )
1938 ->method( 'selectRow' );
1940 $mockCache = $this->getMockCache();
1941 $mockDb->expects( $this->never() )
1943 $mockDb->expects( $this->never() )
1945 $mockDb->expects( $this->never() )
1946 ->method( 'delete' );
1948 $store = $this->newWatchedItemStore(
1949 $this->getMockLoadBalancer( $mockDb ),
1953 // Note: This does not actually assert the job is correct
1954 $callableCallCounter = 0;
1955 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1956 $callableCallCounter++
;
1957 $this->assertInternalType( 'callable', $callable );
1959 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1962 $store->resetNotificationTimestamp(
1968 $this->assertEquals( 1, $callableCallCounter );
1970 ScopedCallback
::consume( $scopedOverride );
1977 * @return PHPUnit_Framework_MockObject_MockObject|Title
1979 private function getMockTitle( $text, $ns = 0 ) {
1980 $title = $this->getMock( Title
::class );
1981 $title->expects( $this->any() )
1982 ->method( 'getText' )
1983 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1984 $title->expects( $this->any() )
1985 ->method( 'getDbKey' )
1986 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1987 $title->expects( $this->any() )
1988 ->method( 'getNamespace' )
1989 ->will( $this->returnValue( $ns ) );
1993 private function verifyCallbackJob(
1995 LinkTarget
$expectedTitle,
1997 callable
$notificationTimestampCondition
1999 $this->assertInternalType( 'callable', $callback );
2001 $callbackReflector = new ReflectionFunction( $callback );
2002 $vars = $callbackReflector->getStaticVariables();
2003 $this->assertArrayHasKey( 'job', $vars );
2004 $this->assertInstanceOf( ActivityUpdateJob
::class, $vars['job'] );
2006 /** @var ActivityUpdateJob $job */
2007 $job = $vars['job'];
2008 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2009 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2011 $jobParams = $job->getParams();
2012 $this->assertArrayHasKey( 'type', $jobParams );
2013 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2014 $this->assertArrayHasKey( 'userid', $jobParams );
2015 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2016 $this->assertArrayHasKey( 'notifTime', $jobParams );
2017 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2020 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2021 $user = $this->getMockNonAnonUserWithId( 1 );
2023 $title = $this->getMockTitle( 'SomeTitle' );
2024 $title->expects( $this->once() )
2025 ->method( 'getNextRevisionID' )
2027 ->will( $this->returnValue( false ) );
2029 $mockDb = $this->getMockDb();
2030 $mockDb->expects( $this->never() )
2031 ->method( 'selectRow' );
2033 $mockCache = $this->getMockCache();
2034 $mockDb->expects( $this->never() )
2036 $mockDb->expects( $this->never() )
2038 $mockDb->expects( $this->never() )
2039 ->method( 'delete' );
2041 $store = $this->newWatchedItemStore(
2042 $this->getMockLoadBalancer( $mockDb ),
2046 $callableCallCounter = 0;
2047 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2048 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2049 $callableCallCounter++
;
2050 $this->verifyCallbackJob(
2055 return $time === null;
2062 $store->resetNotificationTimestamp(
2069 $this->assertEquals( 1, $callableCallCounter );
2071 ScopedCallback
::consume( $scopedOverride );
2074 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2075 $user = $this->getMockNonAnonUserWithId( 1 );
2077 $title = $this->getMockTitle( 'SomeDbKey' );
2078 $title->expects( $this->once() )
2079 ->method( 'getNextRevisionID' )
2081 ->will( $this->returnValue( 33 ) );
2083 $mockDb = $this->getMockDb();
2084 $mockDb->expects( $this->once() )
2085 ->method( 'selectRow' )
2088 'wl_notificationtimestamp',
2091 'wl_namespace' => 0,
2092 'wl_title' => 'SomeDbKey',
2095 ->will( $this->returnValue(
2096 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2099 $mockCache = $this->getMockCache();
2100 $mockDb->expects( $this->never() )
2102 $mockDb->expects( $this->never() )
2104 $mockDb->expects( $this->never() )
2105 ->method( 'delete' );
2107 $store = $this->newWatchedItemStore(
2108 $this->getMockLoadBalancer( $mockDb ),
2112 $addUpdateCallCounter = 0;
2113 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2114 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2115 $addUpdateCallCounter++
;
2116 $this->verifyCallbackJob(
2121 return $time !== null && $time > '20151212010101';
2127 $getTimestampCallCounter = 0;
2128 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2129 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2130 $getTimestampCallCounter++
;
2131 $this->assertEquals( $title, $titleParam );
2132 $this->assertEquals( $oldid, $oldidParam );
2137 $store->resetNotificationTimestamp(
2144 $this->assertEquals( 1, $addUpdateCallCounter );
2145 $this->assertEquals( 1, $getTimestampCallCounter );
2147 ScopedCallback
::consume( $scopedOverrideDeferred );
2148 ScopedCallback
::consume( $scopedOverrideRevision );
2151 public function testResetNotificationTimestamp_notWatchedPageForced() {
2152 $user = $this->getMockNonAnonUserWithId( 1 );
2154 $title = $this->getMockTitle( 'SomeDbKey' );
2155 $title->expects( $this->once() )
2156 ->method( 'getNextRevisionID' )
2158 ->will( $this->returnValue( 33 ) );
2160 $mockDb = $this->getMockDb();
2161 $mockDb->expects( $this->once() )
2162 ->method( 'selectRow' )
2165 'wl_notificationtimestamp',
2168 'wl_namespace' => 0,
2169 'wl_title' => 'SomeDbKey',
2172 ->will( $this->returnValue( false ) );
2174 $mockCache = $this->getMockCache();
2175 $mockDb->expects( $this->never() )
2177 $mockDb->expects( $this->never() )
2179 $mockDb->expects( $this->never() )
2180 ->method( 'delete' );
2182 $store = $this->newWatchedItemStore(
2183 $this->getMockLoadBalancer( $mockDb ),
2187 $callableCallCounter = 0;
2188 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2189 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2190 $callableCallCounter++
;
2191 $this->verifyCallbackJob(
2196 return $time === null;
2203 $store->resetNotificationTimestamp(
2210 $this->assertEquals( 1, $callableCallCounter );
2212 ScopedCallback
::consume( $scopedOverride );
2215 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2216 $user = $this->getMockNonAnonUserWithId( 1 );
2218 $title = $this->getMockTitle( 'SomeDbKey' );
2219 $title->expects( $this->once() )
2220 ->method( 'getNextRevisionID' )
2222 ->will( $this->returnValue( 33 ) );
2224 $mockDb = $this->getMockDb();
2225 $mockDb->expects( $this->once() )
2226 ->method( 'selectRow' )
2229 'wl_notificationtimestamp',
2232 'wl_namespace' => 0,
2233 'wl_title' => 'SomeDbKey',
2236 ->will( $this->returnValue(
2237 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2240 $mockCache = $this->getMockCache();
2241 $mockDb->expects( $this->never() )
2243 $mockDb->expects( $this->never() )
2245 $mockDb->expects( $this->never() )
2246 ->method( 'delete' );
2248 $store = $this->newWatchedItemStore(
2249 $this->getMockLoadBalancer( $mockDb ),
2253 $addUpdateCallCounter = 0;
2254 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2255 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2256 $addUpdateCallCounter++
;
2257 $this->verifyCallbackJob(
2262 return $time === '30151212010101';
2268 $getTimestampCallCounter = 0;
2269 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2270 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2271 $getTimestampCallCounter++
;
2272 $this->assertEquals( $title, $titleParam );
2273 $this->assertEquals( $oldid, $oldidParam );
2278 $store->resetNotificationTimestamp(
2285 $this->assertEquals( 1, $addUpdateCallCounter );
2286 $this->assertEquals( 1, $getTimestampCallCounter );
2288 ScopedCallback
::consume( $scopedOverrideDeferred );
2289 ScopedCallback
::consume( $scopedOverrideRevision );
2292 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2293 $user = $this->getMockNonAnonUserWithId( 1 );
2295 $title = $this->getMockTitle( 'SomeDbKey' );
2296 $title->expects( $this->once() )
2297 ->method( 'getNextRevisionID' )
2299 ->will( $this->returnValue( 33 ) );
2301 $mockDb = $this->getMockDb();
2302 $mockDb->expects( $this->once() )
2303 ->method( 'selectRow' )
2306 'wl_notificationtimestamp',
2309 'wl_namespace' => 0,
2310 'wl_title' => 'SomeDbKey',
2313 ->will( $this->returnValue(
2314 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2317 $mockCache = $this->getMockCache();
2318 $mockDb->expects( $this->never() )
2320 $mockDb->expects( $this->never() )
2322 $mockDb->expects( $this->never() )
2323 ->method( 'delete' );
2325 $store = $this->newWatchedItemStore(
2326 $this->getMockLoadBalancer( $mockDb ),
2330 $addUpdateCallCounter = 0;
2331 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2332 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2333 $addUpdateCallCounter++
;
2334 $this->verifyCallbackJob(
2339 return $time === false;
2345 $getTimestampCallCounter = 0;
2346 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2347 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2348 $getTimestampCallCounter++
;
2349 $this->assertEquals( $title, $titleParam );
2350 $this->assertEquals( $oldid, $oldidParam );
2355 $store->resetNotificationTimestamp(
2362 $this->assertEquals( 1, $addUpdateCallCounter );
2363 $this->assertEquals( 1, $getTimestampCallCounter );
2365 ScopedCallback
::consume( $scopedOverrideDeferred );
2366 ScopedCallback
::consume( $scopedOverrideRevision );
2369 public function testSetNotificationTimestampsForUser_anonUser() {
2370 $store = $this->newWatchedItemStore(
2371 $this->getMockLoadBalancer( $this->getMockDb() ),
2372 $this->getMockCache()
2374 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2377 public function testSetNotificationTimestampsForUser_allRows() {
2378 $user = $this->getMockNonAnonUserWithId( 1 );
2379 $timestamp = '20100101010101';
2381 $mockDb = $this->getMockDb();
2382 $mockDb->expects( $this->once() )
2383 ->method( 'update' )
2386 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2389 ->will( $this->returnValue( true ) );
2390 $mockDb->expects( $this->exactly( 1 ) )
2391 ->method( 'timestamp' )
2392 ->will( $this->returnCallback( function( $value ) {
2393 return 'TS' . $value . 'TS';
2396 $store = $this->newWatchedItemStore(
2397 $this->getMockLoadBalancer( $mockDb ),
2398 $this->getMockCache()
2402 $store->setNotificationTimestampsForUser( $user, $timestamp )
2406 public function testSetNotificationTimestampsForUser_specificTargets() {
2407 $user = $this->getMockNonAnonUserWithId( 1 );
2408 $timestamp = '20100101010101';
2409 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2411 $mockDb = $this->getMockDb();
2412 $mockDb->expects( $this->once() )
2413 ->method( 'update' )
2416 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2417 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2419 ->will( $this->returnValue( true ) );
2420 $mockDb->expects( $this->exactly( 1 ) )
2421 ->method( 'timestamp' )
2422 ->will( $this->returnCallback( function( $value ) {
2423 return 'TS' . $value . 'TS';
2425 $mockDb->expects( $this->once() )
2426 ->method( 'makeWhereFrom2d' )
2428 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2429 $this->isType( 'string' ),
2430 $this->isType( 'string' )
2432 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2434 $store = $this->newWatchedItemStore(
2435 $this->getMockLoadBalancer( $mockDb ),
2436 $this->getMockCache()
2440 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2444 public function testUpdateNotificationTimestamp_watchersExist() {
2445 $mockDb = $this->getMockDb();
2446 $mockDb->expects( $this->once() )
2447 ->method( 'selectFieldValues' )
2453 'wl_namespace' => 0,
2454 'wl_title' => 'SomeDbKey',
2455 'wl_notificationtimestamp IS NULL'
2458 ->will( $this->returnValue( [ '2', '3' ] ) );
2459 $mockDb->expects( $this->once() )
2460 ->method( 'update' )
2463 [ 'wl_notificationtimestamp' => null ],
2465 'wl_user' => [ 2, 3 ],
2466 'wl_namespace' => 0,
2467 'wl_title' => 'SomeDbKey',
2471 $mockCache = $this->getMockCache();
2472 $mockCache->expects( $this->never() )->method( 'set' );
2473 $mockCache->expects( $this->never() )->method( 'get' );
2474 $mockCache->expects( $this->never() )->method( 'delete' );
2476 $store = $this->newWatchedItemStore(
2477 $this->getMockLoadBalancer( $mockDb ),
2481 $this->assertEquals(
2483 $store->updateNotificationTimestamp(
2484 $this->getMockNonAnonUserWithId( 1 ),
2485 new TitleValue( 0, 'SomeDbKey' ),
2491 public function testUpdateNotificationTimestamp_noWatchers() {
2492 $mockDb = $this->getMockDb();
2493 $mockDb->expects( $this->once() )
2494 ->method( 'selectFieldValues' )
2500 'wl_namespace' => 0,
2501 'wl_title' => 'SomeDbKey',
2502 'wl_notificationtimestamp IS NULL'
2506 $this->returnValue( [] )
2508 $mockDb->expects( $this->never() )
2509 ->method( 'update' );
2511 $mockCache = $this->getMockCache();
2512 $mockCache->expects( $this->never() )->method( 'set' );
2513 $mockCache->expects( $this->never() )->method( 'get' );
2514 $mockCache->expects( $this->never() )->method( 'delete' );
2516 $store = $this->newWatchedItemStore(
2517 $this->getMockLoadBalancer( $mockDb ),
2521 $watchers = $store->updateNotificationTimestamp(
2522 $this->getMockNonAnonUserWithId( 1 ),
2523 new TitleValue( 0, 'SomeDbKey' ),
2526 $this->assertInternalType( 'array', $watchers );
2527 $this->assertEmpty( $watchers );
2530 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2531 $user = $this->getMockNonAnonUserWithId( 1 );
2532 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2534 $mockDb = $this->getMockDb();
2535 $mockDb->expects( $this->once() )
2536 ->method( 'selectRow' )
2537 ->will( $this->returnValue(
2538 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2540 $mockDb->expects( $this->once() )
2541 ->method( 'selectFieldValues' )
2543 $this->returnValue( [ '2', '3' ] )
2545 $mockDb->expects( $this->once() )
2546 ->method( 'update' );
2548 $mockCache = $this->getMockCache();
2549 $mockCache->expects( $this->once() )
2551 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2552 $mockCache->expects( $this->once() )
2554 ->with( '0:SomeDbKey:1' );
2555 $mockCache->expects( $this->once() )
2556 ->method( 'delete' )
2557 ->with( '0:SomeDbKey:1' );
2559 $store = $this->newWatchedItemStore(
2560 $this->getMockLoadBalancer( $mockDb ),
2564 // This will add the item to the cache
2565 $store->getWatchedItem( $user, $titleValue );
2567 $store->updateNotificationTimestamp(
2568 $this->getMockNonAnonUserWithId( 1 ),