Merge pull request #8 from zachgersh/master

Allow Github Resource to Fetch Private Assets
This commit is contained in:
Alex Suraci
2015-08-08 10:28:42 -07:00
112 changed files with 1554 additions and 415 deletions

8
Godeps/Godeps.json generated
View File

@@ -15,10 +15,6 @@
"Comment": "v3.0.0", "Comment": "v3.0.0",
"Rev": "2f3112b6f8f18f9df8743cc75ed51da08094ef6a" "Rev": "2f3112b6f8f18f9df8743cc75ed51da08094ef6a"
}, },
{
"ImportPath": "github.com/google/go-github/github",
"Rev": "7ea4ee6d222607c11ea86e99a6f6723beeae785d"
},
{ {
"ImportPath": "github.com/google/go-querystring/query", "ImportPath": "github.com/google/go-querystring/query",
"Rev": "d8840cbb2baa915f4836edda4750050a2c0b7aea" "Rev": "d8840cbb2baa915f4836edda4750050a2c0b7aea"
@@ -36,6 +32,10 @@
"ImportPath": "github.com/onsi/gomega", "ImportPath": "github.com/onsi/gomega",
"Comment": "v1.0-28-g8adf9e1", "Comment": "v1.0-28-g8adf9e1",
"Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2" "Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2"
},
{
"ImportPath": "github.com/zachgersh/go-github/github",
"Rev": "f47a8b33261f10482dfede3cc839c4fcf34da04f"
} }
] ]
} }

View File

@@ -7,6 +7,12 @@ package github
import "fmt" import "fmt"
// StarredRepository is returned by ListStarred.
type StarredRepository struct {
StarredAt *Timestamp `json:"starred_at,omitempty"`
Repository *Repository `json:"repo,omitempty"`
}
// ListStargazers lists people who have starred the specified repo. // ListStargazers lists people who have starred the specified repo.
// //
// GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers // GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers
@@ -49,7 +55,7 @@ type ActivityListStarredOptions struct {
// will list the starred repositories for the authenticated user. // will list the starred repositories for the authenticated user.
// //
// GitHub API docs: http://developer.github.com/v3/activity/starring/#list-repositories-being-starred // GitHub API docs: http://developer.github.com/v3/activity/starring/#list-repositories-being-starred
func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]Repository, *Response, error) { func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptions) ([]StarredRepository, *Response, error) {
var u string var u string
if user != "" { if user != "" {
u = fmt.Sprintf("users/%v/starred", user) u = fmt.Sprintf("users/%v/starred", user)
@@ -66,7 +72,10 @@ func (s *ActivityService) ListStarred(user string, opt *ActivityListStarredOptio
return nil, nil, err return nil, nil, err
} }
repos := new([]Repository) // TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeStarringPreview)
repos := new([]StarredRepository)
resp, err := s.client.Do(req, repos) resp, err := s.client.Do(req, repos)
if err != nil { if err != nil {
return nil, resp, err return nil, resp, err

View File

@@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
"time"
) )
func TestActivityService_ListStargazers(t *testing.T) { func TestActivityService_ListStargazers(t *testing.T) {
@@ -42,7 +43,8 @@ func TestActivityService_ListStarred_authenticatedUser(t *testing.T) {
mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
fmt.Fprint(w, `[{"id":1}]`) testHeader(t, r, "Accept", mediaTypeStarringPreview)
fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":1}}]`)
}) })
repos, _, err := client.Activity.ListStarred("", nil) repos, _, err := client.Activity.ListStarred("", nil)
@@ -50,7 +52,7 @@ func TestActivityService_ListStarred_authenticatedUser(t *testing.T) {
t.Errorf("Activity.ListStarred returned error: %v", err) t.Errorf("Activity.ListStarred returned error: %v", err)
} }
want := []Repository{{ID: Int(1)}} want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(1)}}}
if !reflect.DeepEqual(repos, want) { if !reflect.DeepEqual(repos, want) {
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
} }
@@ -62,12 +64,13 @@ func TestActivityService_ListStarred_specifiedUser(t *testing.T) {
mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeStarringPreview)
testFormValues(t, r, values{ testFormValues(t, r, values{
"sort": "created", "sort": "created",
"direction": "asc", "direction": "asc",
"page": "2", "page": "2",
}) })
fmt.Fprint(w, `[{"id":2}]`) fmt.Fprint(w, `[{"starred_at":"2002-02-10T15:30:00Z","repo":{"id":2}}]`)
}) })
opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}} opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}}
@@ -76,7 +79,7 @@ func TestActivityService_ListStarred_specifiedUser(t *testing.T) {
t.Errorf("Activity.ListStarred returned error: %v", err) t.Errorf("Activity.ListStarred returned error: %v", err)
} }
want := []Repository{{ID: Int(2)}} want := []StarredRepository{{StarredAt: &Timestamp{time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)}, Repository: &Repository{ID: Int(2)}}}
if !reflect.DeepEqual(repos, want) { if !reflect.DeepEqual(repos, want) {
t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want)
} }

View File

@@ -28,23 +28,24 @@ Authentication
The go-github library does not directly handle authentication. Instead, when The go-github library does not directly handle authentication. Instead, when
creating a new client, pass an http.Client that can handle authentication for creating a new client, pass an http.Client that can handle authentication for
you. The easiest and recommended way to do this is using the goauth2 library, you. The easiest and recommended way to do this is using the golang.org/x/oauth2
but you can always use any other library that provides an http.Client. If you library, but you can always use any other library that provides an http.Client.
have an OAuth2 access token (for example, a personal API token), you can use it If you have an OAuth2 access token (for example, a personal API token), you can
with the goauth2 using: use it with the oauth2 library using:
import "code.google.com/p/goauth2/oauth" import "golang.org/x/oauth2"
// simple OAuth transport if you already have an access token; func main() {
// see goauth2 library for full usage ts := oauth2.StaticTokenSource(
t := &oauth.Transport{ &oauth2.Token{AccessToken: "... your access token ..."},
Token: &oauth.Token{AccessToken: "..."}, )
} tc := oauth2.NewClient(oauth2.NoContext, ts)
client := github.NewClient(t.Client()) client := github.NewClient(tc)
// list all repositories for the authenticated user // list all repositories for the authenticated user
repos, _, err := client.Repositories.List("", nil) repos, _, err := client.Repositories.List("", nil)
}
Note that when using an authenticated Client, all calls made by the client will Note that when using an authenticated Client, all calls made by the client will
include the specified OAuth token. Therefore, authenticated clients should include the specified OAuth token. Therefore, authenticated clients should

View File

@@ -157,6 +157,24 @@ func (s *GistsService) Get(id string) (*Gist, *Response, error) {
return gist, resp, err return gist, resp, err
} }
// GetRevision gets a specific revision of a gist.
//
// GitHub API docs: https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist
func (s *GistsService) GetRevision(id, sha string) (*Gist, *Response, error) {
u := fmt.Sprintf("gists/%v/%v", id, sha)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
gist := new(Gist)
resp, err := s.client.Do(req, gist)
if err != nil {
return nil, resp, err
}
return gist, resp, err
}
// Create a gist for authenticated user. // Create a gist for authenticated user.
// //
// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist // GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist

View File

