2 use MediaWiki\Linker\LinkTarget
;
7 * @covers WatchedItemStore
9 class WatchedItemStoreUnitTest
extends PHPUnit_Framework_TestCase
{
12 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
14 private function getMockDb() {
15 return $this->getMock( IDatabase
::class );
19 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
21 private function getMockLoadBalancer(
23 $expectedConnectionType = null,
24 $readOnlyReason = false
26 $mock = $this->getMockBuilder( LoadBalancer
::class )
27 ->disableOriginalConstructor()
29 if ( $expectedConnectionType !== null ) {
30 $mock->expects( $this->any() )
31 ->method( 'getConnection' )
32 ->with( $expectedConnectionType )
33 ->will( $this->returnValue( $mockDb ) );
35 $mock->expects( $this->any() )
36 ->method( 'getConnection' )
37 ->will( $this->returnValue( $mockDb ) );
39 $mock->expects( $this->any() )
40 ->method( 'getReadOnlyReason' )
41 ->will( $this->returnValue( $readOnlyReason ) );
46 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
48 private function getMockCache() {
49 $mock = $this->getMockBuilder( HashBagOStuff
::class )
50 ->disableOriginalConstructor()
52 $mock->expects( $this->any() )
54 ->will( $this->returnCallback( function() {
55 return implode( ':', func_get_args() );
62 * @return PHPUnit_Framework_MockObject_MockObject|User
64 private function getMockNonAnonUserWithId( $id ) {
65 $mock = $this->getMock( User
::class );
66 $mock->expects( $this->any() )
68 ->will( $this->returnValue( false ) );
69 $mock->expects( $this->any() )
71 ->will( $this->returnValue( $id ) );
78 private function getAnonUser() {
79 return User
::newFromName( 'Anon_User' );
82 private function getFakeRow( array $rowValues ) {
83 $fakeRow = new stdClass();
84 foreach ( $rowValues as $valueName => $value ) {
85 $fakeRow->$valueName = $value;
90 private function newWatchedItemStore( LoadBalancer
$loadBalancer, HashBagOStuff
$cache ) {
91 return new WatchedItemStore(
97 public function testGetDefaultInstance() {
98 $instanceOne = WatchedItemStore
::getDefaultInstance();
99 $instanceTwo = WatchedItemStore
::getDefaultInstance();
100 $this->assertSame( $instanceOne, $instanceTwo );
103 public function testOverrideDefaultInstance() {
104 $instance = WatchedItemStore
::getDefaultInstance();
105 $scopedOverride = $instance->overrideDefaultInstance( null );
107 $this->assertNotSame( $instance, WatchedItemStore
::getDefaultInstance() );
109 unset( $scopedOverride );
111 $this->assertSame( $instance, WatchedItemStore
::getDefaultInstance() );
114 public function testCountWatchedItems() {
115 $user = $this->getMockNonAnonUserWithId( 1 );
117 $mockDb = $this->getMockDb();
118 $mockDb->expects( $this->exactly( 1 ) )
119 ->method( 'selectField' )
124 'wl_user' => $user->getId(),
126 $this->isType( 'string' )
128 ->will( $this->returnValue( 12 ) );
130 $mockCache = $this->getMockCache();
131 $mockCache->expects( $this->never() )->method( 'get' );
132 $mockCache->expects( $this->never() )->method( 'set' );
133 $mockCache->expects( $this->never() )->method( 'delete' );
135 $store = $this->newWatchedItemStore(
136 $this->getMockLoadBalancer( $mockDb ),
140 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
143 public function testCountWatchers() {
144 $titleValue = new TitleValue( 0, 'SomeDbKey' );
146 $mockDb = $this->getMockDb();
147 $mockDb->expects( $this->exactly( 1 ) )
148 ->method( 'selectField' )
153 'wl_namespace' => $titleValue->getNamespace(),
154 'wl_title' => $titleValue->getDBkey(),
156 $this->isType( 'string' )
158 ->will( $this->returnValue( 7 ) );
160 $mockCache = $this->getMockCache();
161 $mockCache->expects( $this->never() )->method( 'get' );
162 $mockCache->expects( $this->never() )->method( 'set' );
163 $mockCache->expects( $this->never() )->method( 'delete' );
165 $store = $this->newWatchedItemStore(
166 $this->getMockLoadBalancer( $mockDb ),
170 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
173 public function testCountWatchersMultiple() {
175 new TitleValue( 0, 'SomeDbKey' ),
176 new TitleValue( 0, 'OtherDbKey' ),
177 new TitleValue( 1, 'AnotherDbKey' ),
180 $mockDb = $this->getMockDb();
183 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
184 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
185 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
188 $mockDb->expects( $this->once() )
189 ->method( 'makeWhereFrom2d' )
191 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
192 $this->isType( 'string' ),
193 $this->isType( 'string' )
195 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
196 $mockDb->expects( $this->once() )
200 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
201 [ 'makeWhereFrom2d return value' ],
202 $this->isType( 'string' ),
204 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
208 $this->returnValue( $dbResult )
211 $mockCache = $this->getMockCache();
212 $mockCache->expects( $this->never() )->method( 'get' );
213 $mockCache->expects( $this->never() )->method( 'set' );
214 $mockCache->expects( $this->never() )->method( 'delete' );
216 $store = $this->newWatchedItemStore(
217 $this->getMockLoadBalancer( $mockDb ),
222 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
223 1 => [ 'AnotherDbKey' => 500 ],
225 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
228 public function provideIntWithDbUnsafeVersion() {
231 [ "50; DROP TABLE watchlist;\n--" ],
236 * @dataProvider provideIntWithDbUnsafeVersion
238 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
240 new TitleValue( 0, 'SomeDbKey' ),
241 new TitleValue( 0, 'OtherDbKey' ),
242 new TitleValue( 1, 'AnotherDbKey' ),
245 $mockDb = $this->getMockDb();
248 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
249 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
250 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
253 $mockDb->expects( $this->once() )
254 ->method( 'makeWhereFrom2d' )
256 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
257 $this->isType( 'string' ),
258 $this->isType( 'string' )
260 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
261 $mockDb->expects( $this->once() )
265 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
266 [ 'makeWhereFrom2d return value' ],
267 $this->isType( 'string' ),
269 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
270 'HAVING' => 'COUNT(*) >= 50',
274 $this->returnValue( $dbResult )
277 $mockCache = $this->getMockCache();
278 $mockCache->expects( $this->never() )->method( 'get' );
279 $mockCache->expects( $this->never() )->method( 'set' );
280 $mockCache->expects( $this->never() )->method( 'delete' );
282 $store = $this->newWatchedItemStore(
283 $this->getMockLoadBalancer( $mockDb ),
288 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
289 1 => [ 'AnotherDbKey' => 500 ],
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' )
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 ) {
319 $mockDb->expects( $this->exactly( 1 ) )
320 ->method( 'timestamp' )
321 ->will( $this->returnCallback( function( $value ) {
322 return 'TS' . $value . 'TS';
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 ),
335 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
338 public function testCountVisitingWatchersMultiple() {
339 $titleValuesWithThresholds = [
340 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
341 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
342 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
346 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
347 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
348 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
350 $mockDb = $this->getMockDb();
351 $mockDb->expects( $this->exactly( 2 * 3 ) )
352 ->method( 'addQuotes' )
353 ->will( $this->returnCallback( function( $value ) {
356 $mockDb->expects( $this->exactly( 3 ) )
357 ->method( 'timestamp' )
358 ->will( $this->returnCallback( function( $value ) {
359 return 'TS' . $value . 'TS';
361 $mockDb->expects( $this->any() )
362 ->method( 'makeList' )
364 $this->isType( 'array' ),
365 $this->isType( 'int' )
367 ->will( $this->returnCallback( function( $a, $conj ) {
368 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
369 return join( $sqlConj, array_map( function( $s ) {
370 return '(' . $s . ')';
374 $mockDb->expects( $this->never() )
375 ->method( 'makeWhereFrom2d' );
378 '((wl_namespace = 0) AND (' .
379 "(((wl_title = 'SomeDbKey') AND (" .
380 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
382 "(wl_title = 'OtherDbKey') AND (" .
383 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
385 ') OR ((wl_namespace = 1) AND (' .
386 "(((wl_title = 'AnotherDbKey') AND (".
387 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
389 $mockDb->expects( $this->once() )
393 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
395 $this->isType( 'string' ),
397 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
401 $this->returnValue( $dbResult )
404 $mockCache = $this->getMockCache();
405 $mockCache->expects( $this->never() )->method( 'get' );
406 $mockCache->expects( $this->never() )->method( 'set' );
407 $mockCache->expects( $this->never() )->method( 'delete' );
409 $store = $this->newWatchedItemStore(
410 $this->getMockLoadBalancer( $mockDb ),
415 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
416 1 => [ 'AnotherDbKey' => 500 ],
420 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
424 public function testCountVisitingWatchersMultiple_withMissingTargets() {
425 $titleValuesWithThresholds = [
426 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
427 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
428 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
429 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
430 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
434 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
435 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
436 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
438 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
441 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
444 $mockDb = $this->getMockDb();
445 $mockDb->expects( $this->exactly( 2 * 3 ) )
446 ->method( 'addQuotes' )
447 ->will( $this->returnCallback( function( $value ) {
450 $mockDb->expects( $this->exactly( 3 ) )
451 ->method( 'timestamp' )
452 ->will( $this->returnCallback( function( $value ) {
453 return 'TS' . $value . 'TS';
455 $mockDb->expects( $this->any() )
456 ->method( 'makeList' )
458 $this->isType( 'array' ),
459 $this->isType( 'int' )
461 ->will( $this->returnCallback( function( $a, $conj ) {
462 $sqlConj = $conj === LIST_AND ?
' AND ' : ' OR ';
463 return join( $sqlConj, array_map( function( $s ) {
464 return '(' . $s . ')';
468 $mockDb->expects( $this->once() )
469 ->method( 'makeWhereFrom2d' )
471 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
472 $this->isType( 'string' ),
473 $this->isType( 'string' )
475 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
478 '((wl_namespace = 0) AND (' .
479 "(((wl_title = 'SomeDbKey') AND (" .
480 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
482 "(wl_title = 'OtherDbKey') AND (" .
483 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
485 ') OR ((wl_namespace = 1) AND (' .
486 "(((wl_title = 'AnotherDbKey') AND (".
487 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
490 '(makeWhereFrom2d return value)';
491 $mockDb->expects( $this->once() )
495 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
497 $this->isType( 'string' ),
499 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
503 $this->returnValue( $dbResult )
506 $mockCache = $this->getMockCache();
507 $mockCache->expects( $this->never() )->method( 'get' );
508 $mockCache->expects( $this->never() )->method( 'set' );
509 $mockCache->expects( $this->never() )->method( 'delete' );
511 $store = $this->newWatchedItemStore(
512 $this->getMockLoadBalancer( $mockDb ),
518 'SomeDbKey' => 100, 'OtherDbKey' => 300,
519 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
521 1 => [ 'AnotherDbKey' => 500 ],
525 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
530 * @dataProvider provideIntWithDbUnsafeVersion
532 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
533 $titleValuesWithThresholds = [
534 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
535 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
536 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
539 $mockDb = $this->getMockDb();
540 $mockDb->expects( $this->any() )
541 ->method( 'makeList' )
542 ->will( $this->returnValue( 'makeList return value' ) );
543 $mockDb->expects( $this->once() )
547 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
548 'makeList return value',
549 $this->isType( 'string' ),
551 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
552 'HAVING' => 'COUNT(*) >= 50',
556 $this->returnValue( [] )
559 $mockCache = $this->getMockCache();
560 $mockCache->expects( $this->never() )->method( 'get' );
561 $mockCache->expects( $this->never() )->method( 'set' );
562 $mockCache->expects( $this->never() )->method( 'delete' );
564 $store = $this->newWatchedItemStore(
565 $this->getMockLoadBalancer( $mockDb ),
570 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
571 1 => [ 'AnotherDbKey' => 0 ],
575 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
579 public function testCountUnreadNotifications() {
580 $user = $this->getMockNonAnonUserWithId( 1 );
582 $mockDb = $this->getMockDb();
583 $mockDb->expects( $this->exactly( 1 ) )
584 ->method( 'selectRowCount' )
589 "wl_notificationtimestamp IS NOT NULL",
592 $this->isType( 'string' )
594 ->will( $this->returnValue( 9 ) );
596 $mockCache = $this->getMockCache();
597 $mockCache->expects( $this->never() )->method( 'set' );
598 $mockCache->expects( $this->never() )->method( 'get' );
599 $mockCache->expects( $this->never() )->method( 'delete' );
601 $store = $this->newWatchedItemStore(
602 $this->getMockLoadBalancer( $mockDb ),
606 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
610 * @dataProvider provideIntWithDbUnsafeVersion
612 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
613 $user = $this->getMockNonAnonUserWithId( 1 );
615 $mockDb = $this->getMockDb();
616 $mockDb->expects( $this->exactly( 1 ) )
617 ->method( 'selectRowCount' )
622 "wl_notificationtimestamp IS NOT NULL",
625 $this->isType( 'string' ),
628 ->will( $this->returnValue( 50 ) );
630 $mockCache = $this->getMockCache();
631 $mockCache->expects( $this->never() )->method( 'set' );
632 $mockCache->expects( $this->never() )->method( 'get' );
633 $mockCache->expects( $this->never() )->method( 'delete' );
635 $store = $this->newWatchedItemStore(
636 $this->getMockLoadBalancer( $mockDb ),
642 $store->countUnreadNotifications( $user, $limit )
647 * @dataProvider provideIntWithDbUnsafeVersion
649 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
650 $user = $this->getMockNonAnonUserWithId( 1 );
652 $mockDb = $this->getMockDb();
653 $mockDb->expects( $this->exactly( 1 ) )
654 ->method( 'selectRowCount' )
659 "wl_notificationtimestamp IS NOT NULL",
662 $this->isType( 'string' ),
665 ->will( $this->returnValue( 9 ) );
667 $mockCache = $this->getMockCache();
668 $mockCache->expects( $this->never() )->method( 'set' );
669 $mockCache->expects( $this->never() )->method( 'get' );
670 $mockCache->expects( $this->never() )->method( 'delete' );
672 $store = $this->newWatchedItemStore(
673 $this->getMockLoadBalancer( $mockDb ),
679 $store->countUnreadNotifications( $user, $limit )
683 public function testDuplicateEntry_nothingToDuplicate() {
684 $mockDb = $this->getMockDb();
685 $mockDb->expects( $this->once() )
691 'wl_notificationtimestamp',
695 'wl_title' => 'Old_Title',
697 'WatchedItemStore::duplicateEntry',
700 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
702 $store = $this->newWatchedItemStore(
703 $this->getMockLoadBalancer( $mockDb ),
704 $this->getMockCache()
707 $store->duplicateEntry(
708 Title
::newFromText( 'Old_Title' ),
709 Title
::newFromText( 'New_Title' )
713 public function testDuplicateEntry_somethingToDuplicate() {
715 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
716 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
719 $mockDb = $this->getMockDb();
720 $mockDb->expects( $this->at( 0 ) )
726 'wl_notificationtimestamp',
730 'wl_title' => 'Old_Title',
733 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
734 $mockDb->expects( $this->at( 1 ) )
735 ->method( 'replace' )
738 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
743 'wl_title' => 'New_Title',
744 'wl_notificationtimestamp' => '20151212010101',
749 'wl_title' => 'New_Title',
750 'wl_notificationtimestamp' => null,
753 $this->isType( 'string' )
756 $mockCache = $this->getMockCache();
757 $mockCache->expects( $this->never() )->method( 'get' );
758 $mockCache->expects( $this->never() )->method( 'delete' );
760 $store = $this->newWatchedItemStore(
761 $this->getMockLoadBalancer( $mockDb ),
765 $store->duplicateEntry(
766 Title
::newFromText( 'Old_Title' ),
767 Title
::newFromText( 'New_Title' )
771 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
772 $mockDb = $this->getMockDb();
773 $mockDb->expects( $this->at( 0 ) )
779 'wl_notificationtimestamp',
783 'wl_title' => 'Old_Title',
786 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
787 $mockDb->expects( $this->at( 1 ) )
793 'wl_notificationtimestamp',
797 'wl_title' => 'Old_Title',
800 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
802 $mockCache = $this->getMockCache();
803 $mockCache->expects( $this->never() )->method( 'get' );
804 $mockCache->expects( $this->never() )->method( 'delete' );
806 $store = $this->newWatchedItemStore(
807 $this->getMockLoadBalancer( $mockDb ),
811 $store->duplicateAllAssociatedEntries(
812 Title
::newFromText( 'Old_Title' ),
813 Title
::newFromText( 'New_Title' )
817 public function provideLinkTargetPairs() {
819 [ Title
::newFromText( 'Old_Title' ), Title
::newFromText( 'New_Title' ) ],
820 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
825 * @dataProvider provideLinkTargetPairs
827 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
828 LinkTarget
$oldTarget,
829 LinkTarget
$newTarget
832 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
835 $mockDb = $this->getMockDb();
836 $mockDb->expects( $this->at( 0 ) )
842 'wl_notificationtimestamp',
845 'wl_namespace' => $oldTarget->getNamespace(),
846 'wl_title' => $oldTarget->getDBkey(),
849 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
850 $mockDb->expects( $this->at( 1 ) )
851 ->method( 'replace' )
854 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
858 'wl_namespace' => $newTarget->getNamespace(),
859 'wl_title' => $newTarget->getDBkey(),
860 'wl_notificationtimestamp' => '20151212010101',
863 $this->isType( 'string' )
865 $mockDb->expects( $this->at( 2 ) )
871 'wl_notificationtimestamp',
874 'wl_namespace' => $oldTarget->getNamespace() +
1,
875 'wl_title' => $oldTarget->getDBkey(),
878 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
879 $mockDb->expects( $this->at( 3 ) )
880 ->method( 'replace' )
883 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
887 'wl_namespace' => $newTarget->getNamespace() +
1,
888 'wl_title' => $newTarget->getDBkey(),
889 'wl_notificationtimestamp' => '20151212010101',
892 $this->isType( 'string' )
895 $mockCache = $this->getMockCache();
896 $mockCache->expects( $this->never() )->method( 'get' );
897 $mockCache->expects( $this->never() )->method( 'delete' );
899 $store = $this->newWatchedItemStore(
900 $this->getMockLoadBalancer( $mockDb ),
904 $store->duplicateAllAssociatedEntries(
910 public function testAddWatch_nonAnonymousUser() {
911 $mockDb = $this->getMockDb();
912 $mockDb->expects( $this->once() )
920 'wl_title' => 'Some_Page',
921 'wl_notificationtimestamp' => null,
926 $mockCache = $this->getMockCache();
927 $mockCache->expects( $this->once() )
929 ->with( '0:Some_Page:1' );
931 $store = $this->newWatchedItemStore(
932 $this->getMockLoadBalancer( $mockDb ),
937 $this->getMockNonAnonUserWithId( 1 ),
938 Title
::newFromText( 'Some_Page' )
942 public function testAddWatch_anonymousUser() {
943 $mockDb = $this->getMockDb();
944 $mockDb->expects( $this->never() )
945 ->method( 'insert' );
947 $mockCache = $this->getMockCache();
948 $mockCache->expects( $this->never() )
949 ->method( 'delete' );
951 $store = $this->newWatchedItemStore(
952 $this->getMockLoadBalancer( $mockDb ),
957 $this->getAnonUser(),
958 Title
::newFromText( 'Some_Page' )
962 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
963 $store = $this->newWatchedItemStore(
964 $this->getMockLoadBalancer( $this->getMockDb(), null, 'Some Reason' ),
965 $this->getMockCache()
969 $store->addWatchBatchForUser(
970 $this->getMockNonAnonUserWithId( 1 ),
971 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
976 public function testAddWatchBatchForUser_nonAnonymousUser() {
977 $mockDb = $this->getMockDb();
978 $mockDb->expects( $this->once() )
986 'wl_title' => 'Some_Page',
987 'wl_notificationtimestamp' => null,
992 'wl_title' => 'Some_Page',
993 'wl_notificationtimestamp' => null,
998 $mockCache = $this->getMockCache();
999 $mockCache->expects( $this->exactly( 2 ) )
1000 ->method( 'delete' );
1001 $mockCache->expects( $this->at( 1 ) )
1002 ->method( 'delete' )
1003 ->with( '0:Some_Page:1' );
1004 $mockCache->expects( $this->at( 3 ) )
1005 ->method( 'delete' )
1006 ->with( '1:Some_Page:1' );
1008 $store = $this->newWatchedItemStore(
1009 $this->getMockLoadBalancer( $mockDb ),
1013 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1016 $store->addWatchBatchForUser(
1018 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1023 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1024 $mockDb = $this->getMockDb();
1025 $mockDb->expects( $this->never() )
1026 ->method( 'insert' );
1028 $mockCache = $this->getMockCache();
1029 $mockCache->expects( $this->never() )
1030 ->method( 'delete' );
1032 $store = $this->newWatchedItemStore(
1033 $this->getMockLoadBalancer( $mockDb ),
1038 $store->addWatchBatchForUser(
1039 $this->getAnonUser(),
1040 [ new TitleValue( 0, 'Other_Page' ) ]
1045 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1046 $user = $this->getMockNonAnonUserWithId( 1 );
1047 $mockDb = $this->getMockDb();
1048 $mockDb->expects( $this->never() )
1049 ->method( 'insert' );
1051 $mockCache = $this->getMockCache();
1052 $mockCache->expects( $this->never() )
1053 ->method( 'delete' );
1055 $store = $this->newWatchedItemStore(
1056 $this->getMockLoadBalancer( $mockDb ),
1061 $store->addWatchBatchForUser( $user, [] )
1065 public function testLoadWatchedItem_existingItem() {
1066 $mockDb = $this->getMockDb();
1067 $mockDb->expects( $this->once() )
1068 ->method( 'selectRow' )
1071 'wl_notificationtimestamp',
1074 'wl_namespace' => 0,
1075 'wl_title' => 'SomeDbKey',
1078 ->will( $this->returnValue(
1079 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1082 $mockCache = $this->getMockCache();
1083 $mockCache->expects( $this->once() )
1089 $store = $this->newWatchedItemStore(
1090 $this->getMockLoadBalancer( $mockDb ),
1094 $watchedItem = $store->loadWatchedItem(
1095 $this->getMockNonAnonUserWithId( 1 ),
1096 new TitleValue( 0, 'SomeDbKey' )
1098 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1099 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1100 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1101 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1104 public function testLoadWatchedItem_noItem() {
1105 $mockDb = $this->getMockDb();
1106 $mockDb->expects( $this->once() )
1107 ->method( 'selectRow' )
1110 'wl_notificationtimestamp',
1113 'wl_namespace' => 0,
1114 'wl_title' => 'SomeDbKey',
1117 ->will( $this->returnValue( [] ) );
1119 $mockCache = $this->getMockCache();
1120 $mockCache->expects( $this->never() )->method( 'get' );
1121 $mockCache->expects( $this->never() )->method( 'delete' );
1123 $store = $this->newWatchedItemStore(
1124 $this->getMockLoadBalancer( $mockDb ),
1129 $store->loadWatchedItem(
1130 $this->getMockNonAnonUserWithId( 1 ),
1131 new TitleValue( 0, 'SomeDbKey' )
1136 public function testLoadWatchedItem_anonymousUser() {
1137 $mockDb = $this->getMockDb();
1138 $mockDb->expects( $this->never() )
1139 ->method( 'selectRow' );
1141 $mockCache = $this->getMockCache();
1142 $mockCache->expects( $this->never() )->method( 'get' );
1143 $mockCache->expects( $this->never() )->method( 'delete' );
1145 $store = $this->newWatchedItemStore(
1146 $this->getMockLoadBalancer( $mockDb ),
1151 $store->loadWatchedItem(
1152 $this->getAnonUser(),
1153 new TitleValue( 0, 'SomeDbKey' )
1158 public function testRemoveWatch_existingItem() {
1159 $mockDb = $this->getMockDb();
1160 $mockDb->expects( $this->once() )
1161 ->method( 'delete' )
1166 'wl_namespace' => 0,
1167 'wl_title' => 'SomeDbKey',
1170 $mockDb->expects( $this->once() )
1171 ->method( 'affectedRows' )
1172 ->will( $this->returnValue( 1 ) );
1174 $mockCache = $this->getMockCache();
1175 $mockCache->expects( $this->never() )->method( 'get' );
1176 $mockCache->expects( $this->once() )
1177 ->method( 'delete' )
1178 ->with( '0:SomeDbKey:1' );
1180 $store = $this->newWatchedItemStore(
1181 $this->getMockLoadBalancer( $mockDb ),
1186 $store->removeWatch(
1187 $this->getMockNonAnonUserWithId( 1 ),
1188 new TitleValue( 0, 'SomeDbKey' )
1193 public function testRemoveWatch_noItem() {
1194 $mockDb = $this->getMockDb();
1195 $mockDb->expects( $this->once() )
1196 ->method( 'delete' )
1201 'wl_namespace' => 0,
1202 'wl_title' => 'SomeDbKey',
1205 $mockDb->expects( $this->once() )
1206 ->method( 'affectedRows' )
1207 ->will( $this->returnValue( 0 ) );
1209 $mockCache = $this->getMockCache();
1210 $mockCache->expects( $this->never() )->method( 'get' );
1211 $mockCache->expects( $this->once() )
1212 ->method( 'delete' )
1213 ->with( '0:SomeDbKey:1' );
1215 $store = $this->newWatchedItemStore(
1216 $this->getMockLoadBalancer( $mockDb ),
1221 $store->removeWatch(
1222 $this->getMockNonAnonUserWithId( 1 ),
1223 new TitleValue( 0, 'SomeDbKey' )
1228 public function testRemoveWatch_anonymousUser() {
1229 $mockDb = $this->getMockDb();
1230 $mockDb->expects( $this->never() )
1231 ->method( 'delete' );
1233 $mockCache = $this->getMockCache();
1234 $mockCache->expects( $this->never() )->method( 'get' );
1235 $mockCache->expects( $this->never() )
1236 ->method( 'delete' );
1238 $store = $this->newWatchedItemStore(
1239 $this->getMockLoadBalancer( $mockDb ),
1244 $store->removeWatch(
1245 $this->getAnonUser(),
1246 new TitleValue( 0, 'SomeDbKey' )
1251 public function testGetWatchedItem_existingItem() {
1252 $mockDb = $this->getMockDb();
1253 $mockDb->expects( $this->once() )
1254 ->method( 'selectRow' )
1257 'wl_notificationtimestamp',
1260 'wl_namespace' => 0,
1261 'wl_title' => 'SomeDbKey',
1264 ->will( $this->returnValue(
1265 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1268 $mockCache = $this->getMockCache();
1269 $mockCache->expects( $this->never() )->method( 'delete' );
1270 $mockCache->expects( $this->once() )
1275 ->will( $this->returnValue( null ) );
1276 $mockCache->expects( $this->once() )
1282 $store = $this->newWatchedItemStore(
1283 $this->getMockLoadBalancer( $mockDb ),
1287 $watchedItem = $store->getWatchedItem(
1288 $this->getMockNonAnonUserWithId( 1 ),
1289 new TitleValue( 0, 'SomeDbKey' )
1291 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1292 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1293 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1294 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1297 public function testGetWatchedItem_cachedItem() {
1298 $mockDb = $this->getMockDb();
1299 $mockDb->expects( $this->never() )
1300 ->method( 'selectRow' );
1302 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1303 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1304 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1306 $mockCache = $this->getMockCache();
1307 $mockCache->expects( $this->never() )->method( 'delete' );
1308 $mockCache->expects( $this->never() )->method( 'set' );
1309 $mockCache->expects( $this->once() )
1314 ->will( $this->returnValue( $cachedItem ) );
1316 $store = $this->newWatchedItemStore(
1317 $this->getMockLoadBalancer( $mockDb ),
1321 $this->assertEquals(
1323 $store->getWatchedItem(
1330 public function testGetWatchedItem_noItem() {
1331 $mockDb = $this->getMockDb();
1332 $mockDb->expects( $this->once() )
1333 ->method( 'selectRow' )
1336 'wl_notificationtimestamp',
1339 'wl_namespace' => 0,
1340 'wl_title' => 'SomeDbKey',
1343 ->will( $this->returnValue( [] ) );
1345 $mockCache = $this->getMockCache();
1346 $mockCache->expects( $this->never() )->method( 'set' );
1347 $mockCache->expects( $this->never() )->method( 'delete' );
1348 $mockCache->expects( $this->once() )
1350 ->with( '0:SomeDbKey:1' )
1351 ->will( $this->returnValue( false ) );
1353 $store = $this->newWatchedItemStore(
1354 $this->getMockLoadBalancer( $mockDb ),
1359 $store->getWatchedItem(
1360 $this->getMockNonAnonUserWithId( 1 ),
1361 new TitleValue( 0, 'SomeDbKey' )
1366 public function testGetWatchedItem_anonymousUser() {
1367 $mockDb = $this->getMockDb();
1368 $mockDb->expects( $this->never() )
1369 ->method( 'selectRow' );
1371 $mockCache = $this->getMockCache();
1372 $mockCache->expects( $this->never() )->method( 'set' );
1373 $mockCache->expects( $this->never() )->method( 'get' );
1374 $mockCache->expects( $this->never() )->method( 'delete' );
1376 $store = $this->newWatchedItemStore(
1377 $this->getMockLoadBalancer( $mockDb ),
1382 $store->getWatchedItem(
1383 $this->getAnonUser(),
1384 new TitleValue( 0, 'SomeDbKey' )
1389 public function testGetWatchedItemsForUser() {
1390 $mockDb = $this->getMockDb();
1391 $mockDb->expects( $this->once() )
1392 ->method( 'select' )
1395 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1398 ->will( $this->returnValue( [
1399 $this->getFakeRow( [
1400 'wl_namespace' => 0,
1401 'wl_title' => 'Foo1',
1402 'wl_notificationtimestamp' => '20151212010101',
1404 $this->getFakeRow( [
1405 'wl_namespace' => 1,
1406 'wl_title' => 'Foo2',
1407 'wl_notificationtimestamp' => null,
1411 $mockCache = $this->getMockCache();
1412 $mockCache->expects( $this->never() )->method( 'delete' );
1413 $mockCache->expects( $this->never() )->method( 'get' );
1414 $mockCache->expects( $this->never() )->method( 'set' );
1416 $store = $this->newWatchedItemStore(
1417 $this->getMockLoadBalancer( $mockDb ),
1420 $user = $this->getMockNonAnonUserWithId( 1 );
1422 $watchedItems = $store->getWatchedItemsForUser( $user );
1424 $this->assertInternalType( 'array', $watchedItems );
1425 $this->assertCount( 2, $watchedItems );
1426 foreach ( $watchedItems as $watchedItem ) {
1427 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1429 $this->assertEquals(
1430 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1433 $this->assertEquals(
1434 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1439 public function provideDbTypes() {
1441 [ false, DB_SLAVE
],
1442 [ true, DB_MASTER
],
1447 * @dataProvider provideDbTypes
1449 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1450 $mockDb = $this->getMockDb();
1451 $mockCache = $this->getMockCache();
1452 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1453 $user = $this->getMockNonAnonUserWithId( 1 );
1455 $mockDb->expects( $this->once() )
1456 ->method( 'select' )
1459 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1461 $this->isType( 'string' ),
1462 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1464 ->will( $this->returnValue( [] ) );
1466 $store = $this->newWatchedItemStore(
1471 $watchedItems = $store->getWatchedItemsForUser(
1473 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore
::SORT_ASC
]
1475 $this->assertEquals( [], $watchedItems );
1478 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1479 $store = $this->newWatchedItemStore(
1480 $this->getMockLoadBalancer( $this->getMockDb() ),
1481 $this->getMockCache()
1484 $this->setExpectedException( 'InvalidArgumentException' );
1485 $store->getWatchedItemsForUser(
1486 $this->getMockNonAnonUserWithId( 1 ),
1491 public function testIsWatchedItem_existingItem() {
1492 $mockDb = $this->getMockDb();
1493 $mockDb->expects( $this->once() )
1494 ->method( 'selectRow' )
1497 'wl_notificationtimestamp',
1500 'wl_namespace' => 0,
1501 'wl_title' => 'SomeDbKey',
1504 ->will( $this->returnValue(
1505 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1508 $mockCache = $this->getMockCache();
1509 $mockCache->expects( $this->never() )->method( 'delete' );
1510 $mockCache->expects( $this->once() )
1512 ->with( '0:SomeDbKey:1' )
1513 ->will( $this->returnValue( false ) );
1514 $mockCache->expects( $this->once() )
1520 $store = $this->newWatchedItemStore(
1521 $this->getMockLoadBalancer( $mockDb ),
1527 $this->getMockNonAnonUserWithId( 1 ),
1528 new TitleValue( 0, 'SomeDbKey' )
1533 public function testIsWatchedItem_noItem() {
1534 $mockDb = $this->getMockDb();
1535 $mockDb->expects( $this->once() )
1536 ->method( 'selectRow' )
1539 'wl_notificationtimestamp',
1542 'wl_namespace' => 0,
1543 'wl_title' => 'SomeDbKey',
1546 ->will( $this->returnValue( [] ) );
1548 $mockCache = $this->getMockCache();
1549 $mockCache->expects( $this->never() )->method( 'set' );
1550 $mockCache->expects( $this->never() )->method( 'delete' );
1551 $mockCache->expects( $this->once() )
1553 ->with( '0:SomeDbKey:1' )
1554 ->will( $this->returnValue( false ) );
1556 $store = $this->newWatchedItemStore(
1557 $this->getMockLoadBalancer( $mockDb ),
1563 $this->getMockNonAnonUserWithId( 1 ),
1564 new TitleValue( 0, 'SomeDbKey' )
1569 public function testIsWatchedItem_anonymousUser() {
1570 $mockDb = $this->getMockDb();
1571 $mockDb->expects( $this->never() )
1572 ->method( 'selectRow' );
1574 $mockCache = $this->getMockCache();
1575 $mockCache->expects( $this->never() )->method( 'set' );
1576 $mockCache->expects( $this->never() )->method( 'get' );
1577 $mockCache->expects( $this->never() )->method( 'delete' );
1579 $store = $this->newWatchedItemStore(
1580 $this->getMockLoadBalancer( $mockDb ),
1586 $this->getAnonUser(),
1587 new TitleValue( 0, 'SomeDbKey' )
1592 public function testGetNotificationTimestampsBatch() {
1594 new TitleValue( 0, 'SomeDbKey' ),
1595 new TitleValue( 1, 'AnotherDbKey' ),
1598 $mockDb = $this->getMockDb();
1600 $this->getFakeRow( [
1601 'wl_namespace' => 0,
1602 'wl_title' => 'SomeDbKey',
1603 'wl_notificationtimestamp' => '20151212010101',
1607 'wl_namespace' => 1,
1608 'wl_title' => 'AnotherDbKey',
1609 'wl_notificationtimestamp' => null,
1614 $mockDb->expects( $this->once() )
1615 ->method( 'makeWhereFrom2d' )
1617 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1618 $this->isType( 'string' ),
1619 $this->isType( 'string' )
1621 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1622 $mockDb->expects( $this->once() )
1623 ->method( 'select' )
1626 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1628 'makeWhereFrom2d return value',
1631 $this->isType( 'string' )
1633 ->will( $this->returnValue( $dbResult ) );
1635 $mockCache = $this->getMockCache();
1636 $mockCache->expects( $this->exactly( 2 ) )
1639 [ '0:SomeDbKey:1' ],
1640 [ '1:AnotherDbKey:1' ]
1642 ->will( $this->returnValue( null ) );
1643 $mockCache->expects( $this->never() )->method( 'set' );
1644 $mockCache->expects( $this->never() )->method( 'delete' );
1646 $store = $this->newWatchedItemStore(
1647 $this->getMockLoadBalancer( $mockDb ),
1651 $this->assertEquals(
1653 0 => [ 'SomeDbKey' => '20151212010101', ],
1654 1 => [ 'AnotherDbKey' => null, ],
1656 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1660 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1662 new TitleValue( 0, 'OtherDbKey' ),
1665 $mockDb = $this->getMockDb();
1667 $mockDb->expects( $this->once() )
1668 ->method( 'makeWhereFrom2d' )
1670 [ [ 'OtherDbKey' => 1 ] ],
1671 $this->isType( 'string' ),
1672 $this->isType( 'string' )
1674 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1675 $mockDb->expects( $this->once() )
1676 ->method( 'select' )
1679 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1681 'makeWhereFrom2d return value',
1684 $this->isType( 'string' )
1686 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1688 $mockCache = $this->getMockCache();
1689 $mockCache->expects( $this->once() )
1691 ->with( '0:OtherDbKey:1' )
1692 ->will( $this->returnValue( null ) );
1693 $mockCache->expects( $this->never() )->method( 'set' );
1694 $mockCache->expects( $this->never() )->method( 'delete' );
1696 $store = $this->newWatchedItemStore(
1697 $this->getMockLoadBalancer( $mockDb ),
1701 $this->assertEquals(
1703 0 => [ 'OtherDbKey' => false, ],
1705 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1709 public function testGetNotificationTimestampsBatch_cachedItem() {
1711 new TitleValue( 0, 'SomeDbKey' ),
1712 new TitleValue( 1, 'AnotherDbKey' ),
1715 $user = $this->getMockNonAnonUserWithId( 1 );
1716 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1718 $mockDb = $this->getMockDb();
1720 $mockDb->expects( $this->once() )
1721 ->method( 'makeWhereFrom2d' )
1723 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1724 $this->isType( 'string' ),
1725 $this->isType( 'string' )
1727 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1728 $mockDb->expects( $this->once() )
1729 ->method( 'select' )
1732 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1734 'makeWhereFrom2d return value',
1737 $this->isType( 'string' )
1739 ->will( $this->returnValue( [
1741 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1745 $mockCache = $this->getMockCache();
1746 $mockCache->expects( $this->at( 1 ) )
1748 ->with( '0:SomeDbKey:1' )
1749 ->will( $this->returnValue( $cachedItem ) );
1750 $mockCache->expects( $this->at( 3 ) )
1752 ->with( '1:AnotherDbKey:1' )
1753 ->will( $this->returnValue( null ) );
1754 $mockCache->expects( $this->never() )->method( 'set' );
1755 $mockCache->expects( $this->never() )->method( 'delete' );
1757 $store = $this->newWatchedItemStore(
1758 $this->getMockLoadBalancer( $mockDb ),
1762 $this->assertEquals(
1764 0 => [ 'SomeDbKey' => '20151212010101', ],
1765 1 => [ 'AnotherDbKey' => null, ],
1767 $store->getNotificationTimestampsBatch( $user, $targets )
1771 public function testGetNotificationTimestampsBatch_allItemsCached() {
1773 new TitleValue( 0, 'SomeDbKey' ),
1774 new TitleValue( 1, 'AnotherDbKey' ),
1777 $user = $this->getMockNonAnonUserWithId( 1 );
1779 new WatchedItem( $user, $targets[0], '20151212010101' ),
1780 new WatchedItem( $user, $targets[1], null ),
1782 $mockDb = $this->getMockDb();
1783 $mockDb->expects( $this->never() )->method( $this->anything() );
1785 $mockCache = $this->getMockCache();
1786 $mockCache->expects( $this->at( 1 ) )
1788 ->with( '0:SomeDbKey:1' )
1789 ->will( $this->returnValue( $cachedItems[0] ) );
1790 $mockCache->expects( $this->at( 3 ) )
1792 ->with( '1:AnotherDbKey:1' )
1793 ->will( $this->returnValue( $cachedItems[1] ) );
1794 $mockCache->expects( $this->never() )->method( 'set' );
1795 $mockCache->expects( $this->never() )->method( 'delete' );
1797 $store = $this->newWatchedItemStore(
1798 $this->getMockLoadBalancer( $mockDb ),
1802 $this->assertEquals(
1804 0 => [ 'SomeDbKey' => '20151212010101', ],
1805 1 => [ 'AnotherDbKey' => null, ],
1807 $store->getNotificationTimestampsBatch( $user, $targets )
1811 public function testGetNotificationTimestampsBatch_anonymousUser() {
1813 new TitleValue( 0, 'SomeDbKey' ),
1814 new TitleValue( 1, 'AnotherDbKey' ),
1817 $mockDb = $this->getMockDb();
1818 $mockDb->expects( $this->never() )->method( $this->anything() );
1820 $mockCache = $this->getMockCache();
1821 $mockCache->expects( $this->never() )->method( $this->anything() );
1823 $store = $this->newWatchedItemStore(
1824 $this->getMockLoadBalancer( $mockDb ),
1828 $this->assertEquals(
1830 0 => [ 'SomeDbKey' => false, ],
1831 1 => [ 'AnotherDbKey' => false, ],
1833 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1837 public function testResetNotificationTimestamp_anonymousUser() {
1838 $mockDb = $this->getMockDb();
1839 $mockDb->expects( $this->never() )
1840 ->method( 'selectRow' );
1842 $mockCache = $this->getMockCache();
1843 $mockCache->expects( $this->never() )->method( 'get' );
1844 $mockCache->expects( $this->never() )->method( 'set' );
1845 $mockCache->expects( $this->never() )->method( 'delete' );
1847 $store = $this->newWatchedItemStore(
1848 $this->getMockLoadBalancer( $mockDb ),
1853 $store->resetNotificationTimestamp(
1854 $this->getAnonUser(),
1855 Title
::newFromText( 'SomeDbKey' )
1860 public function testResetNotificationTimestamp_noItem() {
1861 $mockDb = $this->getMockDb();
1862 $mockDb->expects( $this->once() )
1863 ->method( 'selectRow' )
1866 'wl_notificationtimestamp',
1869 'wl_namespace' => 0,
1870 'wl_title' => 'SomeDbKey',
1873 ->will( $this->returnValue( [] ) );
1875 $mockCache = $this->getMockCache();
1876 $mockCache->expects( $this->never() )->method( 'get' );
1877 $mockCache->expects( $this->never() )->method( 'set' );
1878 $mockCache->expects( $this->never() )->method( 'delete' );
1880 $store = $this->newWatchedItemStore(
1881 $this->getMockLoadBalancer( $mockDb ),
1886 $store->resetNotificationTimestamp(
1887 $this->getMockNonAnonUserWithId( 1 ),
1888 Title
::newFromText( 'SomeDbKey' )
1893 public function testResetNotificationTimestamp_item() {
1894 $user = $this->getMockNonAnonUserWithId( 1 );
1895 $title = Title
::newFromText( 'SomeDbKey' );
1897 $mockDb = $this->getMockDb();
1898 $mockDb->expects( $this->once() )
1899 ->method( 'selectRow' )
1902 'wl_notificationtimestamp',
1905 'wl_namespace' => 0,
1906 'wl_title' => 'SomeDbKey',
1909 ->will( $this->returnValue(
1910 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1913 $mockCache = $this->getMockCache();
1914 $mockCache->expects( $this->never() )->method( 'get' );
1915 $mockCache->expects( $this->once() )
1919 $this->isInstanceOf( WatchedItem
::class )
1921 $mockCache->expects( $this->once() )
1922 ->method( 'delete' )
1923 ->with( '0:SomeDbKey:1' );
1925 $store = $this->newWatchedItemStore(
1926 $this->getMockLoadBalancer( $mockDb ),
1930 // Note: This does not actually assert the job is correct
1931 $callableCallCounter = 0;
1932 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1933 $callableCallCounter++
;
1934 $this->assertInternalType( 'callable', $callable );
1936 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1939 $store->resetNotificationTimestamp(
1944 $this->assertEquals( 1, $callableCallCounter );
1946 ScopedCallback
::consume( $scopedOverride );
1949 public function testResetNotificationTimestamp_noItemForced() {
1950 $user = $this->getMockNonAnonUserWithId( 1 );
1951 $title = Title
::newFromText( 'SomeDbKey' );
1953 $mockDb = $this->getMockDb();
1954 $mockDb->expects( $this->never() )
1955 ->method( 'selectRow' );
1957 $mockCache = $this->getMockCache();
1958 $mockDb->expects( $this->never() )
1960 $mockDb->expects( $this->never() )
1962 $mockDb->expects( $this->never() )
1963 ->method( 'delete' );
1965 $store = $this->newWatchedItemStore(
1966 $this->getMockLoadBalancer( $mockDb ),
1970 // Note: This does not actually assert the job is correct
1971 $callableCallCounter = 0;
1972 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1973 $callableCallCounter++
;
1974 $this->assertInternalType( 'callable', $callable );
1976 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1979 $store->resetNotificationTimestamp(
1985 $this->assertEquals( 1, $callableCallCounter );
1987 ScopedCallback
::consume( $scopedOverride );
1994 * @return PHPUnit_Framework_MockObject_MockObject|Title
1996 private function getMockTitle( $text, $ns = 0 ) {
1997 $title = $this->getMock( Title
::class );
1998 $title->expects( $this->any() )
1999 ->method( 'getText' )
2000 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2001 $title->expects( $this->any() )
2002 ->method( 'getDbKey' )
2003 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2004 $title->expects( $this->any() )
2005 ->method( 'getNamespace' )
2006 ->will( $this->returnValue( $ns ) );
2010 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2011 $user = $this->getMockNonAnonUserWithId( 1 );
2013 $title = $this->getMockTitle( 'SomeTitle' );
2014 $title->expects( $this->once() )
2015 ->method( 'getNextRevisionID' )
2017 ->will( $this->returnValue( false ) );
2019 $mockDb = $this->getMockDb();
2020 $mockDb->expects( $this->never() )
2021 ->method( 'selectRow' );
2023 $mockCache = $this->getMockCache();
2024 $mockDb->expects( $this->never() )
2026 $mockDb->expects( $this->never() )
2028 $mockDb->expects( $this->never() )
2029 ->method( 'delete' );
2031 $store = $this->newWatchedItemStore(
2032 $this->getMockLoadBalancer( $mockDb ),
2036 // Note: This does not actually assert the job is correct
2037 $callableCallCounter = 0;
2038 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2039 function( $callable ) use ( &$callableCallCounter ) {
2040 $callableCallCounter++
;
2041 $this->assertInternalType( 'callable', $callable );
2046 $store->resetNotificationTimestamp(
2053 $this->assertEquals( 1, $callableCallCounter );
2055 ScopedCallback
::consume( $scopedOverride );
2058 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2059 $user = $this->getMockNonAnonUserWithId( 1 );
2061 $title = $this->getMockTitle( 'SomeDbKey' );
2062 $title->expects( $this->once() )
2063 ->method( 'getNextRevisionID' )
2065 ->will( $this->returnValue( 33 ) );
2067 $mockDb = $this->getMockDb();
2068 $mockDb->expects( $this->once() )
2069 ->method( 'selectRow' )
2072 'wl_notificationtimestamp',
2075 'wl_namespace' => 0,
2076 'wl_title' => 'SomeDbKey',
2079 ->will( $this->returnValue(
2080 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2083 $mockCache = $this->getMockCache();
2084 $mockDb->expects( $this->never() )
2086 $mockDb->expects( $this->never() )
2088 $mockDb->expects( $this->never() )
2089 ->method( 'delete' );
2091 $store = $this->newWatchedItemStore(
2092 $this->getMockLoadBalancer( $mockDb ),
2096 // Note: This does not actually assert the job is correct
2097 $addUpdateCallCounter = 0;
2098 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2099 function( $callable ) use ( &$addUpdateCallCounter ) {
2100 $addUpdateCallCounter++
;
2101 $this->assertInternalType( 'callable', $callable );
2105 $getTimestampCallCounter = 0;
2106 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2107 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2108 $getTimestampCallCounter++
;
2109 $this->assertEquals( $title, $titleParam );
2110 $this->assertEquals( $oldid, $oldidParam );
2115 $store->resetNotificationTimestamp(
2122 $this->assertEquals( 1, $addUpdateCallCounter );
2123 $this->assertEquals( 1, $getTimestampCallCounter );
2125 ScopedCallback
::consume( $scopedOverrideDeferred );
2126 ScopedCallback
::consume( $scopedOverrideRevision );
2129 public function testUpdateNotificationTimestamp_watchersExist() {
2130 $mockDb = $this->getMockDb();
2131 $mockDb->expects( $this->once() )
2132 ->method( 'select' )
2138 'wl_namespace' => 0,
2139 'wl_title' => 'SomeDbKey',
2140 'wl_notificationtimestamp IS NULL'
2144 $this->returnValue( [
2145 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2146 $this->getFakeRow( [ 'wl_user' => '3' ] )
2149 $mockDb->expects( $this->once() )
2150 ->method( 'onTransactionIdle' )
2151 ->with( $this->isType( 'callable' ) )
2152 ->will( $this->returnCallback( function( $callable ) {
2155 $mockDb->expects( $this->once() )
2156 ->method( 'update' )
2159 [ 'wl_notificationtimestamp' => null ],
2161 'wl_user' => [ 2, 3 ],
2162 'wl_namespace' => 0,
2163 'wl_title' => 'SomeDbKey',
2167 $mockCache = $this->getMockCache();
2168 $mockCache->expects( $this->never() )->method( 'set' );
2169 $mockCache->expects( $this->never() )->method( 'get' );
2170 $mockCache->expects( $this->never() )->method( 'delete' );
2172 $store = $this->newWatchedItemStore(
2173 $this->getMockLoadBalancer( $mockDb ),
2177 $this->assertEquals(
2179 $store->updateNotificationTimestamp(
2180 $this->getMockNonAnonUserWithId( 1 ),
2181 new TitleValue( 0, 'SomeDbKey' ),
2187 public function testUpdateNotificationTimestamp_noWatchers() {
2188 $mockDb = $this->getMockDb();
2189 $mockDb->expects( $this->once() )
2190 ->method( 'select' )
2196 'wl_namespace' => 0,
2197 'wl_title' => 'SomeDbKey',
2198 'wl_notificationtimestamp IS NULL'
2202 $this->returnValue( [] )
2204 $mockDb->expects( $this->never() )
2205 ->method( 'onTransactionIdle' );
2206 $mockDb->expects( $this->never() )
2207 ->method( 'update' );
2209 $mockCache = $this->getMockCache();
2210 $mockCache->expects( $this->never() )->method( 'set' );
2211 $mockCache->expects( $this->never() )->method( 'get' );
2212 $mockCache->expects( $this->never() )->method( 'delete' );
2214 $store = $this->newWatchedItemStore(
2215 $this->getMockLoadBalancer( $mockDb ),
2219 $watchers = $store->updateNotificationTimestamp(
2220 $this->getMockNonAnonUserWithId( 1 ),
2221 new TitleValue( 0, 'SomeDbKey' ),
2224 $this->assertInternalType( 'array', $watchers );
2225 $this->assertEmpty( $watchers );
2228 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2229 $user = $this->getMockNonAnonUserWithId( 1 );
2230 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2232 $mockDb = $this->getMockDb();
2233 $mockDb->expects( $this->once() )
2234 ->method( 'selectRow' )
2235 ->will( $this->returnValue(
2236 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2238 $mockDb->expects( $this->once() )
2239 ->method( 'select' )
2241 $this->returnValue( [
2242 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2243 $this->getFakeRow( [ 'wl_user' => '3' ] )
2246 $mockDb->expects( $this->once() )
2247 ->method( 'onTransactionIdle' )
2248 ->with( $this->isType( 'callable' ) )
2249 ->will( $this->returnCallback( function( $callable ) {
2252 $mockDb->expects( $this->once() )
2253 ->method( 'update' );
2255 $mockCache = $this->getMockCache();
2256 $mockCache->expects( $this->once() )
2258 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2259 $mockCache->expects( $this->once() )
2261 ->with( '0:SomeDbKey:1' );
2262 $mockCache->expects( $this->once() )
2263 ->method( 'delete' )
2264 ->with( '0:SomeDbKey:1' );
2266 $store = $this->newWatchedItemStore(
2267 $this->getMockLoadBalancer( $mockDb ),
2271 // This will add the item to the cache
2272 $store->getWatchedItem( $user, $titleValue );
2274 $store->updateNotificationTimestamp(
2275 $this->getMockNonAnonUserWithId( 1 ),