sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/label/label_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package label 18 19 import ( 20 "fmt" 21 "sort" 22 "strings" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 "github.com/sirupsen/logrus" 28 "k8s.io/apimachinery/pkg/util/sets" 29 "sigs.k8s.io/prow/pkg/config" 30 "sigs.k8s.io/prow/pkg/github" 31 "sigs.k8s.io/prow/pkg/github/fakegithub" 32 "sigs.k8s.io/prow/pkg/labels" 33 "sigs.k8s.io/prow/pkg/plugins" 34 ) 35 36 const ( 37 orgMember = "Alice" 38 nonOrgMember = "Bob" 39 ) 40 41 func formatWithPRInfo(labels ...string) []string { 42 r := []string{} 43 for _, l := range labels { 44 r = append(r, fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 1, l)) 45 } 46 if len(r) == 0 { 47 return nil 48 } 49 return r 50 } 51 52 func TestHandleComment(t *testing.T) { 53 type testCase struct { 54 name string 55 body string 56 commenter string 57 extraLabels []string 58 restrictedLabels map[string][]plugins.RestrictedLabel 59 expectedNewLabels []string 60 expectedRemovedLabels []string 61 expectedBotComment bool 62 repoLabels []string 63 issueLabels []string 64 expectedCommentText string 65 action github.GenericCommentEventAction 66 teams map[string]map[string]fakegithub.TeamWithMembers 67 } 68 testcases := []testCase{ 69 { 70 name: "Irrelevant comment", 71 body: "irrelelvant", 72 expectedNewLabels: []string{}, 73 expectedRemovedLabels: []string{}, 74 repoLabels: []string{}, 75 issueLabels: []string{}, 76 commenter: orgMember, 77 action: github.GenericCommentActionCreated, 78 }, 79 { 80 name: "Empty Area", 81 body: "/area", 82 expectedNewLabels: []string{}, 83 expectedRemovedLabels: []string{}, 84 repoLabels: []string{"area/infra"}, 85 issueLabels: []string{"area/infra"}, 86 commenter: orgMember, 87 action: github.GenericCommentActionCreated, 88 }, 89 { 90 name: "Add Single Area Label", 91 body: "/area infra", 92 repoLabels: []string{"area/infra"}, 93 issueLabels: []string{}, 94 expectedNewLabels: formatWithPRInfo("area/infra"), 95 expectedRemovedLabels: []string{}, 96 commenter: orgMember, 97 action: github.GenericCommentActionCreated, 98 }, 99 { 100 name: "Add Single Area Label when already present on Issue", 101 body: "/area infra", 102 repoLabels: []string{"area/infra"}, 103 issueLabels: []string{"area/infra"}, 104 expectedNewLabels: []string{}, 105 expectedRemovedLabels: []string{}, 106 commenter: orgMember, 107 action: github.GenericCommentActionCreated, 108 }, 109 { 110 name: "Add Single Priority Label", 111 body: "/priority critical", 112 repoLabels: []string{"area/infra", "priority/critical"}, 113 issueLabels: []string{}, 114 expectedNewLabels: formatWithPRInfo("priority/critical"), 115 expectedRemovedLabels: []string{}, 116 commenter: orgMember, 117 action: github.GenericCommentActionCreated, 118 }, 119 { 120 name: "Add Single Kind Label", 121 body: "/kind bug", 122 repoLabels: []string{"area/infra", "priority/critical", labels.Bug}, 123 issueLabels: []string{}, 124 expectedNewLabels: formatWithPRInfo(labels.Bug), 125 expectedRemovedLabels: []string{}, 126 commenter: orgMember, 127 action: github.GenericCommentActionCreated, 128 }, 129 { 130 name: "Add Single Triage Label", 131 body: "/triage needs-information", 132 repoLabels: []string{"area/infra", "triage/needs-information"}, 133 issueLabels: []string{"area/infra"}, 134 expectedNewLabels: formatWithPRInfo("triage/needs-information"), 135 expectedRemovedLabels: []string{}, 136 commenter: orgMember, 137 action: github.GenericCommentActionCreated, 138 }, 139 { 140 name: "Org member can add triage/accepted label", 141 body: "/triage accepted", 142 repoLabels: []string{"triage/accepted"}, 143 issueLabels: []string{}, 144 expectedNewLabels: formatWithPRInfo("triage/accepted"), 145 expectedRemovedLabels: []string{}, 146 commenter: orgMember, 147 action: github.GenericCommentActionCreated, 148 }, 149 { 150 name: "Non org member cannot add triage/accepted label", 151 body: "/triage accepted", 152 repoLabels: []string{"triage/accepted", "kind/bug"}, 153 issueLabels: []string{"kind/bug"}, 154 expectedNewLabels: formatWithPRInfo(), 155 expectedRemovedLabels: []string{}, 156 commenter: nonOrgMember, 157 expectedBotComment: true, 158 expectedCommentText: "The label `triage/accepted` cannot be applied. Only GitHub organization members can add the label.", 159 action: github.GenericCommentActionCreated, 160 }, 161 { 162 name: "Non org member can add triage/needs-information label", 163 body: "/triage needs-information", 164 repoLabels: []string{"area/infra", "triage/needs-information"}, 165 issueLabels: []string{"area/infra"}, 166 expectedNewLabels: formatWithPRInfo("triage/needs-information"), 167 expectedRemovedLabels: []string{}, 168 commenter: nonOrgMember, 169 action: github.GenericCommentActionCreated, 170 }, 171 { 172 name: "Adding Labels is Case Insensitive", 173 body: "/kind BuG", 174 repoLabels: []string{"area/infra", "priority/critical", labels.Bug}, 175 issueLabels: []string{}, 176 expectedNewLabels: formatWithPRInfo(labels.Bug), 177 expectedRemovedLabels: []string{}, 178 commenter: orgMember, 179 action: github.GenericCommentActionCreated, 180 }, 181 { 182 name: "Adding Labels is Case Insensitive", 183 body: "/kind bug", 184 repoLabels: []string{"area/infra", "priority/critical", labels.Bug}, 185 issueLabels: []string{}, 186 expectedNewLabels: formatWithPRInfo(labels.Bug), 187 expectedRemovedLabels: []string{}, 188 commenter: orgMember, 189 action: github.GenericCommentActionCreated, 190 }, 191 { 192 name: "Can't Add Non Existent Label", 193 body: "/priority critical", 194 repoLabels: []string{"area/infra"}, 195 issueLabels: []string{}, 196 expectedNewLabels: formatWithPRInfo(), 197 expectedRemovedLabels: []string{}, 198 commenter: orgMember, 199 expectedBotComment: true, 200 expectedCommentText: "The label(s) `priority/critical` cannot be applied, because the repository doesn't have them.", 201 action: github.GenericCommentActionCreated, 202 }, 203 { 204 name: "Non Org Member Can't Add", 205 body: "/area infra", 206 repoLabels: []string{"area/infra", "priority/critical", labels.Bug}, 207 issueLabels: []string{}, 208 expectedNewLabels: formatWithPRInfo("area/infra"), 209 expectedRemovedLabels: []string{}, 210 commenter: nonOrgMember, 211 action: github.GenericCommentActionCreated, 212 }, 213 { 214 name: "Command must start at the beginning of the line", 215 body: " /area infra", 216 repoLabels: []string{"area/infra", "area/api", "priority/critical", "priority/urgent", "priority/important", labels.Bug}, 217 issueLabels: []string{}, 218 expectedNewLabels: formatWithPRInfo(), 219 expectedRemovedLabels: []string{}, 220 commenter: orgMember, 221 action: github.GenericCommentActionCreated, 222 }, 223 { 224 name: "Can't Add Labels Non Existing Labels", 225 body: "/area lgtm", 226 repoLabels: []string{"area/infra", "area/api", "priority/critical"}, 227 issueLabels: []string{}, 228 expectedNewLabels: formatWithPRInfo(), 229 expectedRemovedLabels: []string{}, 230 commenter: orgMember, 231 expectedBotComment: true, 232 expectedCommentText: "The label(s) `area/lgtm` cannot be applied, because the repository doesn't have them.", 233 action: github.GenericCommentActionCreated, 234 }, 235 { 236 name: "Add Multiple Area Labels", 237 body: "/area api infra", 238 repoLabels: []string{"area/infra", "area/api", "priority/critical", "priority/urgent"}, 239 issueLabels: []string{}, 240 expectedNewLabels: formatWithPRInfo("area/api", "area/infra"), 241 expectedRemovedLabels: []string{}, 242 commenter: orgMember, 243 action: github.GenericCommentActionCreated, 244 }, 245 { 246 name: "Add Multiple Area Labels one already present on Issue", 247 body: "/area api infra", 248 repoLabels: []string{"area/infra", "area/api", "priority/critical", "priority/urgent"}, 249 issueLabels: []string{"area/api"}, 250 expectedNewLabels: formatWithPRInfo("area/infra"), 251 expectedRemovedLabels: []string{}, 252 commenter: orgMember, 253 action: github.GenericCommentActionCreated, 254 }, 255 { 256 name: "Add Multiple Priority Labels", 257 body: "/priority critical important", 258 repoLabels: []string{"priority/critical", "priority/important"}, 259 issueLabels: []string{}, 260 expectedNewLabels: formatWithPRInfo("priority/critical", "priority/important"), 261 expectedRemovedLabels: []string{}, 262 commenter: orgMember, 263 action: github.GenericCommentActionCreated, 264 }, 265 { 266 name: "Add Multiple Area Labels, With Trailing Whitespace", 267 body: "/area api infra ", 268 repoLabels: []string{"area/infra", "area/api"}, 269 issueLabels: []string{}, 270 expectedNewLabels: formatWithPRInfo("area/api", "area/infra"), 271 expectedRemovedLabels: []string{}, 272 commenter: orgMember, 273 action: github.GenericCommentActionCreated, 274 }, 275 { 276 name: "Label Prefix Must Match Command (Area-Priority Mismatch)", 277 body: "/area urgent", 278 repoLabels: []string{"area/infra", "area/api", "priority/critical", "priority/urgent"}, 279 issueLabels: []string{}, 280 expectedNewLabels: formatWithPRInfo(), 281 expectedRemovedLabels: []string{}, 282 commenter: orgMember, 283 expectedBotComment: true, 284 expectedCommentText: "The label(s) `area/urgent` cannot be applied, because the repository doesn't have them.", 285 action: github.GenericCommentActionCreated, 286 }, 287 { 288 name: "Label Prefix Must Match Command (Priority-Area Mismatch)", 289 body: "/priority infra", 290 repoLabels: []string{"area/infra", "area/api", "priority/critical", "priority/urgent"}, 291 issueLabels: []string{}, 292 expectedNewLabels: formatWithPRInfo(), 293 expectedRemovedLabels: []string{}, 294 commenter: orgMember, 295 expectedBotComment: true, 296 expectedCommentText: "The label(s) `priority/infra` cannot be applied, because the repository doesn't have them.", 297 action: github.GenericCommentActionCreated, 298 }, 299 { 300 name: "Add Multiple Area Labels (Some Valid)", 301 body: "/area lgtm infra", 302 repoLabels: []string{"area/infra", "area/api"}, 303 issueLabels: []string{}, 304 expectedNewLabels: formatWithPRInfo("area/infra"), 305 expectedRemovedLabels: []string{}, 306 commenter: orgMember, 307 expectedBotComment: true, 308 expectedCommentText: "The label(s) `area/lgtm` cannot be applied, because the repository doesn't have them.", 309 action: github.GenericCommentActionCreated, 310 }, 311 { 312 name: "Add Multiple Committee Labels (Some Valid)", 313 body: "/committee steering calamity", 314 repoLabels: []string{"committee/conduct", "committee/steering"}, 315 issueLabels: []string{}, 316 expectedNewLabels: formatWithPRInfo("committee/steering"), 317 expectedRemovedLabels: []string{}, 318 commenter: orgMember, 319 expectedBotComment: true, 320 expectedCommentText: "The label(s) `committee/calamity` cannot be applied, because the repository doesn't have them.", 321 action: github.GenericCommentActionCreated, 322 }, 323 { 324 name: "Non org member adds multiple triage labels (some valid)", 325 body: "/triage needs-information accepted", 326 repoLabels: []string{"triage/needs-information", "triage/accepted"}, 327 issueLabels: []string{}, 328 expectedNewLabels: formatWithPRInfo("triage/needs-information"), 329 expectedRemovedLabels: []string{}, 330 commenter: nonOrgMember, 331 expectedBotComment: true, 332 expectedCommentText: "The label `triage/accepted` cannot be applied. Only GitHub organization members can add the label.", 333 action: github.GenericCommentActionCreated, 334 }, 335 { 336 name: "Add Multiple Types of Labels Different Lines", 337 body: "/priority urgent\n/area infra", 338 repoLabels: []string{"area/infra", "priority/urgent"}, 339 issueLabels: []string{}, 340 expectedNewLabels: formatWithPRInfo("priority/urgent", "area/infra"), 341 expectedRemovedLabels: []string{}, 342 commenter: orgMember, 343 action: github.GenericCommentActionCreated, 344 }, 345 { 346 name: "Remove Area Label when no such Label on Repo", 347 body: "/remove-area infra", 348 repoLabels: []string{}, 349 issueLabels: []string{}, 350 expectedNewLabels: []string{}, 351 expectedRemovedLabels: []string{}, 352 commenter: orgMember, 353 expectedBotComment: true, 354 action: github.GenericCommentActionCreated, 355 }, 356 { 357 name: "Remove Area Label when no such Label on Issue", 358 body: "/remove-area infra", 359 repoLabels: []string{"area/infra"}, 360 issueLabels: []string{}, 361 expectedNewLabels: []string{}, 362 expectedRemovedLabels: []string{}, 363 commenter: orgMember, 364 expectedBotComment: true, 365 action: github.GenericCommentActionCreated, 366 }, 367 { 368 name: "Remove Area Label", 369 body: "/remove-area infra", 370 repoLabels: []string{"area/infra"}, 371 issueLabels: []string{"area/infra"}, 372 expectedNewLabels: []string{}, 373 expectedRemovedLabels: formatWithPRInfo("area/infra"), 374 commenter: orgMember, 375 action: github.GenericCommentActionCreated, 376 }, 377 { 378 name: "Remove Committee Label", 379 body: "/remove-committee infinite-monkeys", 380 repoLabels: []string{"area/infra", "sig/testing", "committee/infinite-monkeys"}, 381 issueLabels: []string{"area/infra", "sig/testing", "committee/infinite-monkeys"}, 382 expectedNewLabels: []string{}, 383 expectedRemovedLabels: formatWithPRInfo("committee/infinite-monkeys"), 384 commenter: orgMember, 385 action: github.GenericCommentActionCreated, 386 }, 387 { 388 name: "Remove Kind Label", 389 body: "/remove-kind api-server", 390 repoLabels: []string{"area/infra", "priority/high", "kind/api-server", "needs-kind"}, 391 issueLabels: []string{"area/infra", "priority/high", "kind/api-server"}, 392 expectedNewLabels: formatWithPRInfo("needs-kind"), 393 expectedRemovedLabels: formatWithPRInfo("kind/api-server"), 394 commenter: orgMember, 395 action: github.GenericCommentActionCreated, 396 }, 397 { 398 name: "Remove Priority Label", 399 body: "/remove-priority high", 400 repoLabels: []string{"area/infra", "priority/high", "needs-priority"}, 401 issueLabels: []string{"area/infra", "priority/high"}, 402 expectedNewLabels: formatWithPRInfo("needs-priority"), 403 expectedRemovedLabels: formatWithPRInfo("priority/high"), 404 commenter: orgMember, 405 action: github.GenericCommentActionCreated, 406 }, 407 { 408 name: "Remove SIG Label", 409 body: "/remove-sig testing", 410 repoLabels: []string{"area/infra", "sig/testing", "needs-sig"}, 411 issueLabels: []string{"area/infra", "sig/testing"}, 412 expectedNewLabels: formatWithPRInfo("needs-sig"), 413 expectedRemovedLabels: formatWithPRInfo("sig/testing"), 414 commenter: orgMember, 415 action: github.GenericCommentActionCreated, 416 }, 417 { 418 name: "Remove one of many SIG Label", 419 body: "/remove-sig testing", 420 repoLabels: []string{"area/infra", "sig/testing", "sig/node", "sig/auth", "needs-sig"}, 421 issueLabels: []string{"area/infra", "sig/testing", "sig/node", "sig/auth"}, 422 expectedNewLabels: []string{}, 423 expectedRemovedLabels: formatWithPRInfo("sig/testing"), 424 commenter: orgMember, 425 action: github.GenericCommentActionCreated, 426 }, 427 { 428 name: "Add and Remove SIG Label", 429 body: "/remove-sig testing\n/sig node", 430 repoLabels: []string{"area/infra", "sig/testing", "sig/node", "needs-sig"}, 431 issueLabels: []string{"area/infra", "sig/testing"}, 432 expectedNewLabels: formatWithPRInfo("sig/node"), 433 expectedRemovedLabels: formatWithPRInfo("sig/testing"), 434 commenter: orgMember, 435 action: github.GenericCommentActionCreated, 436 }, 437 { 438 name: "Remove WG Policy", 439 body: "/remove-wg policy", 440 repoLabels: []string{"area/infra", "wg/policy"}, 441 issueLabels: []string{"area/infra", "wg/policy"}, 442 expectedNewLabels: []string{}, 443 expectedRemovedLabels: formatWithPRInfo("wg/policy"), 444 commenter: orgMember, 445 action: github.GenericCommentActionCreated, 446 }, 447 { 448 name: "Remove Triage Label", 449 body: "/remove-triage needs-information accepted", 450 repoLabels: []string{"area/infra", "triage/needs-information", "triage/accepted", "needs-triage"}, 451 issueLabels: []string{"area/infra", "triage/needs-information", "triage/accepted"}, 452 expectedNewLabels: formatWithPRInfo("needs-triage"), 453 expectedRemovedLabels: formatWithPRInfo("triage/needs-information", "triage/accepted"), 454 commenter: orgMember, 455 action: github.GenericCommentActionCreated, 456 }, 457 { 458 name: "Remove Multiple Labels", 459 body: "/remove-priority low high\n/remove-kind api-server\n/remove-area infra", 460 repoLabels: []string{"area/infra", "priority/high", "priority/low", "kind/api-server"}, 461 issueLabels: []string{"area/infra", "priority/high", "priority/low", "kind/api-server"}, 462 expectedNewLabels: []string{}, 463 expectedRemovedLabels: formatWithPRInfo("priority/low", "priority/high", "kind/api-server", "area/infra"), 464 commenter: orgMember, 465 expectedBotComment: true, 466 action: github.GenericCommentActionCreated, 467 }, 468 { 469 name: "Add and Remove Label at the same time", 470 body: "/remove-area infra\n/area test", 471 repoLabels: []string{"area/infra", "area/test"}, 472 issueLabels: []string{"area/infra"}, 473 expectedNewLabels: formatWithPRInfo("area/test"), 474 expectedRemovedLabels: formatWithPRInfo("area/infra"), 475 commenter: orgMember, 476 action: github.GenericCommentActionCreated, 477 }, 478 { 479 name: "Add and Remove the same Label", 480 body: "/remove-area infra\n/area infra", 481 repoLabels: []string{"area/infra"}, 482 issueLabels: []string{"area/infra"}, 483 expectedNewLabels: []string{}, 484 expectedRemovedLabels: formatWithPRInfo("area/infra"), 485 commenter: orgMember, 486 action: github.GenericCommentActionCreated, 487 }, 488 { 489 name: "Multiple Add and Delete Labels", 490 body: "/remove-area ruby\n/remove-kind srv\n/remove-priority l m\n/area go\n/kind cli\n/priority h", 491 repoLabels: []string{"area/go", "area/ruby", "kind/cli", "kind/srv", "priority/h", "priority/m", "priority/l"}, 492 issueLabels: []string{"area/ruby", "kind/srv", "priority/l", "priority/m"}, 493 expectedNewLabels: formatWithPRInfo("area/go", "kind/cli", "priority/h"), 494 expectedRemovedLabels: formatWithPRInfo("area/ruby", "kind/srv", "priority/l", "priority/m"), 495 commenter: orgMember, 496 action: github.GenericCommentActionCreated, 497 }, 498 { 499 name: "Do nothing with empty /label command", 500 body: "/label", 501 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 502 repoLabels: []string{"orchestrator/foo"}, 503 issueLabels: []string{}, 504 expectedNewLabels: []string{}, 505 expectedRemovedLabels: []string{}, 506 commenter: orgMember, 507 action: github.GenericCommentActionCreated, 508 }, 509 { 510 name: "Do nothing with empty /remove-label command", 511 body: "/remove-label", 512 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 513 repoLabels: []string{"orchestrator/foo"}, 514 issueLabels: []string{}, 515 expectedNewLabels: []string{}, 516 expectedRemovedLabels: []string{}, 517 commenter: orgMember, 518 action: github.GenericCommentActionCreated, 519 }, 520 { 521 name: "Add custom label", 522 body: "/label orchestrator/foo", 523 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 524 repoLabels: []string{"orchestrator/foo"}, 525 issueLabels: []string{}, 526 expectedNewLabels: formatWithPRInfo("orchestrator/foo"), 527 expectedRemovedLabels: []string{}, 528 commenter: orgMember, 529 action: github.GenericCommentActionCreated, 530 }, 531 { 532 name: "Add custom label with trailing space", 533 body: "/label orchestrator/foo ", 534 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 535 repoLabels: []string{"orchestrator/foo"}, 536 issueLabels: []string{}, 537 expectedNewLabels: formatWithPRInfo("orchestrator/foo"), 538 expectedRemovedLabels: []string{}, 539 commenter: orgMember, 540 action: github.GenericCommentActionCreated, 541 }, 542 { 543 name: "Add custom label with trailing LF newline", 544 body: "/label orchestrator/foo\n", 545 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 546 repoLabels: []string{"orchestrator/foo"}, 547 issueLabels: []string{}, 548 expectedNewLabels: formatWithPRInfo("orchestrator/foo"), 549 expectedRemovedLabels: []string{}, 550 commenter: orgMember, 551 action: github.GenericCommentActionCreated, 552 }, 553 { 554 name: "Add custom label with trailing CRLF newline", 555 body: "/label orchestrator/foo\r\n", 556 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 557 repoLabels: []string{"orchestrator/foo"}, 558 issueLabels: []string{}, 559 expectedNewLabels: formatWithPRInfo("orchestrator/foo"), 560 expectedRemovedLabels: []string{}, 561 commenter: orgMember, 562 action: github.GenericCommentActionCreated, 563 }, 564 { 565 name: "Cannot add missing custom label", 566 body: "/label orchestrator/foo", 567 extraLabels: []string{"orchestrator/jar", "orchestrator/bar"}, 568 repoLabels: []string{"orchestrator/foo"}, 569 issueLabels: []string{}, 570 expectedNewLabels: []string{}, 571 expectedRemovedLabels: []string{}, 572 commenter: orgMember, 573 expectedBotComment: true, 574 expectedCommentText: "The label(s) `/label orchestrator/foo` cannot be applied. These labels are supported: `orchestrator/jar, orchestrator/bar`", 575 action: github.GenericCommentActionCreated, 576 }, 577 { 578 name: "Remove custom label", 579 body: "/remove-label orchestrator/foo", 580 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 581 repoLabels: []string{"orchestrator/foo"}, 582 issueLabels: []string{"orchestrator/foo"}, 583 expectedNewLabels: []string{}, 584 expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"), 585 commenter: orgMember, 586 action: github.GenericCommentActionCreated, 587 }, 588 { 589 name: "Remove custom label with trailing space", 590 body: "/remove-label orchestrator/foo ", 591 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 592 repoLabels: []string{"orchestrator/foo"}, 593 issueLabels: []string{"orchestrator/foo"}, 594 expectedNewLabels: []string{}, 595 expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"), 596 commenter: orgMember, 597 action: github.GenericCommentActionCreated, 598 }, 599 { 600 name: "Remove custom label with trailing LF newline", 601 body: "/remove-label orchestrator/foo\n", 602 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 603 repoLabels: []string{"orchestrator/foo"}, 604 issueLabels: []string{"orchestrator/foo"}, 605 expectedNewLabels: []string{}, 606 expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"), 607 commenter: orgMember, 608 action: github.GenericCommentActionCreated, 609 }, 610 { 611 name: "Remove custom label with trailing CRLF newline", 612 body: "/remove-label orchestrator/foo\r\n", 613 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 614 repoLabels: []string{"orchestrator/foo"}, 615 issueLabels: []string{"orchestrator/foo"}, 616 expectedNewLabels: []string{}, 617 expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"), 618 commenter: orgMember, 619 action: github.GenericCommentActionCreated, 620 }, 621 { 622 name: "Cannot remove missing custom label", 623 body: "/remove-label orchestrator/jar", 624 extraLabels: []string{"orchestrator/foo", "orchestrator/bar"}, 625 repoLabels: []string{"orchestrator/foo"}, 626 issueLabels: []string{"orchestrator/foo"}, 627 expectedNewLabels: []string{}, 628 expectedRemovedLabels: []string{}, 629 commenter: orgMember, 630 expectedBotComment: true, 631 expectedCommentText: "The label(s) `/remove-label orchestrator/jar` cannot be applied. These labels are supported: `orchestrator/foo, orchestrator/bar`", 632 action: github.GenericCommentActionCreated, 633 }, 634 { 635 name: "Don't comment when deleting label addition", 636 body: "/kind bug", 637 repoLabels: []string{"area/infra", "priority/critical", labels.Bug}, 638 issueLabels: []string{}, 639 expectedNewLabels: []string{}, 640 expectedRemovedLabels: []string{}, 641 commenter: orgMember, 642 expectedBotComment: false, 643 action: github.GenericCommentActionDeleted, 644 }, 645 { 646 name: "Don't comment when deleting label removal", 647 body: "/remove-committee infinite-monkeys", 648 repoLabels: []string{"area/infra", "sig/testing", "committee/infinite-monkeys"}, 649 issueLabels: []string{"area/infra", "sig/testing", "committee/infinite-monkeys"}, 650 expectedNewLabels: []string{}, 651 expectedRemovedLabels: []string{}, 652 commenter: orgMember, 653 expectedBotComment: false, 654 action: github.GenericCommentActionDeleted, 655 }, 656 { 657 name: "Don't take action while editing body", 658 body: "/kind bug", 659 repoLabels: []string{labels.Bug}, 660 issueLabels: []string{}, 661 expectedNewLabels: []string{}, 662 expectedRemovedLabels: []string{}, 663 commenter: orgMember, 664 expectedBotComment: false, 665 action: github.GenericCommentActionEdited, 666 }, 667 { 668 name: "Strip markdown comments", 669 body: ` 670 <!-- 671 /kind bug 672 /kind cleanup 673 --> 674 /area infra 675 `, 676 repoLabels: []string{"area/infra"}, 677 issueLabels: []string{}, 678 expectedNewLabels: formatWithPRInfo("area/infra"), 679 expectedRemovedLabels: []string{}, 680 commenter: orgMember, 681 action: github.GenericCommentActionCreated, 682 }, 683 { 684 name: "Strip markdown comments non greedy", 685 body: ` 686 <!-- 687 /kind bug 688 --> 689 /kind cleanup 690 <!-- 691 /area infra 692 --> 693 /kind regression 694 `, 695 repoLabels: []string{"kind/cleanup", "kind/regression"}, 696 issueLabels: []string{}, 697 expectedNewLabels: formatWithPRInfo("kind/cleanup", "kind/regression"), 698 expectedRemovedLabels: []string{}, 699 commenter: orgMember, 700 action: github.GenericCommentActionCreated, 701 }, 702 { 703 name: "Restricted label addition, user is not in group", 704 body: `/label restricted-label`, 705 repoLabels: []string{"restricted-label"}, 706 commenter: orgMember, 707 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}}, 708 action: github.GenericCommentActionCreated, 709 expectedBotComment: true, 710 }, 711 { 712 name: "Restricted label addition, user is in group", 713 body: `/label restricted-label`, 714 repoLabels: []string{"restricted-label"}, 715 commenter: orgMember, 716 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}}, 717 action: github.GenericCommentActionCreated, 718 teams: map[string]map[string]fakegithub.TeamWithMembers{"org": {"privileged-group": {Members: sets.New[string](orgMember)}}}, 719 expectedNewLabels: formatWithPRInfo("restricted-label"), 720 }, 721 { 722 name: "Restricted label addition, user is in allowed_users", 723 body: `/label restricted-label`, 724 repoLabels: []string{"restricted-label"}, 725 commenter: orgMember, 726 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedUsers: []string{orgMember}}}}, 727 action: github.GenericCommentActionCreated, 728 expectedNewLabels: formatWithPRInfo("restricted-label"), 729 }, 730 { 731 name: "Restricted label removal, user is not in group", 732 body: `/remove-label restricted-label`, 733 repoLabels: []string{"restricted-label"}, 734 issueLabels: []string{"restricted-label"}, 735 commenter: orgMember, 736 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}}, 737 action: github.GenericCommentActionCreated, 738 expectedBotComment: true, 739 }, 740 { 741 name: "Restricted label removal, user is in group", 742 body: `/remove-label restricted-label`, 743 repoLabels: []string{"restricted-label"}, 744 issueLabels: []string{"restricted-label"}, 745 commenter: orgMember, 746 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}}, 747 action: github.GenericCommentActionCreated, 748 teams: map[string]map[string]fakegithub.TeamWithMembers{"org": {"privileged-group": {Members: sets.New[string](orgMember)}}}, 749 expectedRemovedLabels: formatWithPRInfo("restricted-label"), 750 }, 751 { 752 name: "Restricted label removal, user is in allowed_users", 753 body: `/remove-label restricted-label`, 754 repoLabels: []string{"restricted-label"}, 755 issueLabels: []string{"restricted-label"}, 756 commenter: orgMember, 757 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedUsers: []string{orgMember}}}}, 758 action: github.GenericCommentActionCreated, 759 expectedRemovedLabels: formatWithPRInfo("restricted-label"), 760 }, 761 } 762 763 for _, tc := range testcases { 764 t.Run(tc.name, func(t *testing.T) { 765 sort.Strings(tc.expectedNewLabels) 766 fakeClient := fakegithub.NewFakeClient() 767 fakeClient.Issues = make(map[int]*github.Issue) 768 fakeClient.IssueComments = make(map[int][]github.IssueComment) 769 fakeClient.RepoLabelsExisting = tc.repoLabels 770 fakeClient.OrgMembers = map[string][]string{"org": {orgMember}} 771 fakeClient.IssueLabelsAdded = []string{} 772 fakeClient.IssueLabelsRemoved = []string{} 773 fakeClient.Teams = tc.teams 774 // Add initial labels 775 for _, label := range tc.issueLabels { 776 fakeClient.AddLabel("org", "repo", 1, label) 777 } 778 e := &github.GenericCommentEvent{ 779 Action: tc.action, 780 Body: tc.body, 781 Number: 1, 782 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 783 User: github.User{Login: tc.commenter}, 784 } 785 err := handleComment(fakeClient, logrus.WithField("plugin", PluginName), plugins.Label{AdditionalLabels: tc.extraLabels, RestrictedLabels: tc.restrictedLabels}, e) 786 if err != nil { 787 t.Fatalf("didn't expect error from handle comment test: %v", err) 788 } 789 790 // Check that all the correct labels (and only the correct labels) were added. 791 expectLabels := append(formatWithPRInfo(tc.issueLabels...), tc.expectedNewLabels...) 792 if expectLabels == nil { 793 expectLabels = []string{} 794 } 795 sort.Strings(expectLabels) 796 sort.Strings(fakeClient.IssueLabelsAdded) 797 if diff := cmp.Diff(expectLabels, fakeClient.IssueLabelsAdded, cmpopts.EquateEmpty()); diff != "" { 798 t.Errorf("labels expected to add do not match actual added labels: %s", diff) 799 } 800 801 sort.Strings(tc.expectedRemovedLabels) 802 sort.Strings(fakeClient.IssueLabelsRemoved) 803 if diff := cmp.Diff(tc.expectedRemovedLabels, fakeClient.IssueLabelsRemoved, cmpopts.EquateEmpty()); diff != "" { 804 t.Errorf("expected removed labels differ from actual removed labels: %s", diff) 805 } 806 if len(fakeClient.IssueCommentsAdded) > 0 && !tc.expectedBotComment { 807 t.Errorf("unexpected bot comments: %#v", fakeClient.IssueCommentsAdded) 808 } 809 if len(fakeClient.IssueCommentsAdded) == 0 && tc.expectedBotComment { 810 t.Error("expected a bot comment but got none") 811 } 812 if tc.expectedBotComment && len(tc.expectedCommentText) > 0 { 813 if len(fakeClient.IssueComments) < 1 { 814 t.Errorf("expected actual: %v", fakeClient.IssueComments) 815 } 816 if len(fakeClient.IssueComments[1]) != 1 || !strings.Contains(fakeClient.IssueComments[1][0].Body, tc.expectedCommentText) { 817 t.Errorf("expected: `%v`, actual: `%v`", tc.expectedCommentText, fakeClient.IssueComments[1][0].Body) 818 } 819 } 820 }) 821 } 822 } 823 824 func TestHandleLabelAdd(t *testing.T) { 825 type testCase struct { 826 name string 827 restrictedLabels map[string][]plugins.RestrictedLabel 828 expectedAssignees []string 829 labelAdded string 830 action github.PullRequestEventAction 831 } 832 testCases := []testCase{ 833 { 834 name: "label added with no auto-assign configured", 835 labelAdded: "some-label", 836 action: github.PullRequestActionLabeled, 837 }, 838 { 839 name: "assign users for restricted label on label add", 840 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "secondary-label", AllowedUsers: []string{"bill", "sally"}, AssignOn: []plugins.AssignOnLabel{{Label: "initial-label"}}}}}, 841 labelAdded: "initial-label", 842 action: github.PullRequestActionLabeled, 843 expectedAssignees: formatWithPRInfo("bill", "sally"), 844 }, 845 { 846 name: "no assigned users on irrelevant label add", 847 restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "secondary-label", AllowedUsers: []string{"bill", "sally"}, AssignOn: []plugins.AssignOnLabel{{Label: "initial-label"}}}}}, 848 labelAdded: "other-label", 849 action: github.PullRequestActionLabeled, 850 }, 851 } 852 for _, tc := range testCases { 853 t.Run(tc.name, func(t *testing.T) { 854 fakeClient := fakegithub.NewFakeClient() 855 e := &github.PullRequestEvent{ 856 Action: tc.action, 857 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 858 PullRequest: github.PullRequest{Number: 1}, 859 Label: github.Label{Name: tc.labelAdded}, 860 } 861 err := handleLabelAdd(fakeClient, logrus.WithField("plugin", PluginName), plugins.Label{RestrictedLabels: tc.restrictedLabels}, e) 862 if err != nil { 863 t.Fatalf("didn't expect error from handle label test: %v", err) 864 } 865 if diff := cmp.Diff(tc.expectedAssignees, fakeClient.AssigneesAdded, cmpopts.EquateEmpty()); diff != "" { 866 t.Errorf("expected added assignees differ from actual: %s", diff) 867 } 868 }) 869 } 870 } 871 872 func TestHelpProvider(t *testing.T) { 873 enabledRepos := []config.OrgRepo{ 874 {Org: "org1", Repo: "repo"}, 875 {Org: "org2", Repo: "repo"}, 876 } 877 cases := []struct { 878 name string 879 config *plugins.Configuration 880 enabledRepos []config.OrgRepo 881 err bool 882 configInfoIncludes []string 883 }{ 884 { 885 name: "Empty config", 886 config: &plugins.Configuration{}, 887 enabledRepos: enabledRepos, 888 configInfoIncludes: []string{configString(defaultLabels)}, 889 }, 890 { 891 name: "With AdditionalLabels", 892 config: &plugins.Configuration{ 893 Label: plugins.Label{ 894 AdditionalLabels: []string{"sig", "triage", "wg"}, 895 }, 896 }, 897 enabledRepos: enabledRepos, 898 configInfoIncludes: []string{configString(append(defaultLabels, "sig", "triage", "wg"))}, 899 }, 900 } 901 for _, c := range cases { 902 t.Run(c.name, func(t *testing.T) { 903 pluginHelp, err := helpProvider(c.config, c.enabledRepos) 904 if err != nil && !c.err { 905 t.Fatalf("helpProvider error: %v", err) 906 } 907 for _, msg := range c.configInfoIncludes { 908 if !strings.Contains(pluginHelp.Config[""], msg) { 909 t.Fatalf("helpProvider.Config error mismatch: didn't get %v, but wanted it", msg) 910 } 911 } 912 }) 913 } 914 }