github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/label/label_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 label
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/go-cmp/cmp/cmpopts"
    27  	"github.com/sirupsen/logrus"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"sigs.k8s.io/prow/pkg/config"
    30  	"sigs.k8s.io/prow/pkg/github"
    31  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    32  	"sigs.k8s.io/prow/pkg/labels"
    33  	"sigs.k8s.io/prow/pkg/plugins"
    34  )
    35  
    36  const (
    37  	orgMember    = "Alice"
    38  	nonOrgMember = "Bob"
    39  )
    40  
    41  func formatWithPRInfo(labels ...string) []string {
    42  	r := []string{}
    43  	for _, l := range labels {
    44  		r = append(r, fmt.Sprintf("%s/%s#%d:%s", "org", "repo", 1, l))
    45  	}
    46  	if len(r) == 0 {
    47  		return nil
    48  	}
    49  	return r
    50  }
    51  
    52  func TestHandleComment(t *testing.T) {
    53  	type testCase struct {
    54  		name                  string
    55  		body                  string
    56  		commenter             string
    57  		extraLabels           []string
    58  		restrictedLabels      map[string][]plugins.RestrictedLabel
    59  		expectedNewLabels     []string
    60  		expectedRemovedLabels []string
    61  		expectedBotComment    bool
    62  		repoLabels            []string
    63  		issueLabels           []string
    64  		expectedCommentText   string
    65  		action                github.GenericCommentEventAction
    66  		teams                 map[string]map[string]fakegithub.TeamWithMembers
    67  	}
    68  	testcases := []testCase{
    69  		{
    70  			name:                  "Irrelevant comment",
    71  			body:                  "irrelelvant",
    72  			expectedNewLabels:     []string{},
    73  			expectedRemovedLabels: []string{},
    74  			repoLabels:            []string{},
    75  			issueLabels:           []string{},
    76  			commenter:             orgMember,
    77  			action:                github.GenericCommentActionCreated,
    78  		},
    79  		{
    80  			name:                  "Empty Area",
    81  			body:                  "/area",
    82  			expectedNewLabels:     []string{},
    83  			expectedRemovedLabels: []string{},
    84  			repoLabels:            []string{"area/infra"},
    85  			issueLabels:           []string{"area/infra"},
    86  			commenter:             orgMember,
    87  			action:                github.GenericCommentActionCreated,
    88  		},
    89  		{
    90  			name:                  "Add Single Area Label",
    91  			body:                  "/area infra",
    92  			repoLabels:            []string{"area/infra"},
    93  			issueLabels:           []string{},
    94  			expectedNewLabels:     formatWithPRInfo("area/infra"),
    95  			expectedRemovedLabels: []string{},
    96  			commenter:             orgMember,
    97  			action:                github.GenericCommentActionCreated,
    98  		},
    99  		{
   100  			name:                  "Add Single Area Label when already present on Issue",
   101  			body:                  "/area infra",
   102  			repoLabels:            []string{"area/infra"},
   103  			issueLabels:           []string{"area/infra"},
   104  			expectedNewLabels:     []string{},
   105  			expectedRemovedLabels: []string{},
   106  			commenter:             orgMember,
   107  			action:                github.GenericCommentActionCreated,
   108  		},
   109  		{
   110  			name:                  "Add Single Priority Label",
   111  			body:                  "/priority critical",
   112  			repoLabels:            []string{"area/infra", "priority/critical"},
   113  			issueLabels:           []string{},
   114  			expectedNewLabels:     formatWithPRInfo("priority/critical"),
   115  			expectedRemovedLabels: []string{},
   116  			commenter:             orgMember,
   117  			action:                github.GenericCommentActionCreated,
   118  		},
   119  		{
   120  			name:                  "Add Single Kind Label",
   121  			body:                  "/kind bug",
   122  			repoLabels:            []string{"area/infra", "priority/critical", labels.Bug},
   123  			issueLabels:           []string{},
   124  			expectedNewLabels:     formatWithPRInfo(labels.Bug),
   125  			expectedRemovedLabels: []string{},
   126  			commenter:             orgMember,
   127  			action:                github.GenericCommentActionCreated,
   128  		},
   129  		{
   130  			name:                  "Add Single Triage Label",
   131  			body:                  "/triage needs-information",
   132  			repoLabels:            []string{"area/infra", "triage/needs-information"},
   133  			issueLabels:           []string{"area/infra"},
   134  			expectedNewLabels:     formatWithPRInfo("triage/needs-information"),
   135  			expectedRemovedLabels: []string{},
   136  			commenter:             orgMember,
   137  			action:                github.GenericCommentActionCreated,
   138  		},
   139  		{
   140  			name:                  "Org member can add triage/accepted label",
   141  			body:                  "/triage accepted",
   142  			repoLabels:            []string{"triage/accepted"},
   143  			issueLabels:           []string{},
   144  			expectedNewLabels:     formatWithPRInfo("triage/accepted"),
   145  			expectedRemovedLabels: []string{},
   146  			commenter:             orgMember,
   147  			action:                github.GenericCommentActionCreated,
   148  		},
   149  		{
   150  			name:                  "Non org member cannot add triage/accepted label",
   151  			body:                  "/triage accepted",
   152  			repoLabels:            []string{"triage/accepted", "kind/bug"},
   153  			issueLabels:           []string{"kind/bug"},
   154  			expectedNewLabels:     formatWithPRInfo(),
   155  			expectedRemovedLabels: []string{},
   156  			commenter:             nonOrgMember,
   157  			expectedBotComment:    true,
   158  			expectedCommentText:   "The label `triage/accepted` cannot be applied. Only GitHub organization members can add the label.",
   159  			action:                github.GenericCommentActionCreated,
   160  		},
   161  		{
   162  			name:                  "Non org member can add triage/needs-information label",
   163  			body:                  "/triage needs-information",
   164  			repoLabels:            []string{"area/infra", "triage/needs-information"},
   165  			issueLabels:           []string{"area/infra"},
   166  			expectedNewLabels:     formatWithPRInfo("triage/needs-information"),
   167  			expectedRemovedLabels: []string{},
   168  			commenter:             nonOrgMember,
   169  			action:                github.GenericCommentActionCreated,
   170  		},
   171  		{
   172  			name:                  "Adding Labels is Case Insensitive",
   173  			body:                  "/kind BuG",
   174  			repoLabels:            []string{"area/infra", "priority/critical", labels.Bug},
   175  			issueLabels:           []string{},
   176  			expectedNewLabels:     formatWithPRInfo(labels.Bug),
   177  			expectedRemovedLabels: []string{},
   178  			commenter:             orgMember,
   179  			action:                github.GenericCommentActionCreated,
   180  		},
   181  		{
   182  			name:                  "Adding Labels is Case Insensitive",
   183  			body:                  "/kind bug",
   184  			repoLabels:            []string{"area/infra", "priority/critical", labels.Bug},
   185  			issueLabels:           []string{},
   186  			expectedNewLabels:     formatWithPRInfo(labels.Bug),
   187  			expectedRemovedLabels: []string{},
   188  			commenter:             orgMember,
   189  			action:                github.GenericCommentActionCreated,
   190  		},
   191  		{
   192  			name:                  "Can't Add Non Existent Label",
   193  			body:                  "/priority critical",
   194  			repoLabels:            []string{"area/infra"},
   195  			issueLabels:           []string{},
   196  			expectedNewLabels:     formatWithPRInfo(),
   197  			expectedRemovedLabels: []string{},
   198  			commenter:             orgMember,
   199  			expectedBotComment:    true,
   200  			expectedCommentText:   "The label(s) `priority/critical` cannot be applied, because the repository doesn't have them.",
   201  			action:                github.GenericCommentActionCreated,
   202  		},
   203  		{
   204  			name:                  "Non Org Member Can't Add",
   205  			body:                  "/area infra",
   206  			repoLabels:            []string{"area/infra", "priority/critical", labels.Bug},
   207  			issueLabels:           []string{},
   208  			expectedNewLabels:     formatWithPRInfo("area/infra"),
   209  			expectedRemovedLabels: []string{},
   210  			commenter:             nonOrgMember,
   211  			action:                github.GenericCommentActionCreated,
   212  		},
   213  		{
   214  			name:                  "Command must start at the beginning of the line",
   215  			body:                  "  /area infra",
   216  			repoLabels:            []string{"area/infra", "area/api", "priority/critical", "priority/urgent", "priority/important", labels.Bug},
   217  			issueLabels:           []string{},
   218  			expectedNewLabels:     formatWithPRInfo(),
   219  			expectedRemovedLabels: []string{},
   220  			commenter:             orgMember,
   221  			action:                github.GenericCommentActionCreated,
   222  		},
   223  		{
   224  			name:                  "Can't Add Labels Non Existing Labels",
   225  			body:                  "/area lgtm",
   226  			repoLabels:            []string{"area/infra", "area/api", "priority/critical"},
   227  			issueLabels:           []string{},
   228  			expectedNewLabels:     formatWithPRInfo(),
   229  			expectedRemovedLabels: []string{},
   230  			commenter:             orgMember,
   231  			expectedBotComment:    true,
   232  			expectedCommentText:   "The label(s) `area/lgtm` cannot be applied, because the repository doesn't have them.",
   233  			action:                github.GenericCommentActionCreated,
   234  		},
   235  		{
   236  			name:                  "Add Multiple Area Labels",
   237  			body:                  "/area api infra",
   238  			repoLabels:            []string{"area/infra", "area/api", "priority/critical", "priority/urgent"},
   239  			issueLabels:           []string{},
   240  			expectedNewLabels:     formatWithPRInfo("area/api", "area/infra"),
   241  			expectedRemovedLabels: []string{},
   242  			commenter:             orgMember,
   243  			action:                github.GenericCommentActionCreated,
   244  		},
   245  		{
   246  			name:                  "Add Multiple Area Labels one already present on Issue",
   247  			body:                  "/area api infra",
   248  			repoLabels:            []string{"area/infra", "area/api", "priority/critical", "priority/urgent"},
   249  			issueLabels:           []string{"area/api"},
   250  			expectedNewLabels:     formatWithPRInfo("area/infra"),
   251  			expectedRemovedLabels: []string{},
   252  			commenter:             orgMember,
   253  			action:                github.GenericCommentActionCreated,
   254  		},
   255  		{
   256  			name:                  "Add Multiple Priority Labels",
   257  			body:                  "/priority critical important",
   258  			repoLabels:            []string{"priority/critical", "priority/important"},
   259  			issueLabels:           []string{},
   260  			expectedNewLabels:     formatWithPRInfo("priority/critical", "priority/important"),
   261  			expectedRemovedLabels: []string{},
   262  			commenter:             orgMember,
   263  			action:                github.GenericCommentActionCreated,
   264  		},
   265  		{
   266  			name:                  "Add Multiple Area Labels, With Trailing Whitespace",
   267  			body:                  "/area api infra ",
   268  			repoLabels:            []string{"area/infra", "area/api"},
   269  			issueLabels:           []string{},
   270  			expectedNewLabels:     formatWithPRInfo("area/api", "area/infra"),
   271  			expectedRemovedLabels: []string{},
   272  			commenter:             orgMember,
   273  			action:                github.GenericCommentActionCreated,
   274  		},
   275  		{
   276  			name:                  "Label Prefix Must Match Command (Area-Priority Mismatch)",
   277  			body:                  "/area urgent",
   278  			repoLabels:            []string{"area/infra", "area/api", "priority/critical", "priority/urgent"},
   279  			issueLabels:           []string{},
   280  			expectedNewLabels:     formatWithPRInfo(),
   281  			expectedRemovedLabels: []string{},
   282  			commenter:             orgMember,
   283  			expectedBotComment:    true,
   284  			expectedCommentText:   "The label(s) `area/urgent` cannot be applied, because the repository doesn't have them.",
   285  			action:                github.GenericCommentActionCreated,
   286  		},
   287  		{
   288  			name:                  "Label Prefix Must Match Command (Priority-Area Mismatch)",
   289  			body:                  "/priority infra",
   290  			repoLabels:            []string{"area/infra", "area/api", "priority/critical", "priority/urgent"},
   291  			issueLabels:           []string{},
   292  			expectedNewLabels:     formatWithPRInfo(),
   293  			expectedRemovedLabels: []string{},
   294  			commenter:             orgMember,
   295  			expectedBotComment:    true,
   296  			expectedCommentText:   "The label(s) `priority/infra` cannot be applied, because the repository doesn't have them.",
   297  			action:                github.GenericCommentActionCreated,
   298  		},
   299  		{
   300  			name:                  "Add Multiple Area Labels (Some Valid)",
   301  			body:                  "/area lgtm infra",
   302  			repoLabels:            []string{"area/infra", "area/api"},
   303  			issueLabels:           []string{},
   304  			expectedNewLabels:     formatWithPRInfo("area/infra"),
   305  			expectedRemovedLabels: []string{},
   306  			commenter:             orgMember,
   307  			expectedBotComment:    true,
   308  			expectedCommentText:   "The label(s) `area/lgtm` cannot be applied, because the repository doesn't have them.",
   309  			action:                github.GenericCommentActionCreated,
   310  		},
   311  		{
   312  			name:                  "Add Multiple Committee Labels (Some Valid)",
   313  			body:                  "/committee steering calamity",
   314  			repoLabels:            []string{"committee/conduct", "committee/steering"},
   315  			issueLabels:           []string{},
   316  			expectedNewLabels:     formatWithPRInfo("committee/steering"),
   317  			expectedRemovedLabels: []string{},
   318  			commenter:             orgMember,
   319  			expectedBotComment:    true,
   320  			expectedCommentText:   "The label(s) `committee/calamity` cannot be applied, because the repository doesn't have them.",
   321  			action:                github.GenericCommentActionCreated,
   322  		},
   323  		{
   324  			name:                  "Non org member adds multiple triage labels (some valid)",
   325  			body:                  "/triage needs-information accepted",
   326  			repoLabels:            []string{"triage/needs-information", "triage/accepted"},
   327  			issueLabels:           []string{},
   328  			expectedNewLabels:     formatWithPRInfo("triage/needs-information"),
   329  			expectedRemovedLabels: []string{},
   330  			commenter:             nonOrgMember,
   331  			expectedBotComment:    true,
   332  			expectedCommentText:   "The label `triage/accepted` cannot be applied. Only GitHub organization members can add the label.",
   333  			action:                github.GenericCommentActionCreated,
   334  		},
   335  		{
   336  			name:                  "Add Multiple Types of Labels Different Lines",
   337  			body:                  "/priority urgent\n/area infra",
   338  			repoLabels:            []string{"area/infra", "priority/urgent"},
   339  			issueLabels:           []string{},
   340  			expectedNewLabels:     formatWithPRInfo("priority/urgent", "area/infra"),
   341  			expectedRemovedLabels: []string{},
   342  			commenter:             orgMember,
   343  			action:                github.GenericCommentActionCreated,
   344  		},
   345  		{
   346  			name:                  "Remove Area Label when no such Label on Repo",
   347  			body:                  "/remove-area infra",
   348  			repoLabels:            []string{},
   349  			issueLabels:           []string{},
   350  			expectedNewLabels:     []string{},
   351  			expectedRemovedLabels: []string{},
   352  			commenter:             orgMember,
   353  			expectedBotComment:    true,
   354  			action:                github.GenericCommentActionCreated,
   355  		},
   356  		{
   357  			name:                  "Remove Area Label when no such Label on Issue",
   358  			body:                  "/remove-area infra",
   359  			repoLabels:            []string{"area/infra"},
   360  			issueLabels:           []string{},
   361  			expectedNewLabels:     []string{},
   362  			expectedRemovedLabels: []string{},
   363  			commenter:             orgMember,
   364  			expectedBotComment:    true,
   365  			action:                github.GenericCommentActionCreated,
   366  		},
   367  		{
   368  			name:                  "Remove Area Label",
   369  			body:                  "/remove-area infra",
   370  			repoLabels:            []string{"area/infra"},
   371  			issueLabels:           []string{"area/infra"},
   372  			expectedNewLabels:     []string{},
   373  			expectedRemovedLabels: formatWithPRInfo("area/infra"),
   374  			commenter:             orgMember,
   375  			action:                github.GenericCommentActionCreated,
   376  		},
   377  		{
   378  			name:                  "Remove Committee Label",
   379  			body:                  "/remove-committee infinite-monkeys",
   380  			repoLabels:            []string{"area/infra", "sig/testing", "committee/infinite-monkeys"},
   381  			issueLabels:           []string{"area/infra", "sig/testing", "committee/infinite-monkeys"},
   382  			expectedNewLabels:     []string{},
   383  			expectedRemovedLabels: formatWithPRInfo("committee/infinite-monkeys"),
   384  			commenter:             orgMember,
   385  			action:                github.GenericCommentActionCreated,
   386  		},
   387  		{
   388  			name:                  "Remove Kind Label",
   389  			body:                  "/remove-kind api-server",
   390  			repoLabels:            []string{"area/infra", "priority/high", "kind/api-server", "needs-kind"},
   391  			issueLabels:           []string{"area/infra", "priority/high", "kind/api-server"},
   392  			expectedNewLabels:     formatWithPRInfo("needs-kind"),
   393  			expectedRemovedLabels: formatWithPRInfo("kind/api-server"),
   394  			commenter:             orgMember,
   395  			action:                github.GenericCommentActionCreated,
   396  		},
   397  		{
   398  			name:                  "Remove Priority Label",
   399  			body:                  "/remove-priority high",
   400  			repoLabels:            []string{"area/infra", "priority/high", "needs-priority"},
   401  			issueLabels:           []string{"area/infra", "priority/high"},
   402  			expectedNewLabels:     formatWithPRInfo("needs-priority"),
   403  			expectedRemovedLabels: formatWithPRInfo("priority/high"),
   404  			commenter:             orgMember,
   405  			action:                github.GenericCommentActionCreated,
   406  		},
   407  		{
   408  			name:                  "Remove SIG Label",
   409  			body:                  "/remove-sig testing",
   410  			repoLabels:            []string{"area/infra", "sig/testing", "needs-sig"},
   411  			issueLabels:           []string{"area/infra", "sig/testing"},
   412  			expectedNewLabels:     formatWithPRInfo("needs-sig"),
   413  			expectedRemovedLabels: formatWithPRInfo("sig/testing"),
   414  			commenter:             orgMember,
   415  			action:                github.GenericCommentActionCreated,
   416  		},
   417  		{
   418  			name:                  "Remove one of many SIG Label",
   419  			body:                  "/remove-sig testing",
   420  			repoLabels:            []string{"area/infra", "sig/testing", "sig/node", "sig/auth", "needs-sig"},
   421  			issueLabels:           []string{"area/infra", "sig/testing", "sig/node", "sig/auth"},
   422  			expectedNewLabels:     []string{},
   423  			expectedRemovedLabels: formatWithPRInfo("sig/testing"),
   424  			commenter:             orgMember,
   425  			action:                github.GenericCommentActionCreated,
   426  		},
   427  		{
   428  			name:                  "Add and Remove SIG Label",
   429  			body:                  "/remove-sig testing\n/sig node",
   430  			repoLabels:            []string{"area/infra", "sig/testing", "sig/node", "needs-sig"},
   431  			issueLabels:           []string{"area/infra", "sig/testing"},
   432  			expectedNewLabels:     formatWithPRInfo("sig/node"),
   433  			expectedRemovedLabels: formatWithPRInfo("sig/testing"),
   434  			commenter:             orgMember,
   435  			action:                github.GenericCommentActionCreated,
   436  		},
   437  		{
   438  			name:                  "Remove WG Policy",
   439  			body:                  "/remove-wg policy",
   440  			repoLabels:            []string{"area/infra", "wg/policy"},
   441  			issueLabels:           []string{"area/infra", "wg/policy"},
   442  			expectedNewLabels:     []string{},
   443  			expectedRemovedLabels: formatWithPRInfo("wg/policy"),
   444  			commenter:             orgMember,
   445  			action:                github.GenericCommentActionCreated,
   446  		},
   447  		{
   448  			name:                  "Remove Triage Label",
   449  			body:                  "/remove-triage needs-information accepted",
   450  			repoLabels:            []string{"area/infra", "triage/needs-information", "triage/accepted", "needs-triage"},
   451  			issueLabels:           []string{"area/infra", "triage/needs-information", "triage/accepted"},
   452  			expectedNewLabels:     formatWithPRInfo("needs-triage"),
   453  			expectedRemovedLabels: formatWithPRInfo("triage/needs-information", "triage/accepted"),
   454  			commenter:             orgMember,
   455  			action:                github.GenericCommentActionCreated,
   456  		},
   457  		{
   458  			name:                  "Remove Multiple Labels",
   459  			body:                  "/remove-priority low high\n/remove-kind api-server\n/remove-area  infra",
   460  			repoLabels:            []string{"area/infra", "priority/high", "priority/low", "kind/api-server"},
   461  			issueLabels:           []string{"area/infra", "priority/high", "priority/low", "kind/api-server"},
   462  			expectedNewLabels:     []string{},
   463  			expectedRemovedLabels: formatWithPRInfo("priority/low", "priority/high", "kind/api-server", "area/infra"),
   464  			commenter:             orgMember,
   465  			expectedBotComment:    true,
   466  			action:                github.GenericCommentActionCreated,
   467  		},
   468  		{
   469  			name:                  "Add and Remove Label at the same time",
   470  			body:                  "/remove-area infra\n/area test",
   471  			repoLabels:            []string{"area/infra", "area/test"},
   472  			issueLabels:           []string{"area/infra"},
   473  			expectedNewLabels:     formatWithPRInfo("area/test"),
   474  			expectedRemovedLabels: formatWithPRInfo("area/infra"),
   475  			commenter:             orgMember,
   476  			action:                github.GenericCommentActionCreated,
   477  		},
   478  		{
   479  			name:                  "Add and Remove the same Label",
   480  			body:                  "/remove-area infra\n/area infra",
   481  			repoLabels:            []string{"area/infra"},
   482  			issueLabels:           []string{"area/infra"},
   483  			expectedNewLabels:     []string{},
   484  			expectedRemovedLabels: formatWithPRInfo("area/infra"),
   485  			commenter:             orgMember,
   486  			action:                github.GenericCommentActionCreated,
   487  		},
   488  		{
   489  			name:                  "Multiple Add and Delete Labels",
   490  			body:                  "/remove-area ruby\n/remove-kind srv\n/remove-priority l m\n/area go\n/kind cli\n/priority h",
   491  			repoLabels:            []string{"area/go", "area/ruby", "kind/cli", "kind/srv", "priority/h", "priority/m", "priority/l"},
   492  			issueLabels:           []string{"area/ruby", "kind/srv", "priority/l", "priority/m"},
   493  			expectedNewLabels:     formatWithPRInfo("area/go", "kind/cli", "priority/h"),
   494  			expectedRemovedLabels: formatWithPRInfo("area/ruby", "kind/srv", "priority/l", "priority/m"),
   495  			commenter:             orgMember,
   496  			action:                github.GenericCommentActionCreated,
   497  		},
   498  		{
   499  			name:                  "Do nothing with empty /label command",
   500  			body:                  "/label",
   501  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   502  			repoLabels:            []string{"orchestrator/foo"},
   503  			issueLabels:           []string{},
   504  			expectedNewLabels:     []string{},
   505  			expectedRemovedLabels: []string{},
   506  			commenter:             orgMember,
   507  			action:                github.GenericCommentActionCreated,
   508  		},
   509  		{
   510  			name:                  "Do nothing with empty /remove-label command",
   511  			body:                  "/remove-label",
   512  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   513  			repoLabels:            []string{"orchestrator/foo"},
   514  			issueLabels:           []string{},
   515  			expectedNewLabels:     []string{},
   516  			expectedRemovedLabels: []string{},
   517  			commenter:             orgMember,
   518  			action:                github.GenericCommentActionCreated,
   519  		},
   520  		{
   521  			name:                  "Add custom label",
   522  			body:                  "/label orchestrator/foo",
   523  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   524  			repoLabels:            []string{"orchestrator/foo"},
   525  			issueLabels:           []string{},
   526  			expectedNewLabels:     formatWithPRInfo("orchestrator/foo"),
   527  			expectedRemovedLabels: []string{},
   528  			commenter:             orgMember,
   529  			action:                github.GenericCommentActionCreated,
   530  		},
   531  		{
   532  			name:                  "Add custom label with trailing space",
   533  			body:                  "/label orchestrator/foo ",
   534  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   535  			repoLabels:            []string{"orchestrator/foo"},
   536  			issueLabels:           []string{},
   537  			expectedNewLabels:     formatWithPRInfo("orchestrator/foo"),
   538  			expectedRemovedLabels: []string{},
   539  			commenter:             orgMember,
   540  			action:                github.GenericCommentActionCreated,
   541  		},
   542  		{
   543  			name:                  "Add custom label with trailing LF newline",
   544  			body:                  "/label orchestrator/foo\n",
   545  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   546  			repoLabels:            []string{"orchestrator/foo"},
   547  			issueLabels:           []string{},
   548  			expectedNewLabels:     formatWithPRInfo("orchestrator/foo"),
   549  			expectedRemovedLabels: []string{},
   550  			commenter:             orgMember,
   551  			action:                github.GenericCommentActionCreated,
   552  		},
   553  		{
   554  			name:                  "Add custom label with trailing CRLF newline",
   555  			body:                  "/label orchestrator/foo\r\n",
   556  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   557  			repoLabels:            []string{"orchestrator/foo"},
   558  			issueLabels:           []string{},
   559  			expectedNewLabels:     formatWithPRInfo("orchestrator/foo"),
   560  			expectedRemovedLabels: []string{},
   561  			commenter:             orgMember,
   562  			action:                github.GenericCommentActionCreated,
   563  		},
   564  		{
   565  			name:                  "Cannot add missing custom label",
   566  			body:                  "/label orchestrator/foo",
   567  			extraLabels:           []string{"orchestrator/jar", "orchestrator/bar"},
   568  			repoLabels:            []string{"orchestrator/foo"},
   569  			issueLabels:           []string{},
   570  			expectedNewLabels:     []string{},
   571  			expectedRemovedLabels: []string{},
   572  			commenter:             orgMember,
   573  			expectedBotComment:    true,
   574  			expectedCommentText:   "The label(s) `/label orchestrator/foo` cannot be applied. These labels are supported: `orchestrator/jar, orchestrator/bar`",
   575  			action:                github.GenericCommentActionCreated,
   576  		},
   577  		{
   578  			name:                  "Remove custom label",
   579  			body:                  "/remove-label orchestrator/foo",
   580  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   581  			repoLabels:            []string{"orchestrator/foo"},
   582  			issueLabels:           []string{"orchestrator/foo"},
   583  			expectedNewLabels:     []string{},
   584  			expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"),
   585  			commenter:             orgMember,
   586  			action:                github.GenericCommentActionCreated,
   587  		},
   588  		{
   589  			name:                  "Remove custom label with trailing space",
   590  			body:                  "/remove-label orchestrator/foo ",
   591  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   592  			repoLabels:            []string{"orchestrator/foo"},
   593  			issueLabels:           []string{"orchestrator/foo"},
   594  			expectedNewLabels:     []string{},
   595  			expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"),
   596  			commenter:             orgMember,
   597  			action:                github.GenericCommentActionCreated,
   598  		},
   599  		{
   600  			name:                  "Remove custom label with trailing LF newline",
   601  			body:                  "/remove-label orchestrator/foo\n",
   602  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   603  			repoLabels:            []string{"orchestrator/foo"},
   604  			issueLabels:           []string{"orchestrator/foo"},
   605  			expectedNewLabels:     []string{},
   606  			expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"),
   607  			commenter:             orgMember,
   608  			action:                github.GenericCommentActionCreated,
   609  		},
   610  		{
   611  			name:                  "Remove custom label with trailing CRLF newline",
   612  			body:                  "/remove-label orchestrator/foo\r\n",
   613  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   614  			repoLabels:            []string{"orchestrator/foo"},
   615  			issueLabels:           []string{"orchestrator/foo"},
   616  			expectedNewLabels:     []string{},
   617  			expectedRemovedLabels: formatWithPRInfo("orchestrator/foo"),
   618  			commenter:             orgMember,
   619  			action:                github.GenericCommentActionCreated,
   620  		},
   621  		{
   622  			name:                  "Cannot remove missing custom label",
   623  			body:                  "/remove-label orchestrator/jar",
   624  			extraLabels:           []string{"orchestrator/foo", "orchestrator/bar"},
   625  			repoLabels:            []string{"orchestrator/foo"},
   626  			issueLabels:           []string{"orchestrator/foo"},
   627  			expectedNewLabels:     []string{},
   628  			expectedRemovedLabels: []string{},
   629  			commenter:             orgMember,
   630  			expectedBotComment:    true,
   631  			expectedCommentText:   "The label(s) `/remove-label orchestrator/jar` cannot be applied. These labels are supported: `orchestrator/foo, orchestrator/bar`",
   632  			action:                github.GenericCommentActionCreated,
   633  		},
   634  		{
   635  			name:                  "Don't comment when deleting label addition",
   636  			body:                  "/kind bug",
   637  			repoLabels:            []string{"area/infra", "priority/critical", labels.Bug},
   638  			issueLabels:           []string{},
   639  			expectedNewLabels:     []string{},
   640  			expectedRemovedLabels: []string{},
   641  			commenter:             orgMember,
   642  			expectedBotComment:    false,
   643  			action:                github.GenericCommentActionDeleted,
   644  		},
   645  		{
   646  			name:                  "Don't comment when deleting label removal",
   647  			body:                  "/remove-committee infinite-monkeys",
   648  			repoLabels:            []string{"area/infra", "sig/testing", "committee/infinite-monkeys"},
   649  			issueLabels:           []string{"area/infra", "sig/testing", "committee/infinite-monkeys"},
   650  			expectedNewLabels:     []string{},
   651  			expectedRemovedLabels: []string{},
   652  			commenter:             orgMember,
   653  			expectedBotComment:    false,
   654  			action:                github.GenericCommentActionDeleted,
   655  		},
   656  		{
   657  			name:                  "Don't take action while editing body",
   658  			body:                  "/kind bug",
   659  			repoLabels:            []string{labels.Bug},
   660  			issueLabels:           []string{},
   661  			expectedNewLabels:     []string{},
   662  			expectedRemovedLabels: []string{},
   663  			commenter:             orgMember,
   664  			expectedBotComment:    false,
   665  			action:                github.GenericCommentActionEdited,
   666  		},
   667  		{
   668  			name: "Strip markdown comments",
   669  			body: `
   670  <!--
   671  /kind bug
   672  /kind cleanup
   673  -->
   674  /area infra
   675  `,
   676  			repoLabels:            []string{"area/infra"},
   677  			issueLabels:           []string{},
   678  			expectedNewLabels:     formatWithPRInfo("area/infra"),
   679  			expectedRemovedLabels: []string{},
   680  			commenter:             orgMember,
   681  			action:                github.GenericCommentActionCreated,
   682  		},
   683  		{
   684  			name: "Strip markdown comments non greedy",
   685  			body: `
   686  <!--
   687  /kind bug
   688  -->
   689  /kind cleanup
   690  <!--
   691  /area infra
   692  -->
   693  /kind regression
   694  `,
   695  			repoLabels:            []string{"kind/cleanup", "kind/regression"},
   696  			issueLabels:           []string{},
   697  			expectedNewLabels:     formatWithPRInfo("kind/cleanup", "kind/regression"),
   698  			expectedRemovedLabels: []string{},
   699  			commenter:             orgMember,
   700  			action:                github.GenericCommentActionCreated,
   701  		},
   702  		{
   703  			name:               "Restricted label addition, user is not in group",
   704  			body:               `/label restricted-label`,
   705  			repoLabels:         []string{"restricted-label"},
   706  			commenter:          orgMember,
   707  			restrictedLabels:   map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}},
   708  			action:             github.GenericCommentActionCreated,
   709  			expectedBotComment: true,
   710  		},
   711  		{
   712  			name:              "Restricted label addition, user is in group",
   713  			body:              `/label restricted-label`,
   714  			repoLabels:        []string{"restricted-label"},
   715  			commenter:         orgMember,
   716  			restrictedLabels:  map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}},
   717  			action:            github.GenericCommentActionCreated,
   718  			teams:             map[string]map[string]fakegithub.TeamWithMembers{"org": {"privileged-group": {Members: sets.New[string](orgMember)}}},
   719  			expectedNewLabels: formatWithPRInfo("restricted-label"),
   720  		},
   721  		{
   722  			name:              "Restricted label addition, user is in allowed_users",
   723  			body:              `/label restricted-label`,
   724  			repoLabels:        []string{"restricted-label"},
   725  			commenter:         orgMember,
   726  			restrictedLabels:  map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedUsers: []string{orgMember}}}},
   727  			action:            github.GenericCommentActionCreated,
   728  			expectedNewLabels: formatWithPRInfo("restricted-label"),
   729  		},
   730  		{
   731  			name:               "Restricted label removal, user is not in group",
   732  			body:               `/remove-label restricted-label`,
   733  			repoLabels:         []string{"restricted-label"},
   734  			issueLabels:        []string{"restricted-label"},
   735  			commenter:          orgMember,
   736  			restrictedLabels:   map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}},
   737  			action:             github.GenericCommentActionCreated,
   738  			expectedBotComment: true,
   739  		},
   740  		{
   741  			name:                  "Restricted label removal, user is in group",
   742  			body:                  `/remove-label restricted-label`,
   743  			repoLabels:            []string{"restricted-label"},
   744  			issueLabels:           []string{"restricted-label"},
   745  			commenter:             orgMember,
   746  			restrictedLabels:      map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedTeams: []string{"privileged-group"}}}},
   747  			action:                github.GenericCommentActionCreated,
   748  			teams:                 map[string]map[string]fakegithub.TeamWithMembers{"org": {"privileged-group": {Members: sets.New[string](orgMember)}}},
   749  			expectedRemovedLabels: formatWithPRInfo("restricted-label"),
   750  		},
   751  		{
   752  			name:                  "Restricted label removal, user is in allowed_users",
   753  			body:                  `/remove-label restricted-label`,
   754  			repoLabels:            []string{"restricted-label"},
   755  			issueLabels:           []string{"restricted-label"},
   756  			commenter:             orgMember,
   757  			restrictedLabels:      map[string][]plugins.RestrictedLabel{"org": {{Label: "restricted-label", AllowedUsers: []string{orgMember}}}},
   758  			action:                github.GenericCommentActionCreated,
   759  			expectedRemovedLabels: formatWithPRInfo("restricted-label"),
   760  		},
   761  	}
   762  
   763  	for _, tc := range testcases {
   764  		t.Run(tc.name, func(t *testing.T) {
   765  			sort.Strings(tc.expectedNewLabels)
   766  			fakeClient := fakegithub.NewFakeClient()
   767  			fakeClient.Issues = make(map[int]*github.Issue)
   768  			fakeClient.IssueComments = make(map[int][]github.IssueComment)
   769  			fakeClient.RepoLabelsExisting = tc.repoLabels
   770  			fakeClient.OrgMembers = map[string][]string{"org": {orgMember}}
   771  			fakeClient.IssueLabelsAdded = []string{}
   772  			fakeClient.IssueLabelsRemoved = []string{}
   773  			fakeClient.Teams = tc.teams
   774  			// Add initial labels
   775  			for _, label := range tc.issueLabels {
   776  				fakeClient.AddLabel("org", "repo", 1, label)
   777  			}
   778  			e := &github.GenericCommentEvent{
   779  				Action: tc.action,
   780  				Body:   tc.body,
   781  				Number: 1,
   782  				Repo:   github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   783  				User:   github.User{Login: tc.commenter},
   784  			}
   785  			err := handleComment(fakeClient, logrus.WithField("plugin", PluginName), plugins.Label{AdditionalLabels: tc.extraLabels, RestrictedLabels: tc.restrictedLabels}, e)
   786  			if err != nil {
   787  				t.Fatalf("didn't expect error from handle comment test: %v", err)
   788  			}
   789  
   790  			// Check that all the correct labels (and only the correct labels) were added.
   791  			expectLabels := append(formatWithPRInfo(tc.issueLabels...), tc.expectedNewLabels...)
   792  			if expectLabels == nil {
   793  				expectLabels = []string{}
   794  			}
   795  			sort.Strings(expectLabels)
   796  			sort.Strings(fakeClient.IssueLabelsAdded)
   797  			if diff := cmp.Diff(expectLabels, fakeClient.IssueLabelsAdded, cmpopts.EquateEmpty()); diff != "" {
   798  				t.Errorf("labels expected to add do not match actual added labels: %s", diff)
   799  			}
   800  
   801  			sort.Strings(tc.expectedRemovedLabels)
   802  			sort.Strings(fakeClient.IssueLabelsRemoved)
   803  			if diff := cmp.Diff(tc.expectedRemovedLabels, fakeClient.IssueLabelsRemoved, cmpopts.EquateEmpty()); diff != "" {
   804  				t.Errorf("expected removed labels differ from actual removed labels: %s", diff)
   805  			}
   806  			if len(fakeClient.IssueCommentsAdded) > 0 && !tc.expectedBotComment {
   807  				t.Errorf("unexpected bot comments: %#v", fakeClient.IssueCommentsAdded)
   808  			}
   809  			if len(fakeClient.IssueCommentsAdded) == 0 && tc.expectedBotComment {
   810  				t.Error("expected a bot comment but got none")
   811  			}
   812  			if tc.expectedBotComment && len(tc.expectedCommentText) > 0 {
   813  				if len(fakeClient.IssueComments) < 1 {
   814  					t.Errorf("expected actual: %v", fakeClient.IssueComments)
   815  				}
   816  				if len(fakeClient.IssueComments[1]) != 1 || !strings.Contains(fakeClient.IssueComments[1][0].Body, tc.expectedCommentText) {
   817  					t.Errorf("expected: `%v`, actual: `%v`", tc.expectedCommentText, fakeClient.IssueComments[1][0].Body)
   818  				}
   819  			}
   820  		})
   821  	}
   822  }
   823  
   824  func TestHandleLabelAdd(t *testing.T) {
   825  	type testCase struct {
   826  		name              string
   827  		restrictedLabels  map[string][]plugins.RestrictedLabel
   828  		expectedAssignees []string
   829  		labelAdded        string
   830  		action            github.PullRequestEventAction
   831  	}
   832  	testCases := []testCase{
   833  		{
   834  			name:       "label added with no auto-assign configured",
   835  			labelAdded: "some-label",
   836  			action:     github.PullRequestActionLabeled,
   837  		},
   838  		{
   839  			name:              "assign users for restricted label on label add",
   840  			restrictedLabels:  map[string][]plugins.RestrictedLabel{"org": {{Label: "secondary-label", AllowedUsers: []string{"bill", "sally"}, AssignOn: []plugins.AssignOnLabel{{Label: "initial-label"}}}}},
   841  			labelAdded:        "initial-label",
   842  			action:            github.PullRequestActionLabeled,
   843  			expectedAssignees: formatWithPRInfo("bill", "sally"),
   844  		},
   845  		{
   846  			name:             "no assigned users on irrelevant label add",
   847  			restrictedLabels: map[string][]plugins.RestrictedLabel{"org": {{Label: "secondary-label", AllowedUsers: []string{"bill", "sally"}, AssignOn: []plugins.AssignOnLabel{{Label: "initial-label"}}}}},
   848  			labelAdded:       "other-label",
   849  			action:           github.PullRequestActionLabeled,
   850  		},
   851  	}
   852  	for _, tc := range testCases {
   853  		t.Run(tc.name, func(t *testing.T) {
   854  			fakeClient := fakegithub.NewFakeClient()
   855  			e := &github.PullRequestEvent{
   856  				Action:      tc.action,
   857  				Repo:        github.Repo{Owner: github.User{Login: "org"}, Name: "repo"},
   858  				PullRequest: github.PullRequest{Number: 1},
   859  				Label:       github.Label{Name: tc.labelAdded},
   860  			}
   861  			err := handleLabelAdd(fakeClient, logrus.WithField("plugin", PluginName), plugins.Label{RestrictedLabels: tc.restrictedLabels}, e)
   862  			if err != nil {
   863  				t.Fatalf("didn't expect error from handle label test: %v", err)
   864  			}
   865  			if diff := cmp.Diff(tc.expectedAssignees, fakeClient.AssigneesAdded, cmpopts.EquateEmpty()); diff != "" {
   866  				t.Errorf("expected added assignees differ from actual: %s", diff)
   867  			}
   868  		})
   869  	}
   870  }
   871  
   872  func TestHelpProvider(t *testing.T) {
   873  	enabledRepos := []config.OrgRepo{
   874  		{Org: "org1", Repo: "repo"},
   875  		{Org: "org2", Repo: "repo"},
   876  	}
   877  	cases := []struct {
   878  		name               string
   879  		config             *plugins.Configuration
   880  		enabledRepos       []config.OrgRepo
   881  		err                bool
   882  		configInfoIncludes []string
   883  	}{
   884  		{
   885  			name:               "Empty config",
   886  			config:             &plugins.Configuration{},
   887  			enabledRepos:       enabledRepos,
   888  			configInfoIncludes: []string{configString(defaultLabels)},
   889  		},
   890  		{
   891  			name: "With AdditionalLabels",
   892  			config: &plugins.Configuration{
   893  				Label: plugins.Label{
   894  					AdditionalLabels: []string{"sig", "triage", "wg"},
   895  				},
   896  			},
   897  			enabledRepos:       enabledRepos,
   898  			configInfoIncludes: []string{configString(append(defaultLabels, "sig", "triage", "wg"))},
   899  		},
   900  	}
   901  	for _, c := range cases {
   902  		t.Run(c.name, func(t *testing.T) {
   903  			pluginHelp, err := helpProvider(c.config, c.enabledRepos)
   904  			if err != nil && !c.err {
   905  				t.Fatalf("helpProvider error: %v", err)
   906  			}
   907  			for _, msg := range c.configInfoIncludes {
   908  				if !strings.Contains(pluginHelp.Config[""], msg) {
   909  					t.Fatalf("helpProvider.Config error mismatch: didn't get %v, but wanted it", msg)
   910  				}
   911  			}
   912  		})
   913  	}
   914  }