sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/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  	"os"
    22  	"path/filepath"
    23  	"regexp"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/sirupsen/logrus"
    29  	"sigs.k8s.io/yaml"
    30  
    31  	"k8s.io/apimachinery/pkg/api/equality"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  
    34  	"sigs.k8s.io/prow/pkg/config"
    35  	"sigs.k8s.io/prow/pkg/github"
    36  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    37  	"sigs.k8s.io/prow/pkg/layeredsets"
    38  	"sigs.k8s.io/prow/pkg/plugins"
    39  	"sigs.k8s.io/prow/pkg/plugins/ownersconfig"
    40  	"sigs.k8s.io/prow/pkg/repoowners"
    41  )
    42  
    43  type fakeOwnersClient struct {
    44  	approvers map[string]layeredsets.String
    45  	reviewers map[string]layeredsets.String
    46  }
    47  
    48  func (f *fakeOwnersClient) LoadRepoOwnersSha(org, repo, base, sha string, updateCache bool) (repoowners.RepoOwner, error) {
    49  	return &fakeRepoOwners{approvers: f.approvers, reviewers: f.reviewers}, nil
    50  }
    51  
    52  var _ repoowners.Interface = &fakeOwnersClient{}
    53  
    54  func (f *fakeOwnersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwner, error) {
    55  	return &fakeRepoOwners{approvers: f.approvers, reviewers: f.reviewers}, nil
    56  }
    57  
    58  func (f *fakeOwnersClient) WithFields(fields logrus.Fields) repoowners.Interface {
    59  	return f
    60  }
    61  
    62  func (f *fakeOwnersClient) WithGitHubClient(client github.Client) repoowners.Interface {
    63  	return f
    64  }
    65  
    66  func (f *fakeOwnersClient) ForPlugin(string) repoowners.Interface {
    67  	return f
    68  }
    69  
    70  func (f *fakeOwnersClient) Used() bool {
    71  	return true
    72  }
    73  
    74  type fakeRepoOwners struct {
    75  	approvers   map[string]layeredsets.String
    76  	reviewers   map[string]layeredsets.String
    77  	dirDenylist []*regexp.Regexp
    78  }
    79  
    80  func (f *fakeRepoOwners) AllApprovers() sets.Set[string] {
    81  	return sets.Set[string]{}
    82  }
    83  
    84  func (f *fakeRepoOwners) AllOwners() sets.Set[string] {
    85  	return sets.Set[string]{}
    86  }
    87  
    88  func (f *fakeRepoOwners) AllReviewers() sets.Set[string] {
    89  	return sets.Set[string]{}
    90  }
    91  
    92  func (f *fakeRepoOwners) Filenames() ownersconfig.Filenames {
    93  	return ownersconfig.FakeFilenames
    94  }
    95  
    96  type fakePruner struct {
    97  	GitHubClient  *fakegithub.FakeClient
    98  	IssueComments []github.IssueComment
    99  }
   100  
   101  func (fp *fakePruner) PruneComments(shouldPrune func(github.IssueComment) bool) {
   102  	for _, comment := range fp.IssueComments {
   103  		if shouldPrune(comment) {
   104  			fp.GitHubClient.IssueCommentsDeleted = append(fp.GitHubClient.IssueCommentsDeleted, comment.Body)
   105  		}
   106  	}
   107  }
   108  
   109  var _ repoowners.RepoOwner = &fakeRepoOwners{}
   110  
   111  func (f *fakeRepoOwners) FindApproverOwnersForFile(path string) string    { return "" }
   112  func (f *fakeRepoOwners) FindReviewersOwnersForFile(path string) string   { return "" }
   113  func (f *fakeRepoOwners) FindLabelsForFile(path string) sets.Set[string]  { return nil }
   114  func (f *fakeRepoOwners) IsNoParentOwners(path string) bool               { return false }
   115  func (f *fakeRepoOwners) IsAutoApproveUnownedSubfolders(path string) bool { return false }
   116  func (f *fakeRepoOwners) LeafApprovers(path string) sets.Set[string]      { return nil }
   117  func (f *fakeRepoOwners) Approvers(path string) layeredsets.String        { return f.approvers[path] }
   118  func (f *fakeRepoOwners) LeafReviewers(path string) sets.Set[string]      { return nil }
   119  func (f *fakeRepoOwners) Reviewers(path string) layeredsets.String        { return f.reviewers[path] }
   120  func (f *fakeRepoOwners) RequiredReviewers(path string) sets.Set[string]  { return nil }
   121  func (f *fakeRepoOwners) TopLevelApprovers() sets.Set[string]             { return nil }
   122  
   123  func (f *fakeRepoOwners) ParseSimpleConfig(path string) (repoowners.SimpleConfig, error) {
   124  	dir := filepath.Dir(path)
   125  	for _, re := range f.dirDenylist {
   126  		if re.MatchString(dir) {
   127  			return repoowners.SimpleConfig{}, filepath.SkipDir
   128  		}
   129  	}
   130  
   131  	b, err := os.ReadFile(path)
   132  	if err != nil {
   133  		return repoowners.SimpleConfig{}, err
   134  	}
   135  	full := new(repoowners.SimpleConfig)
   136  	err = yaml.Unmarshal(b, full)
   137  	return *full, err
   138  }
   139  
   140  func (f *fakeRepoOwners) ParseFullConfig(path string) (repoowners.FullConfig, error) {
   141  	dir := filepath.Dir(path)
   142  	for _, re := range f.dirDenylist {
   143  		if re.MatchString(dir) {
   144  			return repoowners.FullConfig{}, filepath.SkipDir
   145  		}
   146  	}
   147  
   148  	b, err := os.ReadFile(path)
   149  	if err != nil {
   150  		return repoowners.FullConfig{}, err
   151  	}
   152  	full := new(repoowners.FullConfig)
   153  	err = yaml.Unmarshal(b, full)
   154  	return *full, err
   155  }
   156  
   157  var approvers = map[string]layeredsets.String{
   158  	"doc/README.md": layeredsets.NewString("cjwagner", "jessica"),
   159  }
   160  
   161  var reviewers = map[string]layeredsets.String{
   162  	"doc/README.md": layeredsets.NewString("alice", "bob", "mark", "sam"),
   163  }
   164  
   165  func TestLGTMComment(t *testing.T) {
   166  	var testcases = []struct {
   167  		name          string
   168  		body          string
   169  		commenter     string
   170  		hasLGTM       bool
   171  		shouldToggle  bool
   172  		shouldComment bool
   173  		shouldAssign  bool
   174  		skipCollab    bool
   175  		storeTreeHash bool
   176  		shouldRequest bool
   177  	}{
   178  		{
   179  			name:         "non-lgtm comment",
   180  			body:         "uh oh",
   181  			commenter:    "collab2",
   182  			hasLGTM:      false,
   183  			shouldToggle: false,
   184  		},
   185  		{
   186  			name:          "lgtm comment by reviewer, no lgtm on pr",
   187  			body:          "/lgtm",
   188  			commenter:     "collab1",
   189  			hasLGTM:       false,
   190  			shouldToggle:  true,
   191  			shouldComment: true,
   192  		},
   193  		{
   194  			name:          "LGTM comment by reviewer, no lgtm on pr",
   195  			body:          "/LGTM",
   196  			commenter:     "collab1",
   197  			hasLGTM:       false,
   198  			shouldToggle:  true,
   199  			shouldComment: true,
   200  		},
   201  		{
   202  			name:         "lgtm comment by reviewer, lgtm on pr",
   203  			body:         "/lgtm",
   204  			commenter:    "collab1",
   205  			hasLGTM:      true,
   206  			shouldToggle: false,
   207  		},
   208  		{
   209  			name:          "lgtm comment by author",
   210  			body:          "/lgtm",
   211  			commenter:     "author",
   212  			hasLGTM:       false,
   213  			shouldToggle:  false,
   214  			shouldComment: true,
   215  		},
   216  		{
   217  			name:          "lgtm cancel by author",
   218  			body:          "/lgtm cancel",
   219  			commenter:     "author",
   220  			hasLGTM:       true,
   221  			shouldToggle:  true,
   222  			shouldAssign:  false,
   223  			shouldComment: false,
   224  			shouldRequest: true,
   225  		},
   226  		{
   227  			name:          "remove lgtm by author",
   228  			body:          "/remove-lgtm",
   229  			commenter:     "author",
   230  			hasLGTM:       true,
   231  			shouldToggle:  true,
   232  			shouldAssign:  false,
   233  			shouldComment: false,
   234  			shouldRequest: true,
   235  		},
   236  		{
   237  			name:          "lgtm comment by non-reviewer",
   238  			body:          "/lgtm",
   239  			commenter:     "collab2",
   240  			hasLGTM:       false,
   241  			shouldToggle:  true,
   242  			shouldComment: true,
   243  			shouldAssign:  true,
   244  		},
   245  		{
   246  			name:          "lgtm comment by non-reviewer, with trailing space",
   247  			body:          "/lgtm ",
   248  			commenter:     "collab2",
   249  			hasLGTM:       false,
   250  			shouldToggle:  true,
   251  			shouldComment: true,
   252  			shouldAssign:  true,
   253  		},
   254  		{
   255  			name:          "lgtm comment by non-reviewer, with no-issue",
   256  			body:          "/lgtm no-issue",
   257  			commenter:     "collab2",
   258  			hasLGTM:       false,
   259  			shouldToggle:  true,
   260  			shouldComment: true,
   261  			shouldAssign:  true,
   262  		},
   263  		{
   264  			name:          "lgtm comment by non-reviewer, with no-issue and trailing space",
   265  			body:          "/lgtm no-issue \r",
   266  			commenter:     "collab2",
   267  			hasLGTM:       false,
   268  			shouldToggle:  true,
   269  			shouldComment: true,
   270  			shouldAssign:  true,
   271  		},
   272  		{
   273  			name:          "lgtm comment by rando",
   274  			body:          "/lgtm",
   275  			commenter:     "not-in-the-org",
   276  			hasLGTM:       false,
   277  			shouldToggle:  false,
   278  			shouldComment: true,
   279  			shouldAssign:  false,
   280  		},
   281  		{
   282  			name:          "lgtm cancel by non-reviewer",
   283  			body:          "/lgtm cancel",
   284  			commenter:     "collab2",
   285  			hasLGTM:       true,
   286  			shouldToggle:  true,
   287  			shouldComment: false,
   288  			shouldAssign:  true,
   289  			shouldRequest: true,
   290  		},
   291  		{
   292  			name:          "remove lgtm by non-reviewer",
   293  			body:          "/remove-lgtm",
   294  			commenter:     "collab2",
   295  			hasLGTM:       true,
   296  			shouldToggle:  true,
   297  			shouldComment: false,
   298  			shouldAssign:  true,
   299  			shouldRequest: true,
   300  		},
   301  		{
   302  			name:          "lgtm cancel by rando",
   303  			body:          "/lgtm cancel",
   304  			commenter:     "not-in-the-org",
   305  			hasLGTM:       true,
   306  			shouldToggle:  false,
   307  			shouldComment: true,
   308  			shouldAssign:  false,
   309  		},
   310  		{
   311  			name:          "remove lgtm by rando",
   312  			body:          "/remove-lgtm",
   313  			commenter:     "not-in-the-org",
   314  			hasLGTM:       true,
   315  			shouldToggle:  false,
   316  			shouldComment: true,
   317  			shouldAssign:  false,
   318  		},
   319  		{
   320  			name:          "lgtm cancel comment by reviewer",
   321  			body:          "/lgtm cancel",
   322  			commenter:     "collab1",
   323  			hasLGTM:       true,
   324  			shouldToggle:  true,
   325  			shouldRequest: true,
   326  		},
   327  		{
   328  			name:          "remove-lgtm comment by reviewer",
   329  			body:          "/remove-lgtm",
   330  			commenter:     "collab1",
   331  			hasLGTM:       true,
   332  			shouldToggle:  true,
   333  			shouldRequest: true,
   334  		},
   335  		{
   336  			name:          "lgtm cancel comment by reviewer, with trailing space",
   337  			body:          "/lgtm cancel \r",
   338  			commenter:     "collab1",
   339  			hasLGTM:       true,
   340  			shouldToggle:  true,
   341  			shouldRequest: true,
   342  		},
   343  		{
   344  			name:          "remove lgtm comment by reviewer, with trailing space",
   345  			body:          "/remove-lgtm \r",
   346  			commenter:     "collab1",
   347  			hasLGTM:       true,
   348  			shouldToggle:  true,
   349  			shouldRequest: true,
   350  		},
   351  		{
   352  			name:         "lgtm cancel comment by reviewer, no lgtm",
   353  			body:         "/lgtm cancel",
   354  			commenter:    "collab1",
   355  			hasLGTM:      false,
   356  			shouldToggle: false,
   357  		},
   358  		{
   359  			name:         "remove lgtm comment by reviewer, no lgtm",
   360  			body:         "/remove-lgtm",
   361  			commenter:    "collab1",
   362  			hasLGTM:      false,
   363  			shouldToggle: false,
   364  		},
   365  		{
   366  			name:          "lgtm comment, based off OWNERS only",
   367  			body:          "/lgtm",
   368  			commenter:     "sam",
   369  			hasLGTM:       false,
   370  			shouldToggle:  true,
   371  			shouldComment: true,
   372  			skipCollab:    true,
   373  		},
   374  		{
   375  			name:          "lgtm comment by assignee, but not collab",
   376  			body:          "/lgtm",
   377  			commenter:     "assignee1",
   378  			hasLGTM:       false,
   379  			shouldToggle:  false,
   380  			shouldComment: true,
   381  			shouldAssign:  false,
   382  		},
   383  	}
   384  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   385  	for _, tc := range testcases {
   386  		t.Logf("Running scenario %q", tc.name)
   387  		fc := fakegithub.NewFakeClient()
   388  		fc.IssueComments = make(map[int][]github.IssueComment)
   389  		fc.PullRequests = map[int]*github.PullRequest{
   390  			5: {
   391  				Base: github.PullRequestBranch{
   392  					Ref: "master",
   393  				},
   394  				Head: github.PullRequestBranch{
   395  					SHA: SHA,
   396  				},
   397  			},
   398  		}
   399  		fc.PullRequestChanges = map[int][]github.PullRequestChange{
   400  			5: {
   401  				{Filename: "doc/README.md"},
   402  			},
   403  		}
   404  		fc.Collaborators = []string{"collab1", "collab2"}
   405  		e := &github.GenericCommentEvent{
   406  			Action:      github.GenericCommentActionCreated,
   407  			IssueState:  "open",
   408  			IsPR:        true,
   409  			Body:        tc.body,
   410  			User:        github.User{Login: tc.commenter},
   411  			IssueAuthor: github.User{Login: "author"},
   412  			Number:      5,
   413  			Assignees:   []github.User{{Login: "collab1"}, {Login: "assignee1"}},
   414  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   415  			HTMLURL:     "<url>",
   416  		}
   417  		if tc.hasLGTM {
   418  			fc.IssueLabelsAdded = []string{"org/repo#5:" + LGTMLabel}
   419  		}
   420  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   421  		pc := &plugins.Configuration{}
   422  		if tc.skipCollab {
   423  			pc.Owners.SkipCollaborators = []string{"org/repo"}
   424  		}
   425  		pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   426  			Repos:         []string{"org/repo"},
   427  			StoreTreeHash: true,
   428  		})
   429  		fp := &fakePruner{
   430  			GitHubClient:  fc,
   431  			IssueComments: fc.IssueComments[5],
   432  		}
   433  		if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   434  			t.Errorf("didn't expect error from lgtmComment: %v", err)
   435  			continue
   436  		}
   437  		if tc.shouldAssign {
   438  			found := false
   439  			for _, a := range fc.AssigneesAdded {
   440  				if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.commenter) {
   441  					found = true
   442  					break
   443  				}
   444  			}
   445  			if !found || len(fc.AssigneesAdded) != 1 {
   446  				t.Errorf("should have assigned %s but added assignees are %s", tc.commenter, fc.AssigneesAdded)
   447  			}
   448  		} else if len(fc.AssigneesAdded) != 0 {
   449  			t.Errorf("should not have assigned anyone but assigned %s", fc.AssigneesAdded)
   450  		}
   451  		if tc.shouldToggle {
   452  			if tc.hasLGTM {
   453  				if len(fc.IssueLabelsRemoved) == 0 {
   454  					t.Error("should have removed LGTM.")
   455  				} else if len(fc.IssueLabelsAdded) > 1 {
   456  					t.Error("should not have added LGTM.")
   457  				}
   458  			} else {
   459  				if len(fc.IssueLabelsAdded) == 0 {
   460  					t.Error("should have added LGTM.")
   461  				} else if len(fc.IssueLabelsRemoved) > 0 {
   462  					t.Error("should not have removed LGTM.")
   463  				}
   464  			}
   465  		} else if len(fc.IssueLabelsRemoved) > 0 {
   466  			t.Error("should not have removed LGTM.")
   467  		} else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) {
   468  			t.Error("should not have added LGTM.")
   469  		}
   470  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   471  			t.Error("should have commented.")
   472  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   473  			t.Error("should not have commented.")
   474  		}
   475  		if tc.shouldRequest && len(fc.ReviewersRequested) == 0 {
   476  			t.Error("should have re-requested reviewers")
   477  		}
   478  		if !tc.shouldRequest && len(fc.ReviewersRequested) > 0 {
   479  			t.Errorf("should not have re-requested reviewers, but requested these reviewers %v", fc.ReviewersRequested)
   480  		}
   481  	}
   482  }
   483  
   484  func TestLGTMCommentWithLGTMNoti(t *testing.T) {
   485  	var testcases = []struct {
   486  		name         string
   487  		body         string
   488  		commenter    string
   489  		shouldDelete bool
   490  	}{
   491  		{
   492  			name:         "non-lgtm comment",
   493  			body:         "uh oh",
   494  			commenter:    "collab2",
   495  			shouldDelete: false,
   496  		},
   497  		{
   498  			name:         "lgtm comment by reviewer, no lgtm on pr",
   499  			body:         "/lgtm",
   500  			commenter:    "collab1",
   501  			shouldDelete: true,
   502  		},
   503  		{
   504  			name:         "LGTM comment by reviewer, no lgtm on pr",
   505  			body:         "/LGTM",
   506  			commenter:    "collab1",
   507  			shouldDelete: true,
   508  		},
   509  		{
   510  			name:         "lgtm comment by author",
   511  			body:         "/lgtm",
   512  			commenter:    "author",
   513  			shouldDelete: false,
   514  		},
   515  		{
   516  			name:         "lgtm comment by non-reviewer",
   517  			body:         "/lgtm",
   518  			commenter:    "collab2",
   519  			shouldDelete: true,
   520  		},
   521  		{
   522  			name:         "lgtm comment by non-reviewer, with trailing space",
   523  			body:         "/lgtm ",
   524  			commenter:    "collab2",
   525  			shouldDelete: true,
   526  		},
   527  		{
   528  			name:         "lgtm comment by non-reviewer, with no-issue",
   529  			body:         "/lgtm no-issue",
   530  			commenter:    "collab2",
   531  			shouldDelete: true,
   532  		},
   533  		{
   534  			name:         "lgtm comment by non-reviewer, with no-issue and trailing space",
   535  			body:         "/lgtm no-issue \r",
   536  			commenter:    "collab2",
   537  			shouldDelete: true,
   538  		},
   539  		{
   540  			name:         "lgtm comment by rando",
   541  			body:         "/lgtm",
   542  			commenter:    "not-in-the-org",
   543  			shouldDelete: false,
   544  		},
   545  		{
   546  			name:         "lgtm cancel comment by reviewer, no lgtm",
   547  			body:         "/lgtm cancel",
   548  			commenter:    "collab1",
   549  			shouldDelete: false,
   550  		},
   551  		{
   552  			name:         "remove-lgtm comment by reviewer, no lgtm",
   553  			body:         "/remove-lgtm",
   554  			commenter:    "collab1",
   555  			shouldDelete: false,
   556  		},
   557  	}
   558  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   559  	for _, tc := range testcases {
   560  		fc := fakegithub.NewFakeClient()
   561  		fc.IssueComments = make(map[int][]github.IssueComment)
   562  		fc.PullRequests = map[int]*github.PullRequest{
   563  			5: {
   564  				Head: github.PullRequestBranch{
   565  					SHA: SHA,
   566  				},
   567  			},
   568  		}
   569  		fc.Collaborators = []string{"collab1", "collab2"}
   570  		e := &github.GenericCommentEvent{
   571  			Action:      github.GenericCommentActionCreated,
   572  			IssueState:  "open",
   573  			IsPR:        true,
   574  			Body:        tc.body,
   575  			User:        github.User{Login: tc.commenter},
   576  			IssueAuthor: github.User{Login: "author"},
   577  			Number:      5,
   578  			Assignees:   []github.User{{Login: "collab1"}, {Login: "assignee1"}},
   579  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   580  			HTMLURL:     "<url>",
   581  		}
   582  		botUser, err := fc.BotUser()
   583  		if err != nil {
   584  			t.Fatalf("For case %s, could not get Bot nam", tc.name)
   585  		}
   586  		ic := github.IssueComment{
   587  			User: github.User{
   588  				Login: botUser.Login,
   589  			},
   590  			Body: removeLGTMLabelNoti,
   591  		}
   592  		fc.IssueComments[5] = append(fc.IssueComments[5], ic)
   593  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   594  		pc := &plugins.Configuration{}
   595  		fp := &fakePruner{
   596  			GitHubClient:  fc,
   597  			IssueComments: fc.IssueComments[5],
   598  		}
   599  		if err := handleGenericComment(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   600  			t.Errorf("For case %s, didn't expect error from lgtmComment: %v", tc.name, err)
   601  			continue
   602  		}
   603  		deleted := false
   604  		for _, body := range fc.IssueCommentsDeleted {
   605  			if body == removeLGTMLabelNoti {
   606  				deleted = true
   607  				break
   608  			}
   609  		}
   610  		if tc.shouldDelete {
   611  			if !deleted {
   612  				t.Errorf("For case %s, LGTM removed notification should have been deleted", tc.name)
   613  			}
   614  		} else {
   615  			if deleted {
   616  				t.Errorf("For case %s, LGTM removed notification should not have been deleted", tc.name)
   617  			}
   618  		}
   619  	}
   620  }
   621  
   622  func TestLGTMFromApproveReview(t *testing.T) {
   623  	var testcases = []struct {
   624  		name          string
   625  		state         github.ReviewState
   626  		action        github.ReviewEventAction
   627  		body          string
   628  		reviewer      string
   629  		hasLGTM       bool
   630  		shouldToggle  bool
   631  		shouldComment bool
   632  		shouldAssign  bool
   633  		storeTreeHash bool
   634  	}{
   635  		{
   636  			name:          "Edit approve review by reviewer, no lgtm on pr",
   637  			state:         github.ReviewStateApproved,
   638  			action:        github.ReviewActionEdited,
   639  			reviewer:      "collab1",
   640  			hasLGTM:       false,
   641  			shouldToggle:  false,
   642  			storeTreeHash: true,
   643  		},
   644  		{
   645  			name:          "Dismiss approve review by reviewer, no lgtm on pr",
   646  			state:         github.ReviewStateApproved,
   647  			action:        github.ReviewActionDismissed,
   648  			reviewer:      "collab1",
   649  			hasLGTM:       false,
   650  			shouldToggle:  false,
   651  			storeTreeHash: true,
   652  		},
   653  		{
   654  			name:          "Request changes review by reviewer, no lgtm on pr",
   655  			state:         github.ReviewStateChangesRequested,
   656  			action:        github.ReviewActionSubmitted,
   657  			reviewer:      "collab1",
   658  			hasLGTM:       false,
   659  			shouldToggle:  false,
   660  			shouldAssign:  false,
   661  			shouldComment: false,
   662  		},
   663  		{
   664  			name:         "Request changes review by reviewer, lgtm on pr",
   665  			state:        github.ReviewStateChangesRequested,
   666  			action:       github.ReviewActionSubmitted,
   667  			reviewer:     "collab1",
   668  			hasLGTM:      true,
   669  			shouldToggle: true,
   670  			shouldAssign: false,
   671  		},
   672  		{
   673  			name:          "Approve review by reviewer, no lgtm on pr",
   674  			state:         github.ReviewStateApproved,
   675  			action:        github.ReviewActionSubmitted,
   676  			reviewer:      "collab1",
   677  			hasLGTM:       false,
   678  			shouldToggle:  true,
   679  			shouldComment: true,
   680  			storeTreeHash: true,
   681  		},
   682  		{
   683  			name:          "Approve review by reviewer, no lgtm on pr, do not store tree_hash",
   684  			state:         github.ReviewStateApproved,
   685  			action:        github.ReviewActionSubmitted,
   686  			reviewer:      "collab1",
   687  			hasLGTM:       false,
   688  			shouldToggle:  true,
   689  			shouldComment: false,
   690  		},
   691  		{
   692  			name:         "Approve review by reviewer, lgtm on pr",
   693  			state:        github.ReviewStateApproved,
   694  			action:       github.ReviewActionSubmitted,
   695  			reviewer:     "collab1",
   696  			hasLGTM:      true,
   697  			shouldToggle: false,
   698  			shouldAssign: false,
   699  		},
   700  		{
   701  			name:          "Approve review by non-reviewer, no lgtm on pr",
   702  			state:         github.ReviewStateApproved,
   703  			action:        github.ReviewActionSubmitted,
   704  			reviewer:      "collab2",
   705  			hasLGTM:       false,
   706  			shouldToggle:  true,
   707  			shouldComment: true,
   708  			shouldAssign:  true,
   709  			storeTreeHash: true,
   710  		},
   711  		{
   712  			name:          "Request changes review by non-reviewer, no lgtm on pr",
   713  			state:         github.ReviewStateChangesRequested,
   714  			action:        github.ReviewActionSubmitted,
   715  			reviewer:      "collab2",
   716  			hasLGTM:       false,
   717  			shouldToggle:  false,
   718  			shouldComment: false,
   719  			shouldAssign:  true,
   720  		},
   721  		{
   722  			name:          "Approve review by rando",
   723  			state:         github.ReviewStateApproved,
   724  			action:        github.ReviewActionSubmitted,
   725  			reviewer:      "not-in-the-org",
   726  			hasLGTM:       false,
   727  			shouldToggle:  false,
   728  			shouldComment: true,
   729  			shouldAssign:  false,
   730  		},
   731  		{
   732  			name:          "Comment review by issue author, no lgtm on pr",
   733  			state:         github.ReviewStateCommented,
   734  			action:        github.ReviewActionSubmitted,
   735  			reviewer:      "author",
   736  			hasLGTM:       false,
   737  			shouldToggle:  false,
   738  			shouldComment: false,
   739  			shouldAssign:  false,
   740  		},
   741  		{
   742  			name:          "Comment body has /lgtm on Comment Review ",
   743  			state:         github.ReviewStateCommented,
   744  			action:        github.ReviewActionSubmitted,
   745  			reviewer:      "collab1",
   746  			body:          "/lgtm",
   747  			hasLGTM:       false,
   748  			shouldToggle:  false,
   749  			shouldComment: false,
   750  			shouldAssign:  false,
   751  		},
   752  		{
   753  			name:          "Comment body has /lgtm cancel on Approve Review",
   754  			state:         github.ReviewStateApproved,
   755  			action:        github.ReviewActionSubmitted,
   756  			reviewer:      "collab1",
   757  			body:          "/lgtm cancel",
   758  			hasLGTM:       false,
   759  			shouldToggle:  false,
   760  			shouldComment: false,
   761  			shouldAssign:  false,
   762  		},
   763  	}
   764  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   765  	for _, tc := range testcases {
   766  		fc := fakegithub.NewFakeClient()
   767  		fc.IssueComments = make(map[int][]github.IssueComment)
   768  		fc.IssueLabelsAdded = []string{}
   769  		fc.PullRequests = map[int]*github.PullRequest{
   770  			5: {
   771  				Head: github.PullRequestBranch{
   772  					SHA: SHA,
   773  				},
   774  			},
   775  		}
   776  		fc.Collaborators = []string{"collab1", "collab2"}
   777  		e := &github.ReviewEvent{
   778  			Action:      tc.action,
   779  			Review:      github.Review{Body: tc.body, State: tc.state, HTMLURL: "<url>", User: github.User{Login: tc.reviewer}},
   780  			PullRequest: github.PullRequest{User: github.User{Login: "author"}, Assignees: []github.User{{Login: "collab1"}, {Login: "assignee1"}}, Number: 5},
   781  			Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   782  		}
   783  		if tc.hasLGTM {
   784  			fc.IssueLabelsAdded = append(fc.IssueLabelsAdded, "org/repo#5:"+LGTMLabel)
   785  		}
   786  		oc := &fakeOwnersClient{approvers: approvers, reviewers: reviewers}
   787  		pc := &plugins.Configuration{}
   788  		pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
   789  			Repos:         []string{"org/repo"},
   790  			StoreTreeHash: tc.storeTreeHash,
   791  		})
   792  		fp := &fakePruner{
   793  			GitHubClient:  fc,
   794  			IssueComments: fc.IssueComments[5],
   795  		}
   796  		if err := handlePullRequestReview(fc, pc, oc, logrus.WithField("plugin", PluginName), fp, *e); err != nil {
   797  			t.Errorf("For case %s, didn't expect error from pull request review: %v", tc.name, err)
   798  			continue
   799  		}
   800  		if tc.shouldAssign {
   801  			found := false
   802  			for _, a := range fc.AssigneesAdded {
   803  				if a == fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 5, tc.reviewer) {
   804  					found = true
   805  					break
   806  				}
   807  			}
   808  			if !found || len(fc.AssigneesAdded) != 1 {
   809  				t.Errorf("For case %s, should have assigned %s but added assignees are %s", tc.name, tc.reviewer, fc.AssigneesAdded)
   810  			}
   811  		} else if len(fc.AssigneesAdded) != 0 {
   812  			t.Errorf("For case %s, should not have assigned anyone but assigned %s", tc.name, fc.AssigneesAdded)
   813  		}
   814  		if tc.shouldToggle {
   815  			if tc.hasLGTM {
   816  				if len(fc.IssueLabelsRemoved) == 0 {
   817  					t.Errorf("For case %s, should have removed LGTM.", tc.name)
   818  				} else if len(fc.IssueLabelsAdded) > 1 {
   819  					t.Errorf("For case %s, should not have added LGTM.", tc.name)
   820  				}
   821  			} else {
   822  				if len(fc.IssueLabelsAdded) == 0 {
   823  					t.Errorf("For case %s, should have added LGTM.", tc.name)
   824  				} else if len(fc.IssueLabelsRemoved) > 0 {
   825  					t.Errorf("For case %s, should not have removed LGTM.", tc.name)
   826  				}
   827  			}
   828  		} else if len(fc.IssueLabelsRemoved) > 0 {
   829  			t.Errorf("For case %s, should not have removed LGTM.", tc.name)
   830  		} else if (tc.hasLGTM && len(fc.IssueLabelsAdded) > 1) || (!tc.hasLGTM && len(fc.IssueLabelsAdded) > 0) {
   831  			t.Errorf("For case %s, should not have added LGTM.", tc.name)
   832  		}
   833  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   834  			t.Errorf("For case %s, should have commented.", tc.name)
   835  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   836  			t.Errorf("For case %s, should not have commented.", tc.name)
   837  		}
   838  	}
   839  }
   840  
   841  func TestHandlePullRequest(t *testing.T) {
   842  	SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
   843  	treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
   844  	cases := []struct {
   845  		name             string
   846  		event            github.PullRequestEvent
   847  		removeLabelErr   error
   848  		createCommentErr error
   849  
   850  		err                error
   851  		IssueLabelsAdded   []string
   852  		IssueLabelsRemoved []string
   853  		issueComments      map[int][]github.IssueComment
   854  		trustedTeam        string
   855  
   856  		expectNoComments bool
   857  
   858  		shouldRequest bool
   859  	}{
   860  		{
   861  			name: "pr_synchronize, no RemoveLabel error",
   862  			event: github.PullRequestEvent{
   863  				Action: github.PullRequestActionSynchronize,
   864  				PullRequest: github.PullRequest{
   865  					Number: 101,
   866  					Base: github.PullRequestBranch{
   867  						Repo: github.Repo{
   868  							Owner: github.User{
   869  								Login: "kubernetes",
   870  							},
   871  							Name: "kubernetes",
   872  						},
   873  					},
   874  					Head: github.PullRequestBranch{
   875  						SHA: SHA,
   876  					},
   877  					Assignees: []github.User{
   878  						{
   879  							Login: "TestReviewer",
   880  						},
   881  					},
   882  				},
   883  			},
   884  			IssueLabelsRemoved: []string{LGTMLabel},
   885  			shouldRequest:      true,
   886  			issueComments: map[int][]github.IssueComment{
   887  				101: {
   888  					{
   889  						Body: removeLGTMLabelNoti,
   890  						User: github.User{Login: fakegithub.Bot},
   891  					},
   892  				},
   893  			},
   894  			expectNoComments: false,
   895  		},
   896  		{
   897  			name: "Sticky LGTM for trusted team members",
   898  			event: github.PullRequestEvent{
   899  				Action: github.PullRequestActionSynchronize,
   900  				PullRequest: github.PullRequest{
   901  					Number: 101,
   902  					Base: github.PullRequestBranch{
   903  						Repo: github.Repo{
   904  							Owner: github.User{
   905  								Login: "kubernetes",
   906  							},
   907  							Name: "kubernetes",
   908  						},
   909  					},
   910  					User: github.User{
   911  						Login: "sig-lead",
   912  					},
   913  					MergeSHA: &SHA,
   914  					Assignees: []github.User{
   915  						{
   916  							Login: "TestReviewer",
   917  						},
   918  					},
   919  				},
   920  			},
   921  			trustedTeam:      "Leads",
   922  			expectNoComments: true,
   923  		},
   924  		{
   925  			name: "LGTM not sticky for trusted user if disabled",
   926  			event: github.PullRequestEvent{
   927  				Action: github.PullRequestActionSynchronize,
   928  				PullRequest: github.PullRequest{
   929  					Number: 101,
   930  					Base: github.PullRequestBranch{
   931  						Repo: github.Repo{
   932  							Owner: github.User{
   933  								Login: "kubernetes",
   934  							},
   935  							Name: "kubernetes",
   936  						},
   937  					},
   938  					User: github.User{
   939  						Login: "sig-lead",
   940  					},
   941  					MergeSHA: &SHA,
   942  					Assignees: []github.User{
   943  						{
   944  							Login: "TestReviewer",
   945  						},
   946  					},
   947  				},
   948  			},
   949  			IssueLabelsRemoved: []string{LGTMLabel},
   950  			shouldRequest:      true,
   951  			issueComments: map[int][]github.IssueComment{
   952  				101: {
   953  					{
   954  						Body: removeLGTMLabelNoti,
   955  						User: github.User{Login: fakegithub.Bot},
   956  					},
   957  				},
   958  			},
   959  			expectNoComments: false,
   960  		},
   961  		{
   962  			name: "LGTM not sticky for non trusted user",
   963  			event: github.PullRequestEvent{
   964  				Action: github.PullRequestActionSynchronize,
   965  				PullRequest: github.PullRequest{
   966  					Number: 101,
   967  					Base: github.PullRequestBranch{
   968  						Repo: github.Repo{
   969  							Owner: github.User{
   970  								Login: "kubernetes",
   971  							},
   972  							Name: "kubernetes",
   973  						},
   974  					},
   975  					User: github.User{
   976  						Login: "sig-lead",
   977  					},
   978  					MergeSHA: &SHA,
   979  					Assignees: []github.User{
   980  						{
   981  							Login: "TestReviewer",
   982  						},
   983  					},
   984  				},
   985  			},
   986  			IssueLabelsRemoved: []string{LGTMLabel},
   987  			shouldRequest:      true,
   988  			issueComments: map[int][]github.IssueComment{
   989  				101: {
   990  					{
   991  						Body: removeLGTMLabelNoti,
   992  						User: github.User{Login: fakegithub.Bot},
   993  					},
   994  				},
   995  			},
   996  			trustedTeam:      "Committers",
   997  			expectNoComments: false,
   998  		},
   999  		{
  1000  			name: "pr_assigned",
  1001  			event: github.PullRequestEvent{
  1002  				Action: "assigned",
  1003  			},
  1004  			expectNoComments: true,
  1005  		},
  1006  		{
  1007  			name: "pr_synchronize, same tree-hash, keep label",
  1008  			event: github.PullRequestEvent{
  1009  				Action: github.PullRequestActionSynchronize,
  1010  				PullRequest: github.PullRequest{
  1011  					Number: 101,
  1012  					Base: github.PullRequestBranch{
  1013  						Repo: github.Repo{
  1014  							Owner: github.User{
  1015  								Login: "kubernetes",
  1016  							},
  1017  							Name: "kubernetes",
  1018  						},
  1019  					},
  1020  					Head: github.PullRequestBranch{
  1021  						SHA: SHA,
  1022  					},
  1023  					Assignees: []github.User{
  1024  						{
  1025  							Login: "TestReviewer",
  1026  						},
  1027  					},
  1028  				},
  1029  			},
  1030  			issueComments: map[int][]github.IssueComment{
  1031  				101: {
  1032  					{
  1033  						Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA),
  1034  						User: github.User{Login: fakegithub.Bot},
  1035  					},
  1036  				},
  1037  			},
  1038  			expectNoComments: true,
  1039  		},
  1040  		{
  1041  			name: "pr_synchronize, same tree-hash, keep label, edited comment",
  1042  			event: github.PullRequestEvent{
  1043  				Action: github.PullRequestActionSynchronize,
  1044  				PullRequest: github.PullRequest{
  1045  					Number: 101,
  1046  					Base: github.PullRequestBranch{
  1047  						Repo: github.Repo{
  1048  							Owner: github.User{
  1049  								Login: "kubernetes",
  1050  							},
  1051  							Name: "kubernetes",
  1052  						},
  1053  					},
  1054  					Head: github.PullRequestBranch{
  1055  						SHA: SHA,
  1056  					},
  1057  					Assignees: []github.User{
  1058  						{
  1059  							Login: "TestReviewer",
  1060  						},
  1061  					},
  1062  				},
  1063  			},
  1064  			IssueLabelsRemoved: []string{LGTMLabel},
  1065  			shouldRequest:      true,
  1066  			issueComments: map[int][]github.IssueComment{
  1067  				101: {
  1068  					{
  1069  						Body:      fmt.Sprintf(addLGTMLabelNotification, treeSHA),
  1070  						User:      github.User{Login: fakegithub.Bot},
  1071  						CreatedAt: time.Date(1981, 2, 21, 12, 30, 0, 0, time.UTC),
  1072  						UpdatedAt: time.Date(1981, 2, 21, 12, 31, 0, 0, time.UTC),
  1073  					},
  1074  				},
  1075  			},
  1076  			expectNoComments: false,
  1077  		},
  1078  		{
  1079  			name: "pr_synchronize, 2 tree-hash comments, keep label",
  1080  			event: github.PullRequestEvent{
  1081  				Action: github.PullRequestActionSynchronize,
  1082  				PullRequest: github.PullRequest{
  1083  					Number: 101,
  1084  					Base: github.PullRequestBranch{
  1085  						Repo: github.Repo{
  1086  							Owner: github.User{
  1087  								Login: "kubernetes",
  1088  							},
  1089  							Name: "kubernetes",
  1090  						},
  1091  					},
  1092  					Head: github.PullRequestBranch{
  1093  						SHA: SHA,
  1094  					},
  1095  					Assignees: []github.User{
  1096  						{
  1097  							Login: "TestReviewer",
  1098  						},
  1099  					},
  1100  				},
  1101  			},
  1102  			issueComments: map[int][]github.IssueComment{
  1103  				101: {
  1104  					{
  1105  						Body: fmt.Sprintf(addLGTMLabelNotification, "older_treeSHA"),
  1106  						User: github.User{Login: fakegithub.Bot},
  1107  					},
  1108  					{
  1109  						Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA),
  1110  						User: github.User{Login: fakegithub.Bot},
  1111  					},
  1112  				},
  1113  			},
  1114  			expectNoComments: true,
  1115  		},
  1116  		{
  1117  			name: "pr_synchronize, no RemoveLabel error, no assignees; no requested reviewers",
  1118  			event: github.PullRequestEvent{
  1119  				Action: github.PullRequestActionSynchronize,
  1120  				PullRequest: github.PullRequest{
  1121  					Number: 101,
  1122  					Base: github.PullRequestBranch{
  1123  						Repo: github.Repo{
  1124  							Owner: github.User{
  1125  								Login: "kubernetes",
  1126  							},
  1127  							Name: "kubernetes",
  1128  						},
  1129  					},
  1130  					Head: github.PullRequestBranch{
  1131  						SHA: SHA,
  1132  					},
  1133  				},
  1134  			},
  1135  			IssueLabelsRemoved: []string{LGTMLabel},
  1136  			shouldRequest:      false,
  1137  			issueComments: map[int][]github.IssueComment{
  1138  				101: {
  1139  					{
  1140  						Body: removeLGTMLabelNoti,
  1141  						User: github.User{Login: fakegithub.Bot},
  1142  					},
  1143  				},
  1144  			},
  1145  			expectNoComments: false,
  1146  		},
  1147  	}
  1148  	for _, c := range cases {
  1149  		t.Run(c.name, func(t *testing.T) {
  1150  			fakeGitHub := fakegithub.NewFakeClient()
  1151  			fakeGitHub.IssueComments = c.issueComments
  1152  			fakeGitHub.PullRequests = map[int]*github.PullRequest{
  1153  				101: {
  1154  					Base: github.PullRequestBranch{
  1155  						Ref: "master",
  1156  					},
  1157  					Head: github.PullRequestBranch{
  1158  						SHA: SHA,
  1159  					},
  1160  				},
  1161  			}
  1162  			fakeGitHub.Commits = make(map[string]github.RepositoryCommit)
  1163  			fakeGitHub.Collaborators = []string{"collab"}
  1164  			fakeGitHub.IssueLabelsAdded = c.IssueLabelsAdded
  1165  			fakeGitHub.IssueLabelsAdded = append(fakeGitHub.IssueLabelsAdded, "kubernetes/kubernetes#101:lgtm")
  1166  			commit := github.RepositoryCommit{}
  1167  			commit.Commit.Tree.SHA = treeSHA
  1168  			fakeGitHub.Commits[SHA] = commit
  1169  			pc := &plugins.Configuration{}
  1170  			pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
  1171  				Repos:          []string{"kubernetes/kubernetes"},
  1172  				StoreTreeHash:  true,
  1173  				StickyLgtmTeam: c.trustedTeam,
  1174  			})
  1175  			err := handlePullRequest(
  1176  				logrus.WithField("plugin", "approve"),
  1177  				fakeGitHub,
  1178  				pc,
  1179  				&c.event,
  1180  			)
  1181  
  1182  			if err != nil && c.err == nil {
  1183  				t.Fatalf("handlePullRequest error: %v", err)
  1184  			}
  1185  
  1186  			if err == nil && c.err != nil {
  1187  				t.Fatalf("handlePullRequest wanted error: %v, got nil", c.err)
  1188  			}
  1189  
  1190  			if got, want := err, c.err; !equality.Semantic.DeepEqual(got, want) {
  1191  				t.Fatalf("handlePullRequest error mismatch: got %v, want %v", got, want)
  1192  			}
  1193  
  1194  			if got, want := len(fakeGitHub.IssueLabelsRemoved), len(c.IssueLabelsRemoved); got != want {
  1195  				t.Logf("IssueLabelsRemoved: got %v, want: %v", fakeGitHub.IssueLabelsRemoved, c.IssueLabelsRemoved)
  1196  				t.Fatalf("IssueLabelsRemoved length mismatch: got %d, want %d", got, want)
  1197  			}
  1198  
  1199  			if got, want := fakeGitHub.IssueComments, c.issueComments; !equality.Semantic.DeepEqual(got, want) {
  1200  				t.Fatalf("LGTM revmoved notifications mismatch: got %v, want %v", got, want)
  1201  			}
  1202  			if c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) > 0 {
  1203  				t.Fatalf("expected no comments but got %v", fakeGitHub.IssueCommentsAdded)
  1204  			}
  1205  			if !c.expectNoComments && len(fakeGitHub.IssueCommentsAdded) == 0 {
  1206  				t.Fatalf("expected comments but got none")
  1207  			}
  1208  			if c.shouldRequest && len(fakeGitHub.ReviewersRequested) == 0 {
  1209  				t.Error("should have re-requested reviewers")
  1210  			}
  1211  			if !c.shouldRequest && len(fakeGitHub.ReviewersRequested) > 0 {
  1212  				t.Errorf("should not have re-requested reviewers, but requested these reviewers %v", fakeGitHub.ReviewersRequested)
  1213  			}
  1214  		})
  1215  	}
  1216  }
  1217  
  1218  func TestAddTreeHashComment(t *testing.T) {
  1219  	cases := []struct {
  1220  		name          string
  1221  		author        string
  1222  		trustedTeam   string
  1223  		expectTreeSha bool
  1224  	}{
  1225  		{
  1226  			name:          "Tree SHA added",
  1227  			author:        "Bob",
  1228  			expectTreeSha: true,
  1229  		},
  1230  		{
  1231  			name:          "Tree SHA if sticky lgtm off",
  1232  			author:        "sig-lead",
  1233  			expectTreeSha: true,
  1234  		},
  1235  		{
  1236  			name:          "No Tree SHA if sticky lgtm",
  1237  			author:        "sig-lead",
  1238  			trustedTeam:   "Leads",
  1239  			expectTreeSha: false,
  1240  		},
  1241  	}
  1242  
  1243  	for _, c := range cases {
  1244  		t.Run(c.name, func(t *testing.T) {
  1245  
  1246  			SHA := "0bd3ed50c88cd53a09316bf7a298f900e9371652"
  1247  			treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
  1248  			pc := &plugins.Configuration{}
  1249  			pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
  1250  				Repos:          []string{"kubernetes/kubernetes"},
  1251  				StoreTreeHash:  true,
  1252  				StickyLgtmTeam: c.trustedTeam,
  1253  			})
  1254  			rc := reviewCtx{
  1255  				author:      "collab1",
  1256  				issueAuthor: c.author,
  1257  				repo: github.Repo{
  1258  					Owner: github.User{
  1259  						Login: "kubernetes",
  1260  					},
  1261  					Name: "kubernetes",
  1262  				},
  1263  				number: 101,
  1264  				body:   "/lgtm",
  1265  			}
  1266  			fc := fakegithub.NewFakeClient()
  1267  			fc.Commits = make(map[string]github.RepositoryCommit)
  1268  			fc.IssueComments = map[int][]github.IssueComment{}
  1269  			fc.PullRequests = map[int]*github.PullRequest{
  1270  				101: {
  1271  					Base: github.PullRequestBranch{
  1272  						Ref: "master",
  1273  					},
  1274  					Head: github.PullRequestBranch{
  1275  						SHA: SHA,
  1276  					},
  1277  				},
  1278  			}
  1279  			fc.Collaborators = []string{"collab1", "collab2"}
  1280  			commit := github.RepositoryCommit{}
  1281  			commit.Commit.Tree.SHA = treeSHA
  1282  			fc.Commits[SHA] = commit
  1283  			handle(true, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), &fakePruner{})
  1284  			found := false
  1285  			for _, body := range fc.IssueCommentsAdded {
  1286  				if addLGTMLabelNotificationRe.MatchString(body) {
  1287  					found = true
  1288  					break
  1289  				}
  1290  			}
  1291  			if c.expectTreeSha {
  1292  				if !found {
  1293  					t.Fatalf("expected tree_hash comment but got none")
  1294  				}
  1295  			} else {
  1296  				if found {
  1297  					t.Fatalf("expected no tree_hash comment but got one")
  1298  				}
  1299  			}
  1300  		})
  1301  	}
  1302  }
  1303  
  1304  func TestRemoveTreeHashComment(t *testing.T) {
  1305  	treeSHA := "6dcb09b5b57875f334f61aebed695e2e4193db5e"
  1306  	pc := &plugins.Configuration{}
  1307  	pc.Lgtm = append(pc.Lgtm, plugins.Lgtm{
  1308  		Repos:         []string{"kubernetes/kubernetes"},
  1309  		StoreTreeHash: true,
  1310  	})
  1311  	rc := reviewCtx{
  1312  		author:      "collab1",
  1313  		issueAuthor: "bob",
  1314  		repo: github.Repo{
  1315  			Owner: github.User{
  1316  				Login: "kubernetes",
  1317  			},
  1318  			Name: "kubernetes",
  1319  		},
  1320  		assignees: []github.User{{Login: "alice"}},
  1321  		number:    101,
  1322  		body:      "/lgtm cancel",
  1323  	}
  1324  	fc := fakegithub.NewFakeClient()
  1325  	fc.IssueComments = map[int][]github.IssueComment{
  1326  		101: {
  1327  			{
  1328  				Body: fmt.Sprintf(addLGTMLabelNotification, treeSHA),
  1329  				User: github.User{Login: fakegithub.Bot},
  1330  			},
  1331  		},
  1332  	}
  1333  	fc.Collaborators = []string{"collab1", "collab2"}
  1334  	fc.IssueLabelsAdded = []string{"kubernetes/kubernetes#101:" + LGTMLabel}
  1335  	fp := &fakePruner{
  1336  		GitHubClient:  fc,
  1337  		IssueComments: fc.IssueComments[101],
  1338  	}
  1339  	handle(false, pc, &fakeOwnersClient{}, rc, fc, logrus.WithField("plugin", PluginName), fp)
  1340  	found := false
  1341  	for _, body := range fc.IssueCommentsDeleted {
  1342  		if addLGTMLabelNotificationRe.MatchString(body) {
  1343  			found = true
  1344  			break
  1345  		}
  1346  	}
  1347  	if !found {
  1348  		t.Fatalf("expected deleted tree_hash comment but got none")
  1349  	}
  1350  }
  1351  
  1352  func TestHelpProvider(t *testing.T) {
  1353  	enabledRepos := []config.OrgRepo{
  1354  		{Org: "org1", Repo: "repo"},
  1355  		{Org: "org2", Repo: "repo"},
  1356  	}
  1357  	cases := []struct {
  1358  		name               string
  1359  		config             *plugins.Configuration
  1360  		enabledRepos       []config.OrgRepo
  1361  		err                bool
  1362  		configInfoIncludes []string
  1363  		configInfoExcludes []string
  1364  	}{
  1365  		{
  1366  			name:               "Empty config",
  1367  			config:             &plugins.Configuration{},
  1368  			enabledRepos:       enabledRepos,
  1369  			configInfoExcludes: []string{configInfoReviewActsAsLgtm, configInfoStoreTreeHash, configInfoStickyLgtmTeam("team1")},
  1370  		},
  1371  		{
  1372  			name: "StoreTreeHash enabled",
  1373  			config: &plugins.Configuration{
  1374  				Lgtm: []plugins.Lgtm{
  1375  					{
  1376  						Repos:         []string{"org2/repo"},
  1377  						StoreTreeHash: true,
  1378  					},
  1379  				},
  1380  			},
  1381  			enabledRepos:       enabledRepos,
  1382  			configInfoExcludes: []string{configInfoReviewActsAsLgtm, configInfoStickyLgtmTeam("team1")},
  1383  			configInfoIncludes: []string{configInfoStoreTreeHash},
  1384  		},
  1385  		{
  1386  			name: "All configs enabled",
  1387  			config: &plugins.Configuration{
  1388  				Lgtm: []plugins.Lgtm{
  1389  					{
  1390  						Repos:            []string{"org2/repo"},
  1391  						ReviewActsAsLgtm: true,
  1392  						StoreTreeHash:    true,
  1393  						StickyLgtmTeam:   "team1",
  1394  					},
  1395  				},
  1396  			},
  1397  			enabledRepos:       enabledRepos,
  1398  			configInfoIncludes: []string{configInfoReviewActsAsLgtm, configInfoStoreTreeHash, configInfoStickyLgtmTeam("team1")},
  1399  		},
  1400  	}
  1401  	for _, c := range cases {
  1402  		t.Run(c.name, func(t *testing.T) {
  1403  			pluginHelp, err := helpProvider(c.config, c.enabledRepos)
  1404  			if err != nil && !c.err {
  1405  				t.Fatalf("helpProvider error: %v", err)
  1406  			}
  1407  			for _, msg := range c.configInfoExcludes {
  1408  				if strings.Contains(pluginHelp.Config["org2/repo"], msg) {
  1409  					t.Fatalf("helpProvider.Config error mismatch: got %v, but didn't want it", msg)
  1410  				}
  1411  			}
  1412  			for _, msg := range c.configInfoIncludes {
  1413  				if !strings.Contains(pluginHelp.Config["org2/repo"], msg) {
  1414  					t.Fatalf("helpProvider.Config error mismatch: didn't get %v, but wanted it", msg)
  1415  				}
  1416  			}
  1417  		})
  1418  	}
  1419  }