github.com/abayer/test-infra@v0.0.5/prow/plugins/approve/approve_test.go (about) 1 /* 2 Copyright 2017 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 approve 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/sirupsen/logrus" 27 28 "github.com/ghodss/yaml" 29 30 "k8s.io/apimachinery/pkg/util/sets" 31 "k8s.io/test-infra/prow/github" 32 "k8s.io/test-infra/prow/github/fakegithub" 33 "k8s.io/test-infra/prow/plugins" 34 "k8s.io/test-infra/prow/plugins/approve/approvers" 35 "k8s.io/test-infra/prow/repoowners" 36 ) 37 38 const prNumber = 1 39 40 // TestPluginConfig validates that there are no duplicate repos in the approve plugin config. 41 func TestPluginConfig(t *testing.T) { 42 pa := &plugins.PluginAgent{} 43 44 b, err := ioutil.ReadFile("../../plugins.yaml") 45 if err != nil { 46 t.Fatalf("Failed to read plugin config: %v.", err) 47 } 48 np := &plugins.Configuration{} 49 if err := yaml.Unmarshal(b, np); err != nil { 50 t.Fatalf("Failed to unmarshal plugin config: %v.", err) 51 } 52 pa.Set(np) 53 54 orgs := map[string]bool{} 55 repos := map[string]bool{} 56 for _, config := range pa.Config().Approve { 57 for _, entry := range config.Repos { 58 if strings.Contains(entry, "/") { 59 if repos[entry] { 60 t.Errorf("The repo %q is duplicated in the 'approve' plugin configuration.", entry) 61 } 62 repos[entry] = true 63 } else { 64 if orgs[entry] { 65 t.Errorf("The org %q is duplicated in the 'approve' plugin configuration.", entry) 66 } 67 orgs[entry] = true 68 } 69 } 70 } 71 for repo := range repos { 72 org := strings.Split(repo, "/")[0] 73 if orgs[org] { 74 t.Errorf("The repo %q is duplicated with %q in the 'approve' plugin configuration.", repo, org) 75 } 76 } 77 } 78 79 func newTestComment(user, body string) github.IssueComment { 80 return github.IssueComment{User: github.User{Login: user}, Body: body} 81 } 82 83 func newTestCommentTime(t time.Time, user, body string) github.IssueComment { 84 c := newTestComment(user, body) 85 c.CreatedAt = t 86 return c 87 } 88 89 func newTestReview(user, body string, state github.ReviewState) github.Review { 90 return github.Review{User: github.User{Login: user}, Body: body, State: state} 91 } 92 93 func newTestReviewTime(t time.Time, user, body string, state github.ReviewState) github.Review { 94 r := newTestReview(user, body, state) 95 r.SubmittedAt = t 96 return r 97 } 98 99 func newFakeGithubClient(hasLabel, humanApproved bool, files []string, comments []github.IssueComment, reviews []github.Review) *fakegithub.FakeClient { 100 labels := []string{"org/repo#1:lgtm"} 101 if hasLabel { 102 labels = append(labels, fmt.Sprintf("org/repo#%v:approved", prNumber)) 103 } 104 events := []github.ListedIssueEvent{ 105 { 106 Event: github.IssueActionLabeled, 107 Label: github.Label{Name: "approved"}, 108 Actor: github.User{Login: "k8s-merge-robot"}, 109 }, 110 } 111 if humanApproved { 112 events = append( 113 events, 114 github.ListedIssueEvent{ 115 Event: github.IssueActionLabeled, 116 Label: github.Label{Name: "approved"}, 117 Actor: github.User{Login: "human"}, 118 CreatedAt: time.Now(), 119 }, 120 ) 121 } 122 var changes []github.PullRequestChange 123 for _, file := range files { 124 changes = append(changes, github.PullRequestChange{Filename: file}) 125 } 126 return &fakegithub.FakeClient{ 127 LabelsAdded: labels, 128 PullRequestChanges: map[int][]github.PullRequestChange{prNumber: changes}, 129 IssueComments: map[int][]github.IssueComment{prNumber: comments}, 130 IssueEvents: map[int][]github.ListedIssueEvent{prNumber: events}, 131 Reviews: map[int][]github.Review{prNumber: reviews}, 132 } 133 } 134 135 type fakeRepo struct { 136 approvers, leafApprovers map[string]sets.String 137 approverOwners map[string]string 138 } 139 140 func (fr fakeRepo) Approvers(path string) sets.String { 141 return fr.approvers[path] 142 } 143 func (fr fakeRepo) LeafApprovers(path string) sets.String { 144 return fr.leafApprovers[path] 145 } 146 func (fr fakeRepo) FindApproverOwnersForFile(path string) string { 147 return fr.approverOwners[path] 148 } 149 func (fr fakeRepo) IsNoParentOwners(path string) bool { 150 return false 151 } 152 153 func TestHandle(t *testing.T) { 154 // This function does not need to test IsApproved, that is tested in approvers/approvers_test.go. 155 156 // includes tests with mixed case usernames 157 // includes tests with stale notifications 158 tests := []struct { 159 name string 160 branch string 161 prBody string 162 hasLabel bool 163 humanApproved bool 164 files []string 165 comments []github.IssueComment 166 reviews []github.Review 167 168 selfApprove bool 169 needsIssue bool 170 lgtmActsAsApprove bool 171 reviewActsAsApprove bool 172 173 expectDelete bool 174 expectComment bool 175 expectedComment string 176 expectToggle bool 177 }{ 178 179 // breaking cases 180 // case: /approve in PR body 181 { 182 name: "initial notification (approved)", 183 hasLabel: false, 184 files: []string{"c/c.go"}, 185 comments: []github.IssueComment{}, 186 reviews: []github.Review{}, 187 selfApprove: true, 188 needsIssue: false, 189 lgtmActsAsApprove: false, 190 reviewActsAsApprove: false, 191 192 expectDelete: false, 193 expectToggle: true, 194 expectComment: true, 195 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 196 197 This pull-request has been approved by: *<a href="#" title="Author self-approved">cjwagner</a>* 198 199 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 200 201 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 202 203 <details > 204 Needs approval from an approver in each of these files: 205 206 - ~~[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)~~ [cjwagner] 207 208 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 209 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 210 </details> 211 <!-- META={"approvers":[]} -->`, 212 }, 213 { 214 name: "initial notification (unapproved)", 215 hasLabel: false, 216 files: []string{"c/c.go"}, 217 comments: []github.IssueComment{}, 218 reviews: []github.Review{}, 219 selfApprove: false, 220 needsIssue: false, 221 lgtmActsAsApprove: false, 222 reviewActsAsApprove: false, 223 224 expectDelete: false, 225 expectToggle: false, 226 expectComment: true, 227 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 228 229 This pull-request has been approved by: 230 To fully approve this pull request, please assign additional approvers. 231 We suggest the following additional approver: **cjwagner** 232 233 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 234 235 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 236 237 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 238 239 <details open> 240 Needs approval from an approver in each of these files: 241 242 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 243 244 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 245 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 246 </details> 247 <!-- META={"approvers":["cjwagner"]} -->`, 248 }, 249 { 250 name: "no-issue comment", 251 hasLabel: false, 252 files: []string{"a/a.go"}, 253 comments: []github.IssueComment{newTestComment("Alice", "stuff\n/approve no-issue \nmore stuff")}, 254 reviews: []github.Review{}, 255 selfApprove: false, 256 needsIssue: true, 257 lgtmActsAsApprove: false, 258 reviewActsAsApprove: false, 259 260 expectDelete: false, 261 expectToggle: true, 262 expectComment: true, 263 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 264 265 This pull-request has been approved by: *<a href="" title="Approved">Alice</a>* 266 267 Associated issue requirement bypassed by: *<a href="" title="Approved">Alice</a>* 268 269 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 270 271 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 272 273 <details > 274 Needs approval from an approver in each of these files: 275 276 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 277 278 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 279 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 280 </details> 281 <!-- META={"approvers":[]} -->`, 282 }, 283 { 284 name: "issue provided in PR body", 285 prBody: "some changes that fix #42.\n/assign", 286 hasLabel: false, 287 files: []string{"a/a.go"}, 288 comments: []github.IssueComment{newTestComment("Alice", "stuff\n/approve")}, 289 reviews: []github.Review{}, 290 selfApprove: false, 291 needsIssue: true, 292 lgtmActsAsApprove: false, 293 reviewActsAsApprove: false, 294 295 expectDelete: false, 296 expectToggle: true, 297 expectComment: true, 298 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 299 300 This pull-request has been approved by: *<a href="" title="Approved">Alice</a>* 301 302 Associated issue: *#42* 303 304 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 305 306 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 307 308 <details > 309 Needs approval from an approver in each of these files: 310 311 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 312 313 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 314 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 315 </details> 316 <!-- META={"approvers":[]} -->`, 317 }, 318 { 319 name: "non-implicit self approve no-issue", 320 hasLabel: false, 321 files: []string{"a/a.go", "c/c.go"}, 322 comments: []github.IssueComment{ 323 newTestComment("ALIcE", "stuff\n/approve"), 324 newTestComment("cjwagner", "stuff\n/approve no-issue"), 325 }, 326 reviews: []github.Review{}, 327 selfApprove: false, 328 needsIssue: true, 329 lgtmActsAsApprove: false, 330 reviewActsAsApprove: false, 331 332 expectDelete: false, 333 expectToggle: true, 334 expectComment: true, 335 expectedComment: "", 336 }, 337 { 338 name: "implicit self approve, missing issue", 339 hasLabel: false, 340 files: []string{"a/a.go", "c/c.go"}, 341 comments: []github.IssueComment{ 342 newTestComment("ALIcE", "stuff\n/approve"), 343 newTestCommentTime(time.Now(), "k8s-ci-robot", `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 344 345 This pull-request has been approved by: *<a href="" title="Approved">ALIcE</a>*, *<a href="#" title="Author self-approved">cjwagner</a>* 346 347 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with `+"`/approve no-issue`"+` 348 349 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 350 351 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 352 353 <details > 354 Needs approval from an approver in each of these files: 355 356 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [ALIcE] 357 - ~~[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)~~ [cjwagner] 358 359 Approvers can indicate their approval by writing `+"`/approve`"+` in a comment 360 Approvers can cancel approval by writing `+"`/approve cancel`"+` in a comment 361 </details> 362 <!-- META={"approvers":[]} -->`), 363 }, 364 reviews: []github.Review{}, 365 selfApprove: true, 366 needsIssue: true, 367 lgtmActsAsApprove: false, 368 reviewActsAsApprove: false, 369 370 expectDelete: false, 371 expectToggle: false, 372 expectComment: false, 373 }, 374 { 375 name: "remove approval with /approve cancel", 376 hasLabel: true, 377 files: []string{"a/a.go"}, 378 comments: []github.IssueComment{ 379 newTestComment("Alice", "/approve no-issue"), 380 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), 381 newTestComment("Alice", "stuff\n/approve cancel \nmore stuff"), 382 }, 383 reviews: []github.Review{}, 384 selfApprove: true, // no-op test 385 needsIssue: true, 386 lgtmActsAsApprove: false, 387 reviewActsAsApprove: false, 388 389 expectDelete: true, 390 expectToggle: true, 391 expectComment: true, 392 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 393 394 This pull-request has been approved by: *<a href="#" title="Author self-approved">cjwagner</a>* 395 To fully approve this pull request, please assign additional approvers. 396 We suggest the following additional approver: **alice** 397 398 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @alice`" + ` in a comment when ready. 399 400 *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + ` 401 402 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 403 404 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 405 406 <details open> 407 Needs approval from an approver in each of these files: 408 409 - **[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)** 410 411 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 412 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 413 </details> 414 <!-- META={"approvers":["alice"]} -->`, 415 }, 416 { 417 name: "remove approval after sync", 418 prBody: "Changes the thing.\n fixes #42", 419 hasLabel: true, 420 files: []string{"a/a.go", "b/b.go"}, 421 comments: []github.IssueComment{ 422 newTestComment("bOb", "stuff\n/approve \nblah"), 423 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), 424 }, 425 reviews: []github.Review{}, 426 selfApprove: true, // no-op test 427 needsIssue: false, 428 lgtmActsAsApprove: false, 429 reviewActsAsApprove: false, 430 431 expectDelete: true, 432 expectToggle: true, 433 expectComment: true, 434 }, 435 { 436 name: "cancel implicit self approve", 437 prBody: "Changes the thing.\n fixes #42", 438 hasLabel: true, 439 files: []string{"c/c.go"}, 440 comments: []github.IssueComment{ 441 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), 442 newTestCommentTime(time.Now(), "CJWagner", "stuff\n/approve cancel \nmore stuff"), 443 }, 444 reviews: []github.Review{}, 445 selfApprove: true, 446 needsIssue: true, 447 lgtmActsAsApprove: false, 448 reviewActsAsApprove: false, 449 450 expectDelete: true, 451 expectToggle: true, 452 expectComment: true, 453 }, 454 { 455 name: "cancel implicit self approve (with lgtm-after-commit message)", 456 prBody: "Changes the thing.\n fixes #42", 457 hasLabel: true, 458 files: []string{"c/c.go"}, 459 comments: []github.IssueComment{ 460 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), 461 newTestCommentTime(time.Now(), "CJWagner", "/lgtm cancel //PR changed after LGTM, removing LGTM."), 462 }, 463 reviews: []github.Review{}, 464 selfApprove: true, 465 needsIssue: true, 466 lgtmActsAsApprove: true, 467 reviewActsAsApprove: false, 468 469 expectDelete: true, 470 expectToggle: true, 471 expectComment: true, 472 }, 473 { 474 name: "up to date, poked by pr sync", 475 prBody: "Finally fixes kubernetes/kubernetes#1\n", 476 hasLabel: true, 477 files: []string{"a/a.go", "a/aa.go"}, 478 comments: []github.IssueComment{ 479 newTestComment("alice", "stuff\n/approve\nblah"), 480 newTestCommentTime(time.Now(), "k8s-ci-robot", `[APPROVALNOTIFIER] This PR is **APPROVED** 481 482 This pull-request has been approved by: *<a href="" title="Approved">alice</a>* 483 484 Associated issue: *#1* 485 486 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 487 488 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 489 490 <details > 491 Needs approval from an approver in each of these files: 492 493 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [alice] 494 495 Approvers can indicate their approval by writing `+"`/approve`"+` in a comment 496 Approvers can cancel approval by writing `+"`/approve cancel`"+` in a comment 497 </details> 498 <!-- META={"approvers":[]} -->`), 499 }, 500 reviews: []github.Review{}, 501 selfApprove: false, 502 needsIssue: true, 503 lgtmActsAsApprove: false, 504 reviewActsAsApprove: false, 505 506 expectDelete: false, 507 expectToggle: false, 508 expectComment: false, 509 }, 510 { 511 name: "out of date, poked by pr sync", 512 prBody: "Finally fixes kubernetes/kubernetes#1\n", 513 hasLabel: false, 514 files: []string{"a/a.go", "a/aa.go"}, // previous commits may have been ["b/b.go"] 515 comments: []github.IssueComment{ 516 newTestComment("alice", "stuff\n/approve\nblah"), 517 newTestCommentTime(time.Now(), "k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **NOT APPROVED**\n\nblah"), 518 }, 519 reviews: []github.Review{}, 520 selfApprove: false, 521 needsIssue: true, 522 lgtmActsAsApprove: false, 523 reviewActsAsApprove: false, 524 525 expectDelete: true, 526 expectToggle: true, 527 expectComment: true, 528 }, 529 { 530 name: "human added approve", 531 hasLabel: true, 532 humanApproved: true, 533 files: []string{"a/a.go"}, 534 comments: []github.IssueComment{ 535 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **NOT APPROVED**\n\nblah"), 536 }, 537 reviews: []github.Review{}, 538 selfApprove: false, 539 needsIssue: false, 540 lgtmActsAsApprove: false, 541 reviewActsAsApprove: false, 542 543 expectDelete: true, 544 expectToggle: false, 545 expectComment: true, 546 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 547 548 Approval requirements bypassed by manually added approval. 549 550 This pull-request has been approved by: 551 552 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 553 554 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 555 556 <details > 557 Needs approval from an approver in each of these files: 558 559 - **[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)** 560 561 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 562 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 563 </details> 564 <!-- META={"approvers":["alice"]} -->`, 565 }, 566 { 567 name: "lgtm means approve", 568 prBody: "This is a great PR that will fix\nlots of things!", 569 hasLabel: false, 570 files: []string{"a/a.go", "a/aa.go"}, 571 comments: []github.IssueComment{ 572 newTestComment("k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **NOT APPROVED**\n\nblah"), 573 newTestCommentTime(time.Now(), "alice", "stuff\n/lgtm\nblah"), 574 }, 575 reviews: []github.Review{}, 576 selfApprove: false, 577 needsIssue: false, 578 lgtmActsAsApprove: true, 579 reviewActsAsApprove: false, 580 581 expectDelete: true, 582 expectToggle: true, 583 expectComment: true, 584 }, 585 { 586 name: "lgtm does not mean approve", 587 prBody: "This is a great PR that will fix\nlots of things!", 588 hasLabel: false, 589 files: []string{"a/a.go", "a/aa.go"}, 590 comments: []github.IssueComment{ 591 newTestComment("k8s-ci-robot", `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 592 593 This pull-request has been approved by: 594 To fully approve this pull request, please assign additional approvers. 595 We suggest the following additional approver: **alice** 596 597 If they are not already assigned, you can assign the PR to them by writing `+"`/assign @alice`"+` in a comment when ready. 598 599 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 600 601 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 602 603 <details open> 604 Needs approval from an approver in each of these files: 605 606 - **[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)** 607 608 Approvers can indicate their approval by writing `+"`/approve`"+` in a comment 609 Approvers can cancel approval by writing `+"`/approve cancel`"+` in a comment 610 </details> 611 <!-- META={"approvers":["alice"]} -->`), 612 newTestCommentTime(time.Now(), "alice", "stuff\n/lgtm\nblah"), 613 }, 614 reviews: []github.Review{}, 615 selfApprove: false, 616 needsIssue: false, 617 lgtmActsAsApprove: false, 618 reviewActsAsApprove: false, 619 620 expectDelete: false, 621 expectToggle: false, 622 expectComment: false, 623 }, 624 { 625 name: "approve in review body with empty state", 626 hasLabel: false, 627 files: []string{"a/a.go"}, 628 comments: []github.IssueComment{}, 629 reviews: []github.Review{newTestReview("Alice", "stuff\n/approve", "")}, 630 selfApprove: false, 631 needsIssue: false, 632 lgtmActsAsApprove: false, 633 reviewActsAsApprove: false, 634 635 expectDelete: false, 636 expectToggle: true, 637 expectComment: true, 638 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 639 640 This pull-request has been approved by: *<a href="" title="Approved">Alice</a>* 641 642 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 643 644 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 645 646 <details > 647 Needs approval from an approver in each of these files: 648 649 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 650 651 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 652 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 653 </details> 654 <!-- META={"approvers":[]} -->`, 655 }, 656 { 657 name: "approved review but reviewActsAsApprove disabled", 658 hasLabel: false, 659 files: []string{"c/c.go"}, 660 comments: []github.IssueComment{}, 661 reviews: []github.Review{newTestReview("cjwagner", "stuff", github.ReviewStateApproved)}, 662 selfApprove: false, 663 needsIssue: false, 664 lgtmActsAsApprove: false, 665 reviewActsAsApprove: false, 666 667 expectDelete: false, 668 expectToggle: false, 669 expectComment: true, 670 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 671 672 This pull-request has been approved by: 673 To fully approve this pull request, please assign additional approvers. 674 We suggest the following additional approver: **cjwagner** 675 676 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 677 678 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 679 680 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 681 682 <details open> 683 Needs approval from an approver in each of these files: 684 685 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 686 687 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 688 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 689 </details> 690 <!-- META={"approvers":["cjwagner"]} -->`, 691 }, 692 { 693 name: "approved review with reviewActsAsApprove enabled", 694 hasLabel: false, 695 files: []string{"a/a.go"}, 696 comments: []github.IssueComment{}, 697 reviews: []github.Review{newTestReview("Alice", "stuff", github.ReviewStateApproved)}, 698 selfApprove: false, 699 needsIssue: false, 700 lgtmActsAsApprove: false, 701 reviewActsAsApprove: true, 702 703 expectDelete: false, 704 expectToggle: true, 705 expectComment: true, 706 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 707 708 This pull-request has been approved by: *<a href="" title="Approved">Alice</a>* 709 710 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 711 712 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 713 714 <details > 715 Needs approval from an approver in each of these files: 716 717 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 718 719 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 720 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 721 </details> 722 <!-- META={"approvers":[]} -->`, 723 }, 724 { 725 name: "reviews in non-approving state (should not approve)", 726 hasLabel: false, 727 files: []string{"c/c.go"}, 728 comments: []github.IssueComment{}, 729 reviews: []github.Review{ 730 newTestReview("cjwagner", "stuff", "COMMENTED"), 731 newTestReview("cjwagner", "unsubmitted stuff", "PENDING"), 732 }, 733 selfApprove: false, 734 needsIssue: false, 735 lgtmActsAsApprove: false, 736 reviewActsAsApprove: true, 737 738 expectDelete: false, 739 expectToggle: false, 740 expectComment: true, 741 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 742 743 This pull-request has been approved by: 744 To fully approve this pull request, please assign additional approvers. 745 We suggest the following additional approver: **cjwagner** 746 747 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 748 749 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 750 751 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 752 753 <details open> 754 Needs approval from an approver in each of these files: 755 756 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 757 758 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 759 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 760 </details> 761 <!-- META={"approvers":["cjwagner"]} -->`, 762 }, 763 { 764 name: "review in request changes state means cancel", 765 hasLabel: true, 766 files: []string{"c/c.go"}, 767 comments: []github.IssueComment{ 768 newTestCommentTime(time.Now().Add(time.Hour), "k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), // second 769 }, 770 reviews: []github.Review{ 771 newTestReviewTime(time.Now(), "cjwagner", "yep", github.ReviewStateApproved), // first 772 newTestReviewTime(time.Now().Add(time.Hour*2), "cjwagner", "nope", github.ReviewStateChangesRequested), // third 773 }, 774 selfApprove: false, 775 needsIssue: false, 776 lgtmActsAsApprove: false, 777 reviewActsAsApprove: true, 778 779 expectDelete: true, 780 expectToggle: true, 781 expectComment: true, 782 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 783 784 This pull-request has been approved by: 785 To fully approve this pull request, please assign additional approvers. 786 We suggest the following additional approver: **cjwagner** 787 788 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 789 790 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 791 792 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 793 794 <details open> 795 Needs approval from an approver in each of these files: 796 797 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 798 799 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 800 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 801 </details> 802 <!-- META={"approvers":["cjwagner"]} -->`, 803 }, 804 { 805 name: "approve cancel command supersedes earlier approved review", 806 hasLabel: true, 807 files: []string{"c/c.go"}, 808 comments: []github.IssueComment{ 809 newTestCommentTime(time.Now().Add(time.Hour), "k8s-ci-robot", "[APPROVALNOTIFIER] This PR is **APPROVED**\n\nblah"), // second 810 newTestCommentTime(time.Now().Add(time.Hour*2), "cjwagner", "stuff\n/approve cancel \nmore stuff"), // third 811 }, 812 reviews: []github.Review{ 813 newTestReviewTime(time.Now(), "cjwagner", "yep", github.ReviewStateApproved), // first 814 }, 815 selfApprove: false, 816 needsIssue: false, 817 lgtmActsAsApprove: false, 818 reviewActsAsApprove: true, 819 820 expectDelete: true, 821 expectToggle: true, 822 expectComment: true, 823 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 824 825 This pull-request has been approved by: 826 To fully approve this pull request, please assign additional approvers. 827 We suggest the following additional approver: **cjwagner** 828 829 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 830 831 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 832 833 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 834 835 <details open> 836 Needs approval from an approver in each of these files: 837 838 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 839 840 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 841 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 842 </details> 843 <!-- META={"approvers":["cjwagner"]} -->`, 844 }, 845 { 846 name: "approve cancel command supersedes simultaneous approved review", 847 hasLabel: false, 848 files: []string{"c/c.go"}, 849 comments: []github.IssueComment{}, 850 reviews: []github.Review{ 851 newTestReview("cjwagner", "/approve cancel", github.ReviewStateApproved), 852 }, 853 selfApprove: false, 854 needsIssue: false, 855 lgtmActsAsApprove: false, 856 reviewActsAsApprove: true, 857 858 expectDelete: false, 859 expectToggle: false, 860 expectComment: true, 861 expectedComment: `[APPROVALNOTIFIER] This PR is **NOT APPROVED** 862 863 This pull-request has been approved by: 864 To fully approve this pull request, please assign additional approvers. 865 We suggest the following additional approver: **cjwagner** 866 867 If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @cjwagner`" + ` in a comment when ready. 868 869 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 870 871 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 872 873 <details open> 874 Needs approval from an approver in each of these files: 875 876 - **[c/OWNERS](https://github.com/org/repo/blob/master/c/OWNERS)** 877 878 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 879 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 880 </details> 881 <!-- META={"approvers":["cjwagner"]} -->`, 882 }, 883 { 884 name: "approve command supersedes simultaneous changes requested review", 885 hasLabel: false, 886 files: []string{"a/a.go"}, 887 comments: []github.IssueComment{}, 888 reviews: []github.Review{newTestReview("Alice", "/approve", github.ReviewStateChangesRequested)}, 889 selfApprove: false, 890 needsIssue: false, 891 lgtmActsAsApprove: false, 892 reviewActsAsApprove: true, 893 894 expectDelete: false, 895 expectToggle: true, 896 expectComment: true, 897 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 898 899 This pull-request has been approved by: *<a href="" title="Approved">Alice</a>* 900 901 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 902 903 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 904 905 <details > 906 Needs approval from an approver in each of these files: 907 908 - ~~[a/OWNERS](https://github.com/org/repo/blob/master/a/OWNERS)~~ [Alice] 909 910 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 911 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 912 </details> 913 <!-- META={"approvers":[]} -->`, 914 }, 915 { 916 name: "different branch, initial notification (approved)", 917 branch: "dev", 918 hasLabel: false, 919 files: []string{"c/c.go"}, 920 comments: []github.IssueComment{}, 921 reviews: []github.Review{}, 922 selfApprove: true, 923 needsIssue: false, 924 lgtmActsAsApprove: false, 925 reviewActsAsApprove: false, 926 927 expectDelete: false, 928 expectToggle: true, 929 expectComment: true, 930 expectedComment: `[APPROVALNOTIFIER] This PR is **APPROVED** 931 932 This pull-request has been approved by: *<a href="#" title="Author self-approved">cjwagner</a>* 933 934 The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands). 935 936 The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process) 937 938 <details > 939 Needs approval from an approver in each of these files: 940 941 - ~~[c/OWNERS](https://github.com/org/repo/blob/dev/c/OWNERS)~~ [cjwagner] 942 943 Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment 944 Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment 945 </details> 946 <!-- META={"approvers":[]} -->`, 947 }, 948 } 949 950 fr := fakeRepo{ 951 approvers: map[string]sets.String{ 952 "a": sets.NewString("alice"), 953 "a/b": sets.NewString("alice", "bob"), 954 "c": sets.NewString("cblecker", "cjwagner"), 955 }, 956 leafApprovers: map[string]sets.String{ 957 "a": sets.NewString("alice"), 958 "a/b": sets.NewString("bob"), 959 "c": sets.NewString("cblecker", "cjwagner"), 960 }, 961 approverOwners: map[string]string{ 962 "a/a.go": "a", 963 "a/aa.go": "a", 964 "a/b/b.go": "a/b", 965 "c/c.go": "c", 966 }, 967 } 968 969 for _, test := range tests { 970 fghc := newFakeGithubClient(test.hasLabel, test.humanApproved, test.files, test.comments, test.reviews) 971 branch := "master" 972 if test.branch != "" { 973 branch = test.branch 974 } 975 976 if err := handle( 977 logrus.WithField("plugin", "approve"), 978 fghc, 979 fr, 980 &plugins.Approve{ 981 Repos: []string{"org/repo"}, 982 ImplicitSelfApprove: test.selfApprove, 983 IssueRequired: test.needsIssue, 984 LgtmActsAsApprove: test.lgtmActsAsApprove, 985 ReviewActsAsApprove: test.reviewActsAsApprove, 986 }, 987 &state{ 988 org: "org", 989 repo: "repo", 990 branch: branch, 991 number: prNumber, 992 body: test.prBody, 993 author: "cjwagner", 994 assignees: []github.User{{Login: "spxtr"}}, 995 }, 996 ); err != nil { 997 t.Errorf("[%s] Unexpected error handling event: %v.", test.name, err) 998 } 999 1000 if test.expectDelete { 1001 if len(fghc.IssueCommentsDeleted) != 1 { 1002 t.Errorf( 1003 "[%s] Expected 1 notification to be deleted but %d notifications were deleted.", 1004 test.name, 1005 len(fghc.IssueCommentsDeleted), 1006 ) 1007 } 1008 } else { 1009 if len(fghc.IssueCommentsDeleted) != 0 { 1010 t.Errorf( 1011 "[%s] Expected 0 notifications to be deleted but %d notification was deleted.", 1012 test.name, 1013 len(fghc.IssueCommentsDeleted), 1014 ) 1015 } 1016 } 1017 if test.expectComment { 1018 if len(fghc.IssueCommentsAdded) != 1 { 1019 t.Errorf( 1020 "[%s] Expected 1 notification to be added but %d notifications were added.", 1021 test.name, 1022 len(fghc.IssueCommentsAdded), 1023 ) 1024 } else if expect, got := fmt.Sprintf("org/repo#%v:", prNumber)+test.expectedComment, fghc.IssueCommentsAdded[0]; test.expectedComment != "" && got != expect { 1025 t.Errorf( 1026 "[%s] Expected the created notification to be:\n%s\n\nbut got:\n%s\n\n", 1027 test.name, 1028 expect, 1029 got, 1030 ) 1031 } 1032 } else { 1033 if len(fghc.IssueCommentsAdded) != 0 { 1034 t.Errorf( 1035 "[%s] Expected 0 notifications to be added but %d notification was added.", 1036 test.name, 1037 len(fghc.IssueCommentsAdded), 1038 ) 1039 } 1040 } 1041 1042 labelAdded := false 1043 for _, l := range fghc.LabelsAdded { 1044 if l == fmt.Sprintf("org/repo#%v:approved", prNumber) { 1045 if labelAdded { 1046 t.Errorf("[%s] The approved label was applied to a PR that already had it!", test.name) 1047 } 1048 labelAdded = true 1049 } 1050 } 1051 if test.hasLabel { 1052 labelAdded = false 1053 } 1054 toggled := labelAdded 1055 for _, l := range fghc.LabelsRemoved { 1056 if l == fmt.Sprintf("org/repo#%v:approved", prNumber) { 1057 if !test.hasLabel { 1058 t.Errorf("[%s] The approved label was removed from a PR that doesn't have it!", test.name) 1059 } 1060 toggled = true 1061 } 1062 } 1063 if test.expectToggle != toggled { 1064 t.Errorf( 1065 "[%s] Expected 'approved' label toggled: %t, but got %t.", 1066 test.name, 1067 test.expectToggle, 1068 toggled, 1069 ) 1070 } 1071 } 1072 } 1073 1074 // TODO: cache approvers 'GetFilesApprovers' and 'GetCCs' since these are called repeatedly and are 1075 // expensive. 1076 1077 type fakeOwnersClient struct{} 1078 1079 func (foc fakeOwnersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwnerInterface, error) { 1080 return fakeRepoOwners{}, nil 1081 } 1082 1083 type fakeRepoOwners struct { 1084 fakeRepo 1085 } 1086 1087 func (fro fakeRepoOwners) FindLabelsForFile(path string) sets.String { 1088 return sets.NewString() 1089 } 1090 1091 func (fro fakeRepoOwners) FindReviewersOwnersForFile(path string) string { 1092 return "" 1093 } 1094 1095 func (fro fakeRepoOwners) LeafReviewers(path string) sets.String { 1096 return sets.NewString() 1097 } 1098 1099 func (fro fakeRepoOwners) Reviewers(path string) sets.String { 1100 return sets.NewString() 1101 } 1102 1103 func (fro fakeRepoOwners) RequiredReviewers(path string) sets.String { 1104 return sets.NewString() 1105 } 1106 1107 // func (fro fakeRepoOwners) FindReviewersOwners 1108 1109 func getTestHandleFunc() func(log *logrus.Entry, ghc githubClient, repo approvers.RepoInterface, opts *plugins.Approve, pr *state) error { 1110 return func(log *logrus.Entry, ghc githubClient, repo approvers.RepoInterface, opts *plugins.Approve, pr *state) error { 1111 return nil 1112 } 1113 } 1114 1115 func TestHandleGenericComment(t *testing.T) { 1116 tests := []struct { 1117 name string 1118 commentEvent github.GenericCommentEvent 1119 lgtmActsAsApprove bool 1120 expectHandle bool 1121 }{ 1122 { 1123 name: "valid approve command", 1124 commentEvent: github.GenericCommentEvent{ 1125 Action: github.GenericCommentActionCreated, 1126 IsPR: true, 1127 Body: "/approve", 1128 Number: 1, 1129 User: github.User{ 1130 Login: "author", 1131 }, 1132 }, 1133 expectHandle: true, 1134 }, 1135 { 1136 name: "not comment created", 1137 commentEvent: github.GenericCommentEvent{ 1138 Action: github.GenericCommentActionEdited, 1139 IsPR: true, 1140 Body: "/approve", 1141 Number: 1, 1142 User: github.User{ 1143 Login: "author", 1144 }, 1145 }, 1146 expectHandle: false, 1147 }, 1148 { 1149 name: "not PR", 1150 commentEvent: github.GenericCommentEvent{ 1151 Action: github.GenericCommentActionEdited, 1152 IsPR: false, 1153 Body: "/approve", 1154 Number: 1, 1155 User: github.User{ 1156 Login: "author", 1157 }, 1158 }, 1159 expectHandle: false, 1160 }, 1161 { 1162 name: "closed PR", 1163 commentEvent: github.GenericCommentEvent{ 1164 Action: github.GenericCommentActionCreated, 1165 IsPR: true, 1166 Body: "/approve", 1167 Number: 1, 1168 User: github.User{ 1169 Login: "author", 1170 }, 1171 IssueState: "closed", 1172 }, 1173 expectHandle: false, 1174 }, 1175 { 1176 name: "no approve command", 1177 commentEvent: github.GenericCommentEvent{ 1178 Action: github.GenericCommentActionCreated, 1179 IsPR: true, 1180 Body: "stuff", 1181 Number: 1, 1182 User: github.User{ 1183 Login: "author", 1184 }, 1185 }, 1186 expectHandle: false, 1187 }, 1188 { 1189 name: "lgtm without lgtmActsAsApprove", 1190 commentEvent: github.GenericCommentEvent{ 1191 Action: github.GenericCommentActionCreated, 1192 IsPR: true, 1193 Body: "/lgtm", 1194 Number: 1, 1195 User: github.User{ 1196 Login: "author", 1197 }, 1198 }, 1199 expectHandle: false, 1200 }, 1201 { 1202 name: "lgtm with lgtmActsAsApprove", 1203 commentEvent: github.GenericCommentEvent{ 1204 Action: github.GenericCommentActionCreated, 1205 IsPR: true, 1206 Body: "/lgtm", 1207 Number: 1, 1208 User: github.User{ 1209 Login: "author", 1210 }, 1211 }, 1212 lgtmActsAsApprove: true, 1213 expectHandle: true, 1214 }, 1215 } 1216 1217 var handled bool 1218 handleFunc = func(log *logrus.Entry, ghc githubClient, repo approvers.RepoInterface, opts *plugins.Approve, pr *state) error { 1219 handled = true 1220 return nil 1221 } 1222 defer func() { 1223 handleFunc = handle 1224 }() 1225 1226 repo := github.Repo{ 1227 Owner: github.User{ 1228 Login: "org", 1229 }, 1230 Name: "repo", 1231 } 1232 pr := github.PullRequest{ 1233 Base: github.PullRequestBranch{ 1234 Ref: "branch", 1235 }, 1236 Number: 1, 1237 } 1238 fghc := &fakegithub.FakeClient{ 1239 PullRequests: map[int]*github.PullRequest{1: &pr}, 1240 } 1241 1242 for _, test := range tests { 1243 test.commentEvent.Repo = repo 1244 config := &plugins.Configuration{} 1245 config.Approve = append(config.Approve, plugins.Approve{ 1246 Repos: []string{test.commentEvent.Repo.Owner.Login}, 1247 LgtmActsAsApprove: test.lgtmActsAsApprove, 1248 }) 1249 err := handleGenericComment( 1250 logrus.WithField("plugin", "approve"), 1251 fghc, 1252 fakeOwnersClient{}, 1253 config, 1254 &test.commentEvent, 1255 ) 1256 1257 if test.expectHandle && !handled { 1258 t.Errorf("%s: expected call to handleFunc, but it wasn't called", test.name) 1259 } 1260 1261 if !test.expectHandle && handled { 1262 t.Errorf("%s: expected no call to handleFunc, but it was called", test.name) 1263 } 1264 1265 if err != nil { 1266 t.Errorf("%s: error calling handleGenericComment: %v", test.name, err) 1267 } 1268 handled = false 1269 } 1270 } 1271 1272 func TestHandleReviewEvent(t *testing.T) { 1273 tests := []struct { 1274 name string 1275 reviewEvent github.ReviewEvent 1276 lgtmActsAsApprove bool 1277 reviewActsAsApprove bool 1278 expectHandle bool 1279 }{ 1280 { 1281 name: "approved state", 1282 reviewEvent: github.ReviewEvent{ 1283 Action: github.ReviewActionSubmitted, 1284 Review: github.Review{ 1285 Body: "looks good", 1286 User: github.User{ 1287 Login: "author", 1288 }, 1289 State: github.ReviewStateApproved, 1290 }, 1291 }, 1292 reviewActsAsApprove: true, 1293 expectHandle: true, 1294 }, 1295 { 1296 name: "changes requested state", 1297 reviewEvent: github.ReviewEvent{ 1298 Action: github.ReviewActionSubmitted, 1299 Review: github.Review{ 1300 Body: "looks bad", 1301 User: github.User{ 1302 Login: "author", 1303 }, 1304 State: github.ReviewStateChangesRequested, 1305 }, 1306 }, 1307 reviewActsAsApprove: true, 1308 expectHandle: true, 1309 }, 1310 { 1311 name: "pending state", 1312 reviewEvent: github.ReviewEvent{ 1313 Action: github.ReviewActionSubmitted, 1314 Review: github.Review{ 1315 Body: "looks good", 1316 User: github.User{ 1317 Login: "author", 1318 }, 1319 State: github.ReviewStatePending, 1320 }, 1321 }, 1322 reviewActsAsApprove: true, 1323 expectHandle: false, 1324 }, 1325 { 1326 name: "edited review", 1327 reviewEvent: github.ReviewEvent{ 1328 Action: github.ReviewActionEdited, 1329 Review: github.Review{ 1330 Body: "looks good", 1331 User: github.User{ 1332 Login: "author", 1333 }, 1334 State: github.ReviewStateApproved, 1335 }, 1336 }, 1337 reviewActsAsApprove: true, 1338 expectHandle: false, 1339 }, 1340 { 1341 name: "dismissed review", 1342 reviewEvent: github.ReviewEvent{ 1343 Action: github.ReviewActionDismissed, 1344 Review: github.Review{ 1345 Body: "looks good", 1346 User: github.User{ 1347 Login: "author", 1348 }, 1349 State: github.ReviewStateDismissed, 1350 }, 1351 }, 1352 reviewActsAsApprove: true, 1353 expectHandle: false, 1354 }, 1355 { 1356 name: "approve command", 1357 reviewEvent: github.ReviewEvent{ 1358 Action: github.ReviewActionSubmitted, 1359 Review: github.Review{ 1360 Body: "/approve", 1361 User: github.User{ 1362 Login: "author", 1363 }, 1364 State: github.ReviewStateApproved, 1365 }, 1366 }, 1367 reviewActsAsApprove: true, 1368 expectHandle: false, 1369 }, 1370 { 1371 name: "lgtm command", 1372 reviewEvent: github.ReviewEvent{ 1373 Action: github.ReviewActionSubmitted, 1374 Review: github.Review{ 1375 Body: "/lgtm", 1376 User: github.User{ 1377 Login: "author", 1378 }, 1379 State: github.ReviewStateApproved, 1380 }, 1381 }, 1382 lgtmActsAsApprove: true, 1383 reviewActsAsApprove: true, 1384 expectHandle: false, 1385 }, 1386 { 1387 name: "feature disabled", 1388 reviewEvent: github.ReviewEvent{ 1389 Action: github.ReviewActionSubmitted, 1390 Review: github.Review{ 1391 Body: "looks good", 1392 User: github.User{ 1393 Login: "author", 1394 }, 1395 State: github.ReviewStateApproved, 1396 }, 1397 }, 1398 reviewActsAsApprove: false, 1399 expectHandle: false, 1400 }, 1401 } 1402 1403 var handled bool 1404 handleFunc = func(log *logrus.Entry, ghc githubClient, repo approvers.RepoInterface, opts *plugins.Approve, pr *state) error { 1405 handled = true 1406 return nil 1407 } 1408 defer func() { 1409 handleFunc = handle 1410 }() 1411 1412 repo := github.Repo{ 1413 Owner: github.User{ 1414 Login: "org", 1415 }, 1416 Name: "repo", 1417 } 1418 pr := github.PullRequest{ 1419 Base: github.PullRequestBranch{ 1420 Ref: "branch", 1421 }, 1422 Number: 1, 1423 } 1424 fghc := &fakegithub.FakeClient{ 1425 PullRequests: map[int]*github.PullRequest{1: &pr}, 1426 } 1427 1428 for _, test := range tests { 1429 test.reviewEvent.Repo = repo 1430 test.reviewEvent.PullRequest = pr 1431 config := &plugins.Configuration{} 1432 config.Approve = append(config.Approve, plugins.Approve{ 1433 Repos: []string{test.reviewEvent.Repo.Owner.Login}, 1434 LgtmActsAsApprove: test.lgtmActsAsApprove, 1435 ReviewActsAsApprove: test.reviewActsAsApprove, 1436 }) 1437 err := handleReview( 1438 logrus.WithField("plugin", "approve"), 1439 fghc, 1440 fakeOwnersClient{}, 1441 config, 1442 &test.reviewEvent, 1443 ) 1444 1445 if test.expectHandle && !handled { 1446 t.Errorf("%s: expected call to handleFunc, but it wasn't called", test.name) 1447 } 1448 1449 if !test.expectHandle && handled { 1450 t.Errorf("%s: expected no call to handleFunc, but it was called", test.name) 1451 } 1452 1453 if err != nil { 1454 t.Errorf("%s: error calling handleGenericComment: %v", test.name, err) 1455 } 1456 handled = false 1457 } 1458 } 1459 1460 func TestHandlePullRequestEvent(t *testing.T) { 1461 tests := []struct { 1462 name string 1463 prEvent github.PullRequestEvent 1464 expectHandle bool 1465 }{ 1466 { 1467 name: "pr opened", 1468 prEvent: github.PullRequestEvent{ 1469 Action: github.PullRequestActionOpened, 1470 }, 1471 expectHandle: true, 1472 }, 1473 { 1474 name: "pr reopened", 1475 prEvent: github.PullRequestEvent{ 1476 Action: github.PullRequestActionReopened, 1477 }, 1478 expectHandle: true, 1479 }, 1480 { 1481 name: "pr sync", 1482 prEvent: github.PullRequestEvent{ 1483 Action: github.PullRequestActionSynchronize, 1484 }, 1485 expectHandle: true, 1486 }, 1487 { 1488 name: "pr labeled", 1489 prEvent: github.PullRequestEvent{ 1490 Action: github.PullRequestActionLabeled, 1491 Label: github.Label{ 1492 Name: approvedLabel, 1493 }, 1494 }, 1495 expectHandle: true, 1496 }, 1497 { 1498 name: "pr another label", 1499 prEvent: github.PullRequestEvent{ 1500 Action: github.PullRequestActionLabeled, 1501 Label: github.Label{ 1502 Name: "some-label", 1503 }, 1504 }, 1505 expectHandle: false, 1506 }, 1507 { 1508 name: "pr closed", 1509 prEvent: github.PullRequestEvent{ 1510 Action: github.PullRequestActionLabeled, 1511 Label: github.Label{ 1512 Name: approvedLabel, 1513 }, 1514 PullRequest: github.PullRequest{ 1515 State: "closed", 1516 }, 1517 }, 1518 expectHandle: false, 1519 }, 1520 { 1521 name: "pr review requested", 1522 prEvent: github.PullRequestEvent{ 1523 Action: github.PullRequestActionReviewRequested, 1524 }, 1525 expectHandle: false, 1526 }, 1527 } 1528 1529 var handled bool 1530 handleFunc = func(log *logrus.Entry, ghc githubClient, repo approvers.RepoInterface, opts *plugins.Approve, pr *state) error { 1531 handled = true 1532 return nil 1533 } 1534 defer func() { 1535 handleFunc = handle 1536 }() 1537 1538 repo := github.Repo{ 1539 Owner: github.User{ 1540 Login: "org", 1541 }, 1542 Name: "repo", 1543 } 1544 fghc := &fakegithub.FakeClient{} 1545 1546 for _, test := range tests { 1547 test.prEvent.Repo = repo 1548 err := handlePullRequest( 1549 logrus.WithField("plugin", "approve"), 1550 fghc, 1551 fakeOwnersClient{}, 1552 &plugins.Configuration{}, 1553 &test.prEvent, 1554 ) 1555 1556 if test.expectHandle && !handled { 1557 t.Errorf("%s: expected call to handleFunc, but it wasn't called", test.name) 1558 } 1559 1560 if !test.expectHandle && handled { 1561 t.Errorf("%s: expected no call to handleFunc, but it was called", test.name) 1562 } 1563 1564 if err != nil { 1565 t.Errorf("%s: error calling handleGenericComment: %v", test.name, err) 1566 } 1567 handled = false 1568 } 1569 }