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