From 51442f25ff14439883b331ee7fe9627be9ed25e2 Mon Sep 17 00:00:00 2001 From: Akeda Bagus Date: Fri, 28 Jun 2013 01:08:20 +0700 Subject: [PATCH] Add GistsService that handles GitHub's Gists API. fixes #18 --- github/gists.go | 237 +++++++++++++++++++++++++++++ github/gists_test.go | 345 +++++++++++++++++++++++++++++++++++++++++++ github/github.go | 2 + 3 files changed, 584 insertions(+) create mode 100644 github/gists.go create mode 100644 github/gists_test.go diff --git a/github/gists.go b/github/gists.go new file mode 100644 index 0000000..96a9476 --- /dev/null +++ b/github/gists.go @@ -0,0 +1,237 @@ +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "fmt" + "net/url" + "time" +) + +// GistsService handles communication with the Gist related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/gists/ +type GistsService struct { + client *Client +} + +// Gist represents a GitHub's gist. +type Gist struct { + ID string `json:"id,omitempty"` + Description string `json:"description,omitempty"` + Public bool `json:"public,omitempty"` + User *User `json:"user,omitempty"` + Files map[GistFilename]GistFile `json:"files,omitempty"` + Comments int `json:"comments,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + GitPullURL string `json:"git_pull_url,omitempty"` + GitPushURL string `json:"git_push_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +// GistFilename represents filename on a gist. +type GistFilename string + +// GistFile represents a file on a gist. +type GistFile struct { + Size int `json:"size,omitempty"` + Filename GistFilename `json:"filename,omitempty"` + RawURL string `json:"raw_url,omitempty"` + Content string `json:"content,omitempty"` +} + +// GistListOptions specifies the optional parameters to the +// GistsService.List, GistsService.ListAll, and GistsService.ListStarred methods. +type GistListOptions struct { + // Since filters Gists by time. + Since time.Time +} + +// List gists for a user. Passing the empty string will list +// all public gists if called anonymously. However, if the call +// is authenticated, it will returns all gists for the authenticated +// user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) List(user string, opt *GistListOptions) ([]Gist, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/gists", user) + } else { + u = "gists" + } + if opt != nil { + params := url.Values{} + if !opt.Since.IsZero() { + params.Add("since", opt.Since.Format(time.RFC3339)) + } + u += "?" + params.Encode() + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + gists := new([]Gist) + _, err = s.client.Do(req, gists) + return *gists, err +} + +// ListAll lists all public gists. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListAll(opt *GistListOptions) ([]Gist, error) { + u := "gists/public" + if opt != nil { + params := url.Values{} + if !opt.Since.IsZero() { + params.Add("since", opt.Since.Format(time.RFC3339)) + } + u += "?" + params.Encode() + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + gists := new([]Gist) + _, err = s.client.Do(req, gists) + return *gists, err +} + +// ListStarred lists starred gists of authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListStarred(opt *GistListOptions) ([]Gist, error) { + u := "gists/starred" + if opt != nil { + params := url.Values{} + if !opt.Since.IsZero() { + params.Add("since", opt.Since.Format(time.RFC3339)) + } + u += "?" + params.Encode() + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + gists := new([]Gist) + _, err = s.client.Do(req, gists) + return *gists, err +} + +// Get a single gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#get-a-single-gist +func (s *GistsService) Get(id string) (*Gist, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + gist := new(Gist) + _, err = s.client.Do(req, gist) + return gist, err +} + +// Create a gist for authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist +func (s *GistsService) Create(gist *Gist) (*Gist, error) { + u := "gists" + req, err := s.client.NewRequest("POST", u, gist) + if err != nil { + return nil, err + } + g := new(Gist) + _, err = s.client.Do(req, g) + return g, err +} + +// Edit a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#edit-a-gist +func (s *GistsService) Edit(id string, gist *Gist) (*Gist, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("PATCH", u, gist) + if err != nil { + return nil, err + } + g := new(Gist) + _, err = s.client.Do(req, g) + return g, err +} + +// Delete a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#delete-a-gist +func (s *GistsService) Delete(id string) error { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return err + } + _, err = s.client.Do(req, nil) + return err +} + +// Star a gist on behalf of authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#star-a-gist +func (s *GistsService) Star(id string) error { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return err + } + _, err = s.client.Do(req, nil) + return err +} + +// Unstar a gist on a behalf of authenticated user. +// +// Github API docs: http://developer.github.com/v3/gists/#unstar-a-gist +func (s *GistsService) Unstar(id string) error { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return err + } + _, err = s.client.Do(req, nil) + return err +} + +// Starred checks if a gist is starred by authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/gists/#check-if-a-gist-is-starred +func (s *GistsService) Starred(id string) (bool, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, err + } + _, err = s.client.Do(req, nil) + return parseBoolResponse(err) +} + +// Fork a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#fork-a-gist +func (s *GistsService) Fork(id string) (*Gist, error) { + u := fmt.Sprintf("gists/%v/forks", id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + g := new(Gist) + _, err = s.client.Do(req, g) + return g, err +} diff --git a/github/gists_test.go b/github/gists_test.go new file mode 100644 index 0000000..5808689 --- /dev/null +++ b/github/gists_test.go @@ -0,0 +1,345 @@ +// 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 ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + "time" +) + +func TestGistsService_List(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/users/u/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, err := client.Gists.List("u", opt) + + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []Gist{Gist{ID: "1"}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_withEmptyUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + gists, err := client.Gists.List("", nil) + if err != nil { + t.Errorf("Gists.List returned error: %v", err) + } + + want := []Gist{Gist{ID: "1"}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_ListAll(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, err := client.Gists.ListAll(opt) + + if err != nil { + t.Errorf("Gists.ListAll returned error: %v", err) + } + + want := []Gist{Gist{ID: "1"}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListAll returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_ListStarred(t *testing.T) { + setup() + defer teardown() + + since := "2013-01-01T00:00:00Z" + + mux.HandleFunc("/gists/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": since, + }) + fmt.Fprint(w, `[{"id": "1"}]`) + }) + + opt := &GistListOptions{Since: time.Date(2013, time.January, 1, 0, 0, 0, 0, time.UTC)} + gists, err := client.Gists.ListStarred(opt) + + if err != nil { + t.Errorf("Gists.ListStarred returned error: %v", err) + } + + want := []Gist{Gist{ID: "1"}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.ListStarred returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": "1"}`) + }) + + gist, err := client.Gists.Get("1") + + if err != nil { + t.Errorf("Gists.Get returned error: %v", err) + } + + want := &Gist{ID: "1"} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Create(t *testing.T) { + setup() + defer teardown() + + input := &Gist{ + Description: "Gist description", + Public: false, + Files: map[GistFilename]GistFile{ + "test.txt": GistFile{Content: "Gist file content"}, + }, + } + + mux.HandleFunc("/gists", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, + ` + { + "id": "1", + "description": "Gist description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + } + } + }`) + }) + + gist, err := client.Gists.Create(input) + if err != nil { + t.Errorf("Gists.Create returned error: %v", err) + } + + want := &Gist{ + ID: "1", + Description: "Gist description", + Public: false, + Files: map[GistFilename]GistFile{ + "test.txt": GistFile{Filename: "test.txt"}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Create returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &Gist{ + Description: "New description", + Files: map[GistFilename]GistFile{ + "new.txt": GistFile{Content: "new file content"}, + }, + } + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Gist) + 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", + "description": "new description", + "public": false, + "files": { + "test.txt": { + "filename": "test.txt" + }, + "new.txt": { + "filename": "new.txt" + } + } + }`) + }) + + gist, err := client.Gists.Edit("1", input) + if err != nil { + t.Errorf("Gists.Edit returned error: %v", err) + } + + want := &Gist{ + ID: "1", + Description: "new description", + Public: false, + Files: map[GistFilename]GistFile{ + "test.txt": GistFile{Filename: "test.txt"}, + "new.txt": GistFile{Filename: "new.txt"}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Edit returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + err := client.Gists.Delete("1") + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_Star(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + err := client.Gists.Star("1") + if err != nil { + t.Errorf("Gists.Star returned error: %v", err) + } +} + +func TestGistsService_Unstar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + err := client.Gists.Unstar("1") + if err != nil { + t.Errorf("Gists.Unstar returned error: %v", err) + } +} + +func TestGistsService_Starred_hasStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, err := client.Gists.Starred("1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_Starred_noStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/star", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, err := client.Gists.Starred("1") + if err != nil { + t.Errorf("Gists.Starred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Gists.Starred returned %+v, want %+v", star, want) + } +} + +func TestGistsService_Fork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + fmt.Fprint(w, `{"id": "2"}`) + }) + + gist, err := client.Gists.Fork("1") + + if err != nil { + t.Errorf("Gists.Fork returned error: %v", err) + } + + want := &Gist{ID: "2"} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Fork returned %+v, want %+v", gist, want) + } +} diff --git a/github/github.go b/github/github.go index 4ad98c1..98f93d5 100644 --- a/github/github.go +++ b/github/github.go @@ -90,6 +90,7 @@ type Client struct { PullRequests *PullRequestsService Repositories *RepositoriesService Users *UsersService + Gists *GistsService } // ListOptions specifies the optional parameters to various List methods that @@ -115,6 +116,7 @@ func NewClient(httpClient *http.Client) *Client { c.PullRequests = &PullRequestsService{client: c} c.Repositories = &RepositoriesService{client: c} c.Users = &UsersService{client: c} + c.Gists = &GistsService{client: c} return c }