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