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