@@ -146,6 +146,32 @@ func TestGistsService_Get_invalidID(t *testing.T) {
testURLParseError(t, err) testURLParseError(t, err)
} }
func TestGistsService_GetRevision(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/gists/1/s", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id": "1"}`)
})
gist, _, err := client.Gists.GetRevision("1", "s")
if err != nil {
t.Errorf("Gists.Get returned error: %v", err)
}
want := &Gist{ID: String("1")}
if !reflect.DeepEqual(gist, want) {
t.Errorf("Gists.Get returned %+v, want %+v", gist, want)
}
}
func TestGistsService_GetRevision_invalidID(t *testing.T) {
_, _, err := client.Gists.GetRevision("%", "%")
testURLParseError(t, err)
}
func TestGistsService_Create(t *testing.T) { func TestGistsService_Create(t *testing.T) {
setup() setup()
defer teardown() defer teardown()

View File

@@ -37,11 +37,15 @@ const (
// Media Type values to access preview APIs // Media Type values to access preview APIs
// https://developer.github.com/changes/2014-08-05-team-memberships-api/ // https://developer.github.com/changes/2015-03-09-licenses-api/
mediaTypeMembershipPreview = "application/vnd.github.the-wasp-preview+json" mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json"
// https://developer.github.com/changes/2014-01-09-preview-the-new-deployments-api/ // https://developer.github.com/changes/2014-12-09-new-attributes-for-stars-api/
mediaTypeDeploymentPreview = "application/vnd.github.cannonball-preview+json" mediaTypeStarringPreview = "application/vnd.github.v3.star+json"
// https://developer.github.com/changes/2015-06-24-api-enhancements-for-working-with-organization-permissions/
mediaTypeOrgPermissionPreview = "application/vnd.github.ironman-preview+json"
mediaTypeOrgPermissionRepoPreview = "application/vnd.github.ironman-preview.repository+json"
) )
// A Client manages communication with the GitHub API. // A Client manages communication with the GitHub API.
@@ -77,6 +81,7 @@ type Client struct {
Repositories *RepositoriesService Repositories *RepositoriesService
Search *SearchService Search *SearchService
Users *UsersService Users *UsersService
Licenses *LicensesService
} }
// ListOptions specifies the optional parameters to various List methods that // ListOptions specifies the optional parameters to various List methods that
@@ -119,7 +124,7 @@ func addOptions(s string, opt interface{}) (string, error) {
// NewClient returns a new GitHub API client. If a nil httpClient is // NewClient returns a new GitHub API client. If a nil httpClient is
// provided, http.DefaultClient will be used. To use API methods which require // provided, http.DefaultClient will be used. To use API methods which require
// authentication, provide an http.Client that will perform the authentication // authentication, provide an http.Client that will perform the authentication
// for you (such as that provided by the goauth2 library). // for you (such as that provided by the golang.org/x/oauth2 library).
func NewClient(httpClient *http.Client) *Client { func NewClient(httpClient *http.Client) *Client {
if httpClient == nil { if httpClient == nil {
httpClient = http.DefaultClient httpClient = http.DefaultClient
@@ -138,6 +143,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Repositories = &RepositoriesService{client: c} c.Repositories = &RepositoriesService{client: c}
c.Search = &SearchService{client: c} c.Search = &SearchService{client: c}
c.Users = &UsersService{client: c} c.Users = &UsersService{client: c}
c.Licenses = &LicensesService{client: c}
return c return c
} }
@@ -219,7 +225,7 @@ type Response struct {
Rate Rate
} }
// newResponse creats a new Response for the provided http.Response. // newResponse creates a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response { func newResponse(r *http.Response) *Response {
response := &Response{Response: r} response := &Response{Response: r}
response.populatePageValues() response.populatePageValues()
@@ -333,10 +339,24 @@ type ErrorResponse struct {
func (r *ErrorResponse) Error() string { func (r *ErrorResponse) Error() string {
return fmt.Sprintf("%v %v: %d %v %+v", return fmt.Sprintf("%v %v: %d %v %+v",
r.Response.Request.Method, r.Response.Request.URL, r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
r.Response.StatusCode, r.Message, r.Errors) r.Response.StatusCode, r.Message, r.Errors)
} }
// sanitizeURL redacts the client_id and client_secret tokens from the URL which
// may be exposed to the user, specifically in the ErrorResponse error message.
func sanitizeURL(uri *url.URL) *url.URL {
if uri == nil {
return nil
}
params := uri.Query()
if len(params.Get("client_secret")) > 0 {
params.Set("client_secret", "REDACTED")
uri.RawQuery = params.Encode()
}
return uri
}
/* /*
An Error reports more details on an individual error in an ErrorResponse. An Error reports more details on an individual error in an ErrorResponse.
These are the possible validation error codes: These are the possible validation error codes:

View File

@@ -430,6 +430,25 @@ func TestDo_rateLimit_errorResponse(t *testing.T) {
} }
} }
func TestSanitizeURL(t *testing.T) {
tests := []struct {
in, want string
}{
{"/?a=b", "/?a=b"},
{"/?a=b&client_secret=secret", "/?a=b&client_secret=REDACTED"},
{"/?a=b&client_id=id&client_secret=secret", "/?a=b&client_id=id&client_secret=REDACTED"},
}
for _, tt := range tests {
inURL, _ := url.Parse(tt.in)
want, _ := url.Parse(tt.want)
if got := sanitizeURL(inURL); !reflect.DeepEqual(got, want) {
t.Errorf("sanitizeURL(%v) returned %v, want %v", tt.in, got, want)
}
}
}
func TestCheckResponse(t *testing.T) { func TestCheckResponse(t *testing.T) {
res := &http.Response{ res := &http.Response{
Request: &http.Request{}, Request: &http.Request{},

View File

@@ -51,7 +51,7 @@ func (i Issue) String() string {
type IssueRequest struct { type IssueRequest struct {
Title *string `json:"title,omitempty"` Title *string `json:"title,omitempty"`
Body *string `json:"body,omitempty"` Body *string `json:"body,omitempty"`
Labels []string `json:"labels,omitempty"` Labels *[]string `json:"labels,omitempty"`
Assignee *string `json:"assignee,omitempty"` Assignee *string `json:"assignee,omitempty"`
State *string `json:"state,omitempty"` State *string `json:"state,omitempty"`
Milestone *int `json:"milestone,omitempty"` Milestone *int `json:"milestone,omitempty"`
@@ -72,7 +72,7 @@ type IssueListOptions struct {
Labels []string `url:"labels,comma,omitempty"` Labels []string `url:"labels,comma,omitempty"`
// Sort specifies how to sort issues. Possible values are: created, updated, // Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned". // and comments. Default value is "created".
Sort string `url:"sort,omitempty"` Sort string `url:"sort,omitempty"`
// Direction in which to sort issues. Possible values are: asc, desc. // Direction in which to sort issues. Possible values are: asc, desc.
@@ -166,7 +166,7 @@ type IssueListByRepoOptions struct {
Labels []string `url:"labels,omitempty,comma"` Labels []string `url:"labels,omitempty,comma"`
// Sort specifies how to sort issues. Possible values are: created, updated, // Sort specifies how to sort issues. Possible values are: created, updated,
// and comments. Default value is "assigned". // and comments. Default value is "created".
Sort string `url:"sort,omitempty"` Sort string `url:"sort,omitempty"`
// Direction in which to sort issues. Possible values are: asc, desc. // Direction in which to sort issues. Possible values are: asc, desc.

View File

@@ -7,7 +7,7 @@ package github
import "fmt" import "fmt"
// Label represents a GitHib label on an Issue // Label represents a GitHub label on an Issue
type Label struct { type Label struct {
URL *string `json:"url,omitempty"` URL *string `json:"url,omitempty"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`

View File

@@ -176,7 +176,7 @@ func TestIssuesService_Create(t *testing.T) {
Title: String("t"), Title: String("t"),
Body: String("b"), Body: String("b"),
Assignee: String("a"), Assignee: String("a"),
Labels: []string{"l1", "l2"}, Labels: &[]string{"l1", "l2"},
} }
mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) {

View File

@@ -0,0 +1,81 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import "fmt"
// LicensesService handles communication with the license related
// methods of the GitHub API.
//
// GitHub API docs: http://developer.github.com/v3/pulls/
type LicensesService struct {
client *Client
}
// License represents an open source license.
type License struct {
Key *string `json:"key,omitempty"`
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
Featured *bool `json:"featured,omitempty"`
Description *string `json:"description,omitempty"`
Category *string `json:"category,omitempty"`
Implementation *string `json:"implementation,omitempty"`
Required *[]string `json:"required,omitempty"`
Permitted *[]string `json:"permitted,omitempty"`
Forbidden *[]string `json:"forbidden,omitempty"`
Body *string `json:"body,omitempty"`
}
func (l License) String() string {
return Stringify(l)
}
// List popular open source licenses.
//
// GitHub API docs: https://developer.github.com/v3/licenses/#list-all-licenses
func (s *LicensesService) List() ([]License, *Response, error) {
req, err := s.client.NewRequest("GET", "licenses", nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeLicensesPreview)
licenses := new([]License)
resp, err := s.client.Do(req, licenses)
if err != nil {
return nil, resp, err
}
return *licenses, resp, err
}
// Get extended metadata for one license.
//
// GitHub API docs: https://developer.github.com/v3/licenses/#get-an-individual-license
func (s *LicensesService) Get(licenseName string) (*License, *Response, error) {
u := fmt.Sprintf("licenses/%s", licenseName)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeLicensesPreview)
license := new(License)
resp, err := s.client.Do(req, license)
if err != nil {
return nil, resp, err
}
return license, resp, err
}

View File

@@ -0,0 +1,64 @@
// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"fmt"
"net/http"
"reflect"
"testing"
)
func TestLicensesService_List(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/licenses", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `[{"key":"mit","name":"MIT","url":"https://api.github.com/licenses/mit"}]`)
})
licenses, _, err := client.Licenses.List()
if err != nil {
t.Errorf("Licenses.List returned error: %v", err)
}
want := []License{{
Key: String("mit"),
Name: String("MIT"),
URL: String("https://api.github.com/licenses/mit"),
}}
if !reflect.DeepEqual(licenses, want) {
t.Errorf("Licenses.List returned %+v, want %+v", licenses, want)
}
}
func TestLicensesService_Get(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/licenses/mit", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `{"key":"mit","name":"MIT"}`)
})
license, _, err := client.Licenses.Get("mit")
if err != nil {
t.Errorf("Licenses.Get returned error: %v", err)
}
want := &License{Key: String("mit"), Name: String("MIT")}
if !reflect.DeepEqual(license, want) {
t.Errorf("Licenses.Get returned %+v, want %+v", license, want)
}
}
func TestLicensesService_Get_invalidTemplate(t *testing.T) {
_, _, err := client.Licenses.Get("%")
testURLParseError(t, err)
}

View File

@@ -98,6 +98,10 @@ type APIMeta struct {
// username and password, sudo mode, and two-factor authentication are // username and password, sudo mode, and two-factor authentication are
// not supported on these servers.) // not supported on these servers.)
VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"` VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"`
// An array of IP addresses in CIDR format specifying the addresses
// which serve GitHub Pages websites.
Pages []string `json:"pages,omitempty"`
} }
// APIMeta returns information about GitHub.com, the service. Or, if you access // APIMeta returns information about GitHub.com, the service. Or, if you access
@@ -159,3 +163,35 @@ func (c *Client) Zen() (string, *Response, error) {
return buf.String(), resp, nil return buf.String(), resp, nil
} }
// ServiceHook represents a hook that has configuration settings, a list of
// available events, and default events.
type ServiceHook struct {
Name *string `json:"name,omitempty"`
Events []string `json:"events,omitempty"`
SupportedEvents []string `json:"supported_events,omitempty"`
Schema [][]string `json:"schema,omitempty"`
}
func (s *ServiceHook) String() string {
return Stringify(s)
}
// ListServiceHooks lists all of the available service hooks.
//
// GitHub API docs: https://developer.github.com/webhooks/#services
func (c *Client) ListServiceHooks() ([]ServiceHook, *Response, error) {
u := "hooks"
req, err := c.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hooks := new([]ServiceHook)
resp, err := c.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
}

