Make WatchedItemStoreUnitTest compatible with PHPUnit 6
[lhc/web/wiklou.git] / tests / phpunit / includes / watcheditem / WatchedItemStoreUnitTest.php
1 <?php
2 use MediaWiki\Linker\LinkTarget;
3 use Wikimedia\Rdbms\LoadBalancer;
4 use Wikimedia\ScopedCallback;
5 use Wikimedia\TestingAccessWrapper;
6
7 /**
8 * @author Addshore
9 *
10 * @covers WatchedItemStore
11 */
12 class WatchedItemStoreUnitTest extends MediaWikiTestCase {
13
14 /**
15 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
16 */
17 private function getMockDb() {
18 return $this->createMock( IDatabase::class );
19 }
20
21 /**
22 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
23 */
24 private function getMockLoadBalancer(
25 $mockDb,
26 $expectedConnectionType = null
27 ) {
28 $mock = $this->getMockBuilder( LoadBalancer::class )
29 ->disableOriginalConstructor()
30 ->getMock();
31 if ( $expectedConnectionType !== null ) {
32 $mock->expects( $this->any() )
33 ->method( 'getConnectionRef' )
34 ->with( $expectedConnectionType )
35 ->will( $this->returnValue( $mockDb ) );
36 } else {
37 $mock->expects( $this->any() )
38 ->method( 'getConnectionRef' )
39 ->will( $this->returnValue( $mockDb ) );
40 }
41 return $mock;
42 }
43
44 /**
45 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
46 */
47 private function getMockCache() {
48 $mock = $this->getMockBuilder( HashBagOStuff::class )
49 ->disableOriginalConstructor()
50 ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
51 ->getMock();
52 $mock->expects( $this->any() )
53 ->method( 'makeKey' )
54 ->will( $this->returnCallback( function () {
55 return implode( ':', func_get_args() );
56 } ) );
57 return $mock;
58 }
59
60 /**
61 * @return PHPUnit_Framework_MockObject_MockObject|ReadOnlyMode
62 */
63 private function getMockReadOnlyMode( $readOnly = false ) {
64 $mock = $this->getMockBuilder( ReadOnlyMode::class )
65 ->disableOriginalConstructor()
66 ->getMock();
67 $mock->expects( $this->any() )
68 ->method( 'isReadOnly' )
69 ->will( $this->returnValue( $readOnly ) );
70 return $mock;
71 }
72
73 /**
74 * @param int $id
75 * @return PHPUnit_Framework_MockObject_MockObject|User
76 */
77 private function getMockNonAnonUserWithId( $id ) {
78 $mock = $this->createMock( User::class );
79 $mock->expects( $this->any() )
80 ->method( 'isAnon' )
81 ->will( $this->returnValue( false ) );
82 $mock->expects( $this->any() )
83 ->method( 'getId' )
84 ->will( $this->returnValue( $id ) );
85 return $mock;
86 }
87
88 /**
89 * @return User
90 */
91 private function getAnonUser() {
92 return User::newFromName( 'Anon_User' );
93 }
94
95 private function getFakeRow( array $rowValues ) {
96 $fakeRow = new stdClass();
97 foreach ( $rowValues as $valueName => $value ) {
98 $fakeRow->$valueName = $value;
99 }
100 return $fakeRow;
101 }
102
103 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache,
104 ReadOnlyMode $readOnlyMode
105 ) {
106 return new WatchedItemStore(
107 $loadBalancer,
108 $cache,
109 $readOnlyMode,
110 1000
111 );
112 }
113
114 public function testClearWatchedItems() {
115 $user = $this->getMockNonAnonUserWithId( 7 );
116
117 $mockDb = $this->getMockDb();
118 $mockDb->expects( $this->once() )
119 ->method( 'selectField' )
120 ->with(
121 'watchlist',
122 'COUNT(*)',
123 [
124 'wl_user' => $user->getId(),
125 ],
126 $this->isType( 'string' )
127 )
128 ->will( $this->returnValue( 12 ) );
129 $mockDb->expects( $this->once() )
130 ->method( 'delete' )
131 ->with(
132 'watchlist',
133 [ 'wl_user' => 7 ],
134 $this->isType( 'string' )
135 );
136
137 $mockCache = $this->getMockCache();
138 $mockCache->expects( $this->never() )->method( 'get' );
139 $mockCache->expects( $this->never() )->method( 'set' );
140 $mockCache->expects( $this->once() )
141 ->method( 'delete' )
142 ->with( 'RM-KEY' );
143
144 $store = $this->newWatchedItemStore(
145 $this->getMockLoadBalancer( $mockDb ),
146 $mockCache,
147 $this->getMockReadOnlyMode()
148 );
149 TestingAccessWrapper::newFromObject( $store )
150 ->cacheIndex = [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
151
152 $this->assertTrue( $store->clearUserWatchedItems( $user ) );
153 }
154
155 public function testClearWatchedItems_tooManyItemsWatched() {
156 $user = $this->getMockNonAnonUserWithId( 7 );
157
158 $mockDb = $this->getMockDb();
159 $mockDb->expects( $this->once() )
160 ->method( 'selectField' )
161 ->with(
162 'watchlist',
163 'COUNT(*)',
164 [
165 'wl_user' => $user->getId(),
166 ],
167 $this->isType( 'string' )
168 )
169 ->will( $this->returnValue( 99999 ) );
170
171 $mockCache = $this->getMockCache();
172 $mockCache->expects( $this->never() )->method( 'get' );
173 $mockCache->expects( $this->never() )->method( 'set' );
174 $mockCache->expects( $this->never() )->method( 'delete' );
175
176 $store = $this->newWatchedItemStore(
177 $this->getMockLoadBalancer( $mockDb ),
178 $mockCache,
179 $this->getMockReadOnlyMode()
180 );
181
182 $this->assertFalse( $store->clearUserWatchedItems( $user ) );
183 }
184
185 public function testCountWatchedItems() {
186 $user = $this->getMockNonAnonUserWithId( 1 );
187
188 $mockDb = $this->getMockDb();
189 $mockDb->expects( $this->exactly( 1 ) )
190 ->method( 'selectField' )
191 ->with(
192 'watchlist',
193 'COUNT(*)',
194 [
195 'wl_user' => $user->getId(),
196 ],
197 $this->isType( 'string' )
198 )
199 ->will( $this->returnValue( '12' ) );
200
201 $mockCache = $this->getMockCache();
202 $mockCache->expects( $this->never() )->method( 'get' );
203 $mockCache->expects( $this->never() )->method( 'set' );
204 $mockCache->expects( $this->never() )->method( 'delete' );
205
206 $store = $this->newWatchedItemStore(
207 $this->getMockLoadBalancer( $mockDb ),
208 $mockCache,
209 $this->getMockReadOnlyMode()
210 );
211
212 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
213 }
214
215 public function testCountWatchers() {
216 $titleValue = new TitleValue( 0, 'SomeDbKey' );
217
218 $mockDb = $this->getMockDb();
219 $mockDb->expects( $this->exactly( 1 ) )
220 ->method( 'selectField' )
221 ->with(
222 'watchlist',
223 'COUNT(*)',
224 [
225 'wl_namespace' => $titleValue->getNamespace(),
226 'wl_title' => $titleValue->getDBkey(),
227 ],
228 $this->isType( 'string' )
229 )
230 ->will( $this->returnValue( '7' ) );
231
232 $mockCache = $this->getMockCache();
233 $mockCache->expects( $this->never() )->method( 'get' );
234 $mockCache->expects( $this->never() )->method( 'set' );
235 $mockCache->expects( $this->never() )->method( 'delete' );
236
237 $store = $this->newWatchedItemStore(
238 $this->getMockLoadBalancer( $mockDb ),
239 $mockCache,
240 $this->getMockReadOnlyMode()
241 );
242
243 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
244 }
245
246 public function testCountWatchersMultiple() {
247 $titleValues = [
248 new TitleValue( 0, 'SomeDbKey' ),
249 new TitleValue( 0, 'OtherDbKey' ),
250 new TitleValue( 1, 'AnotherDbKey' ),
251 ];
252
253 $mockDb = $this->getMockDb();
254
255 $dbResult = [
256 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
257 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
258 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
259 ),
260 ];
261 $mockDb->expects( $this->once() )
262 ->method( 'makeWhereFrom2d' )
263 ->with(
264 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
265 $this->isType( 'string' ),
266 $this->isType( 'string' )
267 )
268 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
269 $mockDb->expects( $this->once() )
270 ->method( 'select' )
271 ->with(
272 'watchlist',
273 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
274 [ 'makeWhereFrom2d return value' ],
275 $this->isType( 'string' ),
276 [
277 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
278 ]
279 )
280 ->will(
281 $this->returnValue( $dbResult )
282 );
283
284 $mockCache = $this->getMockCache();
285 $mockCache->expects( $this->never() )->method( 'get' );
286 $mockCache->expects( $this->never() )->method( 'set' );
287 $mockCache->expects( $this->never() )->method( 'delete' );
288
289 $store = $this->newWatchedItemStore(
290 $this->getMockLoadBalancer( $mockDb ),
291 $mockCache,
292 $this->getMockReadOnlyMode()
293 );
294
295 $expected = [
296 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
297 1 => [ 'AnotherDbKey' => 500 ],
298 ];
299 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
300 }
301
302 public function provideIntWithDbUnsafeVersion() {
303 return [
304 [ 50 ],
305 [ "50; DROP TABLE watchlist;\n--" ],
306 ];
307 }
308
309 /**
310 * @dataProvider provideIntWithDbUnsafeVersion
311 */
312 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
313 $titleValues = [
314 new TitleValue( 0, 'SomeDbKey' ),
315 new TitleValue( 0, 'OtherDbKey' ),
316 new TitleValue( 1, 'AnotherDbKey' ),
317 ];
318
319 $mockDb = $this->getMockDb();
320
321 $dbResult = [
322 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
323 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
324 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
325 ),
326 ];
327 $mockDb->expects( $this->once() )
328 ->method( 'makeWhereFrom2d' )
329 ->with(
330 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
331 $this->isType( 'string' ),
332 $this->isType( 'string' )
333 )
334 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
335 $mockDb->expects( $this->once() )
336 ->method( 'select' )
337 ->with(
338 'watchlist',
339 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
340 [ 'makeWhereFrom2d return value' ],
341 $this->isType( 'string' ),
342 [
343 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
344 'HAVING' => 'COUNT(*) >= 50',
345 ]
346 )
347 ->will(
348 $this->returnValue( $dbResult )
349 );
350
351 $mockCache = $this->getMockCache();
352 $mockCache->expects( $this->never() )->method( 'get' );
353 $mockCache->expects( $this->never() )->method( 'set' );
354 $mockCache->expects( $this->never() )->method( 'delete' );
355
356 $store = $this->newWatchedItemStore(
357 $this->getMockLoadBalancer( $mockDb ),
358 $mockCache,
359 $this->getMockReadOnlyMode()
360 );
361
362 $expected = [
363 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
364 1 => [ 'AnotherDbKey' => 500 ],
365 ];
366 $this->assertEquals(
367 $expected,
368 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
369 );
370 }
371
372 public function testCountVisitingWatchers() {
373 $titleValue = new TitleValue( 0, 'SomeDbKey' );
374
375 $mockDb = $this->getMockDb();
376 $mockDb->expects( $this->exactly( 1 ) )
377 ->method( 'selectField' )
378 ->with(
379 'watchlist',
380 'COUNT(*)',
381 [
382 'wl_namespace' => $titleValue->getNamespace(),
383 'wl_title' => $titleValue->getDBkey(),
384 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
385 ],
386 $this->isType( 'string' )
387 )
388 ->will( $this->returnValue( '7' ) );
389 $mockDb->expects( $this->exactly( 1 ) )
390 ->method( 'addQuotes' )
391 ->will( $this->returnCallback( function ( $value ) {
392 return "'$value'";
393 } ) );
394 $mockDb->expects( $this->exactly( 1 ) )
395 ->method( 'timestamp' )
396 ->will( $this->returnCallback( function ( $value ) {
397 return 'TS' . $value . 'TS';
398 } ) );
399
400 $mockCache = $this->getMockCache();
401 $mockCache->expects( $this->never() )->method( 'set' );
402 $mockCache->expects( $this->never() )->method( 'get' );
403 $mockCache->expects( $this->never() )->method( 'delete' );
404
405 $store = $this->newWatchedItemStore(
406 $this->getMockLoadBalancer( $mockDb ),
407 $mockCache,
408 $this->getMockReadOnlyMode()
409 );
410
411 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
412 }
413
414 public function testCountVisitingWatchersMultiple() {
415 $titleValuesWithThresholds = [
416 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
417 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
418 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
419 ];
420
421 $dbResult = [
422 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
423 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
424 $this->getFakeRow(
425 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
426 ),
427 ];
428 $mockDb = $this->getMockDb();
429 $mockDb->expects( $this->exactly( 2 * 3 ) )
430 ->method( 'addQuotes' )
431 ->will( $this->returnCallback( function ( $value ) {
432 return "'$value'";
433 } ) );
434 $mockDb->expects( $this->exactly( 3 ) )
435 ->method( 'timestamp' )
436 ->will( $this->returnCallback( function ( $value ) {
437 return 'TS' . $value . 'TS';
438 } ) );
439 $mockDb->expects( $this->any() )
440 ->method( 'makeList' )
441 ->with(
442 $this->isType( 'array' ),
443 $this->isType( 'int' )
444 )
445 ->will( $this->returnCallback( function ( $a, $conj ) {
446 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
447 return implode( $sqlConj, array_map( function ( $s ) {
448 return '(' . $s . ')';
449 }, $a
450 ) );
451 } ) );
452 $mockDb->expects( $this->never() )
453 ->method( 'makeWhereFrom2d' );
454
455 $expectedCond =
456 '((wl_namespace = 0) AND (' .
457 "(((wl_title = 'SomeDbKey') AND (" .
458 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
459 ')) OR (' .
460 "(wl_title = 'OtherDbKey') AND (" .
461 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
462 '))))' .
463 ') OR ((wl_namespace = 1) AND (' .
464 "(((wl_title = 'AnotherDbKey') AND (".
465 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
466 ')))))';
467 $mockDb->expects( $this->once() )
468 ->method( 'select' )
469 ->with(
470 'watchlist',
471 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
472 $expectedCond,
473 $this->isType( 'string' ),
474 [
475 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
476 ]
477 )
478 ->will(
479 $this->returnValue( $dbResult )
480 );
481
482 $mockCache = $this->getMockCache();
483 $mockCache->expects( $this->never() )->method( 'get' );
484 $mockCache->expects( $this->never() )->method( 'set' );
485 $mockCache->expects( $this->never() )->method( 'delete' );
486
487 $store = $this->newWatchedItemStore(
488 $this->getMockLoadBalancer( $mockDb ),
489 $mockCache,
490 $this->getMockReadOnlyMode()
491 );
492
493 $expected = [
494 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
495 1 => [ 'AnotherDbKey' => 500 ],
496 ];
497 $this->assertEquals(
498 $expected,
499 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
500 );
501 }
502
503 public function testCountVisitingWatchersMultiple_withMissingTargets() {
504 $titleValuesWithThresholds = [
505 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
506 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
507 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
508 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
509 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
510 ];
511
512 $dbResult = [
513 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
514 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
515 $this->getFakeRow(
516 [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
517 ),
518 $this->getFakeRow(
519 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '100' ]
520 ),
521 $this->getFakeRow(
522 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => '0', 'watchers' => '200' ]
523 ),
524 ];
525 $mockDb = $this->getMockDb();
526 $mockDb->expects( $this->exactly( 2 * 3 ) )
527 ->method( 'addQuotes' )
528 ->will( $this->returnCallback( function ( $value ) {
529 return "'$value'";
530 } ) );
531 $mockDb->expects( $this->exactly( 3 ) )
532 ->method( 'timestamp' )
533 ->will( $this->returnCallback( function ( $value ) {
534 return 'TS' . $value . 'TS';
535 } ) );
536 $mockDb->expects( $this->any() )
537 ->method( 'makeList' )
538 ->with(
539 $this->isType( 'array' ),
540 $this->isType( 'int' )
541 )
542 ->will( $this->returnCallback( function ( $a, $conj ) {
543 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
544 return implode( $sqlConj, array_map( function ( $s ) {
545 return '(' . $s . ')';
546 }, $a
547 ) );
548 } ) );
549 $mockDb->expects( $this->once() )
550 ->method( 'makeWhereFrom2d' )
551 ->with(
552 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
553 $this->isType( 'string' ),
554 $this->isType( 'string' )
555 )
556 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
557
558 $expectedCond =
559 '((wl_namespace = 0) AND (' .
560 "(((wl_title = 'SomeDbKey') AND (" .
561 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
562 ')) OR (' .
563 "(wl_title = 'OtherDbKey') AND (" .
564 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
565 '))))' .
566 ') OR ((wl_namespace = 1) AND (' .
567 "(((wl_title = 'AnotherDbKey') AND (".
568 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
569 '))))' .
570 ') OR ' .
571 '(makeWhereFrom2d return value)';
572 $mockDb->expects( $this->once() )
573 ->method( 'select' )
574 ->with(
575 'watchlist',
576 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
577 $expectedCond,
578 $this->isType( 'string' ),
579 [
580 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
581 ]
582 )
583 ->will(
584 $this->returnValue( $dbResult )
585 );
586
587 $mockCache = $this->getMockCache();
588 $mockCache->expects( $this->never() )->method( 'get' );
589 $mockCache->expects( $this->never() )->method( 'set' );
590 $mockCache->expects( $this->never() )->method( 'delete' );
591
592 $store = $this->newWatchedItemStore(
593 $this->getMockLoadBalancer( $mockDb ),
594 $mockCache,
595 $this->getMockReadOnlyMode()
596 );
597
598 $expected = [
599 0 => [
600 'SomeDbKey' => 100, 'OtherDbKey' => 300,
601 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
602 ],
603 1 => [ 'AnotherDbKey' => 500 ],
604 ];
605 $this->assertEquals(
606 $expected,
607 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
608 );
609 }
610
611 /**
612 * @dataProvider provideIntWithDbUnsafeVersion
613 */
614 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
615 $titleValuesWithThresholds = [
616 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
617 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
618 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
619 ];
620
621 $mockDb = $this->getMockDb();
622 $mockDb->expects( $this->any() )
623 ->method( 'makeList' )
624 ->will( $this->returnValue( 'makeList return value' ) );
625 $mockDb->expects( $this->once() )
626 ->method( 'select' )
627 ->with(
628 'watchlist',
629 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
630 'makeList return value',
631 $this->isType( 'string' ),
632 [
633 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
634 'HAVING' => 'COUNT(*) >= 50',
635 ]
636 )
637 ->will(
638 $this->returnValue( [] )
639 );
640
641 $mockCache = $this->getMockCache();
642 $mockCache->expects( $this->never() )->method( 'get' );
643 $mockCache->expects( $this->never() )->method( 'set' );
644 $mockCache->expects( $this->never() )->method( 'delete' );
645
646 $store = $this->newWatchedItemStore(
647 $this->getMockLoadBalancer( $mockDb ),
648 $mockCache,
649 $this->getMockReadOnlyMode()
650 );
651
652 $expected = [
653 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
654 1 => [ 'AnotherDbKey' => 0 ],
655 ];
656 $this->assertEquals(
657 $expected,
658 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
659 );
660 }
661
662 public function testCountUnreadNotifications() {
663 $user = $this->getMockNonAnonUserWithId( 1 );
664
665 $mockDb = $this->getMockDb();
666 $mockDb->expects( $this->exactly( 1 ) )
667 ->method( 'selectRowCount' )
668 ->with(
669 'watchlist',
670 '1',
671 [
672 "wl_notificationtimestamp IS NOT NULL",
673 'wl_user' => 1,
674 ],
675 $this->isType( 'string' )
676 )
677 ->will( $this->returnValue( '9' ) );
678
679 $mockCache = $this->getMockCache();
680 $mockCache->expects( $this->never() )->method( 'set' );
681 $mockCache->expects( $this->never() )->method( 'get' );
682 $mockCache->expects( $this->never() )->method( 'delete' );
683
684 $store = $this->newWatchedItemStore(
685 $this->getMockLoadBalancer( $mockDb ),
686 $mockCache,
687 $this->getMockReadOnlyMode()
688 );
689
690 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
691 }
692
693 /**
694 * @dataProvider provideIntWithDbUnsafeVersion
695 */
696 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
697 $user = $this->getMockNonAnonUserWithId( 1 );
698
699 $mockDb = $this->getMockDb();
700 $mockDb->expects( $this->exactly( 1 ) )
701 ->method( 'selectRowCount' )
702 ->with(
703 'watchlist',
704 '1',
705 [
706 "wl_notificationtimestamp IS NOT NULL",
707 'wl_user' => 1,
708 ],
709 $this->isType( 'string' ),
710 [ 'LIMIT' => 50 ]
711 )
712 ->will( $this->returnValue( '50' ) );
713
714 $mockCache = $this->getMockCache();
715 $mockCache->expects( $this->never() )->method( 'set' );
716 $mockCache->expects( $this->never() )->method( 'get' );
717 $mockCache->expects( $this->never() )->method( 'delete' );
718
719 $store = $this->newWatchedItemStore(
720 $this->getMockLoadBalancer( $mockDb ),
721 $mockCache,
722 $this->getMockReadOnlyMode()
723 );
724
725 $this->assertSame(
726 true,
727 $store->countUnreadNotifications( $user, $limit )
728 );
729 }
730
731 /**
732 * @dataProvider provideIntWithDbUnsafeVersion
733 */
734 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
735 $user = $this->getMockNonAnonUserWithId( 1 );
736
737 $mockDb = $this->getMockDb();
738 $mockDb->expects( $this->exactly( 1 ) )
739 ->method( 'selectRowCount' )
740 ->with(
741 'watchlist',
742 '1',
743 [
744 "wl_notificationtimestamp IS NOT NULL",
745 'wl_user' => 1,
746 ],
747 $this->isType( 'string' ),
748 [ 'LIMIT' => 50 ]
749 )
750 ->will( $this->returnValue( '9' ) );
751
752 $mockCache = $this->getMockCache();
753 $mockCache->expects( $this->never() )->method( 'set' );
754 $mockCache->expects( $this->never() )->method( 'get' );
755 $mockCache->expects( $this->never() )->method( 'delete' );
756
757 $store = $this->newWatchedItemStore(
758 $this->getMockLoadBalancer( $mockDb ),
759 $mockCache,
760 $this->getMockReadOnlyMode()
761 );
762
763 $this->assertEquals(
764 9,
765 $store->countUnreadNotifications( $user, $limit )
766 );
767 }
768
769 public function testDuplicateEntry_nothingToDuplicate() {
770 $mockDb = $this->getMockDb();
771 $mockDb->expects( $this->once() )
772 ->method( 'select' )
773 ->with(
774 'watchlist',
775 [
776 'wl_user',
777 'wl_notificationtimestamp',
778 ],
779 [
780 'wl_namespace' => 0,
781 'wl_title' => 'Old_Title',
782 ],
783 'WatchedItemStore::duplicateEntry',
784 [ 'FOR UPDATE' ]
785 )
786 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
787
788 $store = $this->newWatchedItemStore(
789 $this->getMockLoadBalancer( $mockDb ),
790 $this->getMockCache(),
791 $this->getMockReadOnlyMode()
792 );
793
794 $store->duplicateEntry(
795 Title::newFromText( 'Old_Title' ),
796 Title::newFromText( 'New_Title' )
797 );
798 }
799
800 public function testDuplicateEntry_somethingToDuplicate() {
801 $fakeRows = [
802 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
803 $this->getFakeRow( [ 'wl_user' => '2', 'wl_notificationtimestamp' => null ] ),
804 ];
805
806 $mockDb = $this->getMockDb();
807 $mockDb->expects( $this->at( 0 ) )
808 ->method( 'select' )
809 ->with(
810 'watchlist',
811 [
812 'wl_user',
813 'wl_notificationtimestamp',
814 ],
815 [
816 'wl_namespace' => 0,
817 'wl_title' => 'Old_Title',
818 ]
819 )
820 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
821 $mockDb->expects( $this->at( 1 ) )
822 ->method( 'replace' )
823 ->with(
824 'watchlist',
825 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
826 [
827 [
828 'wl_user' => 1,
829 'wl_namespace' => 0,
830 'wl_title' => 'New_Title',
831 'wl_notificationtimestamp' => '20151212010101',
832 ],
833 [
834 'wl_user' => 2,
835 'wl_namespace' => 0,
836 'wl_title' => 'New_Title',
837 'wl_notificationtimestamp' => null,
838 ],
839 ],
840 $this->isType( 'string' )
841 );
842
843 $mockCache = $this->getMockCache();
844 $mockCache->expects( $this->never() )->method( 'get' );
845 $mockCache->expects( $this->never() )->method( 'delete' );
846
847 $store = $this->newWatchedItemStore(
848 $this->getMockLoadBalancer( $mockDb ),
849 $mockCache,
850 $this->getMockReadOnlyMode()
851 );
852
853 $store->duplicateEntry(
854 Title::newFromText( 'Old_Title' ),
855 Title::newFromText( 'New_Title' )
856 );
857 }
858
859 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
860 $mockDb = $this->getMockDb();
861 $mockDb->expects( $this->at( 0 ) )
862 ->method( 'select' )
863 ->with(
864 'watchlist',
865 [
866 'wl_user',
867 'wl_notificationtimestamp',
868 ],
869 [
870 'wl_namespace' => 0,
871 'wl_title' => 'Old_Title',
872 ]
873 )
874 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
875 $mockDb->expects( $this->at( 1 ) )
876 ->method( 'select' )
877 ->with(
878 'watchlist',
879 [
880 'wl_user',
881 'wl_notificationtimestamp',
882 ],
883 [
884 'wl_namespace' => 1,
885 'wl_title' => 'Old_Title',
886 ]
887 )
888 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
889
890 $mockCache = $this->getMockCache();
891 $mockCache->expects( $this->never() )->method( 'get' );
892 $mockCache->expects( $this->never() )->method( 'delete' );
893
894 $store = $this->newWatchedItemStore(
895 $this->getMockLoadBalancer( $mockDb ),
896 $mockCache,
897 $this->getMockReadOnlyMode()
898 );
899
900 $store->duplicateAllAssociatedEntries(
901 Title::newFromText( 'Old_Title' ),
902 Title::newFromText( 'New_Title' )
903 );
904 }
905
906 public function provideLinkTargetPairs() {
907 return [
908 [ Title::newFromText( 'Old_Title' ), Title::newFromText( 'New_Title' ) ],
909 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
910 ];
911 }
912
913 /**
914 * @dataProvider provideLinkTargetPairs
915 */
916 public function testDuplicateAllAssociatedEntries_somethingToDuplicate(
917 LinkTarget $oldTarget,
918 LinkTarget $newTarget
919 ) {
920 $fakeRows = [
921 $this->getFakeRow( [ 'wl_user' => '1', 'wl_notificationtimestamp' => '20151212010101' ] ),
922 ];
923
924 $mockDb = $this->getMockDb();
925 $mockDb->expects( $this->at( 0 ) )
926 ->method( 'select' )
927 ->with(
928 'watchlist',
929 [
930 'wl_user',
931 'wl_notificationtimestamp',
932 ],
933 [
934 'wl_namespace' => $oldTarget->getNamespace(),
935 'wl_title' => $oldTarget->getDBkey(),
936 ]
937 )
938 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
939 $mockDb->expects( $this->at( 1 ) )
940 ->method( 'replace' )
941 ->with(
942 'watchlist',
943 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
944 [
945 [
946 'wl_user' => 1,
947 'wl_namespace' => $newTarget->getNamespace(),
948 'wl_title' => $newTarget->getDBkey(),
949 'wl_notificationtimestamp' => '20151212010101',
950 ],
951 ],
952 $this->isType( 'string' )
953 );
954 $mockDb->expects( $this->at( 2 ) )
955 ->method( 'select' )
956 ->with(
957 'watchlist',
958 [
959 'wl_user',
960 'wl_notificationtimestamp',
961 ],
962 [
963 'wl_namespace' => $oldTarget->getNamespace() + 1,
964 'wl_title' => $oldTarget->getDBkey(),
965 ]
966 )
967 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
968 $mockDb->expects( $this->at( 3 ) )
969 ->method( 'replace' )
970 ->with(
971 'watchlist',
972 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
973 [
974 [
975 'wl_user' => 1,
976 'wl_namespace' => $newTarget->getNamespace() + 1,
977 'wl_title' => $newTarget->getDBkey(),
978 'wl_notificationtimestamp' => '20151212010101',
979 ],
980 ],
981 $this->isType( 'string' )
982 );
983
984 $mockCache = $this->getMockCache();
985 $mockCache->expects( $this->never() )->method( 'get' );
986 $mockCache->expects( $this->never() )->method( 'delete' );
987
988 $store = $this->newWatchedItemStore(
989 $this->getMockLoadBalancer( $mockDb ),
990 $mockCache,
991 $this->getMockReadOnlyMode()
992 );
993
994 $store->duplicateAllAssociatedEntries(
995 $oldTarget,
996 $newTarget
997 );
998 }
999
1000 public function testAddWatch_nonAnonymousUser() {
1001 $mockDb = $this->getMockDb();
1002 $mockDb->expects( $this->once() )
1003 ->method( 'insert' )
1004 ->with(
1005 'watchlist',
1006 [
1007 [
1008 'wl_user' => 1,
1009 'wl_namespace' => 0,
1010 'wl_title' => 'Some_Page',
1011 'wl_notificationtimestamp' => null,
1012 ]
1013 ]
1014 );
1015
1016 $mockCache = $this->getMockCache();
1017 $mockCache->expects( $this->once() )
1018 ->method( 'delete' )
1019 ->with( '0:Some_Page:1' );
1020
1021 $store = $this->newWatchedItemStore(
1022 $this->getMockLoadBalancer( $mockDb ),
1023 $mockCache,
1024 $this->getMockReadOnlyMode()
1025 );
1026
1027 $store->addWatch(
1028 $this->getMockNonAnonUserWithId( 1 ),
1029 Title::newFromText( 'Some_Page' )
1030 );
1031 }
1032
1033 public function testAddWatch_anonymousUser() {
1034 $mockDb = $this->getMockDb();
1035 $mockDb->expects( $this->never() )
1036 ->method( 'insert' );
1037
1038 $mockCache = $this->getMockCache();
1039 $mockCache->expects( $this->never() )
1040 ->method( 'delete' );
1041
1042 $store = $this->newWatchedItemStore(
1043 $this->getMockLoadBalancer( $mockDb ),
1044 $mockCache,
1045 $this->getMockReadOnlyMode()
1046 );
1047
1048 $store->addWatch(
1049 $this->getAnonUser(),
1050 Title::newFromText( 'Some_Page' )
1051 );
1052 }
1053
1054 public function testAddWatchBatchForUser_readOnlyDBReturnsFalse() {
1055 $store = $this->newWatchedItemStore(
1056 $this->getMockLoadBalancer( $this->getMockDb() ),
1057 $this->getMockCache(),
1058 $this->getMockReadOnlyMode( true )
1059 );
1060
1061 $this->assertFalse(
1062 $store->addWatchBatchForUser(
1063 $this->getMockNonAnonUserWithId( 1 ),
1064 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1065 )
1066 );
1067 }
1068
1069 public function testAddWatchBatchForUser_nonAnonymousUser() {
1070 $mockDb = $this->getMockDb();
1071 $mockDb->expects( $this->once() )
1072 ->method( 'insert' )
1073 ->with(
1074 'watchlist',
1075 [
1076 [
1077 'wl_user' => 1,
1078 'wl_namespace' => 0,
1079 'wl_title' => 'Some_Page',
1080 'wl_notificationtimestamp' => null,
1081 ],
1082 [
1083 'wl_user' => 1,
1084 'wl_namespace' => 1,
1085 'wl_title' => 'Some_Page',
1086 'wl_notificationtimestamp' => null,
1087 ]
1088 ]
1089 );
1090
1091 $mockCache = $this->getMockCache();
1092 $mockCache->expects( $this->exactly( 2 ) )
1093 ->method( 'delete' );
1094 $mockCache->expects( $this->at( 1 ) )
1095 ->method( 'delete' )
1096 ->with( '0:Some_Page:1' );
1097 $mockCache->expects( $this->at( 3 ) )
1098 ->method( 'delete' )
1099 ->with( '1:Some_Page:1' );
1100
1101 $store = $this->newWatchedItemStore(
1102 $this->getMockLoadBalancer( $mockDb ),
1103 $mockCache,
1104 $this->getMockReadOnlyMode()
1105 );
1106
1107 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1108
1109 $this->assertTrue(
1110 $store->addWatchBatchForUser(
1111 $mockUser,
1112 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1113 )
1114 );
1115 }
1116
1117 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1118 $mockDb = $this->getMockDb();
1119 $mockDb->expects( $this->never() )
1120 ->method( 'insert' );
1121
1122 $mockCache = $this->getMockCache();
1123 $mockCache->expects( $this->never() )
1124 ->method( 'delete' );
1125
1126 $store = $this->newWatchedItemStore(
1127 $this->getMockLoadBalancer( $mockDb ),
1128 $mockCache,
1129 $this->getMockReadOnlyMode()
1130 );
1131
1132 $this->assertFalse(
1133 $store->addWatchBatchForUser(
1134 $this->getAnonUser(),
1135 [ new TitleValue( 0, 'Other_Page' ) ]
1136 )
1137 );
1138 }
1139
1140 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1141 $user = $this->getMockNonAnonUserWithId( 1 );
1142 $mockDb = $this->getMockDb();
1143 $mockDb->expects( $this->never() )
1144 ->method( 'insert' );
1145
1146 $mockCache = $this->getMockCache();
1147 $mockCache->expects( $this->never() )
1148 ->method( 'delete' );
1149
1150 $store = $this->newWatchedItemStore(
1151 $this->getMockLoadBalancer( $mockDb ),
1152 $mockCache,
1153 $this->getMockReadOnlyMode()
1154 );
1155
1156 $this->assertTrue(
1157 $store->addWatchBatchForUser( $user, [] )
1158 );
1159 }
1160
1161 public function testLoadWatchedItem_existingItem() {
1162 $mockDb = $this->getMockDb();
1163 $mockDb->expects( $this->once() )
1164 ->method( 'selectRow' )
1165 ->with(
1166 'watchlist',
1167 'wl_notificationtimestamp',
1168 [
1169 'wl_user' => 1,
1170 'wl_namespace' => 0,
1171 'wl_title' => 'SomeDbKey',
1172 ]
1173 )
1174 ->will( $this->returnValue(
1175 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1176 ) );
1177
1178 $mockCache = $this->getMockCache();
1179 $mockCache->expects( $this->once() )
1180 ->method( 'set' )
1181 ->with(
1182 '0:SomeDbKey:1'
1183 );
1184
1185 $store = $this->newWatchedItemStore(
1186 $this->getMockLoadBalancer( $mockDb ),
1187 $mockCache,
1188 $this->getMockReadOnlyMode()
1189 );
1190
1191 $watchedItem = $store->loadWatchedItem(
1192 $this->getMockNonAnonUserWithId( 1 ),
1193 new TitleValue( 0, 'SomeDbKey' )
1194 );
1195 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1196 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1197 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1198 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1199 }
1200
1201 public function testLoadWatchedItem_noItem() {
1202 $mockDb = $this->getMockDb();
1203 $mockDb->expects( $this->once() )
1204 ->method( 'selectRow' )
1205 ->with(
1206 'watchlist',
1207 'wl_notificationtimestamp',
1208 [
1209 'wl_user' => 1,
1210 'wl_namespace' => 0,
1211 'wl_title' => 'SomeDbKey',
1212 ]
1213 )
1214 ->will( $this->returnValue( [] ) );
1215
1216 $mockCache = $this->getMockCache();
1217 $mockCache->expects( $this->never() )->method( 'get' );
1218 $mockCache->expects( $this->never() )->method( 'delete' );
1219
1220 $store = $this->newWatchedItemStore(
1221 $this->getMockLoadBalancer( $mockDb ),
1222 $mockCache,
1223 $this->getMockReadOnlyMode()
1224 );
1225
1226 $this->assertFalse(
1227 $store->loadWatchedItem(
1228 $this->getMockNonAnonUserWithId( 1 ),
1229 new TitleValue( 0, 'SomeDbKey' )
1230 )
1231 );
1232 }
1233
1234 public function testLoadWatchedItem_anonymousUser() {
1235 $mockDb = $this->getMockDb();
1236 $mockDb->expects( $this->never() )
1237 ->method( 'selectRow' );
1238
1239 $mockCache = $this->getMockCache();
1240 $mockCache->expects( $this->never() )->method( 'get' );
1241 $mockCache->expects( $this->never() )->method( 'delete' );
1242
1243 $store = $this->newWatchedItemStore(
1244 $this->getMockLoadBalancer( $mockDb ),
1245 $mockCache,
1246 $this->getMockReadOnlyMode()
1247 );
1248
1249 $this->assertFalse(
1250 $store->loadWatchedItem(
1251 $this->getAnonUser(),
1252 new TitleValue( 0, 'SomeDbKey' )
1253 )
1254 );
1255 }
1256
1257 public function testRemoveWatch_existingItem() {
1258 $mockDb = $this->getMockDb();
1259 $mockDb->expects( $this->once() )
1260 ->method( 'delete' )
1261 ->with(
1262 'watchlist',
1263 [
1264 'wl_user' => 1,
1265 'wl_namespace' => 0,
1266 'wl_title' => 'SomeDbKey',
1267 ]
1268 );
1269 $mockDb->expects( $this->once() )
1270 ->method( 'affectedRows' )
1271 ->will( $this->returnValue( 1 ) );
1272
1273 $mockCache = $this->getMockCache();
1274 $mockCache->expects( $this->never() )->method( 'get' );
1275 $mockCache->expects( $this->once() )
1276 ->method( 'delete' )
1277 ->with( '0:SomeDbKey:1' );
1278
1279 $store = $this->newWatchedItemStore(
1280 $this->getMockLoadBalancer( $mockDb ),
1281 $mockCache,
1282 $this->getMockReadOnlyMode()
1283 );
1284
1285 $this->assertTrue(
1286 $store->removeWatch(
1287 $this->getMockNonAnonUserWithId( 1 ),
1288 new TitleValue( 0, 'SomeDbKey' )
1289 )
1290 );
1291 }
1292
1293 public function testRemoveWatch_noItem() {
1294 $mockDb = $this->getMockDb();
1295 $mockDb->expects( $this->once() )
1296 ->method( 'delete' )
1297 ->with(
1298 'watchlist',
1299 [
1300 'wl_user' => 1,
1301 'wl_namespace' => 0,
1302 'wl_title' => 'SomeDbKey',
1303 ]
1304 );
1305 $mockDb->expects( $this->once() )
1306 ->method( 'affectedRows' )
1307 ->will( $this->returnValue( 0 ) );
1308
1309 $mockCache = $this->getMockCache();
1310 $mockCache->expects( $this->never() )->method( 'get' );
1311 $mockCache->expects( $this->once() )
1312 ->method( 'delete' )
1313 ->with( '0:SomeDbKey:1' );
1314
1315 $store = $this->newWatchedItemStore(
1316 $this->getMockLoadBalancer( $mockDb ),
1317 $mockCache,
1318 $this->getMockReadOnlyMode()
1319 );
1320
1321 $this->assertFalse(
1322 $store->removeWatch(
1323 $this->getMockNonAnonUserWithId( 1 ),
1324 new TitleValue( 0, 'SomeDbKey' )
1325 )
1326 );
1327 }
1328
1329 public function testRemoveWatch_anonymousUser() {
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->never() )
1332 ->method( 'delete' );
1333
1334 $mockCache = $this->getMockCache();
1335 $mockCache->expects( $this->never() )->method( 'get' );
1336 $mockCache->expects( $this->never() )
1337 ->method( 'delete' );
1338
1339 $store = $this->newWatchedItemStore(
1340 $this->getMockLoadBalancer( $mockDb ),
1341 $mockCache,
1342 $this->getMockReadOnlyMode()
1343 );
1344
1345 $this->assertFalse(
1346 $store->removeWatch(
1347 $this->getAnonUser(),
1348 new TitleValue( 0, 'SomeDbKey' )
1349 )
1350 );
1351 }
1352
1353 public function testGetWatchedItem_existingItem() {
1354 $mockDb = $this->getMockDb();
1355 $mockDb->expects( $this->once() )
1356 ->method( 'selectRow' )
1357 ->with(
1358 'watchlist',
1359 'wl_notificationtimestamp',
1360 [
1361 'wl_user' => 1,
1362 'wl_namespace' => 0,
1363 'wl_title' => 'SomeDbKey',
1364 ]
1365 )
1366 ->will( $this->returnValue(
1367 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1368 ) );
1369
1370 $mockCache = $this->getMockCache();
1371 $mockCache->expects( $this->never() )->method( 'delete' );
1372 $mockCache->expects( $this->once() )
1373 ->method( 'get' )
1374 ->with(
1375 '0:SomeDbKey:1'
1376 )
1377 ->will( $this->returnValue( null ) );
1378 $mockCache->expects( $this->once() )
1379 ->method( 'set' )
1380 ->with(
1381 '0:SomeDbKey:1'
1382 );
1383
1384 $store = $this->newWatchedItemStore(
1385 $this->getMockLoadBalancer( $mockDb ),
1386 $mockCache,
1387 $this->getMockReadOnlyMode()
1388 );
1389
1390 $watchedItem = $store->getWatchedItem(
1391 $this->getMockNonAnonUserWithId( 1 ),
1392 new TitleValue( 0, 'SomeDbKey' )
1393 );
1394 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1395 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1396 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1397 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1398 }
1399
1400 public function testGetWatchedItem_cachedItem() {
1401 $mockDb = $this->getMockDb();
1402 $mockDb->expects( $this->never() )
1403 ->method( 'selectRow' );
1404
1405 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1406 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1407 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1408
1409 $mockCache = $this->getMockCache();
1410 $mockCache->expects( $this->never() )->method( 'delete' );
1411 $mockCache->expects( $this->never() )->method( 'set' );
1412 $mockCache->expects( $this->once() )
1413 ->method( 'get' )
1414 ->with(
1415 '0:SomeDbKey:1'
1416 )
1417 ->will( $this->returnValue( $cachedItem ) );
1418
1419 $store = $this->newWatchedItemStore(
1420 $this->getMockLoadBalancer( $mockDb ),
1421 $mockCache,
1422 $this->getMockReadOnlyMode()
1423 );
1424
1425 $this->assertEquals(
1426 $cachedItem,
1427 $store->getWatchedItem(
1428 $mockUser,
1429 $linkTarget
1430 )
1431 );
1432 }
1433
1434 public function testGetWatchedItem_noItem() {
1435 $mockDb = $this->getMockDb();
1436 $mockDb->expects( $this->once() )
1437 ->method( 'selectRow' )
1438 ->with(
1439 'watchlist',
1440 'wl_notificationtimestamp',
1441 [
1442 'wl_user' => 1,
1443 'wl_namespace' => 0,
1444 'wl_title' => 'SomeDbKey',
1445 ]
1446 )
1447 ->will( $this->returnValue( [] ) );
1448
1449 $mockCache = $this->getMockCache();
1450 $mockCache->expects( $this->never() )->method( 'set' );
1451 $mockCache->expects( $this->never() )->method( 'delete' );
1452 $mockCache->expects( $this->once() )
1453 ->method( 'get' )
1454 ->with( '0:SomeDbKey:1' )
1455 ->will( $this->returnValue( false ) );
1456
1457 $store = $this->newWatchedItemStore(
1458 $this->getMockLoadBalancer( $mockDb ),
1459 $mockCache,
1460 $this->getMockReadOnlyMode()
1461 );
1462
1463 $this->assertFalse(
1464 $store->getWatchedItem(
1465 $this->getMockNonAnonUserWithId( 1 ),
1466 new TitleValue( 0, 'SomeDbKey' )
1467 )
1468 );
1469 }
1470
1471 public function testGetWatchedItem_anonymousUser() {
1472 $mockDb = $this->getMockDb();
1473 $mockDb->expects( $this->never() )
1474 ->method( 'selectRow' );
1475
1476 $mockCache = $this->getMockCache();
1477 $mockCache->expects( $this->never() )->method( 'set' );
1478 $mockCache->expects( $this->never() )->method( 'get' );
1479 $mockCache->expects( $this->never() )->method( 'delete' );
1480
1481 $store = $this->newWatchedItemStore(
1482 $this->getMockLoadBalancer( $mockDb ),
1483 $mockCache,
1484 $this->getMockReadOnlyMode()
1485 );
1486
1487 $this->assertFalse(
1488 $store->getWatchedItem(
1489 $this->getAnonUser(),
1490 new TitleValue( 0, 'SomeDbKey' )
1491 )
1492 );
1493 }
1494
1495 public function testGetWatchedItemsForUser() {
1496 $mockDb = $this->getMockDb();
1497 $mockDb->expects( $this->once() )
1498 ->method( 'select' )
1499 ->with(
1500 'watchlist',
1501 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1502 [ 'wl_user' => 1 ]
1503 )
1504 ->will( $this->returnValue( [
1505 $this->getFakeRow( [
1506 'wl_namespace' => 0,
1507 'wl_title' => 'Foo1',
1508 'wl_notificationtimestamp' => '20151212010101',
1509 ] ),
1510 $this->getFakeRow( [
1511 'wl_namespace' => 1,
1512 'wl_title' => 'Foo2',
1513 'wl_notificationtimestamp' => null,
1514 ] ),
1515 ] ) );
1516
1517 $mockCache = $this->getMockCache();
1518 $mockCache->expects( $this->never() )->method( 'delete' );
1519 $mockCache->expects( $this->never() )->method( 'get' );
1520 $mockCache->expects( $this->never() )->method( 'set' );
1521
1522 $store = $this->newWatchedItemStore(
1523 $this->getMockLoadBalancer( $mockDb ),
1524 $mockCache,
1525 $this->getMockReadOnlyMode()
1526 );
1527 $user = $this->getMockNonAnonUserWithId( 1 );
1528
1529 $watchedItems = $store->getWatchedItemsForUser( $user );
1530
1531 $this->assertInternalType( 'array', $watchedItems );
1532 $this->assertCount( 2, $watchedItems );
1533 foreach ( $watchedItems as $watchedItem ) {
1534 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1535 }
1536 $this->assertEquals(
1537 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1538 $watchedItems[0]
1539 );
1540 $this->assertEquals(
1541 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1542 $watchedItems[1]
1543 );
1544 }
1545
1546 public function provideDbTypes() {
1547 return [
1548 [ false, DB_REPLICA ],
1549 [ true, DB_MASTER ],
1550 ];
1551 }
1552
1553 /**
1554 * @dataProvider provideDbTypes
1555 */
1556 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1557 $mockDb = $this->getMockDb();
1558 $mockCache = $this->getMockCache();
1559 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1560 $user = $this->getMockNonAnonUserWithId( 1 );
1561
1562 $mockDb->expects( $this->once() )
1563 ->method( 'select' )
1564 ->with(
1565 'watchlist',
1566 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1567 [ 'wl_user' => 1 ],
1568 $this->isType( 'string' ),
1569 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1570 )
1571 ->will( $this->returnValue( [] ) );
1572
1573 $store = $this->newWatchedItemStore(
1574 $mockLoadBalancer,
1575 $mockCache,
1576 $this->getMockReadOnlyMode()
1577 );
1578
1579 $watchedItems = $store->getWatchedItemsForUser(
1580 $user,
1581 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1582 );
1583 $this->assertEquals( [], $watchedItems );
1584 }
1585
1586 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1587 $store = $this->newWatchedItemStore(
1588 $this->getMockLoadBalancer( $this->getMockDb() ),
1589 $this->getMockCache(),
1590 $this->getMockReadOnlyMode()
1591 );
1592
1593 $this->setExpectedException( InvalidArgumentException::class );
1594 $store->getWatchedItemsForUser(
1595 $this->getMockNonAnonUserWithId( 1 ),
1596 [ 'sort' => 'foo' ]
1597 );
1598 }
1599
1600 public function testIsWatchedItem_existingItem() {
1601 $mockDb = $this->getMockDb();
1602 $mockDb->expects( $this->once() )
1603 ->method( 'selectRow' )
1604 ->with(
1605 'watchlist',
1606 'wl_notificationtimestamp',
1607 [
1608 'wl_user' => 1,
1609 'wl_namespace' => 0,
1610 'wl_title' => 'SomeDbKey',
1611 ]
1612 )
1613 ->will( $this->returnValue(
1614 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1615 ) );
1616
1617 $mockCache = $this->getMockCache();
1618 $mockCache->expects( $this->never() )->method( 'delete' );
1619 $mockCache->expects( $this->once() )
1620 ->method( 'get' )
1621 ->with( '0:SomeDbKey:1' )
1622 ->will( $this->returnValue( false ) );
1623 $mockCache->expects( $this->once() )
1624 ->method( 'set' )
1625 ->with(
1626 '0:SomeDbKey:1'
1627 );
1628
1629 $store = $this->newWatchedItemStore(
1630 $this->getMockLoadBalancer( $mockDb ),
1631 $mockCache,
1632 $this->getMockReadOnlyMode()
1633 );
1634
1635 $this->assertTrue(
1636 $store->isWatched(
1637 $this->getMockNonAnonUserWithId( 1 ),
1638 new TitleValue( 0, 'SomeDbKey' )
1639 )
1640 );
1641 }
1642
1643 public function testIsWatchedItem_noItem() {
1644 $mockDb = $this->getMockDb();
1645 $mockDb->expects( $this->once() )
1646 ->method( 'selectRow' )
1647 ->with(
1648 'watchlist',
1649 'wl_notificationtimestamp',
1650 [
1651 'wl_user' => 1,
1652 'wl_namespace' => 0,
1653 'wl_title' => 'SomeDbKey',
1654 ]
1655 )
1656 ->will( $this->returnValue( [] ) );
1657
1658 $mockCache = $this->getMockCache();
1659 $mockCache->expects( $this->never() )->method( 'set' );
1660 $mockCache->expects( $this->never() )->method( 'delete' );
1661 $mockCache->expects( $this->once() )
1662 ->method( 'get' )
1663 ->with( '0:SomeDbKey:1' )
1664 ->will( $this->returnValue( false ) );
1665
1666 $store = $this->newWatchedItemStore(
1667 $this->getMockLoadBalancer( $mockDb ),
1668 $mockCache,
1669 $this->getMockReadOnlyMode()
1670 );
1671
1672 $this->assertFalse(
1673 $store->isWatched(
1674 $this->getMockNonAnonUserWithId( 1 ),
1675 new TitleValue( 0, 'SomeDbKey' )
1676 )
1677 );
1678 }
1679
1680 public function testIsWatchedItem_anonymousUser() {
1681 $mockDb = $this->getMockDb();
1682 $mockDb->expects( $this->never() )
1683 ->method( 'selectRow' );
1684
1685 $mockCache = $this->getMockCache();
1686 $mockCache->expects( $this->never() )->method( 'set' );
1687 $mockCache->expects( $this->never() )->method( 'get' );
1688 $mockCache->expects( $this->never() )->method( 'delete' );
1689
1690 $store = $this->newWatchedItemStore(
1691 $this->getMockLoadBalancer( $mockDb ),
1692 $mockCache,
1693 $this->getMockReadOnlyMode()
1694 );
1695
1696 $this->assertFalse(
1697 $store->isWatched(
1698 $this->getAnonUser(),
1699 new TitleValue( 0, 'SomeDbKey' )
1700 )
1701 );
1702 }
1703
1704 public function testGetNotificationTimestampsBatch() {
1705 $targets = [
1706 new TitleValue( 0, 'SomeDbKey' ),
1707 new TitleValue( 1, 'AnotherDbKey' ),
1708 ];
1709
1710 $mockDb = $this->getMockDb();
1711 $dbResult = [
1712 $this->getFakeRow( [
1713 'wl_namespace' => '0',
1714 'wl_title' => 'SomeDbKey',
1715 'wl_notificationtimestamp' => '20151212010101',
1716 ] ),
1717 $this->getFakeRow(
1718 [
1719 'wl_namespace' => '1',
1720 'wl_title' => 'AnotherDbKey',
1721 'wl_notificationtimestamp' => null,
1722 ]
1723 ),
1724 ];
1725
1726 $mockDb->expects( $this->once() )
1727 ->method( 'makeWhereFrom2d' )
1728 ->with(
1729 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1730 $this->isType( 'string' ),
1731 $this->isType( 'string' )
1732 )
1733 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1734 $mockDb->expects( $this->once() )
1735 ->method( 'select' )
1736 ->with(
1737 'watchlist',
1738 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1739 [
1740 'makeWhereFrom2d return value',
1741 'wl_user' => 1
1742 ],
1743 $this->isType( 'string' )
1744 )
1745 ->will( $this->returnValue( $dbResult ) );
1746
1747 $mockCache = $this->getMockCache();
1748 $mockCache->expects( $this->exactly( 2 ) )
1749 ->method( 'get' )
1750 ->withConsecutive(
1751 [ '0:SomeDbKey:1' ],
1752 [ '1:AnotherDbKey:1' ]
1753 )
1754 ->will( $this->returnValue( null ) );
1755 $mockCache->expects( $this->never() )->method( 'set' );
1756 $mockCache->expects( $this->never() )->method( 'delete' );
1757
1758 $store = $this->newWatchedItemStore(
1759 $this->getMockLoadBalancer( $mockDb ),
1760 $mockCache,
1761 $this->getMockReadOnlyMode()
1762 );
1763
1764 $this->assertEquals(
1765 [
1766 0 => [ 'SomeDbKey' => '20151212010101', ],
1767 1 => [ 'AnotherDbKey' => null, ],
1768 ],
1769 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1770 );
1771 }
1772
1773 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1774 $targets = [
1775 new TitleValue( 0, 'OtherDbKey' ),
1776 ];
1777
1778 $mockDb = $this->getMockDb();
1779
1780 $mockDb->expects( $this->once() )
1781 ->method( 'makeWhereFrom2d' )
1782 ->with(
1783 [ [ 'OtherDbKey' => 1 ] ],
1784 $this->isType( 'string' ),
1785 $this->isType( 'string' )
1786 )
1787 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1788 $mockDb->expects( $this->once() )
1789 ->method( 'select' )
1790 ->with(
1791 'watchlist',
1792 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1793 [
1794 'makeWhereFrom2d return value',
1795 'wl_user' => 1
1796 ],
1797 $this->isType( 'string' )
1798 )
1799 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1800
1801 $mockCache = $this->getMockCache();
1802 $mockCache->expects( $this->once() )
1803 ->method( 'get' )
1804 ->with( '0:OtherDbKey:1' )
1805 ->will( $this->returnValue( null ) );
1806 $mockCache->expects( $this->never() )->method( 'set' );
1807 $mockCache->expects( $this->never() )->method( 'delete' );
1808
1809 $store = $this->newWatchedItemStore(
1810 $this->getMockLoadBalancer( $mockDb ),
1811 $mockCache,
1812 $this->getMockReadOnlyMode()
1813 );
1814
1815 $this->assertEquals(
1816 [
1817 0 => [ 'OtherDbKey' => false, ],
1818 ],
1819 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1820 );
1821 }
1822
1823 public function testGetNotificationTimestampsBatch_cachedItem() {
1824 $targets = [
1825 new TitleValue( 0, 'SomeDbKey' ),
1826 new TitleValue( 1, 'AnotherDbKey' ),
1827 ];
1828
1829 $user = $this->getMockNonAnonUserWithId( 1 );
1830 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1831
1832 $mockDb = $this->getMockDb();
1833
1834 $mockDb->expects( $this->once() )
1835 ->method( 'makeWhereFrom2d' )
1836 ->with(
1837 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1838 $this->isType( 'string' ),
1839 $this->isType( 'string' )
1840 )
1841 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1842 $mockDb->expects( $this->once() )
1843 ->method( 'select' )
1844 ->with(
1845 'watchlist',
1846 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1847 [
1848 'makeWhereFrom2d return value',
1849 'wl_user' => 1
1850 ],
1851 $this->isType( 'string' )
1852 )
1853 ->will( $this->returnValue( [
1854 $this->getFakeRow(
1855 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1856 )
1857 ] ) );
1858
1859 $mockCache = $this->getMockCache();
1860 $mockCache->expects( $this->at( 1 ) )
1861 ->method( 'get' )
1862 ->with( '0:SomeDbKey:1' )
1863 ->will( $this->returnValue( $cachedItem ) );
1864 $mockCache->expects( $this->at( 3 ) )
1865 ->method( 'get' )
1866 ->with( '1:AnotherDbKey:1' )
1867 ->will( $this->returnValue( null ) );
1868 $mockCache->expects( $this->never() )->method( 'set' );
1869 $mockCache->expects( $this->never() )->method( 'delete' );
1870
1871 $store = $this->newWatchedItemStore(
1872 $this->getMockLoadBalancer( $mockDb ),
1873 $mockCache,
1874 $this->getMockReadOnlyMode()
1875 );
1876
1877 $this->assertEquals(
1878 [
1879 0 => [ 'SomeDbKey' => '20151212010101', ],
1880 1 => [ 'AnotherDbKey' => null, ],
1881 ],
1882 $store->getNotificationTimestampsBatch( $user, $targets )
1883 );
1884 }
1885
1886 public function testGetNotificationTimestampsBatch_allItemsCached() {
1887 $targets = [
1888 new TitleValue( 0, 'SomeDbKey' ),
1889 new TitleValue( 1, 'AnotherDbKey' ),
1890 ];
1891
1892 $user = $this->getMockNonAnonUserWithId( 1 );
1893 $cachedItems = [
1894 new WatchedItem( $user, $targets[0], '20151212010101' ),
1895 new WatchedItem( $user, $targets[1], null ),
1896 ];
1897 $mockDb = $this->getMockDb();
1898 $mockDb->expects( $this->never() )->method( $this->anything() );
1899
1900 $mockCache = $this->getMockCache();
1901 $mockCache->expects( $this->at( 1 ) )
1902 ->method( 'get' )
1903 ->with( '0:SomeDbKey:1' )
1904 ->will( $this->returnValue( $cachedItems[0] ) );
1905 $mockCache->expects( $this->at( 3 ) )
1906 ->method( 'get' )
1907 ->with( '1:AnotherDbKey:1' )
1908 ->will( $this->returnValue( $cachedItems[1] ) );
1909 $mockCache->expects( $this->never() )->method( 'set' );
1910 $mockCache->expects( $this->never() )->method( 'delete' );
1911
1912 $store = $this->newWatchedItemStore(
1913 $this->getMockLoadBalancer( $mockDb ),
1914 $mockCache,
1915 $this->getMockReadOnlyMode()
1916 );
1917
1918 $this->assertEquals(
1919 [
1920 0 => [ 'SomeDbKey' => '20151212010101', ],
1921 1 => [ 'AnotherDbKey' => null, ],
1922 ],
1923 $store->getNotificationTimestampsBatch( $user, $targets )
1924 );
1925 }
1926
1927 public function testGetNotificationTimestampsBatch_anonymousUser() {
1928 $targets = [
1929 new TitleValue( 0, 'SomeDbKey' ),
1930 new TitleValue( 1, 'AnotherDbKey' ),
1931 ];
1932
1933 $mockDb = $this->getMockDb();
1934 $mockDb->expects( $this->never() )->method( $this->anything() );
1935
1936 $mockCache = $this->getMockCache();
1937 $mockCache->expects( $this->never() )->method( $this->anything() );
1938
1939 $store = $this->newWatchedItemStore(
1940 $this->getMockLoadBalancer( $mockDb ),
1941 $mockCache,
1942 $this->getMockReadOnlyMode()
1943 );
1944
1945 $this->assertEquals(
1946 [
1947 0 => [ 'SomeDbKey' => false, ],
1948 1 => [ 'AnotherDbKey' => false, ],
1949 ],
1950 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1951 );
1952 }
1953
1954 public function testResetNotificationTimestamp_anonymousUser() {
1955 $mockDb = $this->getMockDb();
1956 $mockDb->expects( $this->never() )
1957 ->method( 'selectRow' );
1958
1959 $mockCache = $this->getMockCache();
1960 $mockCache->expects( $this->never() )->method( 'get' );
1961 $mockCache->expects( $this->never() )->method( 'set' );
1962 $mockCache->expects( $this->never() )->method( 'delete' );
1963
1964 $store = $this->newWatchedItemStore(
1965 $this->getMockLoadBalancer( $mockDb ),
1966 $mockCache,
1967 $this->getMockReadOnlyMode()
1968 );
1969
1970 $this->assertFalse(
1971 $store->resetNotificationTimestamp(
1972 $this->getAnonUser(),
1973 Title::newFromText( 'SomeDbKey' )
1974 )
1975 );
1976 }
1977
1978 public function testResetNotificationTimestamp_noItem() {
1979 $mockDb = $this->getMockDb();
1980 $mockDb->expects( $this->once() )
1981 ->method( 'selectRow' )
1982 ->with(
1983 'watchlist',
1984 'wl_notificationtimestamp',
1985 [
1986 'wl_user' => 1,
1987 'wl_namespace' => 0,
1988 'wl_title' => 'SomeDbKey',
1989 ]
1990 )
1991 ->will( $this->returnValue( [] ) );
1992
1993 $mockCache = $this->getMockCache();
1994 $mockCache->expects( $this->never() )->method( 'get' );
1995 $mockCache->expects( $this->never() )->method( 'set' );
1996 $mockCache->expects( $this->never() )->method( 'delete' );
1997
1998 $store = $this->newWatchedItemStore(
1999 $this->getMockLoadBalancer( $mockDb ),
2000 $mockCache,
2001 $this->getMockReadOnlyMode()
2002 );
2003
2004 $this->assertFalse(
2005 $store->resetNotificationTimestamp(
2006 $this->getMockNonAnonUserWithId( 1 ),
2007 Title::newFromText( 'SomeDbKey' )
2008 )
2009 );
2010 }
2011
2012 public function testResetNotificationTimestamp_item() {
2013 $user = $this->getMockNonAnonUserWithId( 1 );
2014 $title = Title::newFromText( 'SomeDbKey' );
2015
2016 $mockDb = $this->getMockDb();
2017 $mockDb->expects( $this->once() )
2018 ->method( 'selectRow' )
2019 ->with(
2020 'watchlist',
2021 'wl_notificationtimestamp',
2022 [
2023 'wl_user' => 1,
2024 'wl_namespace' => 0,
2025 'wl_title' => 'SomeDbKey',
2026 ]
2027 )
2028 ->will( $this->returnValue(
2029 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2030 ) );
2031
2032 $mockCache = $this->getMockCache();
2033 $mockCache->expects( $this->never() )->method( 'get' );
2034 $mockCache->expects( $this->once() )
2035 ->method( 'set' )
2036 ->with(
2037 '0:SomeDbKey:1',
2038 $this->isInstanceOf( WatchedItem::class )
2039 );
2040 $mockCache->expects( $this->once() )
2041 ->method( 'delete' )
2042 ->with( '0:SomeDbKey:1' );
2043
2044 $store = $this->newWatchedItemStore(
2045 $this->getMockLoadBalancer( $mockDb ),
2046 $mockCache,
2047 $this->getMockReadOnlyMode()
2048 );
2049
2050 // Note: This does not actually assert the job is correct
2051 $callableCallCounter = 0;
2052 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2053 $callableCallCounter++;
2054 $this->assertInternalType( 'callable', $callable );
2055 };
2056 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2057
2058 $this->assertTrue(
2059 $store->resetNotificationTimestamp(
2060 $user,
2061 $title
2062 )
2063 );
2064 $this->assertEquals( 1, $callableCallCounter );
2065
2066 ScopedCallback::consume( $scopedOverride );
2067 }
2068
2069 public function testResetNotificationTimestamp_noItemForced() {
2070 $user = $this->getMockNonAnonUserWithId( 1 );
2071 $title = Title::newFromText( 'SomeDbKey' );
2072
2073 $mockDb = $this->getMockDb();
2074 $mockDb->expects( $this->never() )
2075 ->method( 'selectRow' );
2076
2077 $mockCache = $this->getMockCache();
2078 $mockCache->expects( $this->never() )->method( 'get' );
2079 $mockCache->expects( $this->never() )->method( 'set' );
2080 $mockCache->expects( $this->once() )
2081 ->method( 'delete' )
2082 ->with( '0:SomeDbKey:1' );
2083
2084 $store = $this->newWatchedItemStore(
2085 $this->getMockLoadBalancer( $mockDb ),
2086 $mockCache,
2087 $this->getMockReadOnlyMode()
2088 );
2089
2090 // Note: This does not actually assert the job is correct
2091 $callableCallCounter = 0;
2092 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2093 $callableCallCounter++;
2094 $this->assertInternalType( 'callable', $callable );
2095 };
2096 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2097
2098 $this->assertTrue(
2099 $store->resetNotificationTimestamp(
2100 $user,
2101 $title,
2102 'force'
2103 )
2104 );
2105 $this->assertEquals( 1, $callableCallCounter );
2106
2107 ScopedCallback::consume( $scopedOverride );
2108 }
2109
2110 /**
2111 * @param string $text
2112 * @param int $ns
2113 *
2114 * @return PHPUnit_Framework_MockObject_MockObject|Title
2115 */
2116 private function getMockTitle( $text, $ns = 0 ) {
2117 $title = $this->createMock( Title::class );
2118 $title->expects( $this->any() )
2119 ->method( 'getText' )
2120 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2121 $title->expects( $this->any() )
2122 ->method( 'getDbKey' )
2123 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2124 $title->expects( $this->any() )
2125 ->method( 'getNamespace' )
2126 ->will( $this->returnValue( $ns ) );
2127 return $title;
2128 }
2129
2130 private function verifyCallbackJob(
2131 $callback,
2132 LinkTarget $expectedTitle,
2133 $expectedUserId,
2134 callable $notificationTimestampCondition
2135 ) {
2136 $this->assertInternalType( 'callable', $callback );
2137
2138 $callbackReflector = new ReflectionFunction( $callback );
2139 $vars = $callbackReflector->getStaticVariables();
2140 $this->assertArrayHasKey( 'job', $vars );
2141 $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
2142
2143 /** @var ActivityUpdateJob $job */
2144 $job = $vars['job'];
2145 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2146 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2147
2148 $jobParams = $job->getParams();
2149 $this->assertArrayHasKey( 'type', $jobParams );
2150 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2151 $this->assertArrayHasKey( 'userid', $jobParams );
2152 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2153 $this->assertArrayHasKey( 'notifTime', $jobParams );
2154 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2155 }
2156
2157 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2158 $user = $this->getMockNonAnonUserWithId( 1 );
2159 $oldid = 22;
2160 $title = $this->getMockTitle( 'SomeTitle' );
2161 $title->expects( $this->once() )
2162 ->method( 'getNextRevisionID' )
2163 ->with( $oldid )
2164 ->will( $this->returnValue( false ) );
2165
2166 $mockDb = $this->getMockDb();
2167 $mockDb->expects( $this->never() )
2168 ->method( 'selectRow' );
2169
2170 $mockCache = $this->getMockCache();
2171 $mockCache->expects( $this->never() )->method( 'get' );
2172 $mockCache->expects( $this->never() )->method( 'set' );
2173 $mockCache->expects( $this->once() )
2174 ->method( 'delete' )
2175 ->with( '0:SomeTitle:1' );
2176
2177 $store = $this->newWatchedItemStore(
2178 $this->getMockLoadBalancer( $mockDb ),
2179 $mockCache,
2180 $this->getMockReadOnlyMode()
2181 );
2182
2183 $callableCallCounter = 0;
2184 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2185 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2186 $callableCallCounter++;
2187 $this->verifyCallbackJob(
2188 $callable,
2189 $title,
2190 $user->getId(),
2191 function ( $time ) {
2192 return $time === null;
2193 }
2194 );
2195 }
2196 );
2197
2198 $this->assertTrue(
2199 $store->resetNotificationTimestamp(
2200 $user,
2201 $title,
2202 'force',
2203 $oldid
2204 )
2205 );
2206 $this->assertEquals( 1, $callableCallCounter );
2207
2208 ScopedCallback::consume( $scopedOverride );
2209 }
2210
2211 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2212 $user = $this->getMockNonAnonUserWithId( 1 );
2213 $oldid = 22;
2214 $title = $this->getMockTitle( 'SomeDbKey' );
2215 $title->expects( $this->once() )
2216 ->method( 'getNextRevisionID' )
2217 ->with( $oldid )
2218 ->will( $this->returnValue( 33 ) );
2219
2220 $mockDb = $this->getMockDb();
2221 $mockDb->expects( $this->once() )
2222 ->method( 'selectRow' )
2223 ->with(
2224 'watchlist',
2225 'wl_notificationtimestamp',
2226 [
2227 'wl_user' => 1,
2228 'wl_namespace' => 0,
2229 'wl_title' => 'SomeDbKey',
2230 ]
2231 )
2232 ->will( $this->returnValue(
2233 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2234 ) );
2235
2236 $mockCache = $this->getMockCache();
2237 $mockCache->expects( $this->never() )->method( 'get' );
2238 $mockCache->expects( $this->once() )
2239 ->method( 'set' )
2240 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2241 $mockCache->expects( $this->once() )
2242 ->method( 'delete' )
2243 ->with( '0:SomeDbKey:1' );
2244
2245 $store = $this->newWatchedItemStore(
2246 $this->getMockLoadBalancer( $mockDb ),
2247 $mockCache,
2248 $this->getMockReadOnlyMode()
2249 );
2250
2251 $addUpdateCallCounter = 0;
2252 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2253 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2254 $addUpdateCallCounter++;
2255 $this->verifyCallbackJob(
2256 $callable,
2257 $title,
2258 $user->getId(),
2259 function ( $time ) {
2260 return $time !== null && $time > '20151212010101';
2261 }
2262 );
2263 }
2264 );
2265
2266 $getTimestampCallCounter = 0;
2267 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2268 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2269 $getTimestampCallCounter++;
2270 $this->assertEquals( $title, $titleParam );
2271 $this->assertEquals( $oldid, $oldidParam );
2272 }
2273 );
2274
2275 $this->assertTrue(
2276 $store->resetNotificationTimestamp(
2277 $user,
2278 $title,
2279 'force',
2280 $oldid
2281 )
2282 );
2283 $this->assertEquals( 1, $addUpdateCallCounter );
2284 $this->assertEquals( 1, $getTimestampCallCounter );
2285
2286 ScopedCallback::consume( $scopedOverrideDeferred );
2287 ScopedCallback::consume( $scopedOverrideRevision );
2288 }
2289
2290 public function testResetNotificationTimestamp_notWatchedPageForced() {
2291 $user = $this->getMockNonAnonUserWithId( 1 );
2292 $oldid = 22;
2293 $title = $this->getMockTitle( 'SomeDbKey' );
2294 $title->expects( $this->once() )
2295 ->method( 'getNextRevisionID' )
2296 ->with( $oldid )
2297 ->will( $this->returnValue( 33 ) );
2298
2299 $mockDb = $this->getMockDb();
2300 $mockDb->expects( $this->once() )
2301 ->method( 'selectRow' )
2302 ->with(
2303 'watchlist',
2304 'wl_notificationtimestamp',
2305 [
2306 'wl_user' => 1,
2307 'wl_namespace' => 0,
2308 'wl_title' => 'SomeDbKey',
2309 ]
2310 )
2311 ->will( $this->returnValue( false ) );
2312
2313 $mockCache = $this->getMockCache();
2314 $mockCache->expects( $this->never() )->method( 'get' );
2315 $mockCache->expects( $this->never() )->method( 'set' );
2316 $mockCache->expects( $this->once() )
2317 ->method( 'delete' )
2318 ->with( '0:SomeDbKey:1' );
2319
2320 $store = $this->newWatchedItemStore(
2321 $this->getMockLoadBalancer( $mockDb ),
2322 $mockCache,
2323 $this->getMockReadOnlyMode()
2324 );
2325
2326 $callableCallCounter = 0;
2327 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2328 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2329 $callableCallCounter++;
2330 $this->verifyCallbackJob(
2331 $callable,
2332 $title,
2333 $user->getId(),
2334 function ( $time ) {
2335 return $time === null;
2336 }
2337 );
2338 }
2339 );
2340
2341 $this->assertTrue(
2342 $store->resetNotificationTimestamp(
2343 $user,
2344 $title,
2345 'force',
2346 $oldid
2347 )
2348 );
2349 $this->assertEquals( 1, $callableCallCounter );
2350
2351 ScopedCallback::consume( $scopedOverride );
2352 }
2353
2354 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2355 $user = $this->getMockNonAnonUserWithId( 1 );
2356 $oldid = 22;
2357 $title = $this->getMockTitle( 'SomeDbKey' );
2358 $title->expects( $this->once() )
2359 ->method( 'getNextRevisionID' )
2360 ->with( $oldid )
2361 ->will( $this->returnValue( 33 ) );
2362
2363 $mockDb = $this->getMockDb();
2364 $mockDb->expects( $this->once() )
2365 ->method( 'selectRow' )
2366 ->with(
2367 'watchlist',
2368 'wl_notificationtimestamp',
2369 [
2370 'wl_user' => 1,
2371 'wl_namespace' => 0,
2372 'wl_title' => 'SomeDbKey',
2373 ]
2374 )
2375 ->will( $this->returnValue(
2376 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2377 ) );
2378
2379 $mockCache = $this->getMockCache();
2380 $mockCache->expects( $this->never() )->method( 'get' );
2381 $mockCache->expects( $this->once() )
2382 ->method( 'set' )
2383 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2384 $mockCache->expects( $this->once() )
2385 ->method( 'delete' )
2386 ->with( '0:SomeDbKey:1' );
2387
2388 $store = $this->newWatchedItemStore(
2389 $this->getMockLoadBalancer( $mockDb ),
2390 $mockCache,
2391 $this->getMockReadOnlyMode()
2392 );
2393
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 === '30151212010101';
2404 }
2405 );
2406 }
2407 );
2408
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 );
2415 }
2416 );
2417
2418 $this->assertTrue(
2419 $store->resetNotificationTimestamp(
2420 $user,
2421 $title,
2422 'force',
2423 $oldid
2424 )
2425 );
2426 $this->assertEquals( 1, $addUpdateCallCounter );
2427 $this->assertEquals( 1, $getTimestampCallCounter );
2428
2429 ScopedCallback::consume( $scopedOverrideDeferred );
2430 ScopedCallback::consume( $scopedOverrideRevision );
2431 }
2432
2433 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2434 $user = $this->getMockNonAnonUserWithId( 1 );
2435 $oldid = 22;
2436 $title = $this->getMockTitle( 'SomeDbKey' );
2437 $title->expects( $this->once() )
2438 ->method( 'getNextRevisionID' )
2439 ->with( $oldid )
2440 ->will( $this->returnValue( 33 ) );
2441
2442 $mockDb = $this->getMockDb();
2443 $mockDb->expects( $this->once() )
2444 ->method( 'selectRow' )
2445 ->with(
2446 'watchlist',
2447 'wl_notificationtimestamp',
2448 [
2449 'wl_user' => 1,
2450 'wl_namespace' => 0,
2451 'wl_title' => 'SomeDbKey',
2452 ]
2453 )
2454 ->will( $this->returnValue(
2455 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2456 ) );
2457
2458 $mockCache = $this->getMockCache();
2459 $mockCache->expects( $this->never() )->method( 'get' );
2460 $mockCache->expects( $this->once() )
2461 ->method( 'set' )
2462 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2463 $mockCache->expects( $this->once() )
2464 ->method( 'delete' )
2465 ->with( '0:SomeDbKey:1' );
2466
2467 $store = $this->newWatchedItemStore(
2468 $this->getMockLoadBalancer( $mockDb ),
2469 $mockCache,
2470 $this->getMockReadOnlyMode()
2471 );
2472
2473 $addUpdateCallCounter = 0;
2474 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2475 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2476 $addUpdateCallCounter++;
2477 $this->verifyCallbackJob(
2478 $callable,
2479 $title,
2480 $user->getId(),
2481 function ( $time ) {
2482 return $time === false;
2483 }
2484 );
2485 }
2486 );
2487
2488 $getTimestampCallCounter = 0;
2489 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2490 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2491 $getTimestampCallCounter++;
2492 $this->assertEquals( $title, $titleParam );
2493 $this->assertEquals( $oldid, $oldidParam );
2494 }
2495 );
2496
2497 $this->assertTrue(
2498 $store->resetNotificationTimestamp(
2499 $user,
2500 $title,
2501 '',
2502 $oldid
2503 )
2504 );
2505 $this->assertEquals( 1, $addUpdateCallCounter );
2506 $this->assertEquals( 1, $getTimestampCallCounter );
2507
2508 ScopedCallback::consume( $scopedOverrideDeferred );
2509 ScopedCallback::consume( $scopedOverrideRevision );
2510 }
2511
2512 public function testSetNotificationTimestampsForUser_anonUser() {
2513 $store = $this->newWatchedItemStore(
2514 $this->getMockLoadBalancer( $this->getMockDb() ),
2515 $this->getMockCache(),
2516 $this->getMockReadOnlyMode()
2517 );
2518 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2519 }
2520
2521 public function testSetNotificationTimestampsForUser_allRows() {
2522 $user = $this->getMockNonAnonUserWithId( 1 );
2523 $timestamp = '20100101010101';
2524
2525 $mockDb = $this->getMockDb();
2526 $mockDb->expects( $this->once() )
2527 ->method( 'update' )
2528 ->with(
2529 'watchlist',
2530 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2531 [ 'wl_user' => 1 ]
2532 )
2533 ->will( $this->returnValue( true ) );
2534 $mockDb->expects( $this->exactly( 1 ) )
2535 ->method( 'timestamp' )
2536 ->will( $this->returnCallback( function ( $value ) {
2537 return 'TS' . $value . 'TS';
2538 } ) );
2539
2540 $store = $this->newWatchedItemStore(
2541 $this->getMockLoadBalancer( $mockDb ),
2542 $this->getMockCache(),
2543 $this->getMockReadOnlyMode()
2544 );
2545
2546 $this->assertTrue(
2547 $store->setNotificationTimestampsForUser( $user, $timestamp )
2548 );
2549 }
2550
2551 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2552 $user = $this->getMockNonAnonUserWithId( 1 );
2553 $timestamp = null;
2554
2555 $mockDb = $this->getMockDb();
2556 $mockDb->expects( $this->once() )
2557 ->method( 'update' )
2558 ->with(
2559 'watchlist',
2560 [ 'wl_notificationtimestamp' => null ],
2561 [ 'wl_user' => 1 ]
2562 )
2563 ->will( $this->returnValue( true ) );
2564 $mockDb->expects( $this->exactly( 0 ) )
2565 ->method( 'timestamp' )
2566 ->will( $this->returnCallback( function ( $value ) {
2567 return 'TS' . $value . 'TS';
2568 } ) );
2569
2570 $store = $this->newWatchedItemStore(
2571 $this->getMockLoadBalancer( $mockDb ),
2572 $this->getMockCache(),
2573 $this->getMockReadOnlyMode()
2574 );
2575
2576 $this->assertTrue(
2577 $store->setNotificationTimestampsForUser( $user, $timestamp )
2578 );
2579 }
2580
2581 public function testSetNotificationTimestampsForUser_specificTargets() {
2582 $user = $this->getMockNonAnonUserWithId( 1 );
2583 $timestamp = '20100101010101';
2584 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2585
2586 $mockDb = $this->getMockDb();
2587 $mockDb->expects( $this->once() )
2588 ->method( 'update' )
2589 ->with(
2590 'watchlist',
2591 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2592 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2593 )
2594 ->will( $this->returnValue( true ) );
2595 $mockDb->expects( $this->exactly( 1 ) )
2596 ->method( 'timestamp' )
2597 ->will( $this->returnCallback( function ( $value ) {
2598 return 'TS' . $value . 'TS';
2599 } ) );
2600 $mockDb->expects( $this->once() )
2601 ->method( 'makeWhereFrom2d' )
2602 ->with(
2603 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2604 $this->isType( 'string' ),
2605 $this->isType( 'string' )
2606 )
2607 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2608
2609 $store = $this->newWatchedItemStore(
2610 $this->getMockLoadBalancer( $mockDb ),
2611 $this->getMockCache(),
2612 $this->getMockReadOnlyMode()
2613 );
2614
2615 $this->assertTrue(
2616 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2617 );
2618 }
2619
2620 public function testUpdateNotificationTimestamp_watchersExist() {
2621 $mockDb = $this->getMockDb();
2622 $mockDb->expects( $this->once() )
2623 ->method( 'selectFieldValues' )
2624 ->with(
2625 'watchlist',
2626 'wl_user',
2627 [
2628 'wl_user != 1',
2629 'wl_namespace' => 0,
2630 'wl_title' => 'SomeDbKey',
2631 'wl_notificationtimestamp IS NULL'
2632 ]
2633 )
2634 ->will( $this->returnValue( [ '2', '3' ] ) );
2635 $mockDb->expects( $this->once() )
2636 ->method( 'update' )
2637 ->with(
2638 'watchlist',
2639 [ 'wl_notificationtimestamp' => null ],
2640 [
2641 'wl_user' => [ 2, 3 ],
2642 'wl_namespace' => 0,
2643 'wl_title' => 'SomeDbKey',
2644 ]
2645 );
2646
2647 $mockCache = $this->getMockCache();
2648 $mockCache->expects( $this->never() )->method( 'set' );
2649 $mockCache->expects( $this->never() )->method( 'get' );
2650 $mockCache->expects( $this->never() )->method( 'delete' );
2651
2652 $store = $this->newWatchedItemStore(
2653 $this->getMockLoadBalancer( $mockDb ),
2654 $mockCache,
2655 $this->getMockReadOnlyMode()
2656 );
2657
2658 $this->assertEquals(
2659 [ 2, 3 ],
2660 $store->updateNotificationTimestamp(
2661 $this->getMockNonAnonUserWithId( 1 ),
2662 new TitleValue( 0, 'SomeDbKey' ),
2663 '20151212010101'
2664 )
2665 );
2666 }
2667
2668 public function testUpdateNotificationTimestamp_noWatchers() {
2669 $mockDb = $this->getMockDb();
2670 $mockDb->expects( $this->once() )
2671 ->method( 'selectFieldValues' )
2672 ->with(
2673 'watchlist',
2674 'wl_user',
2675 [
2676 'wl_user != 1',
2677 'wl_namespace' => 0,
2678 'wl_title' => 'SomeDbKey',
2679 'wl_notificationtimestamp IS NULL'
2680 ]
2681 )
2682 ->will(
2683 $this->returnValue( [] )
2684 );
2685 $mockDb->expects( $this->never() )
2686 ->method( 'update' );
2687
2688 $mockCache = $this->getMockCache();
2689 $mockCache->expects( $this->never() )->method( 'set' );
2690 $mockCache->expects( $this->never() )->method( 'get' );
2691 $mockCache->expects( $this->never() )->method( 'delete' );
2692
2693 $store = $this->newWatchedItemStore(
2694 $this->getMockLoadBalancer( $mockDb ),
2695 $mockCache,
2696 $this->getMockReadOnlyMode()
2697 );
2698
2699 $watchers = $store->updateNotificationTimestamp(
2700 $this->getMockNonAnonUserWithId( 1 ),
2701 new TitleValue( 0, 'SomeDbKey' ),
2702 '20151212010101'
2703 );
2704 $this->assertInternalType( 'array', $watchers );
2705 $this->assertEmpty( $watchers );
2706 }
2707
2708 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2709 $user = $this->getMockNonAnonUserWithId( 1 );
2710 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2711
2712 $mockDb = $this->getMockDb();
2713 $mockDb->expects( $this->once() )
2714 ->method( 'selectRow' )
2715 ->will( $this->returnValue(
2716 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2717 ) );
2718 $mockDb->expects( $this->once() )
2719 ->method( 'selectFieldValues' )
2720 ->will(
2721 $this->returnValue( [ '2', '3' ] )
2722 );
2723 $mockDb->expects( $this->once() )
2724 ->method( 'update' );
2725
2726 $mockCache = $this->getMockCache();
2727 $mockCache->expects( $this->once() )
2728 ->method( 'set' )
2729 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2730 $mockCache->expects( $this->once() )
2731 ->method( 'get' )
2732 ->with( '0:SomeDbKey:1' );
2733 $mockCache->expects( $this->once() )
2734 ->method( 'delete' )
2735 ->with( '0:SomeDbKey:1' );
2736
2737 $store = $this->newWatchedItemStore(
2738 $this->getMockLoadBalancer( $mockDb ),
2739 $mockCache,
2740 $this->getMockReadOnlyMode()
2741 );
2742
2743 // This will add the item to the cache
2744 $store->getWatchedItem( $user, $titleValue );
2745
2746 $store->updateNotificationTimestamp(
2747 $this->getMockNonAnonUserWithId( 1 ),
2748 $titleValue,
2749 '20151212010101'
2750 );
2751 }
2752
2753 }