MediaWiki REL1_28
WatchedItemStoreUnitTest.php
Go to the documentation of this file.
1<?php
3use Wikimedia\ScopedCallback;
4
11
15 private function getMockDb() {
16 return $this->getMock( IDatabase::class );
17 }
18
22 private function getMockLoadBalancer(
23 $mockDb,
24 $expectedConnectionType = null,
25 $readOnlyReason = false
26 ) {
27 $mock = $this->getMockBuilder( LoadBalancer::class )
28 ->disableOriginalConstructor()
29 ->getMock();
30 if ( $expectedConnectionType !== null ) {
31 $mock->expects( $this->any() )
32 ->method( 'getConnectionRef' )
33 ->with( $expectedConnectionType )
34 ->will( $this->returnValue( $mockDb ) );
35 } else {
36 $mock->expects( $this->any() )
37 ->method( 'getConnectionRef' )
38 ->will( $this->returnValue( $mockDb ) );
39 }
40 $mock->expects( $this->any() )
41 ->method( 'getReadOnlyReason' )
42 ->will( $this->returnValue( $readOnlyReason ) );
43 return $mock;
44 }
45
49 private function getMockCache() {
50 $mock = $this->getMockBuilder( HashBagOStuff::class )
51 ->disableOriginalConstructor()
52 ->getMock();
53 $mock->expects( $this->any() )
54 ->method( 'makeKey' )
55 ->will( $this->returnCallback( function() {
56 return implode( ':', func_get_args() );
57 } ) );
58 return $mock;
59 }
60
65 private function getMockNonAnonUserWithId( $id ) {
66 $mock = $this->getMock( User::class );
67 $mock->expects( $this->any() )
68 ->method( 'isAnon' )
69 ->will( $this->returnValue( false ) );
70 $mock->expects( $this->any() )
71 ->method( 'getId' )
72 ->will( $this->returnValue( $id ) );
73 return $mock;
74 }
75
79 private function getAnonUser() {
80 return User::newFromName( 'Anon_User' );
81 }
82
83 private function getFakeRow( array $rowValues ) {
84 $fakeRow = new stdClass();
85 foreach ( $rowValues as $valueName => $value ) {
86 $fakeRow->$valueName = $value;
87 }
88 return $fakeRow;
89 }
90
91 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache ) {
92 return new WatchedItemStore(
93 $loadBalancer,
94 $cache
95 );
96 }
97
98 public function testCountWatchedItems() {
99 $user = $this->getMockNonAnonUserWithId( 1 );
100
101 $mockDb = $this->getMockDb();
102 $mockDb->expects( $this->exactly( 1 ) )
103 ->method( 'selectField' )
104 ->with(
105 'watchlist',
106 'COUNT(*)',
107 [
108 'wl_user' => $user->getId(),
109 ],
110 $this->isType( 'string' )
111 )
112 ->will( $this->returnValue( 12 ) );
113
114 $mockCache = $this->getMockCache();
115 $mockCache->expects( $this->never() )->method( 'get' );
116 $mockCache->expects( $this->never() )->method( 'set' );
117 $mockCache->expects( $this->never() )->method( 'delete' );
118
119 $store = $this->newWatchedItemStore(
120 $this->getMockLoadBalancer( $mockDb ),
121 $mockCache
122 );
123
124 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
125 }
126
127 public function testCountWatchers() {
128 $titleValue = new TitleValue( 0, 'SomeDbKey' );
129
130 $mockDb = $this->getMockDb();
131 $mockDb->expects( $this->exactly( 1 ) )
132 ->method( 'selectField' )
133 ->with(
134 'watchlist',
135 'COUNT(*)',
136 [
137 'wl_namespace' => $titleValue->getNamespace(),
138 'wl_title' => $titleValue->getDBkey(),
139 ],
140 $this->isType( 'string' )
141 )
142 ->will( $this->returnValue( 7 ) );
143
144 $mockCache = $this->getMockCache();
145 $mockCache->expects( $this->never() )->method( 'get' );
146 $mockCache->expects( $this->never() )->method( 'set' );
147 $mockCache->expects( $this->never() )->method( 'delete' );
148
149 $store = $this->newWatchedItemStore(
150 $this->getMockLoadBalancer( $mockDb ),
151 $mockCache
152 );
153
154 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
155 }
156
157 public function testCountWatchersMultiple() {
158 $titleValues = [
159 new TitleValue( 0, 'SomeDbKey' ),
160 new TitleValue( 0, 'OtherDbKey' ),
161 new TitleValue( 1, 'AnotherDbKey' ),
162 ];
163
164 $mockDb = $this->getMockDb();
165
166 $dbResult = [
167 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
168 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
169 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
170 ),
171 ];
172 $mockDb->expects( $this->once() )
173 ->method( 'makeWhereFrom2d' )
174 ->with(
175 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
176 $this->isType( 'string' ),
177 $this->isType( 'string' )
178 )
179 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
180 $mockDb->expects( $this->once() )
181 ->method( 'select' )
182 ->with(
183 'watchlist',
184 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
185 [ 'makeWhereFrom2d return value' ],
186 $this->isType( 'string' ),
187 [
188 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
189 ]
190 )
191 ->will(
192 $this->returnValue( $dbResult )
193 );
194
195 $mockCache = $this->getMockCache();
196 $mockCache->expects( $this->never() )->method( 'get' );
197 $mockCache->expects( $this->never() )->method( 'set' );
198 $mockCache->expects( $this->never() )->method( 'delete' );
199
200 $store = $this->newWatchedItemStore(
201 $this->getMockLoadBalancer( $mockDb ),
202 $mockCache
203 );
204
205 $expected = [
206 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
207 1 => [ 'AnotherDbKey' => 500 ],
208 ];
209 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
210 }
211
213 return [
214 [ 50 ],
215 [ "50; DROP TABLE watchlist;\n--" ],
216 ];
217 }
218
222 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
223 $titleValues = [
224 new TitleValue( 0, 'SomeDbKey' ),
225 new TitleValue( 0, 'OtherDbKey' ),
226 new TitleValue( 1, 'AnotherDbKey' ),
227 ];
228
229 $mockDb = $this->getMockDb();
230
231 $dbResult = [
232 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
233 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
234 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
235 ),
236 ];
237 $mockDb->expects( $this->once() )
238 ->method( 'makeWhereFrom2d' )
239 ->with(
240 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
241 $this->isType( 'string' ),
242 $this->isType( 'string' )
243 )
244 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
245 $mockDb->expects( $this->once() )
246 ->method( 'select' )
247 ->with(
248 'watchlist',
249 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
250 [ 'makeWhereFrom2d return value' ],
251 $this->isType( 'string' ),
252 [
253 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
254 'HAVING' => 'COUNT(*) >= 50',
255 ]
256 )
257 ->will(
258 $this->returnValue( $dbResult )
259 );
260
261 $mockCache = $this->getMockCache();
262 $mockCache->expects( $this->never() )->method( 'get' );
263 $mockCache->expects( $this->never() )->method( 'set' );
264 $mockCache->expects( $this->never() )->method( 'delete' );
265
266 $store = $this->newWatchedItemStore(
267 $this->getMockLoadBalancer( $mockDb ),
268 $mockCache
269 );
270
271 $expected = [
272 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
273 1 => [ 'AnotherDbKey' => 500 ],
274 ];
275 $this->assertEquals(
276 $expected,
277 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
278 );
279 }
280
281 public function testCountVisitingWatchers() {
282 $titleValue = new TitleValue( 0, 'SomeDbKey' );
283
284 $mockDb = $this->getMockDb();
285 $mockDb->expects( $this->exactly( 1 ) )
286 ->method( 'selectField' )
287 ->with(
288 'watchlist',
289 'COUNT(*)',
290 [
291 'wl_namespace' => $titleValue->getNamespace(),
292 'wl_title' => $titleValue->getDBkey(),
293 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
294 ],
295 $this->isType( 'string' )
296 )
297 ->will( $this->returnValue( 7 ) );
298 $mockDb->expects( $this->exactly( 1 ) )
299 ->method( 'addQuotes' )
300 ->will( $this->returnCallback( function( $value ) {
301 return "'$value'";
302 } ) );
303 $mockDb->expects( $this->exactly( 1 ) )
304 ->method( 'timestamp' )
305 ->will( $this->returnCallback( function( $value ) {
306 return 'TS' . $value . 'TS';
307 } ) );
308
309 $mockCache = $this->getMockCache();
310 $mockCache->expects( $this->never() )->method( 'set' );
311 $mockCache->expects( $this->never() )->method( 'get' );
312 $mockCache->expects( $this->never() )->method( 'delete' );
313
314 $store = $this->newWatchedItemStore(
315 $this->getMockLoadBalancer( $mockDb ),
316 $mockCache
317 );
318
319 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
320 }
321
323 $titleValuesWithThresholds = [
324 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
325 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
326 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
327 ];
328
329 $dbResult = [
330 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
331 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
332 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
333 ];
334 $mockDb = $this->getMockDb();
335 $mockDb->expects( $this->exactly( 2 * 3 ) )
336 ->method( 'addQuotes' )
337 ->will( $this->returnCallback( function( $value ) {
338 return "'$value'";
339 } ) );
340 $mockDb->expects( $this->exactly( 3 ) )
341 ->method( 'timestamp' )
342 ->will( $this->returnCallback( function( $value ) {
343 return 'TS' . $value . 'TS';
344 } ) );
345 $mockDb->expects( $this->any() )
346 ->method( 'makeList' )
347 ->with(
348 $this->isType( 'array' ),
349 $this->isType( 'int' )
350 )
351 ->will( $this->returnCallback( function( $a, $conj ) {
352 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
353 return join( $sqlConj, array_map( function( $s ) {
354 return '(' . $s . ')';
355 }, $a
356 ) );
357 } ) );
358 $mockDb->expects( $this->never() )
359 ->method( 'makeWhereFrom2d' );
360
361 $expectedCond =
362 '((wl_namespace = 0) AND (' .
363 "(((wl_title = 'SomeDbKey') AND (" .
364 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
365 ')) OR (' .
366 "(wl_title = 'OtherDbKey') AND (" .
367 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
368 '))))' .
369 ') OR ((wl_namespace = 1) AND (' .
370 "(((wl_title = 'AnotherDbKey') AND (".
371 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
372 ')))))';
373 $mockDb->expects( $this->once() )
374 ->method( 'select' )
375 ->with(
376 'watchlist',
377 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
378 $expectedCond,
379 $this->isType( 'string' ),
380 [
381 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
382 ]
383 )
384 ->will(
385 $this->returnValue( $dbResult )
386 );
387
388 $mockCache = $this->getMockCache();
389 $mockCache->expects( $this->never() )->method( 'get' );
390 $mockCache->expects( $this->never() )->method( 'set' );
391 $mockCache->expects( $this->never() )->method( 'delete' );
392
393 $store = $this->newWatchedItemStore(
394 $this->getMockLoadBalancer( $mockDb ),
395 $mockCache
396 );
397
398 $expected = [
399 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
400 1 => [ 'AnotherDbKey' => 500 ],
401 ];
402 $this->assertEquals(
403 $expected,
404 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
405 );
406 }
407
409 $titleValuesWithThresholds = [
410 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
411 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
412 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
413 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
414 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
415 ];
416
417 $dbResult = [
418 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
419 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
420 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
421 $this->getFakeRow(
422 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
423 ),
424 $this->getFakeRow(
425 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
426 ),
427 ];
428 $mockDb = $this->getMockDb();
429 $mockDb->expects( $this->exactly( 2 * 3 ) )
430 ->method( 'addQuotes' )
431 ->will( $this->returnCallback( function( $value ) {
432 return "'$value'";
433 } ) );
434 $mockDb->expects( $this->exactly( 3 ) )
435 ->method( 'timestamp' )
436 ->will( $this->returnCallback( function( $value ) {
437 return 'TS' . $value . 'TS';
438 } ) );
439 $mockDb->expects( $this->any() )
440 ->method( 'makeList' )
441 ->with(
442 $this->isType( 'array' ),
443 $this->isType( 'int' )
444 )
445 ->will( $this->returnCallback( function( $a, $conj ) {
446 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
447 return join( $sqlConj, array_map( function( $s ) {
448 return '(' . $s . ')';
449 }, $a
450 ) );
451 } ) );
452 $mockDb->expects( $this->once() )
453 ->method( 'makeWhereFrom2d' )
454 ->with(
455 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
456 $this->isType( 'string' ),
457 $this->isType( 'string' )
458 )
459 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
460
461 $expectedCond =
462 '((wl_namespace = 0) AND (' .
463 "(((wl_title = 'SomeDbKey') AND (" .
464 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
465 ')) OR (' .
466 "(wl_title = 'OtherDbKey') AND (" .
467 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
468 '))))' .
469 ') OR ((wl_namespace = 1) AND (' .
470 "(((wl_title = 'AnotherDbKey') AND (".
471 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
472 '))))' .
473 ') OR ' .
474 '(makeWhereFrom2d return value)';
475 $mockDb->expects( $this->once() )
476 ->method( 'select' )
477 ->with(
478 'watchlist',
479 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
480 $expectedCond,
481 $this->isType( 'string' ),
482 [
483 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
484 ]
485 )
486 ->will(
487 $this->returnValue( $dbResult )
488 );
489
490 $mockCache = $this->getMockCache();
491 $mockCache->expects( $this->never() )->method( 'get' );
492 $mockCache->expects( $this->never() )->method( 'set' );
493 $mockCache->expects( $this->never() )->method( 'delete' );
494
495 $store = $this->newWatchedItemStore(
496 $this->getMockLoadBalancer( $mockDb ),
497 $mockCache
498 );
499
500 $expected = [
501 0 => [
502 'SomeDbKey' => 100, 'OtherDbKey' => 300,
503 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
504 ],
505 1 => [ 'AnotherDbKey' => 500 ],
506 ];
507 $this->assertEquals(
508 $expected,
509 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
510 );
511 }
512
517 $titleValuesWithThresholds = [
518 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
519 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
520 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
521 ];
522
523 $mockDb = $this->getMockDb();
524 $mockDb->expects( $this->any() )
525 ->method( 'makeList' )
526 ->will( $this->returnValue( 'makeList return value' ) );
527 $mockDb->expects( $this->once() )
528 ->method( 'select' )
529 ->with(
530 'watchlist',
531 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
532 'makeList return value',
533 $this->isType( 'string' ),
534 [
535 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
536 'HAVING' => 'COUNT(*) >= 50',
537 ]
538 )
539 ->will(
540 $this->returnValue( [] )
541 );
542
543 $mockCache = $this->getMockCache();
544 $mockCache->expects( $this->never() )->method( 'get' );
545 $mockCache->expects( $this->never() )->method( 'set' );
546 $mockCache->expects( $this->never() )->method( 'delete' );
547
548 $store = $this->newWatchedItemStore(
549 $this->getMockLoadBalancer( $mockDb ),
550 $mockCache
551 );
552
553 $expected = [
554 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
555 1 => [ 'AnotherDbKey' => 0 ],
556 ];
557 $this->assertEquals(
558 $expected,
559 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
560 );
561 }
562
564 $user = $this->getMockNonAnonUserWithId( 1 );
565
566 $mockDb = $this->getMockDb();
567 $mockDb->expects( $this->exactly( 1 ) )
568 ->method( 'selectRowCount' )
569 ->with(
570 'watchlist',
571 '1',
572 [
573 "wl_notificationtimestamp IS NOT NULL",
574 'wl_user' => 1,
575 ],
576 $this->isType( 'string' )
577 )
578 ->will( $this->returnValue( 9 ) );
579
580 $mockCache = $this->getMockCache();
581 $mockCache->expects( $this->never() )->method( 'set' );
582 $mockCache->expects( $this->never() )->method( 'get' );
583 $mockCache->expects( $this->never() )->method( 'delete' );
584
585 $store = $this->newWatchedItemStore(
586 $this->getMockLoadBalancer( $mockDb ),
587 $mockCache
588 );
589
590 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
591 }
592
597 $user = $this->getMockNonAnonUserWithId( 1 );
598
599 $mockDb = $this->getMockDb();
600 $mockDb->expects( $this->exactly( 1 ) )
601 ->method( 'selectRowCount' )
602 ->with(
603 'watchlist',
604 '1',
605 [
606 "wl_notificationtimestamp IS NOT NULL",
607 'wl_user' => 1,
608 ],
609 $this->isType( 'string' ),
610 [ 'LIMIT' => 50 ]
611 )
612 ->will( $this->returnValue( 50 ) );
613
614 $mockCache = $this->getMockCache();
615 $mockCache->expects( $this->never() )->method( 'set' );
616 $mockCache->expects( $this->never() )->method( 'get' );
617 $mockCache->expects( $this->never() )->method( 'delete' );
618
619 $store = $this->newWatchedItemStore(
620 $this->getMockLoadBalancer( $mockDb ),
621 $mockCache
622 );
623
624 $this->assertSame(
625 true,
626 $store->countUnreadNotifications( $user, $limit )
627 );
628 }
629
634 $user = $this->getMockNonAnonUserWithId( 1 );
635
636 $mockDb = $this->getMockDb();
637 $mockDb->expects( $this->exactly( 1 ) )
638 ->method( 'selectRowCount' )
639 ->with(
640 'watchlist',
641 '1',
642 [
643 "wl_notificationtimestamp IS NOT NULL",
644 'wl_user' => 1,
645 ],
646 $this->isType( 'string' ),
647 [ 'LIMIT' => 50 ]
648 )
649 ->will( $this->returnValue( 9 ) );
650
651 $mockCache = $this->getMockCache();
652 $mockCache->expects( $this->never() )->method( 'set' );
653 $mockCache->expects( $this->never() )->method( 'get' );
654 $mockCache->expects( $this->never() )->method( 'delete' );
655
656 $store = $this->newWatchedItemStore(
657 $this->getMockLoadBalancer( $mockDb ),
658 $mockCache
659 );
660
661 $this->assertEquals(
662 9,
663 $store->countUnreadNotifications( $user, $limit )
664 );
665 }
666
668 $mockDb = $this->getMockDb();
669 $mockDb->expects( $this->once() )
670 ->method( 'select' )
671 ->with(
672 'watchlist',
673 [
674 'wl_user',
675 'wl_notificationtimestamp',
676 ],
677 [
678 'wl_namespace' => 0,
679 'wl_title' => 'Old_Title',
680 ],
681 'WatchedItemStore::duplicateEntry',
682 [ 'FOR UPDATE' ]
683 )
684 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
685
686 $store = $this->newWatchedItemStore(
687 $this->getMockLoadBalancer( $mockDb ),
688 $this->getMockCache()
689 );
690
691 $store->duplicateEntry(
692 Title::newFromText( 'Old_Title' ),
693 Title::newFromText( 'New_Title' )
694 );
695 }
696
698 $fakeRows = [
699 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
700 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
701 ];
702
703 $mockDb = $this->getMockDb();
704 $mockDb->expects( $this->at( 0 ) )
705 ->method( 'select' )
706 ->with(
707 'watchlist',
708 [
709 'wl_user',
710 'wl_notificationtimestamp',
711 ],
712 [
713 'wl_namespace' => 0,
714 'wl_title' => 'Old_Title',
715 ]
716 )
717 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
718 $mockDb->expects( $this->at( 1 ) )
719 ->method( 'replace' )
720 ->with(
721 'watchlist',
722 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
723 [
724 [
725 'wl_user' => 1,
726 'wl_namespace' => 0,
727 'wl_title' => 'New_Title',
728 'wl_notificationtimestamp' => '20151212010101',
729 ],
730 [
731 'wl_user' => 2,
732 'wl_namespace' => 0,
733 'wl_title' => 'New_Title',
734 'wl_notificationtimestamp' => null,
735 ],
736 ],
737 $this->isType( 'string' )
738 );
739
740 $mockCache = $this->getMockCache();
741 $mockCache->expects( $this->never() )->method( 'get' );
742 $mockCache->expects( $this->never() )->method( 'delete' );
743
744 $store = $this->newWatchedItemStore(
745 $this->getMockLoadBalancer( $mockDb ),
746 $mockCache
747 );
748
749 $store->duplicateEntry(
750 Title::newFromText( 'Old_Title' ),
751 Title::newFromText( 'New_Title' )
752 );
753 }
754
756 $mockDb = $this->getMockDb();
757 $mockDb->expects( $this->at( 0 ) )
758 ->method( 'select' )
759 ->with(
760 'watchlist',
761 [
762 'wl_user',
763 'wl_notificationtimestamp',
764 ],
765 [
766 'wl_namespace' => 0,
767 'wl_title' => 'Old_Title',
768 ]
769 )
770 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
771 $mockDb->expects( $this->at( 1 ) )
772 ->method( 'select' )
773 ->with(
774 'watchlist',
775 [
776 'wl_user',
777 'wl_notificationtimestamp',
778 ],
779 [
780 'wl_namespace' => 1,
781 'wl_title' => 'Old_Title',
782 ]
783 )
784 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
785
786 $mockCache = $this->getMockCache();
787 $mockCache->expects( $this->never() )->method( 'get' );
788 $mockCache->expects( $this->never() )->method( 'delete' );
789
790 $store = $this->newWatchedItemStore(
791 $this->getMockLoadBalancer( $mockDb ),
792 $mockCache
793 );
794
795 $store->duplicateAllAssociatedEntries(
796 Title::newFromText( 'Old_Title' ),
797 Title::newFromText( 'New_Title' )
798 );
799 }
800
801 public function provideLinkTargetPairs() {
802 return [
803 [ Title::newFromText( 'Old_Title' ), Title::newFromText( 'New_Title' ) ],
804 [ new TitleValue( 0, 'Old_Title' ), new TitleValue( 0, 'New_Title' ) ],
805 ];
806 }
807
812 LinkTarget $oldTarget,
813 LinkTarget $newTarget
814 ) {
815 $fakeRows = [
816 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
817 ];
818
819 $mockDb = $this->getMockDb();
820 $mockDb->expects( $this->at( 0 ) )
821 ->method( 'select' )
822 ->with(
823 'watchlist',
824 [
825 'wl_user',
826 'wl_notificationtimestamp',
827 ],
828 [
829 'wl_namespace' => $oldTarget->getNamespace(),
830 'wl_title' => $oldTarget->getDBkey(),
831 ]
832 )
833 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
834 $mockDb->expects( $this->at( 1 ) )
835 ->method( 'replace' )
836 ->with(
837 'watchlist',
838 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
839 [
840 [
841 'wl_user' => 1,
842 'wl_namespace' => $newTarget->getNamespace(),
843 'wl_title' => $newTarget->getDBkey(),
844 'wl_notificationtimestamp' => '20151212010101',
845 ],
846 ],
847 $this->isType( 'string' )
848 );
849 $mockDb->expects( $this->at( 2 ) )
850 ->method( 'select' )
851 ->with(
852 'watchlist',
853 [
854 'wl_user',
855 'wl_notificationtimestamp',
856 ],
857 [
858 'wl_namespace' => $oldTarget->getNamespace() + 1,
859 'wl_title' => $oldTarget->getDBkey(),
860 ]
861 )
862 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
863 $mockDb->expects( $this->at( 3 ) )
864 ->method( 'replace' )
865 ->with(
866 'watchlist',
867 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
868 [
869 [
870 'wl_user' => 1,
871 'wl_namespace' => $newTarget->getNamespace() + 1,
872 'wl_title' => $newTarget->getDBkey(),
873 'wl_notificationtimestamp' => '20151212010101',
874 ],
875 ],
876 $this->isType( 'string' )
877 );
878
879 $mockCache = $this->getMockCache();
880 $mockCache->expects( $this->never() )->method( 'get' );
881 $mockCache->expects( $this->never() )->method( 'delete' );
882
883 $store = $this->newWatchedItemStore(
884 $this->getMockLoadBalancer( $mockDb ),
885 $mockCache
886 );
887
888 $store->duplicateAllAssociatedEntries(
889 $oldTarget,
890 $newTarget
891 );
892 }
893
895 $mockDb = $this->getMockDb();
896 $mockDb->expects( $this->once() )
897 ->method( 'insert' )
898 ->with(
899 'watchlist',
900 [
901 [
902 'wl_user' => 1,
903 'wl_namespace' => 0,
904 'wl_title' => 'Some_Page',
905 'wl_notificationtimestamp' => null,
906 ]
907 ]
908 );
909
910 $mockCache = $this->getMockCache();
911 $mockCache->expects( $this->once() )
912 ->method( 'delete' )
913 ->with( '0:Some_Page:1' );
914
915 $store = $this->newWatchedItemStore(
916 $this->getMockLoadBalancer( $mockDb ),
917 $mockCache
918 );
919
920 $store->addWatch(
921 $this->getMockNonAnonUserWithId( 1 ),
922 Title::newFromText( 'Some_Page' )
923 );
924 }
925
926 public function testAddWatch_anonymousUser() {
927 $mockDb = $this->getMockDb();
928 $mockDb->expects( $this->never() )
929 ->method( 'insert' );
930
931 $mockCache = $this->getMockCache();
932 $mockCache->expects( $this->never() )
933 ->method( 'delete' );
934
935 $store = $this->newWatchedItemStore(
936 $this->getMockLoadBalancer( $mockDb ),
937 $mockCache
938 );
939
940 $store->addWatch(
941 $this->getAnonUser(),
942 Title::newFromText( 'Some_Page' )
943 );
944 }
945
947 $store = $this->newWatchedItemStore(
948 $this->getMockLoadBalancer( $this->getMockDb(), null, 'Some Reason' ),
949 $this->getMockCache()
950 );
951
952 $this->assertFalse(
953 $store->addWatchBatchForUser(
954 $this->getMockNonAnonUserWithId( 1 ),
955 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
956 )
957 );
958 }
959
961 $mockDb = $this->getMockDb();
962 $mockDb->expects( $this->once() )
963 ->method( 'insert' )
964 ->with(
965 'watchlist',
966 [
967 [
968 'wl_user' => 1,
969 'wl_namespace' => 0,
970 'wl_title' => 'Some_Page',
971 'wl_notificationtimestamp' => null,
972 ],
973 [
974 'wl_user' => 1,
975 'wl_namespace' => 1,
976 'wl_title' => 'Some_Page',
977 'wl_notificationtimestamp' => null,
978 ]
979 ]
980 );
981
982 $mockCache = $this->getMockCache();
983 $mockCache->expects( $this->exactly( 2 ) )
984 ->method( 'delete' );
985 $mockCache->expects( $this->at( 1 ) )
986 ->method( 'delete' )
987 ->with( '0:Some_Page:1' );
988 $mockCache->expects( $this->at( 3 ) )
989 ->method( 'delete' )
990 ->with( '1:Some_Page:1' );
991
992 $store = $this->newWatchedItemStore(
993 $this->getMockLoadBalancer( $mockDb ),
994 $mockCache
995 );
996
997 $mockUser = $this->getMockNonAnonUserWithId( 1 );
998
999 $this->assertTrue(
1000 $store->addWatchBatchForUser(
1001 $mockUser,
1002 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
1003 )
1004 );
1005 }
1006
1008 $mockDb = $this->getMockDb();
1009 $mockDb->expects( $this->never() )
1010 ->method( 'insert' );
1011
1012 $mockCache = $this->getMockCache();
1013 $mockCache->expects( $this->never() )
1014 ->method( 'delete' );
1015
1016 $store = $this->newWatchedItemStore(
1017 $this->getMockLoadBalancer( $mockDb ),
1018 $mockCache
1019 );
1020
1021 $this->assertFalse(
1022 $store->addWatchBatchForUser(
1023 $this->getAnonUser(),
1024 [ new TitleValue( 0, 'Other_Page' ) ]
1025 )
1026 );
1027 }
1028
1030 $user = $this->getMockNonAnonUserWithId( 1 );
1031 $mockDb = $this->getMockDb();
1032 $mockDb->expects( $this->never() )
1033 ->method( 'insert' );
1034
1035 $mockCache = $this->getMockCache();
1036 $mockCache->expects( $this->never() )
1037 ->method( 'delete' );
1038
1039 $store = $this->newWatchedItemStore(
1040 $this->getMockLoadBalancer( $mockDb ),
1041 $mockCache
1042 );
1043
1044 $this->assertTrue(
1045 $store->addWatchBatchForUser( $user, [] )
1046 );
1047 }
1048
1050 $mockDb = $this->getMockDb();
1051 $mockDb->expects( $this->once() )
1052 ->method( 'selectRow' )
1053 ->with(
1054 'watchlist',
1055 'wl_notificationtimestamp',
1056 [
1057 'wl_user' => 1,
1058 'wl_namespace' => 0,
1059 'wl_title' => 'SomeDbKey',
1060 ]
1061 )
1062 ->will( $this->returnValue(
1063 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1064 ) );
1065
1066 $mockCache = $this->getMockCache();
1067 $mockCache->expects( $this->once() )
1068 ->method( 'set' )
1069 ->with(
1070 '0:SomeDbKey:1'
1071 );
1072
1073 $store = $this->newWatchedItemStore(
1074 $this->getMockLoadBalancer( $mockDb ),
1075 $mockCache
1076 );
1077
1078 $watchedItem = $store->loadWatchedItem(
1079 $this->getMockNonAnonUserWithId( 1 ),
1080 new TitleValue( 0, 'SomeDbKey' )
1081 );
1082 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1083 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1084 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1085 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1086 }
1087
1088 public function testLoadWatchedItem_noItem() {
1089 $mockDb = $this->getMockDb();
1090 $mockDb->expects( $this->once() )
1091 ->method( 'selectRow' )
1092 ->with(
1093 'watchlist',
1094 'wl_notificationtimestamp',
1095 [
1096 'wl_user' => 1,
1097 'wl_namespace' => 0,
1098 'wl_title' => 'SomeDbKey',
1099 ]
1100 )
1101 ->will( $this->returnValue( [] ) );
1102
1103 $mockCache = $this->getMockCache();
1104 $mockCache->expects( $this->never() )->method( 'get' );
1105 $mockCache->expects( $this->never() )->method( 'delete' );
1106
1107 $store = $this->newWatchedItemStore(
1108 $this->getMockLoadBalancer( $mockDb ),
1109 $mockCache
1110 );
1111
1112 $this->assertFalse(
1113 $store->loadWatchedItem(
1114 $this->getMockNonAnonUserWithId( 1 ),
1115 new TitleValue( 0, 'SomeDbKey' )
1116 )
1117 );
1118 }
1119
1121 $mockDb = $this->getMockDb();
1122 $mockDb->expects( $this->never() )
1123 ->method( 'selectRow' );
1124
1125 $mockCache = $this->getMockCache();
1126 $mockCache->expects( $this->never() )->method( 'get' );
1127 $mockCache->expects( $this->never() )->method( 'delete' );
1128
1129 $store = $this->newWatchedItemStore(
1130 $this->getMockLoadBalancer( $mockDb ),
1131 $mockCache
1132 );
1133
1134 $this->assertFalse(
1135 $store->loadWatchedItem(
1136 $this->getAnonUser(),
1137 new TitleValue( 0, 'SomeDbKey' )
1138 )
1139 );
1140 }
1141
1143 $mockDb = $this->getMockDb();
1144 $mockDb->expects( $this->once() )
1145 ->method( 'delete' )
1146 ->with(
1147 'watchlist',
1148 [
1149 'wl_user' => 1,
1150 'wl_namespace' => 0,
1151 'wl_title' => 'SomeDbKey',
1152 ]
1153 );
1154 $mockDb->expects( $this->once() )
1155 ->method( 'affectedRows' )
1156 ->will( $this->returnValue( 1 ) );
1157
1158 $mockCache = $this->getMockCache();
1159 $mockCache->expects( $this->never() )->method( 'get' );
1160 $mockCache->expects( $this->once() )
1161 ->method( 'delete' )
1162 ->with( '0:SomeDbKey:1' );
1163
1164 $store = $this->newWatchedItemStore(
1165 $this->getMockLoadBalancer( $mockDb ),
1166 $mockCache
1167 );
1168
1169 $this->assertTrue(
1170 $store->removeWatch(
1171 $this->getMockNonAnonUserWithId( 1 ),
1172 new TitleValue( 0, 'SomeDbKey' )
1173 )
1174 );
1175 }
1176
1177 public function testRemoveWatch_noItem() {
1178 $mockDb = $this->getMockDb();
1179 $mockDb->expects( $this->once() )
1180 ->method( 'delete' )
1181 ->with(
1182 'watchlist',
1183 [
1184 'wl_user' => 1,
1185 'wl_namespace' => 0,
1186 'wl_title' => 'SomeDbKey',
1187 ]
1188 );
1189 $mockDb->expects( $this->once() )
1190 ->method( 'affectedRows' )
1191 ->will( $this->returnValue( 0 ) );
1192
1193 $mockCache = $this->getMockCache();
1194 $mockCache->expects( $this->never() )->method( 'get' );
1195 $mockCache->expects( $this->once() )
1196 ->method( 'delete' )
1197 ->with( '0:SomeDbKey:1' );
1198
1199 $store = $this->newWatchedItemStore(
1200 $this->getMockLoadBalancer( $mockDb ),
1201 $mockCache
1202 );
1203
1204 $this->assertFalse(
1205 $store->removeWatch(
1206 $this->getMockNonAnonUserWithId( 1 ),
1207 new TitleValue( 0, 'SomeDbKey' )
1208 )
1209 );
1210 }
1211
1213 $mockDb = $this->getMockDb();
1214 $mockDb->expects( $this->never() )
1215 ->method( 'delete' );
1216
1217 $mockCache = $this->getMockCache();
1218 $mockCache->expects( $this->never() )->method( 'get' );
1219 $mockCache->expects( $this->never() )
1220 ->method( 'delete' );
1221
1222 $store = $this->newWatchedItemStore(
1223 $this->getMockLoadBalancer( $mockDb ),
1224 $mockCache
1225 );
1226
1227 $this->assertFalse(
1228 $store->removeWatch(
1229 $this->getAnonUser(),
1230 new TitleValue( 0, 'SomeDbKey' )
1231 )
1232 );
1233 }
1234
1236 $mockDb = $this->getMockDb();
1237 $mockDb->expects( $this->once() )
1238 ->method( 'selectRow' )
1239 ->with(
1240 'watchlist',
1241 'wl_notificationtimestamp',
1242 [
1243 'wl_user' => 1,
1244 'wl_namespace' => 0,
1245 'wl_title' => 'SomeDbKey',
1246 ]
1247 )
1248 ->will( $this->returnValue(
1249 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1250 ) );
1251
1252 $mockCache = $this->getMockCache();
1253 $mockCache->expects( $this->never() )->method( 'delete' );
1254 $mockCache->expects( $this->once() )
1255 ->method( 'get' )
1256 ->with(
1257 '0:SomeDbKey:1'
1258 )
1259 ->will( $this->returnValue( null ) );
1260 $mockCache->expects( $this->once() )
1261 ->method( 'set' )
1262 ->with(
1263 '0:SomeDbKey:1'
1264 );
1265
1266 $store = $this->newWatchedItemStore(
1267 $this->getMockLoadBalancer( $mockDb ),
1268 $mockCache
1269 );
1270
1271 $watchedItem = $store->getWatchedItem(
1272 $this->getMockNonAnonUserWithId( 1 ),
1273 new TitleValue( 0, 'SomeDbKey' )
1274 );
1275 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1276 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1277 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1278 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1279 }
1280
1282 $mockDb = $this->getMockDb();
1283 $mockDb->expects( $this->never() )
1284 ->method( 'selectRow' );
1285
1286 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1287 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1288 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1289
1290 $mockCache = $this->getMockCache();
1291 $mockCache->expects( $this->never() )->method( 'delete' );
1292 $mockCache->expects( $this->never() )->method( 'set' );
1293 $mockCache->expects( $this->once() )
1294 ->method( 'get' )
1295 ->with(
1296 '0:SomeDbKey:1'
1297 )
1298 ->will( $this->returnValue( $cachedItem ) );
1299
1300 $store = $this->newWatchedItemStore(
1301 $this->getMockLoadBalancer( $mockDb ),
1302 $mockCache
1303 );
1304
1305 $this->assertEquals(
1306 $cachedItem,
1307 $store->getWatchedItem(
1308 $mockUser,
1309 $linkTarget
1310 )
1311 );
1312 }
1313
1314 public function testGetWatchedItem_noItem() {
1315 $mockDb = $this->getMockDb();
1316 $mockDb->expects( $this->once() )
1317 ->method( 'selectRow' )
1318 ->with(
1319 'watchlist',
1320 'wl_notificationtimestamp',
1321 [
1322 'wl_user' => 1,
1323 'wl_namespace' => 0,
1324 'wl_title' => 'SomeDbKey',
1325 ]
1326 )
1327 ->will( $this->returnValue( [] ) );
1328
1329 $mockCache = $this->getMockCache();
1330 $mockCache->expects( $this->never() )->method( 'set' );
1331 $mockCache->expects( $this->never() )->method( 'delete' );
1332 $mockCache->expects( $this->once() )
1333 ->method( 'get' )
1334 ->with( '0:SomeDbKey:1' )
1335 ->will( $this->returnValue( false ) );
1336
1337 $store = $this->newWatchedItemStore(
1338 $this->getMockLoadBalancer( $mockDb ),
1339 $mockCache
1340 );
1341
1342 $this->assertFalse(
1343 $store->getWatchedItem(
1344 $this->getMockNonAnonUserWithId( 1 ),
1345 new TitleValue( 0, 'SomeDbKey' )
1346 )
1347 );
1348 }
1349
1351 $mockDb = $this->getMockDb();
1352 $mockDb->expects( $this->never() )
1353 ->method( 'selectRow' );
1354
1355 $mockCache = $this->getMockCache();
1356 $mockCache->expects( $this->never() )->method( 'set' );
1357 $mockCache->expects( $this->never() )->method( 'get' );
1358 $mockCache->expects( $this->never() )->method( 'delete' );
1359
1360 $store = $this->newWatchedItemStore(
1361 $this->getMockLoadBalancer( $mockDb ),
1362 $mockCache
1363 );
1364
1365 $this->assertFalse(
1366 $store->getWatchedItem(
1367 $this->getAnonUser(),
1368 new TitleValue( 0, 'SomeDbKey' )
1369 )
1370 );
1371 }
1372
1373 public function testGetWatchedItemsForUser() {
1374 $mockDb = $this->getMockDb();
1375 $mockDb->expects( $this->once() )
1376 ->method( 'select' )
1377 ->with(
1378 'watchlist',
1379 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1380 [ 'wl_user' => 1 ]
1381 )
1382 ->will( $this->returnValue( [
1383 $this->getFakeRow( [
1384 'wl_namespace' => 0,
1385 'wl_title' => 'Foo1',
1386 'wl_notificationtimestamp' => '20151212010101',
1387 ] ),
1388 $this->getFakeRow( [
1389 'wl_namespace' => 1,
1390 'wl_title' => 'Foo2',
1391 'wl_notificationtimestamp' => null,
1392 ] ),
1393 ] ) );
1394
1395 $mockCache = $this->getMockCache();
1396 $mockCache->expects( $this->never() )->method( 'delete' );
1397 $mockCache->expects( $this->never() )->method( 'get' );
1398 $mockCache->expects( $this->never() )->method( 'set' );
1399
1400 $store = $this->newWatchedItemStore(
1401 $this->getMockLoadBalancer( $mockDb ),
1402 $mockCache
1403 );
1404 $user = $this->getMockNonAnonUserWithId( 1 );
1405
1406 $watchedItems = $store->getWatchedItemsForUser( $user );
1407
1408 $this->assertInternalType( 'array', $watchedItems );
1409 $this->assertCount( 2, $watchedItems );
1410 foreach ( $watchedItems as $watchedItem ) {
1411 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1412 }
1413 $this->assertEquals(
1414 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1415 $watchedItems[0]
1416 );
1417 $this->assertEquals(
1418 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1419 $watchedItems[1]
1420 );
1421 }
1422
1423 public function provideDbTypes() {
1424 return [
1425 [ false, DB_SLAVE ],
1426 [ true, DB_MASTER ],
1427 ];
1428 }
1429
1433 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1434 $mockDb = $this->getMockDb();
1435 $mockCache = $this->getMockCache();
1436 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1437 $user = $this->getMockNonAnonUserWithId( 1 );
1438
1439 $mockDb->expects( $this->once() )
1440 ->method( 'select' )
1441 ->with(
1442 'watchlist',
1443 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1444 [ 'wl_user' => 1 ],
1445 $this->isType( 'string' ),
1446 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1447 )
1448 ->will( $this->returnValue( [] ) );
1449
1450 $store = $this->newWatchedItemStore(
1451 $mockLoadBalancer,
1452 $mockCache
1453 );
1454
1455 $watchedItems = $store->getWatchedItemsForUser(
1456 $user,
1457 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1458 );
1459 $this->assertEquals( [], $watchedItems );
1460 }
1461
1463 $store = $this->newWatchedItemStore(
1464 $this->getMockLoadBalancer( $this->getMockDb() ),
1465 $this->getMockCache()
1466 );
1467
1468 $this->setExpectedException( 'InvalidArgumentException' );
1469 $store->getWatchedItemsForUser(
1470 $this->getMockNonAnonUserWithId( 1 ),
1471 [ 'sort' => 'foo' ]
1472 );
1473 }
1474
1476 $mockDb = $this->getMockDb();
1477 $mockDb->expects( $this->once() )
1478 ->method( 'selectRow' )
1479 ->with(
1480 'watchlist',
1481 'wl_notificationtimestamp',
1482 [
1483 'wl_user' => 1,
1484 'wl_namespace' => 0,
1485 'wl_title' => 'SomeDbKey',
1486 ]
1487 )
1488 ->will( $this->returnValue(
1489 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1490 ) );
1491
1492 $mockCache = $this->getMockCache();
1493 $mockCache->expects( $this->never() )->method( 'delete' );
1494 $mockCache->expects( $this->once() )
1495 ->method( 'get' )
1496 ->with( '0:SomeDbKey:1' )
1497 ->will( $this->returnValue( false ) );
1498 $mockCache->expects( $this->once() )
1499 ->method( 'set' )
1500 ->with(
1501 '0:SomeDbKey:1'
1502 );
1503
1504 $store = $this->newWatchedItemStore(
1505 $this->getMockLoadBalancer( $mockDb ),
1506 $mockCache
1507 );
1508
1509 $this->assertTrue(
1510 $store->isWatched(
1511 $this->getMockNonAnonUserWithId( 1 ),
1512 new TitleValue( 0, 'SomeDbKey' )
1513 )
1514 );
1515 }
1516
1517 public function testIsWatchedItem_noItem() {
1518 $mockDb = $this->getMockDb();
1519 $mockDb->expects( $this->once() )
1520 ->method( 'selectRow' )
1521 ->with(
1522 'watchlist',
1523 'wl_notificationtimestamp',
1524 [
1525 'wl_user' => 1,
1526 'wl_namespace' => 0,
1527 'wl_title' => 'SomeDbKey',
1528 ]
1529 )
1530 ->will( $this->returnValue( [] ) );
1531
1532 $mockCache = $this->getMockCache();
1533 $mockCache->expects( $this->never() )->method( 'set' );
1534 $mockCache->expects( $this->never() )->method( 'delete' );
1535 $mockCache->expects( $this->once() )
1536 ->method( 'get' )
1537 ->with( '0:SomeDbKey:1' )
1538 ->will( $this->returnValue( false ) );
1539
1540 $store = $this->newWatchedItemStore(
1541 $this->getMockLoadBalancer( $mockDb ),
1542 $mockCache
1543 );
1544
1545 $this->assertFalse(
1546 $store->isWatched(
1547 $this->getMockNonAnonUserWithId( 1 ),
1548 new TitleValue( 0, 'SomeDbKey' )
1549 )
1550 );
1551 }
1552
1554 $mockDb = $this->getMockDb();
1555 $mockDb->expects( $this->never() )
1556 ->method( 'selectRow' );
1557
1558 $mockCache = $this->getMockCache();
1559 $mockCache->expects( $this->never() )->method( 'set' );
1560 $mockCache->expects( $this->never() )->method( 'get' );
1561 $mockCache->expects( $this->never() )->method( 'delete' );
1562
1563 $store = $this->newWatchedItemStore(
1564 $this->getMockLoadBalancer( $mockDb ),
1565 $mockCache
1566 );
1567
1568 $this->assertFalse(
1569 $store->isWatched(
1570 $this->getAnonUser(),
1571 new TitleValue( 0, 'SomeDbKey' )
1572 )
1573 );
1574 }
1575
1577 $targets = [
1578 new TitleValue( 0, 'SomeDbKey' ),
1579 new TitleValue( 1, 'AnotherDbKey' ),
1580 ];
1581
1582 $mockDb = $this->getMockDb();
1583 $dbResult = [
1584 $this->getFakeRow( [
1585 'wl_namespace' => 0,
1586 'wl_title' => 'SomeDbKey',
1587 'wl_notificationtimestamp' => '20151212010101',
1588 ] ),
1589 $this->getFakeRow(
1590 [
1591 'wl_namespace' => 1,
1592 'wl_title' => 'AnotherDbKey',
1593 'wl_notificationtimestamp' => null,
1594 ]
1595 ),
1596 ];
1597
1598 $mockDb->expects( $this->once() )
1599 ->method( 'makeWhereFrom2d' )
1600 ->with(
1601 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1602 $this->isType( 'string' ),
1603 $this->isType( 'string' )
1604 )
1605 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1606 $mockDb->expects( $this->once() )
1607 ->method( 'select' )
1608 ->with(
1609 'watchlist',
1610 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1611 [
1612 'makeWhereFrom2d return value',
1613 'wl_user' => 1
1614 ],
1615 $this->isType( 'string' )
1616 )
1617 ->will( $this->returnValue( $dbResult ) );
1618
1619 $mockCache = $this->getMockCache();
1620 $mockCache->expects( $this->exactly( 2 ) )
1621 ->method( 'get' )
1622 ->withConsecutive(
1623 [ '0:SomeDbKey:1' ],
1624 [ '1:AnotherDbKey:1' ]
1625 )
1626 ->will( $this->returnValue( null ) );
1627 $mockCache->expects( $this->never() )->method( 'set' );
1628 $mockCache->expects( $this->never() )->method( 'delete' );
1629
1630 $store = $this->newWatchedItemStore(
1631 $this->getMockLoadBalancer( $mockDb ),
1632 $mockCache
1633 );
1634
1635 $this->assertEquals(
1636 [
1637 0 => [ 'SomeDbKey' => '20151212010101', ],
1638 1 => [ 'AnotherDbKey' => null, ],
1639 ],
1640 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1641 );
1642 }
1643
1645 $targets = [
1646 new TitleValue( 0, 'OtherDbKey' ),
1647 ];
1648
1649 $mockDb = $this->getMockDb();
1650
1651 $mockDb->expects( $this->once() )
1652 ->method( 'makeWhereFrom2d' )
1653 ->with(
1654 [ [ 'OtherDbKey' => 1 ] ],
1655 $this->isType( 'string' ),
1656 $this->isType( 'string' )
1657 )
1658 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1659 $mockDb->expects( $this->once() )
1660 ->method( 'select' )
1661 ->with(
1662 'watchlist',
1663 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1664 [
1665 'makeWhereFrom2d return value',
1666 'wl_user' => 1
1667 ],
1668 $this->isType( 'string' )
1669 )
1670 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1671
1672 $mockCache = $this->getMockCache();
1673 $mockCache->expects( $this->once() )
1674 ->method( 'get' )
1675 ->with( '0:OtherDbKey:1' )
1676 ->will( $this->returnValue( null ) );
1677 $mockCache->expects( $this->never() )->method( 'set' );
1678 $mockCache->expects( $this->never() )->method( 'delete' );
1679
1680 $store = $this->newWatchedItemStore(
1681 $this->getMockLoadBalancer( $mockDb ),
1682 $mockCache
1683 );
1684
1685 $this->assertEquals(
1686 [
1687 0 => [ 'OtherDbKey' => false, ],
1688 ],
1689 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1690 );
1691 }
1692
1694 $targets = [
1695 new TitleValue( 0, 'SomeDbKey' ),
1696 new TitleValue( 1, 'AnotherDbKey' ),
1697 ];
1698
1699 $user = $this->getMockNonAnonUserWithId( 1 );
1700 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1701
1702 $mockDb = $this->getMockDb();
1703
1704 $mockDb->expects( $this->once() )
1705 ->method( 'makeWhereFrom2d' )
1706 ->with(
1707 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1708 $this->isType( 'string' ),
1709 $this->isType( 'string' )
1710 )
1711 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1712 $mockDb->expects( $this->once() )
1713 ->method( 'select' )
1714 ->with(
1715 'watchlist',
1716 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1717 [
1718 'makeWhereFrom2d return value',
1719 'wl_user' => 1
1720 ],
1721 $this->isType( 'string' )
1722 )
1723 ->will( $this->returnValue( [
1724 $this->getFakeRow(
1725 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1726 )
1727 ] ) );
1728
1729 $mockCache = $this->getMockCache();
1730 $mockCache->expects( $this->at( 1 ) )
1731 ->method( 'get' )
1732 ->with( '0:SomeDbKey:1' )
1733 ->will( $this->returnValue( $cachedItem ) );
1734 $mockCache->expects( $this->at( 3 ) )
1735 ->method( 'get' )
1736 ->with( '1:AnotherDbKey:1' )
1737 ->will( $this->returnValue( null ) );
1738 $mockCache->expects( $this->never() )->method( 'set' );
1739 $mockCache->expects( $this->never() )->method( 'delete' );
1740
1741 $store = $this->newWatchedItemStore(
1742 $this->getMockLoadBalancer( $mockDb ),
1743 $mockCache
1744 );
1745
1746 $this->assertEquals(
1747 [
1748 0 => [ 'SomeDbKey' => '20151212010101', ],
1749 1 => [ 'AnotherDbKey' => null, ],
1750 ],
1751 $store->getNotificationTimestampsBatch( $user, $targets )
1752 );
1753 }
1754
1756 $targets = [
1757 new TitleValue( 0, 'SomeDbKey' ),
1758 new TitleValue( 1, 'AnotherDbKey' ),
1759 ];
1760
1761 $user = $this->getMockNonAnonUserWithId( 1 );
1762 $cachedItems = [
1763 new WatchedItem( $user, $targets[0], '20151212010101' ),
1764 new WatchedItem( $user, $targets[1], null ),
1765 ];
1766 $mockDb = $this->getMockDb();
1767 $mockDb->expects( $this->never() )->method( $this->anything() );
1768
1769 $mockCache = $this->getMockCache();
1770 $mockCache->expects( $this->at( 1 ) )
1771 ->method( 'get' )
1772 ->with( '0:SomeDbKey:1' )
1773 ->will( $this->returnValue( $cachedItems[0] ) );
1774 $mockCache->expects( $this->at( 3 ) )
1775 ->method( 'get' )
1776 ->with( '1:AnotherDbKey:1' )
1777 ->will( $this->returnValue( $cachedItems[1] ) );
1778 $mockCache->expects( $this->never() )->method( 'set' );
1779 $mockCache->expects( $this->never() )->method( 'delete' );
1780
1781 $store = $this->newWatchedItemStore(
1782 $this->getMockLoadBalancer( $mockDb ),
1783 $mockCache
1784 );
1785
1786 $this->assertEquals(
1787 [
1788 0 => [ 'SomeDbKey' => '20151212010101', ],
1789 1 => [ 'AnotherDbKey' => null, ],
1790 ],
1791 $store->getNotificationTimestampsBatch( $user, $targets )
1792 );
1793 }
1794
1796 $targets = [
1797 new TitleValue( 0, 'SomeDbKey' ),
1798 new TitleValue( 1, 'AnotherDbKey' ),
1799 ];
1800
1801 $mockDb = $this->getMockDb();
1802 $mockDb->expects( $this->never() )->method( $this->anything() );
1803
1804 $mockCache = $this->getMockCache();
1805 $mockCache->expects( $this->never() )->method( $this->anything() );
1806
1807 $store = $this->newWatchedItemStore(
1808 $this->getMockLoadBalancer( $mockDb ),
1809 $mockCache
1810 );
1811
1812 $this->assertEquals(
1813 [
1814 0 => [ 'SomeDbKey' => false, ],
1815 1 => [ 'AnotherDbKey' => false, ],
1816 ],
1817 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1818 );
1819 }
1820
1822 $mockDb = $this->getMockDb();
1823 $mockDb->expects( $this->never() )
1824 ->method( 'selectRow' );
1825
1826 $mockCache = $this->getMockCache();
1827 $mockCache->expects( $this->never() )->method( 'get' );
1828 $mockCache->expects( $this->never() )->method( 'set' );
1829 $mockCache->expects( $this->never() )->method( 'delete' );
1830
1831 $store = $this->newWatchedItemStore(
1832 $this->getMockLoadBalancer( $mockDb ),
1833 $mockCache
1834 );
1835
1836 $this->assertFalse(
1837 $store->resetNotificationTimestamp(
1838 $this->getAnonUser(),
1839 Title::newFromText( 'SomeDbKey' )
1840 )
1841 );
1842 }
1843
1845 $mockDb = $this->getMockDb();
1846 $mockDb->expects( $this->once() )
1847 ->method( 'selectRow' )
1848 ->with(
1849 'watchlist',
1850 'wl_notificationtimestamp',
1851 [
1852 'wl_user' => 1,
1853 'wl_namespace' => 0,
1854 'wl_title' => 'SomeDbKey',
1855 ]
1856 )
1857 ->will( $this->returnValue( [] ) );
1858
1859 $mockCache = $this->getMockCache();
1860 $mockCache->expects( $this->never() )->method( 'get' );
1861 $mockCache->expects( $this->never() )->method( 'set' );
1862 $mockCache->expects( $this->never() )->method( 'delete' );
1863
1864 $store = $this->newWatchedItemStore(
1865 $this->getMockLoadBalancer( $mockDb ),
1866 $mockCache
1867 );
1868
1869 $this->assertFalse(
1870 $store->resetNotificationTimestamp(
1871 $this->getMockNonAnonUserWithId( 1 ),
1872 Title::newFromText( 'SomeDbKey' )
1873 )
1874 );
1875 }
1876
1878 $user = $this->getMockNonAnonUserWithId( 1 );
1879 $title = Title::newFromText( 'SomeDbKey' );
1880
1881 $mockDb = $this->getMockDb();
1882 $mockDb->expects( $this->once() )
1883 ->method( 'selectRow' )
1884 ->with(
1885 'watchlist',
1886 'wl_notificationtimestamp',
1887 [
1888 'wl_user' => 1,
1889 'wl_namespace' => 0,
1890 'wl_title' => 'SomeDbKey',
1891 ]
1892 )
1893 ->will( $this->returnValue(
1894 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1895 ) );
1896
1897 $mockCache = $this->getMockCache();
1898 $mockCache->expects( $this->never() )->method( 'get' );
1899 $mockCache->expects( $this->once() )
1900 ->method( 'set' )
1901 ->with(
1902 '0:SomeDbKey:1',
1903 $this->isInstanceOf( WatchedItem::class )
1904 );
1905 $mockCache->expects( $this->once() )
1906 ->method( 'delete' )
1907 ->with( '0:SomeDbKey:1' );
1908
1909 $store = $this->newWatchedItemStore(
1910 $this->getMockLoadBalancer( $mockDb ),
1911 $mockCache
1912 );
1913
1914 // Note: This does not actually assert the job is correct
1915 $callableCallCounter = 0;
1916 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1917 $callableCallCounter++;
1918 $this->assertInternalType( 'callable', $callable );
1919 };
1920 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1921
1922 $this->assertTrue(
1923 $store->resetNotificationTimestamp(
1924 $user,
1925 $title
1926 )
1927 );
1928 $this->assertEquals( 1, $callableCallCounter );
1929
1930 ScopedCallback::consume( $scopedOverride );
1931 }
1932
1934 $user = $this->getMockNonAnonUserWithId( 1 );
1935 $title = Title::newFromText( 'SomeDbKey' );
1936
1937 $mockDb = $this->getMockDb();
1938 $mockDb->expects( $this->never() )
1939 ->method( 'selectRow' );
1940
1941 $mockCache = $this->getMockCache();
1942 $mockDb->expects( $this->never() )
1943 ->method( 'get' );
1944 $mockDb->expects( $this->never() )
1945 ->method( 'set' );
1946 $mockDb->expects( $this->never() )
1947 ->method( 'delete' );
1948
1949 $store = $this->newWatchedItemStore(
1950 $this->getMockLoadBalancer( $mockDb ),
1951 $mockCache
1952 );
1953
1954 // Note: This does not actually assert the job is correct
1955 $callableCallCounter = 0;
1956 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1957 $callableCallCounter++;
1958 $this->assertInternalType( 'callable', $callable );
1959 };
1960 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1961
1962 $this->assertTrue(
1963 $store->resetNotificationTimestamp(
1964 $user,
1965 $title,
1966 'force'
1967 )
1968 );
1969 $this->assertEquals( 1, $callableCallCounter );
1970
1971 ScopedCallback::consume( $scopedOverride );
1972 }
1973
1980 private function getMockTitle( $text, $ns = 0 ) {
1981 $title = $this->getMock( Title::class );
1982 $title->expects( $this->any() )
1983 ->method( 'getText' )
1984 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1985 $title->expects( $this->any() )
1986 ->method( 'getDbKey' )
1987 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1988 $title->expects( $this->any() )
1989 ->method( 'getNamespace' )
1990 ->will( $this->returnValue( $ns ) );
1991 return $title;
1992 }
1993
1994 private function verifyCallbackJob(
1995 $callback,
1996 LinkTarget $expectedTitle,
1997 $expectedUserId,
1998 callable $notificationTimestampCondition
1999 ) {
2000 $this->assertInternalType( 'callable', $callback );
2001
2002 $callbackReflector = new ReflectionFunction( $callback );
2003 $vars = $callbackReflector->getStaticVariables();
2004 $this->assertArrayHasKey( 'job', $vars );
2005 $this->assertInstanceOf( ActivityUpdateJob::class, $vars['job'] );
2006
2008 $job = $vars['job'];
2009 $this->assertEquals( $expectedTitle->getDBkey(), $job->getTitle()->getDBkey() );
2010 $this->assertEquals( $expectedTitle->getNamespace(), $job->getTitle()->getNamespace() );
2011
2012 $jobParams = $job->getParams();
2013 $this->assertArrayHasKey( 'type', $jobParams );
2014 $this->assertEquals( 'updateWatchlistNotification', $jobParams['type'] );
2015 $this->assertArrayHasKey( 'userid', $jobParams );
2016 $this->assertEquals( $expectedUserId, $jobParams['userid'] );
2017 $this->assertArrayHasKey( 'notifTime', $jobParams );
2018 $this->assertTrue( $notificationTimestampCondition( $jobParams['notifTime'] ) );
2019 }
2020
2022 $user = $this->getMockNonAnonUserWithId( 1 );
2023 $oldid = 22;
2024 $title = $this->getMockTitle( 'SomeTitle' );
2025 $title->expects( $this->once() )
2026 ->method( 'getNextRevisionID' )
2027 ->with( $oldid )
2028 ->will( $this->returnValue( false ) );
2029
2030 $mockDb = $this->getMockDb();
2031 $mockDb->expects( $this->never() )
2032 ->method( 'selectRow' );
2033
2034 $mockCache = $this->getMockCache();
2035 $mockDb->expects( $this->never() )
2036 ->method( 'get' );
2037 $mockDb->expects( $this->never() )
2038 ->method( 'set' );
2039 $mockDb->expects( $this->never() )
2040 ->method( 'delete' );
2041
2042 $store = $this->newWatchedItemStore(
2043 $this->getMockLoadBalancer( $mockDb ),
2044 $mockCache
2045 );
2046
2047 $callableCallCounter = 0;
2048 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2049 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2050 $callableCallCounter++;
2051 $this->verifyCallbackJob(
2052 $callable,
2053 $title,
2054 $user->getId(),
2055 function( $time ) {
2056 return $time === null;
2057 }
2058 );
2059 }
2060 );
2061
2062 $this->assertTrue(
2063 $store->resetNotificationTimestamp(
2064 $user,
2065 $title,
2066 'force',
2067 $oldid
2068 )
2069 );
2070 $this->assertEquals( 1, $callableCallCounter );
2071
2072 ScopedCallback::consume( $scopedOverride );
2073 }
2074
2076 $user = $this->getMockNonAnonUserWithId( 1 );
2077 $oldid = 22;
2078 $title = $this->getMockTitle( 'SomeDbKey' );
2079 $title->expects( $this->once() )
2080 ->method( 'getNextRevisionID' )
2081 ->with( $oldid )
2082 ->will( $this->returnValue( 33 ) );
2083
2084 $mockDb = $this->getMockDb();
2085 $mockDb->expects( $this->once() )
2086 ->method( 'selectRow' )
2087 ->with(
2088 'watchlist',
2089 'wl_notificationtimestamp',
2090 [
2091 'wl_user' => 1,
2092 'wl_namespace' => 0,
2093 'wl_title' => 'SomeDbKey',
2094 ]
2095 )
2096 ->will( $this->returnValue(
2097 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2098 ) );
2099
2100 $mockCache = $this->getMockCache();
2101 $mockDb->expects( $this->never() )
2102 ->method( 'get' );
2103 $mockDb->expects( $this->never() )
2104 ->method( 'set' );
2105 $mockDb->expects( $this->never() )
2106 ->method( 'delete' );
2107
2108 $store = $this->newWatchedItemStore(
2109 $this->getMockLoadBalancer( $mockDb ),
2110 $mockCache
2111 );
2112
2113 $addUpdateCallCounter = 0;
2114 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2115 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2116 $addUpdateCallCounter++;
2117 $this->verifyCallbackJob(
2118 $callable,
2119 $title,
2120 $user->getId(),
2121 function( $time ) {
2122 return $time !== null && $time > '20151212010101';
2123 }
2124 );
2125 }
2126 );
2127
2128 $getTimestampCallCounter = 0;
2129 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2130 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2131 $getTimestampCallCounter++;
2132 $this->assertEquals( $title, $titleParam );
2133 $this->assertEquals( $oldid, $oldidParam );
2134 }
2135 );
2136
2137 $this->assertTrue(
2138 $store->resetNotificationTimestamp(
2139 $user,
2140 $title,
2141 'force',
2142 $oldid
2143 )
2144 );
2145 $this->assertEquals( 1, $addUpdateCallCounter );
2146 $this->assertEquals( 1, $getTimestampCallCounter );
2147
2148 ScopedCallback::consume( $scopedOverrideDeferred );
2149 ScopedCallback::consume( $scopedOverrideRevision );
2150 }
2151
2153 $user = $this->getMockNonAnonUserWithId( 1 );
2154 $oldid = 22;
2155 $title = $this->getMockTitle( 'SomeDbKey' );
2156 $title->expects( $this->once() )
2157 ->method( 'getNextRevisionID' )
2158 ->with( $oldid )
2159 ->will( $this->returnValue( 33 ) );
2160
2161 $mockDb = $this->getMockDb();
2162 $mockDb->expects( $this->once() )
2163 ->method( 'selectRow' )
2164 ->with(
2165 'watchlist',
2166 'wl_notificationtimestamp',
2167 [
2168 'wl_user' => 1,
2169 'wl_namespace' => 0,
2170 'wl_title' => 'SomeDbKey',
2171 ]
2172 )
2173 ->will( $this->returnValue( false ) );
2174
2175 $mockCache = $this->getMockCache();
2176 $mockDb->expects( $this->never() )
2177 ->method( 'get' );
2178 $mockDb->expects( $this->never() )
2179 ->method( 'set' );
2180 $mockDb->expects( $this->never() )
2181 ->method( 'delete' );
2182
2183 $store = $this->newWatchedItemStore(
2184 $this->getMockLoadBalancer( $mockDb ),
2185 $mockCache
2186 );
2187
2188 $callableCallCounter = 0;
2189 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2190 function( $callable ) use ( &$callableCallCounter, $title, $user ) {
2191 $callableCallCounter++;
2192 $this->verifyCallbackJob(
2193 $callable,
2194 $title,
2195 $user->getId(),
2196 function( $time ) {
2197 return $time === null;
2198 }
2199 );
2200 }
2201 );
2202
2203 $this->assertTrue(
2204 $store->resetNotificationTimestamp(
2205 $user,
2206 $title,
2207 'force',
2208 $oldid
2209 )
2210 );
2211 $this->assertEquals( 1, $callableCallCounter );
2212
2213 ScopedCallback::consume( $scopedOverride );
2214 }
2215
2217 $user = $this->getMockNonAnonUserWithId( 1 );
2218 $oldid = 22;
2219 $title = $this->getMockTitle( 'SomeDbKey' );
2220 $title->expects( $this->once() )
2221 ->method( 'getNextRevisionID' )
2222 ->with( $oldid )
2223 ->will( $this->returnValue( 33 ) );
2224
2225 $mockDb = $this->getMockDb();
2226 $mockDb->expects( $this->once() )
2227 ->method( 'selectRow' )
2228 ->with(
2229 'watchlist',
2230 'wl_notificationtimestamp',
2231 [
2232 'wl_user' => 1,
2233 'wl_namespace' => 0,
2234 'wl_title' => 'SomeDbKey',
2235 ]
2236 )
2237 ->will( $this->returnValue(
2238 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2239 ) );
2240
2241 $mockCache = $this->getMockCache();
2242 $mockDb->expects( $this->never() )
2243 ->method( 'get' );
2244 $mockDb->expects( $this->never() )
2245 ->method( 'set' );
2246 $mockDb->expects( $this->never() )
2247 ->method( 'delete' );
2248
2249 $store = $this->newWatchedItemStore(
2250 $this->getMockLoadBalancer( $mockDb ),
2251 $mockCache
2252 );
2253
2254 $addUpdateCallCounter = 0;
2255 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2256 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2257 $addUpdateCallCounter++;
2258 $this->verifyCallbackJob(
2259 $callable,
2260 $title,
2261 $user->getId(),
2262 function( $time ) {
2263 return $time === '30151212010101';
2264 }
2265 );
2266 }
2267 );
2268
2269 $getTimestampCallCounter = 0;
2270 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2271 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2272 $getTimestampCallCounter++;
2273 $this->assertEquals( $title, $titleParam );
2274 $this->assertEquals( $oldid, $oldidParam );
2275 }
2276 );
2277
2278 $this->assertTrue(
2279 $store->resetNotificationTimestamp(
2280 $user,
2281 $title,
2282 'force',
2283 $oldid
2284 )
2285 );
2286 $this->assertEquals( 1, $addUpdateCallCounter );
2287 $this->assertEquals( 1, $getTimestampCallCounter );
2288
2289 ScopedCallback::consume( $scopedOverrideDeferred );
2290 ScopedCallback::consume( $scopedOverrideRevision );
2291 }
2292
2294 $user = $this->getMockNonAnonUserWithId( 1 );
2295 $oldid = 22;
2296 $title = $this->getMockTitle( 'SomeDbKey' );
2297 $title->expects( $this->once() )
2298 ->method( 'getNextRevisionID' )
2299 ->with( $oldid )
2300 ->will( $this->returnValue( 33 ) );
2301
2302 $mockDb = $this->getMockDb();
2303 $mockDb->expects( $this->once() )
2304 ->method( 'selectRow' )
2305 ->with(
2306 'watchlist',
2307 'wl_notificationtimestamp',
2308 [
2309 'wl_user' => 1,
2310 'wl_namespace' => 0,
2311 'wl_title' => 'SomeDbKey',
2312 ]
2313 )
2314 ->will( $this->returnValue(
2315 $this->getFakeRow( [ 'wl_notificationtimestamp' => '30151212010101' ] )
2316 ) );
2317
2318 $mockCache = $this->getMockCache();
2319 $mockDb->expects( $this->never() )
2320 ->method( 'get' );
2321 $mockDb->expects( $this->never() )
2322 ->method( 'set' );
2323 $mockDb->expects( $this->never() )
2324 ->method( 'delete' );
2325
2326 $store = $this->newWatchedItemStore(
2327 $this->getMockLoadBalancer( $mockDb ),
2328 $mockCache
2329 );
2330
2331 $addUpdateCallCounter = 0;
2332 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2333 function( $callable ) use ( &$addUpdateCallCounter, $title, $user ) {
2334 $addUpdateCallCounter++;
2335 $this->verifyCallbackJob(
2336 $callable,
2337 $title,
2338 $user->getId(),
2339 function( $time ) {
2340 return $time === false;
2341 }
2342 );
2343 }
2344 );
2345
2346 $getTimestampCallCounter = 0;
2347 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2348 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2349 $getTimestampCallCounter++;
2350 $this->assertEquals( $title, $titleParam );
2351 $this->assertEquals( $oldid, $oldidParam );
2352 }
2353 );
2354
2355 $this->assertTrue(
2356 $store->resetNotificationTimestamp(
2357 $user,
2358 $title,
2359 '',
2360 $oldid
2361 )
2362 );
2363 $this->assertEquals( 1, $addUpdateCallCounter );
2364 $this->assertEquals( 1, $getTimestampCallCounter );
2365
2366 ScopedCallback::consume( $scopedOverrideDeferred );
2367 ScopedCallback::consume( $scopedOverrideRevision );
2368 }
2369
2371 $store = $this->newWatchedItemStore(
2372 $this->getMockLoadBalancer( $this->getMockDb() ),
2373 $this->getMockCache()
2374 );
2375 $this->assertFalse( $store->setNotificationTimestampsForUser( $this->getAnonUser(), '' ) );
2376 }
2377
2379 $user = $this->getMockNonAnonUserWithId( 1 );
2380 $timestamp = '20100101010101';
2381
2382 $mockDb = $this->getMockDb();
2383 $mockDb->expects( $this->once() )
2384 ->method( 'update' )
2385 ->with(
2386 'watchlist',
2387 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2388 [ 'wl_user' => 1 ]
2389 )
2390 ->will( $this->returnValue( true ) );
2391 $mockDb->expects( $this->exactly( 1 ) )
2392 ->method( 'timestamp' )
2393 ->will( $this->returnCallback( function( $value ) {
2394 return 'TS' . $value . 'TS';
2395 } ) );
2396
2397 $store = $this->newWatchedItemStore(
2398 $this->getMockLoadBalancer( $mockDb ),
2399 $this->getMockCache()
2400 );
2401
2402 $this->assertTrue(
2403 $store->setNotificationTimestampsForUser( $user, $timestamp )
2404 );
2405 }
2406
2408 $user = $this->getMockNonAnonUserWithId( 1 );
2409 $timestamp = '20100101010101';
2410 $targets = [ new TitleValue( 0, 'Foo' ), new TitleValue( 0, 'Bar' ) ];
2411
2412 $mockDb = $this->getMockDb();
2413 $mockDb->expects( $this->once() )
2414 ->method( 'update' )
2415 ->with(
2416 'watchlist',
2417 [ 'wl_notificationtimestamp' => 'TS' . $timestamp . 'TS' ],
2418 [ 'wl_user' => 1, 0 => 'makeWhereFrom2d return value' ]
2419 )
2420 ->will( $this->returnValue( true ) );
2421 $mockDb->expects( $this->exactly( 1 ) )
2422 ->method( 'timestamp' )
2423 ->will( $this->returnCallback( function( $value ) {
2424 return 'TS' . $value . 'TS';
2425 } ) );
2426 $mockDb->expects( $this->once() )
2427 ->method( 'makeWhereFrom2d' )
2428 ->with(
2429 [ [ 'Foo' => 1, 'Bar' => 1 ] ],
2430 $this->isType( 'string' ),
2431 $this->isType( 'string' )
2432 )
2433 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
2434
2435 $store = $this->newWatchedItemStore(
2436 $this->getMockLoadBalancer( $mockDb ),
2437 $this->getMockCache()
2438 );
2439
2440 $this->assertTrue(
2441 $store->setNotificationTimestampsForUser( $user, $timestamp, $targets )
2442 );
2443 }
2444
2446 $mockDb = $this->getMockDb();
2447 $mockDb->expects( $this->once() )
2448 ->method( 'selectFieldValues' )
2449 ->with(
2450 'watchlist',
2451 'wl_user',
2452 [
2453 'wl_user != 1',
2454 'wl_namespace' => 0,
2455 'wl_title' => 'SomeDbKey',
2456 'wl_notificationtimestamp IS NULL'
2457 ]
2458 )
2459 ->will( $this->returnValue( [ '2', '3' ] ) );
2460 $mockDb->expects( $this->once() )
2461 ->method( 'update' )
2462 ->with(
2463 'watchlist',
2464 [ 'wl_notificationtimestamp' => null ],
2465 [
2466 'wl_user' => [ 2, 3 ],
2467 'wl_namespace' => 0,
2468 'wl_title' => 'SomeDbKey',
2469 ]
2470 );
2471
2472 $mockCache = $this->getMockCache();
2473 $mockCache->expects( $this->never() )->method( 'set' );
2474 $mockCache->expects( $this->never() )->method( 'get' );
2475 $mockCache->expects( $this->never() )->method( 'delete' );
2476
2477 $store = $this->newWatchedItemStore(
2478 $this->getMockLoadBalancer( $mockDb ),
2479 $mockCache
2480 );
2481
2482 $this->assertEquals(
2483 [ 2, 3 ],
2484 $store->updateNotificationTimestamp(
2485 $this->getMockNonAnonUserWithId( 1 ),
2486 new TitleValue( 0, 'SomeDbKey' ),
2487 '20151212010101'
2488 )
2489 );
2490 }
2491
2493 $mockDb = $this->getMockDb();
2494 $mockDb->expects( $this->once() )
2495 ->method( 'selectFieldValues' )
2496 ->with(
2497 'watchlist',
2498 'wl_user',
2499 [
2500 'wl_user != 1',
2501 'wl_namespace' => 0,
2502 'wl_title' => 'SomeDbKey',
2503 'wl_notificationtimestamp IS NULL'
2504 ]
2505 )
2506 ->will(
2507 $this->returnValue( [] )
2508 );
2509 $mockDb->expects( $this->never() )
2510 ->method( 'update' );
2511
2512 $mockCache = $this->getMockCache();
2513 $mockCache->expects( $this->never() )->method( 'set' );
2514 $mockCache->expects( $this->never() )->method( 'get' );
2515 $mockCache->expects( $this->never() )->method( 'delete' );
2516
2517 $store = $this->newWatchedItemStore(
2518 $this->getMockLoadBalancer( $mockDb ),
2519 $mockCache
2520 );
2521
2522 $watchers = $store->updateNotificationTimestamp(
2523 $this->getMockNonAnonUserWithId( 1 ),
2524 new TitleValue( 0, 'SomeDbKey' ),
2525 '20151212010101'
2526 );
2527 $this->assertInternalType( 'array', $watchers );
2528 $this->assertEmpty( $watchers );
2529 }
2530
2532 $user = $this->getMockNonAnonUserWithId( 1 );
2533 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2534
2535 $mockDb = $this->getMockDb();
2536 $mockDb->expects( $this->once() )
2537 ->method( 'selectRow' )
2538 ->will( $this->returnValue(
2539 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2540 ) );
2541 $mockDb->expects( $this->once() )
2542 ->method( 'selectFieldValues' )
2543 ->will(
2544 $this->returnValue( [ '2', '3' ] )
2545 );
2546 $mockDb->expects( $this->once() )
2547 ->method( 'update' );
2548
2549 $mockCache = $this->getMockCache();
2550 $mockCache->expects( $this->once() )
2551 ->method( 'set' )
2552 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2553 $mockCache->expects( $this->once() )
2554 ->method( 'get' )
2555 ->with( '0:SomeDbKey:1' );
2556 $mockCache->expects( $this->once() )
2557 ->method( 'delete' )
2558 ->with( '0:SomeDbKey:1' );
2559
2560 $store = $this->newWatchedItemStore(
2561 $this->getMockLoadBalancer( $mockDb ),
2562 $mockCache
2563 );
2564
2565 // This will add the item to the cache
2566 $store->getWatchedItem( $user, $titleValue );
2567
2568 $store->updateNotificationTimestamp(
2569 $this->getMockNonAnonUserWithId( 1 ),
2570 $titleValue,
2571 '20151212010101'
2572 );
2573 }
2574
2575}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
</td >< td > &</td >< td > t want your writing to be edited mercilessly and redistributed at will
const DB_SLAVE
Definition Defines.php:28
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Simple store for keeping values in an associative array for the current process.
Database connection, tracking, load balancing, and transaction manager for a cluster.
Represents a page (or page fragment) title within MediaWiki.
testCountWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
testDuplicateAllAssociatedEntries_somethingToDuplicate(LinkTarget $oldTarget, LinkTarget $newTarget)
provideLinkTargetPairs
getMockLoadBalancer( $mockDb, $expectedConnectionType=null, $readOnlyReason=false)
testCountUnreadNotifications_withUnreadLimit_overLimit( $limit)
provideIntWithDbUnsafeVersion
testCountUnreadNotifications_withUnreadLimit_underLimit( $limit)
provideIntWithDbUnsafeVersion
newWatchedItemStore(LoadBalancer $loadBalancer, HashBagOStuff $cache)
testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType)
provideDbTypes
verifyCallbackJob( $callback, LinkTarget $expectedTitle, $expectedUserId, callable $notificationTimestampCondition)
testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
Storage layer class for WatchedItems.
Representation of a pair of user and title for watchlist entries.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break anything
const LIST_AND
Definition Defines.php:35
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2162
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition hooks.txt:1135
if( $limit) $timestamp
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
$cache
Definition mcc.php:33
const DB_MASTER
Definition defines.php:23
if(count( $args)< 1) $job