View File

@@ -72,7 +72,7 @@ func TestAPIMeta(t *testing.T) {
mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "verifiable_password_authentication": true}`) fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "pages":["p"], "verifiable_password_authentication": true}`)
}) })
meta, _, err := client.APIMeta() meta, _, err := client.APIMeta()
@@ -83,6 +83,7 @@ func TestAPIMeta(t *testing.T) {
want := &APIMeta{ want := &APIMeta{
Hooks: []string{"h"}, Hooks: []string{"h"},
Git: []string{"g"}, Git: []string{"g"},
Pages: []string{"p"},
VerifiablePasswordAuthentication: Bool(true), VerifiablePasswordAuthentication: Bool(true),
} }
if !reflect.DeepEqual(want, meta) { if !reflect.DeepEqual(want, meta) {
@@ -135,3 +136,35 @@ func TestZen(t *testing.T) {
t.Errorf("Zen returned %+v, want %+v", got, want) t.Errorf("Zen returned %+v, want %+v", got, want)
} }
} }
func TestRepositoriesService_ListServiceHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"name":"n",
"events":["e"],
"supported_events":["s"],
"schema":[
["a", "b"]
]
}]`)
})
hooks, _, err := client.Repositories.ListServiceHooks()
if err != nil {
t.Errorf("Repositories.ListHooks returned error: %v", err)
}
want := []ServiceHook{{
Name: String("n"),
Events: []string{"e"},
SupportedEvents: []string{"s"},
Schema: [][]string{{"a", "b"}},
}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Repositories.ListServiceHooks returned %+v, want %+v", hooks, want)
}
}

View File

@@ -0,0 +1,104 @@
// Copyright 2015 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import "fmt"
// ListHooks lists all Hooks for the specified organization.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#list-hooks
func (s *OrganizationsService) ListHooks(org string, opt *ListOptions) ([]Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks", org)
u, err := addOptions(u, opt)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hooks := new([]Hook)
resp, err := s.client.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
}
// GetHook returns a single specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook
func (s *OrganizationsService) GetHook(org string, id int) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hook := new(Hook)
resp, err := s.client.Do(req, hook)
return hook, resp, err
}
// CreateHook creates a Hook for the specified org.
// Name and Config are required fields.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook
func (s *OrganizationsService) CreateHook(org string, hook *Hook) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks", org)
req, err := s.client.NewRequest("POST", u, hook)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := s.client.Do(req, h)
if err != nil {
return nil, resp, err
}
return h, resp, err
}
// EditHook updates a specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#edit-a-hook
func (s *OrganizationsService) EditHook(org string, id int, hook *Hook) (*Hook, *Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
req, err := s.client.NewRequest("PATCH", u, hook)
if err != nil {
return nil, nil, err
}
h := new(Hook)
resp, err := s.client.Do(req, h)
return h, resp, err
}
// PingHook triggers a 'ping' event to be sent to the Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook
func (s *OrganizationsService) PingHook(org string, id int) (*Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d/pings", org, id)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// DeleteHook deletes a specified Hook.
//
// GitHub API docs: https://developer.github.com/v3/orgs/hooks/#delete-a-hook
func (s *OrganizationsService) DeleteHook(org string, id int) (*Response, error) {
u := fmt.Sprintf("orgs/%v/hooks/%d", org, id)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}

View File

@@ -0,0 +1,134 @@
// Copyright 2015 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package github
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func TestOrganizationsService_ListHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"})
fmt.Fprint(w, `[{"id":1}, {"id":2}]`)
})
opt := &ListOptions{Page: 2}
hooks, _, err := client.Organizations.ListHooks("o", opt)
if err != nil {
t.Errorf("Organizations.ListHooks returned error: %v", err)
}
want := []Hook{{ID: Int(1)}, {ID: Int(2)}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Organizations.ListHooks returned %+v, want %+v", hooks, want)
}
}
func TestOrganizationsService_ListHooks_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.ListHooks("%", nil)
testURLParseError(t, err)
}
func TestOrganizationsService_GetHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":1}`)
})
hook, _, err := client.Organizations.GetHook("o", 1)
if err != nil {
t.Errorf("Organizations.GetHook returned error: %v", err)
}
want := &Hook{ID: Int(1)}
if !reflect.DeepEqual(hook, want) {
t.Errorf("Organizations.GetHook returned %+v, want %+v", hook, want)
}
}
func TestOrganizationsService_GetHook_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.GetHook("%", 1)
testURLParseError(t, err)
}
func TestOrganizationsService_EditHook(t *testing.T) {
setup()
defer teardown()
input := &Hook{Name: String("t")}
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
v := new(Hook)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"id":1}`)
})
hook, _, err := client.Organizations.EditHook("o", 1, input)
if err != nil {
t.Errorf("Organizations.EditHook returned error: %v", err)
}
want := &Hook{ID: Int(1)}
if !reflect.DeepEqual(hook, want) {
t.Errorf("Organizations.EditHook returned %+v, want %+v", hook, want)
}
}
func TestOrganizationsService_EditHook_invalidOrg(t *testing.T) {
_, _, err := client.Organizations.EditHook("%", 1, nil)
testURLParseError(t, err)
}
func TestOrganizationsService_PingHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
})
_, err := client.Organizations.PingHook("o", 1)
if err != nil {
t.Errorf("Organizations.PingHook returned error: %v", err)
}
}
func TestOrganizationsService_DeleteHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/hooks/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
})
_, err := client.Organizations.DeleteHook("o", 1)
if err != nil {
t.Errorf("Organizations.DeleteHook returned error: %v", err)
}
}
func TestOrganizationsService_DeleteHook_invalidOrg(t *testing.T) {
_, err := client.Organizations.DeleteHook("%", 1)
testURLParseError(t, err)
}

View File

@@ -15,7 +15,16 @@ type Membership struct {
// Possible values are: "active", "pending" // Possible values are: "active", "pending"
State *string `json:"state,omitempty"` State *string `json:"state,omitempty"`
// TODO(willnorris): add docs // Role identifies the user's role within the organization or team.
// Possible values for organization membership:
// member - non-owner organization member
// admin - organization owner
//
// Possible values for team membership are:
// member - a normal member of the team
// maintainer - a team maintainer. Able to add/remove other team
// members, promote other team members to team
// maintainer, and edit the teams name and description
Role *string `json:"role,omitempty"` Role *string `json:"role,omitempty"`
// For organization membership, the API URL of the organization. // For organization membership, the API URL of the organization.
@@ -43,6 +52,15 @@ type ListMembersOptions struct {
// 2fa_disabled, all. Default is "all". // 2fa_disabled, all. Default is "all".
Filter string `url:"filter,omitempty"` Filter string `url:"filter,omitempty"`
// Role filters memebers returned by their role in the organization.
// Possible values are:
// all - all members of the organization, regardless of role
// admin - organization owners
// member - non-organization members
//
// Default is "all".
Role string `url:"role,omitempty"`
ListOptions ListOptions
} }
@@ -68,6 +86,10 @@ func (s *OrganizationsService) ListMembers(org string, opt *ListMembersOptions)
return nil, nil, err return nil, nil, err
} }
if opt != nil && opt.Role != "" {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
members := new([]User) members := new([]User)
resp, err := s.client.Do(req, members) resp, err := s.client.Do(req, members)
if err != nil { if err != nil {
@@ -120,7 +142,8 @@ func (s *OrganizationsService) RemoveMember(org, user string) (*Response, error)
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }
// PublicizeMembership publicizes a user's membership in an organization. // PublicizeMembership publicizes a user's membership in an organization. (A
// user cannot publicize the membership for another user.)
// //
// GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership // GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership
func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) { func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) {
@@ -171,9 +194,6 @@ func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
var memberships []Membership var memberships []Membership
resp, err := s.client.Do(req, &memberships) resp, err := s.client.Do(req, &memberships)
if err != nil { if err != nil {
@@ -183,20 +203,25 @@ func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions
return memberships, resp, err return memberships, resp, err
} }
// GetOrgMembership gets the membership for the authenticated user for the // GetOrgMembership gets the membership for a user in a specified organization.
// specified organization. // Passing an empty string for user will get the membership for the
// authenticated user.
// //
// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-organization-membership
// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership // GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership
func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Response, error) { func (s *OrganizationsService) GetOrgMembership(user, org string) (*Membership, *Response, error) {
u := fmt.Sprintf("user/memberships/orgs/%v", org) var u string
if user != "" {
u = fmt.Sprintf("orgs/%v/memberships/%v", org, user)
} else {
u = fmt.Sprintf("user/memberships/orgs/%v", org)
}
req, err := s.client.NewRequest("GET", u, nil) req, err := s.client.NewRequest("GET", u, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
membership := new(Membership) membership := new(Membership)
resp, err := s.client.Do(req, membership) resp, err := s.client.Do(req, membership)
if err != nil { if err != nil {
@@ -206,20 +231,25 @@ func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Respo
return membership, resp, err return membership, resp, err
} }
// EditOrgMembership edits the membership for the authenticated user for the // EditOrgMembership edits the membership for user in specified organization.
// specified organization. // Passing an empty string for user will edit the membership for the
// authenticated user.
// //
// GitHub API docs: https://developer.github.com/v3/orgs/members/#add-or-update-organization-membership
// GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership // GitHub API docs: https://developer.github.com/v3/orgs/members/#edit-your-organization-membership
func (s *OrganizationsService) EditOrgMembership(org string, membership *Membership) (*Membership, *Response, error) { func (s *OrganizationsService) EditOrgMembership(user, org string, membership *Membership) (*Membership, *Response, error) {
u := fmt.Sprintf("user/memberships/orgs/%v", org) var u string
if user != "" {
u = fmt.Sprintf("orgs/%v/memberships/%v", org, user)
} else {
u = fmt.Sprintf("user/memberships/orgs/%v", org)
}
req, err := s.client.NewRequest("PATCH", u, membership) req, err := s.client.NewRequest("PATCH", u, membership)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
m := new(Membership) m := new(Membership)
resp, err := s.client.Do(req, m) resp, err := s.client.Do(req, m)
if err != nil { if err != nil {
@@ -228,3 +258,17 @@ func (s *OrganizationsService) EditOrgMembership(org string, membership *Members
return m, resp, err return m, resp, err
} }
// RemoveOrgMembership removes user from the specified organization. If the
// user has been invited to the organization, this will cancel their invitation.
//
// GitHub API docs: https://developer.github.com/v3/orgs/members/#remove-organization-membership
func (s *OrganizationsService) RemoveOrgMembership(user, org string) (*Response, error) {
u := fmt.Sprintf("orgs/%v/memberships/%v", org, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}

View File

@@ -19,8 +19,10 @@ func TestOrganizationsService_ListMembers(t *testing.T) {
mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{ testFormValues(t, r, values{
"filter": "2fa_disabled", "filter": "2fa_disabled",
"role": "admin",
"page": "2", "page": "2",
}) })
fmt.Fprint(w, `[{"id":1}]`) fmt.Fprint(w, `[{"id":1}]`)
@@ -29,6 +31,7 @@ func TestOrganizationsService_ListMembers(t *testing.T) {
opt := &ListMembersOptions{ opt := &ListMembersOptions{
PublicOnly: false, PublicOnly: false,
Filter: "2fa_disabled", Filter: "2fa_disabled",
Role: "admin",
ListOptions: ListOptions{Page: 2}, ListOptions: ListOptions{Page: 2},
} }
members, _, err := client.Organizations.ListMembers("o", opt) members, _, err := client.Organizations.ListMembers("o", opt)
@@ -217,7 +220,6 @@ func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
testFormValues(t, r, values{ testFormValues(t, r, values{
"state": "active", "state": "active",
"page": "2", "page": "2",
@@ -240,17 +242,16 @@ func TestOrganizationsService_ListOrgMemberships(t *testing.T) {
} }
} }
func TestOrganizationsService_GetOrgMembership(t *testing.T) { func TestOrganizationsService_GetOrgMembership_AuthenticatedUser(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
fmt.Fprint(w, `{"url":"u"}`) fmt.Fprint(w, `{"url":"u"}`)
}) })
membership, _, err := client.Organizations.GetOrgMembership("o") membership, _, err := client.Organizations.GetOrgMembership("", "o")
if err != nil { if err != nil {
t.Errorf("Organizations.GetOrgMembership returned error: %v", err) t.Errorf("Organizations.GetOrgMembership returned error: %v", err)
} }
@@ -261,7 +262,27 @@ func TestOrganizationsService_GetOrgMembership(t *testing.T) {
} }
} }
func TestOrganizationsService_EditOrgMembership(t *testing.T) { func TestOrganizationsService_GetOrgMembership_SpecifiedUser(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.GetOrgMembership("u", "o")
if err != nil {
t.Errorf("Organizations.GetOrgMembership returned error: %v", err)
}
want := &Membership{URL: String("u")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.GetOrgMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_EditOrgMembership_AuthenticatedUser(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
@@ -272,7 +293,6 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
json.NewDecoder(r.Body).Decode(v) json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH") testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
if !reflect.DeepEqual(v, input) { if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input) t.Errorf("Request body = %+v, want %+v", v, input)
} }
@@ -280,7 +300,7 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
fmt.Fprint(w, `{"url":"u"}`) fmt.Fprint(w, `{"url":"u"}`)
}) })
membership, _, err := client.Organizations.EditOrgMembership("o", input) membership, _, err := client.Organizations.EditOrgMembership("", "o", input)
if err != nil { if err != nil {
t.Errorf("Organizations.EditOrgMembership returned error: %v", err) t.Errorf("Organizations.EditOrgMembership returned error: %v", err)
} }
@@ -290,3 +310,47 @@ func TestOrganizationsService_EditOrgMembership(t *testing.T) {
t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want) t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want)
} }
} }
func TestOrganizationsService_EditOrgMembership_SpecifiedUser(t *testing.T) {
setup()
defer teardown()
input := &Membership{State: String("active")}
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
v := new(Membership)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH")
if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input)
}
fmt.Fprint(w, `{"url":"u"}`)
})
membership, _, err := client.Organizations.EditOrgMembership("u", "o", input)
if err != nil {
t.Errorf("Organizations.EditOrgMembership returned error: %v", err)
}
want := &Membership{URL: String("u")}
if !reflect.DeepEqual(membership, want) {
t.Errorf("Organizations.EditOrgMembership returned %+v, want %+v", membership, want)
}
}
func TestOrganizationsService_RemoveOrgMembership(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/orgs/o/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.RemoveOrgMembership("u", "o")
if err != nil {
t.Errorf("Organizations.RemoveOrgMembership returned error: %v", err)
}
}

View File

@@ -14,7 +14,21 @@ type Team struct {
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"` URL *string `json:"url,omitempty"`
Slug *string `json:"slug,omitempty"` Slug *string `json:"slug,omitempty"`
// Permission is deprecated when creating or editing a team in an org
// using the new GitHub permission model. It no longer identifies the
// permission a team has on its repos, but only specifies the default
// permission a repo is initially added with. Avoid confusion by
// specifying a permission value when calling AddTeamRepo.
Permission *string `json:"permission,omitempty"` Permission *string `json:"permission,omitempty"`
// Privacy identifies the level of privacy this team should have.
// Possible values are:
// secret - only visible to organization owners and members of this team
// closed - visible to all members of this organization
// Default is "secret".
Privacy *string `json:"privacy,omitempty"`
MembersCount *int `json:"members_count,omitempty"` MembersCount *int `json:"members_count,omitempty"`
ReposCount *int `json:"repos_count,omitempty"` ReposCount *int `json:"repos_count,omitempty"`
Organization *Organization `json:"organization,omitempty"` Organization *Organization `json:"organization,omitempty"`
@@ -77,6 +91,10 @@ func (s *OrganizationsService) CreateTeam(org string, team *Team) (*Team, *Respo
return nil, nil, err return nil, nil, err
} }
if team.Privacy != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Team) t := new(Team)
resp, err := s.client.Do(req, t) resp, err := s.client.Do(req, t)
if err != nil { if err != nil {
@@ -96,6 +114,10 @@ func (s *OrganizationsService) EditTeam(id int, team *Team) (*Team, *Response, e
return nil, nil, err return nil, nil, err
} }
if team.Privacy != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Team) t := new(Team)
resp, err := s.client.Do(req, t) resp, err := s.client.Do(req, t)
if err != nil { if err != nil {
@@ -118,11 +140,21 @@ func (s *OrganizationsService) DeleteTeam(team int) (*Response, error) {
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }
// OrganizationListTeamMembersOptions specifies the optional parameters to the
// OrganizationsService.ListTeamMembers method.
type OrganizationListTeamMembersOptions struct {
// Role filters members returned by their role in the team. Possible
// values are "all", "member", "maintainer". Default is "all".
Role string `url:"role,omitempty"`
ListOptions
}
// ListTeamMembers lists all of the users who are members of the specified // ListTeamMembers lists all of the users who are members of the specified
// team. // team.
// //
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-members // GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-members
func (s *OrganizationsService) ListTeamMembers(team int, opt *ListOptions) ([]User, *Response, error) { func (s *OrganizationsService) ListTeamMembers(team int, opt *OrganizationListTeamMembersOptions) ([]User, *Response, error) {
u := fmt.Sprintf("teams/%v/members", team) u := fmt.Sprintf("teams/%v/members", team)
u, err := addOptions(u, opt) u, err := addOptions(u, opt)
if err != nil { if err != nil {
@@ -134,6 +166,10 @@ func (s *OrganizationsService) ListTeamMembers(team int, opt *ListOptions) ([]Us
return nil, nil, err return nil, nil, err
} }
if opt != nil && opt.Role != "" {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
members := new([]User) members := new([]User)
resp, err := s.client.Do(req, members) resp, err := s.client.Do(req, members)
if err != nil { if err != nil {
@@ -158,32 +194,6 @@ func (s *OrganizationsService) IsTeamMember(team int, user string) (bool, *Respo
return member, resp, err return member, resp, err
} }
// AddTeamMember adds a user to a team.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-member
func (s *OrganizationsService) AddTeamMember(team int, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/members/%v", team, user)
req, err := s.client.NewRequest("PUT", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// RemoveTeamMember removes a user from a team.
//
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#remove-team-member
func (s *OrganizationsService) RemoveTeamMember(team int, user string) (*Response, error) {
u := fmt.Sprintf("teams/%v/members/%v", team, user)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// ListTeamRepos lists the repositories that the specified team has access to. // ListTeamRepos lists the repositories that the specified team has access to.
// //
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-repos // GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-repos
@@ -208,19 +218,40 @@ func (s *OrganizationsService) ListTeamRepos(team int, opt *ListOptions) ([]Repo
return *repos, resp, err return *repos, resp, err
} }
// IsTeamRepo checks if a team manages the specified repository. // IsTeamRepo checks if a team manages the specified repository. If the
// repository is managed by team, a Repository is returned which includes the
// permissions team has for that repo.
// //
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-repo // GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-repo
func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (bool, *Response, error) { func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (*Repository, *Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("GET", u, nil) req, err := s.client.NewRequest("GET", u, nil)
if err != nil { if err != nil {
return false, nil, err return nil, nil, err
} }
resp, err := s.client.Do(req, nil) req.Header.Set("Accept", mediaTypeOrgPermissionRepoPreview)
manages, err := parseBoolResponse(err)
return manages, resp, err repository := new(Repository)
resp, err := s.client.Do(req, repository)
if err != nil {
return nil, resp, err
}
return repository, resp, err
}
// OrganizationAddTeamRepoOptions specifies the optional parameters to the
// OrganizationsService.AddTeamRepo method.
type OrganizationAddTeamRepoOptions struct {
// Permission specifies the permission to grant the team on this repository.
// Possible values are:
// pull - team members can pull, but not push to or administer this repository
// push - team members can pull and push, but not administer this repository
// admin - team members can pull, push and administer this repository
//
// If not specified, the team's permission attribute will be used.
Permission string `json:"permission,omitempty"`
} }
// AddTeamRepo adds a repository to be managed by the specified team. The // AddTeamRepo adds a repository to be managed by the specified team. The
@@ -228,13 +259,17 @@ func (s *OrganizationsService) IsTeamRepo(team int, owner string, repo string) (
// belongs, or a direct fork of a repository owned by the organization. // belongs, or a direct fork of a repository owned by the organization.
// //
// GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-repo // GitHub API docs: http://developer.github.com/v3/orgs/teams/#add-team-repo
func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string) (*Response, error) { func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string, opt *OrganizationAddTeamRepoOptions) (*Response, error) {
u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo)
req, err := s.client.NewRequest("PUT", u, nil) req, err := s.client.NewRequest("PUT", u, opt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opt != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }
@@ -286,9 +321,6 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
t := new(Membership) t := new(Membership)
resp, err := s.client.Do(req, t) resp, err := s.client.Do(req, t)
if err != nil { if err != nil {
@@ -298,6 +330,20 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
return t, resp, err return t, resp, err
} }
// OrganizationAddTeamMembershipOptions does stuff specifies the optional
// parameters to the OrganizationsService.AddTeamMembership method.
type OrganizationAddTeamMembershipOptions struct {
// Role specifies the role the user should have in the team. Possible
// values are:
// member - a normal member of the team
// maintainer - a team maintainer. Able to add/remove other team
// members, promote other team members to team
// maintainer, and edit the teams name and description
//
// Default value is "member".
Role string `json:"role,omitempty"`
}
// AddTeamMembership adds or invites a user to a team. // AddTeamMembership adds or invites a user to a team.
// //
// In order to add a membership between a user and a team, the authenticated // In order to add a membership between a user and a team, the authenticated
@@ -316,15 +362,16 @@ func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Member
// added as a member of the team. // added as a member of the team.
// //
// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership // GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership
func (s *OrganizationsService) AddTeamMembership(team int, user string) (*Membership, *Response, error) { func (s *OrganizationsService) AddTeamMembership(team int, user string, opt *OrganizationAddTeamMembershipOptions) (*Membership, *Response, error) {
u := fmt.Sprintf("teams/%v/memberships/%v", team, user) u := fmt.Sprintf("teams/%v/memberships/%v", team, user)
req, err := s.client.NewRequest("PUT", u, nil) req, err := s.client.NewRequest("PUT", u, opt)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches if opt != nil {
req.Header.Set("Accept", mediaTypeMembershipPreview) req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
t := new(Membership) t := new(Membership)
resp, err := s.client.Do(req, t) resp, err := s.client.Do(req, t)
@@ -345,8 +392,5 @@ func (s *OrganizationsService) RemoveTeamMembership(team int, user string) (*Res
return nil, err return nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeMembershipPreview)
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }

View File

@@ -64,13 +64,14 @@ func TestOrganizationsService_CreateTeam(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
input := &Team{Name: String("n")} input := &Team{Name: String("n"), Privacy: String("closed")}
mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) {
v := new(Team) v := new(Team)
json.NewDecoder(r.Body).Decode(v) json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "POST") testMethod(t, r, "POST")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, input) { if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input) t.Errorf("Request body = %+v, want %+v", v, input)
} }
@@ -98,13 +99,14 @@ func TestOrganizationsService_EditTeam(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
input := &Team{Name: String("n")} input := &Team{Name: String("n"), Privacy: String("closed")}
mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) {
v := new(Team) v := new(Team)
json.NewDecoder(r.Body).Decode(v) json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PATCH") testMethod(t, r, "PATCH")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, input) { if !reflect.DeepEqual(v, input) {
t.Errorf("Request body = %+v, want %+v", v, input) t.Errorf("Request body = %+v, want %+v", v, input)
} }
@@ -143,11 +145,12 @@ func TestOrganizationsService_ListTeamMembers(t *testing.T) {
mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2"}) testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{"role": "member", "page": "2"})
fmt.Fprint(w, `[{"id":1}]`) fmt.Fprint(w, `[{"id":1}]`)
}) })
opt := &ListOptions{Page: 2} opt := &OrganizationListTeamMembersOptions{Role: "member", ListOptions: ListOptions{Page: 2}}
members, _, err := client.Organizations.ListTeamMembers(1, opt) members, _, err := client.Organizations.ListTeamMembers(1, opt)
if err != nil { if err != nil {
t.Errorf("Organizations.ListTeamMembers returned error: %v", err) t.Errorf("Organizations.ListTeamMembers returned error: %v", err)
@@ -220,46 +223,6 @@ func TestOrganizationsService_IsTeamMember_invalidUser(t *testing.T) {
testURLParseError(t, err) testURLParseError(t, err)
} }
func TestOrganizationsService_AddTeamMember(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PUT")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.AddTeamMember(1, "u")
if err != nil {
t.Errorf("Organizations.AddTeamMember returned error: %v", err)
}
}
func TestOrganizationsService_AddTeamMember_invalidUser(t *testing.T) {
_, err := client.Organizations.AddTeamMember(1, "%")
testURLParseError(t, err)
}
func TestOrganizationsService_RemoveTeamMember(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})
_, err := client.Organizations.RemoveTeamMember(1, "u")
if err != nil {
t.Errorf("Organizations.RemoveTeamMember returned error: %v", err)
}
}
func TestOrganizationsService_RemoveTeamMember_invalidUser(t *testing.T) {
_, err := client.Organizations.RemoveTeamMember(1, "%")
testURLParseError(t, err)
}
func TestOrganizationsService_PublicizeMembership(t *testing.T) { func TestOrganizationsService_PublicizeMembership(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
@@ -328,15 +291,18 @@ func TestOrganizationsService_IsTeamRepo_true(t *testing.T) {
mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
w.WriteHeader(http.StatusNoContent) testHeader(t, r, "Accept", mediaTypeOrgPermissionRepoPreview)
fmt.Fprint(w, `{"id":1}`)
}) })
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") repo, _, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err != nil { if err != nil {
t.Errorf("Organizations.IsTeamRepo returned error: %v", err) t.Errorf("Organizations.IsTeamRepo returned error: %v", err)
} }
if want := true; managed != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) want := &Repository{ID: Int(1)}
if !reflect.DeepEqual(repo, want) {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", repo, want)
} }
} }
@@ -349,12 +315,15 @@ func TestOrganizationsService_IsTeamRepo_false(t *testing.T) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
}) })
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err != nil { if err == nil {
t.Errorf("Organizations.IsTeamRepo returned error: %v", err) t.Errorf("Expected HTTP 404 response")
} }
if want := false; managed != want { if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want)
}
if repo != nil {
t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo)
} }
} }
@@ -367,12 +336,15 @@ func TestOrganizationsService_IsTeamRepo_error(t *testing.T) {
http.Error(w, "BadRequest", http.StatusBadRequest) http.Error(w, "BadRequest", http.StatusBadRequest)
}) })
managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") repo, resp, err := client.Organizations.IsTeamRepo(1, "o", "r")
if err == nil { if err == nil {
t.Errorf("Expected HTTP 400 response") t.Errorf("Expected HTTP 400 response")
} }
if want := false; managed != want { if got, want := resp.Response.StatusCode, http.StatusBadRequest; got != want {
t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) t.Errorf("Organizations.IsTeamRepo returned status %d, want %d", got, want)
}
if repo != nil {
t.Errorf("Organizations.IsTeamRepo returned %+v, want nil", repo)
} }
} }
@@ -385,12 +357,22 @@ func TestOrganizationsService_AddTeamRepo(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
opt := &OrganizationAddTeamRepoOptions{Permission: "admin"}
mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
v := new(OrganizationAddTeamRepoOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT") testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
}) })
_, err := client.Organizations.AddTeamRepo(1, "o", "r") _, err := client.Organizations.AddTeamRepo(1, "o", "r", opt)
if err != nil { if err != nil {
t.Errorf("Organizations.AddTeamRepo returned error: %v", err) t.Errorf("Organizations.AddTeamRepo returned error: %v", err)
} }
@@ -405,14 +387,14 @@ func TestOrganizationsService_AddTeamRepo_noAccess(t *testing.T) {
w.WriteHeader(422) w.WriteHeader(422)
}) })
_, err := client.Organizations.AddTeamRepo(1, "o", "r") _, err := client.Organizations.AddTeamRepo(1, "o", "r", nil)
if err == nil { if err == nil {
t.Errorf("Expcted error to be returned") t.Errorf("Expcted error to be returned")
} }
} }
func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) { func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) {
_, err := client.Organizations.AddTeamRepo(1, "%", "r") _, err := client.Organizations.AddTeamRepo(1, "%", "r", nil)
testURLParseError(t, err) testURLParseError(t, err)
} }
@@ -442,7 +424,6 @@ func TestOrganizationsService_GetTeamMembership(t *testing.T) {
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
fmt.Fprint(w, `{"url":"u", "state":"active"}`) fmt.Fprint(w, `{"url":"u", "state":"active"}`)
}) })
@@ -461,13 +442,22 @@ func TestOrganizationsService_AddTeamMembership(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
opt := &OrganizationAddTeamMembershipOptions{Role: "maintainer"}
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
v := new(OrganizationAddTeamMembershipOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT") testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeMembershipPreview) testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
fmt.Fprint(w, `{"url":"u", "state":"pending"}`) fmt.Fprint(w, `{"url":"u", "state":"pending"}`)
}) })
membership, _, err := client.Organizations.AddTeamMembership(1, "u") membership, _, err := client.Organizations.AddTeamMembership(1, "u", opt)
if err != nil { if err != nil {
t.Errorf("Organizations.AddTeamMembership returned error: %v", err) t.Errorf("Organizations.AddTeamMembership returned error: %v", err)
} }
@@ -484,7 +474,6 @@ func TestOrganizationsService_RemoveTeamMembership(t *testing.T) {
mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE") testMethod(t, r, "DELETE")
testHeader(t, r, "Accept", mediaTypeMembershipPreview)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
}) })

