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