github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/github/client_test.go (about)

     1  /*
     2  Copyright 2016 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 github
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"reflect"
    29  	"strconv"
    30  	"strings"
    31  	"sync/atomic"
    32  	"testing"
    33  	"time"
    34  
    35  	"k8s.io/apimachinery/pkg/util/sets"
    36  )
    37  
    38  type testTime struct {
    39  	now   time.Time
    40  	slept time.Duration
    41  }
    42  
    43  func (tt *testTime) Sleep(d time.Duration) {
    44  	tt.slept = d
    45  }
    46  func (tt *testTime) Until(t time.Time) time.Duration {
    47  	return t.Sub(tt.now)
    48  }
    49  
    50  func getClient(url string) *Client {
    51  	getToken := func() []byte {
    52  		return []byte("")
    53  	}
    54  
    55  	return &Client{
    56  		time:     &standardTime{},
    57  		getToken: getToken,
    58  		client: &http.Client{
    59  			Transport: &http.Transport{
    60  				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    61  			},
    62  		},
    63  		bases: []string{url},
    64  	}
    65  }
    66  
    67  func TestRequestRateLimit(t *testing.T) {
    68  	tc := &testTime{now: time.Now()}
    69  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    70  		if tc.slept == 0 {
    71  			w.Header().Set("X-RateLimit-Remaining", "0")
    72  			w.Header().Set("X-RateLimit-Reset", strconv.Itoa(int(tc.now.Add(time.Second).Unix())))
    73  			http.Error(w, "403 Forbidden", http.StatusForbidden)
    74  		}
    75  	}))
    76  	defer ts.Close()
    77  	c := getClient(ts.URL)
    78  	c.time = tc
    79  	resp, err := c.requestRetry(http.MethodGet, "/", "", nil)
    80  	if err != nil {
    81  		t.Errorf("Error from request: %v", err)
    82  	} else if resp.StatusCode != 200 {
    83  		t.Errorf("Expected status code 200, got %d", resp.StatusCode)
    84  	} else if tc.slept < time.Second {
    85  		t.Errorf("Expected to sleep for at least a second, got %v", tc.slept)
    86  	}
    87  }
    88  
    89  func TestRetry404(t *testing.T) {
    90  	tc := &testTime{now: time.Now()}
    91  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    92  		if tc.slept == 0 {
    93  			http.Error(w, "404 Not Found", http.StatusNotFound)
    94  		}
    95  	}))
    96  	defer ts.Close()
    97  	c := getClient(ts.URL)
    98  	c.time = tc
    99  	resp, err := c.requestRetry(http.MethodGet, "/", "", nil)
   100  	if err != nil {
   101  		t.Errorf("Error from request: %v", err)
   102  	} else if resp.StatusCode != 200 {
   103  		t.Errorf("Expected status code 200, got %d", resp.StatusCode)
   104  	}
   105  }
   106  
   107  func TestRetryBase(t *testing.T) {
   108  	defer func(orig time.Duration) { initialDelay = orig }(initialDelay)
   109  	initialDelay = time.Microsecond
   110  
   111  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   112  	defer ts.Close()
   113  	c := getClient(ts.URL)
   114  	// One good endpoint:
   115  	c.bases = []string{c.bases[0]}
   116  	resp, err := c.requestRetry(http.MethodGet, "/", "", nil)
   117  	if err != nil {
   118  		t.Errorf("Error from request: %v", err)
   119  	} else if resp.StatusCode != 200 {
   120  		t.Errorf("Expected status code 200, got %d", resp.StatusCode)
   121  	}
   122  	// Bad endpoint followed by good endpoint:
   123  	c.bases = []string{"not-a-valid-base", c.bases[0]}
   124  	resp, err = c.requestRetry(http.MethodGet, "/", "", nil)
   125  	if err != nil {
   126  		t.Errorf("Error from request: %v", err)
   127  	} else if resp.StatusCode != 200 {
   128  		t.Errorf("Expected status code 200, got %d", resp.StatusCode)
   129  	}
   130  	// One bad endpoint:
   131  	c.bases = []string{"not-a-valid-base"}
   132  	resp, err = c.requestRetry(http.MethodGet, "/", "", nil)
   133  	if err == nil {
   134  		t.Error("Expected an error from a request to an invalid base, but succeeded!?")
   135  	}
   136  }
   137  
   138  func TestBotName(t *testing.T) {
   139  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   140  		if r.Method != http.MethodGet {
   141  			t.Errorf("Bad method: %s", r.Method)
   142  		}
   143  		if r.URL.Path != "/user" {
   144  			t.Errorf("Bad request path: %s", r.URL.Path)
   145  		}
   146  		fmt.Fprint(w, "{\"login\": \"wowza\"}")
   147  	}))
   148  	c := getClient(ts.URL)
   149  	botName, err := c.BotName()
   150  	if err != nil {
   151  		t.Errorf("Didn't expect error: %v", err)
   152  	} else if botName != "wowza" {
   153  		t.Errorf("Wrong bot name. Got %s, expected wowza.", botName)
   154  	}
   155  	ts.Close()
   156  	botName, err = c.BotName()
   157  	if err != nil {
   158  		t.Errorf("Didn't expect error: %v", err)
   159  	} else if botName != "wowza" {
   160  		t.Errorf("Wrong bot name. Got %s, expected wowza.", botName)
   161  	}
   162  }
   163  
   164  func TestIsMember(t *testing.T) {
   165  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   166  		if r.Method != http.MethodGet {
   167  			t.Errorf("Bad method: %s", r.Method)
   168  		}
   169  		if r.URL.Path != "/orgs/k8s/members/person" {
   170  			t.Errorf("Bad request path: %s", r.URL.Path)
   171  		}
   172  		http.Error(w, "204 No Content", http.StatusNoContent)
   173  	}))
   174  	defer ts.Close()
   175  	c := getClient(ts.URL)
   176  	mem, err := c.IsMember("k8s", "person")
   177  	if err != nil {
   178  		t.Errorf("Didn't expect error: %v", err)
   179  	} else if !mem {
   180  		t.Errorf("Should be member.")
   181  	}
   182  }
   183  
   184  func TestCreateComment(t *testing.T) {
   185  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   186  		if r.Method != http.MethodPost {
   187  			t.Errorf("Bad method: %s", r.Method)
   188  		}
   189  		if r.URL.Path != "/repos/k8s/kuber/issues/5/comments" {
   190  			t.Errorf("Bad request path: %s", r.URL.Path)
   191  		}
   192  		b, err := ioutil.ReadAll(r.Body)
   193  		if err != nil {
   194  			t.Fatalf("Could not read request body: %v", err)
   195  		}
   196  		var ic IssueComment
   197  		if err := json.Unmarshal(b, &ic); err != nil {
   198  			t.Errorf("Could not unmarshal request: %v", err)
   199  		} else if ic.Body != "hello" {
   200  			t.Errorf("Wrong body: %s", ic.Body)
   201  		}
   202  		http.Error(w, "201 Created", http.StatusCreated)
   203  	}))
   204  	defer ts.Close()
   205  	c := getClient(ts.URL)
   206  	if err := c.CreateComment("k8s", "kuber", 5, "hello"); err != nil {
   207  		t.Errorf("Didn't expect error: %v", err)
   208  	}
   209  }
   210  
   211  func TestCreateCommentReaction(t *testing.T) {
   212  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   213  		if r.Method != http.MethodPost {
   214  			t.Errorf("Bad method: %s", r.Method)
   215  		}
   216  		if r.URL.Path != "/repos/k8s/kuber/issues/comments/5/reactions" {
   217  			t.Errorf("Bad request path: %s", r.URL.Path)
   218  		}
   219  		if r.Header.Get("Accept") != "application/vnd.github.squirrel-girl-preview" {
   220  			t.Errorf("Bad Accept header: %s", r.Header.Get("Accept"))
   221  		}
   222  		http.Error(w, "201 Created", http.StatusCreated)
   223  	}))
   224  	defer ts.Close()
   225  	c := getClient(ts.URL)
   226  	if err := c.CreateCommentReaction("k8s", "kuber", 5, "+1"); err != nil {
   227  		t.Errorf("Didn't expect error: %v", err)
   228  	}
   229  }
   230  
   231  func TestDeleteComment(t *testing.T) {
   232  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   233  		if r.Method != http.MethodDelete {
   234  			t.Errorf("Bad method: %s", r.Method)
   235  		}
   236  		if r.URL.Path != "/repos/k8s/kuber/issues/comments/123" {
   237  			t.Errorf("Bad request path: %s", r.URL.Path)
   238  		}
   239  		http.Error(w, "204 No Content", http.StatusNoContent)
   240  	}))
   241  	defer ts.Close()
   242  	c := getClient(ts.URL)
   243  	if err := c.DeleteComment("k8s", "kuber", 123); err != nil {
   244  		t.Errorf("Didn't expect error: %v", err)
   245  	}
   246  }
   247  
   248  func TestGetPullRequest(t *testing.T) {
   249  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   250  		if r.Method != http.MethodGet {
   251  			t.Errorf("Bad method: %s", r.Method)
   252  		}
   253  		if r.URL.Path != "/repos/k8s/kuber/pulls/12" {
   254  			t.Errorf("Bad request path: %s", r.URL.Path)
   255  		}
   256  		pr := PullRequest{
   257  			User: User{Login: "bla"},
   258  		}
   259  		b, err := json.Marshal(&pr)
   260  		if err != nil {
   261  			t.Fatalf("Didn't expect error: %v", err)
   262  		}
   263  		fmt.Fprint(w, string(b))
   264  	}))
   265  	defer ts.Close()
   266  	c := getClient(ts.URL)
   267  	pr, err := c.GetPullRequest("k8s", "kuber", 12)
   268  	if err != nil {
   269  		t.Errorf("Didn't expect error: %v", err)
   270  	} else if pr.User.Login != "bla" {
   271  		t.Errorf("Wrong user: %s", pr.User.Login)
   272  	}
   273  }
   274  
   275  func TestGetPullRequestChanges(t *testing.T) {
   276  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   277  		if r.Method != http.MethodGet {
   278  			t.Errorf("Bad method: %s", r.Method)
   279  		}
   280  		if r.URL.Path != "/repos/k8s/kuber/pulls/12/files" {
   281  			t.Errorf("Bad request path: %s", r.URL.Path)
   282  		}
   283  		changes := []PullRequestChange{
   284  			{Filename: "foo.txt"},
   285  		}
   286  		b, err := json.Marshal(&changes)
   287  		if err != nil {
   288  			t.Fatalf("Didn't expect error: %v", err)
   289  		}
   290  		fmt.Fprint(w, string(b))
   291  	}))
   292  	defer ts.Close()
   293  	c := getClient(ts.URL)
   294  	cs, err := c.GetPullRequestChanges("k8s", "kuber", 12)
   295  	if err != nil {
   296  		t.Errorf("Didn't expect error: %v", err)
   297  	}
   298  	if len(cs) != 1 || cs[0].Filename != "foo.txt" {
   299  		t.Errorf("Wrong result: %#v", cs)
   300  	}
   301  }
   302  
   303  func TestGetRef(t *testing.T) {
   304  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   305  		if r.Method != http.MethodGet {
   306  			t.Errorf("Bad method: %s", r.Method)
   307  		}
   308  		if r.URL.Path != "/repos/k8s/kuber/git/refs/heads/mastah" {
   309  			t.Errorf("Bad request path: %s", r.URL.Path)
   310  		}
   311  		fmt.Fprint(w, `{"object": {"sha":"abcde"}}`)
   312  	}))
   313  	defer ts.Close()
   314  	c := getClient(ts.URL)
   315  	sha, err := c.GetRef("k8s", "kuber", "heads/mastah")
   316  	if err != nil {
   317  		t.Errorf("Didn't expect error: %v", err)
   318  	} else if sha != "abcde" {
   319  		t.Errorf("Wrong sha: %s", sha)
   320  	}
   321  }
   322  
   323  func TestCreateStatus(t *testing.T) {
   324  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   325  		if r.Method != http.MethodPost {
   326  			t.Errorf("Bad method: %s", r.Method)
   327  		}
   328  		if r.URL.Path != "/repos/k8s/kuber/statuses/abcdef" {
   329  			t.Errorf("Bad request path: %s", r.URL.Path)
   330  		}
   331  		b, err := ioutil.ReadAll(r.Body)
   332  		if err != nil {
   333  			t.Fatalf("Could not read request body: %v", err)
   334  		}
   335  		var s Status
   336  		if err := json.Unmarshal(b, &s); err != nil {
   337  			t.Errorf("Could not unmarshal request: %v", err)
   338  		} else if s.Context != "c" {
   339  			t.Errorf("Wrong context: %s", s.Context)
   340  		}
   341  		http.Error(w, "201 Created", http.StatusCreated)
   342  	}))
   343  	defer ts.Close()
   344  	c := getClient(ts.URL)
   345  	if err := c.CreateStatus("k8s", "kuber", "abcdef", Status{
   346  		Context: "c",
   347  	}); err != nil {
   348  		t.Errorf("Didn't expect error: %v", err)
   349  	}
   350  }
   351  
   352  func TestListIssueComments(t *testing.T) {
   353  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   354  		if r.Method != http.MethodGet {
   355  			t.Errorf("Bad method: %s", r.Method)
   356  		}
   357  		if r.URL.Path == "/repos/k8s/kuber/issues/15/comments" {
   358  			ics := []IssueComment{{ID: 1}}
   359  			b, err := json.Marshal(ics)
   360  			if err != nil {
   361  				t.Fatalf("Didn't expect error: %v", err)
   362  			}
   363  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/someotherpath>; rel="next"`, r.Host))
   364  			fmt.Fprint(w, string(b))
   365  		} else if r.URL.Path == "/someotherpath" {
   366  			ics := []IssueComment{{ID: 2}}
   367  			b, err := json.Marshal(ics)
   368  			if err != nil {
   369  				t.Fatalf("Didn't expect error: %v", err)
   370  			}
   371  			fmt.Fprint(w, string(b))
   372  		} else {
   373  			t.Errorf("Bad request path: %s", r.URL.Path)
   374  		}
   375  	}))
   376  	defer ts.Close()
   377  	c := getClient(ts.URL)
   378  	ics, err := c.ListIssueComments("k8s", "kuber", 15)
   379  	if err != nil {
   380  		t.Errorf("Didn't expect error: %v", err)
   381  	} else if len(ics) != 2 {
   382  		t.Errorf("Expected two issues, found %d: %v", len(ics), ics)
   383  	} else if ics[0].ID != 1 || ics[1].ID != 2 {
   384  		t.Errorf("Wrong issue IDs: %v", ics)
   385  	}
   386  }
   387  
   388  func TestAddLabel(t *testing.T) {
   389  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   390  		if r.Method != http.MethodPost {
   391  			t.Errorf("Bad method: %s", r.Method)
   392  		}
   393  		if r.URL.Path != "/repos/k8s/kuber/issues/5/labels" {
   394  			t.Errorf("Bad request path: %s", r.URL.Path)
   395  		}
   396  		b, err := ioutil.ReadAll(r.Body)
   397  		if err != nil {
   398  			t.Fatalf("Could not read request body: %v", err)
   399  		}
   400  		var ls []string
   401  		if err := json.Unmarshal(b, &ls); err != nil {
   402  			t.Errorf("Could not unmarshal request: %v", err)
   403  		} else if len(ls) != 1 {
   404  			t.Errorf("Wrong length labels: %v", ls)
   405  		} else if ls[0] != "yay" {
   406  			t.Errorf("Wrong label: %s", ls[0])
   407  		}
   408  	}))
   409  	defer ts.Close()
   410  	c := getClient(ts.URL)
   411  	if err := c.AddLabel("k8s", "kuber", 5, "yay"); err != nil {
   412  		t.Errorf("Didn't expect error: %v", err)
   413  	}
   414  }
   415  
   416  func TestRemoveLabel(t *testing.T) {
   417  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   418  		if r.Method != http.MethodDelete {
   419  			t.Errorf("Bad method: %s", r.Method)
   420  		}
   421  		if r.URL.Path != "/repos/k8s/kuber/issues/5/labels/yay" {
   422  			t.Errorf("Bad request path: %s", r.URL.Path)
   423  		}
   424  		http.Error(w, "204 No Content", http.StatusNoContent)
   425  	}))
   426  	defer ts.Close()
   427  	c := getClient(ts.URL)
   428  	if err := c.RemoveLabel("k8s", "kuber", 5, "yay"); err != nil {
   429  		t.Errorf("Didn't expect error: %v", err)
   430  	}
   431  }
   432  
   433  func TestRemoveLabelFailsOnOtherThan404(t *testing.T) {
   434  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   435  		if r.Method != http.MethodDelete {
   436  			t.Errorf("Bad method: %s", r.Method)
   437  		}
   438  		if r.URL.Path != "/repos/k8s/kuber/issues/5/labels/yay" {
   439  			t.Errorf("Bad request path: %s", r.URL.Path)
   440  		}
   441  		http.Error(w, "403 Forbidden", http.StatusForbidden)
   442  	}))
   443  	defer ts.Close()
   444  	c := getClient(ts.URL)
   445  	err := c.RemoveLabel("k8s", "kuber", 5, "yay")
   446  	if err == nil {
   447  		t.Errorf("Expected error but got none")
   448  	}
   449  	if _, ok := err.(*LabelNotFound); ok {
   450  		t.Fatalf("Expected error not to be a 404: %v", err)
   451  	}
   452  }
   453  
   454  func TestRemoveLabelNotFound(t *testing.T) {
   455  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   456  		http.Error(w, `{"message": "Label does not exist"}`, 404)
   457  	}))
   458  	defer ts.Close()
   459  	c := getClient(ts.URL)
   460  	err := c.RemoveLabel("any", "old", 3, "label")
   461  
   462  	if err == nil {
   463  		t.Fatalf("RemoveLabel expected an error, got none")
   464  	}
   465  
   466  	if _, ok := err.(*LabelNotFound); !ok {
   467  		t.Fatalf("RemoveLabel expected LabelNotFound error, got %v", err)
   468  	}
   469  }
   470  
   471  func TestAssignIssue(t *testing.T) {
   472  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   473  		if r.Method != http.MethodPost {
   474  			t.Errorf("Bad method: %s", r.Method)
   475  		}
   476  		if r.URL.Path != "/repos/k8s/kuber/issues/5/assignees" {
   477  			t.Errorf("Bad request path: %s", r.URL.Path)
   478  		}
   479  		b, err := ioutil.ReadAll(r.Body)
   480  		if err != nil {
   481  			t.Fatalf("Could not read request body: %v", err)
   482  		}
   483  		var ps map[string][]string
   484  		if err := json.Unmarshal(b, &ps); err != nil {
   485  			t.Errorf("Could not unmarshal request: %v", err)
   486  		} else if len(ps) != 1 {
   487  			t.Errorf("Wrong length patch: %v", ps)
   488  		} else if len(ps["assignees"]) == 3 {
   489  			if ps["assignees"][0] != "george" || ps["assignees"][1] != "jungle" || ps["assignees"][2] != "not-in-the-org" {
   490  				t.Errorf("Wrong assignees: %v", ps)
   491  			}
   492  		} else if len(ps["assignees"]) == 2 {
   493  			if ps["assignees"][0] != "george" || ps["assignees"][1] != "jungle" {
   494  				t.Errorf("Wrong assignees: %v", ps)
   495  			}
   496  
   497  		} else {
   498  			t.Errorf("Wrong assignees length: %v", ps)
   499  		}
   500  		w.WriteHeader(http.StatusCreated)
   501  		json.NewEncoder(w).Encode(Issue{
   502  			Assignees: []User{{Login: "george"}, {Login: "jungle"}, {Login: "ignore-other"}},
   503  		})
   504  	}))
   505  	defer ts.Close()
   506  	c := getClient(ts.URL)
   507  	if err := c.AssignIssue("k8s", "kuber", 5, []string{"george", "jungle"}); err != nil {
   508  		t.Errorf("Unexpected error: %v", err)
   509  	}
   510  	if err := c.AssignIssue("k8s", "kuber", 5, []string{"george", "jungle", "not-in-the-org"}); err == nil {
   511  		t.Errorf("Expected an error")
   512  	} else if merr, ok := err.(MissingUsers); ok {
   513  		if len(merr.Users) != 1 || merr.Users[0] != "not-in-the-org" {
   514  			t.Errorf("Expected [not-in-the-org], not %v", merr.Users)
   515  		}
   516  	} else {
   517  		t.Errorf("Expected MissingUsers error")
   518  	}
   519  }
   520  
   521  func TestUnassignIssue(t *testing.T) {
   522  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   523  		if r.Method != http.MethodDelete {
   524  			t.Errorf("Bad method: %s", r.Method)
   525  		}
   526  		if r.URL.Path != "/repos/k8s/kuber/issues/5/assignees" {
   527  			t.Errorf("Bad request path: %s", r.URL.Path)
   528  		}
   529  		b, err := ioutil.ReadAll(r.Body)
   530  		if err != nil {
   531  			t.Fatalf("Could not read request body: %v", err)
   532  		}
   533  		var ps map[string][]string
   534  		if err := json.Unmarshal(b, &ps); err != nil {
   535  			t.Errorf("Could not unmarshal request: %v", err)
   536  		} else if len(ps) != 1 {
   537  			t.Errorf("Wrong length patch: %v", ps)
   538  		} else if len(ps["assignees"]) == 3 {
   539  			if ps["assignees"][0] != "george" || ps["assignees"][1] != "jungle" || ps["assignees"][2] != "perma-assignee" {
   540  				t.Errorf("Wrong assignees: %v", ps)
   541  			}
   542  		} else if len(ps["assignees"]) == 2 {
   543  			if ps["assignees"][0] != "george" || ps["assignees"][1] != "jungle" {
   544  				t.Errorf("Wrong assignees: %v", ps)
   545  			}
   546  
   547  		} else {
   548  			t.Errorf("Wrong assignees length: %v", ps)
   549  		}
   550  		json.NewEncoder(w).Encode(Issue{
   551  			Assignees: []User{{Login: "perma-assignee"}, {Login: "ignore-other"}},
   552  		})
   553  	}))
   554  	defer ts.Close()
   555  	c := getClient(ts.URL)
   556  	if err := c.UnassignIssue("k8s", "kuber", 5, []string{"george", "jungle"}); err != nil {
   557  		t.Errorf("Unexpected error: %v", err)
   558  	}
   559  	if err := c.UnassignIssue("k8s", "kuber", 5, []string{"george", "jungle", "perma-assignee"}); err == nil {
   560  		t.Errorf("Expected an error")
   561  	} else if merr, ok := err.(ExtraUsers); ok {
   562  		if len(merr.Users) != 1 || merr.Users[0] != "perma-assignee" {
   563  			t.Errorf("Expected [perma-assignee], not %v", merr.Users)
   564  		}
   565  	} else {
   566  		t.Errorf("Expected ExtraUsers error")
   567  	}
   568  }
   569  
   570  func TestReadPaginatedResults(t *testing.T) {
   571  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   572  		if r.Method != http.MethodGet {
   573  			t.Errorf("Bad method: %s", r.Method)
   574  		}
   575  		if r.URL.Path == "/label/foo" {
   576  			objects := []Label{{Name: "foo"}}
   577  			b, err := json.Marshal(objects)
   578  			if err != nil {
   579  				t.Fatalf("Didn't expect error: %v", err)
   580  			}
   581  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/label/bar>; rel="next"`, r.Host))
   582  			fmt.Fprint(w, string(b))
   583  		} else if r.URL.Path == "/label/bar" {
   584  			objects := []Label{{Name: "bar"}}
   585  			b, err := json.Marshal(objects)
   586  			if err != nil {
   587  				t.Fatalf("Didn't expect error: %v", err)
   588  			}
   589  			fmt.Fprint(w, string(b))
   590  		} else {
   591  			t.Errorf("Bad request path: %s", r.URL.Path)
   592  		}
   593  	}))
   594  	defer ts.Close()
   595  	c := getClient(ts.URL)
   596  	path := "/label/foo"
   597  	var labels []Label
   598  	err := c.readPaginatedResults(
   599  		path,
   600  		"",
   601  		func() interface{} {
   602  			return &[]Label{}
   603  		},
   604  		func(obj interface{}) {
   605  			labels = append(labels, *(obj.(*[]Label))...)
   606  		},
   607  	)
   608  	if err != nil {
   609  		t.Errorf("Didn't expect error: %v", err)
   610  	} else if len(labels) != 2 {
   611  		t.Errorf("Expected two labels, found %d: %v", len(labels), labels)
   612  	} else if labels[0].Name != "foo" || labels[1].Name != "bar" {
   613  		t.Errorf("Wrong label names: %v", labels)
   614  	}
   615  }
   616  
   617  func TestListPullRequestComments(t *testing.T) {
   618  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   619  		if r.Method != http.MethodGet {
   620  			t.Errorf("Bad method: %s", r.Method)
   621  		}
   622  		if r.URL.Path == "/repos/k8s/kuber/pulls/15/comments" {
   623  			prcs := []ReviewComment{{ID: 1}}
   624  			b, err := json.Marshal(prcs)
   625  			if err != nil {
   626  				t.Fatalf("Didn't expect error: %v", err)
   627  			}
   628  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/someotherpath>; rel="next"`, r.Host))
   629  			fmt.Fprint(w, string(b))
   630  		} else if r.URL.Path == "/someotherpath" {
   631  			prcs := []ReviewComment{{ID: 2}}
   632  			b, err := json.Marshal(prcs)
   633  			if err != nil {
   634  				t.Fatalf("Didn't expect error: %v", err)
   635  			}
   636  			fmt.Fprint(w, string(b))
   637  		} else {
   638  			t.Errorf("Bad request path: %s", r.URL.Path)
   639  		}
   640  	}))
   641  	defer ts.Close()
   642  	c := getClient(ts.URL)
   643  	prcs, err := c.ListPullRequestComments("k8s", "kuber", 15)
   644  	if err != nil {
   645  		t.Errorf("Didn't expect error: %v", err)
   646  	} else if len(prcs) != 2 {
   647  		t.Errorf("Expected two comments, found %d: %v", len(prcs), prcs)
   648  	} else if prcs[0].ID != 1 || prcs[1].ID != 2 {
   649  		t.Errorf("Wrong issue IDs: %v", prcs)
   650  	}
   651  }
   652  
   653  func TestListReviews(t *testing.T) {
   654  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   655  		if r.Method != http.MethodGet {
   656  			t.Errorf("Bad method: %s", r.Method)
   657  		}
   658  		if r.URL.Path == "/repos/k8s/kuber/pulls/15/reviews" {
   659  			reviews := []Review{{ID: 1}}
   660  			b, err := json.Marshal(reviews)
   661  			if err != nil {
   662  				t.Fatalf("Didn't expect error: %v", err)
   663  			}
   664  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/someotherpath>; rel="next"`, r.Host))
   665  			fmt.Fprint(w, string(b))
   666  		} else if r.URL.Path == "/someotherpath" {
   667  			reviews := []Review{{ID: 2}}
   668  			b, err := json.Marshal(reviews)
   669  			if err != nil {
   670  				t.Fatalf("Didn't expect error: %v", err)
   671  			}
   672  			fmt.Fprint(w, string(b))
   673  		} else {
   674  			t.Errorf("Bad request path: %s", r.URL.Path)
   675  		}
   676  	}))
   677  	defer ts.Close()
   678  	c := getClient(ts.URL)
   679  	reviews, err := c.ListReviews("k8s", "kuber", 15)
   680  	if err != nil {
   681  		t.Errorf("Didn't expect error: %v", err)
   682  	} else if len(reviews) != 2 {
   683  		t.Errorf("Expected two reviews, found %d: %v", len(reviews), reviews)
   684  	} else if reviews[0].ID != 1 || reviews[1].ID != 2 {
   685  		t.Errorf("Wrong review IDs: %v", reviews)
   686  	}
   687  }
   688  
   689  func TestPrepareReviewersBody(t *testing.T) {
   690  	var tests = []struct {
   691  		name         string
   692  		logins       []string
   693  		expectedBody map[string][]string
   694  	}{
   695  		{
   696  			name:         "one reviewer",
   697  			logins:       []string{"george"},
   698  			expectedBody: map[string][]string{"reviewers": {"george"}},
   699  		},
   700  		{
   701  			name:         "three reviewers",
   702  			logins:       []string{"george", "jungle", "chimp"},
   703  			expectedBody: map[string][]string{"reviewers": {"george", "jungle", "chimp"}},
   704  		},
   705  		{
   706  			name:         "one team",
   707  			logins:       []string{"kubernetes/sig-testing-misc"},
   708  			expectedBody: map[string][]string{"team_reviewers": {"sig-testing-misc"}},
   709  		},
   710  		{
   711  			name:         "two teams",
   712  			logins:       []string{"kubernetes/sig-testing-misc", "kubernetes/sig-testing-bugs"},
   713  			expectedBody: map[string][]string{"team_reviewers": {"sig-testing-misc", "sig-testing-bugs"}},
   714  		},
   715  		{
   716  			name:         "one team not in org",
   717  			logins:       []string{"kubernetes/sig-testing-misc", "other-org/sig-testing-bugs"},
   718  			expectedBody: map[string][]string{"team_reviewers": {"sig-testing-misc"}},
   719  		},
   720  		{
   721  			name:         "mixed single",
   722  			logins:       []string{"george", "kubernetes/sig-testing-misc"},
   723  			expectedBody: map[string][]string{"reviewers": {"george"}, "team_reviewers": {"sig-testing-misc"}},
   724  		},
   725  		{
   726  			name:         "mixed multiple",
   727  			logins:       []string{"george", "kubernetes/sig-testing-misc", "kubernetes/sig-testing-bugs", "jungle", "chimp"},
   728  			expectedBody: map[string][]string{"reviewers": {"george", "jungle", "chimp"}, "team_reviewers": {"sig-testing-misc", "sig-testing-bugs"}},
   729  		},
   730  	}
   731  	for _, test := range tests {
   732  		body, _ := prepareReviewersBody(test.logins, "kubernetes")
   733  		if !reflect.DeepEqual(body, test.expectedBody) {
   734  			t.Errorf("%s: got %s instead of %s", test.name, body, test.expectedBody)
   735  		}
   736  	}
   737  }
   738  
   739  func TestRequestReview(t *testing.T) {
   740  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   741  		if r.Method != http.MethodPost {
   742  			t.Errorf("Bad method: %s", r.Method)
   743  		}
   744  		if r.URL.Path != "/repos/k8s/kuber/pulls/5/requested_reviewers" {
   745  			t.Errorf("Bad request path: %s", r.URL.Path)
   746  		}
   747  		b, err := ioutil.ReadAll(r.Body)
   748  		if err != nil {
   749  			t.Fatalf("Could not read request body: %v", err)
   750  		}
   751  		var ps map[string][]string
   752  		if err := json.Unmarshal(b, &ps); err != nil {
   753  			t.Fatalf("Could not unmarshal request: %v", err)
   754  		}
   755  		if len(ps) < 1 || len(ps) > 2 {
   756  			t.Fatalf("Wrong length patch: %v", ps)
   757  		}
   758  		if sets.NewString(ps["reviewers"]...).Has("not-a-collaborator") {
   759  			w.WriteHeader(http.StatusUnprocessableEntity)
   760  			return
   761  		}
   762  		requestedReviewers := []User{}
   763  		for _, reviewers := range ps {
   764  			for _, reviewer := range reviewers {
   765  				requestedReviewers = append(requestedReviewers, User{Login: reviewer})
   766  			}
   767  		}
   768  		w.WriteHeader(http.StatusCreated)
   769  		json.NewEncoder(w).Encode(PullRequest{
   770  			RequestedReviewers: requestedReviewers,
   771  		})
   772  	}))
   773  	defer ts.Close()
   774  	c := getClient(ts.URL)
   775  	if err := c.RequestReview("k8s", "kuber", 5, []string{"george", "jungle"}); err != nil {
   776  		t.Errorf("Unexpected error: %v", err)
   777  	}
   778  	if err := c.RequestReview("k8s", "kuber", 5, []string{"george", "jungle", "k8s/team1"}); err != nil {
   779  		t.Errorf("Unexpected error: %v", err)
   780  	}
   781  	if err := c.RequestReview("k8s", "kuber", 5, []string{"george", "jungle", "not-a-collaborator"}); err == nil {
   782  		t.Errorf("Expected an error")
   783  	} else if merr, ok := err.(MissingUsers); ok {
   784  		if len(merr.Users) != 1 || merr.Users[0] != "not-a-collaborator" {
   785  			t.Errorf("Expected [not-a-collaborator], not %v", merr.Users)
   786  		}
   787  	} else {
   788  		t.Errorf("Expected MissingUsers error")
   789  	}
   790  	if err := c.RequestReview("k8s", "kuber", 5, []string{"george", "jungle", "notk8s/team1"}); err == nil {
   791  		t.Errorf("Expected an error")
   792  	} else if merr, ok := err.(MissingUsers); ok {
   793  		if len(merr.Users) != 1 || merr.Users[0] != "notk8s/team1" {
   794  			t.Errorf("Expected [notk8s/team1], not %v", merr.Users)
   795  		}
   796  	} else {
   797  		t.Errorf("Expected MissingUsers error")
   798  	}
   799  }
   800  
   801  func TestUnrequestReview(t *testing.T) {
   802  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   803  		if r.Method != http.MethodDelete {
   804  			t.Errorf("Bad method: %s", r.Method)
   805  		}
   806  		if r.URL.Path != "/repos/k8s/kuber/pulls/5/requested_reviewers" {
   807  			t.Errorf("Bad request path: %s", r.URL.Path)
   808  		}
   809  		b, err := ioutil.ReadAll(r.Body)
   810  		if err != nil {
   811  			t.Fatalf("Could not read request body: %v", err)
   812  		}
   813  		var ps map[string][]string
   814  		if err := json.Unmarshal(b, &ps); err != nil {
   815  			t.Errorf("Could not unmarshal request: %v", err)
   816  		} else if len(ps) != 1 {
   817  			t.Errorf("Wrong length patch: %v", ps)
   818  		} else if len(ps["reviewers"]) == 3 {
   819  			if ps["reviewers"][0] != "george" || ps["reviewers"][1] != "jungle" || ps["reviewers"][2] != "perma-reviewer" {
   820  				t.Errorf("Wrong reviewers: %v", ps)
   821  			}
   822  		} else if len(ps["reviewers"]) == 2 {
   823  			if ps["reviewers"][0] != "george" || ps["reviewers"][1] != "jungle" {
   824  				t.Errorf("Wrong reviewers: %v", ps)
   825  			}
   826  		} else {
   827  			t.Errorf("Wrong reviewers length: %v", ps)
   828  		}
   829  		json.NewEncoder(w).Encode(PullRequest{
   830  			RequestedReviewers: []User{{Login: "perma-reviewer"}, {Login: "ignore-other"}},
   831  		})
   832  	}))
   833  	defer ts.Close()
   834  	c := getClient(ts.URL)
   835  	if err := c.UnrequestReview("k8s", "kuber", 5, []string{"george", "jungle"}); err != nil {
   836  		t.Errorf("Unexpected error: %v", err)
   837  	}
   838  	if err := c.UnrequestReview("k8s", "kuber", 5, []string{"george", "jungle", "perma-reviewer"}); err == nil {
   839  		t.Errorf("Expected an error")
   840  	} else if merr, ok := err.(ExtraUsers); ok {
   841  		if len(merr.Users) != 1 || merr.Users[0] != "perma-reviewer" {
   842  			t.Errorf("Expected [perma-reviewer], not %v", merr.Users)
   843  		}
   844  	} else {
   845  		t.Errorf("Expected ExtraUsers error")
   846  	}
   847  }
   848  
   849  func TestCloseIssue(t *testing.T) {
   850  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   851  		if r.Method != http.MethodPatch {
   852  			t.Errorf("Bad method: %s", r.Method)
   853  		}
   854  		if r.URL.Path != "/repos/k8s/kuber/issues/5" {
   855  			t.Errorf("Bad request path: %s", r.URL.Path)
   856  		}
   857  		b, err := ioutil.ReadAll(r.Body)
   858  		if err != nil {
   859  			t.Fatalf("Could not read request body: %v", err)
   860  		}
   861  		var ps map[string]string
   862  		if err := json.Unmarshal(b, &ps); err != nil {
   863  			t.Errorf("Could not unmarshal request: %v", err)
   864  		} else if len(ps) != 1 {
   865  			t.Errorf("Wrong length patch: %v", ps)
   866  		} else if ps["state"] != "closed" {
   867  			t.Errorf("Wrong state: %s", ps["state"])
   868  		}
   869  	}))
   870  	defer ts.Close()
   871  	c := getClient(ts.URL)
   872  	if err := c.CloseIssue("k8s", "kuber", 5); err != nil {
   873  		t.Errorf("Didn't expect error: %v", err)
   874  	}
   875  }
   876  
   877  func TestReopenIssue(t *testing.T) {
   878  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   879  		if r.Method != http.MethodPatch {
   880  			t.Errorf("Bad method: %s", r.Method)
   881  		}
   882  		if r.URL.Path != "/repos/k8s/kuber/issues/5" {
   883  			t.Errorf("Bad request path: %s", r.URL.Path)
   884  		}
   885  		b, err := ioutil.ReadAll(r.Body)
   886  		if err != nil {
   887  			t.Fatalf("Could not read request body: %v", err)
   888  		}
   889  		var ps map[string]string
   890  		if err := json.Unmarshal(b, &ps); err != nil {
   891  			t.Errorf("Could not unmarshal request: %v", err)
   892  		} else if len(ps) != 1 {
   893  			t.Errorf("Wrong length patch: %v", ps)
   894  		} else if ps["state"] != "open" {
   895  			t.Errorf("Wrong state: %s", ps["state"])
   896  		}
   897  	}))
   898  	defer ts.Close()
   899  	c := getClient(ts.URL)
   900  	if err := c.ReopenIssue("k8s", "kuber", 5); err != nil {
   901  		t.Errorf("Didn't expect error: %v", err)
   902  	}
   903  }
   904  
   905  func TestClosePR(t *testing.T) {
   906  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   907  		if r.Method != http.MethodPatch {
   908  			t.Errorf("Bad method: %s", r.Method)
   909  		}
   910  		if r.URL.Path != "/repos/k8s/kuber/pulls/5" {
   911  			t.Errorf("Bad request path: %s", r.URL.Path)
   912  		}
   913  		b, err := ioutil.ReadAll(r.Body)
   914  		if err != nil {
   915  			t.Fatalf("Could not read request body: %v", err)
   916  		}
   917  		var ps map[string]string
   918  		if err := json.Unmarshal(b, &ps); err != nil {
   919  			t.Errorf("Could not unmarshal request: %v", err)
   920  		} else if len(ps) != 1 {
   921  			t.Errorf("Wrong length patch: %v", ps)
   922  		} else if ps["state"] != "closed" {
   923  			t.Errorf("Wrong state: %s", ps["state"])
   924  		}
   925  	}))
   926  	defer ts.Close()
   927  	c := getClient(ts.URL)
   928  	if err := c.ClosePR("k8s", "kuber", 5); err != nil {
   929  		t.Errorf("Didn't expect error: %v", err)
   930  	}
   931  }
   932  
   933  func TestReopenPR(t *testing.T) {
   934  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   935  		if r.Method != http.MethodPatch {
   936  			t.Errorf("Bad method: %s", r.Method)
   937  		}
   938  		if r.URL.Path != "/repos/k8s/kuber/pulls/5" {
   939  			t.Errorf("Bad request path: %s", r.URL.Path)
   940  		}
   941  		b, err := ioutil.ReadAll(r.Body)
   942  		if err != nil {
   943  			t.Fatalf("Could not read request body: %v", err)
   944  		}
   945  		var ps map[string]string
   946  		if err := json.Unmarshal(b, &ps); err != nil {
   947  			t.Errorf("Could not unmarshal request: %v", err)
   948  		} else if len(ps) != 1 {
   949  			t.Errorf("Wrong length patch: %v", ps)
   950  		} else if ps["state"] != "open" {
   951  			t.Errorf("Wrong state: %s", ps["state"])
   952  		}
   953  	}))
   954  	defer ts.Close()
   955  	c := getClient(ts.URL)
   956  	if err := c.ReopenPR("k8s", "kuber", 5); err != nil {
   957  		t.Errorf("Didn't expect error: %v", err)
   958  	}
   959  }
   960  
   961  func TestFindIssues(t *testing.T) {
   962  	cases := []struct {
   963  		name  string
   964  		sort  bool
   965  		order bool
   966  	}{
   967  		{
   968  			name: "simple query",
   969  		},
   970  		{
   971  			name: "sort no order",
   972  			sort: true,
   973  		},
   974  		{
   975  			name:  "sort and order",
   976  			sort:  true,
   977  			order: true,
   978  		},
   979  	}
   980  
   981  	issueNum := 5
   982  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   983  		if r.Method != http.MethodGet {
   984  			t.Errorf("Bad method: %s", r.Method)
   985  		}
   986  		if r.URL.Path != "/search/issues" {
   987  			t.Errorf("Bad request path: %s", r.URL.Path)
   988  		}
   989  		issueList := IssuesSearchResult{
   990  			Total: 1,
   991  			Issues: []Issue{
   992  				{
   993  					Number: issueNum,
   994  					Title:  r.URL.RawQuery,
   995  				},
   996  			},
   997  		}
   998  		b, err := json.Marshal(&issueList)
   999  		if err != nil {
  1000  			t.Fatalf("Didn't expect error: %v", err)
  1001  		}
  1002  		fmt.Fprint(w, string(b))
  1003  	}))
  1004  	defer ts.Close()
  1005  	c := getClient(ts.URL)
  1006  
  1007  	for _, tc := range cases {
  1008  		var result []Issue
  1009  		var err error
  1010  		sort := ""
  1011  		if tc.sort {
  1012  			sort = "sort-strategy"
  1013  		}
  1014  		if result, err = c.FindIssues("commit_hash", sort, tc.order); err != nil {
  1015  			t.Errorf("%s: didn't expect error: %v", tc.name, err)
  1016  		}
  1017  		if len(result) != 1 {
  1018  			t.Errorf("%s: unexpected number of results: %v", tc.name, len(result))
  1019  		}
  1020  		if result[0].Number != issueNum {
  1021  			t.Errorf("%s: expected issue number %+v, got %+v", tc.name, issueNum, result[0].Number)
  1022  		}
  1023  		if tc.sort && !strings.Contains(result[0].Title, "sort="+sort) {
  1024  			t.Errorf("%s: missing sort=%s from query: %s", tc.name, sort, result[0].Title)
  1025  		}
  1026  		if tc.order && !strings.Contains(result[0].Title, "order=asc") {
  1027  			t.Errorf("%s: missing order=asc from query: %s", tc.name, result[0].Title)
  1028  		}
  1029  	}
  1030  }
  1031  
  1032  func TestGetFile(t *testing.T) {
  1033  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1034  		if r.Method != http.MethodGet {
  1035  			t.Errorf("Bad method: %s", r.Method)
  1036  		}
  1037  		if r.URL.Path != "/repos/k8s/kuber/contents/foo.txt" {
  1038  			t.Errorf("Bad request path: %s", r.URL.Path)
  1039  		}
  1040  		if r.URL.RawQuery != "" {
  1041  			t.Errorf("Bad request query: %s", r.URL.RawQuery)
  1042  		}
  1043  		c := &Content{
  1044  			Content: base64.StdEncoding.EncodeToString([]byte("abcde")),
  1045  		}
  1046  		b, err := json.Marshal(&c)
  1047  		if err != nil {
  1048  			t.Fatalf("Didn't expect error: %v", err)
  1049  		}
  1050  		fmt.Fprint(w, string(b))
  1051  	}))
  1052  	defer ts.Close()
  1053  	c := getClient(ts.URL)
  1054  	if content, err := c.GetFile("k8s", "kuber", "foo.txt", ""); err != nil {
  1055  		t.Errorf("Didn't expect error: %v", err)
  1056  	} else if string(content) != "abcde" {
  1057  		t.Errorf("Wrong content -- expect: abcde, got: %s", string(content))
  1058  	}
  1059  }
  1060  
  1061  func TestGetFileRef(t *testing.T) {
  1062  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1063  		if r.Method != http.MethodGet {
  1064  			t.Errorf("Bad method: %s", r.Method)
  1065  		}
  1066  		if r.URL.Path != "/repos/k8s/kuber/contents/foo/bar.txt" {
  1067  			t.Errorf("Bad request path: %s", r.URL)
  1068  		}
  1069  		if r.URL.RawQuery != "ref=12345" {
  1070  			t.Errorf("Bad request query: %s", r.URL.RawQuery)
  1071  		}
  1072  		c := &Content{
  1073  			Content: base64.StdEncoding.EncodeToString([]byte("abcde")),
  1074  		}
  1075  		b, err := json.Marshal(&c)
  1076  		if err != nil {
  1077  			t.Fatalf("Didn't expect error: %v", err)
  1078  		}
  1079  		fmt.Fprint(w, string(b))
  1080  	}))
  1081  	defer ts.Close()
  1082  	c := getClient(ts.URL)
  1083  	if content, err := c.GetFile("k8s", "kuber", "foo/bar.txt", "12345"); err != nil {
  1084  		t.Errorf("Didn't expect error: %v", err)
  1085  	} else if string(content) != "abcde" {
  1086  		t.Errorf("Wrong content -- expect: abcde, got: %s", string(content))
  1087  	}
  1088  }
  1089  
  1090  // TestGetLabels tests both GetRepoLabels and GetIssueLabels.
  1091  func TestGetLabels(t *testing.T) {
  1092  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1093  		if r.Method != http.MethodGet {
  1094  			t.Errorf("Bad method: %s", r.Method)
  1095  		}
  1096  		var labels []Label
  1097  		switch r.URL.Path {
  1098  		case "/repos/k8s/kuber/issues/5/labels":
  1099  			labels = []Label{{Name: "issue-label"}}
  1100  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/someotherpath>; rel="next"`, r.Host))
  1101  		case "/repos/k8s/kuber/labels":
  1102  			labels = []Label{{Name: "repo-label"}}
  1103  			w.Header().Set("Link", fmt.Sprintf(`<blorp>; rel="first", <https://%s/someotherpath>; rel="next"`, r.Host))
  1104  		case "/someotherpath":
  1105  			labels = []Label{{Name: "label2"}}
  1106  		default:
  1107  			t.Errorf("Bad request path: %s", r.URL.Path)
  1108  			return
  1109  		}
  1110  		b, err := json.Marshal(labels)
  1111  		if err != nil {
  1112  			t.Fatalf("Didn't expect error: %v", err)
  1113  		}
  1114  		fmt.Fprint(w, string(b))
  1115  	}))
  1116  	defer ts.Close()
  1117  	c := getClient(ts.URL)
  1118  	labels, err := c.GetIssueLabels("k8s", "kuber", 5)
  1119  	if err != nil {
  1120  		t.Errorf("Didn't expect error: %v", err)
  1121  	} else if len(labels) != 2 {
  1122  		t.Errorf("Expected two labels, found %d: %v", len(labels), labels)
  1123  	} else if labels[0].Name != "issue-label" || labels[1].Name != "label2" {
  1124  		t.Errorf("Wrong label names: %v", labels)
  1125  	}
  1126  
  1127  	labels, err = c.GetRepoLabels("k8s", "kuber")
  1128  	if err != nil {
  1129  		t.Errorf("Didn't expect error: %v", err)
  1130  	} else if len(labels) != 2 {
  1131  		t.Errorf("Expected two labels, found %d: %v", len(labels), labels)
  1132  	} else if labels[0].Name != "repo-label" || labels[1].Name != "label2" {
  1133  		t.Errorf("Wrong label names: %v", labels)
  1134  	}
  1135  }
  1136  
  1137  func simpleTestServer(t *testing.T, path string, v interface{}) *httptest.Server {
  1138  	return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1139  		if r.URL.Path == path {
  1140  			b, err := json.Marshal(v)
  1141  			if err != nil {
  1142  				t.Fatalf("Didn't expect error: %v", err)
  1143  			}
  1144  			fmt.Fprint(w, string(b))
  1145  		} else {
  1146  			t.Fatalf("Bad request path: %s", r.URL.Path)
  1147  		}
  1148  	}))
  1149  }
  1150  
  1151  func TestListTeams(t *testing.T) {
  1152  	ts := simpleTestServer(t, "/orgs/foo/teams", []Team{{ID: 1}})
  1153  	defer ts.Close()
  1154  	c := getClient(ts.URL)
  1155  	teams, err := c.ListTeams("foo")
  1156  	if err != nil {
  1157  		t.Errorf("Didn't expect error: %v", err)
  1158  	} else if len(teams) != 1 {
  1159  		t.Errorf("Expected one team, found %d: %v", len(teams), teams)
  1160  	} else if teams[0].ID != 1 {
  1161  		t.Errorf("Wrong team names: %v", teams)
  1162  	}
  1163  }
  1164  
  1165  func TestCreateTeam(t *testing.T) {
  1166  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1167  		if r.Method != http.MethodPost {
  1168  			t.Errorf("Bad method: %s", r.Method)
  1169  		}
  1170  		if r.URL.Path != "/orgs/foo/teams" {
  1171  			t.Errorf("Bad request path: %s", r.URL.Path)
  1172  		}
  1173  		b, err := ioutil.ReadAll(r.Body)
  1174  		if err != nil {
  1175  			t.Fatalf("Could not read request body: %v", err)
  1176  		}
  1177  		var team Team
  1178  		switch err := json.Unmarshal(b, &team); {
  1179  		case err != nil:
  1180  			t.Errorf("Could not unmarshal request: %v", err)
  1181  		case team.Name == "":
  1182  			t.Errorf("client should reject empty names")
  1183  		case team.Name != "frobber":
  1184  			t.Errorf("Bad name: %s", team.Name)
  1185  		}
  1186  		team.Name = "hello"
  1187  		team.Description = "world"
  1188  		team.Privacy = "special"
  1189  		b, err = json.Marshal(team)
  1190  		if err != nil {
  1191  			t.Fatalf("Didn't expect error: %v", err)
  1192  		}
  1193  		w.WriteHeader(http.StatusCreated) // 201
  1194  		fmt.Fprint(w, string(b))
  1195  	}))
  1196  	defer ts.Close()
  1197  	c := getClient(ts.URL)
  1198  	if _, err := c.CreateTeam("foo", Team{Name: ""}); err == nil {
  1199  		t.Errorf("client should reject empty name")
  1200  	}
  1201  	switch team, err := c.CreateTeam("foo", Team{Name: "frobber"}); {
  1202  	case err != nil:
  1203  		t.Errorf("unexpected error: %v", err)
  1204  	case team.Name != "hello":
  1205  		t.Errorf("bad name: %s", team.Name)
  1206  	case team.Description != "world":
  1207  		t.Errorf("bad description: %s", team.Description)
  1208  	case team.Privacy != "special":
  1209  		t.Errorf("bad privacy: %s", team.Privacy)
  1210  	}
  1211  }
  1212  
  1213  func TestEditTeam(t *testing.T) {
  1214  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1215  		if r.Method != http.MethodPatch {
  1216  			t.Errorf("Bad method: %s", r.Method)
  1217  		}
  1218  		if r.URL.Path != "/teams/63" {
  1219  			t.Errorf("Bad request path: %s", r.URL.Path)
  1220  		}
  1221  		b, err := ioutil.ReadAll(r.Body)
  1222  		if err != nil {
  1223  			t.Fatalf("Could not read request body: %v", err)
  1224  		}
  1225  		var team Team
  1226  		switch err := json.Unmarshal(b, &team); {
  1227  		case err != nil:
  1228  			t.Errorf("Could not unmarshal request: %v", err)
  1229  		case team.Name == "":
  1230  			t.Errorf("Bad name: %s", team.Name)
  1231  		}
  1232  		team.Name = "hello"
  1233  		team.Description = "world"
  1234  		team.Privacy = "special"
  1235  		b, err = json.Marshal(team)
  1236  		if err != nil {
  1237  			t.Fatalf("Didn't expect error: %v", err)
  1238  		}
  1239  		w.WriteHeader(http.StatusCreated) // 201
  1240  		fmt.Fprint(w, string(b))
  1241  	}))
  1242  	defer ts.Close()
  1243  	c := getClient(ts.URL)
  1244  	if _, err := c.EditTeam(Team{ID: 0, Name: "frobber"}); err == nil {
  1245  		t.Errorf("client should reject id 0")
  1246  	}
  1247  	switch team, err := c.EditTeam(Team{ID: 63, Name: "frobber"}); {
  1248  	case err != nil:
  1249  		t.Errorf("unexpected error: %v", err)
  1250  	case team.Name != "hello":
  1251  		t.Errorf("bad name: %s", team.Name)
  1252  	case team.Description != "world":
  1253  		t.Errorf("bad description: %s", team.Description)
  1254  	case team.Privacy != "special":
  1255  		t.Errorf("bad privacy: %s", team.Privacy)
  1256  	}
  1257  }
  1258  
  1259  func TestListTeamMembers(t *testing.T) {
  1260  	ts := simpleTestServer(t, "/teams/1/members", []TeamMember{{Login: "foo"}})
  1261  	defer ts.Close()
  1262  	c := getClient(ts.URL)
  1263  	teamMembers, err := c.ListTeamMembers(1, RoleAll)
  1264  	if err != nil {
  1265  		t.Errorf("Didn't expect error: %v", err)
  1266  	} else if len(teamMembers) != 1 {
  1267  		t.Errorf("Expected one team member, found %d: %v", len(teamMembers), teamMembers)
  1268  	} else if teamMembers[0].Login != "foo" {
  1269  		t.Errorf("Wrong team names: %v", teamMembers)
  1270  	}
  1271  }
  1272  
  1273  func TestIsCollaborator(t *testing.T) {
  1274  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1275  		if r.Method != http.MethodGet {
  1276  			t.Errorf("Bad method: %s", r.Method)
  1277  		}
  1278  		if r.URL.Path != "/repos/k8s/kuber/collaborators/person" {
  1279  			t.Errorf("Bad request path: %s", r.URL.Path)
  1280  		}
  1281  		http.Error(w, "204 No Content", http.StatusNoContent)
  1282  	}))
  1283  	defer ts.Close()
  1284  	c := getClient(ts.URL)
  1285  	mem, err := c.IsCollaborator("k8s", "kuber", "person")
  1286  	if err != nil {
  1287  		t.Errorf("Didn't expect error: %v", err)
  1288  	} else if !mem {
  1289  		t.Errorf("Should be member.")
  1290  	}
  1291  }
  1292  
  1293  func TestListCollaborators(t *testing.T) {
  1294  	ts := simpleTestServer(t, "/repos/org/repo/collaborators", []User{{Login: "foo"}, {Login: "bar"}})
  1295  	defer ts.Close()
  1296  	c := getClient(ts.URL)
  1297  	users, err := c.ListCollaborators("org", "repo")
  1298  	if err != nil {
  1299  		t.Errorf("Didn't expect error: %v", err)
  1300  	} else if len(users) != 2 {
  1301  		t.Errorf("Expected two users, found %d: %v", len(users), users)
  1302  		return
  1303  	}
  1304  	if users[0].Login != "foo" {
  1305  		t.Errorf("Wrong user login for index 0: %v", users[0])
  1306  	}
  1307  	if users[1].Login != "bar" {
  1308  		t.Errorf("Wrong user login for index 1: %v", users[1])
  1309  	}
  1310  }
  1311  
  1312  func TestListIssueEvents(t *testing.T) {
  1313  	ts := simpleTestServer(
  1314  		t,
  1315  		"/repos/org/repo/issues/1/events",
  1316  		[]ListedIssueEvent{
  1317  			{Event: IssueActionLabeled},
  1318  			{Event: IssueActionClosed},
  1319  		},
  1320  	)
  1321  	defer ts.Close()
  1322  	c := getClient(ts.URL)
  1323  	events, err := c.ListIssueEvents("org", "repo", 1)
  1324  	if err != nil {
  1325  		t.Errorf("Didn't expect error: %v", err)
  1326  	} else if len(events) != 2 {
  1327  		t.Errorf("Expected two events, found %d: %v", len(events), events)
  1328  		return
  1329  	}
  1330  	if events[0].Event != IssueActionLabeled {
  1331  		t.Errorf("Wrong event for index 0: %v", events[0])
  1332  	}
  1333  	if events[1].Event != IssueActionClosed {
  1334  		t.Errorf("Wrong event for index 1: %v", events[1])
  1335  	}
  1336  }
  1337  
  1338  func TestThrottle(t *testing.T) {
  1339  	ts := simpleTestServer(
  1340  		t,
  1341  		"/repos/org/repo/issues/1/events",
  1342  		[]ListedIssueEvent{
  1343  			{Event: IssueActionOpened},
  1344  			{Event: IssueActionClosed},
  1345  		},
  1346  	)
  1347  	c := getClient(ts.URL)
  1348  	c.Throttle(1, 2)
  1349  	if c.client != &c.throttle {
  1350  		t.Errorf("Bad client %v, expecting %v", c.client, &c.throttle)
  1351  	}
  1352  	if len(c.throttle.throttle) != 2 {
  1353  		t.Fatalf("Expected two items in throttle channel, found %d", len(c.throttle.throttle))
  1354  	}
  1355  	if cap(c.throttle.throttle) != 2 {
  1356  		t.Fatalf("Expected throttle channel capacity of two, found %d", cap(c.throttle.throttle))
  1357  	}
  1358  	events, err := c.ListIssueEvents("org", "repo", 1)
  1359  	if err != nil {
  1360  		t.Errorf("Unexpected error: %v", err)
  1361  	}
  1362  	if len(events) != 2 {
  1363  		t.Errorf("Expected two events, found %d: %v", len(events), events)
  1364  	}
  1365  	if len(c.throttle.throttle) != 1 {
  1366  		t.Errorf("Expected one item in throttle channel, found %d", len(c.throttle.throttle))
  1367  	}
  1368  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  1369  	go func() {
  1370  		if _, err := c.ListIssueEvents("org", "repo", 1); err != nil {
  1371  			t.Errorf("Unexpected error: %v", err)
  1372  		}
  1373  		if _, err := c.ListIssueEvents("org", "repo", 1); err != nil {
  1374  			t.Errorf("Unexpected error: %v", err)
  1375  		}
  1376  		cancel()
  1377  	}()
  1378  	slowed := false
  1379  	for ctx.Err() == nil {
  1380  		// Wait for the client to get throttled
  1381  		if atomic.LoadInt32(&c.throttle.slow) == 0 {
  1382  			continue
  1383  		}
  1384  		// Throttled, now add to the channel
  1385  		slowed = true
  1386  		select {
  1387  		case c.throttle.throttle <- time.Now(): // Add items to the channel
  1388  		case <-ctx.Done():
  1389  		}
  1390  	}
  1391  	if !slowed {
  1392  		t.Errorf("Never throttled")
  1393  	}
  1394  	if err := ctx.Err(); err != context.Canceled {
  1395  		t.Errorf("Expected context cancellation did not happen: %v", err)
  1396  	}
  1397  }
  1398  
  1399  func TestGetBranches(t *testing.T) {
  1400  	ts := simpleTestServer(t, "/repos/org/repo/branches", []Branch{
  1401  		{Name: "master", Protected: false},
  1402  		{Name: "release-3.7", Protected: true},
  1403  	})
  1404  	defer ts.Close()
  1405  	c := getClient(ts.URL)
  1406  	branches, err := c.GetBranches("org", "repo", true)
  1407  	if err != nil {
  1408  		t.Errorf("Unexpected error: %v", err)
  1409  	} else if len(branches) != 2 {
  1410  		t.Errorf("Expected two branches, found %d, %v", len(branches), branches)
  1411  		return
  1412  	}
  1413  	switch {
  1414  	case branches[0].Name != "master":
  1415  		t.Errorf("Wrong branch name for index 0: %v", branches[0])
  1416  	case branches[1].Name != "release-3.7":
  1417  		t.Errorf("Wrong branch name for index 1: %v", branches[1])
  1418  	case branches[1].Protected == false:
  1419  		t.Errorf("Wrong branch protection for index 1: %v", branches[1])
  1420  	}
  1421  }
  1422  
  1423  func TestRemoveBranchProtection(t *testing.T) {
  1424  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1425  		if r.Method != http.MethodDelete {
  1426  			t.Errorf("Bad method: %s", r.Method)
  1427  		}
  1428  		if r.URL.Path != "/repos/org/repo/branches/master/protection" {
  1429  			t.Errorf("Bad request path: %s", r.URL.Path)
  1430  		}
  1431  		http.Error(w, "204 No Content", http.StatusNoContent)
  1432  	}))
  1433  	defer ts.Close()
  1434  	c := getClient(ts.URL)
  1435  	if err := c.RemoveBranchProtection("org", "repo", "master"); err != nil {
  1436  		t.Errorf("Unexpected error: %v", err)
  1437  	}
  1438  }
  1439  
  1440  func TestUpdateBranchProtection(t *testing.T) {
  1441  	cases := []struct {
  1442  		name string
  1443  		// TODO(fejta): expand beyond contexts/pushers
  1444  		contexts []string
  1445  		pushers  []string
  1446  		err      bool
  1447  	}{
  1448  		{
  1449  			name:     "both",
  1450  			contexts: []string{"foo-pr-test", "other"},
  1451  			pushers:  []string{"movers", "awesome-team", "shakers"},
  1452  			err:      false,
  1453  		},
  1454  	}
  1455  
  1456  	for _, tc := range cases {
  1457  		ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1458  			if r.Method != http.MethodPut {
  1459  				t.Errorf("Bad method: %s", r.Method)
  1460  			}
  1461  			if r.URL.Path != "/repos/org/repo/branches/master/protection" {
  1462  				t.Errorf("Bad request path: %s", r.URL.Path)
  1463  			}
  1464  			b, err := ioutil.ReadAll(r.Body)
  1465  			if err != nil {
  1466  				t.Fatalf("Could not read request body: %v", err)
  1467  			}
  1468  			var bpr BranchProtectionRequest
  1469  			if err := json.Unmarshal(b, &bpr); err != nil {
  1470  				t.Errorf("Could not unmarshal request: %v", err)
  1471  			}
  1472  			switch {
  1473  			case bpr.Restrictions != nil && bpr.Restrictions.Teams == nil:
  1474  				t.Errorf("Teams unset")
  1475  			case len(bpr.RequiredStatusChecks.Contexts) != len(tc.contexts):
  1476  				t.Errorf("Bad contexts: %v", bpr.RequiredStatusChecks.Contexts)
  1477  			case len(*bpr.Restrictions.Teams) != len(tc.pushers):
  1478  				t.Errorf("Bad teams: %v", *bpr.Restrictions.Teams)
  1479  			default:
  1480  				mc := map[string]bool{}
  1481  				for _, k := range tc.contexts {
  1482  					mc[k] = true
  1483  				}
  1484  				var missing []string
  1485  				for _, k := range bpr.RequiredStatusChecks.Contexts {
  1486  					if mc[k] != true {
  1487  						missing = append(missing, k)
  1488  					}
  1489  				}
  1490  				if n := len(missing); n > 0 {
  1491  					t.Errorf("%s: missing %d required contexts: %v", tc.name, n, missing)
  1492  				}
  1493  				mp := map[string]bool{}
  1494  				for _, k := range tc.pushers {
  1495  					mp[k] = true
  1496  				}
  1497  				missing = nil
  1498  				for _, k := range *bpr.Restrictions.Teams {
  1499  					if mp[k] != true {
  1500  						missing = append(missing, k)
  1501  					}
  1502  				}
  1503  				if n := len(missing); n > 0 {
  1504  					t.Errorf("%s: missing %d pushers: %v", tc.name, n, missing)
  1505  				}
  1506  			}
  1507  			http.Error(w, "200 OK", http.StatusOK)
  1508  		}))
  1509  		defer ts.Close()
  1510  		c := getClient(ts.URL)
  1511  
  1512  		err := c.UpdateBranchProtection("org", "repo", "master", BranchProtectionRequest{
  1513  			RequiredStatusChecks: &RequiredStatusChecks{
  1514  				Contexts: tc.contexts,
  1515  			},
  1516  			Restrictions: &Restrictions{
  1517  				Teams: &tc.pushers,
  1518  			},
  1519  		})
  1520  		if tc.err && err == nil {
  1521  			t.Errorf("%s: expected error failed to occur", tc.name)
  1522  		}
  1523  		if !tc.err && err != nil {
  1524  			t.Errorf("%s: received unexpected error: %v", tc.name, err)
  1525  		}
  1526  	}
  1527  }
  1528  
  1529  func TestClearMilestone(t *testing.T) {
  1530  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1531  		if r.Method != http.MethodPatch {
  1532  			t.Errorf("Bad method: %s", r.Method)
  1533  		}
  1534  		if r.URL.Path != "/repos/k8s/kuber/issues/5" {
  1535  			t.Errorf("Bad request path: %s", r.URL.Path)
  1536  		}
  1537  		b, err := ioutil.ReadAll(r.Body)
  1538  		if err != nil {
  1539  			t.Fatalf("Could not read request body: %v", err)
  1540  		}
  1541  		var issue Issue
  1542  		if err := json.Unmarshal(b, &issue); err != nil {
  1543  			t.Errorf("Could not unmarshal request: %v", err)
  1544  		} else if issue.Milestone.Title != "" {
  1545  			t.Errorf("Milestone title not empty: %v", issue.Milestone.Title)
  1546  		}
  1547  	}))
  1548  	defer ts.Close()
  1549  	c := getClient(ts.URL)
  1550  	if err := c.ClearMilestone("k8s", "kuber", 5); err != nil {
  1551  		t.Errorf("Didn't expect error: %v", err)
  1552  	}
  1553  }
  1554  
  1555  func TestSetMilestone(t *testing.T) {
  1556  	newMilestone := 42
  1557  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1558  		if r.Method != http.MethodPatch {
  1559  			t.Errorf("Bad method: %s", r.Method)
  1560  		}
  1561  		if r.URL.Path != "/repos/k8s/kuber/issues/5" {
  1562  			t.Errorf("Bad request path: %s", r.URL.Path)
  1563  		}
  1564  		b, err := ioutil.ReadAll(r.Body)
  1565  		if err != nil {
  1566  			t.Fatalf("Could not read request body: %v", err)
  1567  		}
  1568  		var issue struct {
  1569  			Milestone *int `json:"milestone,omitempty"`
  1570  		}
  1571  		if err := json.Unmarshal(b, &issue); err != nil {
  1572  			t.Fatalf("Could not unmarshal request: %v", err)
  1573  		}
  1574  		if issue.Milestone == nil {
  1575  			t.Fatal("Milestone was not set.")
  1576  		}
  1577  		if *issue.Milestone != newMilestone {
  1578  			t.Errorf("Expected milestone to be set to %d, but got %d.", newMilestone, *issue.Milestone)
  1579  		}
  1580  	}))
  1581  	defer ts.Close()
  1582  	c := getClient(ts.URL)
  1583  	if err := c.SetMilestone("k8s", "kuber", 5, newMilestone); err != nil {
  1584  		t.Errorf("Didn't expect error: %v", err)
  1585  	}
  1586  }
  1587  
  1588  func TestListMilestones(t *testing.T) {
  1589  	ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1590  		if r.Method != http.MethodGet {
  1591  			t.Errorf("Bad method: %s", r.Method)
  1592  		}
  1593  		if r.URL.Path != "/repos/k8s/kuber/milestones" {
  1594  			t.Errorf("Bad request path: %s", r.URL.Path)
  1595  		}
  1596  	}))
  1597  	defer ts.Close()
  1598  	c := getClient(ts.URL)
  1599  	if err, _ := c.ListMilestones("k8s", "kuber"); err != nil {
  1600  		t.Errorf("Didn't expect error: %v", err)
  1601  	}
  1602  }