View File

@@ -41,6 +41,8 @@ type PullRequest struct {
HTMLURL *string `json:"html_url,omitempty"` HTMLURL *string `json:"html_url,omitempty"`
IssueURL *string `json:"issue_url,omitempty"` IssueURL *string `json:"issue_url,omitempty"`
StatusesURL *string `json:"statuses_url,omitempty"` StatusesURL *string `json:"statuses_url,omitempty"`
DiffURL *string `json:"diff_url,omitempty"`
PatchURL *string `json:"patch_url,omitempty"`
Head *PullRequestBranch `json:"head,omitempty"` Head *PullRequestBranch `json:"head,omitempty"`
Base *PullRequestBranch `json:"base,omitempty"` Base *PullRequestBranch `json:"base,omitempty"`
@@ -73,6 +75,15 @@ type PullRequestListOptions struct {
// Base filters pull requests by base branch name. // Base filters pull requests by base branch name.
Base string `url:"base,omitempty"` Base string `url:"base,omitempty"`
// Sort specifies how to sort pull requests. Possible values are: created,
// updated, popularity, long-running. Default is "created".
Sort string `url:"sort,omitempty"`
// Direction in which to sort pull requests. Possible values are: asc, desc.
// If Sort is "created" or not specified, Default is "desc", otherwise Default
// is "asc"
Direction string `url:"direction,omitempty"`
ListOptions ListOptions
} }

