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