github.com/abayer/test-infra@v0.0.5/prow/plugins/approve/approve_test.go (about)

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