github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/lgtm/lgtm_test.go (about)

     1  /*
     2  Copyright 2016 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 lgtm
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/sirupsen/logrus"
    25  	"k8s.io/apimachinery/pkg/api/equality"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  
    28  	"k8s.io/test-infra/prow/github"
    29  	"k8s.io/test-infra/prow/github/fakegithub"
    30  	"k8s.io/test-infra/prow/plugins"
    31  	"k8s.io/test-infra/prow/repoowners"
    32  )
    33  
    34  type fakeOwnersClient struct {
    35  	approvers map[string]sets.String
    36  	reviewers map[string]sets.String
    37  }
    38  
    39  var _ repoowners.Interface = &fakeOwnersClient{}
    40  
    41  func (f *fakeOwnersClient) LoadRepoAliases(org, repo, base string) (repoowners.RepoAliases, error) {
    42  	return nil, nil
    43  }
    44  
    45  func (f *fakeOwnersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwner, error) {
    46  	return &fakeRepoOwners{approvers: f.approvers, reviewers: f.reviewers}, nil
    47  }
    48  
    49  type fakeRepoOwners struct {
    50  	approvers map[string]sets.String
    51  	reviewers map[string]sets.String
    52  }
    53  
    54  type fakePruner struct {
    55  	GithubClient  *fakegithub.FakeClient
    56  	IssueComments []github.IssueComment
    57  }
    58  
    59  func (fp *fakePruner) PruneComments(shouldPrune func(github.IssueComment) bool) {
    60  	for _, comment := range fp.IssueComments {
    61  		if shouldPrune(comment) {
    62  			fp.GithubClient.IssueCommentsDeleted = append(fp.GithubClient.IssueCommentsDeleted, comment.Body)
    63  		}
    64  	}
    65  }
    66  
    67  var _ repoowners.RepoOwner = &fakeRepoOwners{}
    68  
    69  func (f *fakeRepoOwners) FindApproverOwnersForFile(path string) string  { return "" }
    70  func (f *fakeRepoOwners) FindReviewersOwnersForFile(path string) string { return "" }
    71  func (f *fakeRepoOwners) FindLabelsForFile(path string) sets.String     { return nil }
    72  func (f *fakeRepoOwners) IsNoParentOwners(path string) bool             { return false }
    73  func (f *fakeRepoOwners) LeafApprovers(path string) sets.String         { return nil }
    74  func (f *fakeRepoOwners) Approvers(path string) sets.String             { return f.approvers[path] }
    75  func (f *fakeRepoOwners) LeafReviewers(path string) sets.String         { return nil }
    76  func (f *fakeRepoOwners) Reviewers(path string) sets.String             { return f.reviewers[path] }
    77  func (f *fakeRepoOwners) RequiredReviewers(path string) sets.String     { return nil }
    78  
    79  var approvers = map[string]sets.String{
    80  	"doc/README.md": {
    81  		"cjwagner": {},
    82  		"jessica":  {},
    83  	},
    84  }
    85  
    86  var reviewers = map[string]sets.String{
    87  	"doc/README.md": {
    88  		"alice": {},
    89  		"bob":   {},
    90  		"mark":  {},
    91  		"sam":   {},
    92  	},
    93  }
    94  
    95  func TestLGTMComment(t *testing.T) {
    96  	var testcases = []struct {
    97  		name          string
    98  		body          string
    99  		commenter     string
   100  		hasLGTM       bool
   101  		shouldToggle  bool
   102  		shouldComment bool
   103  		shouldAssign  bool
   104  		skipCollab    bool
   105  		storeTreeHash bool
   106  	}{
   107  		{
   108  			name:         "non-lgtm comment",
   109  			body:         "uh oh",
   110  			commenter:    "o",
   111  			hasLGTM:      false,
   112  			shouldToggle: false,
   113  		},
   114  		{
   115  			name:          "lgtm comment by reviewer, no lgtm on pr",
   116  			body:          "/lgtm",
   117  			commenter:     "reviewer1",
   118  			hasLGTM:       false,
   119  			shouldToggle:  true,
   120  			shouldComment: true,
   121  		},
   122  		{
   123  			name:          "LGTM comment by reviewer, no lgtm on pr",
   124  			body:          "/LGTM",
   125  			commenter:     "reviewer1",
   126  			hasLGTM:       false,
   127  			shouldToggle:  true,
   128  			shouldComment: true,
   129  		},
   130  		{
   131  			name:         "lgtm comment by reviewer, lgtm on pr",
   132  			body:         "/lgtm",
   133  			commenter:    "reviewer1",
   134  			hasLGTM:      true,
   135  			shouldToggle: false,
   136  		},
   137  		{
   138  			name:          "lgtm comment by author",
   139  			body:          "/lgtm",
   140  			commenter:     "author",
   141  			hasLGTM:       false,
   142  			shouldToggle:  false,
   143  			shouldComment: true,
   144  		},
   145  		{
   146  			name:          "lgtm cancel by author",
   147  			body:          "/lgtm cancel",
   148  			commenter:     "author",
   149  			hasLGTM:       true,
   150  			shouldToggle:  true,
   151  			shouldAssign:  false,
   152  			shouldComment: false,
   153  		},
   154  		{
   155  			name:          "lgtm comment by non-reviewer",
   156  			body:          "/lgtm",
   157  			commenter:     "o",
   158  			hasLGTM:       false,
   159  			shouldToggle:  true,
   160  			shouldComment: true,
   161  			shouldAssign:  true,
   162  		},
   163  		{
   164  			name:          "lgtm comment by non-reviewer, with trailing space",
   165  			body:          "/lgtm ",
   166  			commenter:     "o",
   167  			hasLGTM:       false,
   168  			shouldToggle:  true,
   169  			shouldComment: true,
   170  			shouldAssign:  true,
   171  		},
   172  		{
   173  			name:          "lgtm comment by non-reviewer, with no-issue",
   174  			body:          "/lgtm no-issue",
   175  			commenter:     "o",
   176  			hasLGTM:       false,
   177  			shouldToggle:  true,
   178  			shouldComment: true,
   179  			shouldAssign:  true,
   180  		},
   181  		{
   182  			name:          "lgtm comment by non-reviewer, with no-issue and trailing space",
   183  			body:          "/lgtm no-issue \r",
   184  			commenter:     "o",
   185  			hasLGTM:       false,
   186  			shouldToggle:  true,
   187  			shouldComment: true,
   188  			shouldAssign:  true,
   189  		},
   190  		{
   191  			name:          "lgtm comment by rando",
   192  			body:          "/lgtm",
   193  			commenter:     "not-in-the-org",
   194  			hasLGTM:       false,
   195  			shouldToggle:  false,
   196  			shouldComment: true,
   197  			shouldAssign:  false,
   198  		},
   199  		{
   200  			name:          "lgtm cancel by non-reviewer",
   201  			body:          "/lgtm cancel",
   202  			commenter:     "o",
   203  			hasLGTM:       true,
   204  			shouldToggle:  true,
   205  			shouldComment: false,
   206  			shouldAssign:  true,
   207  		},
   208  		{
   209  			name:          "lgtm cancel by rando",
   210  			body:          "/lgtm cancel",
   211  			commenter:     "not-in-the-org",
   212  			hasLGTM:       true,
   213  			shouldToggle:  false,
   214  			shouldComment: true,
   215  			shouldAssign:  false,
   216  		},
   217  		{
   218  			name:         "lgtm cancel comment by reviewer",
   219  			body:         "/lgtm cancel",
   220  			commenter:    "reviewer1",
   221  			hasLGTM:      true,
   222  			shouldToggle: true,
   223  		},
   224  		{
   225  			name:         "lgtm cancel comment by reviewer, with trailing space",
   226  			body:         "/lgtm cancel \r",
   227  			commenter:    "reviewer1",
   228  			hasLGTM:      true,
   229  			shouldToggle: true,
   230  		},
   231  		{
   232  			name:         "lgtm cancel comment by reviewer, no lgtm",
   233  			body:         "/lgtm cancel",
   234  			commenter:    "reviewer1",
   235  			hasLGTM:      false,
   236  			shouldToggle: false,
   237  		},
   238  		{
   239  			name:          "lgtm comment, based off OWNERS only",
   240  			body:          "/lgtm",
   241  			commenter:     "sam",
   242  			hasLGTM:       false,
   243  			shouldToggle:  true,
   244  			shouldComment: true,
   245  			skipCollab:    true,
   246  		},
   247  	}
   248  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   249  	for _, tc := range testcases {
   250  		t.Logf("Running scenario %q", tc.name)
   251  		fc := &fakegithub.FakeClient{
   252  			IssueComments: make(map[int][]github.IssueComment),
   253  			PullRequests: map[int]*github.PullRequest{
   254  				5: {
   255  					Base: github.PullRequestBranch{
   256  						Ref: "master",
   257  					},
   258  					Head: github.PullRequestBranch{
   259  						SHA: SHA,
   260  					},
   261  				},
   262  			},
   263  			PullRequestChanges: map[int][]github.PullRequestChange{
   264  				5: {
   265  					{Filename: "doc/README.md"},
   266  				},
   267  			},
   268  		}
   269  		e := &github.GenericCommentEvent{
   270  			Action:      github.GenericCommentActionCreated,
   271  			IssueState:  "open",
   272  			IsPR:        true,
   273  			Body:        tc.body,
   274  			User:        github.User{Login: tc.commenter},
   275  			IssueAuthor: github.User{Login: "author"},
   276  			Number:      5,
   277  			Assignees:   []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}},
   278  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   279  			HTMLURL:     "<url>",
   280  		}
   281  		if tc.hasLGTM {
   282  			fc.IssueLabelsAdded = []string{"org/repo#5:" + LGTMLabel}
   283  		}
   284  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   285  		pc := &plugins.Configuration{}
   286  		if tc.skipCollab {
   287  			pc.Owners.SkipCollaborators = []string{"org/repo"}
   288  		}
   289  		pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   290  			Repos:         []string{"org/repo"},
   291  			StoreTreeHash: true,
   292  		})
   293  		fp := &fakePruner{
   294  			GithubClient:  fc,
   295  			IssueComments: fc.IssueComments[5],
   296  		}
   297  		if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   298  			t.Errorf("didn't expect error from lgtmComment: %v", err)
   299  			continue
   300  		}
   301  		if tc.shouldAssign {
   302  			found := false
   303  			for _, a := range fc.AssigneesAdded {
   304  				if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.commenter) {
   305  					found = true
   306  					break
   307  				}
   308  			}
   309  			if !found || len(fc.AssigneesAdded) != 1 {
   310  				t.Errorf("should have assigned %s but added assignees are %s", tc.commenter, fc.AssigneesAdded)
   311  			}
   312  		} else if len(fc.AssigneesAdded) != 0 {
   313  			t.Errorf("should not have assigned anyone but assigned %s", fc.AssigneesAdded)
   314  		}
   315  		if tc.shouldToggle {
   316  			if tc.hasLGTM {
   317  				if len(fc.IssueLabelsRemoved) == 0 {
   318  					t.Error("should have removed LGTM.")
   319  				} else if len(fc.IssueLabelsAdded) > 1 {
   320  					t.Error("should not have added LGTM.")
   321  				}
   322  			} else {
   323  				if len(fc.IssueLabelsAdded) == 0 {
   324  					t.Error("should have added LGTM.")
   325  				} else if len(fc.IssueLabelsRemoved) > 0 {
   326  					t.Error("should not have removed LGTM.")
   327  				}
   328  			}
   329  		} else if len(fc.IssueLabelsRemoved) > 0 {
   330  			t.Error("should not have removed LGTM.")
   331  		} else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) {
   332  			t.Error("should not have added LGTM.")
   333  		}
   334  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   335  			t.Error("should have commented.")
   336  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   337  			t.Error("should not have commented.")
   338  		}
   339  	}
   340  }
   341  
   342  func TestLGTMCommentWithLGTMNoti(t *testing.T) {
   343  	var testcases = []struct {
   344  		name         string
   345  		body         string
   346  		commenter    string
   347  		shouldDelete bool
   348  	}{
   349  		{
   350  			name:         "non-lgtm comment",
   351  			body:         "uh oh",
   352  			commenter:    "o",
   353  			shouldDelete: false,
   354  		},
   355  		{
   356  			name:         "lgtm comment by reviewer, no lgtm on pr",
   357  			body:         "/lgtm",
   358  			commenter:    "reviewer1",
   359  			shouldDelete: true,
   360  		},
   361  		{
   362  			name:         "LGTM comment by reviewer, no lgtm on pr",
   363  			body:         "/LGTM",
   364  			commenter:    "reviewer1",
   365  			shouldDelete: true,
   366  		},
   367  		{
   368  			name:         "lgtm comment by author",
   369  			body:         "/lgtm",
   370  			commenter:    "author",
   371  			shouldDelete: false,
   372  		},
   373  		{
   374  			name:         "lgtm comment by non-reviewer",
   375  			body:         "/lgtm",
   376  			commenter:    "o",
   377  			shouldDelete: true,
   378  		},
   379  		{
   380  			name:         "lgtm comment by non-reviewer, with trailing space",
   381  			body:         "/lgtm ",
   382  			commenter:    "o",
   383  			shouldDelete: true,
   384  		},
   385  		{
   386  			name:         "lgtm comment by non-reviewer, with no-issue",
   387  			body:         "/lgtm no-issue",
   388  			commenter:    "o",
   389  			shouldDelete: true,
   390  		},
   391  		{
   392  			name:         "lgtm comment by non-reviewer, with no-issue and trailing space",
   393  			body:         "/lgtm no-issue \r",
   394  			commenter:    "o",
   395  			shouldDelete: true,
   396  		},
   397  		{
   398  			name:         "lgtm comment by rando",
   399  			body:         "/lgtm",
   400  			commenter:    "not-in-the-org",
   401  			shouldDelete: false,
   402  		},
   403  		{
   404  			name:         "lgtm cancel comment by reviewer, no lgtm",
   405  			body:         "/lgtm cancel",
   406  			commenter:    "reviewer1",
   407  			shouldDelete: false,
   408  		},
   409  	}
   410  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   411  	for _, tc := range testcases {
   412  		fc := &fakegithub.FakeClient{
   413  			IssueComments: make(map[int][]github.IssueComment),
   414  			PullRequests: map[int]*github.PullRequest{
   415  				5: {
   416  					Head: github.PullRequestBranch{
   417  						SHA: SHA,
   418  					},
   419  				},
   420  			},
   421  		}
   422  		e := &github.GenericCommentEvent{
   423  			Action:      github.GenericCommentActionCreated,
   424  			IssueState:  "open",
   425  			IsPR:        true,
   426  			Body:        tc.body,
   427  			User:        github.User{Login: tc.commenter},
   428  			IssueAuthor: github.User{Login: "author"},
   429  			Number:      5,
   430  			Assignees:   []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}},
   431  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   432  			HTMLURL:     "<url>",
   433  		}
   434  		botName, err := fc.BotName()
   435  		if err != nil {
   436  			t.Fatalf("For case %s, could not get Bot nam", tc.name)
   437  		}
   438  		ic := github.IssueComment{
   439  			User: github.User{
   440  				Login: botName,
   441  			},
   442  			Body: removeLGTMLabelNoti,
   443  		}
   444  		fc.IssueComments[5] = append(fc.IssueComments[5], ic)
   445  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   446  		pc := &plugins.Configuration{}
   447  		fp := &fakePruner{
   448  			GithubClient:  fc,
   449  			IssueComments: fc.IssueComments[5],
   450  		}
   451  		if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   452  			t.Errorf("For case %s, didn't expect error from lgtmComment: %v", tc.name, err)
   453  			continue
   454  		}
   455  		deleted := false
   456  		for _, body := range fc.IssueCommentsDeleted {
   457  			if body == removeLGTMLabelNoti {
   458  				deleted = true
   459  				break
   460  			}
   461  		}
   462  		if tc.shouldDelete {
   463  			if !deleted {
   464  				t.Errorf("For case %s, LGTM removed notification should have been deleted", tc.name)
   465  			}
   466  		} else {
   467  			if deleted {
   468  				t.Errorf("For case %s, LGTM removed notification should not have been deleted", tc.name)
   469  			}
   470  		}
   471  	}
   472  }
   473  
   474  func TestLGTMFromApproveReview(t *testing.T) {
   475  	var testcases = []struct {
   476  		name          string
   477  		state         github.ReviewState
   478  		body          string
   479  		reviewer      string
   480  		hasLGTM       bool
   481  		shouldToggle  bool
   482  		shouldComment bool
   483  		shouldAssign  bool
   484  		storeTreeHash bool
   485  	}{
   486  		{
   487  			name:          "Request changes review by reviewer, no lgtm on pr",
   488  			state:         github.ReviewStateChangesRequested,
   489  			reviewer:      "reviewer1",
   490  			hasLGTM:       false,
   491  			shouldToggle:  false,
   492  			shouldAssign:  false,
   493  			shouldComment: false,
   494  		},
   495  		{
   496  			name:         "Request changes review by reviewer, lgtm on pr",
   497  			state:        github.ReviewStateChangesRequested,
   498  			reviewer:     "reviewer1",
   499  			hasLGTM:      true,
   500  			shouldToggle: true,
   501  			shouldAssign: false,
   502  		},
   503  		{
   504  			name:          "Approve review by reviewer, no lgtm on pr",
   505  			state:         github.ReviewStateApproved,
   506  			reviewer:      "reviewer1",
   507  			hasLGTM:       false,
   508  			shouldToggle:  true,
   509  			shouldComment: true,
   510  			storeTreeHash: true,
   511  		},
   512  		{
   513  			name:          "Approve review by reviewer, no lgtm on pr, do not store tree_hash",
   514  			state:         github.ReviewStateApproved,
   515  			reviewer:      "reviewer1",
   516  			hasLGTM:       false,
   517  			shouldToggle:  true,
   518  			shouldComment: false,
   519  		},
   520  		{
   521  			name:         "Approve review by reviewer, lgtm on pr",
   522  			state:        github.ReviewStateApproved,
   523  			reviewer:     "reviewer1",
   524  			hasLGTM:      true,
   525  			shouldToggle: false,
   526  			shouldAssign: false,
   527  		},
   528  		{
   529  			name:          "Approve review by non-reviewer, no lgtm on pr",
   530  			state:         github.ReviewStateApproved,
   531  			reviewer:      "o",
   532  			hasLGTM:       false,
   533  			shouldToggle:  true,
   534  			shouldComment: true,
   535  			shouldAssign:  true,
   536  			storeTreeHash: true,
   537  		},
   538  		{
   539  			name:          "Request changes review by non-reviewer, no lgtm on pr",
   540  			state:         github.ReviewStateChangesRequested,
   541  			reviewer:      "o",
   542  			hasLGTM:       false,
   543  			shouldToggle:  false,
   544  			shouldComment: false,
   545  			shouldAssign:  true,
   546  		},
   547  		{
   548  			name:          "Approve review by rando",
   549  			state:         github.ReviewStateApproved,
   550  			reviewer:      "not-in-the-org",
   551  			hasLGTM:       false,
   552  			shouldToggle:  false,
   553  			shouldComment: true,
   554  			shouldAssign:  false,
   555  		},
   556  		{
   557  			name:          "Comment review by issue author, no lgtm on pr",
   558  			state:         github.ReviewStateCommented,
   559  			reviewer:      "author",
   560  			hasLGTM:       false,
   561  			shouldToggle:  false,
   562  			shouldComment: false,
   563  			shouldAssign:  false,
   564  		},
   565  		{
   566  			name:          "Comment body has /lgtm on Comment Review ",
   567  			state:         github.ReviewStateCommented,
   568  			reviewer:      "reviewer1",
   569  			body:          "/lgtm",
   570  			hasLGTM:       false,
   571  			shouldToggle:  false,
   572  			shouldComment: false,
   573  			shouldAssign:  false,
   574  		},
   575  		{
   576  			name:          "Comment body has /lgtm cancel on Approve Review",
   577  			state:         github.ReviewStateApproved,
   578  			reviewer:      "reviewer1",
   579  			body:          "/lgtm cancel",
   580  			hasLGTM:       false,
   581  			shouldToggle:  false,
   582  			shouldComment: false,
   583  			shouldAssign:  false,
   584  		},
   585  	}
   586  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   587  	for _, tc := range testcases {
   588  		fc := &fakegithub.FakeClient{
   589  			IssueComments:    make(map[int][]github.IssueComment),
   590  			IssueLabelsAdded: []string{},
   591  			PullRequests: map[int]*github.PullRequest{
   592  				5: {
   593  					Head: github.PullRequestBranch{
   594  						SHA: SHA,
   595  					},
   596  				},
   597  			},
   598  		}
   599  		e := &github.ReviewEvent{
   600  			Review:      github.Review{Body: tc.body, State: tc.state, HTMLURL: "<url>", User: github.User{Login: tc.reviewer}},
   601  			PullRequest: github.PullRequest{User: github.User{Login: "author"}, Assignees: []github.User{{Login: "reviewer1"}, {Login: "reviewer2"}}, Number: 5},
   602  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   603  		}
   604  		if tc.hasLGTM {
   605  			fc.IssueLabelsAdded = append(fc.IssueLabelsAdded, "org/repo#5:"+LGTMLabel)
   606  		}
   607  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   608  		pc := &plugins.Configuration{}
   609  		pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   610  			Repos:         []string{"org/repo"},
   611  			StoreTreeHash: tc.storeTreeHash,
   612  		})
   613  		fp := &fakePruner{
   614  			GithubClient:  fc,
   615  			IssueComments: fc.IssueComments[5],
   616  		}
   617  		if err := handlePullRequestReview(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   618  			t.Errorf("For case %s, didn't expect error from pull request review: %v", tc.name, err)
   619  			continue
   620  		}
   621  		if tc.shouldAssign {
   622  			found := false
   623  			for _, a := range fc.AssigneesAdded {
   624  				if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.reviewer) {
   625  					found = true
   626  					break
   627  				}
   628  			}
   629  			if !found || len(fc.AssigneesAdded) != 1 {
   630  				t.Errorf("For case %s, should have assigned %s but added assignees are %s", tc.name, tc.reviewer, fc.AssigneesAdded)
   631  			}
   632  		} else if len(fc.AssigneesAdded) != 0 {
   633  			t.Errorf("For case %s, should not have assigned anyone but assigned %s", tc.name, fc.AssigneesAdded)
   634  		}
   635  		if tc.shouldToggle {
   636  			if tc.hasLGTM {
   637  				if len(fc.IssueLabelsRemoved) == 0 {
   638  					t.Errorf("For case %s, should have removed LGTM.", tc.name)
   639  				} else if len(fc.IssueLabelsAdded) > 1 {
   640  					t.Errorf("For case %s, should not have added LGTM.", tc.name)
   641  				}
   642  			} else {
   643  				if len(fc.IssueLabelsAdded) == 0 {
   644  					t.Errorf("For case %s, should have added LGTM.", tc.name)
   645  				} else if len(fc.IssueLabelsRemoved) > 0 {
   646  					t.Errorf("For case %s, should not have removed LGTM.", tc.name)
   647  				}
   648  			}
   649  		} else if len(fc.IssueLabelsRemoved) > 0 {
   650  			t.Errorf("For case %s, should not have removed LGTM.", tc.name)
   651  		} else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) {
   652  			t.Errorf("For case %s, should not have added LGTM.", tc.name)
   653  		}
   654  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   655  			t.Errorf("For case %s, should have commented.", tc.name)
   656  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   657  			t.Errorf("For case %s, should not have commented.", tc.name)
   658  		}
   659  	}
   660  }
   661  
   662  func TestHandlePullRequest(t *testing.T) {
   663  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   664  	treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
   665  	cases := []struct {
   666  		name             string
   667  		event            github.PullRequestEvent
   668  		removeLabelErr   error
   669  		createCommentErr error
   670  
   671  		err                error
   672  		IssueLabelsAdded   []string
   673  		IssueLabelsRemoved []string
   674  		issueComments      map[int][]github.IssueComment
   675  		trustedTeam        string
   676  
   677  		expectNoComments bool
   678  	}{
   679  		{
   680  			name: "pr_synchronize, no RemoveLabel error",
   681  			event: github.PullRequestEvent{
   682  				Action: github.PullRequestActionSynchronize,
   683  				PullRequest: github.PullRequest{
   684  					Number: 101,
   685  					Base: github.PullRequestBranch{
   686  						Repo: github.Repo{
   687  							Owner: github.User{
   688  								Login: "kubernetes",
   689  							},
   690  							Name: "kubernetes",
   691  						},
   692  					},
   693  					Head: github.PullRequestBranch{
   694  						SHA: SHA,
   695  					},
   696  				},
   697  			},
   698  			IssueLabelsRemoved: []string{LGTMLabel},
   699  			issueComments: map[int][]github.IssueComment{
   700  				101: {
   701  					{
   702  						Body: removeLGTMLabelNoti,
   703  						User: github.User{Login: fakegithub.Bot},
   704  					},
   705  				},
   706  			},
   707  			expectNoComments: false,
   708  		},
   709  		{
   710  			name: "Sticky LGTM for trusted team members",
   711  			event: github.PullRequestEvent{
   712  				Action: github.PullRequestActionSynchronize,
   713  				PullRequest: github.PullRequest{
   714  					Number: 101,
   715  					Base: github.PullRequestBranch{
   716  						Repo: github.Repo{
   717  							Owner: github.User{
   718  								Login: "kubernetes",
   719  							},
   720  							Name: "kubernetes",
   721  						},
   722  					},
   723  					User: github.User{
   724  						Login: "sig-lead",
   725  					},
   726  					MergeSHA: &SHA,
   727  				},
   728  			},
   729  			trustedTeam:      "Leads",
   730  			expectNoComments: true,
   731  		},
   732  		{
   733  			name: "LGTM not sticky for trusted user if disabled",
   734  			event: github.PullRequestEvent{
   735  				Action: github.PullRequestActionSynchronize,
   736  				PullRequest: github.PullRequest{
   737  					Number: 101,
   738  					Base: github.PullRequestBranch{
   739  						Repo: github.Repo{
   740  							Owner: github.User{
   741  								Login: "kubernetes",
   742  							},
   743  							Name: "kubernetes",
   744  						},
   745  					},
   746  					User: github.User{
   747  						Login: "sig-lead",
   748  					},
   749  					MergeSHA: &SHA,
   750  				},
   751  			},
   752  			IssueLabelsRemoved: []string{LGTMLabel},
   753  			issueComments: map[int][]github.IssueComment{
   754  				101: {
   755  					{
   756  						Body: removeLGTMLabelNoti,
   757  						User: github.User{Login: fakegithub.Bot},
   758  					},
   759  				},
   760  			},
   761  			expectNoComments: false,
   762  		},
   763  		{
   764  			name: "LGTM not sticky for non trusted user",
   765  			event: github.PullRequestEvent{
   766  				Action: github.PullRequestActionSynchronize,
   767  				PullRequest: github.PullRequest{
   768  					Number: 101,
   769  					Base: github.PullRequestBranch{
   770  						Repo: github.Repo{
   771  							Owner: github.User{
   772  								Login: "kubernetes",
   773  							},
   774  							Name: "kubernetes",
   775  						},
   776  					},
   777  					User: github.User{
   778  						Login: "sig-lead",
   779  					},
   780  					MergeSHA: &SHA,
   781  				},
   782  			},
   783  			IssueLabelsRemoved: []string{LGTMLabel},
   784  			issueComments: map[int][]github.IssueComment{
   785  				101: {
   786  					{
   787  						Body: removeLGTMLabelNoti,
   788  						User: github.User{Login: fakegithub.Bot},
   789  					},
   790  				},
   791  			},
   792  			trustedTeam:      "Committers",
   793  			expectNoComments: false,
   794  		},
   795  		{
   796  			name: "pr_assigned",
   797  			event: github.PullRequestEvent{
   798  				Action: "assigned",
   799  			},
   800  			expectNoComments: true,
   801  		},
   802  		{
   803  			name: "pr_synchronize, same tree-hash, keep label",
   804  			event: github.PullRequestEvent{
   805  				Action: github.PullRequestActionSynchronize,
   806  				PullRequest: github.PullRequest{
   807  					Number: 101,
   808  					Base: github.PullRequestBranch{
   809  						Repo: github.Repo{
   810  							Owner: github.User{
   811  								Login: "kubernetes",
   812  							},
   813  							Name: "kubernetes",
   814  						},
   815  					},
   816  					Head: github.PullRequestBranch{
   817  						SHA: SHA,
   818  					},
   819  				},
   820  			},
   821  			issueComments: map[int][]github.IssueComment{
   822  				101: {
   823  					{
   824  						Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA),
   825  						User: github.User{Login: fakegithub.Bot},
   826  					},
   827  				},
   828  			},
   829  			expectNoComments: true,
   830  		},
   831  		{
   832  			name: "pr_synchronize, same tree-hash, keep label, edited comment",
   833  			event: github.PullRequestEvent{
   834  				Action: github.PullRequestActionSynchronize,
   835  				PullRequest: github.PullRequest{
   836  					Number: 101,
   837  					Base: github.PullRequestBranch{
   838  						Repo: github.Repo{
   839  							Owner: github.User{
   840  								Login: "kubernetes",
   841  							},
   842  							Name: "kubernetes",
   843  						},
   844  					},
   845  					Head: github.PullRequestBranch{
   846  						SHA: SHA,
   847  					},
   848  				},
   849  			},
   850  			IssueLabelsRemoved: []string{LGTMLabel},
   851  			issueComments: map[int][]github.IssueComment{
   852  				101: {
   853  					{
   854  						Body:      fmt.Sprintf(addLGTMLabelNotification, treeSHA),
   855  						User:      github.User{Login: fakegithub.Bot},
   856  						CreatedAt: time.Date(1981, 2, 21, 12, 30, 0, 0, time.UTC),
   857  						UpdatedAt: time.Date(1981, 2, 21, 12, 31, 0, 0, time.UTC),
   858  					},
   859  				},
   860  			},
   861  			expectNoComments: false,
   862  		},
   863  	}
   864  	for _, c := range cases {
   865  		t.Run(c.name, func(t *testing.T) {
   866  			fakeGitHub := &fakegithub.FakeClient{
   867  				IssueComments: c.issueComments,
   868  				PullRequests: map[int]*github.PullRequest{
   869  					101: {
   870  						Base: github.PullRequestBranch{
   871  							Ref: "master",
   872  						},
   873  						Head: github.PullRequestBranch{
   874  							SHA: SHA,
   875  						},
   876  					},
   877  				},
   878  				Commits:          make(map[string]github.SingleCommit),
   879  				Collaborators:    []string{"collab"},
   880  				IssueLabelsAdded: c.IssueLabelsAdded,
   881  			}
   882  			fakeGitHub.IssueLabelsAdded = append(fakeGitHub.IssueLabelsAdded, "kubernetes/kubernetes#101:lgtm")
   883  			commit := github.SingleCommit{}
   884  			commit.Commit.Tree.SHA = treeSHA
   885  			fakeGitHub.Commits[SHA] = commit
   886  			pc := &plugins.Configuration{}
   887  			pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   888  				Repos:          []string{"kubernetes/kubernetes"},
   889  				StoreTreeHash:  true,
   890  				StickyLgtmTeam: c.trustedTeam,
   891  			})
   892  			err := handlePullRequest(
   893  				logrus.WithField("plugin", "approve"),
   894  				fakeGitHub,
   895  				pc,
   896  				&c.event,
   897  			)
   898  
   899  			if err != nil && c.err == nil {
   900  				t.Fatalf("handlePullRequest error: %v", err)
   901  			}
   902  
   903  			if err == nil && c.err != nil {
   904  				t.Fatalf("handlePullRequest wanted error: %v, got nil", c.err)
   905  			}
   906  
   907  			if got, want := err, c.err; !equality.Semantic.DeepEqual(got, want) {
   908  				t.Fatalf("handlePullRequest error mismatch: got %v, want %v", got, want)
   909  			}
   910  
   911  			if got, want := len(fakeGitHub.IssueLabelsRemoved), len(c.IssueLabelsRemoved); got != want {
   912  				t.Logf("IssueLabelsRemoved: got %v, want: %v", fakeGitHub.IssueLabelsRemoved, c.IssueLabelsRemoved)
   913  				t.Fatalf("IssueLabelsRemoved length mismatch: got %d, want %d", got, want)
   914  			}
   915  
   916  			if got, want := fakeGitHub.IssueComments, c.issueComments; !equality.Semantic.DeepEqual(got, want) {
   917  				t.Fatalf("LGTM revmoved notifications mismatch: got %v, want %v", got, want)
   918  			}
   919  			if c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) > 0 {
   920  				t.Fatalf("expected no comments but got %v", fakeGitHub.IssueCommentsAdded)
   921  			}
   922  			if !c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) == 0 {
   923  				t.Fatalf("expected comments but got none")
   924  			}
   925  		})
   926  	}
   927  }
   928  
   929  func TestAddTreeHashComment(t *testing.T) {
   930  	cases := []struct {
   931  		name          string
   932  		author        string
   933  		trustedTeam   string
   934  		expectTreeSha bool
   935  	}{
   936  		{
   937  			name:          "Tree SHA added",
   938  			author:        "Bob",
   939  			expectTreeSha: true,
   940  		},
   941  		{
   942  			name:          "Tree SHA if sticky lgtm off",
   943  			author:        "sig-lead",
   944  			expectTreeSha: true,
   945  		},
   946  		{
   947  			name:          "No Tree SHA if sticky lgtm",
   948  			author:        "sig-lead",
   949  			trustedTeam:   "Leads",
   950  			expectTreeSha: false,
   951  		},
   952  	}
   953  
   954  	for _, c := range cases {
   955  		t.Run(c.name, func(t *testing.T) {
   956  
   957  			SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   958  			treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
   959  			pc := &plugins.Configuration{}
   960  			pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   961  				Repos:          []string{"kubernetes/kubernetes"},
   962  				StoreTreeHash:  true,
   963  				StickyLgtmTeam: c.trustedTeam,
   964  			})
   965  			rc := reviewCtx{
   966  				author:      "alice",
   967  				issueAuthor: c.author,
   968  				repo: github.Repo{
   969  					Owner: github.User{
   970  						Login: "kubernetes",
   971  					},
   972  					Name: "kubernetes",
   973  				},
   974  				number: 101,
   975  				body:   "/lgtm",
   976  			}
   977  			fc := &fakegithub.FakeClient{
   978  				Commits:       make(map[string]github.SingleCommit),
   979  				IssueComments: map[int][]github.IssueComment{},
   980  				PullRequests: map[int]*github.PullRequest{
   981  					101: {
   982  						Base: github.PullRequestBranch{
   983  							Ref: "master",
   984  						},
   985  						Head: github.PullRequestBranch{
   986  							SHA: SHA,
   987  						},
   988  					},
   989  				},
   990  			}
   991  			commit := github.SingleCommit{}
   992  			commit.Commit.Tree.SHA = treeSHA
   993  			fc.Commits[SHA] = commit
   994  			handle(true, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), &fakePruner{})
   995  			found := false
   996  			for _, body := range fc.IssueCommentsAdded {
   997  				if addLGTMLabelNotificationRe.MatchString(body) {
   998  					found = true
   999  					break
  1000  				}
  1001  			}
  1002  			if c.expectTreeSha {
  1003  				if !found {
  1004  					t.Fatalf("expected tree_hash comment but got none")
  1005  				}
  1006  			} else {
  1007  				if found {
  1008  					t.Fatalf("expected no tree_hash comment but got one")
  1009  				}
  1010  			}
  1011  		})
  1012  	}
  1013  }
  1014  
  1015  func TestRemoveTreeHashComment(t *testing.T) {
  1016  	treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
  1017  	pc := &plugins.Configuration{}
  1018  	pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
  1019  		Repos:         []string{"kubernetes/kubernetes"},
  1020  		StoreTreeHash: true,
  1021  	})
  1022  	rc := reviewCtx{
  1023  		author:      "alice",
  1024  		issueAuthor: "bob",
  1025  		repo: github.Repo{
  1026  			Owner: github.User{
  1027  				Login: "kubernetes",
  1028  			},
  1029  			Name: "kubernetes",
  1030  		},
  1031  		assignees: []github.User{{Login: "alice"}},
  1032  		number:    101,
  1033  		body:      "/lgtm cancel",
  1034  	}
  1035  	fc := &fakegithub.FakeClient{
  1036  		IssueComments: map[int][]github.IssueComment{
  1037  			101: {
  1038  				{
  1039  					Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA),
  1040  					User: github.User{Login: fakegithub.Bot},
  1041  				},
  1042  			},
  1043  		},
  1044  	}
  1045  	fc.IssueLabelsAdded = []string{"kubernetes/kubernetes#101:" + LGTMLabel}
  1046  	fp := &fakePruner{
  1047  		GithubClient:  fc,
  1048  		IssueComments: fc.IssueComments[101],
  1049  	}
  1050  	handle(false, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), fp)
  1051  	found := false
  1052  	for _, body := range fc.IssueCommentsDeleted {
  1053  		if addLGTMLabelNotificationRe.MatchString(body) {
  1054  			found = true
  1055  			break
  1056  		}
  1057  	}
  1058  	if !found {
  1059  		t.Fatalf("expected deleted tree_hash comment but got none")
  1060  	}
  1061  }