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