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