1<?php 2 3use MediaWiki\MediaWikiServices; 4 5/** 6 * @covers ChangeTags 7 * @group Database 8 */ 9class ChangeTagsTest extends MediaWikiIntegrationTestCase { 10 11 protected function setUp() : void { 12 parent::setUp(); 13 14 $this->tablesUsed[] = 'change_tag'; 15 $this->tablesUsed[] = 'change_tag_def'; 16 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'; 24 } 25 26 public function tearDown() : void { 27 ChangeTags::$avoidReopeningTablesForTesting = false; 28 parent::tearDown(); 29 } 30 31 private function emptyChangeTagsTables() { 32 $dbw = wfGetDB( DB_MASTER ); 33 $dbw->delete( 'change_tag', '*' ); 34 $dbw->delete( 'change_tag_def', '*' ); 35 } 36 37 // TODO most methods are not tested 38 39 /** @dataProvider provideModifyDisplayQuery */ 40 public function testModifyDisplayQuery( 41 $origQuery, 42 $filter_tag, 43 $useTags, 44 $avoidReopeningTables, 45 $modifiedQuery 46 ) { 47 $this->setMwGlobals( 'wgUseTagFilter', $useTags ); 48 49 if ( $avoidReopeningTables && $this->db->getType() !== 'mysql' ) { 50 $this->markTestSkipped( 'MySQL only' ); 51 } 52 53 ChangeTags::$avoidReopeningTablesForTesting = $avoidReopeningTables; 54 55 $rcId = 123; 56 ChangeTags::updateTags( [ 'foo', 'bar' ], [], $rcId ); 57 // HACK resolve deferred group concats (see comment in provideModifyDisplayQuery) 58 if ( isset( $modifiedQuery['fields']['ts_tags'] ) ) { 59 $modifiedQuery['fields']['ts_tags'] = wfGetDB( DB_REPLICA ) 60 ->buildGroupConcatField( ...$modifiedQuery['fields']['ts_tags'] ); 61 } 62 if ( isset( $modifiedQuery['exception'] ) ) { 63 $this->expectException( $modifiedQuery['exception'] ); 64 } 65 ChangeTags::modifyDisplayQuery( 66 $origQuery['tables'], 67 $origQuery['fields'], 68 $origQuery['conds'], 69 $origQuery['join_conds'], 70 $origQuery['options'], 71 $filter_tag 72 ); 73 if ( !isset( $modifiedQuery['exception'] ) ) { 74 $this->assertArrayEquals( 75 $modifiedQuery, 76 $origQuery, 77 /* ordered = */ false, 78 /* named = */ true 79 ); 80 } 81 } 82 83 public function provideModifyDisplayQuery() { 84 // HACK if we call $dbr->buildGroupConcatField() now, it will return the wrong table names 85 // We have to have the test runner call it instead 86 $baseConcats = [ ',', [ 'change_tag', 'change_tag_def' ], 'ctd_name' ]; 87 $joinConds = [ 'change_tag_def' => [ 'JOIN', 'ct_tag_id=ctd_id' ] ]; 88 $groupConcats = [ 89 'recentchanges' => array_merge( $baseConcats, [ 'ct_rc_id=rc_id', $joinConds ] ), 90 'logging' => array_merge( $baseConcats, [ 'ct_log_id=log_id', $joinConds ] ), 91 'revision' => array_merge( $baseConcats, [ 'ct_rev_id=rev_id', $joinConds ] ), 92 'archive' => array_merge( $baseConcats, [ 'ct_rev_id=ar_rev_id', $joinConds ] ), 93 ]; 94 95 return [ 96 'simple recentchanges query' => [ 97 [ 98 'tables' => [ 'recentchanges' ], 99 'fields' => [ 'rc_id', 'rc_timestamp' ], 100 'conds' => [ "rc_timestamp > '20170714183203'" ], 101 'join_conds' => [], 102 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ], 103 ], 104 '', // no tag filter 105 true, // tag filtering enabled 106 false, // not avoiding reopening tables 107 [ 108 'tables' => [ 'recentchanges' ], 109 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ], 110 'conds' => [ "rc_timestamp > '20170714183203'" ], 111 'join_conds' => [], 112 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ], 113 ] 114 ], 115 'simple query with strings' => [ 116 [ 117 'tables' => 'recentchanges', 118 'fields' => 'rc_id', 119 'conds' => "rc_timestamp > '20170714183203'", 120 'join_conds' => [], 121 'options' => 'ORDER BY rc_timestamp DESC', 122 ], 123 '', // no tag filter 124 true, // tag filtering enabled 125 false, // not avoiding reopening tables 126 [ 127 'tables' => [ 'recentchanges' ], 128 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ], 129 'conds' => [ "rc_timestamp > '20170714183203'" ], 130 'join_conds' => [], 131 'options' => [ 'ORDER BY rc_timestamp DESC' ], 132 ] 133 ], 134 'recentchanges query with single tag filter' => [ 135 [ 136 'tables' => [ 'recentchanges' ], 137 'fields' => [ 'rc_id', 'rc_timestamp' ], 138 'conds' => [ "rc_timestamp > '20170714183203'" ], 139 'join_conds' => [], 140 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ], 141 ], 142 'foo', 143 true, // tag filtering enabled 144 false, // not avoiding reopening tables 145 [ 146 'tables' => [ 'recentchanges', 'change_tag' ], 147 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ], 148 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ], 149 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rc_id=rc_id' ] ], 150 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ], 151 ] 152 ], 153 'logging query with single tag filter and strings' => [ 154 [ 155 'tables' => 'logging', 156 'fields' => 'log_id', 157 'conds' => "log_timestamp > '20170714183203'", 158 'join_conds' => [], 159 'options' => 'ORDER BY log_timestamp DESC', 160 ], 161 'foo', 162 true, // tag filtering enabled 163 false, // not avoiding reopening tables 164 [ 165 'tables' => [ 'logging', 'change_tag' ], 166 'fields' => [ 'log_id', 'ts_tags' => $groupConcats['logging'] ], 167 'conds' => [ "log_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ], 168 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_log_id=log_id' ] ], 169 'options' => [ 'ORDER BY log_timestamp DESC' ], 170 ] 171 ], 172 'revision query with single tag filter' => [ 173 [ 174 'tables' => [ 'revision' ], 175 'fields' => [ 'rev_id', 'rev_timestamp' ], 176 'conds' => [ "rev_timestamp > '20170714183203'" ], 177 'join_conds' => [], 178 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ], 179 ], 180 'foo', 181 true, // tag filtering enabled 182 false, // not avoiding reopening tables 183 [ 184 'tables' => [ 'revision', 'change_tag' ], 185 'fields' => [ 'rev_id', 'rev_timestamp', 'ts_tags' => $groupConcats['revision'] ], 186 'conds' => [ "rev_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ], 187 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rev_id=rev_id' ] ], 188 'options' => [ 'ORDER BY' => 'rev_timestamp DESC' ], 189 ] 190 ], 191 'archive query with single tag filter' => [ 192 [ 193 'tables' => [ 'archive' ], 194 'fields' => [ 'ar_id', 'ar_timestamp' ], 195 'conds' => [ "ar_timestamp > '20170714183203'" ], 196 'join_conds' => [], 197 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 198 ], 199 'foo', 200 true, // tag filtering enabled 201 false, // not avoiding reopening tables 202 [ 203 'tables' => [ 'archive', 'change_tag' ], 204 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ], 205 'conds' => [ "ar_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ], 206 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rev_id=ar_rev_id' ] ], 207 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 208 ] 209 ], 210 'archive query with single tag filter, avoiding reopening tables' => [ 211 [ 212 'tables' => [ 'archive' ], 213 'fields' => [ 'ar_id', 'ar_timestamp' ], 214 'conds' => [ "ar_timestamp > '20170714183203'" ], 215 'join_conds' => [], 216 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 217 ], 218 'foo', 219 true, // tag filtering enabled 220 true, // avoid reopening tables 221 [ 222 'tables' => [ 'archive', 'change_tag_for_display_query' ], 223 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ], 224 'conds' => [ "ar_timestamp > '20170714183203'", 'ct_tag_id' => [ 1 ] ], 225 'join_conds' => [ 'change_tag_for_display_query' => [ 'JOIN', 'ct_rev_id=ar_rev_id' ] ], 226 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 227 ] 228 ], 229 'unsupported table name throws exception (even without tag filter)' => [ 230 [ 231 'tables' => [ 'foobar' ], 232 'fields' => [ 'fb_id', 'fb_timestamp' ], 233 'conds' => [ "fb_timestamp > '20170714183203'" ], 234 'join_conds' => [], 235 'options' => [ 'ORDER BY' => 'fb_timestamp DESC' ], 236 ], 237 '', 238 true, // tag filtering enabled 239 false, // not avoiding reopening tables 240 [ 'exception' => MWException::class ] 241 ], 242 'tag filter ignored when tag filtering is disabled' => [ 243 [ 244 'tables' => [ 'archive' ], 245 'fields' => [ 'ar_id', 'ar_timestamp' ], 246 'conds' => [ "ar_timestamp > '20170714183203'" ], 247 'join_conds' => [], 248 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 249 ], 250 'foo', 251 false, // tag filtering disabled 252 false, // not avoiding reopening tables 253 [ 254 'tables' => [ 'archive' ], 255 'fields' => [ 'ar_id', 'ar_timestamp', 'ts_tags' => $groupConcats['archive'] ], 256 'conds' => [ "ar_timestamp > '20170714183203'" ], 257 'join_conds' => [], 258 'options' => [ 'ORDER BY' => 'ar_timestamp DESC' ], 259 ] 260 ], 261 'recentchanges query with multiple tag filter' => [ 262 [ 263 'tables' => [ 'recentchanges' ], 264 'fields' => [ 'rc_id', 'rc_timestamp' ], 265 'conds' => [ "rc_timestamp > '20170714183203'" ], 266 'join_conds' => [], 267 'options' => [ 'ORDER BY' => 'rc_timestamp DESC' ], 268 ], 269 [ 'foo', 'bar' ], 270 true, // tag filtering enabled 271 false, // not avoiding reopening tables 272 [ 273 'tables' => [ 'recentchanges', 'change_tag' ], 274 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ], 275 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ], 276 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rc_id=rc_id' ] ], 277 'options' => [ 'ORDER BY' => 'rc_timestamp DESC', 'DISTINCT' ], 278 ] 279 ], 280 'recentchanges query with multiple tag filter that already has DISTINCT' => [ 281 [ 282 'tables' => [ 'recentchanges' ], 283 'fields' => [ 'rc_id', 'rc_timestamp' ], 284 'conds' => [ "rc_timestamp > '20170714183203'" ], 285 'join_conds' => [], 286 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ], 287 ], 288 [ 'foo', 'bar' ], 289 true, // tag filtering enabled 290 false, // not avoiding reopening tables 291 [ 292 'tables' => [ 'recentchanges', 'change_tag' ], 293 'fields' => [ 'rc_id', 'rc_timestamp', 'ts_tags' => $groupConcats['recentchanges'] ], 294 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ], 295 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rc_id=rc_id' ] ], 296 'options' => [ 'DISTINCT', 'ORDER BY' => 'rc_timestamp DESC' ], 297 ] 298 ], 299 'recentchanges query with multiple tag filter with strings' => [ 300 [ 301 'tables' => 'recentchanges', 302 'fields' => 'rc_id', 303 'conds' => "rc_timestamp > '20170714183203'", 304 'join_conds' => [], 305 'options' => 'ORDER BY rc_timestamp DESC', 306 ], 307 [ 'foo', 'bar' ], 308 true, // tag filtering enabled 309 false, // not avoiding reopening tables 310 [ 311 'tables' => [ 'recentchanges', 'change_tag' ], 312 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ], 313 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ], 314 'join_conds' => [ 'change_tag' => [ 'JOIN', 'ct_rc_id=rc_id' ] ], 315 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ], 316 ] 317 ], 318 'recentchanges query with multiple tag filter with strings, avoiding reopening tables' => [ 319 [ 320 'tables' => 'recentchanges', 321 'fields' => 'rc_id', 322 'conds' => "rc_timestamp > '20170714183203'", 323 'join_conds' => [], 324 'options' => 'ORDER BY rc_timestamp DESC', 325 ], 326 [ 'foo', 'bar' ], 327 true, // tag filtering enabled 328 true, // avoid reopening tables 329 [ 330 'tables' => [ 'recentchanges', 'change_tag_for_display_query' ], 331 'fields' => [ 'rc_id', 'ts_tags' => $groupConcats['recentchanges'] ], 332 'conds' => [ "rc_timestamp > '20170714183203'", 'ct_tag_id' => [ 1, 2 ] ], 333 'join_conds' => [ 'change_tag_for_display_query' => [ 'JOIN', 'ct_rc_id=rc_id' ] ], 334 'options' => [ 'ORDER BY rc_timestamp DESC', 'DISTINCT' ], 335 ] 336 ], 337 ]; 338 } 339 340 public static function dataGetSoftwareTags() { 341 return [ 342 [ 343 [ 344 'mw-contentModelChange' => true, 345 'mw-redirect' => true, 346 'mw-rollback' => true, 347 'mw-blank' => true, 348 'mw-replace' => true 349 ], 350 [ 351 'mw-rollback', 352 'mw-replace', 353 'mw-blank' 354 ] 355 ], 356 357 [ 358 [ 359 'mw-contentmodelchanged' => true, 360 'mw-replace' => true, 361 'mw-new-redirects' => true, 362 'mw-changed-redirect-target' => true, 363 'mw-rolback' => true, 364 'mw-blanking' => false 365 ], 366 [ 367 'mw-replace', 368 'mw-changed-redirect-target' 369 ] 370 ], 371 372 [ 373 [ 374 null, 375 false, 376 'Lorem ipsum', 377 'mw-translation' 378 ], 379 [] 380 ], 381 382 [ 383 [], 384 [] 385 ] 386 ]; 387 } 388 389 /** 390 * @dataProvider dataGetSoftwareTags 391 * @covers ChangeTags::getSoftwareTags 392 */ 393 public function testGetSoftwareTags( $softwareTags, $expected ) { 394 $this->setMwGlobals( 'wgSoftwareTags', $softwareTags ); 395 396 $actual = ChangeTags::getSoftwareTags(); 397 // Order of tags in arrays is not important 398 sort( $expected ); 399 sort( $actual ); 400 $this->assertEquals( $expected, $actual ); 401 } 402 403 public function testUpdateTags() { 404 // FIXME: fails under postgres 405 $this->markTestSkippedIfDbType( 'postgres' ); 406 407 $this->emptyChangeTagsTables(); 408 409 $rcId = 123; 410 $revId = 341; 411 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId, $revId ); 412 413 $dbr = wfGetDB( DB_REPLICA ); 414 415 $expected = [ 416 (object)[ 417 'ctd_name' => 'tag1', 418 'ctd_id' => 1, 419 'ctd_count' => 1 420 ], 421 (object)[ 422 'ctd_name' => 'tag2', 423 'ctd_id' => 2, 424 'ctd_count' => 1 425 ], 426 ]; 427 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' ); 428 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 429 430 $expected2 = [ 431 (object)[ 432 'ct_tag_id' => 1, 433 'ct_rc_id' => 123, 434 'ct_rev_id' => 341 435 ], 436 (object)[ 437 'ct_tag_id' => 2, 438 'ct_rc_id' => 123, 439 'ct_rev_id' => 341 440 ], 441 ]; 442 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id', 'ct_rev_id' ], '' ); 443 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) ); 444 445 $rcId = 124; 446 $revId = 342; 447 ChangeTags::updateTags( [ 'tag1' ], [], $rcId, $revId ); 448 ChangeTags::updateTags( [ 'tag3' ], [], $rcId, $revId ); 449 450 $expected = [ 451 (object)[ 452 'ctd_name' => 'tag1', 453 'ctd_id' => 1, 454 'ctd_count' => 2 455 ], 456 (object)[ 457 'ctd_name' => 'tag2', 458 'ctd_id' => 2, 459 'ctd_count' => 1 460 ], 461 (object)[ 462 'ctd_name' => 'tag3', 463 'ctd_id' => 3, 464 'ctd_count' => 1 465 ], 466 ]; 467 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' ); 468 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 469 470 $expected2 = [ 471 (object)[ 472 'ct_tag_id' => 1, 473 'ct_rc_id' => 123, 474 'ct_rev_id' => 341 475 ], 476 (object)[ 477 'ct_tag_id' => 1, 478 'ct_rc_id' => 124, 479 'ct_rev_id' => 342 480 ], 481 (object)[ 482 'ct_tag_id' => 2, 483 'ct_rc_id' => 123, 484 'ct_rev_id' => 341 485 ], 486 (object)[ 487 'ct_tag_id' => 3, 488 'ct_rc_id' => 124, 489 'ct_rev_id' => 342 490 ], 491 ]; 492 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id', 'ct_rev_id' ], '' ); 493 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) ); 494 } 495 496 public function testUpdateTagsSkipDuplicates() { 497 // FIXME: fails under postgres 498 $this->markTestSkippedIfDbType( 'postgres' ); 499 500 $this->emptyChangeTagsTables(); 501 502 $rcId = 123; 503 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId ); 504 ChangeTags::updateTags( [ 'tag2', 'tag3' ], [], $rcId ); 505 506 $dbr = wfGetDB( DB_REPLICA ); 507 508 $expected = [ 509 (object)[ 510 'ctd_name' => 'tag1', 511 'ctd_id' => 1, 512 'ctd_count' => 1 513 ], 514 (object)[ 515 'ctd_name' => 'tag2', 516 'ctd_id' => 2, 517 'ctd_count' => 1 518 ], 519 (object)[ 520 'ctd_name' => 'tag3', 521 'ctd_id' => 3, 522 'ctd_count' => 1 523 ], 524 ]; 525 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' ); 526 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 527 528 $expected2 = [ 529 (object)[ 530 'ct_tag_id' => 1, 531 'ct_rc_id' => 123 532 ], 533 (object)[ 534 'ct_tag_id' => 2, 535 'ct_rc_id' => 123 536 ], 537 (object)[ 538 'ct_tag_id' => 3, 539 'ct_rc_id' => 123 540 ], 541 ]; 542 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' ); 543 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) ); 544 } 545 546 public function testUpdateTagsDoNothingOnRepeatedCall() { 547 // FIXME: fails under postgres 548 $this->markTestSkippedIfDbType( 'postgres' ); 549 550 $this->emptyChangeTagsTables(); 551 552 $rcId = 123; 553 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId ); 554 $res = ChangeTags::updateTags( [ 'tag2', 'tag1' ], [], $rcId ); 555 $this->assertEquals( [ [], [], [ 'tag1', 'tag2' ] ], $res ); 556 557 $dbr = wfGetDB( DB_REPLICA ); 558 559 $expected = [ 560 (object)[ 561 'ctd_name' => 'tag1', 562 'ctd_id' => 1, 563 'ctd_count' => 1 564 ], 565 (object)[ 566 'ctd_name' => 'tag2', 567 'ctd_id' => 2, 568 'ctd_count' => 1 569 ], 570 ]; 571 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' ); 572 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 573 574 $expected2 = [ 575 (object)[ 576 'ct_tag_id' => 1, 577 'ct_rc_id' => 123 578 ], 579 (object)[ 580 'ct_tag_id' => 2, 581 'ct_rc_id' => 123 582 ], 583 ]; 584 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' ); 585 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) ); 586 } 587 588 public function testDeleteTags() { 589 $this->emptyChangeTagsTables(); 590 MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' ); 591 592 $rcId = 123; 593 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId ); 594 595 ChangeTags::updateTags( [], [ 'tag2' ], $rcId ); 596 597 $dbr = wfGetDB( DB_REPLICA ); 598 599 $expected = [ 600 (object)[ 601 'ctd_name' => 'tag1', 602 'ctd_id' => 1, 603 'ctd_count' => 1 604 ], 605 ]; 606 $res = $dbr->select( 'change_tag_def', [ 'ctd_name', 'ctd_id', 'ctd_count' ], '' ); 607 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 608 609 $expected2 = [ 610 (object)[ 611 'ct_tag_id' => 1, 612 'ct_rc_id' => 123 613 ] 614 ]; 615 $res2 = $dbr->select( 'change_tag', [ 'ct_tag_id', 'ct_rc_id' ], '' ); 616 $this->assertEquals( $expected2, iterator_to_array( $res2, false ) ); 617 } 618 619 public function provideTags() { 620 $tags = [ 'tag 1', 'tag 2', 'tag 3' ]; 621 $rcId = 123; 622 $revId = 456; 623 $logId = 789; 624 625 yield [ $tags, $rcId, null, null ]; 626 yield [ $tags, null, $revId, null ]; 627 yield [ $tags, null, null, $logId ]; 628 yield [ $tags, $rcId, $revId, null ]; 629 yield [ $tags, $rcId, null, $logId ]; 630 yield [ $tags, $rcId, $revId, $logId ]; 631 } 632 633 /** 634 * @dataProvider provideTags 635 */ 636 public function testGetTags( array $tags, $rcId, $revId, $logId ) { 637 ChangeTags::addTags( $tags, $rcId, $revId, $logId ); 638 639 $actualTags = ChangeTags::getTags( $this->db, $rcId, $revId, $logId ); 640 641 $this->assertSame( $tags, $actualTags ); 642 } 643 644 public function testGetTags_multiple_arguments() { 645 $rcId = 123; 646 $revId = 456; 647 $logId = 789; 648 649 ChangeTags::addTags( [ 'tag 1' ], $rcId ); 650 ChangeTags::addTags( [ 'tag 2' ], $rcId, $revId ); 651 ChangeTags::addTags( [ 'tag 3' ], $rcId, $revId, $logId ); 652 653 $tags3 = [ 'tag 3' ]; 654 $tags2 = array_merge( $tags3, [ 'tag 2' ] ); 655 $tags1 = array_merge( $tags2, [ 'tag 1' ] ); 656 $this->assertArrayEquals( $tags3, ChangeTags::getTags( $this->db, $rcId, $revId, $logId ) ); 657 $this->assertArrayEquals( $tags2, ChangeTags::getTags( $this->db, $rcId, $revId ) ); 658 $this->assertArrayEquals( $tags1, ChangeTags::getTags( $this->db, $rcId ) ); 659 } 660 661 public function testGetTagsWithData() { 662 $rcId1 = 123; 663 $rcId2 = 456; 664 $rcId3 = 789; 665 ChangeTags::addTags( [ 'tag 1' ], $rcId1, null, null, 'data1' ); 666 ChangeTags::addTags( [ 'tag 3_1' ], $rcId3, null, null ); 667 ChangeTags::addTags( [ 'tag 3_2' ], $rcId3, null, null, 'data3_2' ); 668 669 $data = ChangeTags::getTagsWithData( $this->db, $rcId1 ); 670 $this->assertSame( [ 'tag 1' => 'data1' ], $data ); 671 672 $data = ChangeTags::getTagsWithData( $this->db, $rcId2 ); 673 $this->assertSame( [], $data ); 674 675 $data = ChangeTags::getTagsWithData( $this->db, $rcId3 ); 676 $this->assertArrayEquals( [ 'tag 3_1' => null, 'tag 3_2' => 'data3_2' ], $data, false, true ); 677 } 678 679 public function testTagUsageStatistics() { 680 $this->emptyChangeTagsTables(); 681 MediaWikiServices::getInstance()->resetServiceForTesting( 'NameTableStoreFactory' ); 682 683 $rcId = 123; 684 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId ); 685 686 $rcId = 124; 687 ChangeTags::updateTags( [ 'tag1' ], [], $rcId ); 688 689 $this->assertEquals( [ 'tag1' => 2, 'tag2' => 1 ], ChangeTags::tagUsageStatistics() ); 690 } 691 692 public function testListExplicitlyDefinedTags() { 693 $this->emptyChangeTagsTables(); 694 695 $rcId = 123; 696 ChangeTags::updateTags( [ 'tag1', 'tag2' ], [], $rcId ); 697 ChangeTags::defineTag( 'tag2' ); 698 699 $this->assertEquals( [ 'tag2' ], ChangeTags::listExplicitlyDefinedTags() ); 700 $dbr = wfGetDB( DB_REPLICA ); 701 702 $expected = [ 703 (object)[ 704 'ctd_name' => 'tag1', 705 'ctd_user_defined' => 0 706 ], 707 (object)[ 708 'ctd_name' => 'tag2', 709 'ctd_user_defined' => 1 710 ], 711 ]; 712 $res = $dbr->select( 713 'change_tag_def', 714 [ 'ctd_name', 'ctd_user_defined' ], 715 '', 716 __METHOD__, 717 [ 'ORDER BY' => 'ctd_name' ] 718 ); 719 $this->assertEquals( $expected, iterator_to_array( $res, false ) ); 720 } 721} 722