rdbms: Rename "memCache" to "memStash" in LBFactory
[mediawiki.git] / tests / phpunit / includes / WatchedItemStoreUnitTest.php
blob950e22084578e63a9afb2870f3da6e0ab28ba842
1 <?php
2 use MediaWiki\Linker\LinkTarget;
3 use Wikimedia\ScopedCallback;
5 /**
6 * @author Addshore
8 * @covers WatchedItemStore
9 */
10 class WatchedItemStoreUnitTest extends MediaWikiTestCase {
12 /**
13 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
15 private function getMockDb() {
16 return $this->createMock( IDatabase::class );
19 /**
20 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
22 private function getMockLoadBalancer(
23 $mockDb,
24 $expectedConnectionType = null
25 ) {
26 $mock = $this->getMockBuilder( LoadBalancer::class )
27 ->disableOriginalConstructor()
28 ->getMock();
29 if ( $expectedConnectionType !== null ) {
30 $mock->expects( $this->any() )
31 ->method( 'getConnectionRef' )
32 ->with( $expectedConnectionType )
33 ->will( $this->returnValue( $mockDb ) );
34 } else {
35 $mock->expects( $this->any() )
36 ->method( 'getConnectionRef' )
37 ->will( $this->returnValue( $mockDb ) );
39 return $mock;
42 /**
43 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
45 private function getMockCache() {
46 $mock = $this->getMockBuilder( HashBagOStuff::class )
47 ->disableOriginalConstructor()
48 ->getMock();
49 $mock->expects( $this->any() )
50 ->method( 'makeKey' )
51 ->will( $this->returnCallback( function () {
52 return implode( ':', func_get_args() );
53 } ) );
54 return $mock;
57 /**
58 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
60 private function getMockReadOnlyMode( $readOnly = false ) {
61 $mock = $this->getMockBuilder( ReadOnlyMode::class )
62 ->disableOriginalConstructor()
63 ->getMock();
64 $mock->expects( $this->any() )
65 ->method( 'isReadOnly' )
66 ->will( $this->returnValue( $readOnly ) );
67 return $mock;
70 /**
71 * @param int $id
72 * @return PHPUnit_Framework_MockObject_MockObject|User
74 private function getMockNonAnonUserWithId( $id ) {
75 $mock = $this->createMock( User::class );
76 $mock->expects( $this->any() )
77 ->method( 'isAnon' )
78 ->will( $this->returnValue( false ) );
79 $mock->expects( $this->any() )
80 ->method( 'getId' )
81 ->will( $this->returnValue( $id ) );
82 return $mock;
85 /**
86 * @return User
88 private function getAnonUser() {
89 return User::newFromName( 'Anon_User' );
92 private function getFakeRow( array $rowValues ) {
93 $fakeRow = new stdClass();
94 foreach ( $rowValues as $valueName => $value ) {
95 $fakeRow->$valueName = $value;
97 return $fakeRow;
100 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache,
101 ReadOnlyMode $readOnlyMode
103 return new WatchedItemStore(
104 $loadBalancer,
105 $cache,
106 $readOnlyMode
110 public function testCountWatchedItems() {
111 $user = $this->getMockNonAnonUserWithId( 1 );
113 $mockDb = $this->getMockDb();
114 $mockDb->expects( $this->exactly( 1 ) )
115 ->method( 'selectField' )
116 ->with(
117 'watchlist',
118 'COUNT(*)',
120 'wl_user' => $user->getId(),
122 $this->isType( 'string' )
124 ->will( $this->returnValue( 12 ) );
126 $mockCache = $this->getMockCache();
127 $mockCache->expects( $this->never() )->method( 'get' );
128 $mockCache->expects( $this->never() )->method( 'set' );
129 $mockCache->expects( $this->never() )->method( 'delete' );
131 $store = $this->newWatchedItemStore(
132 $this->getMockLoadBalancer( $mockDb ),
133 $mockCache,
134 $this->getMockReadOnlyMode()
137 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
140 public function testCountWatchers() {
141 $titleValue = new TitleValue( 0, 'SomeDbKey' );
143 $mockDb = $this->getMockDb();
144 $mockDb->expects( $this->exactly( 1 ) )
145 ->method( 'selectField' )
146 ->with(
147 'watchlist',
148 'COUNT(*)',
150 'wl_namespace' => $titleValue->getNamespace(),
151 'wl_title' => $titleValue->getDBkey(),
153 $this->isType( 'string' )
155 ->will( $this->returnValue( 7 ) );
157 $mockCache = $this->getMockCache();
158 $mockCache->expects( $this->never() )->method( 'get' );
159 $mockCache->expects( $this->never() )->method( 'set' );
160 $mockCache->expects( $this->never() )->method( 'delete' );
162 $store = $this->newWatchedItemStore(
163 $this->getMockLoadBalancer( $mockDb ),
164 $mockCache,
165 $this->getMockReadOnlyMode()
168 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
171 public function testCountWatchersMultiple() {
172 $titleValues = [
173 new TitleValue( 0, 'SomeDbKey' ),
174 new TitleValue( 0, 'OtherDbKey' ),
175 new TitleValue( 1, 'AnotherDbKey' ),
178 $mockDb = $this->getMockDb();
180 $dbResult = [
181 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
182 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
183 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
186 $mockDb->expects( $this->once() )
187 ->method( 'makeWhereFrom2d' )
188 ->with(
189 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
190 $this->isType( 'string' ),
191 $this->isType( 'string' )
193 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
194 $mockDb->expects( $this->once() )
195 ->method( 'select' )
196 ->with(
197 'watchlist',
198 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
199 [ 'makeWhereFrom2d return value' ],
200 $this->isType( 'string' ),
202 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
205 ->will(
206 $this->returnValue( $dbResult )
209 $mockCache = $this->getMockCache();
210 $mockCache->expects( $this->never() )->method( 'get' );
211 $mockCache->expects( $this->never() )->method( 'set' );
212 $mockCache->expects( $this->never() )->method( 'delete' );
214 $store = $this->newWatchedItemStore(
215 $this->getMockLoadBalancer( $mockDb ),
216 $mockCache,
217 $this->getMockReadOnlyMode()
220 $expected = [
221 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
222 1 => [ 'AnotherDbKey' => 500 ],
224 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
227 public function provideIntWithDbUnsafeVersion() {
228 return [
229 [ 50 ],
230 [ "50; DROP TABLE watchlist;\n--" ],
235 * @dataProvider provideIntWithDbUnsafeVersion
237 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
238 $titleValues = [
239 new TitleValue( 0, 'SomeDbKey' ),
240 new TitleValue( 0, 'OtherDbKey' ),
241 new TitleValue( 1, 'AnotherDbKey' ),
244 $mockDb = $this->getMockDb();
246 $dbResult = [
247 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
248 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
249 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
252 $mockDb->expects( $this->once() )
253 ->method( 'makeWhereFrom2d' )
254 ->with(
255 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
256 $this->isType( 'string' ),
257 $this->isType( 'string' )
259 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
260 $mockDb->expects( $this->once() )
261 ->method( 'select' )
262 ->with(
263 'watchlist',
264 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
265 [ 'makeWhereFrom2d return value' ],
266 $this->isType( 'string' ),
268 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
269 'HAVING' => 'COUNT(*) >= 50',
272 ->will(
273 $this->returnValue( $dbResult )
276 $mockCache = $this->getMockCache();
277 $mockCache->expects( $this->never() )->method( 'get' );
278 $mockCache->expects( $this->never() )->method( 'set' );
279 $mockCache->expects( $this->never() )->method( 'delete' );
281 $store = $this->newWatchedItemStore(
282 $this->getMockLoadBalancer( $mockDb ),
283 $mockCache,
284 $this->getMockReadOnlyMode()
287 $expected = [
288 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
289 1 => [ 'AnotherDbKey' => 500 ],
291 $this->assertEquals(
292 $expected,
293 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
297 public function testCountVisitingWatchers() {
298 $titleValue = new TitleValue( 0, 'SomeDbKey' );
300 $mockDb = $this->getMockDb();
301 $mockDb->expects( $this->exactly( 1 ) )
302 ->method( 'selectField' )
303 ->with(
304 'watchlist',
305 'COUNT(*)',
307 'wl_namespace' => $titleValue->getNamespace(),
308 'wl_title' => $titleValue->getDBkey(),
309 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
311 $this->isType( 'string' )
313 ->will( $this->returnValue( 7 ) );
314 $mockDb->expects( $this->exactly( 1 ) )
315 ->method( 'addQuotes' )
316 ->will( $this->returnCallback( function ( $value ) {
317 return "'$value'";
318 } ) );
319 $mockDb->expects( $this->exactly( 1 ) )
320 ->method( 'timestamp' )
321 ->will( $this->returnCallback( function ( $value ) {
322 return 'TS' . $value . 'TS';
323 } ) );
325 $mockCache = $this->getMockCache();
326 $mockCache->expects( $this->never() )->method( 'set' );
327 $mockCache->expects( $this->never() )->method( 'get' );
328 $mockCache->expects( $this->never() )->method( 'delete' );
330 $store = $this->newWatchedItemStore(
331 $this->getMockLoadBalancer( $mockDb ),
332 $mockCache,
333 $this->getMockReadOnlyMode()
336 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
339 public function testCountVisitingWatchersMultiple() {
340 $titleValuesWithThresholds = [
341 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
342 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
343 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
346 $dbResult = [
347 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
348 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
349 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
351 $mockDb = $this->getMockDb();
352 $mockDb->expects( $this->exactly( 2 * 3 ) )
353 ->method( 'addQuotes' )
354 ->will( $this->returnCallback( function ( $value ) {
355 return "'$value'";
356 } ) );
357 $mockDb->expects( $this->exactly( 3 ) )
358 ->method( 'timestamp' )
359 ->will( $this->returnCallback( function ( $value ) {
360 return 'TS' . $value . 'TS';
361 } ) );
362 $mockDb->expects( $this->any() )
363 ->method( 'makeList' )
364 ->with(
365 $this->isType( 'array' ),
366 $this->isType( 'int' )
368 ->will( $this->returnCallback( function ( $a, $conj ) {
369 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
370 return join( $sqlConj, array_map( function ( $s ) {
371 return '(' . $s . ')';
372 }, $a
373 ) );
374 } ) );
375 $mockDb->expects( $this->never() )
376 ->method( 'makeWhereFrom2d' );
378 $expectedCond =
379 '((wl_namespace = 0) AND (' .
380 "(((wl_title = 'SomeDbKey') AND (" .
381 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
382 ')) OR (' .
383 "(wl_title = 'OtherDbKey') AND (" .
384 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
385 '))))' .
386 ') OR ((wl_namespace = 1) AND (' .
387 "(((wl_title = 'AnotherDbKey') AND (".
388 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
389 ')))))';
390 $mockDb->expects( $this->once() )
391 ->method( 'select' )
392 ->with(
393 'watchlist',
394 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
395 $expectedCond,
396 $this->isType( 'string' ),
398 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
401 ->will(
402 $this->returnValue( $dbResult )
405 $mockCache = $this->getMockCache();
406 $mockCache->expects( $this->never() )->method( 'get' );
407 $mockCache->expects( $this->never() )->method( 'set' );
408 $mockCache->expects( $this->never() )->method( 'delete' );
410 $store = $this->newWatchedItemStore(
411 $this->getMockLoadBalancer( $mockDb ),
412 $mockCache,
413 $this->getMockReadOnlyMode()
416 $expected = [
417 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
418 1 => [ 'AnotherDbKey' => 500 ],
420 $this->assertEquals(
421 $expected,
422 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
426 public function testCountVisitingWatchersMultiple_withMissingTargets() {
427 $titleValuesWithThresholds = [
428 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
429 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
430 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
431 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
432 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
435 $dbResult = [
436 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
437 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
438 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
439 $this->getFakeRow(
440 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
442 $this->getFakeRow(
443 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
446 $mockDb = $this->getMockDb();
447 $mockDb->expects( $this->exactly( 2 * 3 ) )
448 ->method( 'addQuotes' )
449 ->will( $this->returnCallback( function ( $value ) {
450 return "'$value'";
451 } ) );
452 $mockDb->expects( $this->exactly( 3 ) )
453 ->method( 'timestamp' )
454 ->will( $this->returnCallback( function ( $value ) {
455 return 'TS' . $value . 'TS';
456 } ) );
457 $mockDb->expects( $this->any() )
458 ->method( 'makeList' )
459 ->with(
460 $this->isType( 'array' ),
461 $this->isType( 'int' )
463 ->will( $this->returnCallback( function ( $a, $conj ) {
464 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
465 return join( $sqlConj, array_map( function ( $s ) {
466 return '(' . $s . ')';
467 }, $a
468 ) );
469 } ) );
470 $mockDb->expects( $this->once() )
471 ->method( 'makeWhereFrom2d' )
472 ->with(
473 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
474 $this->isType( 'string' ),
475 $this->isType( 'string' )
477 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
479 $expectedCond =
480 '((wl_namespace = 0) AND (' .
481 "(((wl_title = 'SomeDbKey') AND (" .
482 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
483 ')) OR (' .
484 "(wl_title = 'OtherDbKey') AND (" .
485 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
486 '))))' .
487 ') OR ((wl_namespace = 1) AND (' .
488 "(((wl_title = 'AnotherDbKey') AND (".
489 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
490 '))))' .
491 ') OR ' .
492 '(makeWhereFrom2d return value)';
493 $mockDb->expects( $this->once() )
494 ->method( 'select' )
495 ->with(
496 'watchlist',
497 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
498 $expectedCond,
499 $this->isType( 'string' ),
501 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
504 ->will(
505 $this->returnValue( $dbResult )
508 $mockCache = $this->getMockCache();
509 $mockCache->expects( $this->never() )->method( 'get' );
510 $mockCache->expects( $this->never() )->method( 'set' );
511 $mockCache->expects( $this->never() )->method( 'delete' );
513 $store = $this->newWatchedItemStore(
514 $this->getMockLoadBalancer( $mockDb ),
515 $mockCache,
516 $this->getMockReadOnlyMode()
519 $expected = [
520 0 => [
521 'SomeDbKey' => 100, 'OtherDbKey' => 300,
522 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
524 1 => [ 'AnotherDbKey' => 500 ],
526 $this->assertEquals(
527 $expected,
528 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
533 * @dataProvider provideIntWithDbUnsafeVersion
535 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
536 $titleValuesWithThresholds = [
537 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
538 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
539 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
542 $mockDb = $this->getMockDb();
543 $mockDb->expects( $this->any() )
544 ->method( 'makeList' )
545 ->will( $this->returnValue( 'makeList return value' ) );
546 $mockDb->expects( $this->once() )
547 ->method( 'select' )
548 ->with(
549 'watchlist',
550 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
551 'makeList return value',
552 $this->isType( 'string' ),
554 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
555 'HAVING' => 'COUNT(*) >= 50',
558 ->will(
559 $this->returnValue( [] )
562 $mockCache = $this->getMockCache();
563 $mockCache->expects( $this->never() )->method( 'get' );
564 $mockCache->expects( $this->never() )->method( 'set' );
565 $mockCache->expects( $this->never() )->method( 'delete' );
567 $store = $this->newWatchedItemStore(
568 $this->getMockLoadBalancer( $mockDb ),
569 $mockCache,
570 $this->getMockReadOnlyMode()
573 $expected = [
574 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
575 1 => [ 'AnotherDbKey' => 0 ],
577 $this->assertEquals(
578 $expected,
579 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
583 public function testCountUnreadNotifications() {
584 $user = $this->getMockNonAnonUserWithId( 1 );
586 $mockDb = $this->getMockDb();
587 $mockDb->expects( $this->exactly( 1 ) )
588 ->method( 'selectRowCount' )
589 ->with(
590 'watchlist',
591 '1',
593 "wl_notificationtimestamp IS NOT NULL",
594 'wl_user' => 1,
596 $this->isType( 'string' )
598 ->will( $this->returnValue( 9 ) );
600 $mockCache = $this->getMockCache();
601 $mockCache->expects( $this->never() )->method( 'set' );
602 $mockCache->expects( $this->never() )->method( 'get' );
603 $mockCache->expects( $this->never() )->method( 'delete' );
605 $store = $this->newWatchedItemStore(
606 $this->getMockLoadBalancer( $mockDb ),
607 $mockCache,
608 $this->getMockReadOnlyMode()
611 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
615 * @dataProvider provideIntWithDbUnsafeVersion
617 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
618 $user = $this->getMockNonAnonUserWithId( 1 );
620 $mockDb = $this->getMockDb();
621 $mockDb->expects( $this->exactly( 1 ) )
622 ->method( 'selectRowCount' )
623 ->with(
624 'watchlist',
625 '1',
627 "wl_notificationtimestamp IS NOT NULL",
628 'wl_user' => 1,
630 $this->isType( 'string' ),
631 [ 'LIMIT' => 50 ]
633 ->will( $this->returnValue( 50 ) );
635 $mockCache = $this->getMockCache();
636 $mockCache->expects( $this->never() )->method( 'set' );
637 $mockCache->expects( $this->never() )->method( 'get' );
638 $mockCache->expects( $this->never() )->method( 'delete' );
640 $store = $this->newWatchedItemStore(
641 $this->getMockLoadBalancer( $mockDb ),
642 $mockCache,
643 $this->getMockReadOnlyMode()
646 $this->assertSame(
647 true,
648 $store->countUnreadNotifications( $user, $limit )
653 * @dataProvider provideIntWithDbUnsafeVersion
655 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
656 $user = $this->getMockNonAnonUserWithId( 1 );
658 $mockDb = $this->getMockDb();
659 $mockDb->expects( $this->exactly( 1 ) )
660 ->method( 'selectRowCount' )
661 ->with(
662 'watchlist',
663 '1',
665 "wl_notificationtimestamp IS NOT NULL",
666 'wl_user' => 1,
668 $this->isType( 'string' ),
669 [ 'LIMIT' => 50 ]
671 ->will( $this->returnValue( 9 ) );
673 $mockCache = $this->getMockCache();
674 $mockCache->expects( $this->never() )->method( 'set' );
675 $mockCache->expects( $this->never() )->method( 'get' );
676 $mockCache->expects( $this->never() )->method( 'delete' );
678 $store = $this->newWatchedItemStore(
679 $this->getMockLoadBalancer( $mockDb ),
680 $mockCache,
681 $this->getMockReadOnlyMode()
684 $this->assertEquals(
686 $store->countUnreadNotifications( $user, $limit )
690 public function testDuplicateEntry_nothingToDuplicate() {
691 $mockDb = $this->getMockDb();
692 $mockDb->expects( $this->once() )
693 ->method( 'select' )
694 ->with(
695 'watchlist',
697 'wl_user',
698 'wl_notificationtimestamp',
701 'wl_namespace' => 0,
702 'wl_title' => 'Old_Title',
704 'WatchedItemStore::duplicateEntry',
705 [ 'FOR UPDATE' ]
707 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
709 $store = $this->newWatchedItemStore(
710 $this->getMockLoadBalancer( $mockDb ),
711 $this->getMockCache(),
712 $this->getMockReadOnlyMode()
715 $store->duplicateEntry(
716 Title::newFromText( 'Old_Title' ),
717 Title::newFromText( 'New_Title' )
721 public function testDuplicateEntry_somethingToDuplicate() {
722 $fakeRows = [
723 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
724 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
727 $mockDb = $this->getMockDb();
728 $mockDb->expects( $this->at( 0 ) )
729 ->method( 'select' )
730 ->with(
731 'watchlist',
733 'wl_user',
734 'wl_notificationtimestamp',
737 'wl_namespace' => 0,
738 'wl_title' => 'Old_Title',
741 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
742 $mockDb->expects( $this->at( 1 ) )
743 ->method( 'replace' )
744 ->with(
745 'watchlist',
746 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
749 'wl_user' => 1,
750 'wl_namespace' => 0,
751 'wl_title' => 'New_Title',
752 'wl_notificationtimestamp' => '20151212010101',
755 'wl_user' => 2,
756 'wl_namespace' => 0,
757 'wl_title' => 'New_Title',
758 'wl_notificationtimestamp' => null,
761 $this->isType( 'string' )
764 $mockCache = $this->getMockCache();
765 $mockCache->expects( $this->never() )->method( 'get' );
766 $mockCache->expects( $this->never() )->method( 'delete' );
768 $store = $this->newWatchedItemStore(
769 $this->getMockLoadBalancer( $mockDb ),
770 $mockCache,
771 $this->getMockReadOnlyMode()
774 $store->duplicateEntry(
775 Title::newFromText( 'Old_Title' ),
776 Title::newFromText( 'New_Title' )
780 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
781 $mockDb = $this->getMockDb();
782 $mockDb->expects( $this->at( 0 ) )
783 ->method( 'select' )
784 ->with(
785 'watchlist',
787 'wl_user',
788 'wl_notificationtimestamp',
791 'wl_namespace' => 0,
792 'wl_title' => 'Old_Title',
795 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
796 $mockDb->expects( $this->at( 1 ) )
797 ->method( 'select' )
798 ->with(
799 'watchlist',
801 'wl_user',
802 'wl_notificationtimestamp',
805 'wl_namespace' => 1,
806 'wl_title' => 'Old_Title',
809 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
811 $mockCache = $this->getMockCache();
812 $mockCache->expects( $this->never() )->method( 'get' );
813 $mockCache->expects( $this->never() )->method( 'delete' );
815 $store = $this->newWatchedItemStore(
816 $this->getMockLoadBalancer( $mockDb ),
817 $mockCache,
818 $this->getMockReadOnlyMode()
821 $store->duplicateAllAssociatedEntries(
822 Title::newFromText( 'Old_Title' ),
823 Title::newFromText( 'New_Title' )
827 public function provideLinkTargetPairs() {
828 return [
829 [ Title::newFromText( 'Old_Title' ), Title::newFromText( 'New_Title' ) ],
830 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
835 * @dataProvider provideLinkTargetPairs
837 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
838 LinkTarget $oldTarget,
839 LinkTarget $newTarget
841 $fakeRows = [
842 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
845 $mockDb = $this->getMockDb();
846 $mockDb->expects( $this->at( 0 ) )
847 ->method( 'select' )
848 ->with(
849 'watchlist',
851 'wl_user',
852 'wl_notificationtimestamp',
855 'wl_namespace' => $oldTarget->getNamespace(),
856 'wl_title' => $oldTarget->getDBkey(),
859 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
860 $mockDb->expects( $this->at( 1 ) )
861 ->method( 'replace' )
862 ->with(
863 'watchlist',
864 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
867 'wl_user' => 1,
868 'wl_namespace' => $newTarget->getNamespace(),
869 'wl_title' => $newTarget->getDBkey(),
870 'wl_notificationtimestamp' => '20151212010101',
873 $this->isType( 'string' )
875 $mockDb->expects( $this->at( 2 ) )
876 ->method( 'select' )
877 ->with(
878 'watchlist',
880 'wl_user',
881 'wl_notificationtimestamp',
884 'wl_namespace' => $oldTarget->getNamespace() + 1,
885 'wl_title' => $oldTarget->getDBkey(),
888 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
889 $mockDb->expects( $this->at( 3 ) )
890 ->method( 'replace' )
891 ->with(
892 'watchlist',
893 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
896 'wl_user' => 1,
897 'wl_namespace' => $newTarget->getNamespace() + 1,
898 'wl_title' => $newTarget->getDBkey(),
899 'wl_notificationtimestamp' => '20151212010101',
902 $this->isType( 'string' )
905 $mockCache = $this->getMockCache();
906 $mockCache->expects( $this->never() )->method( 'get' );
907 $mockCache->expects( $this->never() )->method( 'delete' );
909 $store = $this->newWatchedItemStore(
910 $this->getMockLoadBalancer( $mockDb ),
911 $mockCache,
912 $this->getMockReadOnlyMode()
915 $store->duplicateAllAssociatedEntries(
916 $oldTarget,
917 $newTarget
921 public function testAddWatch_nonAnonymousUser() {
922 $mockDb = $this->getMockDb();
923 $mockDb->expects( $this->once() )
924 ->method( 'insert' )
925 ->with(
926 'watchlist',
929 'wl_user' => 1,
930 'wl_namespace' => 0,
931 'wl_title' => 'Some_Page',
932 'wl_notificationtimestamp' => null,
937 $mockCache = $this->getMockCache();
938 $mockCache->expects( $this->once() )
939 ->method( 'delete' )
940 ->with( '0:Some_Page:1' );
942 $store = $this->newWatchedItemStore(
943 $this->getMockLoadBalancer( $mockDb ),
944 $mockCache,
945 $this->getMockReadOnlyMode()
948 $store->addWatch(
949 $this->getMockNonAnonUserWithId( 1 ),
950 Title::newFromText( 'Some_Page' )
954 public function testAddWatch_anonymousUser() {
955 $mockDb = $this->getMockDb();
956 $mockDb->expects( $this->never() )
957 ->method( 'insert' );
959 $mockCache = $this->getMockCache();
960 $mockCache->expects( $this->never() )
961 ->method( 'delete' );
963 $store = $this->newWatchedItemStore(
964 $this->getMockLoadBalancer( $mockDb ),
965 $mockCache,
966 $this->getMockReadOnlyMode()
969 $store->addWatch(
970 $this->getAnonUser(),
971 Title::newFromText( 'Some_Page' )
975 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
976 $store = $this->newWatchedItemStore(
977 $this->getMockLoadBalancer( $this->getMockDb() ),
978 $this->getMockCache(),
979 $this->getMockReadOnlyMode( true )
982 $this->assertFalse(
983 $store->addWatchBatchForUser(
984 $this->getMockNonAnonUserWithId( 1 ),
985 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
990 public function testAddWatchBatchForUser_nonAnonymousUser() {
991 $mockDb = $this->getMockDb();
992 $mockDb->expects( $this->once() )
993 ->method( 'insert' )
994 ->with(
995 'watchlist',
998 'wl_user' => 1,
999 'wl_namespace' => 0,
1000 'wl_title' => 'Some_Page',
1001 'wl_notificationtimestamp' => null,
1004 'wl_user' => 1,
1005 'wl_namespace' => 1,
1006 'wl_title' => 'Some_Page',
1007 'wl_notificationtimestamp' => null,
1012 $mockCache = $this->getMockCache();
1013 $mockCache->expects( $this->exactly( 2 ) )
1014 ->method( 'delete' );
1015 $mockCache->expects( $this->at( 1 ) )
1016 ->method( 'delete' )
1017 ->with( '0:Some_Page:1' );
1018 $mockCache->expects( $this->at( 3 ) )
1019 ->method( 'delete' )
1020 ->with( '1:Some_Page:1' );
1022 $store = $this->newWatchedItemStore(
1023 $this->getMockLoadBalancer( $mockDb ),
1024 $mockCache,
1025 $this->getMockReadOnlyMode()
1028 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1030 $this->assertTrue(
1031 $store->addWatchBatchForUser(
1032 $mockUser,
1033 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1038 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1039 $mockDb = $this->getMockDb();
1040 $mockDb->expects( $this->never() )
1041 ->method( 'insert' );
1043 $mockCache = $this->getMockCache();
1044 $mockCache->expects( $this->never() )
1045 ->method( 'delete' );
1047 $store = $this->newWatchedItemStore(
1048 $this->getMockLoadBalancer( $mockDb ),
1049 $mockCache,
1050 $this->getMockReadOnlyMode()
1053 $this->assertFalse(
1054 $store->addWatchBatchForUser(
1055 $this->getAnonUser(),
1056 [ new TitleValue( 0, 'Other_Page' ) ]
1061 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1062 $user = $this->getMockNonAnonUserWithId( 1 );
1063 $mockDb = $this->getMockDb();
1064 $mockDb->expects( $this->never() )
1065 ->method( 'insert' );
1067 $mockCache = $this->getMockCache();
1068 $mockCache->expects( $this->never() )
1069 ->method( 'delete' );
1071 $store = $this->newWatchedItemStore(
1072 $this->getMockLoadBalancer( $mockDb ),
1073 $mockCache,
1074 $this->getMockReadOnlyMode()
1077 $this->assertTrue(
1078 $store->addWatchBatchForUser( $user, [] )
1082 public function testLoadWatchedItem_existingItem() {
1083 $mockDb = $this->getMockDb();
1084 $mockDb->expects( $this->once() )
1085 ->method( 'selectRow' )
1086 ->with(
1087 'watchlist',
1088 'wl_notificationtimestamp',
1090 'wl_user' => 1,
1091 'wl_namespace' => 0,
1092 'wl_title' => 'SomeDbKey',
1095 ->will( $this->returnValue(
1096 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1097 ) );
1099 $mockCache = $this->getMockCache();
1100 $mockCache->expects( $this->once() )
1101 ->method( 'set' )
1102 ->with(
1103 '0:SomeDbKey:1'
1106 $store = $this->newWatchedItemStore(
1107 $this->getMockLoadBalancer( $mockDb ),
1108 $mockCache,
1109 $this->getMockReadOnlyMode()
1112 $watchedItem = $store->loadWatchedItem(
1113 $this->getMockNonAnonUserWithId( 1 ),
1114 new TitleValue( 0, 'SomeDbKey' )
1116 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1117 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1118 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1119 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1122 public function testLoadWatchedItem_noItem() {
1123 $mockDb = $this->getMockDb();
1124 $mockDb->expects( $this->once() )
1125 ->method( 'selectRow' )
1126 ->with(
1127 'watchlist',
1128 'wl_notificationtimestamp',
1130 'wl_user' => 1,
1131 'wl_namespace' => 0,
1132 'wl_title' => 'SomeDbKey',
1135 ->will( $this->returnValue( [] ) );
1137 $mockCache = $this->getMockCache();
1138 $mockCache->expects( $this->never() )->method( 'get' );
1139 $mockCache->expects( $this->never() )->method( 'delete' );
1141 $store = $this->newWatchedItemStore(
1142 $this->getMockLoadBalancer( $mockDb ),
1143 $mockCache,
1144 $this->getMockReadOnlyMode()
1147 $this->assertFalse(
1148 $store->loadWatchedItem(
1149 $this->getMockNonAnonUserWithId( 1 ),
1150 new TitleValue( 0, 'SomeDbKey' )
1155 public function testLoadWatchedItem_anonymousUser() {
1156 $mockDb = $this->getMockDb();
1157 $mockDb->expects( $this->never() )
1158 ->method( 'selectRow' );
1160 $mockCache = $this->getMockCache();
1161 $mockCache->expects( $this->never() )->method( 'get' );
1162 $mockCache->expects( $this->never() )->method( 'delete' );
1164 $store = $this->newWatchedItemStore(
1165 $this->getMockLoadBalancer( $mockDb ),
1166 $mockCache,
1167 $this->getMockReadOnlyMode()
1170 $this->assertFalse(
1171 $store->loadWatchedItem(
1172 $this->getAnonUser(),
1173 new TitleValue( 0, 'SomeDbKey' )
1178 public function testRemoveWatch_existingItem() {
1179 $mockDb = $this->getMockDb();
1180 $mockDb->expects( $this->once() )
1181 ->method( 'delete' )
1182 ->with(
1183 'watchlist',
1185 'wl_user' => 1,
1186 'wl_namespace' => 0,
1187 'wl_title' => 'SomeDbKey',
1190 $mockDb->expects( $this->once() )
1191 ->method( 'affectedRows' )
1192 ->will( $this->returnValue( 1 ) );
1194 $mockCache = $this->getMockCache();
1195 $mockCache->expects( $this->never() )->method( 'get' );
1196 $mockCache->expects( $this->once() )
1197 ->method( 'delete' )
1198 ->with( '0:SomeDbKey:1' );
1200 $store = $this->newWatchedItemStore(
1201 $this->getMockLoadBalancer( $mockDb ),
1202 $mockCache,
1203 $this->getMockReadOnlyMode()
1206 $this->assertTrue(
1207 $store->removeWatch(
1208 $this->getMockNonAnonUserWithId( 1 ),
1209 new TitleValue( 0, 'SomeDbKey' )
1214 public function testRemoveWatch_noItem() {
1215 $mockDb = $this->getMockDb();
1216 $mockDb->expects( $this->once() )
1217 ->method( 'delete' )
1218 ->with(
1219 'watchlist',
1221 'wl_user' => 1,
1222 'wl_namespace' => 0,
1223 'wl_title' => 'SomeDbKey',
1226 $mockDb->expects( $this->once() )
1227 ->method( 'affectedRows' )
1228 ->will( $this->returnValue( 0 ) );
1230 $mockCache = $this->getMockCache();
1231 $mockCache->expects( $this->never() )->method( 'get' );
1232 $mockCache->expects( $this->once() )
1233 ->method( 'delete' )
1234 ->with( '0:SomeDbKey:1' );
1236 $store = $this->newWatchedItemStore(
1237 $this->getMockLoadBalancer( $mockDb ),
1238 $mockCache,
1239 $this->getMockReadOnlyMode()
1242 $this->assertFalse(
1243 $store->removeWatch(
1244 $this->getMockNonAnonUserWithId( 1 ),
1245 new TitleValue( 0, 'SomeDbKey' )
1250 public function testRemoveWatch_anonymousUser() {
1251 $mockDb = $this->getMockDb();
1252 $mockDb->expects( $this->never() )
1253 ->method( 'delete' );
1255 $mockCache = $this->getMockCache();
1256 $mockCache->expects( $this->never() )->method( 'get' );
1257 $mockCache->expects( $this->never() )
1258 ->method( 'delete' );
1260 $store = $this->newWatchedItemStore(
1261 $this->getMockLoadBalancer( $mockDb ),
1262 $mockCache,
1263 $this->getMockReadOnlyMode()
1266 $this->assertFalse(
1267 $store->removeWatch(
1268 $this->getAnonUser(),
1269 new TitleValue( 0, 'SomeDbKey' )
1274 public function testGetWatchedItem_existingItem() {
1275 $mockDb = $this->getMockDb();
1276 $mockDb->expects( $this->once() )
1277 ->method( 'selectRow' )
1278 ->with(
1279 'watchlist',
1280 'wl_notificationtimestamp',
1282 'wl_user' => 1,
1283 'wl_namespace' => 0,
1284 'wl_title' => 'SomeDbKey',
1287 ->will( $this->returnValue(
1288 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1289 ) );
1291 $mockCache = $this->getMockCache();
1292 $mockCache->expects( $this->never() )->method( 'delete' );
1293 $mockCache->expects( $this->once() )
1294 ->method( 'get' )
1295 ->with(
1296 '0:SomeDbKey:1'
1298 ->will( $this->returnValue( null ) );
1299 $mockCache->expects( $this->once() )
1300 ->method( 'set' )
1301 ->with(
1302 '0:SomeDbKey:1'
1305 $store = $this->newWatchedItemStore(
1306 $this->getMockLoadBalancer( $mockDb ),
1307 $mockCache,
1308 $this->getMockReadOnlyMode()
1311 $watchedItem = $store->getWatchedItem(
1312 $this->getMockNonAnonUserWithId( 1 ),
1313 new TitleValue( 0, 'SomeDbKey' )
1315 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1316 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1317 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1318 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1321 public function testGetWatchedItem_cachedItem() {
1322 $mockDb = $this->getMockDb();
1323 $mockDb->expects( $this->never() )
1324 ->method( 'selectRow' );
1326 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1327 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1328 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1330 $mockCache = $this->getMockCache();
1331 $mockCache->expects( $this->never() )->method( 'delete' );
1332 $mockCache->expects( $this->never() )->method( 'set' );
1333 $mockCache->expects( $this->once() )
1334 ->method( 'get' )
1335 ->with(
1336 '0:SomeDbKey:1'
1338 ->will( $this->returnValue( $cachedItem ) );
1340 $store = $this->newWatchedItemStore(
1341 $this->getMockLoadBalancer( $mockDb ),
1342 $mockCache,
1343 $this->getMockReadOnlyMode()
1346 $this->assertEquals(
1347 $cachedItem,
1348 $store->getWatchedItem(
1349 $mockUser,
1350 $linkTarget
1355 public function testGetWatchedItem_noItem() {
1356 $mockDb = $this->getMockDb();
1357 $mockDb->expects( $this->once() )
1358 ->method( 'selectRow' )
1359 ->with(
1360 'watchlist',
1361 'wl_notificationtimestamp',
1363 'wl_user' => 1,
1364 'wl_namespace' => 0,
1365 'wl_title' => 'SomeDbKey',
1368 ->will( $this->returnValue( [] ) );
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'set' );
1372 $mockCache->expects( $this->never() )->method( 'delete' );
1373 $mockCache->expects( $this->once() )
1374 ->method( 'get' )
1375 ->with( '0:SomeDbKey:1' )
1376 ->will( $this->returnValue( false ) );
1378 $store = $this->newWatchedItemStore(
1379 $this->getMockLoadBalancer( $mockDb ),
1380 $mockCache,
1381 $this->getMockReadOnlyMode()
1384 $this->assertFalse(
1385 $store->getWatchedItem(
1386 $this->getMockNonAnonUserWithId( 1 ),
1387 new TitleValue( 0, 'SomeDbKey' )
1392 public function testGetWatchedItem_anonymousUser() {
1393 $mockDb = $this->getMockDb();
1394 $mockDb->expects( $this->never() )
1395 ->method( 'selectRow' );
1397 $mockCache = $this->getMockCache();
1398 $mockCache->expects( $this->never() )->method( 'set' );
1399 $mockCache->expects( $this->never() )->method( 'get' );
1400 $mockCache->expects( $this->never() )->method( 'delete' );
1402 $store = $this->newWatchedItemStore(
1403 $this->getMockLoadBalancer( $mockDb ),
1404 $mockCache,
1405 $this->getMockReadOnlyMode()
1408 $this->assertFalse(
1409 $store->getWatchedItem(
1410 $this->getAnonUser(),
1411 new TitleValue( 0, 'SomeDbKey' )
1416 public function testGetWatchedItemsForUser() {
1417 $mockDb = $this->getMockDb();
1418 $mockDb->expects( $this->once() )
1419 ->method( 'select' )
1420 ->with(
1421 'watchlist',
1422 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1423 [ 'wl_user' => 1 ]
1425 ->will( $this->returnValue( [
1426 $this->getFakeRow( [
1427 'wl_namespace' => 0,
1428 'wl_title' => 'Foo1',
1429 'wl_notificationtimestamp' => '20151212010101',
1430 ] ),
1431 $this->getFakeRow( [
1432 'wl_namespace' => 1,
1433 'wl_title' => 'Foo2',
1434 'wl_notificationtimestamp' => null,
1435 ] ),
1436 ] ) );
1438 $mockCache = $this->getMockCache();
1439 $mockCache->expects( $this->never() )->method( 'delete' );
1440 $mockCache->expects( $this->never() )->method( 'get' );
1441 $mockCache->expects( $this->never() )->method( 'set' );
1443 $store = $this->newWatchedItemStore(
1444 $this->getMockLoadBalancer( $mockDb ),
1445 $mockCache,
1446 $this->getMockReadOnlyMode()
1448 $user = $this->getMockNonAnonUserWithId( 1 );
1450 $watchedItems = $store->getWatchedItemsForUser( $user );
1452 $this->assertInternalType( 'array', $watchedItems );
1453 $this->assertCount( 2, $watchedItems );
1454 foreach ( $watchedItems as $watchedItem ) {
1455 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1457 $this->assertEquals(
1458 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1459 $watchedItems[0]
1461 $this->assertEquals(
1462 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1463 $watchedItems[1]
1467 public function provideDbTypes() {
1468 return [
1469 [ false, DB_REPLICA ],
1470 [ true, DB_MASTER ],
1475 * @dataProvider provideDbTypes
1477 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1478 $mockDb = $this->getMockDb();
1479 $mockCache = $this->getMockCache();
1480 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1481 $user = $this->getMockNonAnonUserWithId( 1 );
1483 $mockDb->expects( $this->once() )
1484 ->method( 'select' )
1485 ->with(
1486 'watchlist',
1487 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1488 [ 'wl_user' => 1 ],
1489 $this->isType( 'string' ),
1490 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1492 ->will( $this->returnValue( [] ) );
1494 $store = $this->newWatchedItemStore(
1495 $mockLoadBalancer,
1496 $mockCache,
1497 $this->getMockReadOnlyMode()
1500 $watchedItems = $store->getWatchedItemsForUser(
1501 $user,
1502 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1504 $this->assertEquals( [], $watchedItems );
1507 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1508 $store = $this->newWatchedItemStore(
1509 $this->getMockLoadBalancer( $this->getMockDb() ),
1510 $this->getMockCache(),
1511 $this->getMockReadOnlyMode()
1514 $this->setExpectedException( 'InvalidArgumentException' );
1515 $store->getWatchedItemsForUser(
1516 $this->getMockNonAnonUserWithId( 1 ),
1517 [ 'sort' => 'foo' ]
1521 public function testIsWatchedItem_existingItem() {
1522 $mockDb = $this->getMockDb();
1523 $mockDb->expects( $this->once() )
1524 ->method( 'selectRow' )
1525 ->with(
1526 'watchlist',
1527 'wl_notificationtimestamp',
1529 'wl_user' => 1,
1530 'wl_namespace' => 0,
1531 'wl_title' => 'SomeDbKey',
1534 ->will( $this->returnValue(
1535 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1536 ) );
1538 $mockCache = $this->getMockCache();
1539 $mockCache->expects( $this->never() )->method( 'delete' );
1540 $mockCache->expects( $this->once() )
1541 ->method( 'get' )
1542 ->with( '0:SomeDbKey:1' )
1543 ->will( $this->returnValue( false ) );
1544 $mockCache->expects( $this->once() )
1545 ->method( 'set' )
1546 ->with(
1547 '0:SomeDbKey:1'
1550 $store = $this->newWatchedItemStore(
1551 $this->getMockLoadBalancer( $mockDb ),
1552 $mockCache,
1553 $this->getMockReadOnlyMode()
1556 $this->assertTrue(
1557 $store->isWatched(
1558 $this->getMockNonAnonUserWithId( 1 ),
1559 new TitleValue( 0, 'SomeDbKey' )
1564 public function testIsWatchedItem_noItem() {
1565 $mockDb = $this->getMockDb();
1566 $mockDb->expects( $this->once() )
1567 ->method( 'selectRow' )
1568 ->with(
1569 'watchlist',
1570 'wl_notificationtimestamp',
1572 'wl_user' => 1,
1573 'wl_namespace' => 0,
1574 'wl_title' => 'SomeDbKey',
1577 ->will( $this->returnValue( [] ) );
1579 $mockCache = $this->getMockCache();
1580 $mockCache->expects( $this->never() )->method( 'set' );
1581 $mockCache->expects( $this->never() )->method( 'delete' );
1582 $mockCache->expects( $this->once() )
1583 ->method( 'get' )
1584 ->with( '0:SomeDbKey:1' )
1585 ->will( $this->returnValue( false ) );
1587 $store = $this->newWatchedItemStore(
1588 $this->getMockLoadBalancer( $mockDb ),
1589 $mockCache,
1590 $this->getMockReadOnlyMode()
1593 $this->assertFalse(
1594 $store->isWatched(
1595 $this->getMockNonAnonUserWithId( 1 ),
1596 new TitleValue( 0, 'SomeDbKey' )
1601 public function testIsWatchedItem_anonymousUser() {
1602 $mockDb = $this->getMockDb();
1603 $mockDb->expects( $this->never() )
1604 ->method( 'selectRow' );
1606 $mockCache = $this->getMockCache();
1607 $mockCache->expects( $this->never() )->method( 'set' );
1608 $mockCache->expects( $this->never() )->method( 'get' );
1609 $mockCache->expects( $this->never() )->method( 'delete' );
1611 $store = $this->newWatchedItemStore(
1612 $this->getMockLoadBalancer( $mockDb ),
1613 $mockCache,
1614 $this->getMockReadOnlyMode()
1617 $this->assertFalse(
1618 $store->isWatched(
1619 $this->getAnonUser(),
1620 new TitleValue( 0, 'SomeDbKey' )
1625 public function testGetNotificationTimestampsBatch() {
1626 $targets = [
1627 new TitleValue( 0, 'SomeDbKey' ),
1628 new TitleValue( 1, 'AnotherDbKey' ),
1631 $mockDb = $this->getMockDb();
1632 $dbResult = [
1633 $this->getFakeRow( [
1634 'wl_namespace' => 0,
1635 'wl_title' => 'SomeDbKey',
1636 'wl_notificationtimestamp' => '20151212010101',
1637 ] ),
1638 $this->getFakeRow(
1640 'wl_namespace' => 1,
1641 'wl_title' => 'AnotherDbKey',
1642 'wl_notificationtimestamp' => null,
1647 $mockDb->expects( $this->once() )
1648 ->method( 'makeWhereFrom2d' )
1649 ->with(
1650 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1651 $this->isType( 'string' ),
1652 $this->isType( 'string' )
1654 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1655 $mockDb->expects( $this->once() )
1656 ->method( 'select' )
1657 ->with(
1658 'watchlist',
1659 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1661 'makeWhereFrom2d return value',
1662 'wl_user' => 1
1664 $this->isType( 'string' )
1666 ->will( $this->returnValue( $dbResult ) );
1668 $mockCache = $this->getMockCache();
1669 $mockCache->expects( $this->exactly( 2 ) )
1670 ->method( 'get' )
1671 ->withConsecutive(
1672 [ '0:SomeDbKey:1' ],
1673 [ '1:AnotherDbKey: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 ),
1681 $mockCache,
1682 $this->getMockReadOnlyMode()
1685 $this->assertEquals(
1687 0 => [ 'SomeDbKey' => '20151212010101', ],
1688 1 => [ 'AnotherDbKey' => null, ],
1690 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1694 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1695 $targets = [
1696 new TitleValue( 0, 'OtherDbKey' ),
1699 $mockDb = $this->getMockDb();
1701 $mockDb->expects( $this->once() )
1702 ->method( 'makeWhereFrom2d' )
1703 ->with(
1704 [ [ 'OtherDbKey' => 1 ] ],
1705 $this->isType( 'string' ),
1706 $this->isType( 'string' )
1708 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1709 $mockDb->expects( $this->once() )
1710 ->method( 'select' )
1711 ->with(
1712 'watchlist',
1713 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1715 'makeWhereFrom2d return value',
1716 'wl_user' => 1
1718 $this->isType( 'string' )
1720 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1722 $mockCache = $this->getMockCache();
1723 $mockCache->expects( $this->once() )
1724 ->method( 'get' )
1725 ->with( '0:OtherDbKey:1' )
1726 ->will( $this->returnValue( null ) );
1727 $mockCache->expects( $this->never() )->method( 'set' );
1728 $mockCache->expects( $this->never() )->method( 'delete' );
1730 $store = $this->newWatchedItemStore(
1731 $this->getMockLoadBalancer( $mockDb ),
1732 $mockCache,
1733 $this->getMockReadOnlyMode()
1736 $this->assertEquals(
1738 0 => [ 'OtherDbKey' => false, ],
1740 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1744 public function testGetNotificationTimestampsBatch_cachedItem() {
1745 $targets = [
1746 new TitleValue( 0, 'SomeDbKey' ),
1747 new TitleValue( 1, 'AnotherDbKey' ),
1750 $user = $this->getMockNonAnonUserWithId( 1 );
1751 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1753 $mockDb = $this->getMockDb();
1755 $mockDb->expects( $this->once() )
1756 ->method( 'makeWhereFrom2d' )
1757 ->with(
1758 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1759 $this->isType( 'string' ),
1760 $this->isType( 'string' )
1762 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1763 $mockDb->expects( $this->once() )
1764 ->method( 'select' )
1765 ->with(
1766 'watchlist',
1767 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1769 'makeWhereFrom2d return value',
1770 'wl_user' => 1
1772 $this->isType( 'string' )
1774 ->will( $this->returnValue( [
1775 $this->getFakeRow(
1776 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1778 ] ) );
1780 $mockCache = $this->getMockCache();
1781 $mockCache->expects( $this->at( 1 ) )
1782 ->method( 'get' )
1783 ->with( '0:SomeDbKey:1' )
1784 ->will( $this->returnValue( $cachedItem ) );
1785 $mockCache->expects( $this->at( 3 ) )
1786 ->method( 'get' )
1787 ->with( '1:AnotherDbKey:1' )
1788 ->will( $this->returnValue( null ) );
1789 $mockCache->expects( $this->never() )->method( 'set' );
1790 $mockCache->expects( $this->never() )->method( 'delete' );
1792 $store = $this->newWatchedItemStore(
1793 $this->getMockLoadBalancer( $mockDb ),
1794 $mockCache,
1795 $this->getMockReadOnlyMode()
1798 $this->assertEquals(
1800 0 => [ 'SomeDbKey' => '20151212010101', ],
1801 1 => [ 'AnotherDbKey' => null, ],
1803 $store->getNotificationTimestampsBatch( $user, $targets )
1807 public function testGetNotificationTimestampsBatch_allItemsCached() {
1808 $targets = [
1809 new TitleValue( 0, 'SomeDbKey' ),
1810 new TitleValue( 1, 'AnotherDbKey' ),
1813 $user = $this->getMockNonAnonUserWithId( 1 );
1814 $cachedItems = [
1815 new WatchedItem( $user, $targets[0], '20151212010101' ),
1816 new WatchedItem( $user, $targets[1], null ),
1818 $mockDb = $this->getMockDb();
1819 $mockDb->expects( $this->never() )->method( $this->anything() );
1821 $mockCache = $this->getMockCache();
1822 $mockCache->expects( $this->at( 1 ) )
1823 ->method( 'get' )
1824 ->with( '0:SomeDbKey:1' )
1825 ->will( $this->returnValue( $cachedItems[0] ) );
1826 $mockCache->expects( $this->at( 3 ) )
1827 ->method( 'get' )
1828 ->with( '1:AnotherDbKey:1' )
1829 ->will( $this->returnValue( $cachedItems[1] ) );
1830 $mockCache->expects( $this->never() )->method( 'set' );
1831 $mockCache->expects( $this->never() )->method( 'delete' );
1833 $store = $this->newWatchedItemStore(
1834 $this->getMockLoadBalancer( $mockDb ),
1835 $mockCache,
1836 $this->getMockReadOnlyMode()
1839 $this->assertEquals(
1841 0 => [ 'SomeDbKey' => '20151212010101', ],
1842 1 => [ 'AnotherDbKey' => null, ],
1844 $store->getNotificationTimestampsBatch( $user, $targets )
1848 public function testGetNotificationTimestampsBatch_anonymousUser() {
1849 $targets = [
1850 new TitleValue( 0, 'SomeDbKey' ),
1851 new TitleValue( 1, 'AnotherDbKey' ),
1854 $mockDb = $this->getMockDb();
1855 $mockDb->expects( $this->never() )->method( $this->anything() );
1857 $mockCache = $this->getMockCache();
1858 $mockCache->expects( $this->never() )->method( $this->anything() );
1860 $store = $this->newWatchedItemStore(
1861 $this->getMockLoadBalancer( $mockDb ),
1862 $mockCache,
1863 $this->getMockReadOnlyMode()
1866 $this->assertEquals(
1868 0 => [ 'SomeDbKey' => false, ],
1869 1 => [ 'AnotherDbKey' => false, ],
1871 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1875 public function testResetNotificationTimestamp_anonymousUser() {
1876 $mockDb = $this->getMockDb();
1877 $mockDb->expects( $this->never() )
1878 ->method( 'selectRow' );
1880 $mockCache = $this->getMockCache();
1881 $mockCache->expects( $this->never() )->method( 'get' );
1882 $mockCache->expects( $this->never() )->method( 'set' );
1883 $mockCache->expects( $this->never() )->method( 'delete' );
1885 $store = $this->newWatchedItemStore(
1886 $this->getMockLoadBalancer( $mockDb ),
1887 $mockCache,
1888 $this->getMockReadOnlyMode()
1891 $this->assertFalse(
1892 $store->resetNotificationTimestamp(
1893 $this->getAnonUser(),
1894 Title::newFromText( 'SomeDbKey' )
1899 public function testResetNotificationTimestamp_noItem() {
1900 $mockDb = $this->getMockDb();
1901 $mockDb->expects( $this->once() )
1902 ->method( 'selectRow' )
1903 ->with(
1904 'watchlist',
1905 'wl_notificationtimestamp',
1907 'wl_user' => 1,
1908 'wl_namespace' => 0,
1909 'wl_title' => 'SomeDbKey',
1912 ->will( $this->returnValue( [] ) );
1914 $mockCache = $this->getMockCache();
1915 $mockCache->expects( $this->never() )->method( 'get' );
1916 $mockCache->expects( $this->never() )->method( 'set' );
1917 $mockCache->expects( $this->never() )->method( 'delete' );
1919 $store = $this->newWatchedItemStore(
1920 $this->getMockLoadBalancer( $mockDb ),
1921 $mockCache,
1922 $this->getMockReadOnlyMode()
1925 $this->assertFalse(
1926 $store->resetNotificationTimestamp(
1927 $this->getMockNonAnonUserWithId( 1 ),
1928 Title::newFromText( 'SomeDbKey' )
1933 public function testResetNotificationTimestamp_item() {
1934 $user = $this->getMockNonAnonUserWithId( 1 );
1935 $title = Title::newFromText( 'SomeDbKey' );
1937 $mockDb = $this->getMockDb();
1938 $mockDb->expects( $this->once() )
1939 ->method( 'selectRow' )
1940 ->with(
1941 'watchlist',
1942 'wl_notificationtimestamp',
1944 'wl_user' => 1,
1945 'wl_namespace' => 0,
1946 'wl_title' => 'SomeDbKey',
1949 ->will( $this->returnValue(
1950 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1951 ) );
1953 $mockCache = $this->getMockCache();
1954 $mockCache->expects( $this->never() )->method( 'get' );
1955 $mockCache->expects( $this->once() )
1956 ->method( 'set' )
1957 ->with(
1958 '0:SomeDbKey:1',
1959 $this->isInstanceOf( WatchedItem::class )
1961 $mockCache->expects( $this->once() )
1962 ->method( 'delete' )
1963 ->with( '0:SomeDbKey:1' );
1965 $store = $this->newWatchedItemStore(
1966 $this->getMockLoadBalancer( $mockDb ),
1967 $mockCache,
1968 $this->getMockReadOnlyMode()
1971 // Note: This does not actually assert the job is correct
1972 $callableCallCounter = 0;
1973 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
1974 $callableCallCounter++;
1975 $this->assertInternalType( 'callable', $callable );
1977 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1979 $this->assertTrue(
1980 $store->resetNotificationTimestamp(
1981 $user,
1982 $title
1985 $this->assertEquals( 1, $callableCallCounter );
1987 ScopedCallback::consume( $scopedOverride );
1990 public function testResetNotificationTimestamp_noItemForced() {
1991 $user = $this->getMockNonAnonUserWithId( 1 );
1992 $title = Title::newFromText( 'SomeDbKey' );
1994 $mockDb = $this->getMockDb();
1995 $mockDb->expects( $this->never() )
1996 ->method( 'selectRow' );
1998 $mockCache = $this->getMockCache();
1999 $mockDb->expects( $this->never() )
2000 ->method( 'get' );
2001 $mockDb->expects( $this->never() )
2002 ->method( 'set' );
2003 $mockDb->expects( $this->never() )
2004 ->method( 'delete' );
2006 $store = $this->newWatchedItemStore(
2007 $this->getMockLoadBalancer( $mockDb ),
2008 $mockCache,
2009 $this->getMockReadOnlyMode()
2012 // Note: This does not actually assert the job is correct
2013 $callableCallCounter = 0;
2014 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2015 $callableCallCounter++;
2016 $this->assertInternalType( 'callable', $callable );
2018 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2020 $this->assertTrue(
2021 $store->resetNotificationTimestamp(
2022 $user,
2023 $title,
2024 'force'
2027 $this->assertEquals( 1, $callableCallCounter );
2029 ScopedCallback::consume( $scopedOverride );
2033 * @param string $text
2034 * @param int $ns
2036 * @return PHPUnit_Framework_MockObject_MockObject|Title
2038 private function getMockTitle( $text, $ns = 0 ) {
2039 $title = $this->createMock( Title::class );
2040 $title->expects( $this->any() )
2041 ->method( 'getText' )
2042 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2043 $title->expects( $this->any() )
2044 ->method( 'getDbKey' )
2045 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2046 $title->expects( $this->any() )
2047 ->method( 'getNamespace' )
2048 ->will( $this->returnValue( $ns ) );
2049 return $title;
2052 private function verifyCallbackJob(
2053 $callback,
2054 LinkTarget $expectedTitle,
2055 $expectedUserId,
2056 callable $notificationTimestampCondition
2058 $this->assertInternalType( 'callable', $callback );
2060 $callbackReflector = new ReflectionFunction( $callback );
2061 $vars = $callbackReflector->getStaticVariables();
2062 $this->assertArrayHasKey( 'job', $vars );
2063 $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
2065 /** @var ActivityUpdateJob $job */
2066 $job = $vars['job'];
2067 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2068 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2070 $jobParams = $job->getParams();
2071 $this->assertArrayHasKey( 'type', $jobParams );
2072 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2073 $this->assertArrayHasKey( 'userid', $jobParams );
2074 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2075 $this->assertArrayHasKey( 'notifTime', $jobParams );
2076 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2079 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2080 $user = $this->getMockNonAnonUserWithId( 1 );
2081 $oldid = 22;
2082 $title = $this->getMockTitle( 'SomeTitle' );
2083 $title->expects( $this->once() )
2084 ->method( 'getNextRevisionID' )
2085 ->with( $oldid )
2086 ->will( $this->returnValue( false ) );
2088 $mockDb = $this->getMockDb();
2089 $mockDb->expects( $this->never() )
2090 ->method( 'selectRow' );
2092 $mockCache = $this->getMockCache();
2093 $mockDb->expects( $this->never() )
2094 ->method( 'get' );
2095 $mockDb->expects( $this->never() )
2096 ->method( 'set' );
2097 $mockDb->expects( $this->never() )
2098 ->method( 'delete' );
2100 $store = $this->newWatchedItemStore(
2101 $this->getMockLoadBalancer( $mockDb ),
2102 $mockCache,
2103 $this->getMockReadOnlyMode()
2106 $callableCallCounter = 0;
2107 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2108 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2109 $callableCallCounter++;
2110 $this->verifyCallbackJob(
2111 $callable,
2112 $title,
2113 $user->getId(),
2114 function ( $time ) {
2115 return $time === null;
2121 $this->assertTrue(
2122 $store->resetNotificationTimestamp(
2123 $user,
2124 $title,
2125 'force',
2126 $oldid
2129 $this->assertEquals( 1, $callableCallCounter );
2131 ScopedCallback::consume( $scopedOverride );
2134 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2135 $user = $this->getMockNonAnonUserWithId( 1 );
2136 $oldid = 22;
2137 $title = $this->getMockTitle( 'SomeDbKey' );
2138 $title->expects( $this->once() )
2139 ->method( 'getNextRevisionID' )
2140 ->with( $oldid )
2141 ->will( $this->returnValue( 33 ) );
2143 $mockDb = $this->getMockDb();
2144 $mockDb->expects( $this->once() )
2145 ->method( 'selectRow' )
2146 ->with(
2147 'watchlist',
2148 'wl_notificationtimestamp',
2150 'wl_user' => 1,
2151 'wl_namespace' => 0,
2152 'wl_title' => 'SomeDbKey',
2155 ->will( $this->returnValue(
2156 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2157 ) );
2159 $mockCache = $this->getMockCache();
2160 $mockDb->expects( $this->never() )
2161 ->method( 'get' );
2162 $mockDb->expects( $this->never() )
2163 ->method( 'set' );
2164 $mockDb->expects( $this->never() )
2165 ->method( 'delete' );
2167 $store = $this->newWatchedItemStore(
2168 $this->getMockLoadBalancer( $mockDb ),
2169 $mockCache,
2170 $this->getMockReadOnlyMode()
2173 $addUpdateCallCounter = 0;
2174 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2175 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2176 $addUpdateCallCounter++;
2177 $this->verifyCallbackJob(
2178 $callable,
2179 $title,
2180 $user->getId(),
2181 function ( $time ) {
2182 return $time !== null && $time > '20151212010101';
2188 $getTimestampCallCounter = 0;
2189 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2190 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2191 $getTimestampCallCounter++;
2192 $this->assertEquals( $title, $titleParam );
2193 $this->assertEquals( $oldid, $oldidParam );
2197 $this->assertTrue(
2198 $store->resetNotificationTimestamp(
2199 $user,
2200 $title,
2201 'force',
2202 $oldid
2205 $this->assertEquals( 1, $addUpdateCallCounter );
2206 $this->assertEquals( 1, $getTimestampCallCounter );
2208 ScopedCallback::consume( $scopedOverrideDeferred );
2209 ScopedCallback::consume( $scopedOverrideRevision );
2212 public function testResetNotificationTimestamp_notWatchedPageForced() {
2213 $user = $this->getMockNonAnonUserWithId( 1 );
2214 $oldid = 22;
2215 $title = $this->getMockTitle( 'SomeDbKey' );
2216 $title->expects( $this->once() )
2217 ->method( 'getNextRevisionID' )
2218 ->with( $oldid )
2219 ->will( $this->returnValue( 33 ) );
2221 $mockDb = $this->getMockDb();
2222 $mockDb->expects( $this->once() )
2223 ->method( 'selectRow' )
2224 ->with(
2225 'watchlist',
2226 'wl_notificationtimestamp',
2228 'wl_user' => 1,
2229 'wl_namespace' => 0,
2230 'wl_title' => 'SomeDbKey',
2233 ->will( $this->returnValue( false ) );
2235 $mockCache = $this->getMockCache();
2236 $mockDb->expects( $this->never() )
2237 ->method( 'get' );
2238 $mockDb->expects( $this->never() )
2239 ->method( 'set' );
2240 $mockDb->expects( $this->never() )
2241 ->method( 'delete' );
2243 $store = $this->newWatchedItemStore(
2244 $this->getMockLoadBalancer( $mockDb ),
2245 $mockCache,
2246 $this->getMockReadOnlyMode()
2249 $callableCallCounter = 0;
2250 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2251 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2252 $callableCallCounter++;
2253 $this->verifyCallbackJob(
2254 $callable,
2255 $title,
2256 $user->getId(),
2257 function ( $time ) {
2258 return $time === null;
2264 $this->assertTrue(
2265 $store->resetNotificationTimestamp(
2266 $user,
2267 $title,
2268 'force',
2269 $oldid
2272 $this->assertEquals( 1, $callableCallCounter );
2274 ScopedCallback::consume( $scopedOverride );
2277 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2278 $user = $this->getMockNonAnonUserWithId( 1 );
2279 $oldid = 22;
2280 $title = $this->getMockTitle( 'SomeDbKey' );
2281 $title->expects( $this->once() )
2282 ->method( 'getNextRevisionID' )
2283 ->with( $oldid )
2284 ->will( $this->returnValue( 33 ) );
2286 $mockDb = $this->getMockDb();
2287 $mockDb->expects( $this->once() )
2288 ->method( 'selectRow' )
2289 ->with(
2290 'watchlist',
2291 'wl_notificationtimestamp',
2293 'wl_user' => 1,
2294 'wl_namespace' => 0,
2295 'wl_title' => 'SomeDbKey',
2298 ->will( $this->returnValue(
2299 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2300 ) );
2302 $mockCache = $this->getMockCache();
2303 $mockDb->expects( $this->never() )
2304 ->method( 'get' );
2305 $mockDb->expects( $this->never() )
2306 ->method( 'set' );
2307 $mockDb->expects( $this->never() )
2308 ->method( 'delete' );
2310 $store = $this->newWatchedItemStore(
2311 $this->getMockLoadBalancer( $mockDb ),
2312 $mockCache,
2313 $this->getMockReadOnlyMode()
2316 $addUpdateCallCounter = 0;
2317 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2318 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2319 $addUpdateCallCounter++;
2320 $this->verifyCallbackJob(
2321 $callable,
2322 $title,
2323 $user->getId(),
2324 function ( $time ) {
2325 return $time === '30151212010101';
2331 $getTimestampCallCounter = 0;
2332 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2333 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2334 $getTimestampCallCounter++;
2335 $this->assertEquals( $title, $titleParam );
2336 $this->assertEquals( $oldid, $oldidParam );
2340 $this->assertTrue(
2341 $store->resetNotificationTimestamp(
2342 $user,
2343 $title,
2344 'force',
2345 $oldid
2348 $this->assertEquals( 1, $addUpdateCallCounter );
2349 $this->assertEquals( 1, $getTimestampCallCounter );
2351 ScopedCallback::consume( $scopedOverrideDeferred );
2352 ScopedCallback::consume( $scopedOverrideRevision );
2355 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2356 $user = $this->getMockNonAnonUserWithId( 1 );
2357 $oldid = 22;
2358 $title = $this->getMockTitle( 'SomeDbKey' );
2359 $title->expects( $this->once() )
2360 ->method( 'getNextRevisionID' )
2361 ->with( $oldid )
2362 ->will( $this->returnValue( 33 ) );
2364 $mockDb = $this->getMockDb();
2365 $mockDb->expects( $this->once() )
2366 ->method( 'selectRow' )
2367 ->with(
2368 'watchlist',
2369 'wl_notificationtimestamp',
2371 'wl_user' => 1,
2372 'wl_namespace' => 0,
2373 'wl_title' => 'SomeDbKey',
2376 ->will( $this->returnValue(
2377 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2378 ) );
2380 $mockCache = $this->getMockCache();
2381 $mockDb->expects( $this->never() )
2382 ->method( 'get' );
2383 $mockDb->expects( $this->never() )
2384 ->method( 'set' );
2385 $mockDb->expects( $this->never() )
2386 ->method( 'delete' );
2388 $store = $this->newWatchedItemStore(
2389 $this->getMockLoadBalancer( $mockDb ),
2390 $mockCache,
2391 $this->getMockReadOnlyMode()
2394 $addUpdateCallCounter = 0;
2395 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2396 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2397 $addUpdateCallCounter++;
2398 $this->verifyCallbackJob(
2399 $callable,
2400 $title,
2401 $user->getId(),
2402 function ( $time ) {
2403 return $time === false;
2409 $getTimestampCallCounter = 0;
2410 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2411 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2412 $getTimestampCallCounter++;
2413 $this->assertEquals( $title, $titleParam );
2414 $this->assertEquals( $oldid, $oldidParam );
2418 $this->assertTrue(
2419 $store->resetNotificationTimestamp(
2420 $user,
2421 $title,
2423 $oldid
2426 $this->assertEquals( 1, $addUpdateCallCounter );
2427 $this->assertEquals( 1, $getTimestampCallCounter );
2429 ScopedCallback::consume( $scopedOverrideDeferred );
2430 ScopedCallback::consume( $scopedOverrideRevision );
2433 public function testSetNotificationTimestampsForUser_anonUser() {
2434 $store = $this->newWatchedItemStore(
2435 $this->getMockLoadBalancer( $this->getMockDb() ),
2436 $this->getMockCache(),
2437 $this->getMockReadOnlyMode()
2439 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2442 public function testSetNotificationTimestampsForUser_allRows() {
2443 $user = $this->getMockNonAnonUserWithId( 1 );
2444 $timestamp = '20100101010101';
2446 $mockDb = $this->getMockDb();
2447 $mockDb->expects( $this->once() )
2448 ->method( 'update' )
2449 ->with(
2450 'watchlist',
2451 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2452 [ 'wl_user' => 1 ]
2454 ->will( $this->returnValue( true ) );
2455 $mockDb->expects( $this->exactly( 1 ) )
2456 ->method( 'timestamp' )
2457 ->will( $this->returnCallback( function ( $value ) {
2458 return 'TS' . $value . 'TS';
2459 } ) );
2461 $store = $this->newWatchedItemStore(
2462 $this->getMockLoadBalancer( $mockDb ),
2463 $this->getMockCache(),
2464 $this->getMockReadOnlyMode()
2467 $this->assertTrue(
2468 $store->setNotificationTimestampsForUser( $user, $timestamp )
2472 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2473 $user = $this->getMockNonAnonUserWithId( 1 );
2474 $timestamp = null;
2476 $mockDb = $this->getMockDb();
2477 $mockDb->expects( $this->once() )
2478 ->method( 'update' )
2479 ->with(
2480 'watchlist',
2481 [ 'wl_notificationtimestamp' => null ],
2482 [ 'wl_user' => 1 ]
2484 ->will( $this->returnValue( true ) );
2485 $mockDb->expects( $this->exactly( 0 ) )
2486 ->method( 'timestamp' )
2487 ->will( $this->returnCallback( function ( $value ) {
2488 return 'TS' . $value . 'TS';
2489 } ) );
2491 $store = $this->newWatchedItemStore(
2492 $this->getMockLoadBalancer( $mockDb ),
2493 $this->getMockCache(),
2494 $this->getMockReadOnlyMode()
2497 $this->assertTrue(
2498 $store->setNotificationTimestampsForUser( $user, $timestamp )
2502 public function testSetNotificationTimestampsForUser_specificTargets() {
2503 $user = $this->getMockNonAnonUserWithId( 1 );
2504 $timestamp = '20100101010101';
2505 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2507 $mockDb = $this->getMockDb();
2508 $mockDb->expects( $this->once() )
2509 ->method( 'update' )
2510 ->with(
2511 'watchlist',
2512 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2513 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2515 ->will( $this->returnValue( true ) );
2516 $mockDb->expects( $this->exactly( 1 ) )
2517 ->method( 'timestamp' )
2518 ->will( $this->returnCallback( function ( $value ) {
2519 return 'TS' . $value . 'TS';
2520 } ) );
2521 $mockDb->expects( $this->once() )
2522 ->method( 'makeWhereFrom2d' )
2523 ->with(
2524 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2525 $this->isType( 'string' ),
2526 $this->isType( 'string' )
2528 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2530 $store = $this->newWatchedItemStore(
2531 $this->getMockLoadBalancer( $mockDb ),
2532 $this->getMockCache(),
2533 $this->getMockReadOnlyMode()
2536 $this->assertTrue(
2537 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2541 public function testUpdateNotificationTimestamp_watchersExist() {
2542 $mockDb = $this->getMockDb();
2543 $mockDb->expects( $this->once() )
2544 ->method( 'selectFieldValues' )
2545 ->with(
2546 'watchlist',
2547 'wl_user',
2549 'wl_user != 1',
2550 'wl_namespace' => 0,
2551 'wl_title' => 'SomeDbKey',
2552 'wl_notificationtimestamp IS NULL'
2555 ->will( $this->returnValue( [ '2', '3' ] ) );
2556 $mockDb->expects( $this->once() )
2557 ->method( 'update' )
2558 ->with(
2559 'watchlist',
2560 [ 'wl_notificationtimestamp' => null ],
2562 'wl_user' => [ 2, 3 ],
2563 'wl_namespace' => 0,
2564 'wl_title' => 'SomeDbKey',
2568 $mockCache = $this->getMockCache();
2569 $mockCache->expects( $this->never() )->method( 'set' );
2570 $mockCache->expects( $this->never() )->method( 'get' );
2571 $mockCache->expects( $this->never() )->method( 'delete' );
2573 $store = $this->newWatchedItemStore(
2574 $this->getMockLoadBalancer( $mockDb ),
2575 $mockCache,
2576 $this->getMockReadOnlyMode()
2579 $this->assertEquals(
2580 [ 2, 3 ],
2581 $store->updateNotificationTimestamp(
2582 $this->getMockNonAnonUserWithId( 1 ),
2583 new TitleValue( 0, 'SomeDbKey' ),
2584 '20151212010101'
2589 public function testUpdateNotificationTimestamp_noWatchers() {
2590 $mockDb = $this->getMockDb();
2591 $mockDb->expects( $this->once() )
2592 ->method( 'selectFieldValues' )
2593 ->with(
2594 'watchlist',
2595 'wl_user',
2597 'wl_user != 1',
2598 'wl_namespace' => 0,
2599 'wl_title' => 'SomeDbKey',
2600 'wl_notificationtimestamp IS NULL'
2603 ->will(
2604 $this->returnValue( [] )
2606 $mockDb->expects( $this->never() )
2607 ->method( 'update' );
2609 $mockCache = $this->getMockCache();
2610 $mockCache->expects( $this->never() )->method( 'set' );
2611 $mockCache->expects( $this->never() )->method( 'get' );
2612 $mockCache->expects( $this->never() )->method( 'delete' );
2614 $store = $this->newWatchedItemStore(
2615 $this->getMockLoadBalancer( $mockDb ),
2616 $mockCache,
2617 $this->getMockReadOnlyMode()
2620 $watchers = $store->updateNotificationTimestamp(
2621 $this->getMockNonAnonUserWithId( 1 ),
2622 new TitleValue( 0, 'SomeDbKey' ),
2623 '20151212010101'
2625 $this->assertInternalType( 'array', $watchers );
2626 $this->assertEmpty( $watchers );
2629 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2630 $user = $this->getMockNonAnonUserWithId( 1 );
2631 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2633 $mockDb = $this->getMockDb();
2634 $mockDb->expects( $this->once() )
2635 ->method( 'selectRow' )
2636 ->will( $this->returnValue(
2637 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2638 ) );
2639 $mockDb->expects( $this->once() )
2640 ->method( 'selectFieldValues' )
2641 ->will(
2642 $this->returnValue( [ '2', '3' ] )
2644 $mockDb->expects( $this->once() )
2645 ->method( 'update' );
2647 $mockCache = $this->getMockCache();
2648 $mockCache->expects( $this->once() )
2649 ->method( 'set' )
2650 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2651 $mockCache->expects( $this->once() )
2652 ->method( 'get' )
2653 ->with( '0:SomeDbKey:1' );
2654 $mockCache->expects( $this->once() )
2655 ->method( 'delete' )
2656 ->with( '0:SomeDbKey:1' );
2658 $store = $this->newWatchedItemStore(
2659 $this->getMockLoadBalancer( $mockDb ),
2660 $mockCache,
2661 $this->getMockReadOnlyMode()
2664 // This will add the item to the cache
2665 $store->getWatchedItem( $user, $titleValue );
2667 $store->updateNotificationTimestamp(
2668 $this->getMockNonAnonUserWithId( 1 ),
2669 $titleValue,
2670 '20151212010101'