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