3 use MediaWiki\MediaWikiServices
;
9 class ChangeTagsTest
extends MediaWikiTestCase
{
11 public function setUp() {
14 $this->tablesUsed
[] = 'change_tag';
15 $this->tablesUsed
[] = 'change_tag_def';
16 $this->tablesUsed
[] = 'tag_summary';
19 // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
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']
31 if ( isset( $modifiedQuery['exception'] ) ) {
32 $this->setExpectedException( $modifiedQuery['exception'] );
34 ChangeTags
::modifyDisplayQuery(
38 $origQuery['join_conds'],
39 $origQuery['options'],
42 if ( !isset( $modifiedQuery['exception'] ) ) {
43 $this->assertArrayEquals(
46 /* ordered = */ false,
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
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' ],
63 'simple recentchanges query' => [
65 'tables' => [ 'recentchanges' ],
66 'fields' => [ 'rc_id', 'rc_timestamp' ],
67 'conds' => [ "rc_timestamp > '20170714183203'" ],
69 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
72 true, // tag filtering enabled
74 'tables' => [ 'recentchanges' ],
75 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
76 'conds' => [ "rc_timestamp > '20170714183203'" ],
78 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
81 'simple query with strings' => [
83 'tables' => 'recentchanges',
85 'conds' => "rc_timestamp > '20170714183203'",
87 'options' => 'ORDER BY rc_timestamp DESC',
90 true, // tag filtering enabled
92 'tables' => [ 'recentchanges' ],
93 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
94 'conds' => [ "rc_timestamp > '20170714183203'" ],
96 'options' => [ 'ORDER BY rc_timestamp DESC' ],
99 'recentchanges query with single tag filter' => [
101 'tables' => [ 'recentchanges' ],
102 'fields' => [ 'rc_id', 'rc_timestamp' ],
103 'conds' => [ "rc_timestamp > '20170714183203'" ],
105 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
108 true, // tag filtering enabled
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' ],
117 'logging query with single tag filter and strings' => [
119 'tables' => 'logging',
120 'fields' => 'log_id',
121 'conds' => "log_timestamp > '20170714183203'",
123 'options' => 'ORDER BY log_timestamp DESC',
126 true, // tag filtering enabled
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' ],
135 'revision query with single tag filter' => [
137 'tables' => [ 'revision' ],
138 'fields' => [ 'rev_id', 'rev_timestamp' ],
139 'conds' => [ "rev_timestamp > '20170714183203'" ],
141 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
144 true, // tag filtering enabled
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' ],
153 'archive query with single tag filter' => [
155 'tables' => [ 'archive' ],
156 'fields' => [ 'ar_id', 'ar_timestamp' ],
157 'conds' => [ "ar_timestamp > '20170714183203'" ],
159 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
162 true, // tag filtering enabled
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' ],
171 'unsupported table name throws exception (even without tag filter)' => [
173 'tables' => [ 'foobar' ],
174 'fields' => [ 'fb_id', 'fb_timestamp' ],
175 'conds' => [ "fb_timestamp > '20170714183203'" ],
177 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ],
180 true, // tag filtering enabled
181 [ 'exception' => MWException
::class ]
183 'tag filter ignored when tag filtering is disabled' => [
185 'tables' => [ 'archive' ],
186 'fields' => [ 'ar_id', 'ar_timestamp' ],
187 'conds' => [ "ar_timestamp > '20170714183203'" ],
189 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
192 false, // tag filtering disabled
194 'tables' => [ 'archive' ],
195 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
196 'conds' => [ "ar_timestamp > '20170714183203'" ],
198 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
201 'recentchanges query with multiple tag filter' => [
203 'tables' => [ 'recentchanges' ],
204 'fields' => [ 'rc_id', 'rc_timestamp' ],
205 'conds' => [ "rc_timestamp > '20170714183203'" ],
207 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
210 true, // tag filtering enabled
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' ],
219 'recentchanges query with multiple tag filter that already has DISTINCT' => [
221 'tables' => [ 'recentchanges' ],
222 'fields' => [ 'rc_id', 'rc_timestamp' ],
223 'conds' => [ "rc_timestamp > '20170714183203'" ],
225 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
228 true, // tag filtering enabled
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' ],
237 'recentchanges query with multiple tag filter with strings' => [
239 'tables' => 'recentchanges',
241 'conds' => "rc_timestamp > '20170714183203'",
243 'options' => 'ORDER BY rc_timestamp DESC',
246 true, // tag filtering enabled
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' ],
258 public static function dataGetSoftwareTags() {
262 'mw-contentModelChange' => true,
263 'mw-redirect' => true,
264 'mw-rollback' => true,
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
286 'mw-changed-redirect-target'
308 * @dataProvider dataGetSoftwareTags
309 * @covers ChangeTags::getSoftwareTags
311 public function testGetSoftwareTags( $softwareTags, $expected ) {
312 $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
314 $actual = ChangeTags
::getSoftwareTags();
315 // Order of tags in arrays is not important
318 $this->assertEquals( $expected, $actual );
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', '*' );
328 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
330 $dbr = wfGetDB( DB_REPLICA
);
332 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
333 $this->assertEquals( [], iterator_to_array( $res, false ) );
347 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
348 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
351 ChangeTags
::updateTags( [ 'tag1', 'tag3' ], [], $rcId );
353 $dbr = wfGetDB( DB_REPLICA
);
355 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
356 $this->assertEquals( [], iterator_to_array( $res, false ) );
380 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
381 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
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', '*' );
391 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
393 $dbr = wfGetDB( DB_REPLICA
);
397 'ctd_name' => 'tag1',
402 'ctd_name' => 'tag2',
407 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
408 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
422 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
423 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
426 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
428 ChangeTags
::updateTags( [ 'tag3' ], [], $rcId );
430 $dbr = wfGetDB( DB_REPLICA
);
434 'ctd_name' => 'tag1',
439 'ctd_name' => 'tag2',
444 'ctd_name' => 'tag3',
449 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
450 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
474 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
475 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
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', '*' );
485 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
487 ChangeTags
::updateTags( [], [ 'tag2' ], $rcId );
489 $dbr = wfGetDB( DB_REPLICA
);
491 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
492 $this->assertEquals( [], iterator_to_array( $res, false ) );
501 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
502 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
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' );
513 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
515 ChangeTags
::updateTags( [], [ 'tag2' ], $rcId );
517 $dbr = wfGetDB( DB_REPLICA
);
521 'ctd_name' => 'tag1',
526 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
527 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
536 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
537 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );