github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/pkg/ghclient/wrappers_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 ghclient 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/google/go-github/github" 26 ) 27 28 type fakeUserService struct { 29 authenticatedUser string 30 users map[string]*github.User 31 } 32 33 func newFakeUserService(authenticated string, other []string) *fakeUserService { 34 users := map[string]*github.User{authenticated: {Login: &authenticated}} 35 for _, user := range other { 36 userCopy := user 37 users[user] = &github.User{Login: &userCopy} 38 } 39 return &fakeUserService{authenticatedUser: authenticated, users: users} 40 } 41 42 func (f *fakeUserService) Get(ctx context.Context, login string) (*github.User, *github.Response, error) { 43 resp := &github.Response{Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}} 44 if login == "" { 45 login = f.authenticatedUser 46 } 47 if user := f.users[login]; user != nil { 48 return user, resp, nil 49 } 50 return nil, resp, fmt.Errorf("user '%s' does not exist", login) 51 } 52 53 func TestGetUser(t *testing.T) { 54 client := &Client{userService: newFakeUserService("me", []string{"a", "b", "c"})} 55 setForTest(client) 56 // try getting the currently authenticated user 57 if user, err := client.GetUser(""); err != nil { 58 t.Errorf("Unexpected error from GetUser(\"\"): %v.", err) 59 } else if *user.Login != "me" { 60 t.Errorf("GetUser(\"\") returned user %q instead of \"me\".", *user.Login) 61 } 62 // try getting another user 63 if user, err := client.GetUser("b"); err != nil { 64 t.Errorf("Unexpected error from GetUser(\"b\"): %v.", err) 65 } else if *user.Login != "b" { 66 t.Errorf("GetUser(\"b\") returned user %q instead of \"b\".", *user.Login) 67 } 68 // try getting an invalid user 69 if user, err := client.GetUser("d"); err == nil { 70 t.Errorf("Expected error from GetUser(\"d\") (invalid user), but did not get an error.") 71 } else if user != nil { 72 t.Error("Got a user even though GetUser(\"d\") (invalid user) returned a nil user.") 73 } 74 } 75 76 type fakeRepoService struct { 77 org, repo string 78 collaborators []*github.User 79 80 ref string 81 status *github.RepoStatus 82 statusCount int // Number of statuses in combined status. 83 } 84 85 func newFakeRepoService(org, repo, ref string, statuses int, collaborators []string) *fakeRepoService { 86 users := make([]*github.User, 0, len(collaborators)) 87 for _, user := range collaborators { 88 userCopy := user 89 users = append(users, &github.User{Login: &userCopy}) 90 } 91 return &fakeRepoService{org: org, repo: repo, collaborators: users, ref: ref, statusCount: statuses} 92 } 93 94 func (f *fakeRepoService) CreateStatus(ctx context.Context, org, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, *github.Response, error) { 95 resp := &github.Response{ 96 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 97 LastPage: 1, 98 } 99 if org != f.org { 100 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", org, f.org) 101 } 102 if repo != f.repo { 103 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 104 } 105 if ref != f.ref { 106 return nil, resp, fmt.Errorf("ref '%s' not recognized, only '%s' is valid", ref, f.ref) 107 } 108 f.status = status 109 return status, resp, nil 110 } 111 112 func (f *fakeRepoService) GetCombinedStatus(ctx context.Context, org, repo, ref string, opt *github.ListOptions) (*github.CombinedStatus, *github.Response, error) { 113 resp := &github.Response{ 114 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 115 LastPage: (f.statusCount + 1) / 2, 116 } 117 if org != f.org { 118 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", org, f.org) 119 } 120 if repo != f.repo { 121 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 122 } 123 if ref != f.ref { 124 return nil, resp, fmt.Errorf("ref '%s' not recognized, only '%s' is valid", ref, f.ref) 125 } 126 state := "success" 127 context1 := fmt.Sprintf("context %d", (opt.Page*2)-1) 128 context2 := fmt.Sprintf("context %d", opt.Page*2) 129 comb := &github.CombinedStatus{ 130 SHA: &ref, 131 State: &state, 132 Statuses: []github.RepoStatus{{Context: &context1}, {Context: &context2}}, 133 } 134 return comb, resp, nil 135 } 136 137 // ListCollaborators returns 2 collaborators per page of results (served in order). 138 func (f *fakeRepoService) ListCollaborators(ctx context.Context, owner, repo string, opt *github.ListOptions) ([]*github.User, *github.Response, error) { 139 resp := &github.Response{ 140 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 141 LastPage: (len(f.collaborators) + 1) / 2, 142 } 143 if owner != f.org { 144 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", owner, f.org) 145 } 146 if repo != f.repo { 147 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 148 } 149 if len(f.collaborators) == 0 { 150 return nil, resp, nil 151 } 152 return []*github.User{f.collaborators[(opt.Page*2)-2], f.collaborators[(opt.Page*2)-1]}, resp, nil 153 } 154 155 func TestCreateStatus(t *testing.T) { 156 contextStr := "context" 157 stateStr := "some state" 158 descStr := "descriptive description" 159 urlStr := "link" 160 sampleStatus := &github.RepoStatus{ 161 Context: &contextStr, 162 State: &stateStr, 163 Description: &descStr, 164 TargetURL: &urlStr, 165 } 166 svc := newFakeRepoService("k8s", "kuber", "ref", 0, nil) 167 client := &Client{repoService: svc} 168 setForTest(client) 169 status, err := client.CreateStatus("k8s", "kuber", "ref", sampleStatus) 170 if err != nil { 171 t.Fatalf("Unexpected error from CreateStatus with valid args: %v", err) 172 } 173 if status == nil { 174 t.Fatalf("Expected status returned by CreateStatus to be non-nil, but it was nil.") 175 } 176 if *status.Context != contextStr { 177 t.Errorf("Expected RepoStatus from CreateStatus to have a context of '%s' instead of '%s'", contextStr, *status.Context) 178 } 179 if *status.State != stateStr { 180 t.Errorf("Expected RepoStatus from CreateStatus to have a state of '%s' instead of '%s'", stateStr, *status.State) 181 } 182 if *status.Description != descStr { 183 t.Errorf("Expected RepoStatus from CreateStatus to have a description of '%s' instead of '%s'", descStr, *status.Description) 184 } 185 if *status.TargetURL != urlStr { 186 t.Errorf("Expected RepoStatus from CreateStatus to have a target URL of '%s' instead of '%s'", urlStr, *status.TargetURL) 187 } 188 189 _, err = client.CreateStatus("k8s", "kuber", "ref2", sampleStatus) 190 if err == nil { 191 t.Error("Expected error from CreateStatus on invalid ref, but didn't get an error.") 192 } 193 } 194 195 func TestGetCombinedStatus(t *testing.T) { 196 svc := newFakeRepoService("k8s", "kuber", "ref", 6, nil) 197 client := &Client{repoService: svc} 198 setForTest(client) 199 combStatus, err := client.GetCombinedStatus("k8s", "kuber", "ref") 200 if err != nil { 201 t.Fatalf("Unexpected error from GetCombinedStatus on valid args: %v", err) 202 } 203 if combStatus == nil { 204 t.Fatal("Expected the combined status to be non-nil!") 205 } 206 if *combStatus.State != "success" { 207 t.Errorf("Expected the combined status to have state 'success', not '%s'.", *combStatus.State) 208 } 209 if len(combStatus.Statuses) != svc.statusCount { 210 t.Errorf("Expected %d statuses in the combined status, not %d.", svc.statusCount, len(combStatus.Statuses)) 211 } 212 for index, status := range combStatus.Statuses { 213 if status.Context == nil { 214 t.Fatalf("CombinedStatus has status at index %d with a nil 'Context' field.", index) 215 } 216 expectedContext := fmt.Sprintf("context %d", index+1) 217 if *status.Context != expectedContext { 218 t.Errorf("Expected status at index %d to have a context of '%s' instead of '%s'.", index, expectedContext, *status.Context) 219 } 220 } 221 if _, err = client.GetCombinedStatus("k8s", "kuber", "ref2"); err == nil { 222 t.Error("Expected error getting CombinedStatus for non-existent ref, but got none.") 223 } 224 } 225 226 func TestGetCollaborators(t *testing.T) { 227 var users []*github.User 228 var err error 229 // test normal case 230 expected := []string{"a", "b", "c", "d"} 231 client := &Client{repoService: newFakeRepoService("k8s", "kuber", "", 0, expected)} 232 setForTest(client) 233 if users, err = client.GetCollaborators("k8s", "kuber"); err != nil { 234 t.Errorf("Unexpected error from GetCollaborators on valid org and repo: %v.", err) 235 } else { 236 for _, expect := range expected { 237 found := false 238 for _, user := range users { 239 if *user.Login == expect { 240 found = true 241 break 242 } 243 } 244 if !found { 245 t.Errorf("Expected to find %q as a collaborator, but did not.", expect) 246 } 247 } 248 if len(users) > len(expected) { 249 t.Errorf("Expected to find %d collaborators, but found %d instead.", len(expected), len(users)) 250 } 251 } 252 // test invalid repo 253 if users, err = client.GetCollaborators("not-an-org", "not a repo"); err == nil { 254 t.Error("Expected error from GetCollaborators, but did not get an error.") 255 } 256 if len(users) > 0 { 257 t.Errorf("Received users from GetCollaborators even though it returned an error.") 258 } 259 // test empty list 260 client = &Client{repoService: newFakeRepoService("k8s", "kuber", "", 0, nil)} 261 setForTest(client) 262 if users, err = client.GetCollaborators("k8s", "kuber"); err != nil { 263 t.Errorf("Unexpected error from GetCollaborators on valid org and repo: %v.", err) 264 } 265 if len(users) > 0 { 266 t.Errorf("Received users from GetCollaborators even though there are no collaborators.") 267 } 268 } 269 270 type fakeIssueService struct { 271 org, repo string 272 repoLabels []*github.Label 273 repoIssues map[int]*github.Issue 274 } 275 276 func newFakeIssueService(org, repo string, labels []string, issueCount int) *fakeIssueService { 277 repoLabels := make([]*github.Label, 0, len(labels)) 278 for _, label := range labels { 279 labelCopy := label 280 repoLabels = append(repoLabels, &github.Label{Name: &labelCopy}) 281 } 282 repoIssues := map[int]*github.Issue{} 283 for i := 1; i <= issueCount; i++ { 284 iCopy := i 285 text := fmt.Sprintf("%d", i) 286 issue := &github.Issue{ 287 Title: &text, 288 Body: &text, 289 Number: &iCopy, 290 Labels: []github.Label{{Name: &text}}, 291 Assignees: []*github.User{{Login: &text}}, 292 } 293 repoIssues[i] = issue 294 } 295 return &fakeIssueService{org: org, repo: repo, repoLabels: repoLabels, repoIssues: repoIssues} 296 } 297 298 func (f *fakeIssueService) Create(ctx context.Context, owner string, repo string, issue *github.IssueRequest) (*github.Issue, *github.Response, error) { 299 resp := &github.Response{Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}} 300 if owner != f.org { 301 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", owner, f.org) 302 } 303 if repo != f.repo { 304 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 305 } 306 number := len(f.repoIssues) + 1 307 result := &github.Issue{ 308 Title: issue.Title, 309 Body: issue.Body, 310 Number: &number, 311 } 312 for _, label := range *issue.Labels { 313 labelCopy := label 314 result.Labels = append(result.Labels, github.Label{Name: &labelCopy}) 315 } 316 for _, user := range *issue.Assignees { 317 userCopy := user 318 result.Assignees = append(result.Assignees, &github.User{Login: &userCopy}) 319 } 320 f.repoIssues[number] = result 321 return result, resp, nil 322 } 323 324 // ListByRepo returns 2 issues per page of results (served in order by number). 325 func (f *fakeIssueService) ListByRepo(ctx context.Context, org, repo string, opt *github.IssueListByRepoOptions) ([]*github.Issue, *github.Response, error) { 326 resp := &github.Response{ 327 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 328 LastPage: (len(f.repoIssues) + 1) / 2, 329 } 330 if org != f.org { 331 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", org, f.org) 332 } 333 if repo != f.repo { 334 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 335 } 336 if len(f.repoIssues) == 0 { 337 return nil, resp, nil 338 } 339 return []*github.Issue{f.repoIssues[(opt.ListOptions.Page*2)-1], f.repoIssues[opt.ListOptions.Page*2]}, resp, nil 340 } 341 342 // ListLabels returns 2 labels per page or results (served in order). 343 func (f *fakeIssueService) ListLabels(ctx context.Context, owner, repo string, opt *github.ListOptions) ([]*github.Label, *github.Response, error) { 344 resp := &github.Response{ 345 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 346 LastPage: (len(f.repoLabels) + 1) / 2, 347 } 348 if owner != f.org { 349 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", owner, f.org) 350 } 351 if repo != f.repo { 352 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 353 } 354 if len(f.repoLabels) == 0 { 355 return nil, resp, nil 356 } 357 return []*github.Label{f.repoLabels[(opt.Page*2)-2], f.repoLabels[(opt.Page*2)-1]}, resp, nil 358 } 359 360 func TestCreateIssue(t *testing.T) { 361 expectedLabels := []string{"label1", "label2"} 362 expectedAssignees := []string{"user1", "user2"} 363 364 svc := newFakeIssueService("k8s", "kuber", nil, 3) 365 client := &Client{issueService: svc} 366 setForTest(client) 367 issue, err := client.CreateIssue("k8s", "kuber", "Title", "Body", expectedLabels, expectedAssignees) 368 if err != nil { 369 t.Fatalf("Unexpected error from CreateIssue with valid args: %v.", err) 370 } 371 if issue == nil { 372 t.Fatalf("Expected issue returned by CreateIssue to be non-nil, but it was nil.") 373 } 374 if *issue.Title != "Title" { 375 t.Errorf("Expected issue from CreateIssue to have a title of 'Title' instead of '%s'.", *issue.Title) 376 } 377 if *issue.Body != "Body" { 378 t.Errorf("Expected issue from CreateIssue to have a state of 'Body' instead of '%s'.", *issue.Body) 379 } 380 for _, label := range expectedLabels { 381 found := false 382 for _, actual := range issue.Labels { 383 if *actual.Name == label { 384 found = true 385 break 386 } 387 } 388 if !found { 389 t.Errorf("Expected issue from CreateIssue to have the label '%s'.", label) 390 } 391 } 392 for _, assignee := range expectedAssignees { 393 found := false 394 for _, actual := range issue.Assignees { 395 if *actual.Login == assignee { 396 found = true 397 break 398 } 399 } 400 if !found { 401 t.Errorf("Expected issue from CreateIssue to have the assignee '%s'.", assignee) 402 } 403 } 404 405 _, err = client.CreateIssue("k8s", "not-a-repo", "Title", "Body", nil, nil) 406 if err == nil { 407 t.Error("Expected error from CreateIssue on invalid repo, but didn't get an error.") 408 } 409 } 410 411 func TestGetIssues(t *testing.T) { 412 var issues []*github.Issue 413 var err error 414 // test normal case 415 client := &Client{issueService: newFakeIssueService("k8s", "kuber", nil, 10)} 416 setForTest(client) 417 if issues, err = client.GetIssues("k8s", "kuber", &github.IssueListByRepoOptions{}); err != nil { 418 t.Errorf("Unexpected error from GetIssues on valid org and repo: %v.", err) 419 } else { 420 for i := 1; i <= 10; i++ { 421 found := false 422 for _, issue := range issues { 423 if *issue.Number == i { 424 found = true 425 break 426 } 427 } 428 if !found { 429 t.Errorf("Expected to find issue #%d, but did not.", i) 430 } 431 } 432 if len(issues) > 10 { 433 t.Errorf("Expected to find 10 issues, but found %d instead.", len(issues)) 434 } 435 } 436 // test invalid repo 437 if issues, err = client.GetIssues("not-an-org", "not a repo", &github.IssueListByRepoOptions{}); err == nil { 438 t.Error("Expected error from GetIssues, but did not get an error.") 439 } 440 if len(issues) > 0 { 441 t.Errorf("Received issues from GetIssues even though it returned an error.") 442 } 443 // test empty list 444 client = &Client{issueService: newFakeIssueService("k8s", "kuber", nil, 0)} 445 setForTest(client) 446 if issues, err = client.GetIssues("k8s", "kuber", &github.IssueListByRepoOptions{}); err != nil { 447 t.Errorf("Unexpected error from GetIssues on valid org and repo: %v.", err) 448 } 449 if len(issues) > 0 { 450 t.Errorf("Received %d issues from GetIssues even though there are no issues.", len(issues)) 451 } 452 } 453 454 func TestGetRepoLabels(t *testing.T) { 455 var labels []*github.Label 456 var err error 457 // test normal case 458 expected := []string{"a", "b", "c", "d"} 459 client := &Client{issueService: newFakeIssueService("k8s", "kuber", expected, 0)} 460 setForTest(client) 461 if labels, err = client.GetRepoLabels("k8s", "kuber"); err != nil { 462 t.Errorf("Unexpected error from GetRepoLabels on valid org and repo: %v.", err) 463 } else { 464 for _, expect := range expected { 465 found := false 466 for _, label := range labels { 467 if *label.Name == expect { 468 found = true 469 break 470 } 471 } 472 if !found { 473 t.Errorf("Expected to find %q as a label, but did not.", expect) 474 } 475 } 476 if len(labels) > len(expected) { 477 t.Errorf("Expected to find %d labels, but found %d instead.", len(expected), len(labels)) 478 } 479 } 480 // test invalid repo 481 if labels, err = client.GetRepoLabels("not-an-org", "not a repo"); err == nil { 482 t.Error("Expected error from GetRepoLabels, but did not get an error.") 483 } 484 if len(labels) > 0 { 485 t.Errorf("Received labels from GetRepoLabels even though it returned an error.") 486 } 487 // test empty list 488 client = &Client{issueService: newFakeIssueService("k8s", "kuber", nil, 0)} 489 setForTest(client) 490 if labels, err = client.GetRepoLabels("k8s", "kuber"); err != nil { 491 t.Errorf("Unexpected error from GetRepoLabels on valid org and repo: %v.", err) 492 } 493 if len(labels) > 0 { 494 t.Errorf("Received labels from GetRepoLabels even though there are no labels.") 495 } 496 } 497 498 type fakePullRequestService struct { 499 org, repo string 500 prCount int 501 } 502 503 // List returns 2 PRs per page of results. 504 func (f *fakePullRequestService) List(ctx context.Context, org, repo string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error) { 505 resp := &github.Response{ 506 Rate: github.Rate{Limit: 5000, Remaining: 1000, Reset: github.Timestamp{Time: time.Now()}}, 507 LastPage: (f.prCount + 1) / 2, 508 } 509 if org != f.org { 510 return nil, resp, fmt.Errorf("org '%s' not recognized, only '%s' is valid", org, f.org) 511 } 512 if repo != f.repo { 513 return nil, resp, fmt.Errorf("repo '%s' not recognized, only '%s' is valid", repo, f.repo) 514 } 515 title1 := fmt.Sprintf("Title %d", (opts.Page*2)-1) 516 title2 := fmt.Sprintf("Title %d", opts.Page*2) 517 return []*github.PullRequest{{Title: &title1}, {Title: &title2}}, resp, nil 518 } 519 520 func TestForEachPR(t *testing.T) { 521 svc := &fakePullRequestService{org: "k8s", repo: "kuber", prCount: 8} 522 client := &Client{prService: svc} 523 setForTest(client) 524 525 processed := 0 526 process := func(pr *github.PullRequest) error { 527 if pr == nil || pr.Title == nil { 528 return fmt.Errorf("pr %d was invalid", processed+1) 529 } 530 expectedTitle := fmt.Sprintf("Title %d", processed+1) 531 if *pr.Title != expectedTitle { 532 return fmt.Errorf("expected pr title '%s' but got '%s'", expectedTitle, *pr.Title) 533 } 534 processed++ 535 if processed == 13 { 536 // 13th PR processed returns an error because it is very, very unlucky. 537 return fmt.Errorf("some munge error") 538 } 539 return nil 540 } 541 // Test normal run without errors. 542 err := client.ForEachPR("k8s", "kuber", &github.PullRequestListOptions{}, false, process) 543 if err != nil { 544 t.Errorf("Unexpected error from ForEachPR: %v.", err) 545 } 546 if processed != svc.prCount { 547 t.Errorf("Expected ForEachPR to process %d PRs, but %d were processed.", svc.prCount, processed) 548 } 549 550 // Test break on error. 551 processed = 0 552 svc.prCount = 16 553 err = client.ForEachPR("k8s", "kuber", &github.PullRequestListOptions{}, false, process) 554 if err == nil { 555 t.Fatal("Expected error from ForEachPR after processing 13th PR, but got none.") 556 } 557 if processed != 13 { 558 t.Errorf("Expected 13 PRs to be processed, but %d were processed.", processed) 559 } 560 561 // Test continue on error. 562 processed = 0 563 err = client.ForEachPR("k8s", "kuber", &github.PullRequestListOptions{}, true, process) 564 if err != nil { 565 t.Fatalf("Unexpected error from ForEachPR with continue-on-error enabled: %v", err) 566 } 567 if processed != 16 { 568 t.Errorf("Expected 16 PRs to be processed, but %d were processed.", processed) 569 } 570 }