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