MediaWiki  1.33.0
WatchedItemStoreUnitTest.php
Go to the documentation of this file.
1 <?php
5 use Wikimedia\ScopedCallback;
6 use Wikimedia\TestingAccessWrapper;
7 
14 
18  private function getMockDb() {
19  return $this->createMock( IDatabase::class );
20  }
21 
25  private function getMockLoadBalancer(
26  $mockDb,
27  $expectedConnectionType = null
28  ) {
29  $mock = $this->getMockBuilder( LoadBalancer::class )
30  ->disableOriginalConstructor()
31  ->getMock();
32  if ( $expectedConnectionType !== null ) {
33  $mock->expects( $this->any() )
34  ->method( 'getConnectionRef' )
35  ->with( $expectedConnectionType )
36  ->will( $this->returnValue( $mockDb ) );
37  } else {
38  $mock->expects( $this->any() )
39  ->method( 'getConnectionRef' )
40  ->will( $this->returnValue( $mockDb ) );
41  }
42  return $mock;
43  }
44 
48  private function getMockLBFactory(
49  $mockDb,
50  $expectedConnectionType = null
51  ) {
52  $loadBalancer = $this->getMockLoadBalancer( $mockDb, $expectedConnectionType );
53  $mock = $this->getMockBuilder( LBFactory::class )
54  ->disableOriginalConstructor()
55  ->getMock();
56  $mock->expects( $this->any() )
57  ->method( 'getMainLB' )
58  ->will( $this->returnValue( $loadBalancer ) );
59  return $mock;
60  }
61 
65  private function getMockJobQueueGroup() {
66  $mock = $this->getMockBuilder( JobQueueGroup::class )
67  ->disableOriginalConstructor()
68  ->getMock();
69  $mock->expects( $this->any() )
70  ->method( 'push' )
71  ->will( $this->returnCallback( function ( Job $job ) {
72  $job->run();
73  } ) );
74  $mock->expects( $this->any() )
75  ->method( 'lazyPush' )
76  ->will( $this->returnCallback( function ( Job $job ) {
77  $job->run();
78  } ) );
79  return $mock;
80  }
81 
85  private function getMockCache() {
86  $mock = $this->getMockBuilder( HashBagOStuff::class )
87  ->disableOriginalConstructor()
88  ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
89  ->getMock();
90  $mock->expects( $this->any() )
91  ->method( 'makeKey' )
92  ->will( $this->returnCallback( function () {
93  return implode( ':', func_get_args() );
94  } ) );
95  return $mock;
96  }
97 
101  private function getMockReadOnlyMode( $readOnly = false ) {
102  $mock = $this->getMockBuilder( ReadOnlyMode::class )
103  ->disableOriginalConstructor()
104  ->getMock();
105  $mock->expects( $this->any() )
106  ->method( 'isReadOnly' )
107  ->will( $this->returnValue( $readOnly ) );
108  return $mock;
109  }
110 
115  private function getMockNonAnonUserWithId( $id ) {
116  $mock = $this->createMock( User::class );
117  $mock->expects( $this->any() )
118  ->method( 'isAnon' )
119  ->will( $this->returnValue( false ) );
120  $mock->expects( $this->any() )
121  ->method( 'getId' )
122  ->will( $this->returnValue( $id ) );
123  $mock->expects( $this->any() )
124  ->method( 'getUserPage' )
125  ->will( $this->returnValue( Title::makeTitle( NS_USER, 'MockUser' ) ) );
126  return $mock;
127  }
128 
132  private function getAnonUser() {
133  return User::newFromName( 'Anon_User' );
134  }
135 
136  private function getFakeRow( array $rowValues ) {
137  $fakeRow = new stdClass();
138  foreach ( $rowValues as $valueName => $value ) {
139  $fakeRow->$valueName = $value;
140  }
141  return $fakeRow;
142  }
143 
144  private function newWatchedItemStore(
145  LBFactory $lbFactory,
146  JobQueueGroup $queueGroup,
148  ReadOnlyMode $readOnlyMode
149  ) {
150  return new WatchedItemStore(
151  $lbFactory,
152  $queueGroup,
153  new HashBagOStuff(),
154  $cache,
155  $readOnlyMode,
156  1000
157  );
158  }
159 
160  public function testClearWatchedItems() {
161  $user = $this->getMockNonAnonUserWithId( 7 );
162 
163  $mockDb = $this->getMockDb();
164  $mockDb->expects( $this->once() )
165  ->method( 'selectField' )
166  ->with(
167  'watchlist',
168  'COUNT(*)',
169  [
170  'wl_user' => $user->getId(),
171  ],
172  $this->isType( 'string' )
173  )
174  ->will( $this->returnValue( 12 ) );
175  $mockDb->expects( $this->once() )
176  ->method( 'delete' )
177  ->with(
178  'watchlist',
179  [ 'wl_user' => 7 ],
180  $this->isType( 'string' )
181  );
182 
183  $mockCache = $this->getMockCache();
184  $mockCache->expects( $this->never() )->method( 'get' );
185  $mockCache->expects( $this->never() )->method( 'set' );
186  $mockCache->expects( $this->once() )
187  ->method( 'delete' )
188  ->with( 'RM-KEY' );
189 
190  $store = $this->newWatchedItemStore(
191  $this->getMockLBFactory( $mockDb ),
192  $this->getMockJobQueueGroup(),
193  $mockCache,
194  $this->getMockReadOnlyMode()
195  );
196  TestingAccessWrapper::newFromObject( $store )
197  ->cacheIndex = [ 0 => [ 'F' => [ 7 => 'RM-KEY', 9 => 'KEEP-KEY' ] ] ];
198 
199  $this->assertTrue( $store->clearUserWatchedItems( $user ) );
200  }
201 
203  $user = $this->getMockNonAnonUserWithId( 7 );
204 
205  $mockDb = $this->getMockDb();
206  $mockDb->expects( $this->once() )
207  ->method( 'selectField' )
208  ->with(
209  'watchlist',
210  'COUNT(*)',
211  [
212  'wl_user' => $user->getId(),
213  ],
214  $this->isType( 'string' )
215  )
216  ->will( $this->returnValue( 99999 ) );
217 
218  $mockCache = $this->getMockCache();
219  $mockCache->expects( $this->never() )->method( 'get' );
220  $mockCache->expects( $this->never() )->method( 'set' );
221  $mockCache->expects( $this->never() )->method( 'delete' );
222 
223  $store = $this->newWatchedItemStore(
224  $this->getMockLBFactory( $mockDb ),
225  $this->getMockJobQueueGroup(),
226  $mockCache,
227  $this->getMockReadOnlyMode()
228  );
229 
230  $this->assertFalse( $store->clearUserWatchedItems( $user ) );
231  }
232 
233  public function testCountWatchedItems() {
234  $user = $this->getMockNonAnonUserWithId( 1 );
235 
236  $mockDb = $this->getMockDb();
237  $mockDb->expects( $this->exactly( 1 ) )
238  ->method( 'selectField' )
239  ->with(
240  'watchlist',
241  'COUNT(*)',
242  [
243  'wl_user' => $user->getId(),
244  ],
245  $this->isType( 'string' )
246  )
247  ->will( $this->returnValue( '12' ) );
248 
249  $mockCache = $this->getMockCache();
250  $mockCache->expects( $this->never() )->method( 'get' );
251  $mockCache->expects( $this->never() )->method( 'set' );
252  $mockCache->expects( $this->never() )->method( 'delete' );
253 
254  $store = $this->newWatchedItemStore(
255  $this->getMockLBFactory( $mockDb ),
256  $this->getMockJobQueueGroup(),
257  $mockCache,
258  $this->getMockReadOnlyMode()
259  );
260 
261  $this->assertEquals( 12, $store->countWatchedItems( $user ) );
262  }
263 
264  public function testCountWatchers() {
265  $titleValue = new TitleValue( 0, 'SomeDbKey' );
266 
267  $mockDb = $this->getMockDb();
268  $mockDb->expects( $this->exactly( 1 ) )
269  ->method( 'selectField' )
270  ->with(
271  'watchlist',
272  'COUNT(*)',
273  [
274  'wl_namespace' => $titleValue->getNamespace(),
275  'wl_title' => $titleValue->getDBkey(),
276  ],
277  $this->isType( 'string' )
278  )
279  ->will( $this->returnValue( '7' ) );
280 
281  $mockCache = $this->getMockCache();
282  $mockCache->expects( $this->never() )->method( 'get' );
283  $mockCache->expects( $this->never() )->method( 'set' );
284  $mockCache->expects( $this->never() )->method( 'delete' );
285 
286  $store = $this->newWatchedItemStore(
287  $this->getMockLBFactory( $mockDb ),
288  $this->getMockJobQueueGroup(),
289  $mockCache,
290  $this->getMockReadOnlyMode()
291  );
292 
293  $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
294  }
295 
296  public function testCountWatchersMultiple() {
297  $titleValues = [
298  new TitleValue( 0, 'SomeDbKey' ),
299  new TitleValue( 0, 'OtherDbKey' ),
300  new TitleValue( 1, 'AnotherDbKey' ),
301  ];
302 
303  $mockDb = $this->getMockDb();
304 
305  $dbResult = [
306  $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => '0', 'watchers' => '100' ] ),
307  $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => '0', 'watchers' => '300' ] ),
308  $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => '1', 'watchers' => '500' ]
309  ),
310  ];
311  $mockDb->expects( $this->once() )
312  ->method( 'makeWhereFrom2d' )
313  ->with(
314  [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
315  $this->isType( 'string' ),
316  $this->isType( 'string' )
317  )
318  ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
319  $mockDb->expects( $this->once() )
320  ->method( 'select' )
321  ->with(
322  'watchlist',
323  [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
324  [ 'makeWhereFrom2d return value' ],
325  $this->isType( 'string' ),
326  [
327  'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
328  ]
329  )
330  ->will(
331  $this->returnValue( $dbResult )
332  );
333 
334  $mockCache = $this->getMockCache();
335  $mockCache->expects( $this->never() )->method( 'get' );
336  $mockCache->expects( $this->never() )->method( 'set' );
337  $mockCache->expects( $this->never() )->method( 'delete' );
338 
339  $store = $this->newWatchedItemStore(
340  $this->getMockLBFactory( $mockDb ),
341  $this->getMockJobQueueGroup(),
342  $mockCache,
343  $this->getMockReadOnlyMode()
344  );
345 
346  $expected = [
347  0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
348  1 => [ 'AnotherDbKey' => 500 ],
349  ];
350  $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
351  }
352 
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 }
WatchedItemStoreUnitTest\testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced
testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced()
Definition: WatchedItemStoreUnitTest.php:2320
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:306
WatchedItemStoreUnitTest\testSetNotificationTimestampsForUser_allRows
testSetNotificationTimestampsForUser_allRows()
Definition: WatchedItemStoreUnitTest.php:2630
ActivityUpdateJob
Job for updating user activity like "last viewed" timestamps.
Definition: ActivityUpdateJob.php:34
WatchedItemStoreUnitTest\testGetNotificationTimestampsBatch_anonymousUser
testGetNotificationTimestampsBatch_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:2049
WatchedItemStoreUnitTest\testDuplicateEntry_somethingToDuplicate
testDuplicateEntry_somethingToDuplicate()
Definition: WatchedItemStoreUnitTest.php:860
WatchedItemStoreUnitTest\testCountVisitingWatchersMultiple
testCountVisitingWatchersMultiple()
Definition: WatchedItemStoreUnitTest.php:467
WatchedItemStoreUnitTest\testCountVisitingWatchersMultiple_withMissingTargets
testCountVisitingWatchersMultiple_withMissingTargets()
Definition: WatchedItemStoreUnitTest.php:557
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
WatchedItemStoreUnitTest\getMockTitle
getMockTitle( $text, $ns=0)
Definition: WatchedItemStoreUnitTest.php:2235
WatchedItemStoreUnitTest\testResetNotificationTimestamp_noItem
testResetNotificationTimestamp_noItem()
Definition: WatchedItemStoreUnitTest.php:2102
WatchedItemStoreUnitTest\testGetWatchedItemsForUser_badSortOptionThrowsException
testGetWatchedItemsForUser_badSortOptionThrowsException()
Definition: WatchedItemStoreUnitTest.php:1700
WatchedItemStoreUnitTest\testAddWatchBatchForUser_nonAnonymousUser
testAddWatchBatchForUser_nonAnonymousUser()
Definition: WatchedItemStoreUnitTest.php:1135
WatchedItemStoreUnitTest\getMockCache
getMockCache()
Definition: WatchedItemStoreUnitTest.php:85
WatchedItemStoreUnitTest\testIsWatchedItem_noItem
testIsWatchedItem_noItem()
Definition: WatchedItemStoreUnitTest.php:1759
ReadOnlyMode
A service class for fetching the wiki's current read-only mode.
Definition: ReadOnlyMode.php:11
WatchedItemStoreUnitTest\testCountWatchersMultiple
testCountWatchersMultiple()
Definition: WatchedItemStoreUnitTest.php:296
WatchedItemStoreUnitTest\testSetNotificationTimestampsForUser_anonUser
testSetNotificationTimestampsForUser_anonUser()
Definition: WatchedItemStoreUnitTest.php:2620
anything
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break anything
Definition: distributors.txt:39
WatchedItemStoreUnitTest\testCountVisitingWatchers
testCountVisitingWatchers()
Definition: WatchedItemStoreUnitTest.php:424
WatchedItemStoreUnitTest\provideIntWithDbUnsafeVersion
provideIntWithDbUnsafeVersion()
Definition: WatchedItemStoreUnitTest.php:353
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
WatchedItemStoreUnitTest\getFakeRow
getFakeRow(array $rowValues)
Definition: WatchedItemStoreUnitTest.php:136
WatchedItemStoreUnitTest\getMockLBFactory
getMockLBFactory( $mockDb, $expectedConnectionType=null)
Definition: WatchedItemStoreUnitTest.php:48
$s
$s
Definition: mergeMessageFileList.php:186
WatchedItemStoreUnitTest\getMockDb
getMockDb()
Definition: WatchedItemStoreUnitTest.php:18
WatchedItemStoreUnitTest
Definition: WatchedItemStoreUnitTest.php:13
WatchedItemStoreUnitTest\getAnonUser
getAnonUser()
Definition: WatchedItemStoreUnitTest.php:132
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
WatchedItemStoreUnitTest\provideDbTypes
provideDbTypes()
Definition: WatchedItemStoreUnitTest.php:1659
LIST_AND
const LIST_AND
Definition: Defines.php:43
WatchedItemStoreUnitTest\testAddWatch_nonAnonymousUser
testAddWatch_nonAnonymousUser()
Definition: WatchedItemStoreUnitTest.php:1063
WatchedItemStoreUnitTest\getMockJobQueueGroup
getMockJobQueueGroup()
Definition: WatchedItemStoreUnitTest.php:65
WatchedItemStoreUnitTest\newWatchedItemStore
newWatchedItemStore(LBFactory $lbFactory, JobQueueGroup $queueGroup, HashBagOStuff $cache, ReadOnlyMode $readOnlyMode)
Definition: WatchedItemStoreUnitTest.php:144
WatchedItemStoreUnitTest\testClearWatchedItems_tooManyItemsWatched
testClearWatchedItems_tooManyItemsWatched()
Definition: WatchedItemStoreUnitTest.php:202
WatchedItemStoreUnitTest\verifyCallbackJob
verifyCallbackJob(ActivityUpdateJob $job, LinkTarget $expectedTitle, $expectedUserId, callable $notificationTimestampCondition)
Definition: WatchedItemStoreUnitTest.php:2249
WatchedItemStoreUnitTest\testSetNotificationTimestampsForUser_nullTimestamp
testSetNotificationTimestampsForUser_nullTimestamp()
Definition: WatchedItemStoreUnitTest.php:2655
WatchedItemStoreUnitTest\getMockLoadBalancer
getMockLoadBalancer( $mockDb, $expectedConnectionType=null)
Definition: WatchedItemStoreUnitTest.php:25
Job
Class to both describe a background job and handle jobs.
Definition: Job.php:30
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
WatchedItemStoreUnitTest\testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced
testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced()
Definition: WatchedItemStoreUnitTest.php:2267
WatchedItemStoreUnitTest\testRemoveWatch_existingItem
testRemoveWatch_existingItem()
Definition: WatchedItemStoreUnitTest.php:1333
WatchedItemStoreUnitTest\testGetNotificationTimestampsBatch
testGetNotificationTimestampsBatch()
Definition: WatchedItemStoreUnitTest.php:1822
MediaWikiTestCase
Definition: MediaWikiTestCase.php:17
WatchedItemStoreUnitTest\testCountVisitingWatchersMultiple_withMinimumWatchers
testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
Definition: WatchedItemStoreUnitTest.php:669
WatchedItemStoreUnitTest\testResetNotificationTimestamp_item
testResetNotificationTimestamp_item()
Definition: WatchedItemStoreUnitTest.php:2137
WatchedItemStoreUnitTest\testCountWatchers
testCountWatchers()
Definition: WatchedItemStoreUnitTest.php:264
WatchedItemStoreUnitTest\testGetWatchedItem_noItem
testGetWatchedItem_noItem()
Definition: WatchedItemStoreUnitTest.php:1544
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
WatchedItemStoreUnitTest\testResetNotificationTimestamp_notWatchedPageForced
testResetNotificationTimestamp_notWatchedPageForced()
Definition: WatchedItemStoreUnitTest.php:2399
WatchedItemStoreUnitTest\testResetNotificationTimestamp_futureNotificationTimestampNotForced
testResetNotificationTimestamp_futureNotificationTimestampNotForced()
Definition: WatchedItemStoreUnitTest.php:2541
WatchedItemStoreUnitTest\testGetWatchedItem_anonymousUser
testGetWatchedItem_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:1582
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:576
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
WatchedItemStoreUnitTest\testGetNotificationTimestampsBatch_allItemsCached
testGetNotificationTimestampsBatch_allItemsCached()
Definition: WatchedItemStoreUnitTest.php:2007
WatchedItemStoreUnitTest\testRemoveWatch_noItem
testRemoveWatch_noItem()
Definition: WatchedItemStoreUnitTest.php:1384
WatchedItemStoreUnitTest\testDuplicateAllAssociatedEntries_nothingToDuplicate
testDuplicateAllAssociatedEntries_nothingToDuplicate()
Definition: WatchedItemStoreUnitTest.php:920
DB_MASTER
const DB_MASTER
Definition: defines.php:26
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
WatchedItemStoreUnitTest\testClearWatchedItems
testClearWatchedItems()
Definition: WatchedItemStoreUnitTest.php:160
WatchedItemStoreUnitTest\testGetNotificationTimestampsBatch_notWatchedTarget
testGetNotificationTimestampsBatch_notWatchedTarget()
Definition: WatchedItemStoreUnitTest.php:1892
WatchedItemStoreUnitTest\testCountWatchersMultiple_withMinimumWatchers
testCountWatchersMultiple_withMinimumWatchers( $minWatchers)
provideIntWithDbUnsafeVersion
Definition: WatchedItemStoreUnitTest.php:363
WatchedItemStoreUnitTest\testLoadWatchedItem_existingItem
testLoadWatchedItem_existingItem()
Definition: WatchedItemStoreUnitTest.php:1234
WatchedItemStoreUnitTest\testRemoveWatch_anonymousUser
testRemoveWatch_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:1436
will
</td >< td > &</td >< td > t want your writing to be edited mercilessly and redistributed at will
Definition: All_system_messages.txt:914
Wikimedia\Rdbms\LoadBalancer
Database connection, tracking, load balancing, and transaction manager for a cluster.
Definition: LoadBalancer.php:41
null
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
WatchedItemStoreUnitTest\testUpdateNotificationTimestamp_clearsCachedItems
testUpdateNotificationTimestamp_clearsCachedItems()
Definition: WatchedItemStoreUnitTest.php:2804
WatchedItemStoreUnitTest\testAddWatch_anonymousUser
testAddWatch_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:1097
WatchedItemStoreUnitTest\testAddWatchBatchForUser_readOnlyDBReturnsFalse
testAddWatchBatchForUser_readOnlyDBReturnsFalse()
Definition: WatchedItemStoreUnitTest.php:1119
WatchedItemStoreUnitTest\testUpdateNotificationTimestamp_watchersExist
testUpdateNotificationTimestamp_watchersExist()
Definition: WatchedItemStoreUnitTest.php:2714
any
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition: COPYING.txt:326
$value
$value
Definition: styleTest.css.php:49
WatchedItemStoreUnitTest\testSetNotificationTimestampsForUser_specificTargets
testSetNotificationTimestampsForUser_specificTargets()
Definition: WatchedItemStoreUnitTest.php:2679
WatchedItemStoreUnitTest\testGetWatchedItem_cachedItem
testGetWatchedItem_cachedItem()
Definition: WatchedItemStoreUnitTest.php:1509
WatchedItemStoreUnitTest\testCountUnreadNotifications_withUnreadLimit_underLimit
testCountUnreadNotifications_withUnreadLimit_underLimit( $limit)
provideIntWithDbUnsafeVersion
Definition: WatchedItemStoreUnitTest.php:792
WatchedItemStoreUnitTest\provideLinkTargetPairs
provideLinkTargetPairs()
Definition: WatchedItemStoreUnitTest.php:968
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:32
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
WatchedItemStoreUnitTest\testAddWatchBatchForUser_anonymousUsersAreSkipped
testAddWatchBatchForUser_anonymousUsersAreSkipped()
Definition: WatchedItemStoreUnitTest.php:1188
WatchedItemStoreUnitTest\testIsWatchedItem_anonymousUser
testIsWatchedItem_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:1797
WatchedItemStoreInterface\SORT_ASC
const SORT_ASC
Definition: WatchedItemStoreInterface.php:33
WatchedItemStoreUnitTest\testCountUnreadNotifications
testCountUnreadNotifications()
Definition: WatchedItemStoreUnitTest.php:718
WatchedItemStoreUnitTest\testDuplicateAllAssociatedEntries_somethingToDuplicate
testDuplicateAllAssociatedEntries_somethingToDuplicate(LinkTarget $oldTarget, LinkTarget $newTarget)
provideLinkTargetPairs
Definition: WatchedItemStoreUnitTest.php:978
WatchedItemStoreUnitTest\testLoadWatchedItem_noItem
testLoadWatchedItem_noItem()
Definition: WatchedItemStoreUnitTest.php:1275
WatchedItemStore
Storage layer class for WatchedItems.
Definition: WatchedItemStore.php:19
WatchedItemStoreUnitTest\testDuplicateEntry_nothingToDuplicate
testDuplicateEntry_nothingToDuplicate()
Definition: WatchedItemStoreUnitTest.php:828
$cache
$cache
Definition: mcc.php:33
WatchedItemStoreUnitTest\getMockNonAnonUserWithId
getMockNonAnonUserWithId( $id)
Definition: WatchedItemStoreUnitTest.php:115
WatchedItemStoreUnitTest\testResetNotificationTimestamp_anonymousUser
testResetNotificationTimestamp_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:2077
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:49
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:39
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
WatchedItemStoreUnitTest\testIsWatchedItem_existingItem
testIsWatchedItem_existingItem()
Definition: WatchedItemStoreUnitTest.php:1715
WatchedItemStoreUnitTest\getMockReadOnlyMode
getMockReadOnlyMode( $readOnly=false)
Definition: WatchedItemStoreUnitTest.php:101
WatchedItemStoreUnitTest\testCountUnreadNotifications_withUnreadLimit_overLimit
testCountUnreadNotifications_withUnreadLimit_overLimit( $limit)
provideIntWithDbUnsafeVersion
Definition: WatchedItemStoreUnitTest.php:753
NS_USER
const NS_USER
Definition: Defines.php:66
WatchedItemStoreUnitTest\testGetNotificationTimestampsBatch_cachedItem
testGetNotificationTimestampsBatch_cachedItem()
Definition: WatchedItemStoreUnitTest.php:1943
WatchedItemStoreUnitTest\testGetWatchedItem_existingItem
testGetWatchedItem_existingItem()
Definition: WatchedItemStoreUnitTest.php:1461
WatchedItemStoreUnitTest\testResetNotificationTimestamp_futureNotificationTimestampForced
testResetNotificationTimestamp_futureNotificationTimestampForced()
Definition: WatchedItemStoreUnitTest.php:2462
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
$time
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1802
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
WatchedItemStoreUnitTest\testCountWatchedItems
testCountWatchedItems()
Definition: WatchedItemStoreUnitTest.php:233
WatchedItemStoreUnitTest\testGetWatchedItemsForUser
testGetWatchedItemsForUser()
Definition: WatchedItemStoreUnitTest.php:1607
WatchedItemStoreUnitTest\testUpdateNotificationTimestamp_noWatchers
testUpdateNotificationTimestamp_noWatchers()
Definition: WatchedItemStoreUnitTest.php:2763
WatchedItemStoreUnitTest\testGetWatchedItemsForUser_optionsAndEmptyResult
testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType)
provideDbTypes
Definition: WatchedItemStoreUnitTest.php:1669
WatchedItemStoreUnitTest\testResetNotificationTimestamp_noItemForced
testResetNotificationTimestamp_noItemForced()
Definition: WatchedItemStoreUnitTest.php:2191
WatchedItemStoreUnitTest\testAddWatchBatchReturnsTrue_whenGivenEmptyList
testAddWatchBatchReturnsTrue_whenGivenEmptyList()
Definition: WatchedItemStoreUnitTest.php:1212
WatchedItemStoreUnitTest\testLoadWatchedItem_anonymousUser
testLoadWatchedItem_anonymousUser()
Definition: WatchedItemStoreUnitTest.php:1309
JobQueueGroup
Class to handle enqueueing of background jobs.
Definition: JobQueueGroup.php:30
TitleValue
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:36
Title\newFromTitleValue
static newFromTitleValue(TitleValue $titleValue, $forceClone='')
Returns a Title given a TitleValue.
Definition: Title.php:254