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