3 use MediaWiki\Linker\LinkTarget
;
4 use MediaWiki\Revision\RevisionLookup
;
5 use MediaWiki\Revision\RevisionRecord
;
6 use MediaWiki\User\UserIdentityValue
;
7 use Wikimedia\Rdbms\LBFactory
;
8 use Wikimedia\Rdbms\LoadBalancer
;
9 use Wikimedia\TestingAccessWrapper
;
14 * @covers WatchedItemStore
16 class WatchedItemStoreUnitTest
extends MediaWikiTestCase
{
19 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
21 private function getMockDb() {
22 return $this->createMock( IDatabase
::class );
26 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
28 private function getMockLoadBalancer(
30 $expectedConnectionType = null
32 $mock = $this->getMockBuilder( LoadBalancer
::class )
33 ->disableOriginalConstructor()
35 if ( $expectedConnectionType !== null ) {
36 $mock->expects( $this->any() )
37 ->method( 'getConnectionRef' )
38 ->with( $expectedConnectionType )
39 ->will( $this->returnValue( $mockDb ) );
41 $mock->expects( $this->any() )
42 ->method( 'getConnectionRef' )
43 ->will( $this->returnValue( $mockDb ) );
49 * @return PHPUnit_Framework_MockObject_MockObject|LBFactory
51 private function getMockLBFactory(
53 $expectedConnectionType = null
55 $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
56 $mock = $this->getMockBuilder( LBFactory
::class )
57 ->disableOriginalConstructor()
59 $mock->expects( $this->any() )
60 ->method( 'getMainLB' )
61 ->will( $this->returnValue( $loadBalancer ) );
66 * @return PHPUnit_Framework_MockObject_MockObject|JobQueueGroup
68 private function getMockJobQueueGroup() {
69 $mock = $this->getMockBuilder( JobQueueGroup
::class )
70 ->disableOriginalConstructor()
72 $mock->expects( $this->any() )
74 ->will( $this->returnCallback( function ( Job
$job ) {
77 $mock->expects( $this->any() )
78 ->method( 'lazyPush' )
79 ->will( $this->returnCallback( function ( Job
$job ) {
86 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
88 private function getMockCache() {
89 if ( defined( 'HHVM_VERSION' ) ) {
90 $this->markTestSkipped( 'HHVM Reflection buggy' );
93 $mock = $this->getMockBuilder( HashBagOStuff
::class )
94 ->disableOriginalConstructor()
95 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
97 $mock->expects( $this->any() )
99 ->will( $this->returnCallback( function ( ...$args ) {
100 return implode( ':', $args );
106 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
108 private function getMockReadOnlyMode( $readOnly = false ) {
109 $mock = $this->getMockBuilder( ReadOnlyMode
::class )
110 ->disableOriginalConstructor()
112 $mock->expects( $this->any() )
113 ->method( 'isReadOnly' )
114 ->will( $this->returnValue( $readOnly ) );
119 * Assumes that only getSubjectPage and getTalkPage will ever be called, and everything passed
120 * to them will have namespace 0.
122 private function getMockNsInfo() : NamespaceInfo
{
123 $mock = $this->createMock( NamespaceInfo
::class );
124 $mock->method( 'getSubjectPage' )->will( $this->returnArgument( 0 ) );
125 $mock->method( 'getTalkPage' )->will( $this->returnCallback(
126 function ( $target ) {
127 return new TitleValue( 1, $target->getDbKey() );
130 $mock->expects( $this->never() )
131 ->method( $this->anythingBut( 'getSubjectPage', 'getTalkPage' ) );
136 * No methods may be called except provided callbacks, if any.
138 * @param array $callbacks Keys are method names, values are callbacks
139 * @param array $counts Keys are method names, values are expected number of times to be called
140 * (default is any number is okay)
142 private function getMockRevisionLookup(
143 array $callbacks = [], array $counts = []
145 $mock = $this->createMock( RevisionLookup
::class );
146 foreach ( $callbacks as $method => $callback ) {
147 $count = isset( $counts[$method] ) ?
$this->exactly( $counts[$method] ) : $this->any();
148 $mock->expects( $count )
150 ->will( $this->returnCallback( $callbacks[$method] ) );
152 $mock->expects( $this->never() )
153 ->method( $this->anythingBut( ...array_keys( $callbacks ) ) );
157 private function getFakeRow( array $rowValues ) {
158 $fakeRow = new stdClass();
159 foreach ( $rowValues as $valueName => $value ) {
160 $fakeRow->$valueName = $value;
166 * @param array $mocks Associative array providing mocks to use when constructing the
167 * WatchedItemStore. Anything not provided will fall back to a default. Valid keys:
176 private function newWatchedItemStore( array $mocks = [] ) : WatchedItemStore
{
177 return new WatchedItemStore(
178 $mocks['lbFactory'] ??
179 $this->getMockLBFactory( $mocks['db'] ??
$this->getMockDb() ),
180 $mocks['queueGroup'] ??
$this->getMockJobQueueGroup(),
182 $mocks['cache'] ??
$this->getMockCache(),
183 $mocks['readOnlyMode'] ??
$this->getMockReadOnlyMode(),
185 $mocks['nsInfo'] ??
$this->getMockNsInfo(),
186 $mocks['revisionLookup'] ??
$this->getMockRevisionLookup()
190 public function testClearWatchedItems() {
191 $user = new UserIdentityValue( 7, 'MockUser', 0 );
193 $mockDb = $this->getMockDb();
194 $mockDb->expects( $this->once() )
195 ->method( 'selectField' )
200 'wl_user' => $user->getId(),
202 $this->isType( 'string' )
204 ->will( $this->returnValue( 12 ) );
205 $mockDb->expects( $this->once() )
210 $this->isType( 'string' )
213 $mockCache = $this->getMockCache();
214 $mockCache->expects( $this->never() )->method( 'get' );
215 $mockCache->expects( $this->never() )->method( 'set' );
216 $mockCache->expects( $this->once() )
220 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
221 TestingAccessWrapper
::newFromObject( $store )
222 ->cacheIndex
= [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
224 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
227 public function testClearWatchedItems_tooManyItemsWatched() {
228 $user = new UserIdentityValue( 7, 'MockUser', 0 );
230 $mockDb = $this->getMockDb();
231 $mockDb->expects( $this->once() )
232 ->method( 'selectField' )
237 'wl_user' => $user->getId(),
239 $this->isType( 'string' )
241 ->will( $this->returnValue( 99999 ) );
243 $mockCache = $this->getMockCache();
244 $mockCache->expects( $this->never() )->method( 'get' );
245 $mockCache->expects( $this->never() )->method( 'set' );
246 $mockCache->expects( $this->never() )->method( 'delete' );
248 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
250 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
253 public function testCountWatchedItems() {
254 $user = new UserIdentityValue( 1, 'MockUser', 0 );
256 $mockDb = $this->getMockDb();
257 $mockDb->expects( $this->exactly( 1 ) )
258 ->method( 'selectField' )
263 'wl_user' => $user->getId(),
265 $this->isType( 'string' )
267 ->will( $this->returnValue( '12' ) );
269 $mockCache = $this->getMockCache();
270 $mockCache->expects( $this->never() )->method( 'get' );
271 $mockCache->expects( $this->never() )->method( 'set' );
272 $mockCache->expects( $this->never() )->method( 'delete' );
274 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
276 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
279 public function testCountWatchers() {
280 $titleValue = new TitleValue( 0, 'SomeDbKey' );
282 $mockDb = $this->getMockDb();
283 $mockDb->expects( $this->exactly( 1 ) )
284 ->method( 'selectField' )
289 'wl_namespace' => $titleValue->getNamespace(),
290 'wl_title' => $titleValue->getDBkey(),
292 $this->isType( 'string' )
294 ->will( $this->returnValue( '7' ) );
296 $mockCache = $this->getMockCache();
297 $mockCache->expects( $this->never() )->method( 'get' );
298 $mockCache->expects( $this->never() )->method( 'set' );
299 $mockCache->expects( $this->never() )->method( 'delete' );
301 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
303 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
306 public function testCountWatchersMultiple() {
308 new TitleValue( 0, 'SomeDbKey' ),
309 new TitleValue( 0, 'OtherDbKey' ),
310 new TitleValue( 1, 'AnotherDbKey' ),
313 $mockDb = $this->getMockDb();
316 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
317 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
318 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
321 $mockDb->expects( $this->once() )
322 ->method( 'makeWhereFrom2d' )
324 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
325 $this->isType( 'string' ),
326 $this->isType( 'string' )
328 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
329 $mockDb->expects( $this->once() )
333 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
334 [ 'makeWhereFrom2d return value' ],
335 $this->isType( 'string' ),
337 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
341 $this->returnValue( $dbResult )
344 $mockCache = $this->getMockCache();
345 $mockCache->expects( $this->never() )->method( 'get' );
346 $mockCache->expects( $this->never() )->method( 'set' );
347 $mockCache->expects( $this->never() )->method( 'delete' );
349 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
352 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
353 1 => [ 'AnotherDbKey' => 500 ],
355 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
358 public function provideIntWithDbUnsafeVersion() {
361 [ "50; DROP TABLE watchlist;\n--" ],
366 * @dataProvider provideIntWithDbUnsafeVersion
368 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
370 new TitleValue( 0, 'SomeDbKey' ),
371 new TitleValue( 0, 'OtherDbKey' ),
372 new TitleValue( 1, 'AnotherDbKey' ),
375 $mockDb = $this->getMockDb();
378 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
379 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
380 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
383 $mockDb->expects( $this->once() )
384 ->method( 'makeWhereFrom2d' )
386 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
387 $this->isType( 'string' ),
388 $this->isType( 'string' )
390 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
391 $mockDb->expects( $this->once() )
395 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
396 [ 'makeWhereFrom2d return value' ],
397 $this->isType( 'string' ),
399 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
400 'HAVING' => 'COUNT(*) >= 50',
404 $this->returnValue( $dbResult )
407 $mockCache = $this->getMockCache();
408 $mockCache->expects( $this->never() )->method( 'get' );
409 $mockCache->expects( $this->never() )->method( 'set' );
410 $mockCache->expects( $this->never() )->method( 'delete' );
412 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
415 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
416 1 => [ 'AnotherDbKey' => 500 ],
420 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
424 public function testCountVisitingWatchers() {
425 $titleValue = new TitleValue( 0, 'SomeDbKey' );
427 $mockDb = $this->getMockDb();
428 $mockDb->expects( $this->exactly( 1 ) )
429 ->method( 'selectField' )
434 'wl_namespace' => $titleValue->getNamespace(),
435 'wl_title' => $titleValue->getDBkey(),
436 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
438 $this->isType( 'string' )
440 ->will( $this->returnValue( '7' ) );
441 $mockDb->expects( $this->exactly( 1 ) )
442 ->method( 'addQuotes' )
443 ->will( $this->returnCallback( function ( $value ) {
446 $mockDb->expects( $this->exactly( 1 ) )
447 ->method( 'timestamp' )
448 ->will( $this->returnCallback( function ( $value ) {
449 return 'TS' . $value . 'TS';
452 $mockCache = $this->getMockCache();
453 $mockCache->expects( $this->never() )->method( 'set' );
454 $mockCache->expects( $this->never() )->method( 'get' );
455 $mockCache->expects( $this->never() )->method( 'delete' );
457 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
459 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
462 public function testCountVisitingWatchersMultiple() {
463 $titleValuesWithThresholds = [
464 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
465 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
466 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
470 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
471 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
473 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
476 $mockDb = $this->getMockDb();
477 $mockDb->expects( $this->exactly( 2 * 3 ) )
478 ->method( 'addQuotes' )
479 ->will( $this->returnCallback( function ( $value ) {
482 $mockDb->expects( $this->exactly( 3 ) )
483 ->method( 'timestamp' )
484 ->will( $this->returnCallback( function ( $value ) {
485 return 'TS' . $value . 'TS';
487 $mockDb->expects( $this->any() )
488 ->method( 'makeList' )
490 $this->isType( 'array' ),
491 $this->isType( 'int' )
493 ->will( $this->returnCallback( function ( $a, $conj ) {
494 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
495 return implode( $sqlConj, array_map( function ( $s ) {
496 return '(' . $s . ')';
500 $mockDb->expects( $this->never() )
501 ->method( 'makeWhereFrom2d' );
504 '((wl_namespace = 0) AND (' .
505 "(((wl_title = 'SomeDbKey') AND (" .
506 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
508 "(wl_title = 'OtherDbKey') AND (" .
509 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
511 ') OR ((wl_namespace = 1) AND (' .
512 "(((wl_title = 'AnotherDbKey') AND (" .
513 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
515 $mockDb->expects( $this->once() )
519 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
521 $this->isType( 'string' ),
523 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
527 $this->returnValue( $dbResult )
530 $mockCache = $this->getMockCache();
531 $mockCache->expects( $this->never() )->method( 'get' );
532 $mockCache->expects( $this->never() )->method( 'set' );
533 $mockCache->expects( $this->never() )->method( 'delete' );
535 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
538 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
539 1 => [ 'AnotherDbKey' => 500 ],
543 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
547 public function testCountVisitingWatchersMultiple_withMissingTargets() {
548 $titleValuesWithThresholds = [
549 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
550 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
551 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
552 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
553 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
557 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
558 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
560 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
563 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
566 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
569 $mockDb = $this->getMockDb();
570 $mockDb->expects( $this->exactly( 2 * 3 ) )
571 ->method( 'addQuotes' )
572 ->will( $this->returnCallback( function ( $value ) {
575 $mockDb->expects( $this->exactly( 3 ) )
576 ->method( 'timestamp' )
577 ->will( $this->returnCallback( function ( $value ) {
578 return 'TS' . $value . 'TS';
580 $mockDb->expects( $this->any() )
581 ->method( 'makeList' )
583 $this->isType( 'array' ),
584 $this->isType( 'int' )
586 ->will( $this->returnCallback( function ( $a, $conj ) {
587 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
588 return implode( $sqlConj, array_map( function ( $s ) {
589 return '(' . $s . ')';
593 $mockDb->expects( $this->once() )
594 ->method( 'makeWhereFrom2d' )
596 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
597 $this->isType( 'string' ),
598 $this->isType( 'string' )
600 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
603 '((wl_namespace = 0) AND (' .
604 "(((wl_title = 'SomeDbKey') AND (" .
605 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
607 "(wl_title = 'OtherDbKey') AND (" .
608 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
610 ') OR ((wl_namespace = 1) AND (' .
611 "(((wl_title = 'AnotherDbKey') AND (" .
612 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
615 '(makeWhereFrom2d return value)';
616 $mockDb->expects( $this->once() )
620 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
622 $this->isType( 'string' ),
624 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
628 $this->returnValue( $dbResult )
631 $mockCache = $this->getMockCache();
632 $mockCache->expects( $this->never() )->method( 'get' );
633 $mockCache->expects( $this->never() )->method( 'set' );
634 $mockCache->expects( $this->never() )->method( 'delete' );
636 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
640 'SomeDbKey' => 100, 'OtherDbKey' => 300,
641 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
643 1 => [ 'AnotherDbKey' => 500 ],
647 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
652 * @dataProvider provideIntWithDbUnsafeVersion
654 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
655 $titleValuesWithThresholds = [
656 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
657 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
658 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
661 $mockDb = $this->getMockDb();
662 $mockDb->expects( $this->any() )
663 ->method( 'makeList' )
664 ->will( $this->returnValue( 'makeList return value' ) );
665 $mockDb->expects( $this->once() )
669 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
670 'makeList return value',
671 $this->isType( 'string' ),
673 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
674 'HAVING' => 'COUNT(*) >= 50',
678 $this->returnValue( [] )
681 $mockCache = $this->getMockCache();
682 $mockCache->expects( $this->never() )->method( 'get' );
683 $mockCache->expects( $this->never() )->method( 'set' );
684 $mockCache->expects( $this->never() )->method( 'delete' );
686 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
689 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
690 1 => [ 'AnotherDbKey' => 0 ],
694 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
698 public function testCountUnreadNotifications() {
699 $user = new UserIdentityValue( 1, 'MockUser', 0 );
701 $mockDb = $this->getMockDb();
702 $mockDb->expects( $this->exactly( 1 ) )
703 ->method( 'selectRowCount' )
708 "wl_notificationtimestamp IS NOT NULL",
711 $this->isType( 'string' )
713 ->will( $this->returnValue( '9' ) );
715 $mockCache = $this->getMockCache();
716 $mockCache->expects( $this->never() )->method( 'set' );
717 $mockCache->expects( $this->never() )->method( 'get' );
718 $mockCache->expects( $this->never() )->method( 'delete' );
720 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
722 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
726 * @dataProvider provideIntWithDbUnsafeVersion
728 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
729 $user = new UserIdentityValue( 1, 'MockUser', 0 );
731 $mockDb = $this->getMockDb();
732 $mockDb->expects( $this->exactly( 1 ) )
733 ->method( 'selectRowCount' )
738 "wl_notificationtimestamp IS NOT NULL",
741 $this->isType( 'string' ),
744 ->will( $this->returnValue( '50' ) );
746 $mockCache = $this->getMockCache();
747 $mockCache->expects( $this->never() )->method( 'set' );
748 $mockCache->expects( $this->never() )->method( 'get' );
749 $mockCache->expects( $this->never() )->method( 'delete' );
751 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
755 $store->countUnreadNotifications( $user, $limit )
760 * @dataProvider provideIntWithDbUnsafeVersion
762 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
763 $user = new UserIdentityValue( 1, 'MockUser', 0 );
765 $mockDb = $this->getMockDb();
766 $mockDb->expects( $this->exactly( 1 ) )
767 ->method( 'selectRowCount' )
772 "wl_notificationtimestamp IS NOT NULL",
775 $this->isType( 'string' ),
778 ->will( $this->returnValue( '9' ) );
780 $mockCache = $this->getMockCache();
781 $mockCache->expects( $this->never() )->method( 'set' );
782 $mockCache->expects( $this->never() )->method( 'get' );
783 $mockCache->expects( $this->never() )->method( 'delete' );
785 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
789 $store->countUnreadNotifications( $user, $limit )
793 public function testDuplicateEntry_nothingToDuplicate() {
794 $mockDb = $this->getMockDb();
795 $mockDb->expects( $this->once() )
801 'wl_notificationtimestamp',
805 'wl_title' => 'Old_Title',
807 'WatchedItemStore::duplicateEntry',
810 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
812 $store = $this->newWatchedItemStore( [ 'db' => $mockDb ] );
814 $store->duplicateEntry(
815 new TitleValue( 0, 'Old_Title' ),
816 new TitleValue( 0, 'New_Title' )
820 public function testDuplicateEntry_somethingToDuplicate() {
822 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
823 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
826 $mockDb = $this->getMockDb();
827 $mockDb->expects( $this->at( 0 ) )
833 'wl_notificationtimestamp',
837 'wl_title' => 'Old_Title',
840 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
841 $mockDb->expects( $this->at( 1 ) )
842 ->method( 'replace' )
845 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
850 'wl_title' => 'New_Title',
851 'wl_notificationtimestamp' => '20151212010101',
856 'wl_title' => 'New_Title',
857 'wl_notificationtimestamp' => null,
860 $this->isType( 'string' )
863 $mockCache = $this->getMockCache();
864 $mockCache->expects( $this->never() )->method( 'get' );
865 $mockCache->expects( $this->never() )->method( 'delete' );
867 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
869 $store->duplicateEntry(
870 new TitleValue( 0, 'Old_Title' ),
871 new TitleValue( 0, 'New_Title' )
875 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
876 $mockDb = $this->getMockDb();
877 $mockDb->expects( $this->at( 0 ) )
883 'wl_notificationtimestamp',
887 'wl_title' => 'Old_Title',
890 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
891 $mockDb->expects( $this->at( 1 ) )
897 'wl_notificationtimestamp',
901 'wl_title' => 'Old_Title',
904 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
906 $mockCache = $this->getMockCache();
907 $mockCache->expects( $this->never() )->method( 'get' );
908 $mockCache->expects( $this->never() )->method( 'delete' );
910 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
912 $store->duplicateAllAssociatedEntries(
913 new TitleValue( 0, 'Old_Title' ),
914 new TitleValue( 0, 'New_Title' )
918 public function provideLinkTargetPairs() {
920 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
921 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
926 * @dataProvider provideLinkTargetPairs
928 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
929 LinkTarget
$oldTarget,
930 LinkTarget
$newTarget
933 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
936 $mockDb = $this->getMockDb();
937 $mockDb->expects( $this->at( 0 ) )
943 'wl_notificationtimestamp',
946 'wl_namespace' => $oldTarget->getNamespace(),
947 'wl_title' => $oldTarget->getDBkey(),
950 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
951 $mockDb->expects( $this->at( 1 ) )
952 ->method( 'replace' )
955 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
959 'wl_namespace' => $newTarget->getNamespace(),
960 'wl_title' => $newTarget->getDBkey(),
961 'wl_notificationtimestamp' => '20151212010101',
964 $this->isType( 'string' )
966 $mockDb->expects( $this->at( 2 ) )
972 'wl_notificationtimestamp',
975 'wl_namespace' => $oldTarget->getNamespace() +
1,
976 'wl_title' => $oldTarget->getDBkey(),
979 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
980 $mockDb->expects( $this->at( 3 ) )
981 ->method( 'replace' )
984 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
988 'wl_namespace' => $newTarget->getNamespace() +
1,
989 'wl_title' => $newTarget->getDBkey(),
990 'wl_notificationtimestamp' => '20151212010101',
993 $this->isType( 'string' )
996 $mockCache = $this->getMockCache();
997 $mockCache->expects( $this->never() )->method( 'get' );
998 $mockCache->expects( $this->never() )->method( 'delete' );
1000 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1002 $store->duplicateAllAssociatedEntries(
1008 public function testAddWatch_nonAnonymousUser() {
1009 $mockDb = $this->getMockDb();
1010 $mockDb->expects( $this->once() )
1011 ->method( 'insert' )
1017 'wl_namespace' => 0,
1018 'wl_title' => 'Some_Page',
1019 'wl_notificationtimestamp' => null,
1024 $mockCache = $this->getMockCache();
1025 $mockCache->expects( $this->once() )
1026 ->method( 'delete' )
1027 ->with( '0:Some_Page:1' );
1029 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1032 new UserIdentityValue( 1, 'MockUser', 0 ),
1033 new TitleValue( 0, 'Some_Page' )
1037 public function testAddWatch_anonymousUser() {
1038 $mockDb = $this->getMockDb();
1039 $mockDb->expects( $this->never() )
1040 ->method( 'insert' );
1042 $mockCache = $this->getMockCache();
1043 $mockCache->expects( $this->never() )
1044 ->method( 'delete' );
1046 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1049 new UserIdentityValue( 0, 'AnonUser', 0 ),
1050 new TitleValue( 0, 'Some_Page' )
1054 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1055 $store = $this->newWatchedItemStore(
1056 [ 'readOnlyMode' => $this->getMockReadOnlyMode( true ) ] );
1059 $store->addWatchBatchForUser(
1060 new UserIdentityValue( 1, 'MockUser', 0 ),
1061 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1066 public function testAddWatchBatchForUser_nonAnonymousUser() {
1067 $mockDb = $this->getMockDb();
1068 $mockDb->expects( $this->once() )
1069 ->method( 'insert' )
1075 'wl_namespace' => 0,
1076 'wl_title' => 'Some_Page',
1077 'wl_notificationtimestamp' => null,
1081 'wl_namespace' => 1,
1082 'wl_title' => 'Some_Page',
1083 'wl_notificationtimestamp' => null,
1088 $mockDb->expects( $this->once() )
1089 ->method( 'affectedRows' )
1092 $mockCache = $this->getMockCache();
1093 $mockCache->expects( $this->exactly( 2 ) )
1094 ->method( 'delete' );
1095 $mockCache->expects( $this->at( 1 ) )
1096 ->method( 'delete' )
1097 ->with( '0:Some_Page:1' );
1098 $mockCache->expects( $this->at( 3 ) )
1099 ->method( 'delete' )
1100 ->with( '1:Some_Page:1' );
1102 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1104 $mockUser = new UserIdentityValue( 1, 'MockUser', 0 );
1107 $store->addWatchBatchForUser(
1109 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1114 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1115 $mockDb = $this->getMockDb();
1116 $mockDb->expects( $this->never() )
1117 ->method( 'insert' );
1119 $mockCache = $this->getMockCache();
1120 $mockCache->expects( $this->never() )
1121 ->method( 'delete' );
1123 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1126 $store->addWatchBatchForUser(
1127 new UserIdentityValue( 0, 'AnonUser', 0 ),
1128 [ new TitleValue( 0, 'Other_Page' ) ]
1133 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1134 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1135 $mockDb = $this->getMockDb();
1136 $mockDb->expects( $this->never() )
1137 ->method( 'insert' );
1139 $mockCache = $this->getMockCache();
1140 $mockCache->expects( $this->never() )
1141 ->method( 'delete' );
1143 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1146 $store->addWatchBatchForUser( $user, [] )
1150 public function testLoadWatchedItem_existingItem() {
1151 $mockDb = $this->getMockDb();
1152 $mockDb->expects( $this->once() )
1153 ->method( 'selectRow' )
1156 'wl_notificationtimestamp',
1159 'wl_namespace' => 0,
1160 'wl_title' => 'SomeDbKey',
1163 ->will( $this->returnValue(
1164 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1167 $mockCache = $this->getMockCache();
1168 $mockCache->expects( $this->once() )
1174 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1176 $watchedItem = $store->loadWatchedItem(
1177 new UserIdentityValue( 1, 'MockUser', 0 ),
1178 new TitleValue( 0, 'SomeDbKey' )
1180 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1181 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1182 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1183 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1186 public function testLoadWatchedItem_noItem() {
1187 $mockDb = $this->getMockDb();
1188 $mockDb->expects( $this->once() )
1189 ->method( 'selectRow' )
1192 'wl_notificationtimestamp',
1195 'wl_namespace' => 0,
1196 'wl_title' => 'SomeDbKey',
1199 ->will( $this->returnValue( [] ) );
1201 $mockCache = $this->getMockCache();
1202 $mockCache->expects( $this->never() )->method( 'get' );
1203 $mockCache->expects( $this->never() )->method( 'delete' );
1205 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1208 $store->loadWatchedItem(
1209 new UserIdentityValue( 1, 'MockUser', 0 ),
1210 new TitleValue( 0, 'SomeDbKey' )
1215 public function testLoadWatchedItem_anonymousUser() {
1216 $mockDb = $this->getMockDb();
1217 $mockDb->expects( $this->never() )
1218 ->method( 'selectRow' );
1220 $mockCache = $this->getMockCache();
1221 $mockCache->expects( $this->never() )->method( 'get' );
1222 $mockCache->expects( $this->never() )->method( 'delete' );
1224 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1227 $store->loadWatchedItem(
1228 new UserIdentityValue( 0, 'AnonUser', 0 ),
1229 new TitleValue( 0, 'SomeDbKey' )
1234 public function testRemoveWatch_existingItem() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->once() )
1237 ->method( 'delete' )
1243 'wl_namespace' => 0,
1244 'wl_title' => [ 'SomeDbKey' ],
1251 'wl_namespace' => 1,
1252 'wl_title' => [ 'SomeDbKey' ],
1256 $mockDb->expects( $this->exactly( 1 ) )
1257 ->method( 'affectedRows' )
1260 $mockCache = $this->getMockCache();
1261 $mockCache->expects( $this->never() )->method( 'get' );
1262 $mockCache->expects( $this->once() )
1263 ->method( 'delete' )
1265 [ '0:SomeDbKey:1' ],
1269 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1272 $store->removeWatch(
1273 new UserIdentityValue( 1, 'MockUser', 0 ),
1274 new TitleValue( 0, 'SomeDbKey' )
1279 public function testRemoveWatch_noItem() {
1280 $mockDb = $this->getMockDb();
1281 $mockDb->expects( $this->once() )
1282 ->method( 'delete' )
1288 'wl_namespace' => 0,
1289 'wl_title' => [ 'SomeDbKey' ],
1296 'wl_namespace' => 1,
1297 'wl_title' => [ 'SomeDbKey' ],
1302 $mockDb->expects( $this->once() )
1303 ->method( 'affectedRows' )
1306 $mockCache = $this->getMockCache();
1307 $mockCache->expects( $this->never() )->method( 'get' );
1308 $mockCache->expects( $this->once() )
1309 ->method( 'delete' )
1311 [ '0:SomeDbKey:1' ],
1315 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1318 $store->removeWatch(
1319 new UserIdentityValue( 1, 'MockUser', 0 ),
1320 new TitleValue( 0, 'SomeDbKey' )
1325 public function testRemoveWatch_anonymousUser() {
1326 $mockDb = $this->getMockDb();
1327 $mockDb->expects( $this->never() )
1328 ->method( 'delete' );
1330 $mockCache = $this->getMockCache();
1331 $mockCache->expects( $this->never() )->method( 'get' );
1332 $mockCache->expects( $this->never() )
1333 ->method( 'delete' );
1335 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1338 $store->removeWatch(
1339 new UserIdentityValue( 0, 'AnonUser', 0 ),
1340 new TitleValue( 0, 'SomeDbKey' )
1345 public function testGetWatchedItem_existingItem() {
1346 $mockDb = $this->getMockDb();
1347 $mockDb->expects( $this->once() )
1348 ->method( 'selectRow' )
1351 'wl_notificationtimestamp',
1354 'wl_namespace' => 0,
1355 'wl_title' => 'SomeDbKey',
1358 ->will( $this->returnValue(
1359 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1362 $mockCache = $this->getMockCache();
1363 $mockCache->expects( $this->never() )->method( 'delete' );
1364 $mockCache->expects( $this->once() )
1369 ->will( $this->returnValue( null ) );
1370 $mockCache->expects( $this->once() )
1376 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1378 $watchedItem = $store->getWatchedItem(
1379 new UserIdentityValue( 1, 'MockUser', 0 ),
1380 new TitleValue( 0, 'SomeDbKey' )
1382 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1383 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1384 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1385 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1388 public function testGetWatchedItem_cachedItem() {
1389 $mockDb = $this->getMockDb();
1390 $mockDb->expects( $this->never() )
1391 ->method( 'selectRow' );
1393 $mockUser = new UserIdentityValue( 1, 'MockUser', 0 );
1394 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1395 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1397 $mockCache = $this->getMockCache();
1398 $mockCache->expects( $this->never() )->method( 'delete' );
1399 $mockCache->expects( $this->never() )->method( 'set' );
1400 $mockCache->expects( $this->once() )
1405 ->will( $this->returnValue( $cachedItem ) );
1407 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1409 $this->assertEquals(
1411 $store->getWatchedItem(
1418 public function testGetWatchedItem_noItem() {
1419 $mockDb = $this->getMockDb();
1420 $mockDb->expects( $this->once() )
1421 ->method( 'selectRow' )
1424 'wl_notificationtimestamp',
1427 'wl_namespace' => 0,
1428 'wl_title' => 'SomeDbKey',
1431 ->will( $this->returnValue( [] ) );
1433 $mockCache = $this->getMockCache();
1434 $mockCache->expects( $this->never() )->method( 'set' );
1435 $mockCache->expects( $this->never() )->method( 'delete' );
1436 $mockCache->expects( $this->once() )
1438 ->with( '0:SomeDbKey:1' )
1439 ->will( $this->returnValue( false ) );
1441 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1444 $store->getWatchedItem(
1445 new UserIdentityValue( 1, 'MockUser', 0 ),
1446 new TitleValue( 0, 'SomeDbKey' )
1451 public function testGetWatchedItem_anonymousUser() {
1452 $mockDb = $this->getMockDb();
1453 $mockDb->expects( $this->never() )
1454 ->method( 'selectRow' );
1456 $mockCache = $this->getMockCache();
1457 $mockCache->expects( $this->never() )->method( 'set' );
1458 $mockCache->expects( $this->never() )->method( 'get' );
1459 $mockCache->expects( $this->never() )->method( 'delete' );
1461 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1464 $store->getWatchedItem(
1465 new UserIdentityValue( 0, 'AnonUser', 0 ),
1466 new TitleValue( 0, 'SomeDbKey' )
1471 public function testGetWatchedItemsForUser() {
1472 $mockDb = $this->getMockDb();
1473 $mockDb->expects( $this->once() )
1474 ->method( 'select' )
1477 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1480 ->will( $this->returnValue( [
1481 $this->getFakeRow( [
1482 'wl_namespace' => 0,
1483 'wl_title' => 'Foo1',
1484 'wl_notificationtimestamp' => '20151212010101',
1486 $this->getFakeRow( [
1487 'wl_namespace' => 1,
1488 'wl_title' => 'Foo2',
1489 'wl_notificationtimestamp' => null,
1493 $mockCache = $this->getMockCache();
1494 $mockCache->expects( $this->never() )->method( 'delete' );
1495 $mockCache->expects( $this->never() )->method( 'get' );
1496 $mockCache->expects( $this->never() )->method( 'set' );
1498 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1499 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1501 $watchedItems = $store->getWatchedItemsForUser( $user );
1503 $this->assertInternalType( 'array', $watchedItems );
1504 $this->assertCount( 2, $watchedItems );
1505 foreach ( $watchedItems as $watchedItem ) {
1506 $this->assertInstanceOf( WatchedItem
::class, $watchedItem );
1508 $this->assertEquals(
1509 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1512 $this->assertEquals(
1513 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1518 public function provideDbTypes() {
1520 [ false, DB_REPLICA
],
1521 [ true, DB_MASTER
],
1526 * @dataProvider provideDbTypes
1528 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1529 $mockDb = $this->getMockDb();
1530 $mockCache = $this->getMockCache();
1531 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1532 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1534 $mockDb->expects( $this->once() )
1535 ->method( 'select' )
1538 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1540 $this->isType( 'string' ),
1541 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1543 ->will( $this->returnValue( [] ) );
1545 $store = $this->newWatchedItemStore(
1546 [ 'lbFactory' => $mockLoadBalancer, 'cache' => $mockCache ] );
1548 $watchedItems = $store->getWatchedItemsForUser(
1550 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1552 $this->assertEquals( [], $watchedItems );
1555 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1556 $store = $this->newWatchedItemStore();
1558 $this->setExpectedException( InvalidArgumentException
::class );
1559 $store->getWatchedItemsForUser(
1560 new UserIdentityValue( 1, 'MockUser', 0 ),
1565 public function testIsWatchedItem_existingItem() {
1566 $mockDb = $this->getMockDb();
1567 $mockDb->expects( $this->once() )
1568 ->method( 'selectRow' )
1571 'wl_notificationtimestamp',
1574 'wl_namespace' => 0,
1575 'wl_title' => 'SomeDbKey',
1578 ->will( $this->returnValue(
1579 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1582 $mockCache = $this->getMockCache();
1583 $mockCache->expects( $this->never() )->method( 'delete' );
1584 $mockCache->expects( $this->once() )
1586 ->with( '0:SomeDbKey:1' )
1587 ->will( $this->returnValue( false ) );
1588 $mockCache->expects( $this->once() )
1594 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1598 new UserIdentityValue( 1, 'MockUser', 0 ),
1599 new TitleValue( 0, 'SomeDbKey' )
1604 public function testIsWatchedItem_noItem() {
1605 $mockDb = $this->getMockDb();
1606 $mockDb->expects( $this->once() )
1607 ->method( 'selectRow' )
1610 'wl_notificationtimestamp',
1613 'wl_namespace' => 0,
1614 'wl_title' => 'SomeDbKey',
1617 ->will( $this->returnValue( [] ) );
1619 $mockCache = $this->getMockCache();
1620 $mockCache->expects( $this->never() )->method( 'set' );
1621 $mockCache->expects( $this->never() )->method( 'delete' );
1622 $mockCache->expects( $this->once() )
1624 ->with( '0:SomeDbKey:1' )
1625 ->will( $this->returnValue( false ) );
1627 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1631 new UserIdentityValue( 1, 'MockUser', 0 ),
1632 new TitleValue( 0, 'SomeDbKey' )
1637 public function testIsWatchedItem_anonymousUser() {
1638 $mockDb = $this->getMockDb();
1639 $mockDb->expects( $this->never() )
1640 ->method( 'selectRow' );
1642 $mockCache = $this->getMockCache();
1643 $mockCache->expects( $this->never() )->method( 'set' );
1644 $mockCache->expects( $this->never() )->method( 'get' );
1645 $mockCache->expects( $this->never() )->method( 'delete' );
1647 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1651 new UserIdentityValue( 0, 'AnonUser', 0 ),
1652 new TitleValue( 0, 'SomeDbKey' )
1657 public function testGetNotificationTimestampsBatch() {
1659 new TitleValue( 0, 'SomeDbKey' ),
1660 new TitleValue( 1, 'AnotherDbKey' ),
1663 $mockDb = $this->getMockDb();
1665 $this->getFakeRow( [
1666 'wl_namespace' => '0',
1667 'wl_title' => 'SomeDbKey',
1668 'wl_notificationtimestamp' => '20151212010101',
1672 'wl_namespace' => '1',
1673 'wl_title' => 'AnotherDbKey',
1674 'wl_notificationtimestamp' => null,
1679 $mockDb->expects( $this->once() )
1680 ->method( 'makeWhereFrom2d' )
1682 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1683 $this->isType( 'string' ),
1684 $this->isType( 'string' )
1686 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1687 $mockDb->expects( $this->once() )
1688 ->method( 'select' )
1691 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1693 'makeWhereFrom2d return value',
1696 $this->isType( 'string' )
1698 ->will( $this->returnValue( $dbResult ) );
1700 $mockCache = $this->getMockCache();
1701 $mockCache->expects( $this->exactly( 2 ) )
1704 [ '0:SomeDbKey:1' ],
1705 [ '1:AnotherDbKey:1' ]
1707 ->will( $this->returnValue( null ) );
1708 $mockCache->expects( $this->never() )->method( 'set' );
1709 $mockCache->expects( $this->never() )->method( 'delete' );
1711 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1713 $this->assertEquals(
1715 0 => [ 'SomeDbKey' => '20151212010101', ],
1716 1 => [ 'AnotherDbKey' => null, ],
1718 $store->getNotificationTimestampsBatch(
1719 new UserIdentityValue( 1, 'MockUser', 0 ), $targets )
1723 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1725 new TitleValue( 0, 'OtherDbKey' ),
1728 $mockDb = $this->getMockDb();
1730 $mockDb->expects( $this->once() )
1731 ->method( 'makeWhereFrom2d' )
1733 [ [ 'OtherDbKey' => 1 ] ],
1734 $this->isType( 'string' ),
1735 $this->isType( 'string' )
1737 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1738 $mockDb->expects( $this->once() )
1739 ->method( 'select' )
1742 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1744 'makeWhereFrom2d return value',
1747 $this->isType( 'string' )
1749 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1751 $mockCache = $this->getMockCache();
1752 $mockCache->expects( $this->once() )
1754 ->with( '0:OtherDbKey:1' )
1755 ->will( $this->returnValue( null ) );
1756 $mockCache->expects( $this->never() )->method( 'set' );
1757 $mockCache->expects( $this->never() )->method( 'delete' );
1759 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1761 $this->assertEquals(
1763 0 => [ 'OtherDbKey' => false, ],
1765 $store->getNotificationTimestampsBatch(
1766 new UserIdentityValue( 1, 'MockUser', 0 ), $targets )
1770 public function testGetNotificationTimestampsBatch_cachedItem() {
1772 new TitleValue( 0, 'SomeDbKey' ),
1773 new TitleValue( 1, 'AnotherDbKey' ),
1776 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1777 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1779 $mockDb = $this->getMockDb();
1781 $mockDb->expects( $this->once() )
1782 ->method( 'makeWhereFrom2d' )
1784 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1785 $this->isType( 'string' ),
1786 $this->isType( 'string' )
1788 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1789 $mockDb->expects( $this->once() )
1790 ->method( 'select' )
1793 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1795 'makeWhereFrom2d return value',
1798 $this->isType( 'string' )
1800 ->will( $this->returnValue( [
1802 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1806 $mockCache = $this->getMockCache();
1807 $mockCache->expects( $this->at( 1 ) )
1809 ->with( '0:SomeDbKey:1' )
1810 ->will( $this->returnValue( $cachedItem ) );
1811 $mockCache->expects( $this->at( 3 ) )
1813 ->with( '1:AnotherDbKey:1' )
1814 ->will( $this->returnValue( null ) );
1815 $mockCache->expects( $this->never() )->method( 'set' );
1816 $mockCache->expects( $this->never() )->method( 'delete' );
1818 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1820 $this->assertEquals(
1822 0 => [ 'SomeDbKey' => '20151212010101', ],
1823 1 => [ 'AnotherDbKey' => null, ],
1825 $store->getNotificationTimestampsBatch( $user, $targets )
1829 public function testGetNotificationTimestampsBatch_allItemsCached() {
1831 new TitleValue( 0, 'SomeDbKey' ),
1832 new TitleValue( 1, 'AnotherDbKey' ),
1835 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1837 new WatchedItem( $user, $targets[0], '20151212010101' ),
1838 new WatchedItem( $user, $targets[1], null ),
1840 $mockDb = $this->createNoOpMock( IDatabase
::class );
1842 $mockCache = $this->getMockCache();
1843 $mockCache->expects( $this->at( 1 ) )
1845 ->with( '0:SomeDbKey:1' )
1846 ->will( $this->returnValue( $cachedItems[0] ) );
1847 $mockCache->expects( $this->at( 3 ) )
1849 ->with( '1:AnotherDbKey:1' )
1850 ->will( $this->returnValue( $cachedItems[1] ) );
1851 $mockCache->expects( $this->never() )->method( 'set' );
1852 $mockCache->expects( $this->never() )->method( 'delete' );
1854 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1856 $this->assertEquals(
1858 0 => [ 'SomeDbKey' => '20151212010101', ],
1859 1 => [ 'AnotherDbKey' => null, ],
1861 $store->getNotificationTimestampsBatch( $user, $targets )
1865 public function testGetNotificationTimestampsBatch_anonymousUser() {
1866 if ( defined( 'HHVM_VERSION' ) ) {
1867 $this->markTestSkipped( 'HHVM Reflection buggy' );
1871 new TitleValue( 0, 'SomeDbKey' ),
1872 new TitleValue( 1, 'AnotherDbKey' ),
1875 $mockDb = $this->createNoOpMock( IDatabase
::class );
1877 $mockCache = $this->createNoOpMock( HashBagOStuff
::class );
1879 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1881 $this->assertEquals(
1883 0 => [ 'SomeDbKey' => false, ],
1884 1 => [ 'AnotherDbKey' => false, ],
1886 $store->getNotificationTimestampsBatch(
1887 new UserIdentityValue( 0, 'AnonUser', 0 ), $targets )
1891 public function testResetNotificationTimestamp_anonymousUser() {
1892 $mockDb = $this->getMockDb();
1893 $mockDb->expects( $this->never() )
1894 ->method( 'selectRow' );
1896 $mockCache = $this->getMockCache();
1897 $mockCache->expects( $this->never() )->method( 'get' );
1898 $mockCache->expects( $this->never() )->method( 'set' );
1899 $mockCache->expects( $this->never() )->method( 'delete' );
1901 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1904 $store->resetNotificationTimestamp(
1905 new UserIdentityValue( 0, 'AnonUser', 0 ),
1906 new TitleValue( 0, 'SomeDbKey' )
1911 public function testResetNotificationTimestamp_noItem() {
1912 $mockDb = $this->getMockDb();
1913 $mockDb->expects( $this->once() )
1914 ->method( 'selectRow' )
1917 'wl_notificationtimestamp',
1920 'wl_namespace' => 0,
1921 'wl_title' => 'SomeDbKey',
1924 ->will( $this->returnValue( [] ) );
1926 $mockCache = $this->getMockCache();
1927 $mockCache->expects( $this->never() )->method( 'get' );
1928 $mockCache->expects( $this->never() )->method( 'set' );
1929 $mockCache->expects( $this->never() )->method( 'delete' );
1931 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
1934 $store->resetNotificationTimestamp(
1935 new UserIdentityValue( 1, 'MockUser', 0 ),
1936 new TitleValue( 0, 'SomeDbKey' )
1941 public function testResetNotificationTimestamp_item() {
1942 $user = new UserIdentityValue( 1, 'MockUser', 0 );
1943 $title = new TitleValue( 0, 'SomeDbKey' );
1945 $mockDb = $this->getMockDb();
1946 $mockDb->expects( $this->once() )
1947 ->method( 'selectRow' )
1950 'wl_notificationtimestamp',
1953 'wl_namespace' => 0,
1954 'wl_title' => 'SomeDbKey',
1957 ->will( $this->returnValue(
1958 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1961 $mockCache = $this->getMockCache();
1962 $mockCache->expects( $this->never() )->method( 'get' );
1963 $mockCache->expects( $this->once() )
1967 $this->isInstanceOf( WatchedItem
::class )
1969 $mockCache->expects( $this->once() )
1970 ->method( 'delete' )
1971 ->with( '0:SomeDbKey:1' );
1973 $mockQueueGroup = $this->getMockJobQueueGroup();
1974 $mockQueueGroup->expects( $this->once() )
1975 ->method( 'lazyPush' )
1976 ->willReturnCallback( function ( ActivityUpdateJob
$job ) {
1980 // We don't care if these methods actually do anything here
1981 $mockRevisionLookup = $this->getMockRevisionLookup( [
1982 'getRevisionByTitle' => function () {
1985 'getTimestampFromId' => function () {
1986 return '00000000000000';
1990 $store = $this->newWatchedItemStore( [
1992 'queueGroup' => $mockQueueGroup,
1993 'cache' => $mockCache,
1994 'revisionLookup' => $mockRevisionLookup,
1998 $store->resetNotificationTimestamp(
2005 public function testResetNotificationTimestamp_noItemForced() {
2006 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2007 $title = new TitleValue( 0, 'SomeDbKey' );
2009 $mockDb = $this->getMockDb();
2010 $mockDb->expects( $this->never() )
2011 ->method( 'selectRow' );
2013 $mockCache = $this->getMockCache();
2014 $mockCache->expects( $this->never() )->method( 'get' );
2015 $mockCache->expects( $this->never() )->method( 'set' );
2016 $mockCache->expects( $this->once() )
2017 ->method( 'delete' )
2018 ->with( '0:SomeDbKey:1' );
2020 $mockQueueGroup = $this->getMockJobQueueGroup();
2022 // We don't care if these methods actually do anything here
2023 $mockRevisionLookup = $this->getMockRevisionLookup( [
2024 'getRevisionByTitle' => function () {
2027 'getTimestampFromId' => function () {
2028 return '00000000000000';
2032 $store = $this->newWatchedItemStore( [
2034 'queueGroup' => $mockQueueGroup,
2035 'cache' => $mockCache,
2036 'revisionLookup' => $mockRevisionLookup,
2039 $mockQueueGroup->expects( $this->any() )
2040 ->method( 'lazyPush' )
2041 ->will( $this->returnCallback( function ( ActivityUpdateJob
$job ) {
2046 $store->resetNotificationTimestamp(
2054 private function verifyCallbackJob(
2055 ActivityUpdateJob
$job,
2056 LinkTarget
$expectedTitle,
2058 callable
$notificationTimestampCondition
2060 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2061 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2063 $jobParams = $job->getParams();
2064 $this->assertArrayHasKey( 'type', $jobParams );
2065 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2066 $this->assertArrayHasKey( 'userid', $jobParams );
2067 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2068 $this->assertArrayHasKey( 'notifTime', $jobParams );
2069 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2072 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2073 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2075 $title = new TitleValue( 0, 'SomeTitle' );
2077 $mockDb = $this->getMockDb();
2078 $mockDb->expects( $this->never() )
2079 ->method( 'selectRow' );
2081 $mockCache = $this->getMockCache();
2082 $mockCache->expects( $this->never() )->method( 'get' );
2083 $mockCache->expects( $this->never() )->method( 'set' );
2084 $mockCache->expects( $this->once() )
2085 ->method( 'delete' )
2086 ->with( '0:SomeTitle:1' );
2088 $mockQueueGroup = $this->getMockJobQueueGroup();
2090 $mockRevisionRecord = $this->createNoOpMock( RevisionRecord
::class );
2092 $mockRevisionLookup = $this->getMockRevisionLookup( [
2093 'getTimestampFromId' => function () {
2094 return '00000000000000';
2096 'getRevisionById' => function ( $id, $flags ) use ( $oldid, $mockRevisionRecord ) {
2097 $this->assertSame( $oldid, $id );
2098 $this->assertSame( 0, $flags );
2099 return $mockRevisionRecord;
2101 'getNextRevision' =>
2102 function ( $oldRev ) use ( $mockRevisionRecord ) {
2103 $this->assertSame( $mockRevisionRecord, $oldRev );
2107 'getNextRevision' => 1,
2110 $store = $this->newWatchedItemStore( [
2112 'queueGroup' => $mockQueueGroup,
2113 'cache' => $mockCache,
2114 'revisionLookup' => $mockRevisionLookup,
2117 $mockQueueGroup->expects( $this->any() )
2118 ->method( 'lazyPush' )
2119 ->will( $this->returnCallback(
2120 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2121 $this->verifyCallbackJob(
2125 function ( $time ) {
2126 return $time === null;
2133 $store->resetNotificationTimestamp(
2142 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2143 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2145 $title = new TitleValue( 0, 'SomeDbKey' );
2147 $mockRevision = $this->createNoOpMock( RevisionRecord
::class );
2148 $mockNextRevision = $this->createNoOpMock( RevisionRecord
::class );
2150 $mockDb = $this->getMockDb();
2151 $mockDb->expects( $this->once() )
2152 ->method( 'selectRow' )
2155 'wl_notificationtimestamp',
2158 'wl_namespace' => 0,
2159 'wl_title' => 'SomeDbKey',
2162 ->will( $this->returnValue(
2163 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2166 $mockCache = $this->getMockCache();
2167 $mockCache->expects( $this->never() )->method( 'get' );
2168 $mockCache->expects( $this->once() )
2170 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2171 $mockCache->expects( $this->once() )
2172 ->method( 'delete' )
2173 ->with( '0:SomeDbKey:1' );
2175 $mockQueueGroup = $this->getMockJobQueueGroup();
2177 $mockRevisionLookup = $this->getMockRevisionLookup(
2179 'getTimestampFromId' => function ( $oldidParam ) use ( $oldid ) {
2180 $this->assertSame( $oldid, $oldidParam );
2182 'getRevisionById' => function ( $id ) use ( $oldid, $mockRevision ) {
2183 $this->assertSame( $oldid, $id );
2184 return $mockRevision;
2186 'getNextRevision' =>
2187 function ( RevisionRecord
$rev ) use ( $mockRevision, $mockNextRevision ) {
2188 $this->assertSame( $mockRevision, $rev );
2189 return $mockNextRevision;
2193 'getTimestampFromId' => 2,
2194 'getRevisionById' => 1,
2195 'getNextRevision' => 1,
2198 $store = $this->newWatchedItemStore( [
2200 'queueGroup' => $mockQueueGroup,
2201 'cache' => $mockCache,
2202 'revisionLookup' => $mockRevisionLookup,
2205 $mockQueueGroup->expects( $this->any() )
2206 ->method( 'lazyPush' )
2207 ->will( $this->returnCallback(
2208 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2209 $this->verifyCallbackJob(
2213 function ( $time ) {
2214 return $time !== null && $time > '20151212010101';
2221 $store->resetNotificationTimestamp(
2230 public function testResetNotificationTimestamp_notWatchedPageForced() {
2231 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2233 $title = new TitleValue( 0, 'SomeDbKey' );
2235 $mockDb = $this->getMockDb();
2236 $mockDb->expects( $this->once() )
2237 ->method( 'selectRow' )
2240 'wl_notificationtimestamp',
2243 'wl_namespace' => 0,
2244 'wl_title' => 'SomeDbKey',
2247 ->will( $this->returnValue( false ) );
2249 $mockCache = $this->getMockCache();
2250 $mockCache->expects( $this->never() )->method( 'get' );
2251 $mockCache->expects( $this->never() )->method( 'set' );
2252 $mockCache->expects( $this->once() )
2253 ->method( 'delete' )
2254 ->with( '0:SomeDbKey:1' );
2256 $mockQueueGroup = $this->getMockJobQueueGroup();
2258 $mockRevision = $this->createNoOpMock( RevisionRecord
::class );
2259 $mockNextRevision = $this->createNoOpMock( RevisionRecord
::class );
2261 $mockRevisionLookup = $this->getMockRevisionLookup(
2263 'getTimestampFromId' => function ( $oldidParam ) use ( $oldid ) {
2264 $this->assertSame( $oldid, $oldidParam );
2266 'getRevisionById' => function ( $id ) use ( $oldid, $mockRevision ) {
2267 $this->assertSame( $oldid, $id );
2268 return $mockRevision;
2270 'getNextRevision' =>
2271 function ( RevisionRecord
$rev ) use ( $mockRevision, $mockNextRevision ) {
2272 $this->assertSame( $mockRevision, $rev );
2273 return $mockNextRevision;
2277 'getTimestampFromId' => 1,
2278 'getRevisionById' => 1,
2279 'getNextRevision' => 1,
2283 $store = $this->newWatchedItemStore( [
2285 'queueGroup' => $mockQueueGroup,
2286 'cache' => $mockCache,
2287 'revisionLookup' => $mockRevisionLookup,
2290 $mockQueueGroup->expects( $this->any() )
2291 ->method( 'lazyPush' )
2292 ->will( $this->returnCallback(
2293 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2294 $this->verifyCallbackJob(
2298 function ( $time ) {
2299 return $time === null;
2306 $store->resetNotificationTimestamp(
2315 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2316 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2318 $title = new TitleValue( 0, 'SomeDbKey' );
2320 $mockDb = $this->getMockDb();
2321 $mockDb->expects( $this->once() )
2322 ->method( 'selectRow' )
2325 'wl_notificationtimestamp',
2328 'wl_namespace' => 0,
2329 'wl_title' => 'SomeDbKey',
2332 ->will( $this->returnValue(
2333 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2336 $mockCache = $this->getMockCache();
2337 $mockCache->expects( $this->never() )->method( 'get' );
2338 $mockCache->expects( $this->once() )
2340 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2341 $mockCache->expects( $this->once() )
2342 ->method( 'delete' )
2343 ->with( '0:SomeDbKey:1' );
2345 $mockQueueGroup = $this->getMockJobQueueGroup();
2347 $mockRevision = $this->createNoOpMock( RevisionRecord
::class );
2348 $mockNextRevision = $this->createNoOpMock( RevisionRecord
::class );
2350 $mockRevisionLookup = $this->getMockRevisionLookup(
2352 'getTimestampFromId' => function ( $oldidParam ) use ( $oldid ) {
2353 $this->assertEquals( $oldid, $oldidParam );
2355 'getRevisionById' => function ( $id ) use ( $oldid, $mockRevision ) {
2356 $this->assertSame( $oldid, $id );
2357 return $mockRevision;
2359 'getNextRevision' =>
2360 function ( RevisionRecord
$rev ) use ( $mockRevision, $mockNextRevision ) {
2361 $this->assertSame( $mockRevision, $rev );
2362 return $mockNextRevision;
2366 'getTimestampFromId' => 2,
2367 'getRevisionById' => 1,
2368 'getNextRevision' => 1,
2372 $store = $this->newWatchedItemStore( [
2374 'queueGroup' => $mockQueueGroup,
2375 'cache' => $mockCache,
2376 'revisionLookup' => $mockRevisionLookup,
2379 $mockQueueGroup->expects( $this->any() )
2380 ->method( 'lazyPush' )
2381 ->will( $this->returnCallback(
2382 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2383 $this->verifyCallbackJob(
2387 function ( $time ) {
2388 return $time === '30151212010101';
2395 $store->resetNotificationTimestamp(
2404 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2405 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2407 $title = new TitleValue( 0, 'SomeDbKey' );
2409 $mockDb = $this->getMockDb();
2410 $mockDb->expects( $this->once() )
2411 ->method( 'selectRow' )
2414 'wl_notificationtimestamp',
2417 'wl_namespace' => 0,
2418 'wl_title' => 'SomeDbKey',
2421 ->will( $this->returnValue(
2422 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2425 $mockCache = $this->getMockCache();
2426 $mockCache->expects( $this->never() )->method( 'get' );
2427 $mockCache->expects( $this->once() )
2429 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2430 $mockCache->expects( $this->once() )
2431 ->method( 'delete' )
2432 ->with( '0:SomeDbKey:1' );
2434 $mockQueueGroup = $this->getMockJobQueueGroup();
2436 $mockRevision = $this->createNoOpMock( RevisionRecord
::class );
2437 $mockNextRevision = $this->createNoOpMock( RevisionRecord
::class );
2439 $mockRevisionLookup = $this->getMockRevisionLookup(
2441 'getTimestampFromId' => function ( $oldidParam ) use ( $oldid ) {
2442 $this->assertEquals( $oldid, $oldidParam );
2444 'getRevisionById' => function ( $id ) use ( $oldid, $mockRevision ) {
2445 $this->assertSame( $oldid, $id );
2446 return $mockRevision;
2448 'getNextRevision' =>
2449 function ( RevisionRecord
$rev ) use ( $mockRevision, $mockNextRevision ) {
2450 $this->assertSame( $mockRevision, $rev );
2451 return $mockNextRevision;
2455 'getTimestampFromId' => 2,
2456 'getRevisionById' => 1,
2457 'getNextRevision' => 1,
2460 $store = $this->newWatchedItemStore( [
2462 'queueGroup' => $mockQueueGroup,
2463 'cache' => $mockCache,
2464 'revisionLookup' => $mockRevisionLookup,
2467 $mockQueueGroup->expects( $this->any() )
2468 ->method( 'lazyPush' )
2469 ->will( $this->returnCallback(
2470 function ( ActivityUpdateJob
$job ) use ( $title, $user ) {
2471 $this->verifyCallbackJob(
2475 function ( $time ) {
2476 return $time === false;
2483 $store->resetNotificationTimestamp(
2492 public function testSetNotificationTimestampsForUser_anonUser() {
2493 $store = $this->newWatchedItemStore();
2494 $this->assertFalse( $store->setNotificationTimestampsForUser(
2495 new UserIdentityValue( 0, 'AnonUser', 0 ), '' ) );
2498 public function testSetNotificationTimestampsForUser_allRows() {
2499 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2500 $timestamp = '20100101010101';
2502 $store = $this->newWatchedItemStore();
2504 // Note: This does not actually assert the job is correct
2505 $callableCallCounter = 0;
2506 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2507 $callableCallCounter++
;
2508 $this->assertInternalType( 'callable', $callable );
2510 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2513 $store->setNotificationTimestampsForUser( $user, $timestamp )
2515 $this->assertEquals( 1, $callableCallCounter );
2518 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2519 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2522 $store = $this->newWatchedItemStore();
2524 // Note: This does not actually assert the job is correct
2525 $callableCallCounter = 0;
2526 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2527 $callableCallCounter++
;
2528 $this->assertInternalType( 'callable', $callable );
2530 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2533 $store->setNotificationTimestampsForUser( $user, $timestamp )
2537 public function testSetNotificationTimestampsForUser_specificTargets() {
2538 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2539 $timestamp = '20100101010101';
2540 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2542 $mockDb = $this->getMockDb();
2543 $mockDb->expects( $this->once() )
2544 ->method( 'update' )
2547 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2548 [ 'wl_user' => 1, 'wl_namespace' => 0, 'wl_title' => [ 'Foo', 'Bar' ] ]
2550 ->will( $this->returnValue( true ) );
2551 $mockDb->expects( $this->exactly( 1 ) )
2552 ->method( 'timestamp' )
2553 ->will( $this->returnCallback( function ( $value ) {
2554 return 'TS' . $value . 'TS';
2556 $mockDb->expects( $this->once() )
2557 ->method( 'affectedRows' )
2558 ->will( $this->returnValue( 2 ) );
2560 $store = $this->newWatchedItemStore( [ 'db' => $mockDb ] );
2563 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2567 public function testUpdateNotificationTimestamp_watchersExist() {
2568 $mockDb = $this->getMockDb();
2569 $mockDb->expects( $this->once() )
2570 ->method( 'selectFieldValues' )
2576 'wl_namespace' => 0,
2577 'wl_title' => 'SomeDbKey',
2578 'wl_notificationtimestamp IS NULL'
2581 ->will( $this->returnValue( [ '2', '3' ] ) );
2582 $mockDb->expects( $this->once() )
2583 ->method( 'update' )
2586 [ 'wl_notificationtimestamp' => null ],
2588 'wl_user' => [ 2, 3 ],
2589 'wl_namespace' => 0,
2590 'wl_title' => 'SomeDbKey',
2594 $mockCache = $this->getMockCache();
2595 $mockCache->expects( $this->never() )->method( 'set' );
2596 $mockCache->expects( $this->never() )->method( 'get' );
2597 $mockCache->expects( $this->never() )->method( 'delete' );
2599 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
2601 $this->assertEquals(
2603 $store->updateNotificationTimestamp(
2604 new UserIdentityValue( 1, 'MockUser', 0 ),
2605 new TitleValue( 0, 'SomeDbKey' ),
2611 public function testUpdateNotificationTimestamp_noWatchers() {
2612 $mockDb = $this->getMockDb();
2613 $mockDb->expects( $this->once() )
2614 ->method( 'selectFieldValues' )
2620 'wl_namespace' => 0,
2621 'wl_title' => 'SomeDbKey',
2622 'wl_notificationtimestamp IS NULL'
2626 $this->returnValue( [] )
2628 $mockDb->expects( $this->never() )
2629 ->method( 'update' );
2631 $mockCache = $this->getMockCache();
2632 $mockCache->expects( $this->never() )->method( 'set' );
2633 $mockCache->expects( $this->never() )->method( 'get' );
2634 $mockCache->expects( $this->never() )->method( 'delete' );
2636 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
2638 $watchers = $store->updateNotificationTimestamp(
2639 new UserIdentityValue( 1, 'MockUser', 0 ),
2640 new TitleValue( 0, 'SomeDbKey' ),
2643 $this->assertInternalType( 'array', $watchers );
2644 $this->assertEmpty( $watchers );
2647 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2648 $user = new UserIdentityValue( 1, 'MockUser', 0 );
2649 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2651 $mockDb = $this->getMockDb();
2652 $mockDb->expects( $this->once() )
2653 ->method( 'selectRow' )
2654 ->will( $this->returnValue(
2655 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2657 $mockDb->expects( $this->once() )
2658 ->method( 'selectFieldValues' )
2660 $this->returnValue( [ '2', '3' ] )
2662 $mockDb->expects( $this->once() )
2663 ->method( 'update' );
2665 $mockCache = $this->getMockCache();
2666 $mockCache->expects( $this->once() )
2668 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2669 $mockCache->expects( $this->once() )
2671 ->with( '0:SomeDbKey:1' );
2672 $mockCache->expects( $this->once() )
2673 ->method( 'delete' )
2674 ->with( '0:SomeDbKey:1' );
2676 $store = $this->newWatchedItemStore( [ 'db' => $mockDb, 'cache' => $mockCache ] );
2678 // This will add the item to the cache
2679 $store->getWatchedItem( $user, $titleValue );
2681 $store->updateNotificationTimestamp(
2682 new UserIdentityValue( 1, 'MockUser', 0 ),