View File

@@ -93,7 +93,7 @@ func (s *PullRequestsService) GetComment(owner string, repo string, number int)
// CreateComment creates a new comment on the specified pull request. // CreateComment creates a new comment on the specified pull request.
// //
// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment // GitHub API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment
func (s *PullRequestsService) CreateComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) { func (s *PullRequestsService) CreateComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) u := fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number)
req, err := s.client.NewRequest("POST", u, comment) req, err := s.client.NewRequest("POST", u, comment)

View File

@@ -23,12 +23,14 @@ func TestPullRequestsService_List(t *testing.T) {
"state": "closed", "state": "closed",
"head": "h", "head": "h",
"base": "b", "base": "b",
"sort": "created",
"direction": "desc",
"page": "2", "page": "2",
}) })
fmt.Fprint(w, `[{"number":1}]`) fmt.Fprint(w, `[{"number":1}]`)
}) })
opt := &PullRequestListOptions{"closed", "h", "b", ListOptions{Page: 2}} opt := &PullRequestListOptions{"closed", "h", "b", "created", "desc", ListOptions{Page: 2}}
pulls, _, err := client.PullRequests.List("o", "r", opt) pulls, _, err := client.PullRequests.List("o", "r", opt)
if err != nil { if err != nil {
@@ -98,6 +100,29 @@ func TestPullRequestsService_Get_headAndBase(t *testing.T) {
} }
} }
func TestPullRequestService_Get_DiffURLAndPatchURL(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"number":1,
"diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
"patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch"}`)
})
pull, _, err := client.PullRequests.Get("o", "r", 1)
if err != nil {
t.Errorf("PullRequests.Get returned error: %v", err)
}
want := &PullRequest{Number: Int(1), DiffURL: String("https://github.com/octocat/Hello-World/pull/1347.diff"), PatchURL: String("https://github.com/octocat/Hello-World/pull/1347.patch")}
if !reflect.DeepEqual(pull, want) {
t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want)
}
}
func TestPullRequestsService_Get_invalidOwner(t *testing.T) { func TestPullRequestsService_Get_invalidOwner(t *testing.T) {
_, _, err := client.PullRequests.Get("%", "r", 1) _, _, err := client.PullRequests.Get("%", "r", 1)
testURLParseError(t, err) testURLParseError(t, err)

View File

@@ -49,6 +49,9 @@ type Repository struct {
Organization *Organization `json:"organization,omitempty"` Organization *Organization `json:"organization,omitempty"`
Permissions *map[string]bool `json:"permissions,omitempty"` Permissions *map[string]bool `json:"permissions,omitempty"`
// Only provided when using RepositoriesService.Get while in preview
License *License `json:"license,omitempty"`
// Additional mutable fields when creating and editing a repository // Additional mutable fields when creating and editing a repository
Private *bool `json:"private"` Private *bool `json:"private"`
HasIssues *bool `json:"has_issues"` HasIssues *bool `json:"has_issues"`
@@ -255,6 +258,10 @@ func (s *RepositoriesService) Get(owner, repo string) (*Repository, *Response, e
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when the license support fully launches
// https://developer.github.com/v3/licenses/#get-a-repositorys-license
req.Header.Set("Accept", mediaTypeLicensesPreview)
repository := new(Repository) repository := new(Repository)
resp, err := s.client.Do(req, repository) resp, err := s.client.Do(req, repository)
if err != nil { if err != nil {

View File

@@ -22,6 +22,8 @@ func (s *RepositoriesService) ListCollaborators(owner, repo string, opt *ListOpt
return nil, nil, err return nil, nil, err
} }
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
users := new([]User) users := new([]User)
resp, err := s.client.Do(req, users) resp, err := s.client.Do(req, users)
if err != nil { if err != nil {
@@ -49,15 +51,33 @@ func (s *RepositoriesService) IsCollaborator(owner, repo, user string) (bool, *R
return isCollab, resp, err return isCollab, resp, err
} }
// RepositoryAddCollaboratorOptions specifies the optional parameters to the
// RepositoriesService.AddCollaborator method.
type RepositoryAddCollaboratorOptions struct {
// Permission specifies the permission to grant the user on this repository.
// Possible values are:
// pull - team members can pull, but not push to or administer this repository
// push - team members can pull and push, but not administer this repository
// admin - team members can pull, push and administer this repository
//
// Default value is "pull". This option is only valid for organization-owned repositories.
Permission string `json:"permission,omitempty"`
}
// AddCollaborator adds the specified Github user as collaborator to the given repo. // AddCollaborator adds the specified Github user as collaborator to the given repo.
// //
// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator // GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator
func (s *RepositoriesService) AddCollaborator(owner, repo, user string) (*Response, error) { func (s *RepositoriesService) AddCollaborator(owner, repo, user string, opt *RepositoryAddCollaboratorOptions) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user)
req, err := s.client.NewRequest("PUT", u, nil) req, err := s.client.NewRequest("PUT", u, opt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opt != nil {
req.Header.Set("Accept", mediaTypeOrgPermissionPreview)
}
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }

View File

@@ -6,6 +6,7 @@
package github package github
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
@@ -18,6 +19,7 @@ func TestRepositoriesService_ListCollaborators(t *testing.T) {
mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
testFormValues(t, r, values{"page": "2"}) testFormValues(t, r, values{"page": "2"})
fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) fmt.Fprintf(w, `[{"id":1}, {"id":2}]`)
}) })
@@ -86,19 +88,29 @@ func TestRepositoriesService_AddCollaborator(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
opt := &RepositoryAddCollaboratorOptions{Permission: "admin"}
mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) {
v := new(RepositoryAddCollaboratorOptions)
json.NewDecoder(r.Body).Decode(v)
testMethod(t, r, "PUT") testMethod(t, r, "PUT")
testHeader(t, r, "Accept", mediaTypeOrgPermissionPreview)
if !reflect.DeepEqual(v, opt) {
t.Errorf("Request body = %+v, want %+v", v, opt)
}
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
}) })
_, err := client.Repositories.AddCollaborator("o", "r", "u") _, err := client.Repositories.AddCollaborator("o", "r", "u", opt)
if err != nil { if err != nil {
t.Errorf("Repositories.AddCollaborator returned error: %v", err) t.Errorf("Repositories.AddCollaborator returned error: %v", err)
} }
} }
func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) { func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) {
_, err := client.Repositories.AddCollaborator("%", "%", "%") _, err := client.Repositories.AddCollaborator("%", "%", "%", nil)
testURLParseError(t, err) testURLParseError(t, err)
} }

View File

@@ -62,6 +62,7 @@ func (c CommitFile) String() string {
// See CompareCommits() for details. // See CompareCommits() for details.
type CommitsComparison struct { type CommitsComparison struct {
BaseCommit *RepositoryCommit `json:"base_commit,omitempty"` BaseCommit *RepositoryCommit `json:"base_commit,omitempty"`
MergeBaseCommit *RepositoryCommit `json:"merge_base_commit,omitempty"`
// Head can be 'behind' or 'ahead' // Head can be 'behind' or 'ahead'
Status *string `json:"status,omitempty"` Status *string `json:"status,omitempty"`

View File

@@ -13,8 +13,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"path"
) )
// RepositoryContent represents a file or directory in a github repository. // RepositoryContent represents a file or directory in a github repository.
@@ -27,8 +29,9 @@ type RepositoryContent struct {
Content *string `json:"content,omitempty"` Content *string `json:"content,omitempty"`
SHA *string `json:"sha,omitempty"` SHA *string `json:"sha,omitempty"`
URL *string `json:"url,omitempty"` URL *string `json:"url,omitempty"`
GitURL *string `json:"giturl,omitempty"` GitURL *string `json:"git_url,omitempty"`
HTMLURL *string `json:"htmlurl,omitempty"` HTMLURL *string `json:"html_url,omitempty"`
DownloadURL *string `json:"download_url,omitempty"`
} }
// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile. // RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
@@ -90,6 +93,32 @@ func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryConte
return readme, resp, err return readme, resp, err
} }
// DownloadContents returns an io.ReadCloser that reads the contents of the
// specified file. This function will work with files of any size, as opposed
// to GetContents which is limited to 1 Mb files. It is the caller's
// responsibility to close the ReadCloser.
func (s *RepositoriesService) DownloadContents(owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) {
dir := path.Dir(filepath)
filename := path.Base(filepath)
_, dirContents, _, err := s.GetContents(owner, repo, dir, opt)
if err != nil {
return nil, err
}
for _, contents := range dirContents {
if *contents.Name == filename {
if contents.DownloadURL == nil || *contents.DownloadURL == "" {
return nil, fmt.Errorf("No download link found for %s", filepath)
}
resp, err := s.client.client.Get(*contents.DownloadURL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
}
return nil, fmt.Errorf("No file named %s found in %s", filename, dir)
}
// GetContents can return either the metadata and content of a single file // GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or // (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it // subdirectories of a directory (when path references a directory). To make it

View File

@@ -2,6 +2,7 @@ package github
import ( import (
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
@@ -54,7 +55,70 @@ func TestRepositoriesService_GetReadme(t *testing.T) {
} }
} }
func TestRepositoriesService_GetContent_File(t *testing.T) { func TestRepositoriesService_DownloadContents_Success(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"type": "file",
"name": "f",
"download_url": "`+server.URL+`/download/f"
}]`)
})
mux.HandleFunc("/download/f", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, "foo")
})
r, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err != nil {
t.Errorf("Repositories.DownloadContents returned error: %v", err)
}
bytes, err := ioutil.ReadAll(r)
if err != nil {
t.Errorf("Error reading response body: %v", err)
}
r.Close()
if got, want := string(bytes), "foo"; got != want {
t.Errorf("Repositories.DownloadContents returned %v, want %v", got, want)
}
}
func TestRepositoriesService_DownloadContents_NoDownloadURL(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"type": "file",
"name": "f",
}]`)
})
_, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err == nil {
t.Errorf("Repositories.DownloadContents did not return expected error")
}
}
func TestRepositoriesService_DownloadContents_NoFile(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/contents/d", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[]`)
})
_, err := client.Repositories.DownloadContents("o", "r", "d/f", nil)
if err == nil {
t.Errorf("Repositories.DownloadContents did not return expected error")
}
}
func TestRepositoriesService_GetContents_File(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
@@ -69,7 +133,7 @@ func TestRepositoriesService_GetContent_File(t *testing.T) {
}) })
fileContents, _, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) fileContents, _, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{})
if err != nil { if err != nil {
t.Errorf("Repositories.GetContents_File returned error: %v", err) t.Errorf("Repositories.GetContents returned error: %v", err)
} }
want := &RepositoryContent{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Encoding: String("base64"), Path: String("LICENSE")} want := &RepositoryContent{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Encoding: String("base64"), Path: String("LICENSE")}
if !reflect.DeepEqual(fileContents, want) { if !reflect.DeepEqual(fileContents, want) {
@@ -77,7 +141,7 @@ func TestRepositoriesService_GetContent_File(t *testing.T) {
} }
} }
func TestRepositoriesService_GetContent_Directory(t *testing.T) { func TestRepositoriesService_GetContents_Directory(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) {
@@ -96,7 +160,7 @@ func TestRepositoriesService_GetContent_Directory(t *testing.T) {
}) })
_, directoryContents, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) _, directoryContents, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{})
if err != nil { if err != nil {
t.Errorf("Repositories.GetContents_Directory returned error: %v", err) t.Errorf("Repositories.GetContents returned error: %v", err)
} }
want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")}, want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")},
{Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}} {Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}}

