Merge "auth: Follow up on e907d4328dc3e"
[lhc/web/wiklou.git] / tests / phpunit / includes / changetags / ChangeTagsTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4
5 /**
6 * @covers ChangeTags
7 * @group Database
8 */
9 class ChangeTagsTest extends MediaWikiTestCase {
10
11 public function setUp() {
12 parent::setUp();
13
14 $this->tablesUsed[] = 'change_tag';
15 $this->tablesUsed[] = 'change_tag_def';
16
17 // Truncate these to avoid the supposed-to-be-unused IDs in tests here turning
18 // out to be used, leading ChangeTags::updateTags() to pick up bogus rc_id,
19 // log_id, or rev_id values and run into unique constraint violations.
20 $this->tablesUsed[] = 'recentchanges';
21 $this->tablesUsed[] = 'logging';
22 $this->tablesUsed[] = 'revision';
23 $this->tablesUsed[] = 'archive';
24 }
25
26 // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
27
28 /** @dataProvider provideModifyDisplayQuery */
29 public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
30 $this->setMwGlobals( 'wgUseTagFilter', $useTags );
31 $rcId = 123;
32 ChangeTags::updateTags( [ 'foo', 'bar' ], [], $rcId );
33 // HACK resolve deferred group concats (see comment in provideModifyDisplayQuery)
34 if ( isset( $modifiedQuery['fields']['ts_tags'] ) ) {
35 $modifiedQuery['fields']['ts_tags'] = call_user_func_array(
36 [ wfGetDB( DB_REPLICA ), 'buildGroupConcatField' ],
37 $modifiedQuery['fields']['ts_tags']
38 );
39 }
40 if ( isset( $modifiedQuery['exception'] ) ) {
41 $this->setExpectedException( $modifiedQuery['exception'] );
42 }
43 ChangeTags::modifyDisplayQuery(
44 $origQuery['tables'],
45 $origQuery['fields'],
46 $origQuery['conds'],
47 $origQuery['join_conds'],
48 $origQuery['options'],
49 $filter_tag
50 );
51 if ( !isset( $modifiedQuery['exception'] ) ) {
52 $this->assertArrayEquals(
53 $modifiedQuery,
54 $origQuery,
55 /* ordered = */ false,
56 /* named = */ true
57 );
58 }
59 }
60
61 public function provideModifyDisplayQuery() {
62 // HACK if we call $dbr->buildGroupConcatField() now, it will return the wrong table names
63 // We have to have the test runner call it instead
64 $baseConcats = [ ',', [ 'change_tag', 'change_tag_def' ], 'ctd_name' ];
65 $joinConds = [ 'change_tag_def' => [ 'INNER JOIN', 'ct_tag_id=ctd_id' ] ];
66 $groupConcats = [
67 'recentchanges' => array_merge( $baseConcats, [ 'ct_rc_id=rc_id', $joinConds ] ),
68 'logging' => array_merge( $baseConcats, [ 'ct_log_id=log_id', $joinConds ] ),
69 'revision' => array_merge( $baseConcats, [ 'ct_rev_id=rev_id', $joinConds ] ),
70 'archive' => array_merge( $baseConcats, [ 'ct_rev_id=ar_rev_id', $joinConds ] ),
71 ];
72
73 return [
74 'simple recentchanges query' => [
75 [
76 'tables' => [ 'recentchanges' ],
77 'fields' => [ 'rc_id', 'rc_timestamp' ],
78 'conds' => [ "rc_timestamp > '20170714183203'" ],
79 'join_conds' => [],
80 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
81 ],
82 '', // no tag filter
83 true, // tag filtering enabled
84 [
85 'tables' => [ 'recentchanges' ],
86 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
87 'conds' => [ "rc_timestamp > '20170714183203'" ],
88 'join_conds' => [],
89 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
90 ]
91 ],
92 'simple query with strings' => [
93 [
94 'tables' => 'recentchanges',
95 'fields' => 'rc_id',
96 'conds' => "rc_timestamp > '20170714183203'",
97 'join_conds' => [],
98 'options' => 'ORDER BY rc_timestamp DESC',
99 ],
100 '', // no tag filter
101 true, // tag filtering enabled
102 [
103 'tables' => [ 'recentchanges' ],
104 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
105 'conds' => [ "rc_timestamp > '20170714183203'" ],
106 'join_conds' => [],
107 'options' => [ 'ORDER BY rc_timestamp DESC' ],
108 ]
109 ],
110 'recentchanges query with single tag filter' => [
111 [
112 'tables' => [ 'recentchanges' ],
113 'fields' => [ 'rc_id', 'rc_timestamp' ],
114 'conds' => [ "rc_timestamp > '20170714183203'" ],
115 'join_conds' => [],
116 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
117 ],
118 'foo',
119 true, // tag filtering enabled
120 [
121 'tables' => [ 'recentchanges', 'change_tag' ],
122 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
123 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ],
124 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
125 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
126 ]
127 ],
128 'logging query with single tag filter and strings' => [
129 [
130 'tables' => 'logging',
131 'fields' => 'log_id',
132 'conds' => "log_timestamp > '20170714183203'",
133 'join_conds' => [],
134 'options' => 'ORDER BY log_timestamp DESC',
135 ],
136 'foo',
137 true, // tag filtering enabled
138 [
139 'tables' => [ 'logging', 'change_tag' ],
140 'fields' => [ 'log_id', 'ts_tags' => $groupConcats['logging'] ],
141 'conds' => [ "log_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ],
142 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id=log_id' ] ],
143 'options' => [ 'ORDER BY log_timestamp DESC' ],
144 ]
145 ],
146 'revision query with single tag filter' => [
147 [
148 'tables' => [ 'revision' ],
149 'fields' => [ 'rev_id', 'rev_timestamp' ],
150 'conds' => [ "rev_timestamp > '20170714183203'" ],
151 'join_conds' => [],
152 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
153 ],
154 'foo',
155 true, // tag filtering enabled
156 [
157 'tables' => [ 'revision', 'change_tag' ],
158 'fields' => [ 'rev_id', 'rev_timestamp', 'ts_tags' => $groupConcats['revision'] ],
159 'conds' => [ "rev_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ],
160 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=rev_id' ] ],
161 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
162 ]
163 ],
164 'archive query with single tag filter' => [
165 [
166 'tables' => [ 'archive' ],
167 'fields' => [ 'ar_id', 'ar_timestamp' ],
168 'conds' => [ "ar_timestamp > '20170714183203'" ],
169 'join_conds' => [],
170 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
171 ],
172 'foo',
173 true, // tag filtering enabled
174 [
175 'tables' => [ 'archive', 'change_tag' ],
176 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
177 'conds' => [ "ar_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ],
178 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=ar_rev_id' ] ],
179 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
180 ]
181 ],
182 'unsupported table name throws exception (even without tag filter)' => [
183 [
184 'tables' => [ 'foobar' ],
185 'fields' => [ 'fb_id', 'fb_timestamp' ],
186 'conds' => [ "fb_timestamp > '20170714183203'" ],
187 'join_conds' => [],
188 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ],
189 ],
190 '',
191 true, // tag filtering enabled
192 [ 'exception' => MWException::class ]
193 ],
194 'tag filter ignored when tag filtering is disabled' => [
195 [
196 'tables' => [ 'archive' ],
197 'fields' => [ 'ar_id', 'ar_timestamp' ],
198 'conds' => [ "ar_timestamp > '20170714183203'" ],
199 'join_conds' => [],
200 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
201 ],
202 'foo',
203 false, // tag filtering disabled
204 [
205 'tables' => [ 'archive' ],
206 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
207 'conds' => [ "ar_timestamp > '20170714183203'" ],
208 'join_conds' => [],
209 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
210 ]
211 ],
212 'recentchanges query with multiple tag filter' => [
213 [
214 'tables' => [ 'recentchanges' ],
215 'fields' => [ 'rc_id', 'rc_timestamp' ],
216 'conds' => [ "rc_timestamp > '20170714183203'" ],
217 'join_conds' => [],
218 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
219 ],
220 [ 'foo', 'bar' ],
221 true, // tag filtering enabled
222 [
223 'tables' => [ 'recentchanges', 'change_tag' ],
224 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
225 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ],
226 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
227 'options' => [ 'ORDER BY' => 'rc_timestamp DESC', 'DISTINCT' ],
228 ]
229 ],
230 'recentchanges query with multiple tag filter that already has DISTINCT' => [
231 [
232 'tables' => [ 'recentchanges' ],
233 'fields' => [ 'rc_id', 'rc_timestamp' ],
234 'conds' => [ "rc_timestamp > '20170714183203'" ],
235 'join_conds' => [],
236 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
237 ],
238 [ 'foo', 'bar' ],
239 true, // tag filtering enabled
240 [
241 'tables' => [ 'recentchanges', 'change_tag' ],
242 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
243 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ],
244 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
245 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
246 ]
247 ],
248 'recentchanges query with multiple tag filter with strings' => [
249 [
250 'tables' => 'recentchanges',
251 'fields' => 'rc_id',
252 'conds' => "rc_timestamp > '20170714183203'",
253 'join_conds' => [],
254 'options' => 'ORDER BY rc_timestamp DESC',
255 ],
256 [ 'foo', 'bar' ],
257 true, // tag filtering enabled
258 [
259 'tables' => [ 'recentchanges', 'change_tag' ],
260 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
261 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ],
262 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
263 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ],
264 ]
265 ],
266 ];
267 }
268
269 public static function dataGetSoftwareTags() {
270 return [
271 [
272 [
273 'mw-contentModelChange' => true,
274 'mw-redirect' => true,
275 'mw-rollback' => true,
276 'mw-blank' => true,
277 'mw-replace' => true
278 ],
279 [
280 'mw-rollback',
281 'mw-replace',
282 'mw-blank'
283 ]
284 ],
285
286 [
287 [
288 'mw-contentmodelchanged' => true,
289 'mw-replace' => true,
290 'mw-new-redirects' => true,
291 'mw-changed-redirect-target' => true,
292 'mw-rolback' => true,
293 'mw-blanking' => false
294 ],
295 [
296 'mw-replace',
297 'mw-changed-redirect-target'
298 ]
299 ],
300
301 [
302 [
303 null,
304 false,
305 'Lorem ipsum',
306 'mw-translation'
307 ],
308 []
309 ],
310
311 [
312 [],
313 []
314 ]
315 ];
316 }
317
318 /**
319 * @dataProvider dataGetSoftwareTags
320 * @covers ChangeTags::getSoftwareTags
321 */
322 public function testGetSoftwareTags( $softwareTags, $expected ) {
323 $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
324
325 $actual = ChangeTags::getSoftwareTags();
326 // Order of tags in arrays is not important
327 sort( $expected );
328 sort( $actual );
329 $this->assertEquals( $expected, $actual );
330 }
331
332 public function testUpdateTags() {
333 // FIXME: fails under postgres
334 $this->markTestSkippedIfDbType( 'postgres' );
335
336 $dbw = wfGetDB( DB_MASTER );
337 $dbw->delete( 'change_tag', '*' );
338 $dbw->delete( 'change_tag_def', '*' );
339
340 $rcId = 123;
341 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
342
343 $dbr = wfGetDB( DB_REPLICA );
344
345 $expected = [
346 (object)[
347 'ctd_name' => 'tag1',
348 'ctd_id' => 1,
349 'ctd_count' => 1
350 ],
351 (object)[
352 'ctd_name' => 'tag2',
353 'ctd_id' => 2,
354 'ctd_count' => 1
355 ],
356 ];
357 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
358 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
359
360 $expected2 = [
361 (object)[
362 'ct_tag_id' => 1,
363 'ct_rc_id' => 123
364 ],
365 (object)[
366 'ct_tag_id' => 2,
367 'ct_rc_id' => 123
368 ],
369 ];
370 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
371 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
372
373 $rcId = 124;
374 ChangeTags::updateTags( [ 'tag1' ], [], $rcId );
375
376 ChangeTags::updateTags( [ 'tag3' ], [], $rcId );
377
378 $dbr = wfGetDB( DB_REPLICA );
379
380 $expected = [
381 (object)[
382 'ctd_name' => 'tag1',
383 'ctd_id' => 1,
384 'ctd_count' => 2
385 ],
386 (object)[
387 'ctd_name' => 'tag2',
388 'ctd_id' => 2,
389 'ctd_count' => 1
390 ],
391 (object)[
392 'ctd_name' => 'tag3',
393 'ctd_id' => 3,
394 'ctd_count' => 1
395 ],
396 ];
397 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
398 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
399
400 $expected2 = [
401 (object)[
402 'ct_tag_id' => 1,
403 'ct_rc_id' => 123
404 ],
405 (object)[
406 'ct_tag_id' => 2,
407 'ct_rc_id' => 123
408 ],
409 (object)[
410 'ct_tag_id' => 1,
411 'ct_rc_id' => 124
412 ],
413 (object)[
414 'ct_tag_id' => 3,
415 'ct_rc_id' => 124
416 ],
417 ];
418 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
419 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
420 }
421
422 public function testUpdateTagsSkipDuplicates() {
423 // FIXME: fails under postgres
424 $this->markTestSkippedIfDbType( 'postgres' );
425
426 $dbw = wfGetDB( DB_MASTER );
427 $dbw->delete( 'change_tag', '*' );
428 $dbw->delete( 'change_tag_def', '*' );
429
430 $rcId = 123;
431 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
432 ChangeTags::updateTags( [ 'tag2', 'tag3' ], [], $rcId );
433
434 $dbr = wfGetDB( DB_REPLICA );
435
436 $expected = [
437 (object)[
438 'ctd_name' => 'tag1',
439 'ctd_id' => 1,
440 'ctd_count' => 1
441 ],
442 (object)[
443 'ctd_name' => 'tag2',
444 'ctd_id' => 2,
445 'ctd_count' => 1
446 ],
447 (object)[
448 'ctd_name' => 'tag3',
449 'ctd_id' => 3,
450 'ctd_count' => 1
451 ],
452 ];
453 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
454 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
455
456 $expected2 = [
457 (object)[
458 'ct_tag_id' => 1,
459 'ct_rc_id' => 123
460 ],
461 (object)[
462 'ct_tag_id' => 2,
463 'ct_rc_id' => 123
464 ],
465 (object)[
466 'ct_tag_id' => 3,
467 'ct_rc_id' => 123
468 ],
469 ];
470 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
471 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
472 }
473
474 public function testUpdateTagsDoNothingOnRepeatedCall() {
475 // FIXME: fails under postgres
476 $this->markTestSkippedIfDbType( 'postgres' );
477
478 $dbw = wfGetDB( DB_MASTER );
479 $dbw->delete( 'change_tag', '*' );
480 $dbw->delete( 'change_tag_def', '*' );
481
482 $rcId = 123;
483 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
484 $res = ChangeTags::updateTags( [ 'tag2', 'tag1' ], [], $rcId );
485 $this->assertEquals( [ [], [], [ 'tag1', 'tag2' ] ], $res );
486
487 $dbr = wfGetDB( DB_REPLICA );
488
489 $expected = [
490 (object)[
491 'ctd_name' => 'tag1',
492 'ctd_id' => 1,
493 'ctd_count' => 1
494 ],
495 (object)[
496 'ctd_name' => 'tag2',
497 'ctd_id' => 2,
498 'ctd_count' => 1
499 ],
500 ];
501 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
502 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
503
504 $expected2 = [
505 (object)[
506 'ct_tag_id' => 1,
507 'ct_rc_id' => 123
508 ],
509 (object)[
510 'ct_tag_id' => 2,
511 'ct_rc_id' => 123
512 ],
513 ];
514 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
515 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
516 }
517
518 public function testDeleteTags() {
519 $dbw = wfGetDB( DB_MASTER );
520 $dbw->delete( 'change_tag', '*' );
521 $dbw->delete( 'change_tag_def', '*' );
522 MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
523
524 $rcId = 123;
525 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
526
527 ChangeTags::updateTags( [], [ 'tag2' ], $rcId );
528
529 $dbr = wfGetDB( DB_REPLICA );
530
531 $expected = [
532 (object)[
533 'ctd_name' => 'tag1',
534 'ctd_id' => 1,
535 'ctd_count' => 1
536 ],
537 ];
538 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
539 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
540
541 $expected2 = [
542 (object)[
543 'ct_tag_id' => 1,
544 'ct_rc_id' => 123
545 ]
546 ];
547 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
548 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
549 }
550
551 public function testTagUsageStatistics() {
552 $dbw = wfGetDB( DB_MASTER );
553 $dbw->delete( 'change_tag', '*' );
554 $dbw->delete( 'change_tag_def', '*' );
555 MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
556
557 $rcId = 123;
558 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
559
560 $rcId = 124;
561 ChangeTags::updateTags( [ 'tag1' ], [], $rcId );
562
563 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags::tagUsageStatistics() );
564 }
565
566 public function testListExplicitlyDefinedTags() {
567 $dbw = wfGetDB( DB_MASTER );
568 $dbw->delete( 'change_tag', '*' );
569 $dbw->delete( 'change_tag_def', '*' );
570
571 $rcId = 123;
572 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
573 ChangeTags::defineTag( 'tag2' );
574
575 $this->assertEquals( [ 'tag2' ], ChangeTags::listExplicitlyDefinedTags() );
576 $dbr = wfGetDB( DB_REPLICA );
577
578 $expected = [
579 (object)[
580 'ctd_name' => 'tag1',
581 'ctd_user_defined' => 0
582 ],
583 (object)[
584 'ctd_name' => 'tag2',
585 'ctd_user_defined' => 1
586 ],
587 ];
588 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_user_defined' ], '' );
589 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
590 }
591 }