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