View File

@@ -30,7 +30,7 @@ type DeploymentRequest struct {
Ref *string `json:"ref,omitempty"` Ref *string `json:"ref,omitempty"`
Task *string `json:"task,omitempty"` Task *string `json:"task,omitempty"`
AutoMerge *bool `json:"auto_merge,omitempty"` AutoMerge *bool `json:"auto_merge,omitempty"`
RequiredContexts []string `json:"required_contexts,omitempty"` RequiredContexts *[]string `json:"required_contexts,omitempty"`
Payload *string `json:"payload,omitempty"` Payload *string `json:"payload,omitempty"`
Environment *string `json:"environment,omitempty"` Environment *string `json:"environment,omitempty"`
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
@@ -69,9 +69,6 @@ func (s *RepositoriesService) ListDeployments(owner, repo string, opt *Deploymen
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
deployments := new([]Deployment) deployments := new([]Deployment)
resp, err := s.client.Do(req, deployments) resp, err := s.client.Do(req, deployments)
if err != nil { if err != nil {
@@ -92,9 +89,6 @@ func (s *RepositoriesService) CreateDeployment(owner, repo string, request *Depl
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
d := new(Deployment) d := new(Deployment)
resp, err := s.client.Do(req, d) resp, err := s.client.Do(req, d)
if err != nil { if err != nil {
@@ -138,9 +132,6 @@ func (s *RepositoriesService) ListDeploymentStatuses(owner, repo string, deploym
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
statuses := new([]DeploymentStatus) statuses := new([]DeploymentStatus)
resp, err := s.client.Do(req, statuses) resp, err := s.client.Do(req, statuses)
if err != nil { if err != nil {
@@ -161,9 +152,6 @@ func (s *RepositoriesService) CreateDeploymentStatus(owner, repo string, deploym
return nil, nil, err return nil, nil, err
} }
// TODO: remove custom Accept header when this API fully launches
req.Header.Set("Accept", mediaTypeDeploymentPreview)
d := new(DeploymentStatus) d := new(DeploymentStatus)
resp, err := s.client.Do(req, d) resp, err := s.client.Do(req, d)
if err != nil { if err != nil {

View File

@@ -164,6 +164,18 @@ func (s *RepositoriesService) DeleteHook(owner, repo string, id int) (*Response,
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }
// PingHook triggers a 'ping' event to be sent to the Hook.
//
// GitHub API docs: https://developer.github.com/v3/repos/hooks/#ping-a-hook
func (s *RepositoriesService) PingHook(owner, repo string, id int) (*Response, error) {
u := fmt.Sprintf("repos/%v/%v/hooks/%d/pings", owner, repo, id)
req, err := s.client.NewRequest("POST", u, nil)
if err != nil {
return nil, err
}
return s.client.Do(req, nil)
}
// TestHook triggers a test Hook by github. // TestHook triggers a test Hook by github.
// //
// GitHub API docs: http://developer.github.com/v3/repos/hooks/#test-a-push-hook // GitHub API docs: http://developer.github.com/v3/repos/hooks/#test-a-push-hook
@@ -176,34 +188,7 @@ func (s *RepositoriesService) TestHook(owner, repo string, id int) (*Response, e
return s.client.Do(req, nil) return s.client.Do(req, nil)
} }
// ServiceHook represents a hook that has configuration settings, a list of // ListServiceHooks is deprecated. Use Client.ListServiceHooks instead.
// available events, and default events.
type ServiceHook struct {
Name *string `json:"name,omitempty"`
Events []string `json:"events,omitempty"`
SupportedEvents []string `json:"supported_events,omitempty"`
Schema [][]string `json:"schema,omitempty"`
}
func (s *ServiceHook) String() string {
return Stringify(s)
}
// ListServiceHooks lists all of the available service hooks.
//
// GitHub API docs: https://developer.github.com/webhooks/#services
func (s *RepositoriesService) ListServiceHooks() ([]ServiceHook, *Response, error) { func (s *RepositoriesService) ListServiceHooks() ([]ServiceHook, *Response, error) {
u := "hooks" return s.client.ListServiceHooks()
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}
hooks := new([]ServiceHook)
resp, err := s.client.Do(req, hooks)
if err != nil {
return nil, resp, err
}
return *hooks, resp, err
} }

View File

@@ -153,6 +153,20 @@ func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) {
testURLParseError(t, err) testURLParseError(t, err)
} }
func TestRepositoriesService_PingHook(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/hooks/1/pings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
})
_, err := client.Repositories.PingHook("o", "r", 1)
if err != nil {
t.Errorf("Repositories.PingHook returned error: %v", err)
}
}
func TestRepositoriesService_TestHook(t *testing.T) { func TestRepositoriesService_TestHook(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
@@ -171,35 +185,3 @@ func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) {
_, err := client.Repositories.TestHook("%", "%", 1) _, err := client.Repositories.TestHook("%", "%", 1)
testURLParseError(t, err) testURLParseError(t, err)
} }
func TestRepositoriesService_ListServiceHooks(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/hooks", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[{
"name":"n",
"events":["e"],
"supported_events":["s"],
"schema":[
["a", "b"]
]
}]`)
})
hooks, _, err := client.Repositories.ListServiceHooks()
if err != nil {
t.Errorf("Repositories.ListHooks returned error: %v", err)
}
want := []ServiceHook{{
Name: String("n"),
Events: []string{"e"},
SupportedEvents: []string{"s"},
Schema: [][]string{{"a", "b"}},
}}
if !reflect.DeepEqual(hooks, want) {
t.Errorf("Repositories.ListServiceHooks returned %+v, want %+v", hooks, want)
}
}

View File

@@ -8,7 +8,9 @@ package github
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"mime" "mime"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
) )
@@ -85,8 +87,27 @@ func (s *RepositoriesService) ListReleases(owner, repo string, opt *ListOptions)
// GitHub API docs: http://developer.github.com/v3/repos/releases/#get-a-single-release // GitHub API docs: http://developer.github.com/v3/repos/releases/#get-a-single-release
func (s *RepositoriesService) GetRelease(owner, repo string, id int) (*RepositoryRelease, *Response, error) { func (s *RepositoriesService) GetRelease(owner, repo string, id int) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id)
return s.getSingleRelease(u)
}
req, err := s.client.NewRequest("GET", u, nil) // GetLatestRelease fetches the latest published release for the repository.
//
// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release
func (s *RepositoriesService) GetLatestRelease(owner, repo string) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo)
return s.getSingleRelease(u)
}
// GetReleaseByTag fetches a release with the specified tag.
//
// GitHub API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name
func (s *RepositoriesService) GetReleaseByTag(owner, repo, tag string) (*RepositoryRelease, *Response, error) {
u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag)
return s.getSingleRelease(u)
}
func (s *RepositoriesService) getSingleRelease(url string) (*RepositoryRelease, *Response, error) {
req, err := s.client.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -192,6 +213,54 @@ func (s *RepositoriesService) GetReleaseAsset(owner, repo string, id int) (*Rele
return asset, resp, err return asset, resp, err
} }
// DowloadReleaseAsset downloads a release asset.
//
// DowloadReleaseAsset returns an io.ReadCloser that reads the contents of the
// specified release asset. It is the caller's responsibility to close the ReadCloser.
//
// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset
func (s *RepositoriesService) DownloadReleaseAsset(owner, repo string, id int) (io.ReadCloser, error) {
u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", defaultMediaType)
var resp *http.Response
if s.client.client.Transport == nil {
resp, err = http.DefaultTransport.RoundTrip(req)
} else {
resp, err = s.client.client.Transport.RoundTrip(req)
}
if err != nil {
return nil, err
}
// GitHub API streamed the asset directly
if resp.StatusCode == http.StatusOK {
return resp.Body, nil
}
if resp.StatusCode != http.StatusFound {
return nil, fmt.Errorf("Expected status code 200 or 302, got %d", resp.StatusCode)
}
// GitHub API redirected to pre-signed S3 URL
downloadURL, err := resp.Location()
if err != nil {
return nil, err
}
resp, err = http.Get(downloadURL.String())
if err != nil {
return nil, err
}
return resp.Body, nil
}
// EditReleaseAsset edits a repository release asset. // EditReleaseAsset edits a repository release asset.
// //
// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset // GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset

View File

@@ -6,8 +6,10 @@
package github package github
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"reflect" "reflect"
@@ -55,6 +57,46 @@ func TestRepositoriesService_GetRelease(t *testing.T) {
} }
} }
func TestRepositoriesService_GetLatestRelease(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/latest", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":3}`)
})
release, resp, err := client.Repositories.GetLatestRelease("o", "r")
if err != nil {
t.Errorf("Repositories.GetLatestRelease returned error: %v\n%v", err, resp.Body)
}
want := &RepositoryRelease{ID: Int(3)}
if !reflect.DeepEqual(release, want) {
t.Errorf("Repositories.GetLatestRelease returned %+v, want %+v", release, want)
}
}
func TestRepositoriesService_GetReleaseByTag(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/tags/foo", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":13}`)
})
release, resp, err := client.Repositories.GetReleaseByTag("o", "r", "foo")
if err != nil {
t.Errorf("Repositories.GetReleaseByTag returned error: %v\n%v", err, resp.Body)
}
want := &RepositoryRelease{ID: Int(13)}
if !reflect.DeepEqual(release, want) {
t.Errorf("Repositories.GetReleaseByTag returned %+v, want %+v", release, want)
}
}
func TestRepositoriesService_CreateRelease(t *testing.T) { func TestRepositoriesService_CreateRelease(t *testing.T) {
setup() setup()
defer teardown() defer teardown()
@@ -164,6 +206,64 @@ func TestRepositoriesService_GetReleaseAsset(t *testing.T) {
} }
} }
func TestRepositoriesService_DownloadReleaseAsset_Stream(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", defaultMediaType)
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
fmt.Fprint(w, "Hello World")
})
reader, err := client.Repositories.DowloadReleaseAsset("o", "r", 1)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned error: %v", err)
}
want := []byte("Hello World")
content, err := ioutil.ReadAll(reader)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned bad reader: %v", err)
}
if !bytes.Equal(want, content) {
t.Errorf("Repositories.DowloadReleaseAsset returned %+v, want %+v", content, want)
}
}
func TestRepositoriesService_DownloadReleaseAsset_Redirect(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testHeader(t, r, "Accept", defaultMediaType)
w.Header().Set("Location", server.URL+"/github-cloud/releases/1/hello-world.txt")
w.WriteHeader(http.StatusFound)
})
mux.HandleFunc("/github-cloud/releases/1/hello-world.txt", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", "attachment; filename=hello-world.txt")
fmt.Fprint(w, "Hello World")
})
reader, err := client.Repositories.DowloadReleaseAsset("o", "r", 1)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned error: %v", err)
}
want := []byte("Hello World")
content, err := ioutil.ReadAll(reader)
if err != nil {
t.Errorf("Repositories.DowloadReleaseAsset returned bad reader: %v", err)
}
if !bytes.Equal(want, content) {
t.Errorf("Repositories.DowloadReleaseAsset returned %+v, want %+v", content, want)
}
}
func TestRepositoriesService_EditReleaseAsset(t *testing.T) { func TestRepositoriesService_EditReleaseAsset(t *testing.T) {
setup() setup()
defer teardown() defer teardown()

View File

@@ -45,7 +45,6 @@ func TestRepositoriesService_List_specifiedUser(t *testing.T) {
"direction": "asc", "direction": "asc",
"page": "2", "page": "2",
}) })
fmt.Fprint(w, `[{"id":1}]`) fmt.Fprint(w, `[{"id":1}]`)
}) })
@@ -191,7 +190,8 @@ func TestRepositoriesService_Get(t *testing.T) {
mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET") testMethod(t, r, "GET")
fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"}}`) testHeader(t, r, "Accept", mediaTypeLicensesPreview)
fmt.Fprint(w, `{"id":1,"name":"n","description":"d","owner":{"login":"l"},"license":{"key":"mit"}}`)
}) })
repo, _, err := client.Repositories.Get("o", "r") repo, _, err := client.Repositories.Get("o", "r")
@@ -199,7 +199,7 @@ func TestRepositoriesService_Get(t *testing.T) {
t.Errorf("Repositories.Get returned error: %v", err) t.Errorf("Repositories.Get returned error: %v", err)
} }
want := &Repository{ID: Int(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}} want := &Repository{ID: Int(1), Name: String("n"), Description: String("d"), Owner: &User{Login: String("l")}, License: &License{Key: String("mit")}}
if !reflect.DeepEqual(repo, want) { if !reflect.DeepEqual(repo, want) {
t.Errorf("Repositories.Get returned %+v, want %+v", repo, want) t.Errorf("Repositories.Get returned %+v, want %+v", repo, want)
} }

View File

@@ -59,6 +59,10 @@ type User struct {
// TextMatches is only populated from search results that request text matches // TextMatches is only populated from search results that request text matches
// See: search.go and https://developer.github.com/v3/search/#text-match-metadata // See: search.go and https://developer.github.com/v3/search/#text-match-metadata
TextMatches []TextMatch `json:"text_matches,omitempty"` TextMatches []TextMatch `json:"text_matches,omitempty"`
// Permissions identifies the permissions that a user has on a given
// repository. This is only populated when calling Repositories.ListCollaborators.
Permissions *map[string]bool `json:"permissions,omitempty"`
} }
func (u User) String() string { func (u User) String() string {

Some files were not shown because too many files have changed in this diff Show More