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