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