Watchlist: Commit after each batch watchlist insertion and removal
[lhc/web/wiklou.git] / tests / phpunit / includes / watcheditem / WatchedItemStoreUnitTest.php
1 <?php
2 use MediaWiki\Linker\LinkTarget;
3 use Wikimedia\Rdbms\LBFactory;
4 use Wikimedia\Rdbms\LoadBalancer;
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 $mockDb->expects( $this->once() )
1110 ->method( 'affectedRows' )
1111 ->willReturn( 2 );
1112
1113 $mockCache = $this->getMockCache();
1114 $mockCache->expects( $this->exactly( 2 ) )
1115 ->method( 'delete' );
1116 $mockCache->expects( $this->at( 1 ) )
1117 ->method( 'delete' )
1118 ->with( '0:Some_Page:1' );
1119 $mockCache->expects( $this->at( 3 ) )
1120 ->method( 'delete' )
1121 ->with( '1:Some_Page:1' );
1122
1123 $store = $this->newWatchedItemStore(
1124 $this->getMockLBFactory( $mockDb ),
1125 $mockCache,
1126 $this->getMockReadOnlyMode()
1127 );
1128
1129 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1130
1131 $this->assertTrue(
1132 $store->addWatchBatchForUser(
1133 $mockUser,
1134 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1135 )
1136 );
1137 }
1138
1139 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
1140 $mockDb = $this->getMockDb();
1141 $mockDb->expects( $this->never() )
1142 ->method( 'insert' );
1143
1144 $mockCache = $this->getMockCache();
1145 $mockCache->expects( $this->never() )
1146 ->method( 'delete' );
1147
1148 $store = $this->newWatchedItemStore(
1149 $this->getMockLBFactory( $mockDb ),
1150 $mockCache,
1151 $this->getMockReadOnlyMode()
1152 );
1153
1154 $this->assertFalse(
1155 $store->addWatchBatchForUser(
1156 $this->getAnonUser(),
1157 [ new TitleValue( 0, 'Other_Page' ) ]
1158 )
1159 );
1160 }
1161
1162 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1163 $user = $this->getMockNonAnonUserWithId( 1 );
1164 $mockDb = $this->getMockDb();
1165 $mockDb->expects( $this->never() )
1166 ->method( 'insert' );
1167
1168 $mockCache = $this->getMockCache();
1169 $mockCache->expects( $this->never() )
1170 ->method( 'delete' );
1171
1172 $store = $this->newWatchedItemStore(
1173 $this->getMockLBFactory( $mockDb ),
1174 $mockCache,
1175 $this->getMockReadOnlyMode()
1176 );
1177
1178 $this->assertTrue(
1179 $store->addWatchBatchForUser( $user, [] )
1180 );
1181 }
1182
1183 public function testLoadWatchedItem_existingItem() {
1184 $mockDb = $this->getMockDb();
1185 $mockDb->expects( $this->once() )
1186 ->method( 'selectRow' )
1187 ->with(
1188 'watchlist',
1189 'wl_notificationtimestamp',
1190 [
1191 'wl_user' => 1,
1192 'wl_namespace' => 0,
1193 'wl_title' => 'SomeDbKey',
1194 ]
1195 )
1196 ->will( $this->returnValue(
1197 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1198 ) );
1199
1200 $mockCache = $this->getMockCache();
1201 $mockCache->expects( $this->once() )
1202 ->method( 'set' )
1203 ->with(
1204 '0:SomeDbKey:1'
1205 );
1206
1207 $store = $this->newWatchedItemStore(
1208 $this->getMockLBFactory( $mockDb ),
1209 $mockCache,
1210 $this->getMockReadOnlyMode()
1211 );
1212
1213 $watchedItem = $store->loadWatchedItem(
1214 $this->getMockNonAnonUserWithId( 1 ),
1215 new TitleValue( 0, 'SomeDbKey' )
1216 );
1217 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1218 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1219 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1220 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1221 }
1222
1223 public function testLoadWatchedItem_noItem() {
1224 $mockDb = $this->getMockDb();
1225 $mockDb->expects( $this->once() )
1226 ->method( 'selectRow' )
1227 ->with(
1228 'watchlist',
1229 'wl_notificationtimestamp',
1230 [
1231 'wl_user' => 1,
1232 'wl_namespace' => 0,
1233 'wl_title' => 'SomeDbKey',
1234 ]
1235 )
1236 ->will( $this->returnValue( [] ) );
1237
1238 $mockCache = $this->getMockCache();
1239 $mockCache->expects( $this->never() )->method( 'get' );
1240 $mockCache->expects( $this->never() )->method( 'delete' );
1241
1242 $store = $this->newWatchedItemStore(
1243 $this->getMockLBFactory( $mockDb ),
1244 $mockCache,
1245 $this->getMockReadOnlyMode()
1246 );
1247
1248 $this->assertFalse(
1249 $store->loadWatchedItem(
1250 $this->getMockNonAnonUserWithId( 1 ),
1251 new TitleValue( 0, 'SomeDbKey' )
1252 )
1253 );
1254 }
1255
1256 public function testLoadWatchedItem_anonymousUser() {
1257 $mockDb = $this->getMockDb();
1258 $mockDb->expects( $this->never() )
1259 ->method( 'selectRow' );
1260
1261 $mockCache = $this->getMockCache();
1262 $mockCache->expects( $this->never() )->method( 'get' );
1263 $mockCache->expects( $this->never() )->method( 'delete' );
1264
1265 $store = $this->newWatchedItemStore(
1266 $this->getMockLBFactory( $mockDb ),
1267 $mockCache,
1268 $this->getMockReadOnlyMode()
1269 );
1270
1271 $this->assertFalse(
1272 $store->loadWatchedItem(
1273 $this->getAnonUser(),
1274 new TitleValue( 0, 'SomeDbKey' )
1275 )
1276 );
1277 }
1278
1279 public function testRemoveWatch_existingItem() {
1280 $mockDb = $this->getMockDb();
1281 $mockDb->expects( $this->once() )
1282 ->method( 'delete' )
1283 ->withConsecutive(
1284 [
1285 'watchlist',
1286 [
1287 'wl_user' => 1,
1288 'wl_namespace' => 0,
1289 'wl_title' => [ 'SomeDbKey' ],
1290 ],
1291 ],
1292 [
1293 'watchlist',
1294 [
1295 'wl_user' => 1,
1296 'wl_namespace' => 1,
1297 'wl_title' => [ 'SomeDbKey' ],
1298 ]
1299 ]
1300 );
1301 $mockDb->expects( $this->exactly( 1 ) )
1302 ->method( 'affectedRows' )
1303 ->willReturn( 2 );
1304
1305 $mockCache = $this->getMockCache();
1306 $mockCache->expects( $this->never() )->method( 'get' );
1307 $mockCache->expects( $this->once() )
1308 ->method( 'delete' )
1309 ->withConsecutive(
1310 [ '0:SomeDbKey:1' ],
1311 [ '1:SomeDbKey:1' ]
1312 );
1313
1314 $store = $this->newWatchedItemStore(
1315 $this->getMockLBFactory( $mockDb ),
1316 $mockCache,
1317 $this->getMockReadOnlyMode()
1318 );
1319
1320 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1321 $this->assertTrue(
1322 $store->removeWatch(
1323 $this->getMockNonAnonUserWithId( 1 ),
1324 Title::newFromTitleValue( $titleValue )
1325 )
1326 );
1327 }
1328
1329 public function testRemoveWatch_noItem() {
1330 $mockDb = $this->getMockDb();
1331 $mockDb->expects( $this->once() )
1332 ->method( 'delete' )
1333 ->withConsecutive(
1334 [
1335 'watchlist',
1336 [
1337 'wl_user' => 1,
1338 'wl_namespace' => 0,
1339 'wl_title' => [ 'SomeDbKey' ],
1340 ]
1341 ],
1342 [
1343 'watchlist',
1344 [
1345 'wl_user' => 1,
1346 'wl_namespace' => 1,
1347 'wl_title' => [ 'SomeDbKey' ],
1348 ]
1349 ]
1350 );
1351
1352 $mockDb->expects( $this->once() )
1353 ->method( 'affectedRows' )
1354 ->willReturn( 0 );
1355
1356 $mockCache = $this->getMockCache();
1357 $mockCache->expects( $this->never() )->method( 'get' );
1358 $mockCache->expects( $this->once() )
1359 ->method( 'delete' )
1360 ->withConsecutive(
1361 [ '0:SomeDbKey:1' ],
1362 [ '1:SomeDbKey:1' ]
1363 );
1364
1365 $store = $this->newWatchedItemStore(
1366 $this->getMockLBFactory( $mockDb ),
1367 $mockCache,
1368 $this->getMockReadOnlyMode()
1369 );
1370
1371 $titleValue = new TitleValue( 0, 'SomeDbKey' );
1372 $this->assertFalse(
1373 $store->removeWatch(
1374 $this->getMockNonAnonUserWithId( 1 ),
1375 Title::newFromTitleValue( $titleValue )
1376 )
1377 );
1378 }
1379
1380 public function testRemoveWatch_anonymousUser() {
1381 $mockDb = $this->getMockDb();
1382 $mockDb->expects( $this->never() )
1383 ->method( 'delete' );
1384
1385 $mockCache = $this->getMockCache();
1386 $mockCache->expects( $this->never() )->method( 'get' );
1387 $mockCache->expects( $this->never() )
1388 ->method( 'delete' );
1389
1390 $store = $this->newWatchedItemStore(
1391 $this->getMockLBFactory( $mockDb ),
1392 $mockCache,
1393 $this->getMockReadOnlyMode()
1394 );
1395
1396 $this->assertFalse(
1397 $store->removeWatch(
1398 $this->getAnonUser(),
1399 new TitleValue( 0, 'SomeDbKey' )
1400 )
1401 );
1402 }
1403
1404 public function testGetWatchedItem_existingItem() {
1405 $mockDb = $this->getMockDb();
1406 $mockDb->expects( $this->once() )
1407 ->method( 'selectRow' )
1408 ->with(
1409 'watchlist',
1410 'wl_notificationtimestamp',
1411 [
1412 'wl_user' => 1,
1413 'wl_namespace' => 0,
1414 'wl_title' => 'SomeDbKey',
1415 ]
1416 )
1417 ->will( $this->returnValue(
1418 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1419 ) );
1420
1421 $mockCache = $this->getMockCache();
1422 $mockCache->expects( $this->never() )->method( 'delete' );
1423 $mockCache->expects( $this->once() )
1424 ->method( 'get' )
1425 ->with(
1426 '0:SomeDbKey:1'
1427 )
1428 ->will( $this->returnValue( null ) );
1429 $mockCache->expects( $this->once() )
1430 ->method( 'set' )
1431 ->with(
1432 '0:SomeDbKey:1'
1433 );
1434
1435 $store = $this->newWatchedItemStore(
1436 $this->getMockLBFactory( $mockDb ),
1437 $mockCache,
1438 $this->getMockReadOnlyMode()
1439 );
1440
1441 $watchedItem = $store->getWatchedItem(
1442 $this->getMockNonAnonUserWithId( 1 ),
1443 new TitleValue( 0, 'SomeDbKey' )
1444 );
1445 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1446 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1447 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1448 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1449 }
1450
1451 public function testGetWatchedItem_cachedItem() {
1452 $mockDb = $this->getMockDb();
1453 $mockDb->expects( $this->never() )
1454 ->method( 'selectRow' );
1455
1456 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1457 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1458 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1459
1460 $mockCache = $this->getMockCache();
1461 $mockCache->expects( $this->never() )->method( 'delete' );
1462 $mockCache->expects( $this->never() )->method( 'set' );
1463 $mockCache->expects( $this->once() )
1464 ->method( 'get' )
1465 ->with(
1466 '0:SomeDbKey:1'
1467 )
1468 ->will( $this->returnValue( $cachedItem ) );
1469
1470 $store = $this->newWatchedItemStore(
1471 $this->getMockLBFactory( $mockDb ),
1472 $mockCache,
1473 $this->getMockReadOnlyMode()
1474 );
1475
1476 $this->assertEquals(
1477 $cachedItem,
1478 $store->getWatchedItem(
1479 $mockUser,
1480 $linkTarget
1481 )
1482 );
1483 }
1484
1485 public function testGetWatchedItem_noItem() {
1486 $mockDb = $this->getMockDb();
1487 $mockDb->expects( $this->once() )
1488 ->method( 'selectRow' )
1489 ->with(
1490 'watchlist',
1491 'wl_notificationtimestamp',
1492 [
1493 'wl_user' => 1,
1494 'wl_namespace' => 0,
1495 'wl_title' => 'SomeDbKey',
1496 ]
1497 )
1498 ->will( $this->returnValue( [] ) );
1499
1500 $mockCache = $this->getMockCache();
1501 $mockCache->expects( $this->never() )->method( 'set' );
1502 $mockCache->expects( $this->never() )->method( 'delete' );
1503 $mockCache->expects( $this->once() )
1504 ->method( 'get' )
1505 ->with( '0:SomeDbKey:1' )
1506 ->will( $this->returnValue( false ) );
1507
1508 $store = $this->newWatchedItemStore(
1509 $this->getMockLBFactory( $mockDb ),
1510 $mockCache,
1511 $this->getMockReadOnlyMode()
1512 );
1513
1514 $this->assertFalse(
1515 $store->getWatchedItem(
1516 $this->getMockNonAnonUserWithId( 1 ),
1517 new TitleValue( 0, 'SomeDbKey' )
1518 )
1519 );
1520 }
1521
1522 public function testGetWatchedItem_anonymousUser() {
1523 $mockDb = $this->getMockDb();
1524 $mockDb->expects( $this->never() )
1525 ->method( 'selectRow' );
1526
1527 $mockCache = $this->getMockCache();
1528 $mockCache->expects( $this->never() )->method( 'set' );
1529 $mockCache->expects( $this->never() )->method( 'get' );
1530 $mockCache->expects( $this->never() )->method( 'delete' );
1531
1532 $store = $this->newWatchedItemStore(
1533 $this->getMockLBFactory( $mockDb ),
1534 $mockCache,
1535 $this->getMockReadOnlyMode()
1536 );
1537
1538 $this->assertFalse(
1539 $store->getWatchedItem(
1540 $this->getAnonUser(),
1541 new TitleValue( 0, 'SomeDbKey' )
1542 )
1543 );
1544 }
1545
1546 public function testGetWatchedItemsForUser() {
1547 $mockDb = $this->getMockDb();
1548 $mockDb->expects( $this->once() )
1549 ->method( 'select' )
1550 ->with(
1551 'watchlist',
1552 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1553 [ 'wl_user' => 1 ]
1554 )
1555 ->will( $this->returnValue( [
1556 $this->getFakeRow( [
1557 'wl_namespace' => 0,
1558 'wl_title' => 'Foo1',
1559 'wl_notificationtimestamp' => '20151212010101',
1560 ] ),
1561 $this->getFakeRow( [
1562 'wl_namespace' => 1,
1563 'wl_title' => 'Foo2',
1564 'wl_notificationtimestamp' => null,
1565 ] ),
1566 ] ) );
1567
1568 $mockCache = $this->getMockCache();
1569 $mockCache->expects( $this->never() )->method( 'delete' );
1570 $mockCache->expects( $this->never() )->method( 'get' );
1571 $mockCache->expects( $this->never() )->method( 'set' );
1572
1573 $store = $this->newWatchedItemStore(
1574 $this->getMockLBFactory( $mockDb ),
1575 $mockCache,
1576 $this->getMockReadOnlyMode()
1577 );
1578 $user = $this->getMockNonAnonUserWithId( 1 );
1579
1580 $watchedItems = $store->getWatchedItemsForUser( $user );
1581
1582 $this->assertInternalType( 'array', $watchedItems );
1583 $this->assertCount( 2, $watchedItems );
1584 foreach ( $watchedItems as $watchedItem ) {
1585 $this->assertInstanceOf( WatchedItem::class, $watchedItem );
1586 }
1587 $this->assertEquals(
1588 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1589 $watchedItems[0]
1590 );
1591 $this->assertEquals(
1592 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1593 $watchedItems[1]
1594 );
1595 }
1596
1597 public function provideDbTypes() {
1598 return [
1599 [ false, DB_REPLICA ],
1600 [ true, DB_MASTER ],
1601 ];
1602 }
1603
1604 /**
1605 * @dataProvider provideDbTypes
1606 */
1607 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1608 $mockDb = $this->getMockDb();
1609 $mockCache = $this->getMockCache();
1610 $mockLoadBalancer = $this->getMockLBFactory( $mockDb, $dbType );
1611 $user = $this->getMockNonAnonUserWithId( 1 );
1612
1613 $mockDb->expects( $this->once() )
1614 ->method( 'select' )
1615 ->with(
1616 'watchlist',
1617 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1618 [ 'wl_user' => 1 ],
1619 $this->isType( 'string' ),
1620 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1621 )
1622 ->will( $this->returnValue( [] ) );
1623
1624 $store = $this->newWatchedItemStore(
1625 $mockLoadBalancer,
1626 $mockCache,
1627 $this->getMockReadOnlyMode()
1628 );
1629
1630 $watchedItems = $store->getWatchedItemsForUser(
1631 $user,
1632 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1633 );
1634 $this->assertEquals( [], $watchedItems );
1635 }
1636
1637 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1638 $store = $this->newWatchedItemStore(
1639 $this->getMockLBFactory( $this->getMockDb() ),
1640 $this->getMockCache(),
1641 $this->getMockReadOnlyMode()
1642 );
1643
1644 $this->setExpectedException( InvalidArgumentException::class );
1645 $store->getWatchedItemsForUser(
1646 $this->getMockNonAnonUserWithId( 1 ),
1647 [ 'sort' => 'foo' ]
1648 );
1649 }
1650
1651 public function testIsWatchedItem_existingItem() {
1652 $mockDb = $this->getMockDb();
1653 $mockDb->expects( $this->once() )
1654 ->method( 'selectRow' )
1655 ->with(
1656 'watchlist',
1657 'wl_notificationtimestamp',
1658 [
1659 'wl_user' => 1,
1660 'wl_namespace' => 0,
1661 'wl_title' => 'SomeDbKey',
1662 ]
1663 )
1664 ->will( $this->returnValue(
1665 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1666 ) );
1667
1668 $mockCache = $this->getMockCache();
1669 $mockCache->expects( $this->never() )->method( 'delete' );
1670 $mockCache->expects( $this->once() )
1671 ->method( 'get' )
1672 ->with( '0:SomeDbKey:1' )
1673 ->will( $this->returnValue( false ) );
1674 $mockCache->expects( $this->once() )
1675 ->method( 'set' )
1676 ->with(
1677 '0:SomeDbKey:1'
1678 );
1679
1680 $store = $this->newWatchedItemStore(
1681 $this->getMockLBFactory( $mockDb ),
1682 $mockCache,
1683 $this->getMockReadOnlyMode()
1684 );
1685
1686 $this->assertTrue(
1687 $store->isWatched(
1688 $this->getMockNonAnonUserWithId( 1 ),
1689 new TitleValue( 0, 'SomeDbKey' )
1690 )
1691 );
1692 }
1693
1694 public function testIsWatchedItem_noItem() {
1695 $mockDb = $this->getMockDb();
1696 $mockDb->expects( $this->once() )
1697 ->method( 'selectRow' )
1698 ->with(
1699 'watchlist',
1700 'wl_notificationtimestamp',
1701 [
1702 'wl_user' => 1,
1703 'wl_namespace' => 0,
1704 'wl_title' => 'SomeDbKey',
1705 ]
1706 )
1707 ->will( $this->returnValue( [] ) );
1708
1709 $mockCache = $this->getMockCache();
1710 $mockCache->expects( $this->never() )->method( 'set' );
1711 $mockCache->expects( $this->never() )->method( 'delete' );
1712 $mockCache->expects( $this->once() )
1713 ->method( 'get' )
1714 ->with( '0:SomeDbKey:1' )
1715 ->will( $this->returnValue( false ) );
1716
1717 $store = $this->newWatchedItemStore(
1718 $this->getMockLBFactory( $mockDb ),
1719 $mockCache,
1720 $this->getMockReadOnlyMode()
1721 );
1722
1723 $this->assertFalse(
1724 $store->isWatched(
1725 $this->getMockNonAnonUserWithId( 1 ),
1726 new TitleValue( 0, 'SomeDbKey' )
1727 )
1728 );
1729 }
1730
1731 public function testIsWatchedItem_anonymousUser() {
1732 $mockDb = $this->getMockDb();
1733 $mockDb->expects( $this->never() )
1734 ->method( 'selectRow' );
1735
1736 $mockCache = $this->getMockCache();
1737 $mockCache->expects( $this->never() )->method( 'set' );
1738 $mockCache->expects( $this->never() )->method( 'get' );
1739 $mockCache->expects( $this->never() )->method( 'delete' );
1740
1741 $store = $this->newWatchedItemStore(
1742 $this->getMockLBFactory( $mockDb ),
1743 $mockCache,
1744 $this->getMockReadOnlyMode()
1745 );
1746
1747 $this->assertFalse(
1748 $store->isWatched(
1749 $this->getAnonUser(),
1750 new TitleValue( 0, 'SomeDbKey' )
1751 )
1752 );
1753 }
1754
1755 public function testGetNotificationTimestampsBatch() {
1756 $targets = [
1757 new TitleValue( 0, 'SomeDbKey' ),
1758 new TitleValue( 1, 'AnotherDbKey' ),
1759 ];
1760
1761 $mockDb = $this->getMockDb();
1762 $dbResult = [
1763 $this->getFakeRow( [
1764 'wl_namespace' => '0',
1765 'wl_title' => 'SomeDbKey',
1766 'wl_notificationtimestamp' => '20151212010101',
1767 ] ),
1768 $this->getFakeRow(
1769 [
1770 'wl_namespace' => '1',
1771 'wl_title' => 'AnotherDbKey',
1772 'wl_notificationtimestamp' => null,
1773 ]
1774 ),
1775 ];
1776
1777 $mockDb->expects( $this->once() )
1778 ->method( 'makeWhereFrom2d' )
1779 ->with(
1780 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1781 $this->isType( 'string' ),
1782 $this->isType( 'string' )
1783 )
1784 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1785 $mockDb->expects( $this->once() )
1786 ->method( 'select' )
1787 ->with(
1788 'watchlist',
1789 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1790 [
1791 'makeWhereFrom2d return value',
1792 'wl_user' => 1
1793 ],
1794 $this->isType( 'string' )
1795 )
1796 ->will( $this->returnValue( $dbResult ) );
1797
1798 $mockCache = $this->getMockCache();
1799 $mockCache->expects( $this->exactly( 2 ) )
1800 ->method( 'get' )
1801 ->withConsecutive(
1802 [ '0:SomeDbKey:1' ],
1803 [ '1:AnotherDbKey:1' ]
1804 )
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->getMockLBFactory( $mockDb ),
1811 $mockCache,
1812 $this->getMockReadOnlyMode()
1813 );
1814
1815 $this->assertEquals(
1816 [
1817 0 => [ 'SomeDbKey' => '20151212010101', ],
1818 1 => [ 'AnotherDbKey' => null, ],
1819 ],
1820 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1821 );
1822 }
1823
1824 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1825 $targets = [
1826 new TitleValue( 0, 'OtherDbKey' ),
1827 ];
1828
1829 $mockDb = $this->getMockDb();
1830
1831 $mockDb->expects( $this->once() )
1832 ->method( 'makeWhereFrom2d' )
1833 ->with(
1834 [ [ 'OtherDbKey' => 1 ] ],
1835 $this->isType( 'string' ),
1836 $this->isType( 'string' )
1837 )
1838 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1839 $mockDb->expects( $this->once() )
1840 ->method( 'select' )
1841 ->with(
1842 'watchlist',
1843 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1844 [
1845 'makeWhereFrom2d return value',
1846 'wl_user' => 1
1847 ],
1848 $this->isType( 'string' )
1849 )
1850 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1851
1852 $mockCache = $this->getMockCache();
1853 $mockCache->expects( $this->once() )
1854 ->method( 'get' )
1855 ->with( '0:OtherDbKey:1' )
1856 ->will( $this->returnValue( null ) );
1857 $mockCache->expects( $this->never() )->method( 'set' );
1858 $mockCache->expects( $this->never() )->method( 'delete' );
1859
1860 $store = $this->newWatchedItemStore(
1861 $this->getMockLBFactory( $mockDb ),
1862 $mockCache,
1863 $this->getMockReadOnlyMode()
1864 );
1865
1866 $this->assertEquals(
1867 [
1868 0 => [ 'OtherDbKey' => false, ],
1869 ],
1870 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1871 );
1872 }
1873
1874 public function testGetNotificationTimestampsBatch_cachedItem() {
1875 $targets = [
1876 new TitleValue( 0, 'SomeDbKey' ),
1877 new TitleValue( 1, 'AnotherDbKey' ),
1878 ];
1879
1880 $user = $this->getMockNonAnonUserWithId( 1 );
1881 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1882
1883 $mockDb = $this->getMockDb();
1884
1885 $mockDb->expects( $this->once() )
1886 ->method( 'makeWhereFrom2d' )
1887 ->with(
1888 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1889 $this->isType( 'string' ),
1890 $this->isType( 'string' )
1891 )
1892 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1893 $mockDb->expects( $this->once() )
1894 ->method( 'select' )
1895 ->with(
1896 'watchlist',
1897 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1898 [
1899 'makeWhereFrom2d return value',
1900 'wl_user' => 1
1901 ],
1902 $this->isType( 'string' )
1903 )
1904 ->will( $this->returnValue( [
1905 $this->getFakeRow(
1906 [ 'wl_namespace' => '1', 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1907 )
1908 ] ) );
1909
1910 $mockCache = $this->getMockCache();
1911 $mockCache->expects( $this->at( 1 ) )
1912 ->method( 'get' )
1913 ->with( '0:SomeDbKey:1' )
1914 ->will( $this->returnValue( $cachedItem ) );
1915 $mockCache->expects( $this->at( 3 ) )
1916 ->method( 'get' )
1917 ->with( '1:AnotherDbKey:1' )
1918 ->will( $this->returnValue( null ) );
1919 $mockCache->expects( $this->never() )->method( 'set' );
1920 $mockCache->expects( $this->never() )->method( 'delete' );
1921
1922 $store = $this->newWatchedItemStore(
1923 $this->getMockLBFactory( $mockDb ),
1924 $mockCache,
1925 $this->getMockReadOnlyMode()
1926 );
1927
1928 $this->assertEquals(
1929 [
1930 0 => [ 'SomeDbKey' => '20151212010101', ],
1931 1 => [ 'AnotherDbKey' => null, ],
1932 ],
1933 $store->getNotificationTimestampsBatch( $user, $targets )
1934 );
1935 }
1936
1937 public function testGetNotificationTimestampsBatch_allItemsCached() {
1938 $targets = [
1939 new TitleValue( 0, 'SomeDbKey' ),
1940 new TitleValue( 1, 'AnotherDbKey' ),
1941 ];
1942
1943 $user = $this->getMockNonAnonUserWithId( 1 );
1944 $cachedItems = [
1945 new WatchedItem( $user, $targets[0], '20151212010101' ),
1946 new WatchedItem( $user, $targets[1], null ),
1947 ];
1948 $mockDb = $this->getMockDb();
1949 $mockDb->expects( $this->never() )->method( $this->anything() );
1950
1951 $mockCache = $this->getMockCache();
1952 $mockCache->expects( $this->at( 1 ) )
1953 ->method( 'get' )
1954 ->with( '0:SomeDbKey:1' )
1955 ->will( $this->returnValue( $cachedItems[0] ) );
1956 $mockCache->expects( $this->at( 3 ) )
1957 ->method( 'get' )
1958 ->with( '1:AnotherDbKey:1' )
1959 ->will( $this->returnValue( $cachedItems[1] ) );
1960 $mockCache->expects( $this->never() )->method( 'set' );
1961 $mockCache->expects( $this->never() )->method( 'delete' );
1962
1963 $store = $this->newWatchedItemStore(
1964 $this->getMockLBFactory( $mockDb ),
1965 $mockCache,
1966 $this->getMockReadOnlyMode()
1967 );
1968
1969 $this->assertEquals(
1970 [
1971 0 => [ 'SomeDbKey' => '20151212010101', ],
1972 1 => [ 'AnotherDbKey' => null, ],
1973 ],
1974 $store->getNotificationTimestampsBatch( $user, $targets )
1975 );
1976 }
1977
1978 public function testGetNotificationTimestampsBatch_anonymousUser() {
1979 $targets = [
1980 new TitleValue( 0, 'SomeDbKey' ),
1981 new TitleValue( 1, 'AnotherDbKey' ),
1982 ];
1983
1984 $mockDb = $this->getMockDb();
1985 $mockDb->expects( $this->never() )->method( $this->anything() );
1986
1987 $mockCache = $this->getMockCache();
1988 $mockCache->expects( $this->never() )->method( $this->anything() );
1989
1990 $store = $this->newWatchedItemStore(
1991 $this->getMockLBFactory( $mockDb ),
1992 $mockCache,
1993 $this->getMockReadOnlyMode()
1994 );
1995
1996 $this->assertEquals(
1997 [
1998 0 => [ 'SomeDbKey' => false, ],
1999 1 => [ 'AnotherDbKey' => false, ],
2000 ],
2001 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
2002 );
2003 }
2004
2005 public function testResetNotificationTimestamp_anonymousUser() {
2006 $mockDb = $this->getMockDb();
2007 $mockDb->expects( $this->never() )
2008 ->method( 'selectRow' );
2009
2010 $mockCache = $this->getMockCache();
2011 $mockCache->expects( $this->never() )->method( 'get' );
2012 $mockCache->expects( $this->never() )->method( 'set' );
2013 $mockCache->expects( $this->never() )->method( 'delete' );
2014
2015 $store = $this->newWatchedItemStore(
2016 $this->getMockLBFactory( $mockDb ),
2017 $mockCache,
2018 $this->getMockReadOnlyMode()
2019 );
2020
2021 $this->assertFalse(
2022 $store->resetNotificationTimestamp(
2023 $this->getAnonUser(),
2024 Title::newFromText( 'SomeDbKey' )
2025 )
2026 );
2027 }
2028
2029 public function testResetNotificationTimestamp_noItem() {
2030 $mockDb = $this->getMockDb();
2031 $mockDb->expects( $this->once() )
2032 ->method( 'selectRow' )
2033 ->with(
2034 'watchlist',
2035 'wl_notificationtimestamp',
2036 [
2037 'wl_user' => 1,
2038 'wl_namespace' => 0,
2039 'wl_title' => 'SomeDbKey',
2040 ]
2041 )
2042 ->will( $this->returnValue( [] ) );
2043
2044 $mockCache = $this->getMockCache();
2045 $mockCache->expects( $this->never() )->method( 'get' );
2046 $mockCache->expects( $this->never() )->method( 'set' );
2047 $mockCache->expects( $this->never() )->method( 'delete' );
2048
2049 $store = $this->newWatchedItemStore(
2050 $this->getMockLBFactory( $mockDb ),
2051 $mockCache,
2052 $this->getMockReadOnlyMode()
2053 );
2054
2055 $this->assertFalse(
2056 $store->resetNotificationTimestamp(
2057 $this->getMockNonAnonUserWithId( 1 ),
2058 Title::newFromText( 'SomeDbKey' )
2059 )
2060 );
2061 }
2062
2063 public function testResetNotificationTimestamp_item() {
2064 $user = $this->getMockNonAnonUserWithId( 1 );
2065 $title = Title::newFromText( 'SomeDbKey' );
2066
2067 $mockDb = $this->getMockDb();
2068 $mockDb->expects( $this->once() )
2069 ->method( 'selectRow' )
2070 ->with(
2071 'watchlist',
2072 'wl_notificationtimestamp',
2073 [
2074 'wl_user' => 1,
2075 'wl_namespace' => 0,
2076 'wl_title' => 'SomeDbKey',
2077 ]
2078 )
2079 ->will( $this->returnValue(
2080 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2081 ) );
2082
2083 $mockCache = $this->getMockCache();
2084 $mockCache->expects( $this->never() )->method( 'get' );
2085 $mockCache->expects( $this->once() )
2086 ->method( 'set' )
2087 ->with(
2088 '0:SomeDbKey:1',
2089 $this->isInstanceOf( WatchedItem::class )
2090 );
2091 $mockCache->expects( $this->once() )
2092 ->method( 'delete' )
2093 ->with( '0:SomeDbKey:1' );
2094
2095 $store = $this->newWatchedItemStore(
2096 $this->getMockLBFactory( $mockDb ),
2097 $mockCache,
2098 $this->getMockReadOnlyMode()
2099 );
2100
2101 // Note: This does not actually assert the job is correct
2102 $callableCallCounter = 0;
2103 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2104 $callableCallCounter++;
2105 $this->assertInternalType( 'callable', $callable );
2106 };
2107 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2108
2109 $this->assertTrue(
2110 $store->resetNotificationTimestamp(
2111 $user,
2112 $title
2113 )
2114 );
2115 $this->assertEquals( 1, $callableCallCounter );
2116
2117 ScopedCallback::consume( $scopedOverride );
2118 }
2119
2120 public function testResetNotificationTimestamp_noItemForced() {
2121 $user = $this->getMockNonAnonUserWithId( 1 );
2122 $title = Title::newFromText( 'SomeDbKey' );
2123
2124 $mockDb = $this->getMockDb();
2125 $mockDb->expects( $this->never() )
2126 ->method( 'selectRow' );
2127
2128 $mockCache = $this->getMockCache();
2129 $mockCache->expects( $this->never() )->method( 'get' );
2130 $mockCache->expects( $this->never() )->method( 'set' );
2131 $mockCache->expects( $this->once() )
2132 ->method( 'delete' )
2133 ->with( '0:SomeDbKey:1' );
2134
2135 $store = $this->newWatchedItemStore(
2136 $this->getMockLBFactory( $mockDb ),
2137 $mockCache,
2138 $this->getMockReadOnlyMode()
2139 );
2140
2141 // Note: This does not actually assert the job is correct
2142 $callableCallCounter = 0;
2143 $mockCallback = function ( $callable ) use ( &$callableCallCounter ) {
2144 $callableCallCounter++;
2145 $this->assertInternalType( 'callable', $callable );
2146 };
2147 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
2148
2149 $this->assertTrue(
2150 $store->resetNotificationTimestamp(
2151 $user,
2152 $title,
2153 'force'
2154 )
2155 );
2156 $this->assertEquals( 1, $callableCallCounter );
2157
2158 ScopedCallback::consume( $scopedOverride );
2159 }
2160
2161 /**
2162 * @param string $text
2163 * @param int $ns
2164 *
2165 * @return PHPUnit_Framework_MockObject_MockObject|Title
2166 */
2167 private function getMockTitle( $text, $ns = 0 ) {
2168 $title = $this->createMock( Title::class );
2169 $title->expects( $this->any() )
2170 ->method( 'getText' )
2171 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2172 $title->expects( $this->any() )
2173 ->method( 'getDbKey' )
2174 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
2175 $title->expects( $this->any() )
2176 ->method( 'getNamespace' )
2177 ->will( $this->returnValue( $ns ) );
2178 return $title;
2179 }
2180
2181 private function verifyCallbackJob(
2182 $callback,
2183 LinkTarget $expectedTitle,
2184 $expectedUserId,
2185 callable $notificationTimestampCondition
2186 ) {
2187 $this->assertInternalType( 'callable', $callback );
2188
2189 $callbackReflector = new ReflectionFunction( $callback );
2190 $vars = $callbackReflector->getStaticVariables();
2191 $this->assertArrayHasKey( 'job', $vars );
2192 $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
2193
2194 /** @var ActivityUpdateJob $job */
2195 $job = $vars['job'];
2196 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2197 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2198
2199 $jobParams = $job->getParams();
2200 $this->assertArrayHasKey( 'type', $jobParams );
2201 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2202 $this->assertArrayHasKey( 'userid', $jobParams );
2203 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2204 $this->assertArrayHasKey( 'notifTime', $jobParams );
2205 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2206 }
2207
2208 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
2209 $user = $this->getMockNonAnonUserWithId( 1 );
2210 $oldid = 22;
2211 $title = $this->getMockTitle( 'SomeTitle' );
2212 $title->expects( $this->once() )
2213 ->method( 'getNextRevisionID' )
2214 ->with( $oldid )
2215 ->will( $this->returnValue( false ) );
2216
2217 $mockDb = $this->getMockDb();
2218 $mockDb->expects( $this->never() )
2219 ->method( 'selectRow' );
2220
2221 $mockCache = $this->getMockCache();
2222 $mockCache->expects( $this->never() )->method( 'get' );
2223 $mockCache->expects( $this->never() )->method( 'set' );
2224 $mockCache->expects( $this->once() )
2225 ->method( 'delete' )
2226 ->with( '0:SomeTitle:1' );
2227
2228 $store = $this->newWatchedItemStore(
2229 $this->getMockLBFactory( $mockDb ),
2230 $mockCache,
2231 $this->getMockReadOnlyMode()
2232 );
2233
2234 $callableCallCounter = 0;
2235 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2236 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2237 $callableCallCounter++;
2238 $this->verifyCallbackJob(
2239 $callable,
2240 $title,
2241 $user->getId(),
2242 function ( $time ) {
2243 return $time === null;
2244 }
2245 );
2246 }
2247 );
2248
2249 $this->assertTrue(
2250 $store->resetNotificationTimestamp(
2251 $user,
2252 $title,
2253 'force',
2254 $oldid
2255 )
2256 );
2257 $this->assertEquals( 1, $callableCallCounter );
2258
2259 ScopedCallback::consume( $scopedOverride );
2260 }
2261
2262 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2263 $user = $this->getMockNonAnonUserWithId( 1 );
2264 $oldid = 22;
2265 $title = $this->getMockTitle( 'SomeDbKey' );
2266 $title->expects( $this->once() )
2267 ->method( 'getNextRevisionID' )
2268 ->with( $oldid )
2269 ->will( $this->returnValue( 33 ) );
2270
2271 $mockDb = $this->getMockDb();
2272 $mockDb->expects( $this->once() )
2273 ->method( 'selectRow' )
2274 ->with(
2275 'watchlist',
2276 'wl_notificationtimestamp',
2277 [
2278 'wl_user' => 1,
2279 'wl_namespace' => 0,
2280 'wl_title' => 'SomeDbKey',
2281 ]
2282 )
2283 ->will( $this->returnValue(
2284 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2285 ) );
2286
2287 $mockCache = $this->getMockCache();
2288 $mockCache->expects( $this->never() )->method( 'get' );
2289 $mockCache->expects( $this->once() )
2290 ->method( 'set' )
2291 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2292 $mockCache->expects( $this->once() )
2293 ->method( 'delete' )
2294 ->with( '0:SomeDbKey:1' );
2295
2296 $store = $this->newWatchedItemStore(
2297 $this->getMockLBFactory( $mockDb ),
2298 $mockCache,
2299 $this->getMockReadOnlyMode()
2300 );
2301
2302 $addUpdateCallCounter = 0;
2303 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2304 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2305 $addUpdateCallCounter++;
2306 $this->verifyCallbackJob(
2307 $callable,
2308 $title,
2309 $user->getId(),
2310 function ( $time ) {
2311 return $time !== null && $time > '20151212010101';
2312 }
2313 );
2314 }
2315 );
2316
2317 $getTimestampCallCounter = 0;
2318 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2319 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2320 $getTimestampCallCounter++;
2321 $this->assertEquals( $title, $titleParam );
2322 $this->assertEquals( $oldid, $oldidParam );
2323 }
2324 );
2325
2326 $this->assertTrue(
2327 $store->resetNotificationTimestamp(
2328 $user,
2329 $title,
2330 'force',
2331 $oldid
2332 )
2333 );
2334 $this->assertEquals( 1, $addUpdateCallCounter );
2335 $this->assertEquals( 1, $getTimestampCallCounter );
2336
2337 ScopedCallback::consume( $scopedOverrideDeferred );
2338 ScopedCallback::consume( $scopedOverrideRevision );
2339 }
2340
2341 public function testResetNotificationTimestamp_notWatchedPageForced() {
2342 $user = $this->getMockNonAnonUserWithId( 1 );
2343 $oldid = 22;
2344 $title = $this->getMockTitle( 'SomeDbKey' );
2345 $title->expects( $this->once() )
2346 ->method( 'getNextRevisionID' )
2347 ->with( $oldid )
2348 ->will( $this->returnValue( 33 ) );
2349
2350 $mockDb = $this->getMockDb();
2351 $mockDb->expects( $this->once() )
2352 ->method( 'selectRow' )
2353 ->with(
2354 'watchlist',
2355 'wl_notificationtimestamp',
2356 [
2357 'wl_user' => 1,
2358 'wl_namespace' => 0,
2359 'wl_title' => 'SomeDbKey',
2360 ]
2361 )
2362 ->will( $this->returnValue( false ) );
2363
2364 $mockCache = $this->getMockCache();
2365 $mockCache->expects( $this->never() )->method( 'get' );
2366 $mockCache->expects( $this->never() )->method( 'set' );
2367 $mockCache->expects( $this->once() )
2368 ->method( 'delete' )
2369 ->with( '0:SomeDbKey:1' );
2370
2371 $store = $this->newWatchedItemStore(
2372 $this->getMockLBFactory( $mockDb ),
2373 $mockCache,
2374 $this->getMockReadOnlyMode()
2375 );
2376
2377 $callableCallCounter = 0;
2378 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2379 function ( $callable ) use ( &$callableCallCounter, $title, $user ) {
2380 $callableCallCounter++;
2381 $this->verifyCallbackJob(
2382 $callable,
2383 $title,
2384 $user->getId(),
2385 function ( $time ) {
2386 return $time === null;
2387 }
2388 );
2389 }
2390 );
2391
2392 $this->assertTrue(
2393 $store->resetNotificationTimestamp(
2394 $user,
2395 $title,
2396 'force',
2397 $oldid
2398 )
2399 );
2400 $this->assertEquals( 1, $callableCallCounter );
2401
2402 ScopedCallback::consume( $scopedOverride );
2403 }
2404
2405 public function testResetNotificationTimestamp_futureNotificationTimestampForced() {
2406 $user = $this->getMockNonAnonUserWithId( 1 );
2407 $oldid = 22;
2408 $title = $this->getMockTitle( 'SomeDbKey' );
2409 $title->expects( $this->once() )
2410 ->method( 'getNextRevisionID' )
2411 ->with( $oldid )
2412 ->will( $this->returnValue( 33 ) );
2413
2414 $mockDb = $this->getMockDb();
2415 $mockDb->expects( $this->once() )
2416 ->method( 'selectRow' )
2417 ->with(
2418 'watchlist',
2419 'wl_notificationtimestamp',
2420 [
2421 'wl_user' => 1,
2422 'wl_namespace' => 0,
2423 'wl_title' => 'SomeDbKey',
2424 ]
2425 )
2426 ->will( $this->returnValue(
2427 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2428 ) );
2429
2430 $mockCache = $this->getMockCache();
2431 $mockCache->expects( $this->never() )->method( 'get' );
2432 $mockCache->expects( $this->once() )
2433 ->method( 'set' )
2434 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2435 $mockCache->expects( $this->once() )
2436 ->method( 'delete' )
2437 ->with( '0:SomeDbKey:1' );
2438
2439 $store = $this->newWatchedItemStore(
2440 $this->getMockLBFactory( $mockDb ),
2441 $mockCache,
2442 $this->getMockReadOnlyMode()
2443 );
2444
2445 $addUpdateCallCounter = 0;
2446 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2447 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2448 $addUpdateCallCounter++;
2449 $this->verifyCallbackJob(
2450 $callable,
2451 $title,
2452 $user->getId(),
2453 function ( $time ) {
2454 return $time === '30151212010101';
2455 }
2456 );
2457 }
2458 );
2459
2460 $getTimestampCallCounter = 0;
2461 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2462 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2463 $getTimestampCallCounter++;
2464 $this->assertEquals( $title, $titleParam );
2465 $this->assertEquals( $oldid, $oldidParam );
2466 }
2467 );
2468
2469 $this->assertTrue(
2470 $store->resetNotificationTimestamp(
2471 $user,
2472 $title,
2473 'force',
2474 $oldid
2475 )
2476 );
2477 $this->assertEquals( 1, $addUpdateCallCounter );
2478 $this->assertEquals( 1, $getTimestampCallCounter );
2479
2480 ScopedCallback::consume( $scopedOverrideDeferred );
2481 ScopedCallback::consume( $scopedOverrideRevision );
2482 }
2483
2484 public function testResetNotificationTimestamp_futureNotificationTimestampNotForced() {
2485 $user = $this->getMockNonAnonUserWithId( 1 );
2486 $oldid = 22;
2487 $title = $this->getMockTitle( 'SomeDbKey' );
2488 $title->expects( $this->once() )
2489 ->method( 'getNextRevisionID' )
2490 ->with( $oldid )
2491 ->will( $this->returnValue( 33 ) );
2492
2493 $mockDb = $this->getMockDb();
2494 $mockDb->expects( $this->once() )
2495 ->method( 'selectRow' )
2496 ->with(
2497 'watchlist',
2498 'wl_notificationtimestamp',
2499 [
2500 'wl_user' => 1,
2501 'wl_namespace' => 0,
2502 'wl_title' => 'SomeDbKey',
2503 ]
2504 )
2505 ->will( $this->returnValue(
2506 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2507 ) );
2508
2509 $mockCache = $this->getMockCache();
2510 $mockCache->expects( $this->never() )->method( 'get' );
2511 $mockCache->expects( $this->once() )
2512 ->method( 'set' )
2513 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2514 $mockCache->expects( $this->once() )
2515 ->method( 'delete' )
2516 ->with( '0:SomeDbKey:1' );
2517
2518 $store = $this->newWatchedItemStore(
2519 $this->getMockLBFactory( $mockDb ),
2520 $mockCache,
2521 $this->getMockReadOnlyMode()
2522 );
2523
2524 $addUpdateCallCounter = 0;
2525 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2526 function ( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2527 $addUpdateCallCounter++;
2528 $this->verifyCallbackJob(
2529 $callable,
2530 $title,
2531 $user->getId(),
2532 function ( $time ) {
2533 return $time === false;
2534 }
2535 );
2536 }
2537 );
2538
2539 $getTimestampCallCounter = 0;
2540 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2541 function ( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2542 $getTimestampCallCounter++;
2543 $this->assertEquals( $title, $titleParam );
2544 $this->assertEquals( $oldid, $oldidParam );
2545 }
2546 );
2547
2548 $this->assertTrue(
2549 $store->resetNotificationTimestamp(
2550 $user,
2551 $title,
2552 '',
2553 $oldid
2554 )
2555 );
2556 $this->assertEquals( 1, $addUpdateCallCounter );
2557 $this->assertEquals( 1, $getTimestampCallCounter );
2558
2559 ScopedCallback::consume( $scopedOverrideDeferred );
2560 ScopedCallback::consume( $scopedOverrideRevision );
2561 }
2562
2563 public function testSetNotificationTimestampsForUser_anonUser() {
2564 $store = $this->newWatchedItemStore(
2565 $this->getMockLBFactory( $this->getMockDb() ),
2566 $this->getMockCache(),
2567 $this->getMockReadOnlyMode()
2568 );
2569 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2570 }
2571
2572 public function testSetNotificationTimestampsForUser_allRows() {
2573 $user = $this->getMockNonAnonUserWithId( 1 );
2574 $timestamp = '20100101010101';
2575
2576 $mockDb = $this->getMockDb();
2577 $mockDb->expects( $this->once() )
2578 ->method( 'update' )
2579 ->with(
2580 'watchlist',
2581 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2582 [ 'wl_user' => 1 ]
2583 )
2584 ->will( $this->returnValue( true ) );
2585 $mockDb->expects( $this->exactly( 1 ) )
2586 ->method( 'timestamp' )
2587 ->will( $this->returnCallback( function ( $value ) {
2588 return 'TS' . $value . 'TS';
2589 } ) );
2590
2591 $store = $this->newWatchedItemStore(
2592 $this->getMockLBFactory( $mockDb ),
2593 $this->getMockCache(),
2594 $this->getMockReadOnlyMode()
2595 );
2596
2597 $this->assertTrue(
2598 $store->setNotificationTimestampsForUser( $user, $timestamp )
2599 );
2600 }
2601
2602 public function testSetNotificationTimestampsForUser_nullTimestamp() {
2603 $user = $this->getMockNonAnonUserWithId( 1 );
2604 $timestamp = null;
2605
2606 $mockDb = $this->getMockDb();
2607 $mockDb->expects( $this->once() )
2608 ->method( 'update' )
2609 ->with(
2610 'watchlist',
2611 [ 'wl_notificationtimestamp' => null ],
2612 [ 'wl_user' => 1 ]
2613 )
2614 ->will( $this->returnValue( true ) );
2615 $mockDb->expects( $this->exactly( 0 ) )
2616 ->method( 'timestamp' )
2617 ->will( $this->returnCallback( function ( $value ) {
2618 return 'TS' . $value . 'TS';
2619 } ) );
2620
2621 $store = $this->newWatchedItemStore(
2622 $this->getMockLBFactory( $mockDb ),
2623 $this->getMockCache(),
2624 $this->getMockReadOnlyMode()
2625 );
2626
2627 $this->assertTrue(
2628 $store->setNotificationTimestampsForUser( $user, $timestamp )
2629 );
2630 }
2631
2632 public function testSetNotificationTimestampsForUser_specificTargets() {
2633 $user = $this->getMockNonAnonUserWithId( 1 );
2634 $timestamp = '20100101010101';
2635 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2636
2637 $mockDb = $this->getMockDb();
2638 $mockDb->expects( $this->once() )
2639 ->method( 'update' )
2640 ->with(
2641 'watchlist',
2642 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2643 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2644 )
2645 ->will( $this->returnValue( true ) );
2646 $mockDb->expects( $this->exactly( 1 ) )
2647 ->method( 'timestamp' )
2648 ->will( $this->returnCallback( function ( $value ) {
2649 return 'TS' . $value . 'TS';
2650 } ) );
2651 $mockDb->expects( $this->once() )
2652 ->method( 'makeWhereFrom2d' )
2653 ->with(
2654 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2655 $this->isType( 'string' ),
2656 $this->isType( 'string' )
2657 )
2658 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2659
2660 $store = $this->newWatchedItemStore(
2661 $this->getMockLBFactory( $mockDb ),
2662 $this->getMockCache(),
2663 $this->getMockReadOnlyMode()
2664 );
2665
2666 $this->assertTrue(
2667 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2668 );
2669 }
2670
2671 public function testUpdateNotificationTimestamp_watchersExist() {
2672 $mockDb = $this->getMockDb();
2673 $mockDb->expects( $this->once() )
2674 ->method( 'selectFieldValues' )
2675 ->with(
2676 'watchlist',
2677 'wl_user',
2678 [
2679 'wl_user != 1',
2680 'wl_namespace' => 0,
2681 'wl_title' => 'SomeDbKey',
2682 'wl_notificationtimestamp IS NULL'
2683 ]
2684 )
2685 ->will( $this->returnValue( [ '2', '3' ] ) );
2686 $mockDb->expects( $this->once() )
2687 ->method( 'update' )
2688 ->with(
2689 'watchlist',
2690 [ 'wl_notificationtimestamp' => null ],
2691 [
2692 'wl_user' => [ 2, 3 ],
2693 'wl_namespace' => 0,
2694 'wl_title' => 'SomeDbKey',
2695 ]
2696 );
2697
2698 $mockCache = $this->getMockCache();
2699 $mockCache->expects( $this->never() )->method( 'set' );
2700 $mockCache->expects( $this->never() )->method( 'get' );
2701 $mockCache->expects( $this->never() )->method( 'delete' );
2702
2703 $store = $this->newWatchedItemStore(
2704 $this->getMockLBFactory( $mockDb ),
2705 $mockCache,
2706 $this->getMockReadOnlyMode()
2707 );
2708
2709 $this->assertEquals(
2710 [ 2, 3 ],
2711 $store->updateNotificationTimestamp(
2712 $this->getMockNonAnonUserWithId( 1 ),
2713 new TitleValue( 0, 'SomeDbKey' ),
2714 '20151212010101'
2715 )
2716 );
2717 }
2718
2719 public function testUpdateNotificationTimestamp_noWatchers() {
2720 $mockDb = $this->getMockDb();
2721 $mockDb->expects( $this->once() )
2722 ->method( 'selectFieldValues' )
2723 ->with(
2724 'watchlist',
2725 'wl_user',
2726 [
2727 'wl_user != 1',
2728 'wl_namespace' => 0,
2729 'wl_title' => 'SomeDbKey',
2730 'wl_notificationtimestamp IS NULL'
2731 ]
2732 )
2733 ->will(
2734 $this->returnValue( [] )
2735 );
2736 $mockDb->expects( $this->never() )
2737 ->method( 'update' );
2738
2739 $mockCache = $this->getMockCache();
2740 $mockCache->expects( $this->never() )->method( 'set' );
2741 $mockCache->expects( $this->never() )->method( 'get' );
2742 $mockCache->expects( $this->never() )->method( 'delete' );
2743
2744 $store = $this->newWatchedItemStore(
2745 $this->getMockLBFactory( $mockDb ),
2746 $mockCache,
2747 $this->getMockReadOnlyMode()
2748 );
2749
2750 $watchers = $store->updateNotificationTimestamp(
2751 $this->getMockNonAnonUserWithId( 1 ),
2752 new TitleValue( 0, 'SomeDbKey' ),
2753 '20151212010101'
2754 );
2755 $this->assertInternalType( 'array', $watchers );
2756 $this->assertEmpty( $watchers );
2757 }
2758
2759 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2760 $user = $this->getMockNonAnonUserWithId( 1 );
2761 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2762
2763 $mockDb = $this->getMockDb();
2764 $mockDb->expects( $this->once() )
2765 ->method( 'selectRow' )
2766 ->will( $this->returnValue(
2767 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2768 ) );
2769 $mockDb->expects( $this->once() )
2770 ->method( 'selectFieldValues' )
2771 ->will(
2772 $this->returnValue( [ '2', '3' ] )
2773 );
2774 $mockDb->expects( $this->once() )
2775 ->method( 'update' );
2776
2777 $mockCache = $this->getMockCache();
2778 $mockCache->expects( $this->once() )
2779 ->method( 'set' )
2780 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2781 $mockCache->expects( $this->once() )
2782 ->method( 'get' )
2783 ->with( '0:SomeDbKey:1' );
2784 $mockCache->expects( $this->once() )
2785 ->method( 'delete' )
2786 ->with( '0:SomeDbKey:1' );
2787
2788 $store = $this->newWatchedItemStore(
2789 $this->getMockLBFactory( $mockDb ),
2790 $mockCache,
2791 $this->getMockReadOnlyMode()
2792 );
2793
2794 // This will add the item to the cache
2795 $store->getWatchedItem( $user, $titleValue );
2796
2797 $store->updateNotificationTimestamp(
2798 $this->getMockNonAnonUserWithId( 1 ),
2799 $titleValue,
2800 '20151212010101'
2801 );
2802 }
2803
2804 }