github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/assign/assign_test.go (about)

     1  /*
     2  Copyright 2017 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 assign
    18  
    19  import (
    20  	"sort"
    21  	"testing"
    22  
    23  	"github.com/sirupsen/logrus"
    24  
    25  	"sigs.k8s.io/prow/pkg/github"
    26  )
    27  
    28  type fakeClient struct {
    29  	assigned   map[string]int
    30  	unassigned map[string]int
    31  
    32  	requested    map[string]int
    33  	unrequested  map[string]int
    34  	contributors map[string]bool
    35  
    36  	commented bool
    37  }
    38  
    39  func (c *fakeClient) UnassignIssue(owner, repo string, number int, assignees []string) error {
    40  	for _, who := range assignees {
    41  		c.unassigned[who]++
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  func (c *fakeClient) AssignIssue(owner, repo string, number int, assignees []string) error {
    48  	var missing github.MissingUsers
    49  	sort.Strings(assignees)
    50  	if len(assignees) > 10 {
    51  		missing.Users = append(missing.Users, assignees[10:]...)
    52  		for _, who := range assignees[:10] {
    53  			c.assigned[who]++
    54  		}
    55  	} else {
    56  		for _, who := range assignees {
    57  			if who != "evil" {
    58  				c.assigned[who]++
    59  			} else {
    60  				missing.Users = append(missing.Users, who)
    61  			}
    62  		}
    63  	}
    64  
    65  	if len(missing.Users) == 0 {
    66  		return nil
    67  	}
    68  	return missing
    69  }
    70  
    71  func (c *fakeClient) RequestReview(org, repo string, number int, logins []string) error {
    72  	var missing github.MissingUsers
    73  	for _, user := range logins {
    74  		if c.contributors[user] {
    75  			c.requested[user]++
    76  		} else {
    77  			missing.Users = append(missing.Users, user)
    78  		}
    79  	}
    80  	if len(missing.Users) > 0 {
    81  		return missing
    82  	}
    83  	return nil
    84  }
    85  
    86  func (c *fakeClient) UnrequestReview(org, repo string, number int, logins []string) error {
    87  	for _, user := range logins {
    88  		c.unrequested[user]++
    89  	}
    90  	return nil
    91  }
    92  
    93  func (c *fakeClient) CreateComment(owner, repo string, number int, comment string) error {
    94  	c.commented = comment != ""
    95  	return nil
    96  }
    97  
    98  func newFakeClient(contribs []string) *fakeClient {
    99  	c := &fakeClient{
   100  		contributors: make(map[string]bool),
   101  		requested:    make(map[string]int),
   102  		unrequested:  make(map[string]int),
   103  		assigned:     make(map[string]int),
   104  		unassigned:   make(map[string]int),
   105  	}
   106  	for _, user := range contribs {
   107  		c.contributors[user] = true
   108  	}
   109  	return c
   110  }
   111  
   112  func TestParseLogins(t *testing.T) {
   113  	var testcases = []struct {
   114  		name   string
   115  		text   string
   116  		logins []string
   117  	}{
   118  		{
   119  			name: "empty",
   120  			text: "",
   121  		},
   122  		{
   123  			name:   "one",
   124  			text:   " @jungle",
   125  			logins: []string{"jungle"},
   126  		},
   127  		{
   128  			name:   "two",
   129  			text:   " @erick @fejta",
   130  			logins: []string{"erick", "fejta"},
   131  		},
   132  		{
   133  			name:   "one team",
   134  			text:   " @kubernetes/sig-testing-misc",
   135  			logins: []string{"kubernetes/sig-testing-misc"},
   136  		},
   137  		{
   138  			name:   "two teams",
   139  			text:   " @kubernetes/sig-testing-misc @kubernetes/sig-testing-bugs",
   140  			logins: []string{"kubernetes/sig-testing-misc", "kubernetes/sig-testing-bugs"},
   141  		},
   142  	}
   143  	for _, tc := range testcases {
   144  		l := parseLogins(tc.text)
   145  		if len(l) != len(tc.logins) {
   146  			t.Errorf("For case %s, expected %s and got %s", tc.name, tc.logins, l)
   147  		}
   148  		for n, who := range l {
   149  			if tc.logins[n] != who {
   150  				t.Errorf("For case %s, expected %s and got %s", tc.name, tc.logins, l)
   151  			}
   152  		}
   153  	}
   154  }
   155  
   156  // TestAssignAndReview tests that the handle function uses the github client
   157  // to correctly create and/or delete assignments and PR review requests.
   158  func TestAssignAndReview(t *testing.T) {
   159  	var testcases = []struct {
   160  		name        string
   161  		body        string
   162  		commenter   string
   163  		assigned    []string
   164  		unassigned  []string
   165  		requested   []string
   166  		unrequested []string
   167  		commented   bool
   168  	}{
   169  		{
   170  			name:      "unrelated comment",
   171  			body:      "uh oh",
   172  			commenter: "o",
   173  		},
   174  		{
   175  			name:      "assign on open",
   176  			body:      "/assign",
   177  			commenter: "rando",
   178  			assigned:  []string{"rando"},
   179  		},
   180  		{
   181  			name:      "assign me",
   182  			body:      "/assign",
   183  			commenter: "rando",
   184  			assigned:  []string{"rando"},
   185  		},
   186  		{
   187  			name:       "unassign myself",
   188  			body:       "/unassign",
   189  			commenter:  "rando",
   190  			unassigned: []string{"rando"},
   191  		},
   192  		{
   193  			name:      "tab completion",
   194  			body:      "/assign @fejta ",
   195  			commenter: "rando",
   196  			assigned:  []string{"fejta"},
   197  		},
   198  		{
   199  			name:      "no @ works too",
   200  			body:      "/assign fejta",
   201  			commenter: "rando",
   202  			assigned:  []string{"fejta"},
   203  		},
   204  		{
   205  			name:       "multi commands",
   206  			body:       "/assign @fejta\n/unassign @spxtr",
   207  			commenter:  "rando",
   208  			assigned:   []string{"fejta"},
   209  			unassigned: []string{"spxtr"},
   210  		},
   211  		{
   212  			name:      "interesting names",
   213  			body:      "/assign @hello-world @allow_underscore",
   214  			commenter: "rando",
   215  			assigned:  []string{"hello-world", "allow_underscore"},
   216  		},
   217  		{
   218  			name:      "bad login",
   219  			commenter: "rando",
   220  			body:      "/assign @Invalid$User",
   221  		},
   222  		{
   223  			name:      "bad login, no @",
   224  			commenter: "rando",
   225  			body:      "/assign Invalid$User",
   226  		},
   227  		{
   228  			name:      "assign friends",
   229  			body:      "/assign @bert @ernie",
   230  			commenter: "rando",
   231  			assigned:  []string{"bert", "ernie"},
   232  		},
   233  		{
   234  			name:      "assign greater than 10 users",
   235  			body:      "/assign @user1 @user2 @user3 @user4 @user5 @user6 @user7 @user8 @user9 @user10 @user11 @user12 @user13",
   236  			commenter: "rando",
   237  			commented: true,
   238  			assigned:  []string{"user12", "user13", "user6", "user1", "user11", "user2", "user3", "user4", "user5", "user10"},
   239  		},
   240  		{
   241  			name:       "unassign buddies",
   242  			body:       "/unassign @ashitaka @eboshi",
   243  			commenter:  "san",
   244  			unassigned: []string{"ashitaka", "eboshi"},
   245  		},
   246  		{
   247  			name:       "unassign buddies, trailing space.",
   248  			body:       "/unassign @ashitaka @eboshi \r",
   249  			commenter:  "san",
   250  			unassigned: []string{"ashitaka", "eboshi"},
   251  		},
   252  		{
   253  			name:      "evil commenter",
   254  			body:      "/assign @merlin",
   255  			commenter: "evil",
   256  			assigned:  []string{"merlin"},
   257  		},
   258  		{
   259  			name:      "evil commenter self assign",
   260  			body:      "/assign",
   261  			commenter: "evil",
   262  			commented: true,
   263  		},
   264  		{
   265  			name:      "evil assignee",
   266  			body:      "/assign @evil @evil @evil @evil @merlin",
   267  			commenter: "innocent",
   268  			assigned:  []string{"merlin"},
   269  			commented: true,
   270  		},
   271  		{
   272  			name:       "evil unassignee",
   273  			body:       "/unassign @evil @merlin",
   274  			commenter:  "innocent",
   275  			unassigned: []string{"evil", "merlin"},
   276  		},
   277  		{
   278  			name:      "review on open",
   279  			body:      "/cc @merlin",
   280  			commenter: "rando",
   281  			requested: []string{"merlin"},
   282  		},
   283  		{
   284  			name:      "tab completion",
   285  			body:      "/cc @cjwagner ",
   286  			commenter: "rando",
   287  			requested: []string{"cjwagner"},
   288  		},
   289  		{
   290  			name:      "no @ works too",
   291  			body:      "/cc cjwagner ",
   292  			commenter: "rando",
   293  			requested: []string{"cjwagner"},
   294  		},
   295  		{
   296  			name:        "multi commands",
   297  			body:        "/cc @cjwagner\n/uncc @spxtr",
   298  			commenter:   "rando",
   299  			requested:   []string{"cjwagner"},
   300  			unrequested: []string{"spxtr"},
   301  		},
   302  		{
   303  			name:      "interesting names",
   304  			body:      "/cc @hello-world @allow_underscore",
   305  			commenter: "rando",
   306  			requested: []string{"hello-world", "allow_underscore"},
   307  		},
   308  		{
   309  			name:      "bad login",
   310  			commenter: "rando",
   311  			body:      "/cc @Invalid$User",
   312  		},
   313  		{
   314  			name:      "bad login",
   315  			commenter: "rando",
   316  			body:      "/cc Invalid$User",
   317  		},
   318  		{
   319  			name:      "request multiple",
   320  			body:      "/cc @cjwagner @merlin",
   321  			commenter: "rando",
   322  			requested: []string{"cjwagner", "merlin"},
   323  		},
   324  		{
   325  			name:        "unrequest buddies",
   326  			body:        "/uncc @ashitaka @eboshi",
   327  			commenter:   "san",
   328  			unrequested: []string{"ashitaka", "eboshi"},
   329  		},
   330  		{
   331  			name:      "evil commenter",
   332  			body:      "/cc @merlin",
   333  			commenter: "evil",
   334  			requested: []string{"merlin"},
   335  		},
   336  		{
   337  			name:      "evil reviewer requested",
   338  			body:      "/cc @evil @merlin",
   339  			commenter: "innocent",
   340  			requested: []string{"merlin"},
   341  			commented: true,
   342  		},
   343  		{
   344  			name:        "evil reviewer unrequested",
   345  			body:        "/uncc @evil @merlin",
   346  			commenter:   "innocent",
   347  			unrequested: []string{"evil", "merlin"},
   348  		},
   349  		{
   350  			name:        "multi command types",
   351  			body:        "/assign @fejta\n/unassign @spxtr @cjwagner\n/uncc @merlin \n/cc @cjwagner",
   352  			commenter:   "rando",
   353  			assigned:    []string{"fejta"},
   354  			unassigned:  []string{"spxtr", "cjwagner"},
   355  			requested:   []string{"cjwagner"},
   356  			unrequested: []string{"merlin"},
   357  		},
   358  		{
   359  			name:      "request review self",
   360  			body:      "/cc",
   361  			commenter: "cjwagner",
   362  			requested: []string{"cjwagner"},
   363  		},
   364  		{
   365  			name:        "unrequest review self",
   366  			body:        "/uncc",
   367  			commenter:   "cjwagner",
   368  			unrequested: []string{"cjwagner"},
   369  		},
   370  		{
   371  			name:        "request review self, with unrequest friend, with trailing space.",
   372  			body:        "/cc \n/uncc @spxtr ",
   373  			commenter:   "cjwagner",
   374  			requested:   []string{"cjwagner"},
   375  			unrequested: []string{"spxtr"},
   376  		},
   377  		{
   378  			name:      "request team review",
   379  			body:      "/cc @kubernetes/sig-testing-misc",
   380  			commenter: "rando",
   381  			requested: []string{"kubernetes/sig-testing-misc"},
   382  		},
   383  		{
   384  			name:        "unrequest team review",
   385  			body:        "/uncc @kubernetes/sig-testing-misc",
   386  			commenter:   "rando",
   387  			unrequested: []string{"kubernetes/sig-testing-misc"},
   388  		},
   389  	}
   390  	for _, tc := range testcases {
   391  		fc := newFakeClient([]string{"hello-world", "allow_underscore", "cjwagner", "merlin", "kubernetes/sig-testing-misc"})
   392  		e := github.GenericCommentEvent{
   393  			Body:   tc.body,
   394  			User:   github.User{Login: tc.commenter},
   395  			Repo:   github.Repo{Name: "repo", Owner: github.User{Login: "org"}},
   396  			Number: 5,
   397  		}
   398  		if err := handle(newAssignHandler(e, fc, logrus.WithField("plugin", pluginName))); err != nil {
   399  			t.Errorf("For case %s, didn't expect error from handle: %v", tc.name, err)
   400  			continue
   401  		}
   402  		if err := handle(newReviewHandler(e, fc, logrus.WithField("plugin", pluginName))); err != nil {
   403  			t.Errorf("For case %s, didn't expect error from handle: %v", tc.name, err)
   404  			continue
   405  		}
   406  
   407  		if tc.commented != fc.commented {
   408  			t.Errorf("For case %s, expect commented: %v, got commented %v", tc.name, tc.commented, fc.commented)
   409  		}
   410  
   411  		if len(fc.assigned) != len(tc.assigned) {
   412  			t.Errorf("For case %s, assigned actual %v != expected %s", tc.name, fc.assigned, tc.assigned)
   413  		} else {
   414  			for _, who := range tc.assigned {
   415  				if n, ok := fc.assigned[who]; !ok || n < 1 {
   416  					t.Errorf("For case %s, assigned actual %v != expected %s", tc.name, fc.assigned, tc.assigned)
   417  					break
   418  				}
   419  			}
   420  		}
   421  		if len(fc.unassigned) != len(tc.unassigned) {
   422  			t.Errorf("For case %s, unassigned %v != %s", tc.name, fc.unassigned, tc.unassigned)
   423  		} else {
   424  			for _, who := range tc.unassigned {
   425  				if n, ok := fc.unassigned[who]; !ok || n < 1 {
   426  					t.Errorf("For case %s, unassigned %v != %s", tc.name, fc.unassigned, tc.unassigned)
   427  					break
   428  				}
   429  			}
   430  		}
   431  
   432  		if len(fc.requested) != len(tc.requested) {
   433  			t.Errorf("For case %s, requested actual %v != expected %s", tc.name, fc.requested, tc.requested)
   434  		} else {
   435  			for _, who := range tc.requested {
   436  				if n, ok := fc.requested[who]; !ok || n < 1 {
   437  					t.Errorf("For case %s, requested actual %v != expected %s", tc.name, fc.requested, tc.requested)
   438  					break
   439  				}
   440  			}
   441  		}
   442  		if len(fc.unrequested) != len(tc.unrequested) {
   443  			t.Errorf("For case %s, unrequested %v != %s", tc.name, fc.unrequested, tc.unrequested)
   444  		} else {
   445  			for _, who := range tc.unrequested {
   446  				if n, ok := fc.unrequested[who]; !ok || n < 1 {
   447  					t.Errorf("For case %s, unrequested %v != %s", tc.name, fc.unrequested, tc.unrequested)
   448  					break
   449  				}
   450  			}
   451  		}
   452  	}
   453  }