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  }