github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/override/override_test.go (about) 1 /* 2 Copyright 2018 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 override 18 19 import ( 20 "errors" 21 "fmt" 22 "reflect" 23 "strings" 24 "testing" 25 26 "github.com/sirupsen/logrus" 27 "k8s.io/apimachinery/pkg/util/sets" 28 29 "k8s.io/test-infra/prow/config" 30 "k8s.io/test-infra/prow/github" 31 "k8s.io/test-infra/prow/kube" 32 ) 33 34 const ( 35 fakeOrg = "fake-org" 36 fakeRepo = "fake-repo" 37 fakePR = 33 38 fakeSHA = "deadbeef" 39 fakeBaseSHA = "fffffff" 40 adminUser = "admin-user" 41 ) 42 43 type fakeClient struct { 44 comments []string 45 statuses map[string]github.Status 46 presubmits map[string]config.Presubmit 47 jobs sets.String 48 } 49 50 func (c *fakeClient) CreateComment(org, repo string, number int, comment string) error { 51 switch { 52 case org != fakeOrg: 53 return fmt.Errorf("bad org: %s", org) 54 case repo != fakeRepo: 55 return fmt.Errorf("bad repo: %s", repo) 56 case number != fakePR: 57 return fmt.Errorf("bad number: %d", number) 58 case strings.Contains(comment, "fail-comment"): 59 return errors.New("injected CreateComment failure") 60 } 61 c.comments = append(c.comments, comment) 62 return nil 63 } 64 65 func (c *fakeClient) CreateStatus(org, repo, ref string, s github.Status) error { 66 switch { 67 case s.Context == "fail-create": 68 return errors.New("injected CreateStatus failure") 69 case org != fakeOrg: 70 return fmt.Errorf("bad org: %s", org) 71 case repo != fakeRepo: 72 return fmt.Errorf("bad repo: %s", repo) 73 case ref != fakeSHA: 74 return fmt.Errorf("bad ref: %s", ref) 75 } 76 c.statuses[s.Context] = s 77 return nil 78 } 79 80 func (c *fakeClient) GetPullRequest(org, repo string, number int) (*github.PullRequest, error) { 81 switch { 82 case number < 0: 83 return nil, errors.New("injected CreateStatus failure") 84 case org != fakeOrg: 85 return nil, fmt.Errorf("bad org: %s", org) 86 case repo != fakeRepo: 87 return nil, fmt.Errorf("bad repo: %s", repo) 88 case number != fakePR: 89 return nil, fmt.Errorf("bad number: %d", number) 90 } 91 var pr github.PullRequest 92 pr.Head.SHA = fakeSHA 93 return &pr, nil 94 } 95 96 func (c *fakeClient) ListStatuses(org, repo, ref string) ([]github.Status, error) { 97 switch { 98 case org != fakeOrg: 99 return nil, fmt.Errorf("bad org: %s", org) 100 case repo != fakeRepo: 101 return nil, fmt.Errorf("bad repo: %s", repo) 102 case ref != fakeSHA: 103 return nil, fmt.Errorf("bad ref: %s", ref) 104 } 105 var out []github.Status 106 for _, s := range c.statuses { 107 if s.Context == "fail-list" { 108 return nil, errors.New("injected ListStatuses failure") 109 } 110 out = append(out, s) 111 } 112 return out, nil 113 } 114 115 func (c *fakeClient) HasPermission(org, repo, user string, roles ...string) (bool, error) { 116 switch { 117 case org != fakeOrg: 118 return false, fmt.Errorf("bad org: %s", org) 119 case repo != fakeRepo: 120 return false, fmt.Errorf("bad repo: %s", repo) 121 case roles[0] != github.RoleAdmin: 122 return false, fmt.Errorf("bad roles: %s", roles) 123 case user == "fail": 124 return true, errors.New("injected HasRole error") 125 } 126 return user == adminUser, nil 127 } 128 129 func (c *fakeClient) GetRef(org, repo, ref string) (string, error) { 130 if repo == "fail-ref" { 131 return "", errors.New("injected GetRef error") 132 } 133 return fakeBaseSHA, nil 134 } 135 136 func (c *fakeClient) CreateProwJob(pj kube.ProwJob) (kube.ProwJob, error) { 137 if s := pj.Status.State; s != kube.SuccessState { 138 return pj, fmt.Errorf("bad status state: %s", s) 139 } 140 if pj.Spec.Context == "fail-create" { 141 return pj, errors.New("injected CreateProwJob error") 142 } 143 c.jobs.Insert(pj.Spec.Context) 144 return pj, nil 145 } 146 147 func (c *fakeClient) presubmitForContext(org, repo, context string) *config.Presubmit { 148 p, ok := c.presubmits[context] 149 if !ok { 150 return nil 151 } 152 return &p 153 } 154 155 func TestAuthorized(t *testing.T) { 156 cases := []struct { 157 name string 158 user string 159 expected bool 160 }{ 161 { 162 name: "fail closed", 163 user: "fail", 164 }, 165 { 166 name: "reject rando", 167 user: "random", 168 }, 169 { 170 name: "accept admin", 171 user: adminUser, 172 expected: true, 173 }, 174 } 175 176 log := logrus.WithField("plugin", pluginName) 177 for _, tc := range cases { 178 t.Run(tc.name, func(t *testing.T) { 179 if actual := authorized(&fakeClient{}, log, fakeOrg, fakeRepo, tc.user); actual != tc.expected { 180 t.Errorf("actual %t != expected %t", actual, tc.expected) 181 } 182 }) 183 } 184 } 185 186 func TestHandle(t *testing.T) { 187 cases := []struct { 188 name string 189 action github.GenericCommentEventAction 190 issue bool 191 state string 192 comment string 193 contexts map[string]github.Status 194 presubmits map[string]config.Presubmit 195 user string 196 number int 197 expected map[string]github.Status 198 jobs sets.String 199 checkComments []string 200 err bool 201 }{ 202 { 203 name: "successfully override failure", 204 comment: "/override broken-test", 205 contexts: map[string]github.Status{ 206 "broken-test": { 207 Context: "broken-test", 208 State: github.StatusFailure, 209 }, 210 }, 211 expected: map[string]github.Status{ 212 "broken-test": { 213 Context: "broken-test", 214 Description: description(adminUser), 215 State: github.StatusSuccess, 216 }, 217 }, 218 checkComments: []string{"on behalf of " + adminUser}, 219 }, 220 { 221 name: "successfully override pending", 222 comment: "/override hung-test", 223 contexts: map[string]github.Status{ 224 "hung-test": { 225 Context: "hung-test", 226 State: github.StatusPending, 227 }, 228 }, 229 expected: map[string]github.Status{ 230 "hung-test": { 231 Context: "hung-test", 232 Description: description(adminUser), 233 State: github.StatusSuccess, 234 }, 235 }, 236 }, 237 { 238 name: "refuse override from non-admin", 239 comment: "/override broken-test", 240 contexts: map[string]github.Status{ 241 "broken-test": { 242 Context: "broken-test", 243 State: github.StatusPending, 244 }, 245 }, 246 user: "rando", 247 checkComments: []string{"unauthorized"}, 248 expected: map[string]github.Status{ 249 "broken-test": { 250 Context: "broken-test", 251 State: github.StatusPending, 252 }, 253 }, 254 }, 255 { 256 name: "override multiple", 257 comment: "/override broken-test\n/override hung-test", 258 contexts: map[string]github.Status{ 259 "broken-test": { 260 Context: "broken-test", 261 State: github.StatusFailure, 262 }, 263 "hung-test": { 264 Context: "hung-test", 265 State: github.StatusPending, 266 }, 267 }, 268 expected: map[string]github.Status{ 269 "hung-test": { 270 Context: "hung-test", 271 Description: description(adminUser), 272 State: github.StatusSuccess, 273 }, 274 "broken-test": { 275 Context: "broken-test", 276 Description: description(adminUser), 277 State: github.StatusSuccess, 278 }, 279 }, 280 checkComments: []string{fmt.Sprintf("%s: broken-test, hung-test", adminUser)}, 281 }, 282 { 283 name: "ignore non-PRs", 284 issue: true, 285 comment: "/override broken-test", 286 contexts: map[string]github.Status{ 287 "broken-test": { 288 Context: "broken-test", 289 State: github.StatusPending, 290 }, 291 }, 292 expected: map[string]github.Status{ 293 "broken-test": { 294 Context: "broken-test", 295 State: github.StatusPending, 296 }, 297 }, 298 }, 299 { 300 name: "ignore closed issues", 301 state: "closed", 302 comment: "/override broken-test", 303 contexts: map[string]github.Status{ 304 "broken-test": { 305 Context: "broken-test", 306 State: github.StatusPending, 307 }, 308 }, 309 expected: map[string]github.Status{ 310 "broken-test": { 311 Context: "broken-test", 312 State: github.StatusPending, 313 }, 314 }, 315 }, 316 { 317 name: "ignore edits", 318 action: github.GenericCommentActionEdited, 319 comment: "/override broken-test", 320 contexts: map[string]github.Status{ 321 "broken-test": { 322 Context: "broken-test", 323 State: github.StatusPending, 324 }, 325 }, 326 expected: map[string]github.Status{ 327 "broken-test": { 328 Context: "broken-test", 329 State: github.StatusPending, 330 }, 331 }, 332 }, 333 { 334 name: "ignore random text", 335 comment: "/test broken-test", 336 contexts: map[string]github.Status{ 337 "broken-test": { 338 Context: "broken-test", 339 State: github.StatusPending, 340 }, 341 }, 342 expected: map[string]github.Status{ 343 "broken-test": { 344 Context: "broken-test", 345 State: github.StatusPending, 346 }, 347 }, 348 }, 349 { 350 name: "comment on get pr failure", 351 number: fakePR * 2, 352 comment: "/override broken-test", 353 contexts: map[string]github.Status{ 354 "broken-test": { 355 Context: "broken-test", 356 State: github.StatusFailure, 357 }, 358 }, 359 expected: map[string]github.Status{ 360 "broken-test": { 361 Context: "broken-test", 362 Description: description(adminUser), 363 State: github.StatusSuccess, 364 }, 365 }, 366 checkComments: []string{"Cannot get PR"}, 367 }, 368 { 369 name: "comment on list statuses failure", 370 comment: "/override fail-list", 371 contexts: map[string]github.Status{ 372 "fail-list": { 373 Context: "fail-list", 374 State: github.StatusFailure, 375 }, 376 }, 377 expected: map[string]github.Status{ 378 "fail-list": { 379 Context: "fail-list", 380 State: github.StatusFailure, 381 }, 382 }, 383 checkComments: []string{"Cannot get commit statuses"}, 384 }, 385 { 386 name: "do not override passing contexts", 387 comment: "/override passing-test", 388 contexts: map[string]github.Status{ 389 "passing-test": { 390 Context: "passing-test", 391 Description: "preserve description", 392 State: github.StatusSuccess, 393 }, 394 }, 395 expected: map[string]github.Status{ 396 "passing-test": { 397 Context: "passing-test", 398 State: github.StatusSuccess, 399 Description: "preserve description", 400 }, 401 }, 402 }, 403 { 404 name: "create successful prow job", 405 comment: "/override prow-job", 406 contexts: map[string]github.Status{ 407 "prow-job": { 408 Context: "prow-job", 409 Description: "failed", 410 State: github.StatusFailure, 411 }, 412 }, 413 presubmits: map[string]config.Presubmit{ 414 "prow-job": { 415 Context: "prow-job", 416 }, 417 }, 418 jobs: sets.NewString("prow-job"), 419 expected: map[string]github.Status{ 420 "prow-job": { 421 Context: "prow-job", 422 State: github.StatusSuccess, 423 Description: description(adminUser), 424 }, 425 }, 426 }, 427 { 428 name: "override with explanation works", 429 comment: "/override job\r\nobnoxious flake", // github ends lines with \r\n 430 contexts: map[string]github.Status{ 431 "job": { 432 Context: "job", 433 Description: "failed", 434 State: github.StatusFailure, 435 }, 436 }, 437 expected: map[string]github.Status{ 438 "job": { 439 Context: "job", 440 Description: description(adminUser), 441 State: github.StatusSuccess, 442 }, 443 }, 444 }, 445 } 446 447 log := logrus.WithField("plugin", pluginName) 448 for _, tc := range cases { 449 t.Run(tc.name, func(t *testing.T) { 450 var event github.GenericCommentEvent 451 event.Repo.Owner.Login = fakeOrg 452 event.Repo.Name = fakeRepo 453 event.Body = tc.comment 454 event.Number = fakePR 455 event.IsPR = !tc.issue 456 if tc.user == "" { 457 tc.user = adminUser 458 } 459 event.User.Login = tc.user 460 if tc.state == "" { 461 tc.state = "open" 462 } 463 event.IssueState = tc.state 464 if tc.action == "" { 465 tc.action = github.GenericCommentActionCreated 466 } 467 event.Action = tc.action 468 if tc.contexts == nil { 469 tc.contexts = map[string]github.Status{} 470 } 471 fc := fakeClient{ 472 statuses: tc.contexts, 473 presubmits: tc.presubmits, 474 jobs: sets.String{}, 475 } 476 477 if tc.jobs == nil { 478 tc.jobs = sets.String{} 479 } 480 481 err := handle(&fc, log, &event) 482 switch { 483 case err != nil: 484 if !tc.err { 485 t.Errorf("unexpected error: %v", err) 486 } 487 case tc.err: 488 t.Error("failed to receive an error") 489 case !reflect.DeepEqual(fc.statuses, tc.expected): 490 t.Errorf("bad statuses: actual %#v != expected %#v", fc.statuses, tc.expected) 491 case !reflect.DeepEqual(fc.jobs, tc.jobs): 492 t.Errorf("bad jobs: actual %#v != expected %#v", fc.jobs, tc.jobs) 493 } 494 }) 495 } 496 }