3 use MediaWiki\MediaWikiServices
;
9 class ChangeTagsTest
extends MediaWikiTestCase
{
11 public function setUp() {
14 $this->tablesUsed
[] = 'change_tag';
15 $this->tablesUsed
[] = 'change_tag_def';
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';
26 // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
28 /** @dataProvider provideModifyDisplayQuery */
29 public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
30 $this->setMwGlobals( 'wgUseTagFilter', $useTags );
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']
40 if ( isset( $modifiedQuery['exception'] ) ) {
41 $this->setExpectedException( $modifiedQuery['exception'] );
43 ChangeTags
::modifyDisplayQuery(
47 $origQuery['join_conds'],
48 $origQuery['options'],
51 if ( !isset( $modifiedQuery['exception'] ) ) {
52 $this->assertArrayEquals(
55 /* ordered = */ false,
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' => [ 'JOIN', 'ct_tag_id=ctd_id' ] ];
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 ] ),
74 'simple recentchanges query' => [
76 'tables' => [ 'recentchanges' ],
77 'fields' => [ 'rc_id', 'rc_timestamp' ],
78 'conds' => [ "rc_timestamp > '20170714183203'" ],
80 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
83 true, // tag filtering enabled
85 'tables' => [ 'recentchanges' ],
86 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
87 'conds' => [ "rc_timestamp > '20170714183203'" ],
89 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
92 'simple query with strings' => [
94 'tables' => 'recentchanges',
96 'conds' => "rc_timestamp > '20170714183203'",
98 'options' => 'ORDER BY rc_timestamp DESC',
101 true, // tag filtering enabled
103 'tables' => [ 'recentchanges' ],
104 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
105 'conds' => [ "rc_timestamp > '20170714183203'" ],
107 'options' => [ 'ORDER BY rc_timestamp DESC' ],
110 'recentchanges query with single tag filter' => [
112 'tables' => [ 'recentchanges' ],
113 'fields' => [ 'rc_id', 'rc_timestamp' ],
114 'conds' => [ "rc_timestamp > '20170714183203'" ],
116 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
119 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rc_id=rc_id' ] ],
125 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
128 'logging query with single tag filter and strings' => [
130 'tables' => 'logging',
131 'fields' => 'log_id',
132 'conds' => "log_timestamp > '20170714183203'",
134 'options' => 'ORDER BY log_timestamp DESC',
137 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_log_id=log_id' ] ],
143 'options' => [ 'ORDER BY log_timestamp DESC' ],
146 'revision query with single tag filter' => [
148 'tables' => [ 'revision' ],
149 'fields' => [ 'rev_id', 'rev_timestamp' ],
150 'conds' => [ "rev_timestamp > '20170714183203'" ],
152 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
155 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rev_id=rev_id' ] ],
161 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
164 'archive query with single tag filter' => [
166 'tables' => [ 'archive' ],
167 'fields' => [ 'ar_id', 'ar_timestamp' ],
168 'conds' => [ "ar_timestamp > '20170714183203'" ],
170 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
173 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rev_id=ar_rev_id' ] ],
179 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
182 'unsupported table name throws exception (even without tag filter)' => [
184 'tables' => [ 'foobar' ],
185 'fields' => [ 'fb_id', 'fb_timestamp' ],
186 'conds' => [ "fb_timestamp > '20170714183203'" ],
188 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ],
191 true, // tag filtering enabled
192 [ 'exception' => MWException
::class ]
194 'tag filter ignored when tag filtering is disabled' => [
196 'tables' => [ 'archive' ],
197 'fields' => [ 'ar_id', 'ar_timestamp' ],
198 'conds' => [ "ar_timestamp > '20170714183203'" ],
200 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
203 false, // tag filtering disabled
205 'tables' => [ 'archive' ],
206 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
207 'conds' => [ "ar_timestamp > '20170714183203'" ],
209 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
212 'recentchanges query with multiple tag filter' => [
214 'tables' => [ 'recentchanges' ],
215 'fields' => [ 'rc_id', 'rc_timestamp' ],
216 'conds' => [ "rc_timestamp > '20170714183203'" ],
218 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
221 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rc_id=rc_id' ] ],
227 'options' => [ 'ORDER BY' => 'rc_timestamp DESC', 'DISTINCT' ],
230 'recentchanges query with multiple tag filter that already has DISTINCT' => [
232 'tables' => [ 'recentchanges' ],
233 'fields' => [ 'rc_id', 'rc_timestamp' ],
234 'conds' => [ "rc_timestamp > '20170714183203'" ],
236 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
239 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rc_id=rc_id' ] ],
245 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
248 'recentchanges query with multiple tag filter with strings' => [
250 'tables' => 'recentchanges',
252 'conds' => "rc_timestamp > '20170714183203'",
254 'options' => 'ORDER BY rc_timestamp DESC',
257 true, // tag filtering enabled
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' => [ 'JOIN', 'ct_rc_id=rc_id' ] ],
263 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ],
269 public static function dataGetSoftwareTags() {
273 'mw-contentModelChange' => true,
274 'mw-redirect' => true,
275 'mw-rollback' => true,
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
297 'mw-changed-redirect-target'
319 * @dataProvider dataGetSoftwareTags
320 * @covers ChangeTags::getSoftwareTags
322 public function testGetSoftwareTags( $softwareTags, $expected ) {
323 $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
325 $actual = ChangeTags
::getSoftwareTags();
326 // Order of tags in arrays is not important
329 $this->assertEquals( $expected, $actual );
332 public function testUpdateTags() {
333 // FIXME: fails under postgres
334 $this->markTestSkippedIfDbType( 'postgres' );
336 $dbw = wfGetDB( DB_MASTER
);
337 $dbw->delete( 'change_tag', '*' );
338 $dbw->delete( 'change_tag_def', '*' );
342 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId, $revId );
344 $dbr = wfGetDB( DB_REPLICA
);
348 'ctd_name' => 'tag1',
353 'ctd_name' => 'tag2',
358 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
359 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
373 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id', 'ct_rev_id' ], '' );
374 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
378 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId, $revId );
379 ChangeTags
::updateTags( [ 'tag3' ], [], $rcId, $revId );
381 $dbr = wfGetDB( DB_REPLICA
);
385 'ctd_name' => 'tag1',
390 'ctd_name' => 'tag2',
395 'ctd_name' => 'tag3',
400 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
401 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
425 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id', 'ct_rev_id' ], '' );
426 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
429 public function testUpdateTagsSkipDuplicates() {
430 // FIXME: fails under postgres
431 $this->markTestSkippedIfDbType( 'postgres' );
433 $dbw = wfGetDB( DB_MASTER
);
434 $dbw->delete( 'change_tag', '*' );
435 $dbw->delete( 'change_tag_def', '*' );
438 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
439 ChangeTags
::updateTags( [ 'tag2', 'tag3' ], [], $rcId );
441 $dbr = wfGetDB( DB_REPLICA
);
445 'ctd_name' => 'tag1',
450 'ctd_name' => 'tag2',
455 'ctd_name' => 'tag3',
460 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
461 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
477 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
478 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
481 public function testUpdateTagsDoNothingOnRepeatedCall() {
482 // FIXME: fails under postgres
483 $this->markTestSkippedIfDbType( 'postgres' );
485 $dbw = wfGetDB( DB_MASTER
);
486 $dbw->delete( 'change_tag', '*' );
487 $dbw->delete( 'change_tag_def', '*' );
490 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
491 $res = ChangeTags
::updateTags( [ 'tag2', 'tag1' ], [], $rcId );
492 $this->assertEquals( [ [], [], [ 'tag1', 'tag2' ] ], $res );
494 $dbr = wfGetDB( DB_REPLICA
);
498 'ctd_name' => 'tag1',
503 'ctd_name' => 'tag2',
508 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
509 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
521 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
522 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
525 public function testDeleteTags() {
526 $dbw = wfGetDB( DB_MASTER
);
527 $dbw->delete( 'change_tag', '*' );
528 $dbw->delete( 'change_tag_def', '*' );
529 MediaWikiServices
::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
532 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
534 ChangeTags
::updateTags( [], [ 'tag2' ], $rcId );
536 $dbr = wfGetDB( DB_REPLICA
);
540 'ctd_name' => 'tag1',
545 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
546 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
554 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' );
555 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
558 public function testTagUsageStatistics() {
559 $dbw = wfGetDB( DB_MASTER
);
560 $dbw->delete( 'change_tag', '*' );
561 $dbw->delete( 'change_tag_def', '*' );
562 MediaWikiServices
::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
565 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
568 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
570 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags
::tagUsageStatistics() );
573 public function testListExplicitlyDefinedTags() {
574 $dbw = wfGetDB( DB_MASTER
);
575 $dbw->delete( 'change_tag', '*' );
576 $dbw->delete( 'change_tag_def', '*' );
579 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
580 ChangeTags
::defineTag( 'tag2' );
582 $this->assertEquals( [ 'tag2' ], ChangeTags
::listExplicitlyDefinedTags() );
583 $dbr = wfGetDB( DB_REPLICA
);
587 'ctd_name' => 'tag1',
588 'ctd_user_defined' => 0
591 'ctd_name' => 'tag2',
592 'ctd_user_defined' => 1
597 [ 'ctd_name', 'ctd_user_defined' ],
600 [ 'ORDER BY' => 'ctd_name' ]
602 $this->assertEquals( $expected, iterator_to_array( $res, false ) );