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