github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/lgtm/lgtm_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 lgtm 18 19 import ( 20 "fmt" 21 "testing" 22 "time" 23 24 "github.com/sirupsen/logrus" 25 "k8s.io/apimachinery/pkg/api/equality" 26 "k8s.io/apimachinery/pkg/util/sets" 27 28 "k8s.io/test-infra/prow/github" 29 "k8s.io/test-infra/prow/github/fakegithub" 30 "k8s.io/test-infra/prow/plugins" 31 "k8s.io/test-infra/prow/repoowners" 32 ) 33 34 type fakeOwnersClient struct { 35 approvers map[string]sets.String 36 reviewers map[string]sets.String 37 } 38 39 var _ repoowners.Interface = &fakeOwnersClient{} 40 41 func (f *fakeOwnersClient) LoadRepoAliases(org, repo, base string) (repoowners.RepoAliases, error) { 42 return nil, nil 43 } 44 45 func (f *fakeOwnersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwner, error) { 46 return &fakeRepoOwners{approvers: f.approvers, reviewers: f.reviewers}, nil 47 } 48 49 type fakeRepoOwners struct { 50 approvers map[string]sets.String 51 reviewers map[string]sets.String 52 } 53 54 type fakePruner struct { 55 GithubClient *fakegithub.FakeClient 56 IssueComments []github.IssueComment 57 } 58 59 func (fp *fakePruner) PruneComments(shouldPrune func(github.IssueComment) bool) { 60 for _, comment := range fp.IssueComments { 61 if shouldPrune(comment) { 62 fp.GithubClient.IssueCommentsDeleted = append(fp.GithubClient.IssueCommentsDeleted, comment.Body) 63 } 64 } 65 } 66 67 var _ repoowners.RepoOwner = &fakeRepoOwners{} 68 69 func (f *fakeRepoOwners) FindApproverOwnersForFile(path string) string { return "" } 70 func (f *fakeRepoOwners) FindReviewersOwnersForFile(path string) string { return "" } 71 func (f *fakeRepoOwners) FindLabelsForFile(path string) sets.String { return nil } 72 func (f *fakeRepoOwners) IsNoParentOwners(path string) bool { return false } 73 func (f *fakeRepoOwners) LeafApprovers(path string) sets.String { return nil } 74 func (f *fakeRepoOwners) Approvers(path string) sets.String { return f.approvers[path] } 75 func (f *fakeRepoOwners) LeafReviewers(path string) sets.String { return nil } 76 func (f *fakeRepoOwners) Reviewers(path string) sets.String { return f.reviewers[path] } 77 func (f *fakeRepoOwners) RequiredReviewers(path string) sets.String { return nil } 78 79 var approvers = map[string]sets.String{ 80 "doc/README.md": { 81 "cjwagner": {}, 82 "jessica": {}, 83 }, 84 } 85 86 var reviewers = map[string]sets.String{ 87 "doc/README.md": { 88 "alice": {}, 89 "bob": {}, 90 "mark": {}, 91 "sam": {}, 92 }, 93 } 94 95 func TestLGTMComment(t *testing.T) { 96 var testcases = []struct { 97 name string 98 body string 99 commenter string 100 hasLGTM bool 101 shouldToggle bool 102 shouldComment bool 103 shouldAssign bool 104 skipCollab bool 105 storeTreeHash bool 106 }{ 107 { 108 name: "non-lgtm comment", 109 body: "uh oh", 110 commenter: "o", 111 hasLGTM: false, 112 shouldToggle: false, 113 }, 114 { 115 name: "lgtm comment by reviewer, no lgtm on pr", 116 body: "/lgtm", 117 commenter: "reviewer1", 118 hasLGTM: false, 119 shouldToggle: true, 120 shouldComment: true, 121 }, 122 { 123 name: "LGTM comment by reviewer, no lgtm on pr", 124 body: "/LGTM", 125 commenter: "reviewer1", 126 hasLGTM: false, 127 shouldToggle: true, 128 shouldComment: true, 129 }, 130 { 131 name: "lgtm comment by reviewer, lgtm on pr", 132 body: "/lgtm", 133 commenter: "reviewer1", 134 hasLGTM: true, 135 shouldToggle: false, 136 }, 137 { 138 name: "lgtm comment by author", 139 body: "/lgtm", 140 commenter: "author", 141 hasLGTM: false, 142 shouldToggle: false, 143 shouldComment: true, 144 }, 145 { 146 name: "lgtm cancel by author", 147 body: "/lgtm cancel", 148 commenter: "author", 149 hasLGTM: true, 150 shouldToggle: true, 151 shouldAssign: false, 152 shouldComment: false, 153 }, 154 { 155 name: "lgtm comment by non-reviewer", 156 body: "/lgtm", 157 commenter: "o", 158 hasLGTM: false, 159 shouldToggle: true, 160 shouldComment: true, 161 shouldAssign: true, 162 }, 163 { 164 name: "lgtm comment by non-reviewer, with trailing space", 165 body: "/lgtm ", 166 commenter: "o", 167 hasLGTM: false, 168 shouldToggle: true, 169 shouldComment: true, 170 shouldAssign: true, 171 }, 172 { 173 name: "lgtm comment by non-reviewer, with no-issue", 174 body: "/lgtm no-issue", 175 commenter: "o", 176 hasLGTM: false, 177 shouldToggle: true, 178 shouldComment: true, 179 shouldAssign: true, 180 }, 181 { 182 name: "lgtm comment by non-reviewer, with no-issue and trailing space", 183 body: "/lgtm no-issue \r", 184 commenter: "o", 185 hasLGTM: false, 186 shouldToggle: true, 187 shouldComment: true, 188 shouldAssign: true, 189 }, 190 { 191 name: "lgtm comment by rando", 192 body: "/lgtm", 193 commenter: "not-in-the-org", 194 hasLGTM: false, 195 shouldToggle: false, 196 shouldComment: true, 197 shouldAssign: false, 198 }, 199 { 200 name: "lgtm cancel by non-reviewer", 201 body: "/lgtm cancel", 202 commenter: "o", 203 hasLGTM: true, 204 shouldToggle: true, 205 shouldComment: false, 206 shouldAssign: true, 207 }, 208 { 209 name: "lgtm cancel by rando", 210 body: "/lgtm cancel", 211 commenter: "not-in-the-org", 212 hasLGTM: true, 213 shouldToggle: false, 214 shouldComment: true, 215 shouldAssign: false, 216 }, 217 { 218 name: "lgtm cancel comment by reviewer", 219 body: "/lgtm cancel", 220 commenter: "reviewer1", 221 hasLGTM: true, 222 shouldToggle: true, 223 }, 224 { 225 name: "lgtm cancel comment by reviewer, with trailing space", 226 body: "/lgtm cancel \r", 227 commenter: "reviewer1", 228 hasLGTM: true, 229 shouldToggle: true, 230 }, 231 { 232 name: "lgtm cancel comment by reviewer, no lgtm", 233 body: "/lgtm cancel", 234 commenter: "reviewer1", 235 hasLGTM: false, 236 shouldToggle: false, 237 }, 238 { 239 name: "lgtm comment, based off OWNERS only", 240 body: "/lgtm", 241 commenter: "sam", 242 hasLGTM: false, 243 shouldToggle: true, 244 shouldComment: true, 245 skipCollab: true, 246 }, 247 } 248 SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652" 249 for _, tc := range testcases { 250 t.Logf("Running scenario %q", tc.name) 251 fc := &fakegithub.FakeClient{ 252 IssueComments: make(map[int][]github.IssueComment), 253 PullRequests: map[int]*github.PullRequest{ 254 5: { 255 Base: github.PullRequestBranch{ 256 Ref: "master", 257 }, 258 Head: github.PullRequestBranch{ 259 SHA: SHA, 260 }, 261 }, 262 }, 263 PullRequestChanges: map[int][]github.PullRequestChange{ 264 5: { 265 {Filename: "doc/README.md"}, 266 }, 267 }, 268 } 269 e := &github.GenericCommentEvent{ 270 Action: github.GenericCommentActionCreated, 271 IssueState: "open", 272 IsPR: true, 273 Body: tc.body, 274 User: github.User{Login: tc.commenter}, 275 IssueAuthor: github.User{Login: "author"}, 276 Number: 5, 277 Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, 278 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 279 HTMLURL: "<url>", 280 } 281 if tc.hasLGTM { 282 fc.IssueLabelsAdded = []string{"org/repo#5:" + LGTMLabel} 283 } 284 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 285 pc := &plugins.Configuration{} 286 if tc.skipCollab { 287 pc.Owners.SkipCollaborators = []string{"org/repo"} 288 } 289 pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{ 290 Repos: []string{"org/repo"}, 291 StoreTreeHash: true, 292 }) 293 fp := &fakePruner{ 294 GithubClient: fc, 295 IssueComments: fc.IssueComments[5], 296 } 297 if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil { 298 t.Errorf("didn't expect error from lgtmComment: %v", err) 299 continue 300 } 301 if tc.shouldAssign { 302 found := false 303 for _, a := range fc.AssigneesAdded { 304 if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.commenter) { 305 found = true 306 break 307 } 308 } 309 if !found || len(fc.AssigneesAdded) != 1 { 310 t.Errorf("should have assigned %s but added assignees are %s", tc.commenter, fc.AssigneesAdded) 311 } 312 } else if len(fc.AssigneesAdded) != 0 { 313 t.Errorf("should not have assigned anyone but assigned %s", fc.AssigneesAdded) 314 } 315 if tc.shouldToggle { 316 if tc.hasLGTM { 317 if len(fc.IssueLabelsRemoved) == 0 { 318 t.Error("should have removed LGTM.") 319 } else if len(fc.IssueLabelsAdded) > 1 { 320 t.Error("should not have added LGTM.") 321 } 322 } else { 323 if len(fc.IssueLabelsAdded) == 0 { 324 t.Error("should have added LGTM.") 325 } else if len(fc.IssueLabelsRemoved) > 0 { 326 t.Error("should not have removed LGTM.") 327 } 328 } 329 } else if len(fc.IssueLabelsRemoved) > 0 { 330 t.Error("should not have removed LGTM.") 331 } else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) { 332 t.Error("should not have added LGTM.") 333 } 334 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 335 t.Error("should have commented.") 336 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 337 t.Error("should not have commented.") 338 } 339 } 340 } 341 342 func TestLGTMCommentWithLGTMNoti(t *testing.T) { 343 var testcases = []struct { 344 name string 345 body string 346 commenter string 347 shouldDelete bool 348 }{ 349 { 350 name: "non-lgtm comment", 351 body: "uh oh", 352 commenter: "o", 353 shouldDelete: false, 354 }, 355 { 356 name: "lgtm comment by reviewer, no lgtm on pr", 357 body: "/lgtm", 358 commenter: "reviewer1", 359 shouldDelete: true, 360 }, 361 { 362 name: "LGTM comment by reviewer, no lgtm on pr", 363 body: "/LGTM", 364 commenter: "reviewer1", 365 shouldDelete: true, 366 }, 367 { 368 name: "lgtm comment by author", 369 body: "/lgtm", 370 commenter: "author", 371 shouldDelete: false, 372 }, 373 { 374 name: "lgtm comment by non-reviewer", 375 body: "/lgtm", 376 commenter: "o", 377 shouldDelete: true, 378 }, 379 { 380 name: "lgtm comment by non-reviewer, with trailing space", 381 body: "/lgtm ", 382 commenter: "o", 383 shouldDelete: true, 384 }, 385 { 386 name: "lgtm comment by non-reviewer, with no-issue", 387 body: "/lgtm no-issue", 388 commenter: "o", 389 shouldDelete: true, 390 }, 391 { 392 name: "lgtm comment by non-reviewer, with no-issue and trailing space", 393 body: "/lgtm no-issue \r", 394 commenter: "o", 395 shouldDelete: true, 396 }, 397 { 398 name: "lgtm comment by rando", 399 body: "/lgtm", 400 commenter: "not-in-the-org", 401 shouldDelete: false, 402 }, 403 { 404 name: "lgtm cancel comment by reviewer, no lgtm", 405 body: "/lgtm cancel", 406 commenter: "reviewer1", 407 shouldDelete: false, 408 }, 409 } 410 SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652" 411 for _, tc := range testcases { 412 fc := &fakegithub.FakeClient{ 413 IssueComments: make(map[int][]github.IssueComment), 414 PullRequests: map[int]*github.PullRequest{ 415 5: { 416 Head: github.PullRequestBranch{ 417 SHA: SHA, 418 }, 419 }, 420 }, 421 } 422 e := &github.GenericCommentEvent{ 423 Action: github.GenericCommentActionCreated, 424 IssueState: "open", 425 IsPR: true, 426 Body: tc.body, 427 User: github.User{Login: tc.commenter}, 428 IssueAuthor: github.User{Login: "author"}, 429 Number: 5, 430 Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, 431 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 432 HTMLURL: "<url>", 433 } 434 botName, err := fc.BotName() 435 if err != nil { 436 t.Fatalf("For case %s, could not get Bot nam", tc.name) 437 } 438 ic := github.IssueComment{ 439 User: github.User{ 440 Login: botName, 441 }, 442 Body: removeLGTMLabelNoti, 443 } 444 fc.IssueComments[5] = append(fc.IssueComments[5], ic) 445 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 446 pc := &plugins.Configuration{} 447 fp := &fakePruner{ 448 GithubClient: fc, 449 IssueComments: fc.IssueComments[5], 450 } 451 if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil { 452 t.Errorf("For case %s, didn't expect error from lgtmComment: %v", tc.name, err) 453 continue 454 } 455 deleted := false 456 for _, body := range fc.IssueCommentsDeleted { 457 if body == removeLGTMLabelNoti { 458 deleted = true 459 break 460 } 461 } 462 if tc.shouldDelete { 463 if !deleted { 464 t.Errorf("For case %s, LGTM removed notification should have been deleted", tc.name) 465 } 466 } else { 467 if deleted { 468 t.Errorf("For case %s, LGTM removed notification should not have been deleted", tc.name) 469 } 470 } 471 } 472 } 473 474 func TestLGTMFromApproveReview(t *testing.T) { 475 var testcases = []struct { 476 name string 477 state github.ReviewState 478 body string 479 reviewer string 480 hasLGTM bool 481 shouldToggle bool 482 shouldComment bool 483 shouldAssign bool 484 storeTreeHash bool 485 }{ 486 { 487 name: "Request changes review by reviewer, no lgtm on pr", 488 state: github.ReviewStateChangesRequested, 489 reviewer: "reviewer1", 490 hasLGTM: false, 491 shouldToggle: false, 492 shouldAssign: false, 493 shouldComment: false, 494 }, 495 { 496 name: "Request changes review by reviewer, lgtm on pr", 497 state: github.ReviewStateChangesRequested, 498 reviewer: "reviewer1", 499 hasLGTM: true, 500 shouldToggle: true, 501 shouldAssign: false, 502 }, 503 { 504 name: "Approve review by reviewer, no lgtm on pr", 505 state: github.ReviewStateApproved, 506 reviewer: "reviewer1", 507 hasLGTM: false, 508 shouldToggle: true, 509 shouldComment: true, 510 storeTreeHash: true, 511 }, 512 { 513 name: "Approve review by reviewer, no lgtm on pr, do not store tree_hash", 514 state: github.ReviewStateApproved, 515 reviewer: "reviewer1", 516 hasLGTM: false, 517 shouldToggle: true, 518 shouldComment: false, 519 }, 520 { 521 name: "Approve review by reviewer, lgtm on pr", 522 state: github.ReviewStateApproved, 523 reviewer: "reviewer1", 524 hasLGTM: true, 525 shouldToggle: false, 526 shouldAssign: false, 527 }, 528 { 529 name: "Approve review by non-reviewer, no lgtm on pr", 530 state: github.ReviewStateApproved, 531 reviewer: "o", 532 hasLGTM: false, 533 shouldToggle: true, 534 shouldComment: true, 535 shouldAssign: true, 536 storeTreeHash: true, 537 }, 538 { 539 name: "Request changes review by non-reviewer, no lgtm on pr", 540 state: github.ReviewStateChangesRequested, 541 reviewer: "o", 542 hasLGTM: false, 543 shouldToggle: false, 544 shouldComment: false, 545 shouldAssign: true, 546 }, 547 { 548 name: "Approve review by rando", 549 state: github.ReviewStateApproved, 550 reviewer: "not-in-the-org", 551 hasLGTM: false, 552 shouldToggle: false, 553 shouldComment: true, 554 shouldAssign: false, 555 }, 556 { 557 name: "Comment review by issue author, no lgtm on pr", 558 state: github.ReviewStateCommented, 559 reviewer: "author", 560 hasLGTM: false, 561 shouldToggle: false, 562 shouldComment: false, 563 shouldAssign: false, 564 }, 565 { 566 name: "Comment body has /lgtm on Comment Review ", 567 state: github.ReviewStateCommented, 568 reviewer: "reviewer1", 569 body: "/lgtm", 570 hasLGTM: false, 571 shouldToggle: false, 572 shouldComment: false, 573 shouldAssign: false, 574 }, 575 { 576 name: "Comment body has /lgtm cancel on Approve Review", 577 state: github.ReviewStateApproved, 578 reviewer: "reviewer1", 579 body: "/lgtm cancel", 580 hasLGTM: false, 581 shouldToggle: false, 582 shouldComment: false, 583 shouldAssign: false, 584 }, 585 } 586 SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652" 587 for _, tc := range testcases { 588 fc := &fakegithub.FakeClient{ 589 IssueComments: make(map[int][]github.IssueComment), 590 IssueLabelsAdded: []string{}, 591 PullRequests: map[int]*github.PullRequest{ 592 5: { 593 Head: github.PullRequestBranch{ 594 SHA: SHA, 595 }, 596 }, 597 }, 598 } 599 e := &github.ReviewEvent{ 600 Review: github.Review{Body: tc.body, State: tc.state, HTMLURL: "<url>", User: github.User{Login: tc.reviewer}}, 601 PullRequest: github.PullRequest{User: github.User{Login: "author"}, Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, Number: 5}, 602 Repo: github.Repo{Owner: github.User{Login: "org"}, Name: "repo"}, 603 } 604 if tc.hasLGTM { 605 fc.IssueLabelsAdded = append(fc.IssueLabelsAdded, "org/repo#5:"+LGTMLabel) 606 } 607 oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers} 608 pc := &plugins.Configuration{} 609 pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{ 610 Repos: []string{"org/repo"}, 611 StoreTreeHash: tc.storeTreeHash, 612 }) 613 fp := &fakePruner{ 614 GithubClient: fc, 615 IssueComments: fc.IssueComments[5], 616 } 617 if err := handlePullRequestReview(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil { 618 t.Errorf("For case %s, didn't expect error from pull request review: %v", tc.name, err) 619 continue 620 } 621 if tc.shouldAssign { 622 found := false 623 for _, a := range fc.AssigneesAdded { 624 if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.reviewer) { 625 found = true 626 break 627 } 628 } 629 if !found || len(fc.AssigneesAdded) != 1 { 630 t.Errorf("For case %s, should have assigned %s but added assignees are %s", tc.name, tc.reviewer, fc.AssigneesAdded) 631 } 632 } else if len(fc.AssigneesAdded) != 0 { 633 t.Errorf("For case %s, should not have assigned anyone but assigned %s", tc.name, fc.AssigneesAdded) 634 } 635 if tc.shouldToggle { 636 if tc.hasLGTM { 637 if len(fc.IssueLabelsRemoved) == 0 { 638 t.Errorf("For case %s, should have removed LGTM.", tc.name) 639 } else if len(fc.IssueLabelsAdded) > 1 { 640 t.Errorf("For case %s, should not have added LGTM.", tc.name) 641 } 642 } else { 643 if len(fc.IssueLabelsAdded) == 0 { 644 t.Errorf("For case %s, should have added LGTM.", tc.name) 645 } else if len(fc.IssueLabelsRemoved) > 0 { 646 t.Errorf("For case %s, should not have removed LGTM.", tc.name) 647 } 648 } 649 } else if len(fc.IssueLabelsRemoved) > 0 { 650 t.Errorf("For case %s, should not have removed LGTM.", tc.name) 651 } else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) { 652 t.Errorf("For case %s, should not have added LGTM.", tc.name) 653 } 654 if tc.shouldComment && len(fc.IssueComments[5]) != 1 { 655 t.Errorf("For case %s, should have commented.", tc.name) 656 } else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 { 657 t.Errorf("For case %s, should not have commented.", tc.name) 658 } 659 } 660 } 661 662 func TestHandlePullRequest(t *testing.T) { 663 SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652" 664 treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e" 665 cases := []struct { 666 name string 667 event github.PullRequestEvent 668 removeLabelErr error 669 createCommentErr error 670 671 err error 672 IssueLabelsAdded []string 673 IssueLabelsRemoved []string 674 issueComments map[int][]github.IssueComment 675 trustedTeam string 676 677 expectNoComments bool 678 }{ 679 { 680 name: "pr_synchronize, no RemoveLabel error", 681 event: github.PullRequestEvent{ 682 Action: github.PullRequestActionSynchronize, 683 PullRequest: github.PullRequest{ 684 Number: 101, 685 Base: github.PullRequestBranch{ 686 Repo: github.Repo{ 687 Owner: github.User{ 688 Login: "kubernetes", 689 }, 690 Name: "kubernetes", 691 }, 692 }, 693 Head: github.PullRequestBranch{ 694 SHA: SHA, 695 }, 696 }, 697 }, 698 IssueLabelsRemoved: []string{LGTMLabel}, 699 issueComments: map[int][]github.IssueComment{ 700 101: { 701 { 702 Body: removeLGTMLabelNoti, 703 User: github.User{Login: fakegithub.Bot}, 704 }, 705 }, 706 }, 707 expectNoComments: false, 708 }, 709 { 710 name: "Sticky LGTM for trusted team members", 711 event: github.PullRequestEvent{ 712 Action: github.PullRequestActionSynchronize, 713 PullRequest: github.PullRequest{ 714 Number: 101, 715 Base: github.PullRequestBranch{ 716 Repo: github.Repo{ 717 Owner: github.User{ 718 Login: "kubernetes", 719 }, 720 Name: "kubernetes", 721 }, 722 }, 723 User: github.User{ 724 Login: "sig-lead", 725 }, 726 MergeSHA: &SHA, 727 }, 728 }, 729 trustedTeam: "Leads", 730 expectNoComments: true, 731 }, 732 { 733 name: "LGTM not sticky for trusted user if disabled", 734 event: github.PullRequestEvent{ 735 Action: github.PullRequestActionSynchronize, 736 PullRequest: github.PullRequest{ 737 Number: 101, 738 Base: github.PullRequestBranch{ 739 Repo: github.Repo{ 740 Owner: github.User{ 741 Login: "kubernetes", 742 }, 743 Name: "kubernetes", 744 }, 745 }, 746 User: github.User{ 747 Login: "sig-lead", 748 }, 749 MergeSHA: &SHA, 750 }, 751 }, 752 IssueLabelsRemoved: []string{LGTMLabel}, 753 issueComments: map[int][]github.IssueComment{ 754 101: { 755 { 756 Body: removeLGTMLabelNoti, 757 User: github.User{Login: fakegithub.Bot}, 758 }, 759 }, 760 }, 761 expectNoComments: false, 762 }, 763 { 764 name: "LGTM not sticky for non trusted user", 765 event: github.PullRequestEvent{ 766 Action: github.PullRequestActionSynchronize, 767 PullRequest: github.PullRequest{ 768 Number: 101, 769 Base: github.PullRequestBranch{ 770 Repo: github.Repo{ 771 Owner: github.User{ 772 Login: "kubernetes", 773 }, 774 Name: "kubernetes", 775 }, 776 }, 777 User: github.User{ 778 Login: "sig-lead", 779 }, 780 MergeSHA: &SHA, 781 }, 782 }, 783 IssueLabelsRemoved: []string{LGTMLabel}, 784 issueComments: map[int][]github.IssueComment{ 785 101: { 786 { 787 Body: removeLGTMLabelNoti, 788 User: github.User{Login: fakegithub.Bot}, 789 }, 790 }, 791 }, 792 trustedTeam: "Committers", 793 expectNoComments: false, 794 }, 795 { 796 name: "pr_assigned", 797 event: github.PullRequestEvent{ 798 Action: "assigned", 799 }, 800 expectNoComments: true, 801 }, 802 { 803 name: "pr_synchronize, same tree-hash, keep label", 804 event: github.PullRequestEvent{ 805 Action: github.PullRequestActionSynchronize, 806 PullRequest: github.PullRequest{ 807 Number: 101, 808 Base: github.PullRequestBranch{ 809 Repo: github.Repo{ 810 Owner: github.User{ 811 Login: "kubernetes", 812 }, 813 Name: "kubernetes", 814 }, 815 }, 816 Head: github.PullRequestBranch{ 817 SHA: SHA, 818 }, 819 }, 820 }, 821 issueComments: map[int][]github.IssueComment{ 822 101: { 823 { 824 Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA), 825 User: github.User{Login: fakegithub.Bot}, 826 }, 827 }, 828 }, 829 expectNoComments: true, 830 }, 831 { 832 name: "pr_synchronize, same tree-hash, keep label, edited comment", 833 event: github.PullRequestEvent{ 834 Action: github.PullRequestActionSynchronize, 835 PullRequest: github.PullRequest{ 836 Number: 101, 837 Base: github.PullRequestBranch{ 838 Repo: github.Repo{ 839 Owner: github.User{ 840 Login: "kubernetes", 841 }, 842 Name: "kubernetes", 843 }, 844 }, 845 Head: github.PullRequestBranch{ 846 SHA: SHA, 847 }, 848 }, 849 }, 850 IssueLabelsRemoved: []string{LGTMLabel}, 851 issueComments: map[int][]github.IssueComment{ 852 101: { 853 { 854 Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA), 855 User: github.User{Login: fakegithub.Bot}, 856 CreatedAt: time.Date(1981, 2, 21, 12, 30, 0, 0, time.UTC), 857 UpdatedAt: time.Date(1981, 2, 21, 12, 31, 0, 0, time.UTC), 858 }, 859 }, 860 }, 861 expectNoComments: false, 862 }, 863 } 864 for _, c := range cases { 865 t.Run(c.name, func(t *testing.T) { 866 fakeGitHub := &fakegithub.FakeClient{ 867 IssueComments: c.issueComments, 868 PullRequests: map[int]*github.PullRequest{ 869 101: { 870 Base: github.PullRequestBranch{ 871 Ref: "master", 872 }, 873 Head: github.PullRequestBranch{ 874 SHA: SHA, 875 }, 876 }, 877 }, 878 Commits: make(map[string]github.SingleCommit), 879 Collaborators: []string{"collab"}, 880 IssueLabelsAdded: c.IssueLabelsAdded, 881 } 882 fakeGitHub.IssueLabelsAdded = append(fakeGitHub.IssueLabelsAdded, "kubernetes/kubernetes#101:lgtm") 883 commit := github.SingleCommit{} 884 commit.Commit.Tree.SHA = treeSHA 885 fakeGitHub.Commits[SHA] = commit 886 pc := &plugins.Configuration{} 887 pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{ 888 Repos: []string{"kubernetes/kubernetes"}, 889 StoreTreeHash: true, 890 StickyLgtmTeam: c.trustedTeam, 891 }) 892 err := handlePullRequest( 893 logrus.WithField("plugin", "approve"), 894 fakeGitHub, 895 pc, 896 &c.event, 897 ) 898 899 if err != nil && c.err == nil { 900 t.Fatalf("handlePullRequest error: %v", err) 901 } 902 903 if err == nil && c.err != nil { 904 t.Fatalf("handlePullRequest wanted error: %v, got nil", c.err) 905 } 906 907 if got, want := err, c.err; !equality.Semantic.DeepEqual(got, want) { 908 t.Fatalf("handlePullRequest error mismatch: got %v, want %v", got, want) 909 } 910 911 if got, want := len(fakeGitHub.IssueLabelsRemoved), len(c.IssueLabelsRemoved); got != want { 912 t.Logf("IssueLabelsRemoved: got %v, want: %v", fakeGitHub.IssueLabelsRemoved, c.IssueLabelsRemoved) 913 t.Fatalf("IssueLabelsRemoved length mismatch: got %d, want %d", got, want) 914 } 915 916 if got, want := fakeGitHub.IssueComments, c.issueComments; !equality.Semantic.DeepEqual(got, want) { 917 t.Fatalf("LGTM revmoved notifications mismatch: got %v, want %v", got, want) 918 } 919 if c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) > 0 { 920 t.Fatalf("expected no comments but got %v", fakeGitHub.IssueCommentsAdded) 921 } 922 if !c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) == 0 { 923 t.Fatalf("expected comments but got none") 924 } 925 }) 926 } 927 } 928 929 func TestAddTreeHashComment(t *testing.T) { 930 cases := []struct { 931 name string 932 author string 933 trustedTeam string 934 expectTreeSha bool 935 }{ 936 { 937 name: "Tree SHA added", 938 author: "Bob", 939 expectTreeSha: true, 940 }, 941 { 942 name: "Tree SHA if sticky lgtm off", 943 author: "sig-lead", 944 expectTreeSha: true, 945 }, 946 { 947 name: "No Tree SHA if sticky lgtm", 948 author: "sig-lead", 949 trustedTeam: "Leads", 950 expectTreeSha: false, 951 }, 952 } 953 954 for _, c := range cases { 955 t.Run(c.name, func(t *testing.T) { 956 957 SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652" 958 treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e" 959 pc := &plugins.Configuration{} 960 pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{ 961 Repos: []string{"kubernetes/kubernetes"}, 962 StoreTreeHash: true, 963 StickyLgtmTeam: c.trustedTeam, 964 }) 965 rc := reviewCtx{ 966 author: "alice", 967 issueAuthor: c.author, 968 repo: github.Repo{ 969 Owner: github.User{ 970 Login: "kubernetes", 971 }, 972 Name: "kubernetes", 973 }, 974 number: 101, 975 body: "/lgtm", 976 } 977 fc := &fakegithub.FakeClient{ 978 Commits: make(map[string]github.SingleCommit), 979 IssueComments: map[int][]github.IssueComment{}, 980 PullRequests: map[int]*github.PullRequest{ 981 101: { 982 Base: github.PullRequestBranch{ 983 Ref: "master", 984 }, 985 Head: github.PullRequestBranch{ 986 SHA: SHA, 987 }, 988 }, 989 }, 990 } 991 commit := github.SingleCommit{} 992 commit.Commit.Tree.SHA = treeSHA 993 fc.Commits[SHA] = commit 994 handle(true, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), &fakePruner{}) 995 found := false 996 for _, body := range fc.IssueCommentsAdded { 997 if addLGTMLabelNotificationRe.MatchString(body) { 998 found = true 999 break 1000 } 1001 } 1002 if c.expectTreeSha { 1003 if !found { 1004 t.Fatalf("expected tree_hash comment but got none") 1005 } 1006 } else { 1007 if found { 1008 t.Fatalf("expected no tree_hash comment but got one") 1009 } 1010 } 1011 }) 1012 } 1013 } 1014 1015 func TestRemoveTreeHashComment(t *testing.T) { 1016 treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e" 1017 pc := &plugins.Configuration{} 1018 pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{ 1019 Repos: []string{"kubernetes/kubernetes"}, 1020 StoreTreeHash: true, 1021 }) 1022 rc := reviewCtx{ 1023 author: "alice", 1024 issueAuthor: "bob", 1025 repo: github.Repo{ 1026 Owner: github.User{ 1027 Login: "kubernetes", 1028 }, 1029 Name: "kubernetes", 1030 }, 1031 assignees: []github.User{{Login: "alice"}}, 1032 number: 101, 1033 body: "/lgtm cancel", 1034 } 1035 fc := &fakegithub.FakeClient{ 1036 IssueComments: map[int][]github.IssueComment{ 1037 101: { 1038 { 1039 Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA), 1040 User: github.User{Login: fakegithub.Bot}, 1041 }, 1042 }, 1043 }, 1044 } 1045 fc.IssueLabelsAdded = []string{"kubernetes/kubernetes#101:" + LGTMLabel} 1046 fp := &fakePruner{ 1047 GithubClient: fc, 1048 IssueComments: fc.IssueComments[101], 1049 } 1050 handle(false, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), fp) 1051 found := false 1052 for _, body := range fc.IssueCommentsDeleted { 1053 if addLGTMLabelNotificationRe.MatchString(body) { 1054 found = true 1055 break 1056 } 1057 } 1058 if !found { 1059 t.Fatalf("expected deleted tree_hash comment but got none") 1060 } 1061 }