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';
17 $this->tablesUsed
[] = 'valid_tag';
20 // TODO only modifyDisplayQuery and getSoftwareTags are tested, nothing else is
22 /** @dataProvider provideModifyDisplayQuery */
23 public function testModifyDisplayQuery( $origQuery, $filter_tag, $useTags, $modifiedQuery ) {
24 $this->setMwGlobals( 'wgUseTagFilter', $useTags );
25 // HACK resolve deferred group concats (see comment in provideModifyDisplayQuery)
26 if ( isset( $modifiedQuery['fields']['ts_tags'] ) ) {
27 $modifiedQuery['fields']['ts_tags'] = call_user_func_array(
28 [ wfGetDB( DB_REPLICA
), 'buildGroupConcatField' ],
29 $modifiedQuery['fields']['ts_tags']
32 if ( isset( $modifiedQuery['exception'] ) ) {
33 $this->setExpectedException( $modifiedQuery['exception'] );
35 ChangeTags
::modifyDisplayQuery(
39 $origQuery['join_conds'],
40 $origQuery['options'],
43 if ( !isset( $modifiedQuery['exception'] ) ) {
44 $this->assertArrayEquals(
47 /* ordered = */ false,
53 public function provideModifyDisplayQuery() {
54 // HACK if we call $dbr->buildGroupConcatField() now, it will return the wrong table names
55 // We have to have the test runner call it instead
57 'recentchanges' => [ ',', 'change_tag', 'ct_tag', 'ct_rc_id=rc_id' ],
58 'logging' => [ ',', 'change_tag', 'ct_tag', 'ct_log_id=log_id' ],
59 'revision' => [ ',', 'change_tag', 'ct_tag', 'ct_rev_id=rev_id' ],
60 'archive' => [ ',', 'change_tag', 'ct_tag', 'ct_rev_id=ar_rev_id' ],
64 'simple recentchanges query' => [
66 'tables' => [ 'recentchanges' ],
67 'fields' => [ 'rc_id', 'rc_timestamp' ],
68 'conds' => [ "rc_timestamp > '20170714183203'" ],
70 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
73 true, // tag filtering enabled
75 'tables' => [ 'recentchanges' ],
76 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
77 'conds' => [ "rc_timestamp > '20170714183203'" ],
79 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
82 'simple query with strings' => [
84 'tables' => 'recentchanges',
86 'conds' => "rc_timestamp > '20170714183203'",
88 'options' => 'ORDER BY rc_timestamp DESC',
91 true, // tag filtering enabled
93 'tables' => [ 'recentchanges' ],
94 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
95 'conds' => [ "rc_timestamp > '20170714183203'" ],
97 'options' => [ 'ORDER BY rc_timestamp DESC' ],
100 'recentchanges query with single tag filter' => [
102 'tables' => [ 'recentchanges' ],
103 'fields' => [ 'rc_id', 'rc_timestamp' ],
104 'conds' => [ "rc_timestamp > '20170714183203'" ],
106 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
109 true, // tag filtering enabled
111 'tables' => [ 'recentchanges', 'change_tag' ],
112 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
113 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
114 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
115 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
118 'logging query with single tag filter and strings' => [
120 'tables' => 'logging',
121 'fields' => 'log_id',
122 'conds' => "log_timestamp > '20170714183203'",
124 'options' => 'ORDER BY log_timestamp DESC',
127 true, // tag filtering enabled
129 'tables' => [ 'logging', 'change_tag' ],
130 'fields' => [ 'log_id', 'ts_tags' => $groupConcats['logging'] ],
131 'conds' => [ "log_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
132 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id=log_id' ] ],
133 'options' => [ 'ORDER BY log_timestamp DESC' ],
136 'revision query with single tag filter' => [
138 'tables' => [ 'revision' ],
139 'fields' => [ 'rev_id', 'rev_timestamp' ],
140 'conds' => [ "rev_timestamp > '20170714183203'" ],
142 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
145 true, // tag filtering enabled
147 'tables' => [ 'revision', 'change_tag' ],
148 'fields' => [ 'rev_id', 'rev_timestamp', 'ts_tags' => $groupConcats['revision'] ],
149 'conds' => [ "rev_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
150 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=rev_id' ] ],
151 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ],
154 'archive query with single tag filter' => [
156 'tables' => [ 'archive' ],
157 'fields' => [ 'ar_id', 'ar_timestamp' ],
158 'conds' => [ "ar_timestamp > '20170714183203'" ],
160 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
163 true, // tag filtering enabled
165 'tables' => [ 'archive', 'change_tag' ],
166 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
167 'conds' => [ "ar_timestamp > '20170714183203'", 'ct_tag' => 'foo' ],
168 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rev_id=ar_rev_id' ] ],
169 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
172 'unsupported table name throws exception (even without tag filter)' => [
174 'tables' => [ 'foobar' ],
175 'fields' => [ 'fb_id', 'fb_timestamp' ],
176 'conds' => [ "fb_timestamp > '20170714183203'" ],
178 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ],
181 true, // tag filtering enabled
182 [ 'exception' => MWException
::class ]
184 'tag filter ignored when tag filtering is disabled' => [
186 'tables' => [ 'archive' ],
187 'fields' => [ 'ar_id', 'ar_timestamp' ],
188 'conds' => [ "ar_timestamp > '20170714183203'" ],
190 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
193 false, // tag filtering disabled
195 'tables' => [ 'archive' ],
196 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ],
197 'conds' => [ "ar_timestamp > '20170714183203'" ],
199 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ],
202 'recentchanges query with multiple tag filter' => [
204 'tables' => [ 'recentchanges' ],
205 'fields' => [ 'rc_id', 'rc_timestamp' ],
206 'conds' => [ "rc_timestamp > '20170714183203'" ],
208 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ],
211 true, // tag filtering enabled
213 'tables' => [ 'recentchanges', 'change_tag' ],
214 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
215 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
216 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
217 'options' => [ 'ORDER BY' => 'rc_timestamp DESC', 'DISTINCT' ],
220 'recentchanges query with multiple tag filter that already has DISTINCT' => [
222 'tables' => [ 'recentchanges' ],
223 'fields' => [ 'rc_id', 'rc_timestamp' ],
224 'conds' => [ "rc_timestamp > '20170714183203'" ],
226 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
229 true, // tag filtering enabled
231 'tables' => [ 'recentchanges', 'change_tag' ],
232 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ],
233 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
234 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
235 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ],
238 'recentchanges query with multiple tag filter with strings' => [
240 'tables' => 'recentchanges',
242 'conds' => "rc_timestamp > '20170714183203'",
244 'options' => 'ORDER BY rc_timestamp DESC',
247 true, // tag filtering enabled
249 'tables' => [ 'recentchanges', 'change_tag' ],
250 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ],
251 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag' => [ 'foo', 'bar' ] ],
252 'join_conds' => [ 'change_tag' => [ 'INNER JOIN', 'ct_rc_id=rc_id' ] ],
253 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ],
259 public static function dataGetSoftwareTags() {
263 'mw-contentModelChange' => true,
264 'mw-redirect' => true,
265 'mw-rollback' => true,
278 'mw-contentmodelchanged' => true,
279 'mw-replace' => true,
280 'mw-new-redirects' => true,
281 'mw-changed-redirect-target' => true,
282 'mw-rolback' => true,
283 'mw-blanking' => false
287 'mw-changed-redirect-target'
309 * @dataProvider dataGetSoftwareTags
310 * @covers ChangeTags::getSoftwareTags
312 public function testGetSoftwareTags( $softwareTags, $expected ) {
313 $this->setMwGlobals( 'wgSoftwareTags', $softwareTags );
315 $actual = ChangeTags
::getSoftwareTags();
316 // Order of tags in arrays is not important
319 $this->assertEquals( $expected, $actual );
322 public function testUpdateTagsMigrationOld() {
323 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_OLD
);
324 $dbw = wfGetDB( DB_MASTER
);
325 $dbw->delete( 'change_tag', '*' );
326 $dbw->delete( 'change_tag_def', '*' );
329 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
331 $dbr = wfGetDB( DB_REPLICA
);
333 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
334 $this->assertEquals( [], iterator_to_array( $res, false ) );
348 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
349 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
352 ChangeTags
::updateTags( [ 'tag1', 'tag3' ], [], $rcId );
354 $dbr = wfGetDB( DB_REPLICA
);
356 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
357 $this->assertEquals( [], iterator_to_array( $res, false ) );
381 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
382 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
385 public function testUpdateTagsMigrationWriteBoth() {
386 // FIXME: fails under postgres
387 $this->markTestSkippedIfDbType( 'postgres' );
389 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_WRITE_BOTH
);
390 $dbw = wfGetDB( DB_MASTER
);
391 $dbw->delete( 'change_tag', '*' );
392 $dbw->delete( 'change_tag_def', '*' );
395 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
397 $dbr = wfGetDB( DB_REPLICA
);
401 'ctd_name' => 'tag1',
406 'ctd_name' => 'tag2',
411 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
412 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
426 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
427 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
430 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
432 ChangeTags
::updateTags( [ 'tag3' ], [], $rcId );
434 $dbr = wfGetDB( DB_REPLICA
);
438 'ctd_name' => 'tag1',
443 'ctd_name' => 'tag2',
448 'ctd_name' => 'tag3',
453 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
454 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
478 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
479 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
482 public function testDeleteTagsMigrationOld() {
483 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_OLD
);
484 $dbw = wfGetDB( DB_MASTER
);
485 $dbw->delete( 'change_tag', '*' );
486 $dbw->delete( 'change_tag_def', '*' );
489 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
491 ChangeTags
::updateTags( [], [ 'tag2' ], $rcId );
493 $dbr = wfGetDB( DB_REPLICA
);
495 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
496 $this->assertEquals( [], iterator_to_array( $res, false ) );
505 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
506 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
509 public function testDeleteTagsMigrationWriteBoth() {
510 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_WRITE_BOTH
);
511 $dbw = wfGetDB( DB_MASTER
);
512 $dbw->delete( 'change_tag', '*' );
513 $dbw->delete( 'change_tag_def', '*' );
514 MediaWikiServices
::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
517 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
519 ChangeTags
::updateTags( [], [ 'tag2' ], $rcId );
521 $dbr = wfGetDB( DB_REPLICA
);
525 'ctd_name' => 'tag1',
530 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' );
531 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
540 $res2 = $dbr->select( 'change_tag', [ 'ct_tag', 'ct_tag_id', 'ct_rc_id' ], '' );
541 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) );
544 public function testTagUsageStatisticsOldBackend() {
545 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_OLD
);
546 $this->setMwGlobals( 'wgTagStatisticsNewTable', false );
548 $dbw = wfGetDB( DB_MASTER
);
549 $dbw->delete( 'change_tag', '*' );
550 $dbw->delete( 'change_tag_def', '*' );
553 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
556 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
558 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags
::tagUsageStatistics() );
561 public function testTagUsageStatisticsNewMigrationOldBackedn() {
562 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_WRITE_BOTH
);
563 $this->setMwGlobals( 'wgTagStatisticsNewTable', false );
565 $dbw = wfGetDB( DB_MASTER
);
566 $dbw->delete( 'change_tag', '*' );
567 $dbw->delete( 'change_tag_def', '*' );
568 MediaWikiServices
::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
571 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
574 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
576 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags
::tagUsageStatistics() );
579 public function testTagUsageStatisticsNewBackend() {
580 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_WRITE_BOTH
);
581 $this->setMwGlobals( 'wgTagStatisticsNewTable', true );
583 $dbw = wfGetDB( DB_MASTER
);
584 $dbw->delete( 'change_tag', '*' );
585 $dbw->delete( 'change_tag_def', '*' );
586 MediaWikiServices
::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' );
589 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
592 ChangeTags
::updateTags( [ 'tag1' ], [], $rcId );
594 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags
::tagUsageStatistics() );
597 public function testListExplicitlyDefinedTagsOld() {
598 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_OLD
);
599 $dbw = wfGetDB( DB_MASTER
);
600 $dbw->delete( 'change_tag', '*' );
601 $dbw->delete( 'change_tag_def', '*' );
602 $dbw->delete( 'valid_tag', '*' );
605 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
606 ChangeTags
::defineTag( 'tag2' );
608 $this->assertEquals( [ 'tag2' ], ChangeTags
::listExplicitlyDefinedTags() );
609 $dbr = wfGetDB( DB_REPLICA
);
610 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_user_defined' ], '' );
611 $this->assertEquals( [], iterator_to_array( $res, false ) );
613 $this->assertEquals( [ 'tag2' ], $dbr->selectFieldValues( 'valid_tag', 'vt_tag', '' ) );
616 public function testListExplicitlyDefinedTagsWriteBoth() {
617 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_WRITE_BOTH
);
618 $dbw = wfGetDB( DB_MASTER
);
619 $dbw->delete( 'change_tag', '*' );
620 $dbw->delete( 'change_tag_def', '*' );
621 $dbw->delete( 'valid_tag', '*' );
624 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
625 ChangeTags
::defineTag( 'tag2' );
627 $this->assertEquals( [ 'tag2' ], ChangeTags
::listExplicitlyDefinedTags() );
628 $dbr = wfGetDB( DB_REPLICA
);
632 'ctd_name' => 'tag1',
633 'ctd_user_defined' => 0
636 'ctd_name' => 'tag2',
637 'ctd_user_defined' => 1
640 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_user_defined' ], '' );
641 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
643 $this->assertEquals( [ 'tag2' ], $dbr->selectFieldValues( 'valid_tag', 'vt_tag', '' ) );
646 public function testListExplicitlyDefinedTagsNew() {
647 $this->setMwGlobals( 'wgChangeTagsSchemaMigrationStage', MIGRATION_NEW
);
648 $dbw = wfGetDB( DB_MASTER
);
649 $dbw->delete( 'change_tag', '*' );
650 $dbw->delete( 'change_tag_def', '*' );
651 $dbw->delete( 'valid_tag', '*' );
654 ChangeTags
::updateTags( [ 'tag1', 'tag2' ], [], $rcId );
655 ChangeTags
::defineTag( 'tag2' );
657 $this->assertEquals( [ 'tag2' ], ChangeTags
::listExplicitlyDefinedTags() );
658 $dbr = wfGetDB( DB_REPLICA
);
662 'ctd_name' => 'tag1',
663 'ctd_user_defined' => 0
666 'ctd_name' => 'tag2',
667 'ctd_user_defined' => 1
670 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_user_defined' ], '' );
671 $this->assertEquals( $expected, iterator_to_array( $res, false ) );
673 $this->assertEquals( [], $dbr->selectFieldValues( 'valid_tag', 'vt_tag', '' ) );