github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/approve/approvers/approvers_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 approvers
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/sirupsen/logrus"
    23  
    24  	"reflect"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  )
    28  
    29  func TestUnapprovedFiles(t *testing.T) {
    30  	rootApprovers := sets.NewString("Alice", "Bob")
    31  	aApprovers := sets.NewString("Art", "Anne")
    32  	bApprovers := sets.NewString("Bill", "Ben", "Barbara")
    33  	cApprovers := sets.NewString("Chris", "Carol")
    34  	dApprovers := sets.NewString("David", "Dan", "Debbie")
    35  	eApprovers := sets.NewString("Eve", "Erin")
    36  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
    37  	FakeRepoMap := map[string]sets.String{
    38  		"":        rootApprovers,
    39  		"a":       aApprovers,
    40  		"b":       bApprovers,
    41  		"c":       cApprovers,
    42  		"a/d":     dApprovers,
    43  		"a/combo": edcApprovers,
    44  	}
    45  	tests := []struct {
    46  		testName           string
    47  		filenames          []string
    48  		currentlyApproved  sets.String
    49  		expectedUnapproved sets.String
    50  	}{
    51  		{
    52  			testName:           "Empty PR",
    53  			filenames:          []string{},
    54  			currentlyApproved:  sets.NewString(),
    55  			expectedUnapproved: sets.NewString(),
    56  		},
    57  		{
    58  			testName:           "Single Root File PR Approved",
    59  			filenames:          []string{"kubernetes.go"},
    60  			currentlyApproved:  sets.NewString(rootApprovers.List()[0]),
    61  			expectedUnapproved: sets.NewString(),
    62  		},
    63  		{
    64  			testName:           "Single Root File PR No One Approved",
    65  			filenames:          []string{"kubernetes.go"},
    66  			currentlyApproved:  sets.NewString(),
    67  			expectedUnapproved: sets.NewString(""),
    68  		},
    69  		{
    70  			testName:           "B Only UnApproved",
    71  			currentlyApproved:  bApprovers,
    72  			expectedUnapproved: sets.NewString(),
    73  		},
    74  		{
    75  			testName:           "B Files Approved at Root",
    76  			filenames:          []string{"b/test.go", "b/test_1.go"},
    77  			currentlyApproved:  rootApprovers,
    78  			expectedUnapproved: sets.NewString(),
    79  		},
    80  		{
    81  			testName:           "B Only UnApproved",
    82  			filenames:          []string{"b/test_1.go", "b/test.go"},
    83  			currentlyApproved:  sets.NewString(),
    84  			expectedUnapproved: sets.NewString("b"),
    85  		},
    86  		{
    87  			testName:           "Combo and Other; Neither Approved",
    88  			filenames:          []string{"a/combo/test.go", "a/d/test.go"},
    89  			currentlyApproved:  sets.NewString(),
    90  			expectedUnapproved: sets.NewString("a/combo", "a/d"),
    91  		},
    92  		{
    93  			testName:           "Combo and Other; Combo Approved",
    94  			filenames:          []string{"a/combo/test.go", "a/d/test.go"},
    95  			currentlyApproved:  edcApprovers.Difference(dApprovers),
    96  			expectedUnapproved: sets.NewString("a/d"),
    97  		},
    98  		{
    99  			testName:           "Combo and Other; Both Approved",
   100  			filenames:          []string{"a/combo/test.go", "a/d/test.go"},
   101  			currentlyApproved:  edcApprovers.Intersection(dApprovers),
   102  			expectedUnapproved: sets.NewString(),
   103  		},
   104  	}
   105  
   106  	for _, test := range tests {
   107  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: TestSeed, log: logrus.WithField("plugin", "some_plugin")})
   108  		testApprovers.RequireIssue = false
   109  		for approver := range test.currentlyApproved {
   110  			testApprovers.AddApprover(approver, "REFERENCE", false)
   111  		}
   112  		calculated := testApprovers.UnapprovedFiles()
   113  		if !test.expectedUnapproved.Equal(calculated) {
   114  			t.Errorf("Failed for test %v.  Expected unapproved files: %v. Found %v", test.testName, test.expectedUnapproved, calculated)
   115  		}
   116  	}
   117  }
   118  
   119  func TestGetFiles(t *testing.T) {
   120  	rootApprovers := sets.NewString("Alice", "Bob")
   121  	aApprovers := sets.NewString("Art", "Anne")
   122  	bApprovers := sets.NewString("Bill", "Ben", "Barbara")
   123  	cApprovers := sets.NewString("Chris", "Carol")
   124  	dApprovers := sets.NewString("David", "Dan", "Debbie")
   125  	eApprovers := sets.NewString("Eve", "Erin")
   126  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   127  	FakeRepoMap := map[string]sets.String{
   128  		"":        rootApprovers,
   129  		"a":       aApprovers,
   130  		"b":       bApprovers,
   131  		"c":       cApprovers,
   132  		"a/d":     dApprovers,
   133  		"a/combo": edcApprovers,
   134  	}
   135  	tests := []struct {
   136  		testName          string
   137  		filenames         []string
   138  		currentlyApproved sets.String
   139  		expectedFiles     []File
   140  	}{
   141  		{
   142  			testName:          "Empty PR",
   143  			filenames:         []string{},
   144  			currentlyApproved: sets.NewString(),
   145  			expectedFiles:     []File{},
   146  		},
   147  		{
   148  			testName:          "Single Root File PR Approved",
   149  			filenames:         []string{"kubernetes.go"},
   150  			currentlyApproved: sets.NewString(rootApprovers.List()[0]),
   151  			expectedFiles:     []File{ApprovedFile{"", sets.NewString(rootApprovers.List()[0]), "org", "project", "master"}},
   152  		},
   153  		{
   154  			testName:          "Single File PR in B No One Approved",
   155  			filenames:         []string{"b/test.go"},
   156  			currentlyApproved: sets.NewString(),
   157  			expectedFiles:     []File{UnapprovedFile{"b", "org", "project", "master"}},
   158  		},
   159  		{
   160  			testName:          "Single File PR in B Fully Approved",
   161  			filenames:         []string{"b/test.go"},
   162  			currentlyApproved: bApprovers,
   163  			expectedFiles:     []File{ApprovedFile{"b", bApprovers, "org", "project", "master"}},
   164  		},
   165  		{
   166  			testName:          "Single Root File PR No One Approved",
   167  			filenames:         []string{"kubernetes.go"},
   168  			currentlyApproved: sets.NewString(),
   169  			expectedFiles:     []File{UnapprovedFile{"", "org", "project", "master"}},
   170  		},
   171  		{
   172  			testName:          "Combo and Other; Neither Approved",
   173  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   174  			currentlyApproved: sets.NewString(),
   175  			expectedFiles: []File{
   176  				UnapprovedFile{"a/combo", "org", "project", "master"},
   177  				UnapprovedFile{"a/d", "org", "project", "master"},
   178  			},
   179  		},
   180  		{
   181  			testName:          "Combo and Other; Combo Approved",
   182  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   183  			currentlyApproved: eApprovers,
   184  			expectedFiles: []File{
   185  				ApprovedFile{"a/combo", eApprovers, "org", "project", "master"},
   186  				UnapprovedFile{"a/d", "org", "project", "master"},
   187  			},
   188  		},
   189  		{
   190  			testName:          "Combo and Other; Both Approved",
   191  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   192  			currentlyApproved: edcApprovers.Intersection(dApprovers),
   193  			expectedFiles: []File{
   194  				ApprovedFile{"a/combo", edcApprovers.Intersection(dApprovers), "org", "project", "master"},
   195  				ApprovedFile{"a/d", edcApprovers.Intersection(dApprovers), "org", "project", "master"},
   196  			},
   197  		},
   198  		{
   199  			testName:          "Combo, C, D; Combo and C Approved",
   200  			filenames:         []string{"a/combo/test.go", "a/d/test.go", "c/test"},
   201  			currentlyApproved: cApprovers,
   202  			expectedFiles: []File{
   203  				ApprovedFile{"a/combo", cApprovers, "org", "project", "master"},
   204  				UnapprovedFile{"a/d", "org", "project", "master"},
   205  				ApprovedFile{"c", cApprovers, "org", "project", "master"},
   206  			},
   207  		},
   208  		{
   209  			testName:          "Files Approved Multiple times",
   210  			filenames:         []string{"a/test.go", "a/d/test.go", "b/test"},
   211  			currentlyApproved: rootApprovers.Union(aApprovers).Union(bApprovers),
   212  			expectedFiles: []File{
   213  				ApprovedFile{"a", rootApprovers.Union(aApprovers), "org", "project", "master"},
   214  				ApprovedFile{"b", rootApprovers.Union(bApprovers), "org", "project", "master"},
   215  			},
   216  		},
   217  	}
   218  
   219  	for _, test := range tests {
   220  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: TestSeed, log: logrus.WithField("plugin", "some_plugin")})
   221  		testApprovers.RequireIssue = false
   222  		for approver := range test.currentlyApproved {
   223  			testApprovers.AddApprover(approver, "REFERENCE", false)
   224  		}
   225  		calculated := testApprovers.GetFiles("org", "project", "master")
   226  		if !reflect.DeepEqual(test.expectedFiles, calculated) {
   227  			t.Errorf("Failed for test %v.  Expected files: %v. Found %v", test.testName, test.expectedFiles, calculated)
   228  		}
   229  	}
   230  }
   231  
   232  func TestGetCCs(t *testing.T) {
   233  	rootApprovers := sets.NewString("Alice", "Bob")
   234  	aApprovers := sets.NewString("Art", "Anne")
   235  	bApprovers := sets.NewString("Bill", "Ben", "Barbara")
   236  	cApprovers := sets.NewString("Chris", "Carol")
   237  	dApprovers := sets.NewString("David", "Dan", "Debbie")
   238  	eApprovers := sets.NewString("Eve", "Erin")
   239  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   240  	FakeRepoMap := map[string]sets.String{
   241  		"":        rootApprovers,
   242  		"a":       aApprovers,
   243  		"b":       bApprovers,
   244  		"c":       cApprovers,
   245  		"a/d":     dApprovers,
   246  		"a/combo": edcApprovers,
   247  	}
   248  	tests := []struct {
   249  		testName          string
   250  		filenames         []string
   251  		currentlyApproved sets.String
   252  		// testSeed affects who is chosen for CC
   253  		testSeed  int64
   254  		assignees []string
   255  		// order matters for CCs
   256  		expectedCCs []string
   257  	}{
   258  		{
   259  			testName:          "Empty PR",
   260  			filenames:         []string{},
   261  			currentlyApproved: sets.NewString(),
   262  			testSeed:          0,
   263  			expectedCCs:       []string{},
   264  		},
   265  		{
   266  			testName:          "Single Root FFile PR Approved",
   267  			filenames:         []string{"kubernetes.go"},
   268  			currentlyApproved: sets.NewString(rootApprovers.List()[0]),
   269  			testSeed:          13,
   270  			expectedCCs:       []string{},
   271  		},
   272  		{
   273  			testName:          "Single Root File PR Unapproved Seed = 13",
   274  			filenames:         []string{"kubernetes.go"},
   275  			currentlyApproved: sets.NewString(),
   276  			testSeed:          13,
   277  			expectedCCs:       []string{"alice"},
   278  		},
   279  		{
   280  			testName:          "Single Root File PR No One Seed = 10",
   281  			filenames:         []string{"kubernetes.go"},
   282  			testSeed:          10,
   283  			currentlyApproved: sets.NewString(),
   284  			expectedCCs:       []string{"bob"},
   285  		},
   286  		{
   287  			testName:          "Combo and Other; Neither Approved",
   288  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   289  			testSeed:          0,
   290  			currentlyApproved: sets.NewString(),
   291  			expectedCCs:       []string{"dan"},
   292  		},
   293  		{
   294  			testName:          "Combo and Other; Combo Approved",
   295  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   296  			testSeed:          0,
   297  			currentlyApproved: eApprovers,
   298  			expectedCCs:       []string{"dan"},
   299  		},
   300  		{
   301  			testName:          "Combo and Other; Both Approved",
   302  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   303  			testSeed:          0,
   304  			currentlyApproved: dApprovers, // dApprovers can approve combo and d directory
   305  			expectedCCs:       []string{},
   306  		},
   307  		{
   308  			testName:          "Combo, C, D; None Approved",
   309  			filenames:         []string{"a/combo/test.go", "a/d/test.go", "c/test"},
   310  			testSeed:          0,
   311  			currentlyApproved: sets.NewString(),
   312  			// chris can approve c and combo, debbie can approve d
   313  			expectedCCs: []string{"chris", "debbie"},
   314  		},
   315  		{
   316  			testName:          "A, B, C; Nothing Approved",
   317  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   318  			testSeed:          0,
   319  			currentlyApproved: sets.NewString(),
   320  			// Need an approver from each of the three owners files
   321  			expectedCCs: []string{"anne", "bill", "carol"},
   322  		},
   323  		{
   324  			testName:  "A, B, C; Partially approved by non-suggested approvers",
   325  			filenames: []string{"a/test.go", "b/test.go", "c/test"},
   326  			testSeed:  0,
   327  			// Approvers are valid approvers, but not the one we would suggest
   328  			currentlyApproved: sets.NewString("Art", "Ben"),
   329  			// We don't suggest approvers for a and b, only for unapproved c.
   330  			expectedCCs: []string{"carol"},
   331  		},
   332  		{
   333  			testName:  "A, B, C; Nothing approved, but assignees can approve",
   334  			filenames: []string{"a/test.go", "b/test.go", "c/test"},
   335  			testSeed:  0,
   336  			// Approvers are valid approvers, but not the one we would suggest
   337  			currentlyApproved: sets.NewString(),
   338  			assignees:         []string{"Art", "Ben"},
   339  			// We suggest assigned people rather than "suggested" people
   340  			// Suggested would be "Anne", "Bill", "Carol" if no one was assigned.
   341  			expectedCCs: []string{"art", "ben", "carol"},
   342  		},
   343  		{
   344  			testName:          "A, B, C; Nothing approved, but SOME assignees can approve",
   345  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   346  			testSeed:          0,
   347  			currentlyApproved: sets.NewString(),
   348  			// Assignees are a mix of potential approvers and random people
   349  			assignees: []string{"Art", "Ben", "John", "Jack"},
   350  			// We suggest assigned people rather than "suggested" people
   351  			expectedCCs: []string{"art", "ben", "carol"},
   352  		},
   353  		{
   354  			testName:          "Assignee is top OWNER, No one has approved",
   355  			filenames:         []string{"a/test.go"},
   356  			testSeed:          0,
   357  			currentlyApproved: sets.NewString(),
   358  			// Assignee is a root approver
   359  			assignees:   []string{"alice"},
   360  			expectedCCs: []string{"alice"},
   361  		},
   362  	}
   363  
   364  	for _, test := range tests {
   365  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")})
   366  		testApprovers.RequireIssue = false
   367  		for approver := range test.currentlyApproved {
   368  			testApprovers.AddApprover(approver, "REFERENCE", false)
   369  		}
   370  		testApprovers.AddAssignees(test.assignees...)
   371  		calculated := testApprovers.GetCCs()
   372  		if !reflect.DeepEqual(test.expectedCCs, calculated) {
   373  			t.Errorf("Failed for test %v.  Expected CCs: %v. Found %v", test.testName, test.expectedCCs, calculated)
   374  		}
   375  	}
   376  }
   377  
   378  func TestIsApproved(t *testing.T) {
   379  	rootApprovers := sets.NewString("Alice", "Bob")
   380  	aApprovers := sets.NewString("Art", "Anne")
   381  	bApprovers := sets.NewString("Bill", "Ben", "Barbara")
   382  	cApprovers := sets.NewString("Chris", "Carol")
   383  	dApprovers := sets.NewString("David", "Dan", "Debbie")
   384  	eApprovers := sets.NewString("Eve", "Erin")
   385  	edcApprovers := eApprovers.Union(dApprovers).Union(cApprovers)
   386  	FakeRepoMap := map[string]sets.String{
   387  		"":        rootApprovers,
   388  		"a":       aApprovers,
   389  		"b":       bApprovers,
   390  		"c":       cApprovers,
   391  		"a/d":     dApprovers,
   392  		"a/combo": edcApprovers,
   393  	}
   394  	tests := []struct {
   395  		testName          string
   396  		filenames         []string
   397  		currentlyApproved sets.String
   398  		testSeed          int64
   399  		isApproved        bool
   400  	}{
   401  		{
   402  			testName:          "Empty PR",
   403  			filenames:         []string{},
   404  			currentlyApproved: sets.NewString(),
   405  			testSeed:          0,
   406  			isApproved:        true,
   407  		},
   408  		{
   409  			testName:          "Single Root File PR Approved",
   410  			filenames:         []string{"kubernetes.go"},
   411  			currentlyApproved: sets.NewString(rootApprovers.List()[0]),
   412  			testSeed:          3,
   413  			isApproved:        true,
   414  		},
   415  		{
   416  			testName:          "Single Root File PR No One Approved",
   417  			filenames:         []string{"kubernetes.go"},
   418  			testSeed:          0,
   419  			currentlyApproved: sets.NewString(),
   420  			isApproved:        false,
   421  		},
   422  		{
   423  			testName:          "Combo and Other; Neither Approved",
   424  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   425  			testSeed:          0,
   426  			currentlyApproved: sets.NewString(),
   427  			isApproved:        false,
   428  		},
   429  		{
   430  			testName:          "Combo and Other; Both Approved",
   431  			filenames:         []string{"a/combo/test.go", "a/d/test.go"},
   432  			testSeed:          0,
   433  			currentlyApproved: edcApprovers.Intersection(dApprovers),
   434  			isApproved:        true,
   435  		},
   436  		{
   437  			testName:          "A, B, C; Nothing Approved",
   438  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   439  			testSeed:          0,
   440  			currentlyApproved: sets.NewString(),
   441  			isApproved:        false,
   442  		},
   443  		{
   444  			testName:          "A, B, C; Partially Approved",
   445  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   446  			testSeed:          0,
   447  			currentlyApproved: aApprovers.Union(bApprovers),
   448  			isApproved:        false,
   449  		},
   450  		{
   451  			testName:          "A, B, C; Approved At the Root",
   452  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   453  			testSeed:          0,
   454  			currentlyApproved: rootApprovers,
   455  			isApproved:        true,
   456  		},
   457  		{
   458  			testName:          "A, B, C; Approved At the Leaves",
   459  			filenames:         []string{"a/test.go", "b/test.go", "c/test"},
   460  			testSeed:          0,
   461  			currentlyApproved: sets.NewString("Anne", "Ben", "Carol"),
   462  			isApproved:        true,
   463  		},
   464  	}
   465  
   466  	for _, test := range tests {
   467  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: test.testSeed, log: logrus.WithField("plugin", "some_plugin")})
   468  		for approver := range test.currentlyApproved {
   469  			testApprovers.AddApprover(approver, "REFERENCE", false)
   470  		}
   471  		calculated := testApprovers.IsApproved()
   472  		if test.isApproved != calculated {
   473  			t.Errorf("Failed for test %v.  Expected Approval Status: %v. Found %v", test.testName, test.isApproved, calculated)
   474  		}
   475  	}
   476  }
   477  
   478  func TestIsApprovedWithIssue(t *testing.T) {
   479  	aApprovers := sets.NewString("Author", "Anne", "Carl")
   480  	bApprovers := sets.NewString("Bill", "Carl")
   481  	FakeRepoMap := map[string]sets.String{"a": aApprovers, "b": bApprovers}
   482  	tests := []struct {
   483  		testName          string
   484  		filenames         []string
   485  		currentlyApproved map[string]bool
   486  		associatedIssue   int
   487  		isApproved        bool
   488  	}{
   489  		{
   490  			testName:          "Empty PR",
   491  			filenames:         []string{},
   492  			currentlyApproved: map[string]bool{},
   493  			associatedIssue:   0,
   494  			isApproved:        false,
   495  		},
   496  		{
   497  			testName:          "Single file missing issue",
   498  			filenames:         []string{"a/file.go"},
   499  			currentlyApproved: map[string]bool{"Carl": false},
   500  			associatedIssue:   0,
   501  			isApproved:        false,
   502  		},
   503  		{
   504  			testName:          "Single file no-issue",
   505  			filenames:         []string{"a/file.go"},
   506  			currentlyApproved: map[string]bool{"Carl": true},
   507  			associatedIssue:   0,
   508  			isApproved:        true,
   509  		},
   510  		{
   511  			testName:          "Single file with issue",
   512  			filenames:         []string{"a/file.go"},
   513  			currentlyApproved: map[string]bool{"Carl": false},
   514  			associatedIssue:   100,
   515  			isApproved:        true,
   516  		},
   517  		{
   518  			testName:          "Two files missing issue",
   519  			filenames:         []string{"a/file.go", "b/file2.go"},
   520  			currentlyApproved: map[string]bool{"Carl": false},
   521  			associatedIssue:   0,
   522  			isApproved:        false,
   523  		},
   524  		{
   525  			testName:          "Two files no-issue",
   526  			filenames:         []string{"a/file.go", "b/file2.go"},
   527  			currentlyApproved: map[string]bool{"Carl": true},
   528  			associatedIssue:   0,
   529  			isApproved:        true,
   530  		},
   531  		{
   532  			testName:          "Two files two no-issue two approvers",
   533  			filenames:         []string{"a/file.go", "b/file2.go"},
   534  			currentlyApproved: map[string]bool{"Anne": true, "Bill": true},
   535  			associatedIssue:   0,
   536  			isApproved:        true,
   537  		},
   538  		{
   539  			testName:          "Two files one no-issue two approvers",
   540  			filenames:         []string{"a/file.go", "b/file2.go"},
   541  			currentlyApproved: map[string]bool{"Anne": true, "Bill": false},
   542  			associatedIssue:   0,
   543  			isApproved:        true,
   544  		},
   545  		{
   546  			testName:          "Two files missing issue two approvers",
   547  			filenames:         []string{"a/file.go", "b/file2.go"},
   548  			currentlyApproved: map[string]bool{"Anne": false, "Bill": false},
   549  			associatedIssue:   0,
   550  			isApproved:        false,
   551  		},
   552  		{
   553  			testName:          "Self approval (implicit) missing issue",
   554  			filenames:         []string{"a/file.go"},
   555  			currentlyApproved: map[string]bool{},
   556  			associatedIssue:   0,
   557  			isApproved:        false,
   558  		},
   559  		{
   560  			testName:          "Self approval (implicit) with issue",
   561  			filenames:         []string{"a/file.go"},
   562  			currentlyApproved: map[string]bool{},
   563  			associatedIssue:   10,
   564  			isApproved:        true,
   565  		},
   566  		{
   567  			testName:          "Self approval (explicit) missing issue",
   568  			filenames:         []string{"a/file.go"},
   569  			currentlyApproved: map[string]bool{"Author": false},
   570  			associatedIssue:   0,
   571  			isApproved:        false,
   572  		},
   573  		{
   574  			testName:          "Self approval (explicit) no-issue",
   575  			filenames:         []string{"a/file.go"},
   576  			currentlyApproved: map[string]bool{"Author": true},
   577  			associatedIssue:   0,
   578  			isApproved:        true,
   579  		},
   580  		{
   581  			testName:          "Self approval (explicit) missing issue, two files",
   582  			filenames:         []string{"a/file.go", "b/file2.go"},
   583  			currentlyApproved: map[string]bool{"Author": false, "Bill": false},
   584  			associatedIssue:   0,
   585  			isApproved:        false,
   586  		},
   587  		{
   588  			testName:          "Self approval (explicit) no-issue from author, two files",
   589  			filenames:         []string{"a/file.go", "b/file2.go"},
   590  			currentlyApproved: map[string]bool{"Author": true, "Bill": false},
   591  			associatedIssue:   0,
   592  			isApproved:        true,
   593  		},
   594  		{
   595  			testName:          "Self approval (explicit) no-issue from friend, two files",
   596  			filenames:         []string{"a/file.go", "b/file2.go"},
   597  			currentlyApproved: map[string]bool{"Author": false, "Bill": true},
   598  			associatedIssue:   0,
   599  			isApproved:        true,
   600  		},
   601  	}
   602  
   603  	for _, test := range tests {
   604  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(FakeRepoMap), seed: 0, log: logrus.WithField("plugin", "some_plugin")})
   605  		testApprovers.RequireIssue = true
   606  		testApprovers.AssociatedIssue = test.associatedIssue
   607  		for approver, noissue := range test.currentlyApproved {
   608  			testApprovers.AddApprover(approver, "REFERENCE", noissue)
   609  		}
   610  		testApprovers.AddAuthorSelfApprover("Author", "REFERENCE", false)
   611  		calculated := testApprovers.IsApproved()
   612  		if test.isApproved != calculated {
   613  			t.Errorf("Failed for test %v.  Expected Approval Status: %v. Found %v", test.testName, test.isApproved, calculated)
   614  		}
   615  	}
   616  }
   617  
   618  func TestGetFilesApprovers(t *testing.T) {
   619  	tests := []struct {
   620  		testName       string
   621  		filenames      []string
   622  		approvers      []string
   623  		owners         map[string]sets.String
   624  		expectedStatus map[string]sets.String
   625  	}{
   626  		{
   627  			testName:       "Empty PR",
   628  			filenames:      []string{},
   629  			approvers:      []string{},
   630  			owners:         map[string]sets.String{},
   631  			expectedStatus: map[string]sets.String{},
   632  		},
   633  		{
   634  			testName:       "No approvers",
   635  			filenames:      []string{"a/a", "c"},
   636  			approvers:      []string{},
   637  			owners:         map[string]sets.String{"": sets.NewString("RootOwner")},
   638  			expectedStatus: map[string]sets.String{"": {}},
   639  		},
   640  		{
   641  			testName: "Approvers approves some",
   642  			filenames: []string{
   643  				"a/a",
   644  				"c/c",
   645  			},
   646  			approvers: []string{"CApprover"},
   647  			owners: map[string]sets.String{
   648  				"a": sets.NewString("AApprover"),
   649  				"c": sets.NewString("CApprover"),
   650  			},
   651  			expectedStatus: map[string]sets.String{
   652  				"a": {},
   653  				"c": sets.NewString("CApprover"),
   654  			},
   655  		},
   656  		{
   657  			testName: "Multiple approvers",
   658  			filenames: []string{
   659  				"a/a",
   660  				"c/c",
   661  			},
   662  			approvers: []string{"RootApprover", "CApprover"},
   663  			owners: map[string]sets.String{
   664  				"":  sets.NewString("RootApprover"),
   665  				"a": sets.NewString("AApprover"),
   666  				"c": sets.NewString("CApprover"),
   667  			},
   668  			expectedStatus: map[string]sets.String{
   669  				"a": sets.NewString("RootApprover"),
   670  				"c": sets.NewString("RootApprover", "CApprover"),
   671  			},
   672  		},
   673  		{
   674  			testName:       "Case-insensitive approvers",
   675  			filenames:      []string{"file"},
   676  			approvers:      []string{"RootApprover"},
   677  			owners:         map[string]sets.String{"": sets.NewString("rOOtaPProver")},
   678  			expectedStatus: map[string]sets.String{"": sets.NewString("RootApprover")},
   679  		},
   680  	}
   681  
   682  	for _, test := range tests {
   683  		testApprovers := NewApprovers(Owners{filenames: test.filenames, repo: createFakeRepo(test.owners), log: logrus.WithField("plugin", "some_plugin")})
   684  		for _, approver := range test.approvers {
   685  			testApprovers.AddApprover(approver, "REFERENCE", false)
   686  		}
   687  		calculated := testApprovers.GetFilesApprovers()
   688  		if !reflect.DeepEqual(test.expectedStatus, calculated) {
   689  			t.Errorf("Failed for test %v.  Expected approval status: %v. Found %v", test.testName, test.expectedStatus, calculated)
   690  		}
   691  	}
   692  }
   693  
   694  func TestGetMessage(t *testing.T) {
   695  	ap := NewApprovers(
   696  		Owners{
   697  			filenames: []string{"a/a.go", "b/b.go"},
   698  			repo: createFakeRepo(map[string]sets.String{
   699  				"a": sets.NewString("Alice"),
   700  				"b": sets.NewString("Bill"),
   701  			}),
   702  			log: logrus.WithField("plugin", "some_plugin"),
   703  		},
   704  	)
   705  	ap.RequireIssue = true
   706  	ap.AddApprover("Bill", "REFERENCE", false)
   707  
   708  	want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED**
   709  
   710  This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Bill</a>*
   711  To fully approve this pull request, please assign additional approvers.
   712  We suggest the following additional approver: **alice**
   713  
   714  If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @alice`" + ` in a comment when ready.
   715  
   716  *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + `
   717  
   718  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   719  
   720  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   721  
   722  <details open>
   723  Needs approval from an approver in each of these files:
   724  
   725  - **[a/OWNERS](https://github.com/org/project/blob/dev/a/OWNERS)**
   726  - ~~[b/OWNERS](https://github.com/org/project/blob/dev/b/OWNERS)~~ [Bill]
   727  
   728  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   729  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   730  </details>
   731  <!-- META={"approvers":["alice"]} -->`
   732  	if got := GetMessage(ap, "org", "project", "dev"); got == nil {
   733  		t.Error("GetMessage() failed")
   734  	} else if *got != want {
   735  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   736  	}
   737  }
   738  
   739  func TestGetMessageAllApproved(t *testing.T) {
   740  	ap := NewApprovers(
   741  		Owners{
   742  			filenames: []string{"a/a.go", "b/b.go"},
   743  			repo: createFakeRepo(map[string]sets.String{
   744  				"a": sets.NewString("Alice"),
   745  				"b": sets.NewString("Bill"),
   746  			}),
   747  			log: logrus.WithField("plugin", "some_plugin"),
   748  		},
   749  	)
   750  	ap.RequireIssue = true
   751  	ap.AddApprover("Alice", "REFERENCE", false)
   752  	ap.AddLGTMer("Bill", "REFERENCE", false)
   753  
   754  	want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED**
   755  
   756  This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="LGTM">Bill</a>*
   757  
   758  *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + `
   759  
   760  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   761  
   762  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   763  
   764  <details >
   765  Needs approval from an approver in each of these files:
   766  
   767  - ~~[a/OWNERS](https://github.com/org/project/blob/master/a/OWNERS)~~ [Alice]
   768  - ~~[b/OWNERS](https://github.com/org/project/blob/master/b/OWNERS)~~ [Bill]
   769  
   770  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   771  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   772  </details>
   773  <!-- META={"approvers":[]} -->`
   774  	if got := GetMessage(ap, "org", "project", "master"); got == nil {
   775  		t.Error("GetMessage() failed")
   776  	} else if *got != want {
   777  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   778  	}
   779  }
   780  
   781  func TestGetMessageNoneApproved(t *testing.T) {
   782  	ap := NewApprovers(
   783  		Owners{
   784  			filenames: []string{"a/a.go", "b/b.go"},
   785  			repo: createFakeRepo(map[string]sets.String{
   786  				"a": sets.NewString("Alice"),
   787  				"b": sets.NewString("Bill"),
   788  			}),
   789  			log: logrus.WithField("plugin", "some_plugin"),
   790  		},
   791  	)
   792  	ap.AddAuthorSelfApprover("John", "REFERENCE", false)
   793  	ap.RequireIssue = true
   794  	want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED**
   795  
   796  This pull-request has been approved by: *<a href="REFERENCE" title="Author self-approved">John</a>*
   797  To fully approve this pull request, please assign additional approvers.
   798  We suggest the following additional approvers: **alice**, **bill**
   799  
   800  If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @alice @bill`" + ` in a comment when ready.
   801  
   802  *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + `
   803  
   804  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   805  
   806  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   807  
   808  <details open>
   809  Needs approval from an approver in each of these files:
   810  
   811  - **[a/OWNERS](https://github.com/org/project/blob/master/a/OWNERS)**
   812  - **[b/OWNERS](https://github.com/org/project/blob/master/b/OWNERS)**
   813  
   814  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   815  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   816  </details>
   817  <!-- META={"approvers":["alice","bill"]} -->`
   818  	if got := GetMessage(ap, "org", "project", "master"); got == nil {
   819  		t.Error("GetMessage() failed")
   820  	} else if *got != want {
   821  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   822  	}
   823  }
   824  
   825  func TestGetMessageApprovedIssueAssociated(t *testing.T) {
   826  	ap := NewApprovers(
   827  		Owners{
   828  			filenames: []string{"a/a.go", "b/b.go"},
   829  			repo: createFakeRepo(map[string]sets.String{
   830  				"a": sets.NewString("Alice"),
   831  				"b": sets.NewString("Bill"),
   832  			}),
   833  			log: logrus.WithField("plugin", "some_plugin"),
   834  		},
   835  	)
   836  	ap.RequireIssue = true
   837  	ap.AssociatedIssue = 12345
   838  	ap.AddAuthorSelfApprover("John", "REFERENCE", false)
   839  	ap.AddApprover("Bill", "REFERENCE", false)
   840  	ap.AddApprover("Alice", "REFERENCE", false)
   841  
   842  	want := `[APPROVALNOTIFIER] This PR is **APPROVED**
   843  
   844  This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>*, *<a href="REFERENCE" title="Author self-approved">John</a>*
   845  
   846  Associated issue: *#12345*
   847  
   848  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   849  
   850  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   851  
   852  <details >
   853  Needs approval from an approver in each of these files:
   854  
   855  - ~~[a/OWNERS](https://github.com/org/project/blob/master/a/OWNERS)~~ [Alice]
   856  - ~~[b/OWNERS](https://github.com/org/project/blob/master/b/OWNERS)~~ [Bill]
   857  
   858  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   859  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   860  </details>
   861  <!-- META={"approvers":[]} -->`
   862  	if got := GetMessage(ap, "org", "project", "master"); got == nil {
   863  		t.Error("GetMessage() failed")
   864  	} else if *got != want {
   865  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   866  	}
   867  }
   868  
   869  func TestGetMessageApprovedNoIssueByPassed(t *testing.T) {
   870  	ap := NewApprovers(
   871  		Owners{
   872  			filenames: []string{"a/a.go", "b/b.md"},
   873  			repo: createFakeRepo(map[string]sets.String{
   874  				"a": sets.NewString("Alice"),
   875  				"b": sets.NewString("Bill"),
   876  			}),
   877  			log: logrus.WithField("plugin", "some_plugin"),
   878  		},
   879  	)
   880  	ap.RequireIssue = true
   881  	ap.AddAuthorSelfApprover("John", "REFERENCE", false)
   882  	ap.AddApprover("Bill", "REFERENCE", true)
   883  	ap.AddApprover("Alice", "REFERENCE", true)
   884  
   885  	want := `[APPROVALNOTIFIER] This PR is **APPROVED**
   886  
   887  This pull-request has been approved by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>*, *<a href="REFERENCE" title="Author self-approved">John</a>*
   888  
   889  Associated issue requirement bypassed by: *<a href="REFERENCE" title="Approved">Alice</a>*, *<a href="REFERENCE" title="Approved">Bill</a>*
   890  
   891  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   892  
   893  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   894  
   895  <details >
   896  Needs approval from an approver in each of these files:
   897  
   898  - ~~[a/OWNERS](https://github.com/org/project/blob/master/a/OWNERS)~~ [Alice]
   899  - ~~[b/OWNERS](https://github.com/org/project/blob/master/b/OWNERS)~~ [Bill]
   900  
   901  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   902  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   903  </details>
   904  <!-- META={"approvers":[]} -->`
   905  	if got := GetMessage(ap, "org", "project", "master"); got == nil {
   906  		t.Error("GetMessage() failed")
   907  	} else if *got != want {
   908  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   909  	}
   910  }
   911  
   912  func TestGetMessageMDOwners(t *testing.T) {
   913  	ap := NewApprovers(
   914  		Owners{
   915  			filenames: []string{"a/a.go", "b/README.md"},
   916  			repo: createFakeRepo(map[string]sets.String{
   917  				"a":           sets.NewString("Alice"),
   918  				"b":           sets.NewString("Bill"),
   919  				"b/README.md": sets.NewString("Doctor"),
   920  			}),
   921  			log: logrus.WithField("plugin", "some_plugin"),
   922  		},
   923  	)
   924  	ap.AddAuthorSelfApprover("John", "REFERENCE", false)
   925  	ap.RequireIssue = true
   926  	want := `[APPROVALNOTIFIER] This PR is **NOT APPROVED**
   927  
   928  This pull-request has been approved by: *<a href="REFERENCE" title="Author self-approved">John</a>*
   929  To fully approve this pull request, please assign additional approvers.
   930  We suggest the following additional approvers: **alice**, **doctor**
   931  
   932  If they are not already assigned, you can assign the PR to them by writing ` + "`/assign @alice @doctor`" + ` in a comment when ready.
   933  
   934  *No associated issue*. Update pull-request body to add a reference to an issue, or get approval with ` + "`/approve no-issue`" + `
   935  
   936  The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands).
   937  
   938  The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)
   939  
   940  <details open>
   941  Needs approval from an approver in each of these files:
   942  
   943  - **[a/OWNERS](https://github.com/org/project/blob/master/a/OWNERS)**
   944  - **[b/README.md](https://github.com/org/project/blob/master/b/README.md)**
   945  
   946  Approvers can indicate their approval by writing ` + "`/approve`" + ` in a comment
   947  Approvers can cancel approval by writing ` + "`/approve cancel`" + ` in a comment
   948  </details>
   949  <!-- META={"approvers":["alice","doctor"]} -->`
   950  	if got := GetMessage(ap, "org", "project", "master"); got == nil {
   951  		t.Error("GetMessage() failed")
   952  	} else if *got != want {
   953  		t.Errorf("GetMessage() = %+v, want = %+v", *got, want)
   954  	}
   955  }