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