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