sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/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 }