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  }