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