diff --git a/github/github.go b/github/github.go index 6d4e7775798..a58dbfb58d4 100644 --- a/github/github.go +++ b/github/github.go @@ -96,6 +96,9 @@ const ( // https://developer.github.com/changes/2017-01-05-commit-search-api/ mediaTypeCommitSearchPreview = "application/vnd.github.cloak-preview+json" + + // https://developer.github.com/changes/2016-12-14-reviews-api/ + mediaTypePullRequestReviewsPreview = "application/vnd.github.black-cat-preview+json" ) // A Client manages communication with the GitHub API. diff --git a/github/pulls_reviews.go b/github/pulls_reviews.go index ae3cdd4af42..be57af88c72 100644 --- a/github/pulls_reviews.go +++ b/github/pulls_reviews.go @@ -5,15 +5,243 @@ package github -import "time" +import ( + "fmt" + "time" +) // PullRequestReview represents a review of a pull request. type PullRequestReview struct { - ID *int `json:"id,omitempty"` - User *User `json:"user,omitempty"` - Body *string `json:"body,omitempty"` - SubmittedAt *time.Time `json:"submitted_at,omitempty"` + ID *int `json:"id,omitempty"` + User *User `json:"user,omitempty"` + Body *string `json:"body,omitempty"` + SubmittedAt *time.Time `json:"submitted_at,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + PullRequestURL *string `json:"pull_request_url,omitempty"` + State *string `json:"state,omitempty"` +} + +func (p PullRequestReview) String() string { + return Stringify(p) +} + +// DraftReviewComment represents a comment part of the review. +type DraftReviewComment struct { + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` + Body *string `json:"body,omitempty"` +} + +func (c DraftReviewComment) String() string { + return Stringify(c) +} + +// PullRequestReviewRequest represents a request to create a review. +type PullRequestReviewRequest struct { + Body *string `json:"body,omitempty"` + Event *string `json:"event,omitempty"` + Comments []*DraftReviewComment `json:"comments,omitempty"` +} + +func (r PullRequestReviewRequest) String() string { + return Stringify(r) +} + +// PullRequestReviewDismissalRequest represents a request to dismiss a review. +type PullRequestReviewDismissalRequest struct { + Message *string `json:"message,omitempty"` +} + +func (r PullRequestReviewDismissalRequest) String() string { + return Stringify(r) +} + +// ListReviews lists all reviews on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#list-reviews-on-a-pull-request +func (s *PullRequestsService) ListReviews(owner, repo string, number int) ([]*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) + + 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", mediaTypePullRequestReviewsPreview) + + var reviews []*PullRequestReview + resp, err := s.client.Do(req, &reviews) + if err != nil { + return nil, resp, err + } + + return reviews, resp, nil +} + +// GetReview fetches the specified pull request review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#get-a-single-review +func (s *PullRequestsService) GetReview(owner, repo string, number, reviewID int) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) + + 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", mediaTypePullRequestReviewsPreview) + + review := new(PullRequestReview) + resp, err := s.client.Do(req, review) + if err != nil { + return nil, resp, err + } + + return review, resp, nil +} + +// DeletePendingReview deletes the specified pull request pending review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#delete-a-pending-review +func (s *PullRequestsService) DeletePendingReview(owner, repo string, number, reviewID int) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypePullRequestReviewsPreview) + + review := new(PullRequestReview) + resp, err := s.client.Do(req, review) + if err != nil { + return nil, resp, err + } + + return review, resp, nil +} + +// ListReviewComments lists all the comments for the specified review. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#get-a-single-reviews-comments +func (s *PullRequestsService) ListReviewComments(owner, repo string, number, reviewID int) ([]*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/comments", owner, repo, number, reviewID) + + 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", mediaTypePullRequestReviewsPreview) + + var comments []*PullRequestComment + resp, err := s.client.Do(req, &comments) + if err != nil { + return nil, resp, err + } + + return comments, resp, nil +} + +// CreateReview creates a new review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#create-a-pull-request-review +func (s *PullRequestsService) CreateReview(owner, repo string, number int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews", owner, repo, number) + + req, err := s.client.NewRequest("POST", u, review) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypePullRequestReviewsPreview) + + r := new(PullRequestReview) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// SubmitReview submits a specified review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#submit-a-pull-request-review +func (s *PullRequestsService) SubmitReview(owner, repo string, number, reviewID int, review *PullRequestReviewRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/events", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("POST", u, review) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypePullRequestReviewsPreview) + + r := new(PullRequestReview) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, nil +} + +// DismissReview dismisses a specified review on the specified pull request. +// +// TODO: Follow up with GitHub support about an issue with this method's +// returned error format and remove this comment once it's fixed. +// Read more about it here - https://github.com/google/go-github/issues/540 +// +// GitHub API docs: https://developer.github.com/v3/pulls/reviews/#dismiss-a-pull-request-review +func (s *PullRequestsService) DismissReview(owner, repo string, number, reviewID int, review *PullRequestReviewDismissalRequest) (*PullRequestReview, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/reviews/%d/dismissals", owner, repo, number, reviewID) + + req, err := s.client.NewRequest("PUT", u, review) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept header when this API fully launches + req.Header.Set("Accept", mediaTypePullRequestReviewsPreview) + + r := new(PullRequestReview) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } - // State can be "approved", "rejected", or "commented". - State *string `json:"state,omitempty"` + return r, resp, nil } diff --git a/github/pulls_reviews_test.go b/github/pulls_reviews_test.go new file mode 100644 index 00000000000..707fd5a6689 --- /dev/null +++ b/github/pulls_reviews_test.go @@ -0,0 +1,235 @@ +// Copyright 2016 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 TestPullRequestsService_ListReviews(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + reviews, _, err := client.PullRequests.ListReviews("o", "r", 1) + if err != nil { + t.Errorf("PullRequests.ListReviews returned error: %v", err) + } + + want := []*PullRequestReview{ + {ID: Int(1)}, + {ID: Int(2)}, + } + if !reflect.DeepEqual(reviews, want) { + t.Errorf("PullRequests.ListReviews returned %+v, want %+v", reviews, want) + } +} + +func TestPullRequestsService_ListReviews_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.ListReviews("%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_GetReview(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.GetReview("o", "r", 1, 1) + if err != nil { + t.Errorf("PullRequests.GetReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.GetReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_GetReview_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.GetReview("%", "r", 1, 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_DeletePendingReview(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.DeletePendingReview("o", "r", 1, 1) + if err != nil { + t.Errorf("PullRequests.DeletePendingReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.DeletePendingReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_DeletePendingReview_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.DeletePendingReview("%", "r", 1, 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_ListReviewComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + comments, _, err := client.PullRequests.ListReviewComments("o", "r", 1, 1) + if err != nil { + t.Errorf("PullRequests.ListReviewComments returned error: %v", err) + } + + want := []*PullRequestComment{ + {ID: Int(1)}, + {ID: Int(2)}, + } + if !reflect.DeepEqual(comments, want) { + t.Errorf("PullRequests.ListReviewComments returned %+v, want %+v", comments, want) + } +} + +func TestPullRequestsService_ListReviewComments_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.ListReviewComments("%", "r", 1, 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_CreateReview(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestReviewRequest{ + Body: String("b"), + Event: String("APPROVE"), + } + + mux.HandleFunc("/repos/o/r/pulls/1/reviews", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.CreateReview("o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.CreateReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.CreateReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_CreateReview_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.CreateReview("%", "r", 1, &PullRequestReviewRequest{}) + testURLParseError(t, err) +} + +func TestPullRequestsService_SubmitReview(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestReviewRequest{ + Body: String("b"), + Event: String("APPROVE"), + } + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/events", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.SubmitReview("o", "r", 1, 1, input) + if err != nil { + t.Errorf("PullRequests.SubmitReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.SubmitReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_SubmitReview_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.SubmitReview("%", "r", 1, 1, &PullRequestReviewRequest{}) + testURLParseError(t, err) +} + +func TestPullRequestsService_DismissReview(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestReviewDismissalRequest{Message: String("m")} + + mux.HandleFunc("/repos/o/r/pulls/1/reviews/1/dismissals", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestReviewDismissalRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + testHeader(t, r, "Accept", mediaTypePullRequestReviewsPreview) + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"id":1}`) + }) + + review, _, err := client.PullRequests.DismissReview("o", "r", 1, 1, input) + if err != nil { + t.Errorf("PullRequests.DismissReview returned error: %v", err) + } + + want := &PullRequestReview{ID: Int(1)} + if !reflect.DeepEqual(review, want) { + t.Errorf("PullRequests.DismissReview returned %+v, want %+v", review, want) + } +} + +func TestPullRequestsService_DismissReview_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.DismissReview("%", "r", 1, 1, &PullRequestReviewDismissalRequest{}) + testURLParseError(t, err) +} diff --git a/github/strings_test.go b/github/strings_test.go index a393eb6cfc5..a47f1eb6ea4 100644 --- a/github/strings_test.go +++ b/github/strings_test.go @@ -109,6 +109,10 @@ func TestString(t *testing.T) { {Organization{ID: Int(1)}, `github.Organization{ID:1}`}, {PullRequestComment{ID: Int(1)}, `github.PullRequestComment{ID:1}`}, {PullRequest{Number: Int(1)}, `github.PullRequest{Number:1}`}, + {PullRequestReview{ID: Int(1)}, `github.PullRequestReview{ID:1}`}, + {DraftReviewComment{Position: Int(1)}, `github.DraftReviewComment{Position:1}`}, + {PullRequestReviewRequest{Body: String("r")}, `github.PullRequestReviewRequest{Body:"r"}`}, + {PullRequestReviewDismissalRequest{Message: String("r")}, `github.PullRequestReviewDismissalRequest{Message:"r"}`}, {PushEventCommit{SHA: String("s")}, `github.PushEventCommit{SHA:"s"}`}, {PushEvent{PushID: Int(1)}, `github.PushEvent{PushID:1}`}, {Reference{Ref: String("r")}, `github.Reference{Ref:"r"}`},