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