github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/verify-owners/verify-owners_test.go (about)

     1  /*
     2  Copyright 2018 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 verifyowners
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/sirupsen/logrus"
    30  	"sigs.k8s.io/yaml"
    31  
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  
    34  	"sigs.k8s.io/prow/pkg/config"
    35  	"sigs.k8s.io/prow/pkg/git/localgit"
    36  	"sigs.k8s.io/prow/pkg/github"
    37  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    38  	"sigs.k8s.io/prow/pkg/labels"
    39  	"sigs.k8s.io/prow/pkg/layeredsets"
    40  	"sigs.k8s.io/prow/pkg/pluginhelp"
    41  	"sigs.k8s.io/prow/pkg/plugins"
    42  	"sigs.k8s.io/prow/pkg/plugins/ownersconfig"
    43  	"sigs.k8s.io/prow/pkg/repoowners"
    44  )
    45  
    46  var defaultBranch = localgit.DefaultBranch("")
    47  
    48  var ownerFiles = map[string][]byte{
    49  	"emptyApprovers": []byte(`approvers:
    50  reviewers:
    51  - alice
    52  - bob
    53  labels:
    54  - label1
    55  `),
    56  	"emptyApproversFilters": []byte(`filters:
    57    ".*":
    58      approvers:
    59      reviewers:
    60      - alice
    61      - bob
    62      labels:
    63      - label1
    64  `),
    65  	"invalidSyntax": []byte(`approvers:
    66  - jdoe
    67  reviewers:
    68  - alice
    69  - bob
    70  labels
    71  - label1
    72  `),
    73  	"invalidSyntaxFilters": []byte(`filters:
    74    ".*":
    75      approvers:
    76      - jdoe
    77      reviewers:
    78      - alice
    79      - bob
    80      labels
    81      - label1
    82  `),
    83  	"invalidLabels": []byte(`approvers:
    84  - jdoe
    85  reviewers:
    86  - alice
    87  - bob
    88  labels:
    89  - lgtm
    90  `),
    91  	"invalidLabelsFilters": []byte(`filters:
    92    ".*":
    93      approvers:
    94      - jdoe
    95      reviewers:
    96      - alice
    97      - bob
    98      labels:
    99      - lgtm
   100  `),
   101  	"noApprovers": []byte(`reviewers:
   102  - alice
   103  - bob
   104  labels:
   105  - label1
   106  `),
   107  	"noApproversFilters": []byte(`filters:
   108    ".*":
   109      reviewers:
   110      - alice
   111      - bob
   112      labels:
   113      - label1
   114  `),
   115  	"valid": []byte(`approvers:
   116  - jdoe
   117  reviewers:
   118  - alice
   119  - bob
   120  labels:
   121  - label1
   122  `),
   123  	"validFilters": []byte(`filters:
   124    ".*":
   125      approvers:
   126      - jdoe
   127      reviewers:
   128      - alice
   129      - bob
   130      labels:
   131      - label1
   132  `),
   133  	"referencesToBeAddedAlias": []byte(`approvers:
   134  - not-yet-existing-alias
   135  `),
   136  }
   137  
   138  var patches = map[string]string{
   139  	"referencesToBeAddedAlias": `
   140  + - not-yet-existing-alias
   141  `,
   142  }
   143  
   144  var ownerAliasesFiles = map[string][]byte{
   145  	"toBeAddedAlias": []byte(`aliases:
   146    not-yet-existing-alias:
   147    - bob
   148  `),
   149  }
   150  
   151  func IssueLabelsContain(arr []string, str string) bool {
   152  	for _, a := range arr {
   153  		// IssueLabels format is owner/repo#number:label
   154  		b := strings.Split(a, ":")
   155  		if b[len(b)-1] == str {
   156  			return true
   157  		}
   158  	}
   159  	return false
   160  }
   161  
   162  func ownersFilePatch(files []string, ownersFile string, useEmptyPatch bool) map[string]string {
   163  	changes := emptyPatch(files)
   164  	if useEmptyPatch {
   165  		return changes
   166  	}
   167  	for _, file := range files {
   168  		if strings.Contains(file, "OWNERS") && !strings.Contains(file, "OWNERS_ALIASES") {
   169  			changes[file] = patches[ownersFile]
   170  		}
   171  	}
   172  	return changes
   173  }
   174  
   175  func emptyPatch(files []string) map[string]string {
   176  	changes := make(map[string]string, len(files))
   177  	for _, f := range files {
   178  		changes[f] = ""
   179  	}
   180  	return changes
   181  }
   182  
   183  func newFakeGitHubClient(changed map[string]string, removed []string, pr int) *fakegithub.FakeClient {
   184  	var changes []github.PullRequestChange
   185  	for file, patch := range changed {
   186  		changes = append(changes, github.PullRequestChange{Filename: file, Patch: patch})
   187  	}
   188  	for _, file := range removed {
   189  		changes = append(changes, github.PullRequestChange{Filename: file, Status: github.PullRequestFileRemoved})
   190  	}
   191  	fgc := fakegithub.NewFakeClient()
   192  	fgc.PullRequestChanges = map[int][]github.PullRequestChange{pr: changes}
   193  	fgc.Reviews = map[int][]github.Review{}
   194  	fgc.Collaborators = []string{"alice", "bob", "jdoe"}
   195  	fgc.IssueComments = map[int][]github.IssueComment{}
   196  	return fgc
   197  }
   198  
   199  type fakePruner struct{}
   200  
   201  func (fp *fakePruner) PruneComments(shouldPrune func(github.IssueComment) bool) {}
   202  
   203  type fakeRepoownersClient struct {
   204  	foc *fakeOwnersClient
   205  }
   206  
   207  func (froc fakeRepoownersClient) LoadRepoOwners(org, repo, base string) (repoowners.RepoOwner, error) {
   208  	return froc.foc, nil
   209  }
   210  
   211  type fakeOwnersClient struct {
   212  	owners            map[string]string
   213  	approvers         map[string]layeredsets.String
   214  	leafApprovers     map[string]sets.Set[string]
   215  	reviewers         map[string]layeredsets.String
   216  	requiredReviewers map[string]sets.Set[string]
   217  	leafReviewers     map[string]sets.Set[string]
   218  	dirIgnorelist     []*regexp.Regexp
   219  }
   220  
   221  func (foc *fakeOwnersClient) AllApprovers() sets.Set[string] {
   222  	return sets.Set[string]{}
   223  }
   224  
   225  func (foc *fakeOwnersClient) AllOwners() sets.Set[string] {
   226  	return sets.Set[string]{}
   227  }
   228  
   229  func (foc *fakeOwnersClient) AllReviewers() sets.Set[string] {
   230  	return sets.Set[string]{}
   231  }
   232  
   233  func (foc *fakeOwnersClient) Filenames() ownersconfig.Filenames {
   234  	return ownersconfig.FakeFilenames
   235  }
   236  
   237  func (foc *fakeOwnersClient) Approvers(path string) layeredsets.String {
   238  	return foc.approvers[path]
   239  }
   240  
   241  func (foc *fakeOwnersClient) LeafApprovers(path string) sets.Set[string] {
   242  	return foc.leafApprovers[path]
   243  }
   244  
   245  func (foc *fakeOwnersClient) FindApproverOwnersForFile(path string) string {
   246  	return foc.owners[path]
   247  }
   248  
   249  func (foc *fakeOwnersClient) Reviewers(path string) layeredsets.String {
   250  	return foc.reviewers[path]
   251  }
   252  
   253  func (foc *fakeOwnersClient) RequiredReviewers(path string) sets.Set[string] {
   254  	return foc.requiredReviewers[path]
   255  }
   256  
   257  func (foc *fakeOwnersClient) LeafReviewers(path string) sets.Set[string] {
   258  	return foc.leafReviewers[path]
   259  }
   260  
   261  func (foc *fakeOwnersClient) FindReviewersOwnersForFile(path string) string {
   262  	return foc.owners[path]
   263  }
   264  
   265  func (foc *fakeOwnersClient) FindLabelsForFile(path string) sets.Set[string] {
   266  	return sets.Set[string]{}
   267  }
   268  
   269  func (foc *fakeOwnersClient) IsNoParentOwners(path string) bool {
   270  	return false
   271  }
   272  
   273  func (foc *fakeOwnersClient) IsAutoApproveUnownedSubfolders(path string) bool {
   274  	return false
   275  }
   276  
   277  func (foc *fakeOwnersClient) ParseSimpleConfig(path string) (repoowners.SimpleConfig, error) {
   278  	dir := filepath.Dir(path)
   279  	for _, re := range foc.dirIgnorelist {
   280  		if re.MatchString(dir) {
   281  			return repoowners.SimpleConfig{}, filepath.SkipDir
   282  		}
   283  	}
   284  
   285  	b, err := os.ReadFile(path)
   286  	if err != nil {
   287  		return repoowners.SimpleConfig{}, err
   288  	}
   289  	full := new(repoowners.SimpleConfig)
   290  	err = yaml.Unmarshal(b, full)
   291  	return *full, err
   292  }
   293  
   294  func (foc *fakeOwnersClient) ParseFullConfig(path string) (repoowners.FullConfig, error) {
   295  	dir := filepath.Dir(path)
   296  	for _, re := range foc.dirIgnorelist {
   297  		if re.MatchString(dir) {
   298  			return repoowners.FullConfig{}, filepath.SkipDir
   299  		}
   300  	}
   301  
   302  	b, err := os.ReadFile(path)
   303  	if err != nil {
   304  		return repoowners.FullConfig{}, err
   305  	}
   306  	full := new(repoowners.FullConfig)
   307  	err = yaml.Unmarshal(b, full)
   308  	return *full, err
   309  }
   310  
   311  func (foc *fakeOwnersClient) TopLevelApprovers() sets.Set[string] {
   312  	return sets.Set[string]{}
   313  }
   314  
   315  func makeFakeRepoOwnersClient() fakeRepoownersClient {
   316  	return fakeRepoownersClient{
   317  		foc: &fakeOwnersClient{},
   318  	}
   319  }
   320  
   321  func addFilesToRepo(lg *localgit.LocalGit, paths []string, ownersFile string) error {
   322  	origFiles := map[string][]byte{}
   323  	for _, file := range paths {
   324  		if strings.Contains(file, "OWNERS_ALIASES") {
   325  			origFiles[file] = ownerAliasesFiles[ownersFile]
   326  		} else if strings.Contains(file, "OWNERS") {
   327  			origFiles[file] = ownerFiles[ownersFile]
   328  		} else {
   329  			origFiles[file] = []byte("foo")
   330  		}
   331  	}
   332  	return lg.AddCommit("org", "repo", origFiles)
   333  }
   334  
   335  func TestHandleV2(t *testing.T) {
   336  	testHandle(localgit.NewV2, t)
   337  }
   338  
   339  func testHandle(clients localgit.Clients, t *testing.T) {
   340  	var tests = []struct {
   341  		name                string
   342  		filesChanged        []string
   343  		usePatch            bool
   344  		filesRemoved        []string
   345  		ownersFile          string
   346  		filesChangedAfterPR []string
   347  		addedContent        string
   348  		shouldLabel         bool
   349  	}{
   350  		{
   351  			name:         "no OWNERS file",
   352  			filesChanged: []string{"a.go", "b.go"},
   353  			ownersFile:   "valid",
   354  			shouldLabel:  false,
   355  		},
   356  		{
   357  			name:         "no OWNERS file with filters",
   358  			filesChanged: []string{"a.go", "b.go"},
   359  			ownersFile:   "validFilters",
   360  			shouldLabel:  false,
   361  		},
   362  		{
   363  			name:         "good OWNERS file",
   364  			filesChanged: []string{"OWNERS", "b.go"},
   365  			ownersFile:   "valid",
   366  			shouldLabel:  false,
   367  		},
   368  		{
   369  			name:         "good OWNERS file with filters",
   370  			filesChanged: []string{"OWNERS", "b.go"},
   371  			ownersFile:   "validFilters",
   372  			shouldLabel:  false,
   373  		},
   374  		{
   375  			name:         "invalid syntax OWNERS file",
   376  			filesChanged: []string{"OWNERS", "b.go"},
   377  			ownersFile:   "invalidSyntax",
   378  			shouldLabel:  true,
   379  		},
   380  		{
   381  			name:         "invalid syntax OWNERS file with filters",
   382  			filesChanged: []string{"OWNERS", "b.go"},
   383  			ownersFile:   "invalidSyntaxFilters",
   384  			shouldLabel:  true,
   385  		},
   386  		{
   387  			name:         "forbidden labels in OWNERS file",
   388  			filesChanged: []string{"OWNERS", "b.go"},
   389  			ownersFile:   "invalidLabels",
   390  			shouldLabel:  true,
   391  		},
   392  		{
   393  			name:         "forbidden labels in OWNERS file with filters",
   394  			filesChanged: []string{"OWNERS", "b.go"},
   395  			ownersFile:   "invalidLabelsFilters",
   396  			shouldLabel:  true,
   397  		},
   398  		{
   399  			name:         "empty approvers in OWNERS file",
   400  			filesChanged: []string{"OWNERS", "b.go"},
   401  			ownersFile:   "emptyApprovers",
   402  			shouldLabel:  true,
   403  		},
   404  		{
   405  			name:         "empty approvers in OWNERS file with filters",
   406  			filesChanged: []string{"OWNERS", "b.go"},
   407  			ownersFile:   "emptyApproversFilters",
   408  			shouldLabel:  true,
   409  		},
   410  		{
   411  			name:         "no approvers in OWNERS file",
   412  			filesChanged: []string{"OWNERS", "b.go"},
   413  			ownersFile:   "noApprovers",
   414  			shouldLabel:  true,
   415  		},
   416  		{
   417  			name:         "no approvers in OWNERS file with filters",
   418  			filesChanged: []string{"OWNERS", "b.go"},
   419  			ownersFile:   "noApproversFilters",
   420  			shouldLabel:  true,
   421  		},
   422  		{
   423  			name:         "no approvers in pkg/OWNERS file",
   424  			filesChanged: []string{"pkg/OWNERS", "b.go"},
   425  			ownersFile:   "noApprovers",
   426  			shouldLabel:  false,
   427  		},
   428  		{
   429  			name:         "no approvers in pkg/OWNERS file with filters",
   430  			filesChanged: []string{"pkg/OWNERS", "b.go"},
   431  			ownersFile:   "noApproversFilters",
   432  			shouldLabel:  false,
   433  		},
   434  		{
   435  			name:         "OWNERS file was removed",
   436  			filesRemoved: []string{"pkg/OWNERS"},
   437  			ownersFile:   "valid",
   438  			shouldLabel:  false,
   439  		},
   440  		{
   441  			name:         "OWNERS_ALIASES file was removed",
   442  			filesRemoved: []string{"OWNERS_ALIASES"},
   443  			shouldLabel:  false,
   444  		},
   445  		{
   446  			name:                "new alias added after a PR references that alias",
   447  			filesChanged:        []string{"OWNERS"},
   448  			usePatch:            true,
   449  			ownersFile:          "referencesToBeAddedAlias",
   450  			filesChangedAfterPR: []string{"OWNERS_ALIASES"},
   451  			addedContent:        "toBeAddedAlias",
   452  			shouldLabel:         false,
   453  		},
   454  	}
   455  	lg, c, err := clients()
   456  	if err != nil {
   457  		t.Fatalf("Making localgit: %v", err)
   458  	}
   459  	defer func() {
   460  		if err := lg.Clean(); err != nil {
   461  			t.Errorf("Cleaning up localgit: %v", err)
   462  		}
   463  		if err := c.Clean(); err != nil {
   464  			t.Errorf("Cleaning up client: %v", err)
   465  		}
   466  	}()
   467  	if err := lg.MakeFakeRepo("org", "repo"); err != nil {
   468  		t.Fatalf("Making fake repo: %v", err)
   469  	}
   470  	for i, test := range tests {
   471  		t.Run(test.name, func(t *testing.T) {
   472  			pr := i + 1
   473  			// make sure we're on master before branching
   474  			if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
   475  				t.Fatalf("Switching to master branch: %v", err)
   476  			}
   477  			if len(test.filesRemoved) > 0 {
   478  				if err := addFilesToRepo(lg, test.filesRemoved, test.ownersFile); err != nil {
   479  					t.Fatalf("Adding base commit: %v", err)
   480  				}
   481  			}
   482  
   483  			if err := lg.CheckoutNewBranch("org", "repo", fmt.Sprintf("pull/%d/head", pr)); err != nil {
   484  				t.Fatalf("Checking out pull branch: %v", err)
   485  			}
   486  
   487  			if len(test.filesChanged) > 0 {
   488  				if err := addFilesToRepo(lg, test.filesChanged, test.ownersFile); err != nil {
   489  					t.Fatalf("Adding PR commit: %v", err)
   490  				}
   491  			}
   492  			if len(test.filesRemoved) > 0 {
   493  				if err := lg.RmCommit("org", "repo", test.filesRemoved); err != nil {
   494  					t.Fatalf("Adding PR commit (removing files): %v", err)
   495  				}
   496  			}
   497  
   498  			sha, err := lg.RevParse("org", "repo", "HEAD")
   499  			if err != nil {
   500  				t.Fatalf("Getting commit SHA: %v", err)
   501  			}
   502  			if len(test.filesChangedAfterPR) > 0 {
   503  				if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
   504  					t.Fatalf("Switching to master branch: %v", err)
   505  				}
   506  				if err := addFilesToRepo(lg, test.filesChangedAfterPR, test.addedContent); err != nil {
   507  					t.Fatalf("Adding commit to master: %v", err)
   508  				}
   509  			}
   510  			pre := &github.PullRequestEvent{
   511  				PullRequest: github.PullRequest{
   512  					User: github.User{Login: "author"},
   513  					Base: github.PullRequestBranch{
   514  						Ref: defaultBranch,
   515  					},
   516  					Head: github.PullRequestBranch{
   517  						SHA: sha,
   518  					},
   519  				},
   520  			}
   521  			changes := ownersFilePatch(test.filesChanged, test.ownersFile, !test.usePatch)
   522  			fghc := newFakeGitHubClient(changes, test.filesRemoved, pr)
   523  			fghc.PullRequests = map[int]*github.PullRequest{}
   524  			fghc.PullRequests[pr] = &github.PullRequest{
   525  				Base: github.PullRequestBranch{
   526  					Ref: defaultBranch,
   527  				},
   528  			}
   529  
   530  			prInfo := info{
   531  				org:          "org",
   532  				repo:         "repo",
   533  				repoFullName: "org/repo",
   534  				number:       pr,
   535  			}
   536  
   537  			if err := handle(fghc, c, makeFakeRepoOwnersClient(), logrus.WithField("plugin", PluginName), &pre.PullRequest, prInfo, []string{labels.Approved, labels.LGTM}, plugins.Trigger{}, false, &fakePruner{}, ownersconfig.FakeResolver); err != nil {
   538  				t.Fatalf("Handle PR: %v", err)
   539  			}
   540  			if !test.shouldLabel && IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
   541  				t.Fatalf("%s: didn't expect label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
   542  			} else if test.shouldLabel && !IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
   543  				t.Fatalf("%s: expected label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
   544  			}
   545  		})
   546  	}
   547  }
   548  
   549  func TestParseOwnersFileV2(t *testing.T) {
   550  	testParseOwnersFile(localgit.NewV2, t)
   551  }
   552  
   553  func testParseOwnersFile(clients localgit.Clients, t *testing.T) {
   554  	tests := []struct {
   555  		name     string
   556  		document []byte
   557  		patch    string
   558  		errLine  int
   559  	}{
   560  		{
   561  			name:     "emptyApprovers",
   562  			document: ownerFiles["emptyApprovers"],
   563  			errLine:  1,
   564  		},
   565  		{
   566  			name:     "emptyApproversFilters",
   567  			document: ownerFiles["emptyApproversFilters"],
   568  			errLine:  1,
   569  		},
   570  		{
   571  			name:     "invalidSyntax",
   572  			document: ownerFiles["invalidSyntax"],
   573  			errLine:  7,
   574  		},
   575  		{
   576  			name:     "invalidSyntaxFilters",
   577  			document: ownerFiles["invalidSyntaxFilters"],
   578  			errLine:  9,
   579  		},
   580  		{
   581  			name:     "invalidSyntax edit",
   582  			document: ownerFiles["invalidSyntax"],
   583  			patch: `@@ -3,6 +3,6 @@ approvers:
   584   reviewers:
   585   - alice
   586   - bob
   587  -labels:
   588  +labels
   589   - label1
   590   `,
   591  			errLine: 1,
   592  		},
   593  		{
   594  			name:     "noApprovers",
   595  			document: ownerFiles["noApprovers"],
   596  			errLine:  1,
   597  		},
   598  		{
   599  			name:     "noApproversFilters",
   600  			document: ownerFiles["noApproversFilters"],
   601  			errLine:  1,
   602  		},
   603  		{
   604  			name:     "valid",
   605  			document: ownerFiles["valid"],
   606  		},
   607  		{
   608  			name:     "validFilters",
   609  			document: ownerFiles["validFilters"],
   610  		},
   611  	}
   612  
   613  	for i, test := range tests {
   614  		t.Run(test.name, func(t *testing.T) {
   615  			pr := i + 1
   616  			lg, c, err := clients()
   617  			if err != nil {
   618  				t.Fatalf("Making localgit: %v", err)
   619  			}
   620  			defer func() {
   621  				if err := lg.Clean(); err != nil {
   622  					t.Errorf("Cleaning up localgit: %v", err)
   623  				}
   624  				if err := c.Clean(); err != nil {
   625  					t.Errorf("Cleaning up client: %v", err)
   626  				}
   627  			}()
   628  			if err := lg.MakeFakeRepo("org", "repo"); err != nil {
   629  				t.Fatalf("Making fake repo: %v", err)
   630  			}
   631  			// make sure we're on master before branching
   632  			if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
   633  				t.Fatalf("Switching to master branch: %v", err)
   634  			}
   635  			if err := lg.CheckoutNewBranch("org", "repo", fmt.Sprintf("pull/%d/head", pr)); err != nil {
   636  				t.Fatalf("Checking out pull branch: %v", err)
   637  			}
   638  			pullFiles := map[string][]byte{}
   639  			pullFiles["OWNERS"] = test.document
   640  			if err := lg.AddCommit("org", "repo", pullFiles); err != nil {
   641  				t.Fatalf("Adding PR commit: %v", err)
   642  			}
   643  
   644  			if test.patch == "" {
   645  				test.patch = makePatch(test.document)
   646  			}
   647  			change := github.PullRequestChange{
   648  				Filename: "OWNERS",
   649  				Patch:    test.patch,
   650  			}
   651  
   652  			r, err := c.ClientFor("org", "repo")
   653  			if err != nil {
   654  				t.Fatalf("error cloning the repo: %v", err)
   655  			}
   656  			defer func() {
   657  				if err := r.Clean(); err != nil {
   658  					t.Fatalf("error cleaning up repo: %v", err)
   659  				}
   660  			}()
   661  
   662  			path := filepath.Join(r.Directory(), "OWNERS")
   663  			message, _ := parseOwnersFile(&fakeOwnersClient{}, path, change, &logrus.Entry{}, []string{}, ownersconfig.FakeFilenames)
   664  			if message != nil {
   665  				if test.errLine == 0 {
   666  					t.Errorf("%s: expected no error, got one: %s", test.name, message.message)
   667  				}
   668  				if message.line != test.errLine {
   669  					t.Errorf("%s: wrong line for message, expected %d, got %d", test.name, test.errLine, message.line)
   670  				}
   671  			} else if test.errLine != 0 {
   672  				t.Errorf("%s: expected an error, got none", test.name)
   673  			}
   674  		})
   675  	}
   676  }
   677  
   678  func makePatch(b []byte) string {
   679  	p := bytes.Replace(b, []byte{'\n'}, []byte{'\n', '+'}, -1)
   680  	nbLines := bytes.Count(p, []byte{'+'}) + 1
   681  	return fmt.Sprintf("@@ -0,0 +1,%d @@\n+%s", nbLines, p)
   682  }
   683  
   684  func TestHelpProvider(t *testing.T) {
   685  	enabledRepos := []config.OrgRepo{
   686  		{Org: "org1", Repo: "repo"},
   687  		{Org: "org2", Repo: "repo"},
   688  	}
   689  	cases := []struct {
   690  		name         string
   691  		config       *plugins.Configuration
   692  		enabledRepos []config.OrgRepo
   693  		err          bool
   694  		expected     *pluginhelp.PluginHelp
   695  	}{
   696  		{
   697  			name:         "Empty config",
   698  			config:       &plugins.Configuration{},
   699  			enabledRepos: enabledRepos,
   700  			expected: &pluginhelp.PluginHelp{
   701  				Description: "The verify-owners plugin validates OWNERS and OWNERS_ALIASES files (by default) and ensures that they always contain collaborators of the org, if they are modified in a PR. On validation failure it automatically adds the 'do-not-merge/invalid-owners-file' label to the PR, and a review comment on the incriminating file(s). Per-repo configuration for filenames is possible.",
   702  				Config: map[string]string{
   703  					"default": "OWNERS and OWNERS_ALIASES files are validated.",
   704  				},
   705  				Commands: []pluginhelp.Command{{
   706  					Usage:       "/verify-owners",
   707  					Description: "do-not-merge/invalid-owners-file",
   708  					Examples:    []string{"/verify-owners"},
   709  					WhoCanUse:   "Anyone",
   710  				}},
   711  			},
   712  		},
   713  		{
   714  			name: "ReviewerCount specified",
   715  			config: &plugins.Configuration{
   716  				Owners: plugins.Owners{
   717  					LabelsDenyList: []string{"label1", "label2"},
   718  				},
   719  			},
   720  			enabledRepos: enabledRepos,
   721  			expected: &pluginhelp.PluginHelp{
   722  				Description: "The verify-owners plugin validates OWNERS and OWNERS_ALIASES files (by default) and ensures that they always contain collaborators of the org, if they are modified in a PR. On validation failure it automatically adds the 'do-not-merge/invalid-owners-file' label to the PR, and a review comment on the incriminating file(s). Per-repo configuration for filenames is possible.",
   723  				Config: map[string]string{
   724  					"default": "OWNERS and OWNERS_ALIASES files are validated. The verify-owners plugin will complain if OWNERS files contain any of the following banned labels: label1, label2.",
   725  				},
   726  				Commands: []pluginhelp.Command{{
   727  					Usage:       "/verify-owners",
   728  					Description: "do-not-merge/invalid-owners-file",
   729  					Examples:    []string{"/verify-owners"},
   730  					WhoCanUse:   "Anyone",
   731  				}},
   732  			},
   733  		},
   734  	}
   735  	for _, c := range cases {
   736  		t.Run(c.name, func(t *testing.T) {
   737  			pluginHelp, err := helpProvider(c.config, c.enabledRepos)
   738  			if err != nil && !c.err {
   739  				t.Fatalf("helpProvider error: %v", err)
   740  			}
   741  			if diff := cmp.Diff(pluginHelp, c.expected); diff != "" {
   742  				t.Errorf("%s: did not get correct help: %v", c.name, diff)
   743  			}
   744  		})
   745  	}
   746  }
   747  
   748  var ownersFiles = map[string][]byte{
   749  	"nonCollaborators": []byte(`reviewers:
   750  - phippy
   751  - alice
   752  approvers:
   753  - zee
   754  - bob
   755  `),
   756  	"collaboratorsWithAliases": []byte(`reviewers:
   757  - foo-reviewers
   758  - alice
   759  approvers:
   760  - bob
   761  `),
   762  	"nonCollaboratorsWithAliases": []byte(`reviewers:
   763  - foo-reviewers
   764  - alice
   765  - goldie
   766  approvers:
   767  - bob
   768  `),
   769  }
   770  
   771  var ownersPatch = map[string]string{
   772  	"collaboratorAdditions": `@@ -1,4 +1,6 @@
   773   reviewers:
   774   - phippy
   775  +- alice
   776   approvers:
   777   - zee
   778  +- bob
   779  `,
   780  	"collaboratorRemovals": `@@ -1,8 +1,6 @@
   781   reviewers:
   782   - phippy
   783   - alice
   784  -- bob
   785   approvers:
   786   - zee
   787   - bob
   788  -- alice
   789  `,
   790  	"nonCollaboratorAdditions": `@@ -1,4 +1,6 @@
   791   reviewers:
   792  +- phippy
   793   - alice
   794   approvers:
   795  +- zee
   796   - bob
   797  `,
   798  	"nonCollaboratorRemovals": `@@ -1,8 +1,6 @@
   799   reviewers:
   800   - phippy
   801   - alice
   802  -- al
   803   approvers:
   804   - zee
   805   - bob
   806  -- bo
   807  `,
   808  	"nonCollaboratorsWithAliases": `@@ -1,4 +1,6 @@
   809   reviewers:
   810  +- foo-reviewers
   811   - alice
   812  +- goldie
   813   approvers:
   814   - bob
   815  `,
   816  	"collaboratorsWithAliases": `@@ -1,2 +1,5 @@
   817  +reviewers:
   818  +- foo-reviewers
   819  +- alice
   820   approvers:
   821   - bob
   822  `,
   823  }
   824  
   825  var ownersAliases = map[string][]byte{
   826  	"nonCollaborators": []byte(`aliases:
   827    foo-reviewers:
   828    - alice
   829    - phippy
   830    - zee
   831  `),
   832  	"collaborators": []byte(`aliases:
   833    foo-reviewers:
   834    - alice
   835  `),
   836  }
   837  
   838  var ownersAliasesPatch = map[string]string{
   839  	"nonCollaboratorAdditions": `@@ -1,3 +1,5 @@
   840   aliases:
   841     foo-reviewers:
   842     - alice
   843  +  - phippy
   844  +  - zee
   845  `,
   846  	"nonCollaboratorRemovals": `@@ -1,6 +1,5 @@
   847   aliases:
   848     foo-reviewers:
   849     - alice
   850  -  - al
   851     - phippy
   852     - zee
   853  `,
   854  	"collaboratorAdditions": `@@ -1,4 +1,5 @@
   855   aliases:
   856     foo-reviewers:
   857  +  - alice
   858     - phippy
   859     - zee
   860  `,
   861  	"collaboratorRemovals": `@@ -1,6 +1,5 @@
   862   aliases:
   863     foo-reviewers:
   864     - alice
   865  -  - bob
   866     - phippy
   867     - zee
   868  `,
   869  }
   870  
   871  func TestNonCollaboratorsV2(t *testing.T) {
   872  	testNonCollaborators(localgit.NewV2, t)
   873  }
   874  
   875  func testNonCollaborators(clients localgit.Clients, t *testing.T) {
   876  	const nonTrustedNotMemberNotCollaborator = "User is not a member of the org. User is not a collaborator."
   877  	var tests = []struct {
   878  		name                 string
   879  		filesChanged         []string
   880  		ownersFile           string
   881  		ownersPatch          string
   882  		ownersAliasesFile    string
   883  		ownersAliasesPatch   string
   884  		includeVendorOwners  bool
   885  		skipTrustedUserCheck bool
   886  		shouldLabel          bool
   887  		shouldComment        bool
   888  		commentShouldContain string
   889  	}{
   890  		{
   891  			name:          "collaborators additions in OWNERS file",
   892  			filesChanged:  []string{"OWNERS"},
   893  			ownersFile:    "nonCollaborators",
   894  			ownersPatch:   "collaboratorAdditions",
   895  			shouldLabel:   false,
   896  			shouldComment: false,
   897  		},
   898  		{
   899  			name:          "collaborators removals in OWNERS file",
   900  			filesChanged:  []string{"OWNERS"},
   901  			ownersFile:    "nonCollaborators",
   902  			ownersPatch:   "collaboratorRemovals",
   903  			shouldLabel:   false,
   904  			shouldComment: false,
   905  		},
   906  		{
   907  			name:                 "non-collaborators additions in OWNERS file",
   908  			filesChanged:         []string{"OWNERS"},
   909  			ownersFile:           "nonCollaborators",
   910  			ownersPatch:          "nonCollaboratorAdditions",
   911  			shouldLabel:          true,
   912  			shouldComment:        true,
   913  			commentShouldContain: nonTrustedNotMemberNotCollaborator,
   914  		},
   915  		{
   916  			name:          "non-collaborators removal in OWNERS file",
   917  			filesChanged:  []string{"OWNERS"},
   918  			ownersFile:    "nonCollaborators",
   919  			ownersPatch:   "nonCollaboratorRemovals",
   920  			shouldLabel:   false,
   921  			shouldComment: false,
   922  		},
   923  		{
   924  			name:                 "non-collaborators additions in OWNERS file, with skipTrustedUserCheck=true",
   925  			filesChanged:         []string{"OWNERS"},
   926  			ownersFile:           "nonCollaborators",
   927  			ownersPatch:          "nonCollaboratorAdditions",
   928  			skipTrustedUserCheck: true,
   929  			shouldLabel:          false,
   930  			shouldComment:        false,
   931  		},
   932  		{
   933  			name:                 "non-collaborators additions in OWNERS_ALIASES file",
   934  			filesChanged:         []string{"OWNERS_ALIASES"},
   935  			ownersFile:           "collaboratorsWithAliases",
   936  			ownersAliasesFile:    "nonCollaborators",
   937  			ownersAliasesPatch:   "nonCollaboratorAdditions",
   938  			shouldLabel:          true,
   939  			shouldComment:        true,
   940  			commentShouldContain: nonTrustedNotMemberNotCollaborator,
   941  		},
   942  		{
   943  			name:               "non-collaborators removals in OWNERS_ALIASES file",
   944  			filesChanged:       []string{"OWNERS_ALIASES"},
   945  			ownersFile:         "collaboratorsWithAliases",
   946  			ownersAliasesFile:  "nonCollaborators",
   947  			ownersAliasesPatch: "nonCollaboratorRemovals",
   948  			shouldLabel:        false,
   949  			shouldComment:      false,
   950  		},
   951  		{
   952  			name:               "collaborators additions in OWNERS_ALIASES file",
   953  			filesChanged:       []string{"OWNERS_ALIASES"},
   954  			ownersFile:         "collaboratorsWithAliases",
   955  			ownersAliasesFile:  "nonCollaborators",
   956  			ownersAliasesPatch: "collaboratorAdditions",
   957  			shouldLabel:        false,
   958  			shouldComment:      false,
   959  		},
   960  		{
   961  			name:               "collaborators removals in OWNERS_ALIASES file",
   962  			filesChanged:       []string{"OWNERS_ALIASES"},
   963  			ownersFile:         "collaboratorsWithAliases",
   964  			ownersAliasesFile:  "nonCollaborators",
   965  			ownersAliasesPatch: "collaboratorRemovals",
   966  			shouldLabel:        false,
   967  			shouldComment:      false,
   968  		},
   969  		{
   970  			name:                 "non-collaborators additions in OWNERS_ALIASES file, with skipTrustedUserCheck=true",
   971  			filesChanged:         []string{"OWNERS_ALIASES"},
   972  			ownersFile:           "collaboratorsWithAliases",
   973  			ownersAliasesFile:    "nonCollaborators",
   974  			ownersAliasesPatch:   "nonCollaboratorAdditions",
   975  			skipTrustedUserCheck: true,
   976  			shouldLabel:          false,
   977  			shouldComment:        false,
   978  		},
   979  		{
   980  			name:                 "non-collaborators additions in both OWNERS and OWNERS_ALIASES file",
   981  			filesChanged:         []string{"OWNERS", "OWNERS_ALIASES"},
   982  			ownersFile:           "nonCollaboratorsWithAliases",
   983  			ownersPatch:          "nonCollaboratorsWithAliases",
   984  			ownersAliasesFile:    "nonCollaborators",
   985  			ownersAliasesPatch:   "nonCollaboratorAdditions",
   986  			shouldLabel:          true,
   987  			shouldComment:        true,
   988  			commentShouldContain: nonTrustedNotMemberNotCollaborator,
   989  		},
   990  		{
   991  			name:               "collaborator additions in both OWNERS and OWNERS_ALIASES file",
   992  			filesChanged:       []string{"OWNERS", "OWNERS_ALIASES"},
   993  			ownersFile:         "collaboratorsWithAliases",
   994  			ownersPatch:        "collaboratorsWithAliases",
   995  			ownersAliasesFile:  "collaborators",
   996  			ownersAliasesPatch: "collaboratorAdditions",
   997  			shouldLabel:        false,
   998  			shouldComment:      false,
   999  		},
  1000  		{
  1001  			name:          "non-collaborators additions in OWNERS file in vendor subdir",
  1002  			filesChanged:  []string{"vendor/k8s.io/client-go/OWNERS"},
  1003  			ownersFile:    "nonCollaborators",
  1004  			ownersPatch:   "nonCollaboratorAdditions",
  1005  			shouldLabel:   false,
  1006  			shouldComment: false,
  1007  		},
  1008  		{
  1009  			name:                 "non-collaborators additions in OWNERS file in vendor subdir, but include it",
  1010  			filesChanged:         []string{"vendor/k8s.io/client-go/OWNERS"},
  1011  			ownersFile:           "nonCollaborators",
  1012  			ownersPatch:          "nonCollaboratorAdditions",
  1013  			includeVendorOwners:  true,
  1014  			shouldLabel:          true,
  1015  			shouldComment:        true,
  1016  			commentShouldContain: nonTrustedNotMemberNotCollaborator,
  1017  		},
  1018  		{
  1019  			name:                 "non-collaborators additions in OWNERS file in vendor dir",
  1020  			filesChanged:         []string{"vendor/OWNERS"},
  1021  			ownersFile:           "nonCollaborators",
  1022  			ownersPatch:          "nonCollaboratorAdditions",
  1023  			shouldLabel:          true,
  1024  			shouldComment:        true,
  1025  			commentShouldContain: nonTrustedNotMemberNotCollaborator,
  1026  		},
  1027  	}
  1028  	lg, c, err := clients()
  1029  	if err != nil {
  1030  		t.Fatalf("Making localgit: %v", err)
  1031  	}
  1032  	defer func() {
  1033  		if err := lg.Clean(); err != nil {
  1034  			t.Errorf("Cleaning up localgit: %v", err)
  1035  		}
  1036  		if err := c.Clean(); err != nil {
  1037  			t.Errorf("Cleaning up client: %v", err)
  1038  		}
  1039  	}()
  1040  	if err := lg.MakeFakeRepo("org", "repo"); err != nil {
  1041  		t.Fatalf("Making fake repo: %v", err)
  1042  	}
  1043  	for i, test := range tests {
  1044  		t.Run(test.name, func(t *testing.T) {
  1045  			pr := i + 1
  1046  			// make sure we're on master before branching
  1047  			if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
  1048  				t.Fatalf("Switching to master branch: %v", err)
  1049  			}
  1050  			if err := lg.CheckoutNewBranch("org", "repo", fmt.Sprintf("pull/%d/head", pr)); err != nil {
  1051  				t.Fatalf("Checking out pull branch: %v", err)
  1052  			}
  1053  			pullFiles := map[string][]byte{}
  1054  			changes := []github.PullRequestChange{}
  1055  
  1056  			for _, file := range test.filesChanged {
  1057  				if strings.Contains(file, "OWNERS_ALIASES") {
  1058  					pullFiles[file] = ownersAliases[test.ownersAliasesFile]
  1059  					changes = append(changes, github.PullRequestChange{
  1060  						Filename: file,
  1061  						Patch:    ownersAliasesPatch[test.ownersAliasesPatch],
  1062  					})
  1063  				} else if strings.Contains(file, "OWNERS") {
  1064  					pullFiles[file] = ownersFiles[test.ownersFile]
  1065  					changes = append(changes, github.PullRequestChange{
  1066  						Filename: file,
  1067  						Patch:    ownersPatch[test.ownersPatch],
  1068  					})
  1069  				}
  1070  			}
  1071  
  1072  			if err := lg.AddCommit("org", "repo", pullFiles); err != nil {
  1073  				t.Fatalf("Adding PR commit: %v", err)
  1074  			}
  1075  			sha, err := lg.RevParse("org", "repo", "HEAD")
  1076  			if err != nil {
  1077  				t.Fatalf("Getting commit SHA: %v", err)
  1078  			}
  1079  			pre := &github.PullRequestEvent{
  1080  				PullRequest: github.PullRequest{
  1081  					User: github.User{Login: "author"},
  1082  					Base: github.PullRequestBranch{
  1083  						Ref: defaultBranch,
  1084  					},
  1085  					Head: github.PullRequestBranch{
  1086  						SHA: sha,
  1087  					},
  1088  				},
  1089  			}
  1090  			fghc := newFakeGitHubClient(emptyPatch(test.filesChanged), nil, pr)
  1091  			fghc.PullRequestChanges[pr] = changes
  1092  
  1093  			fghc.PullRequests = map[int]*github.PullRequest{}
  1094  			fghc.PullRequests[pr] = &github.PullRequest{
  1095  				Base: github.PullRequestBranch{
  1096  					Ref: fakegithub.TestRef,
  1097  				},
  1098  			}
  1099  
  1100  			froc := makeFakeRepoOwnersClient()
  1101  			if !test.includeVendorOwners {
  1102  				var ignorePatterns []*regexp.Regexp
  1103  				re, err := regexp.Compile("vendor/.*/.*$")
  1104  				if err != nil {
  1105  					t.Fatalf("error compiling regex: %v", err)
  1106  				}
  1107  				ignorePatterns = append(ignorePatterns, re)
  1108  				froc.foc.dirIgnorelist = ignorePatterns
  1109  			}
  1110  
  1111  			prInfo := info{
  1112  				org:          "org",
  1113  				repo:         "repo",
  1114  				repoFullName: "org/repo",
  1115  				number:       pr,
  1116  			}
  1117  
  1118  			if err := handle(fghc, c, froc, logrus.WithField("plugin", PluginName), &pre.PullRequest, prInfo, []string{labels.Approved, labels.LGTM}, plugins.Trigger{}, test.skipTrustedUserCheck, &fakePruner{}, ownersconfig.FakeResolver); err != nil {
  1119  				t.Fatalf("Handle PR: %v", err)
  1120  			}
  1121  			if !test.shouldLabel && IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
  1122  				t.Errorf("%s: didn't expect label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
  1123  			}
  1124  			if test.shouldLabel && !IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
  1125  				t.Errorf("%s: expected label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
  1126  			}
  1127  			if !test.shouldComment && len(fghc.IssueComments[pr]) > 0 {
  1128  				t.Errorf("%s: didn't expect comment", test.name)
  1129  			}
  1130  			if test.shouldComment && len(fghc.IssueComments[pr]) == 0 {
  1131  				t.Errorf("%s: expected comment but didn't receive", test.name)
  1132  			}
  1133  			if test.shouldComment && len(test.commentShouldContain) > 0 && !strings.Contains(fghc.IssueComments[pr][0].Body, test.commentShouldContain) {
  1134  				t.Errorf("%s: expected comment to contain\n%s\nbut it was actually\n%s", test.name, test.commentShouldContain, fghc.IssueComments[pr][0].Body)
  1135  			}
  1136  		})
  1137  	}
  1138  }
  1139  
  1140  func TestHandleGenericCommentV2(t *testing.T) {
  1141  	testHandleGenericComment(localgit.NewV2, t)
  1142  }
  1143  
  1144  func testHandleGenericComment(clients localgit.Clients, t *testing.T) {
  1145  	var tests = []struct {
  1146  		name         string
  1147  		commentEvent github.GenericCommentEvent
  1148  		filesChanged []string
  1149  		filesRemoved []string
  1150  		ownersFile   string
  1151  		shouldLabel  bool
  1152  	}{
  1153  		{
  1154  			name: "no OWNERS file",
  1155  			commentEvent: github.GenericCommentEvent{
  1156  				Action:     github.GenericCommentActionCreated,
  1157  				IssueState: "open",
  1158  				IsPR:       true,
  1159  				Body:       "/verify-owners",
  1160  			},
  1161  			filesChanged: []string{"a.go", "b.go"},
  1162  			ownersFile:   "valid",
  1163  			shouldLabel:  false,
  1164  		},
  1165  		{
  1166  			name: "good OWNERS file",
  1167  			commentEvent: github.GenericCommentEvent{
  1168  				Action:     github.GenericCommentActionCreated,
  1169  				IssueState: "open",
  1170  				IsPR:       true,
  1171  				Body:       "/verify-owners",
  1172  			},
  1173  			filesChanged: []string{"OWNERS", "b.go"},
  1174  			ownersFile:   "valid",
  1175  			shouldLabel:  false,
  1176  		},
  1177  		{
  1178  			name: "invalid syntax OWNERS file",
  1179  			commentEvent: github.GenericCommentEvent{
  1180  				Action:     github.GenericCommentActionCreated,
  1181  				IssueState: "open",
  1182  				IsPR:       true,
  1183  				Body:       "/verify-owners",
  1184  			},
  1185  			filesChanged: []string{"OWNERS", "b.go"},
  1186  			ownersFile:   "invalidSyntax",
  1187  			shouldLabel:  true,
  1188  		},
  1189  		{
  1190  			name: "invalid syntax OWNERS file, unrelated comment",
  1191  			commentEvent: github.GenericCommentEvent{
  1192  				Action: github.GenericCommentActionCreated,
  1193  				IsPR:   true,
  1194  				Body:   "/verify owners",
  1195  			},
  1196  			filesChanged: []string{"OWNERS", "b.go"},
  1197  			ownersFile:   "invalidSyntax",
  1198  			shouldLabel:  false,
  1199  		},
  1200  		{
  1201  			name: "invalid syntax OWNERS file, comment edited",
  1202  			commentEvent: github.GenericCommentEvent{
  1203  				Action: github.GenericCommentActionEdited,
  1204  				IsPR:   true,
  1205  				Body:   "/verify-owners",
  1206  			},
  1207  			filesChanged: []string{"OWNERS", "b.go"},
  1208  			ownersFile:   "invalidSyntax",
  1209  			shouldLabel:  false,
  1210  		},
  1211  		{
  1212  			name: "invalid syntax OWNERS file, comment on an issue",
  1213  			commentEvent: github.GenericCommentEvent{
  1214  				Action: github.GenericCommentActionCreated,
  1215  				IsPR:   false,
  1216  				Body:   "/verify-owners",
  1217  			},
  1218  			filesChanged: []string{"OWNERS", "b.go"},
  1219  			ownersFile:   "invalidSyntax",
  1220  			shouldLabel:  false,
  1221  		},
  1222  	}
  1223  
  1224  	lg, c, err := clients()
  1225  	if err != nil {
  1226  		t.Fatalf("Making localgit: %v", err)
  1227  	}
  1228  	defer func() {
  1229  		if err := lg.Clean(); err != nil {
  1230  			t.Errorf("Cleaning up localgit: %v", err)
  1231  		}
  1232  		if err := c.Clean(); err != nil {
  1233  			t.Errorf("Cleaning up client: %v", err)
  1234  		}
  1235  	}()
  1236  	if err := lg.MakeFakeRepo("org", "repo"); err != nil {
  1237  		t.Fatalf("Making fake repo: %v", err)
  1238  	}
  1239  	for i, test := range tests {
  1240  		t.Run(test.name, func(t *testing.T) {
  1241  			pr := i + 1
  1242  			// make sure we're on master before branching
  1243  			if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
  1244  				t.Fatalf("Switching to master branch: %v", err)
  1245  			}
  1246  			if len(test.filesRemoved) > 0 {
  1247  				if err := addFilesToRepo(lg, test.filesRemoved, test.ownersFile); err != nil {
  1248  					t.Fatalf("Adding base commit: %v", err)
  1249  				}
  1250  			}
  1251  
  1252  			if err := lg.CheckoutNewBranch("org", "repo", fmt.Sprintf("pull/%d/head", pr)); err != nil {
  1253  				t.Fatalf("Checking out pull branch: %v", err)
  1254  			}
  1255  
  1256  			if len(test.filesChanged) > 0 {
  1257  				if err := addFilesToRepo(lg, test.filesChanged, test.ownersFile); err != nil {
  1258  					t.Fatalf("Adding PR commit: %v", err)
  1259  				}
  1260  			}
  1261  			if len(test.filesRemoved) > 0 {
  1262  				if err := lg.RmCommit("org", "repo", test.filesRemoved); err != nil {
  1263  					t.Fatalf("Adding PR commit (removing files): %v", err)
  1264  				}
  1265  			}
  1266  
  1267  			sha, err := lg.RevParse("org", "repo", "HEAD")
  1268  			if err != nil {
  1269  				t.Fatalf("Getting commit SHA: %v", err)
  1270  			}
  1271  
  1272  			test.commentEvent.Repo.Owner.Login = "org"
  1273  			test.commentEvent.Repo.Name = "repo"
  1274  			test.commentEvent.Repo.FullName = "org/repo"
  1275  			test.commentEvent.Number = pr
  1276  
  1277  			fghc := newFakeGitHubClient(emptyPatch(test.filesChanged), test.filesRemoved, pr)
  1278  			fghc.PullRequests = map[int]*github.PullRequest{}
  1279  			fghc.PullRequests[pr] = &github.PullRequest{
  1280  				User: github.User{Login: "author"},
  1281  				Head: github.PullRequestBranch{
  1282  					SHA: sha,
  1283  				},
  1284  				Base: github.PullRequestBranch{
  1285  					Ref: defaultBranch,
  1286  				},
  1287  			}
  1288  
  1289  			if err := handleGenericComment(fghc, c, makeFakeRepoOwnersClient(), logrus.WithField("plugin", PluginName), &test.commentEvent, []string{labels.Approved, labels.LGTM}, plugins.Trigger{}, false, &fakePruner{}, ownersconfig.FakeResolver); err != nil {
  1290  				t.Fatalf("Handle PR: %v", err)
  1291  			}
  1292  			if !test.shouldLabel && IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
  1293  				t.Errorf("%s: didn't expect label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
  1294  			} else if test.shouldLabel && !IssueLabelsContain(fghc.IssueLabelsAdded, labels.InvalidOwners) {
  1295  				t.Errorf("%s: expected label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsAdded)
  1296  			}
  1297  		})
  1298  	}
  1299  }
  1300  
  1301  func testOwnersRemoval(clients localgit.Clients, t *testing.T) {
  1302  	var tests = []struct {
  1303  		name              string
  1304  		ownersRestored    bool
  1305  		aliasesRestored   bool
  1306  		shouldRemoveLabel bool
  1307  	}{
  1308  		{
  1309  			name:              "OWNERS and OWNERS_ALIASES files restored",
  1310  			ownersRestored:    true,
  1311  			aliasesRestored:   true,
  1312  			shouldRemoveLabel: true,
  1313  		},
  1314  		{
  1315  			name:              "OWNERS file restored, OWNERS_ALIASES left",
  1316  			ownersRestored:    true,
  1317  			aliasesRestored:   false,
  1318  			shouldRemoveLabel: true,
  1319  		},
  1320  		{
  1321  			name:              "OWNERS file left, OWNERS_ALIASES left",
  1322  			ownersRestored:    false,
  1323  			aliasesRestored:   false,
  1324  			shouldRemoveLabel: false,
  1325  		},
  1326  	}
  1327  	lg, c, err := clients()
  1328  	if err != nil {
  1329  		t.Fatalf("Making localgit: %v", err)
  1330  	}
  1331  	defer func() {
  1332  		if err := lg.Clean(); err != nil {
  1333  			t.Errorf("Cleaning up localgit: %v", err)
  1334  		}
  1335  		if err := c.Clean(); err != nil {
  1336  			t.Errorf("Cleaning up client: %v", err)
  1337  		}
  1338  	}()
  1339  	if err := lg.MakeFakeRepo("org", "repo"); err != nil {
  1340  		t.Fatalf("Making fake repo: %v", err)
  1341  	}
  1342  
  1343  	if err := addFilesToRepo(lg, []string{"OWNERS"}, "valid"); err != nil {
  1344  		t.Fatalf("Adding base commit: %v", err)
  1345  	}
  1346  
  1347  	if err := addFilesToRepo(lg, []string{"OWNERS_ALIASES"}, "collaborators"); err != nil {
  1348  		t.Fatalf("Adding base commit: %v", err)
  1349  	}
  1350  
  1351  	for i, test := range tests {
  1352  		t.Run(test.name, func(t *testing.T) {
  1353  			pr := i + 1
  1354  			// make sure we're on master before branching
  1355  			if err := lg.Checkout("org", "repo", defaultBranch); err != nil {
  1356  				t.Fatalf("Switching to master branch: %v", err)
  1357  			}
  1358  			pullFiles := map[string][]byte{}
  1359  			pullFiles["a.go"] = []byte("foo")
  1360  
  1361  			if err := lg.CheckoutNewBranch("org", "repo", fmt.Sprintf("pull/%d/head", pr)); err != nil {
  1362  				t.Fatalf("Checking out pull branch: %v", err)
  1363  			}
  1364  
  1365  			if test.ownersRestored == false {
  1366  				if err := addFilesToRepo(lg, []string{"OWNERS"}, "invalidSyntax"); err != nil {
  1367  					t.Fatalf("Adding OWNERS file: %v", err)
  1368  				}
  1369  			}
  1370  
  1371  			if test.aliasesRestored == false {
  1372  				if err := addFilesToRepo(lg, []string{"OWNERS_ALIASES"}, "toBeAddedAlias"); err != nil {
  1373  					t.Fatalf("Adding OWNERS_ALIASES file: %v", err)
  1374  				}
  1375  			}
  1376  
  1377  			if err := lg.AddCommit("org", "repo", pullFiles); err != nil {
  1378  				t.Fatalf("Adding PR commit: %v", err)
  1379  			}
  1380  			sha, err := lg.RevParse("org", "repo", "HEAD")
  1381  			if err != nil {
  1382  				t.Fatalf("Getting commit SHA: %v", err)
  1383  			}
  1384  			pre := &github.PullRequestEvent{
  1385  				PullRequest: github.PullRequest{
  1386  					User: github.User{Login: "author"},
  1387  					Base: github.PullRequestBranch{
  1388  						Ref: defaultBranch,
  1389  					},
  1390  					Head: github.PullRequestBranch{
  1391  						SHA: sha,
  1392  					},
  1393  				},
  1394  			}
  1395  			files := make([]string, 3)
  1396  			files = append(files, "a.go")
  1397  			if !test.ownersRestored {
  1398  				files = append(files, "OWNERS")
  1399  			}
  1400  			if !test.aliasesRestored {
  1401  				files = append(files, "OWNERS_ALIASES")
  1402  			}
  1403  			fghc := newFakeGitHubClient(emptyPatch(files), nil, pr)
  1404  
  1405  			fghc.PullRequests = map[int]*github.PullRequest{}
  1406  			fghc.PullRequests[pr] = &github.PullRequest{
  1407  				Base: github.PullRequestBranch{
  1408  					Ref: fakegithub.TestRef,
  1409  				},
  1410  			}
  1411  
  1412  			prInfo := info{
  1413  				org:          "org",
  1414  				repo:         "repo",
  1415  				repoFullName: "org/repo",
  1416  				number:       pr,
  1417  			}
  1418  			fghc.AddLabel(prInfo.org, prInfo.repo, prInfo.number, labels.InvalidOwners)
  1419  
  1420  			froc := makeFakeRepoOwnersClient()
  1421  
  1422  			if err := handle(fghc, c, froc, logrus.WithField("plugin", PluginName), &pre.PullRequest, prInfo, []string{labels.Approved, labels.LGTM}, plugins.Trigger{}, false, &fakePruner{}, ownersconfig.FakeResolver); err != nil {
  1423  				t.Fatalf("Handle PR: %v", err)
  1424  			}
  1425  			if test.shouldRemoveLabel && !IssueLabelsContain(fghc.IssueLabelsRemoved, labels.InvalidOwners) {
  1426  				t.Errorf("%s: expected label %s in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsRemoved)
  1427  			}
  1428  			if !test.shouldRemoveLabel && IssueLabelsContain(fghc.IssueLabelsRemoved, labels.InvalidOwners) {
  1429  				t.Errorf("%s: didn't expect label %q in %s", test.name, labels.InvalidOwners, fghc.IssueLabelsRemoved)
  1430  			}
  1431  		})
  1432  	}
  1433  }
  1434  
  1435  func TestOwnersRemovalV2(t *testing.T) {
  1436  	testOwnersRemoval(localgit.NewV2, t)
  1437  }