From 40dde19853f1129e6d893aeaf3e06fa1d3440ebb Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 30 Apr 2015 23:49:39 -0700 Subject: [PATCH 1/3] Adds a simple release-note builder that scrapes the Github API for recent PRs. --- build/make-release-notes.sh | 42 +++++++++++++++ contrib/release-notes/README.md | 7 +++ contrib/release-notes/release-notes.go | 73 ++++++++++++++++++++++++++ docs/making-release-notes.md | 30 +++++++++++ 4 files changed, 152 insertions(+) create mode 100755 build/make-release-notes.sh create mode 100644 contrib/release-notes/README.md create mode 100644 contrib/release-notes/release-notes.go create mode 100644 docs/making-release-notes.md diff --git a/build/make-release-notes.sh b/build/make-release-notes.sh new file mode 100755 index 00000000000..c8973a07347 --- /dev/null +++ b/build/make-release-notes.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Clean out the output directory on the docker host. +set -o errexit +set -o nounset +set -o pipefail + +function pop_dir { + popd > /dev/null +} + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. + +if [[ -z "${1:-}" ]]; then + echo "Usage: ${0} " + exit 1 +fi + +pushd . > /dev/null +trap 'pop_dir' INT TERM EXIT + +cd ${KUBE_ROOT}/contrib/release-notes +# TODO: vendor these dependencies, but using godep again will be annoying... +GOPATH=$PWD go get github.com/google/go-github/github +GOPATH=$PWD go get github.com/google/go-querystring/query +GOPATH=$PWD go build release-notes.go +./release-notes --last-release-pr=${1} + diff --git a/contrib/release-notes/README.md b/contrib/release-notes/README.md new file mode 100644 index 00000000000..ed52fb290af --- /dev/null +++ b/contrib/release-notes/README.md @@ -0,0 +1,7 @@ +## A simple tool that scrapes the github API to build a starting point for release notes. + +### Usage +```bash +${KUBERNETES_ROOT}/build/make-release-notes.sh +``` + diff --git a/contrib/release-notes/release-notes.go b/contrib/release-notes/release-notes.go new file mode 100644 index 00000000000..cd2be693647 --- /dev/null +++ b/contrib/release-notes/release-notes.go @@ -0,0 +1,73 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "bytes" + "flag" + "fmt" + "os" + + "github.com/google/go-github/github" +) + +var target = flag.Int("last-release-pr", 0, "The PR number of the last versioned release.") + +func main() { + flag.Parse() + // Automatically determine this from github. + if *target == 0 { + fmt.Printf("--last-release-pr is required.\n") + os.Exit(1) + } + + client := github.NewClient(nil) + + done := false + + opts := github.PullRequestListOptions{ + State: "closed", + Sort: "updated", + Direction: "desc", + ListOptions: github.ListOptions{ + Page: 0, + PerPage: 100, + }, + } + + buffer := &bytes.Buffer{} + for !done { + opts.Page++ + results, _, err := client.PullRequests.List("GoogleCloudPlatform", "kubernetes", &opts) + if err != nil { + fmt.Printf("Error contacting github: %v", err) + os.Exit(1) + } + for _, result := range results { + // Skip Closed but not Merged PRs + if result.MergedAt == nil { + continue + } + if *result.Number == *target { + done = true + break + } + fmt.Fprintf(buffer, " * %s #%d (%s)\n", *result.Title, *result.Number, *result.User.Login) + } + } + fmt.Printf("%s", buffer.Bytes()) +} diff --git a/docs/making-release-notes.md b/docs/making-release-notes.md new file mode 100644 index 00000000000..d7ee79ae6dc --- /dev/null +++ b/docs/making-release-notes.md @@ -0,0 +1,30 @@ +## Making release notes +This documents the process for making release notes for a release. + +### 1) Note the PR number of the previous release +Find the PR that was merged with the previous release. Remember this number +_TODO_: Figure out a way to record this somewhere to save the next release engineer time. + +### 2) Build the release-notes tool +```bash +${KUBERNETES_ROOT}/build/make-release-notes.sh +``` + +### 3) Trim the release notes +This generates a list of the entire set of PRs merged since the last release. It is likely long +and many PRs aren't worth mentioning. + +Open up ```candidate-notes.md``` in your favorite editor. + +Remove, regroup, organize to your hearts content. + + +### 4) Update CHANGELOG.md +With the final markdown all set, cut and paste it to the top of ```CHANGELOG.md``` + +### 5) Update the Release page + * Switch to the [releases](https://github.com/GoogleCloudPlatform/kubernetes/releases) page. + * Open up the release you are working on. + * Cut and paste the final markdown from above into the release notes + * Press Save. + From f34b06be77279b0415e2f1664569256c1e292798 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 6 May 2015 16:44:40 -0700 Subject: [PATCH 2/3] add go-github. --- Godeps/Godeps.json | 8 + .../google/go-github/github/activity.go | 14 + .../go-github/github/activity_events.go | 305 ++++++++ .../go-github/github/activity_events_test.go | 305 ++++++++ .../github/activity_notifications.go | 224 ++++++ .../github/activity_notifications_test.go | 203 ++++++ .../google/go-github/github/activity_star.go | 114 +++ .../go-github/github/activity_star_test.go | 167 +++++ .../go-github/github/activity_watching.go | 131 ++++ .../github/activity_watching_test.go | 177 +++++ .../github.com/google/go-github/github/doc.go | 137 ++++ .../google/go-github/github/gists.go | 281 ++++++++ .../google/go-github/github/gists_comments.go | 118 +++ .../go-github/github/gists_comments_test.go | 155 ++++ .../google/go-github/github/gists_test.go | 411 +++++++++++ .../github.com/google/go-github/github/git.go | 14 + .../google/go-github/github/git_blobs.go | 47 ++ .../google/go-github/github/git_blobs_test.go | 92 +++ .../google/go-github/github/git_commits.go | 112 +++ .../go-github/github/git_commits_test.go | 82 +++ .../google/go-github/github/git_refs.go | 162 +++++ .../google/go-github/github/git_refs_test.go | 280 ++++++++ .../google/go-github/github/git_tags.go | 73 ++ .../google/go-github/github/git_tags_test.go | 68 ++ .../google/go-github/github/git_trees.go | 89 +++ .../google/go-github/github/git_trees_test.go | 189 +++++ .../google/go-github/github/github.go | 581 +++++++++++++++ .../google/go-github/github/github_test.go | 679 ++++++++++++++++++ .../google/go-github/github/gitignore.go | 63 ++ .../google/go-github/github/gitignore_test.go | 58 ++ .../google/go-github/github/issues.go | 261 +++++++ .../go-github/github/issues_assignees.go | 46 ++ .../go-github/github/issues_assignees_test.go | 98 +++ .../go-github/github/issues_comments.go | 138 ++++ .../go-github/github/issues_comments_test.go | 184 +++++ .../google/go-github/github/issues_events.go | 127 ++++ .../go-github/github/issues_events_test.go | 86 +++ .../google/go-github/github/issues_labels.go | 222 ++++++ .../go-github/github/issues_labels_test.go | 313 ++++++++ .../go-github/github/issues_milestones.go | 140 ++++ .../github/issues_milestones_test.go | 157 ++++ .../google/go-github/github/issues_test.go | 242 +++++++ .../google/go-github/github/licenses.go | 81 +++ .../google/go-github/github/licenses_test.go | 64 ++ .../google/go-github/github/misc.go | 193 +++++ .../google/go-github/github/misc_test.go | 169 +++++ .../google/go-github/github/orgs.go | 137 ++++ .../google/go-github/github/orgs_hooks.go | 104 +++ .../go-github/github/orgs_hooks_test.go | 134 ++++ .../google/go-github/github/orgs_members.go | 221 ++++++ .../go-github/github/orgs_members_test.go | 289 ++++++++ .../google/go-github/github/orgs_teams.go | 317 ++++++++ .../go-github/github/orgs_teams_test.go | 474 ++++++++++++ .../google/go-github/github/orgs_test.go | 120 ++++ .../google/go-github/github/pulls.go | 275 +++++++ .../google/go-github/github/pulls_comments.go | 142 ++++ .../go-github/github/pulls_comments_test.go | 189 +++++ .../google/go-github/github/pulls_test.go | 365 ++++++++++ .../google/go-github/github/repos.go | 490 +++++++++++++ .../go-github/github/repos_collaborators.go | 75 ++ .../github/repos_collaborators_test.go | 123 ++++ .../google/go-github/github/repos_comments.go | 150 ++++ .../go-github/github/repos_comments_test.go | 180 +++++ .../google/go-github/github/repos_commits.go | 168 +++++ .../go-github/github/repos_commits_test.go | 191 +++++ .../google/go-github/github/repos_contents.go | 248 +++++++ .../go-github/github/repos_contents_test.go | 304 ++++++++ .../go-github/github/repos_deployments.go | 162 +++++ .../github/repos_deployments_test.go | 87 +++ .../google/go-github/github/repos_forks.go | 73 ++ .../go-github/github/repos_forks_test.go | 73 ++ .../google/go-github/github/repos_hooks.go | 194 +++++ .../go-github/github/repos_hooks_test.go | 187 +++++ .../google/go-github/github/repos_keys.go | 108 +++ .../go-github/github/repos_keys_test.go | 153 ++++ .../google/go-github/github/repos_merging.go | 37 + .../go-github/github/repos_merging_test.go | 47 ++ .../google/go-github/github/repos_pages.go | 90 +++ .../go-github/github/repos_pages_test.go | 73 ++ .../google/go-github/github/repos_releases.go | 258 +++++++ .../go-github/github/repos_releases_test.go | 237 ++++++ .../google/go-github/github/repos_stats.go | 214 ++++++ .../go-github/github/repos_stats_test.go | 210 ++++++ .../google/go-github/github/repos_statuses.go | 128 ++++ .../go-github/github/repos_statuses_test.go | 96 +++ .../google/go-github/github/repos_test.go | 407 +++++++++++ .../google/go-github/github/search.go | 158 ++++ .../google/go-github/github/search_test.go | 196 +++++ .../google/go-github/github/strings.go | 93 +++ .../google/go-github/github/strings_test.go | 137 ++++ .../google/go-github/github/timestamp.go | 41 ++ .../google/go-github/github/timestamp_test.go | 181 +++++ .../google/go-github/github/users.go | 140 ++++ .../go-github/github/users_administration.go | 64 ++ .../github/users_administration_test.go | 71 ++ .../google/go-github/github/users_emails.go | 69 ++ .../go-github/github/users_emails_test.go | 94 +++ .../go-github/github/users_followers.go | 116 +++ .../go-github/github/users_followers_test.go | 222 ++++++ .../google/go-github/github/users_keys.go | 104 +++ .../go-github/github/users_keys_test.go | 124 ++++ .../google/go-github/github/users_test.go | 150 ++++ .../google/go-querystring/query/encode.go | 307 ++++++++ .../go-querystring/query/encode_test.go | 286 ++++++++ 104 files changed, 17943 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/doc.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gists.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/github.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/github_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/licenses.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/licenses_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/misc.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/orgs_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/pulls.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/pulls_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_comments.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_comments_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_commits.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_commits_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_contents.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_contents_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_forks.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_forks_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_keys.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_keys_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_merging.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_merging_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_pages.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_pages_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_releases.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_releases_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_stats.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_stats_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/repos_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/search.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/search_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/strings.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/strings_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/timestamp.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/timestamp_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_administration.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_administration_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_emails.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_emails_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_followers.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_followers_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_keys.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_keys_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-github/github/users_test.go create mode 100644 Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go create mode 100644 Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 33a8cc01c72..080040aaa18 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -284,6 +284,14 @@ "Comment": "0.12.0-21-g3166552", "Rev": "3166552cc4481e48dc1002e000d7e4ae8d5b9850" }, + { + "ImportPath": "github.com/google/go-github/github", + "Rev": "930e6fdb8dc2b11458fdeb55b3cd68e5370a1a28" + }, + { + "ImportPath": "github.com/google/go-querystring/query", + "Rev": "547ef5ac979778feb2f760cdb5f4eae1a2207b86" + }, { "ImportPath": "github.com/google/gofuzz", "Rev": "bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5" diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity.go new file mode 100644 index 00000000000..355de624b23 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity.go @@ -0,0 +1,14 @@ +// 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 + +// ActivityService handles communication with the activity related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/activity/ +type ActivityService struct { + client *Client +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go new file mode 100644 index 00000000000..b8a5e66b999 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_events.go @@ -0,0 +1,305 @@ +// 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" + "time" +) + +// Event represents a GitHub event. +type Event struct { + Type *string `json:"type,omitempty"` + Public *bool `json:"public"` + RawPayload *json.RawMessage `json:"payload,omitempty"` + Repo *Repository `json:"repo,omitempty"` + Actor *User `json:"actor,omitempty"` + Org *Organization `json:"org,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + ID *string `json:"id,omitempty"` +} + +func (e Event) String() string { + return Stringify(e) +} + +// Payload returns the parsed event payload. For recognized event types +// (PushEvent), a value of the corresponding struct type will be returned. +func (e *Event) Payload() (payload interface{}) { + switch *e.Type { + case "PushEvent": + payload = &PushEvent{} + } + if err := json.Unmarshal(*e.RawPayload, &payload); err != nil { + panic(err.Error()) + } + return payload +} + +// PushEvent represents a git push to a GitHub repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/types/#pushevent +type PushEvent struct { + PushID *int `json:"push_id,omitempty"` + Head *string `json:"head,omitempty"` + Ref *string `json:"ref,omitempty"` + Size *int `json:"size,omitempty"` + Commits []PushEventCommit `json:"commits,omitempty"` + Repo *Repository `json:"repository,omitempty"` +} + +func (p PushEvent) String() string { + return Stringify(p) +} + +// PushEventCommit represents a git commit in a GitHub PushEvent. +type PushEventCommit struct { + SHA *string `json:"sha,omitempty"` + Message *string `json:"message,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + URL *string `json:"url,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Modified []string `json:"modified,omitempty"` +} + +func (p PushEventCommit) String() string { + return Stringify(p) +} + +//PullRequestEvent represents the payload delivered by PullRequestEvent webhook +type PullRequestEvent struct { + Action *string `json:"action,omitempty"` + Number *int `json:"number,omitempty"` + PullRequest *PullRequest `json:"pull_request,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// IssueActivityEvent represents the payload delivered by Issue webhook +type IssueActivityEvent struct { + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// IssueCommentEvent represents the payload delivered by IssueComment webhook +// +// This webhook also gets fired for comments on pull requests +type IssueCommentEvent struct { + Action *string `json:"action,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Comment *IssueComment `json:"comment,omitempty"` + Repo *Repository `json:"repository,omitempty"` + Sender *User `json:"sender,omitempty"` +} + +// ListEvents drinks from the firehose of all public events across GitHub. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events +func (s *ActivityService) ListEvents(opt *ListOptions) ([]Event, *Response, error) { + u, err := addOptions("events", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListRepositoryEvents lists events for a repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-repository-events +func (s *ActivityService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/events", owner, repo) + 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListIssueEventsForRepository lists issue events for a repository. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-issue-events-for-a-repository +func (s *ActivityService) ListIssueEventsForRepository(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsForRepoNetwork lists public events for a network of repositories. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories +func (s *ActivityService) ListEventsForRepoNetwork(owner, repo string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("networks/%v/%v/events", owner, repo) + 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsForOrganization lists public events for an organization. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-public-events-for-an-organization +func (s *ActivityService) ListEventsForOrganization(org string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("orgs/%v/events", 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-performed-by-a-user +func (s *ActivityService) ListEventsPerformedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/events/public", user) + } else { + u = fmt.Sprintf("users/%v/events", user) + } + 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListEventsRecievedByUser lists the events recieved by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received +func (s *ActivityService) ListEventsRecievedByUser(user string, publicOnly bool, opt *ListOptions) ([]Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/received_events/public", user) + } else { + u = fmt.Sprintf("users/%v/received_events", user) + } + 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} + +// ListUserEventsForOrganization provides the user’s organization dashboard. You +// must be authenticated as the user to view this. +// +// GitHub API docs: http://developer.github.com/v3/activity/events/#list-events-for-an-organization +func (s *ActivityService) ListUserEventsForOrganization(org, user string, opt *ListOptions) ([]Event, *Response, error) { + u := fmt.Sprintf("users/%v/events/orgs/%v", user, 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 + } + + events := new([]Event) + resp, err := s.client.Do(req, events) + if err != nil { + return nil, resp, err + } + + return *events, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go new file mode 100644 index 00000000000..1541f5e9fe6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_events_test.go @@ -0,0 +1,305 @@ +// 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" +) + +func TestActivityService_ListEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/events", 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} + events, _, err := client.Activity.ListEvents(opt) + if err != nil { + t.Errorf("Activities.ListEvents returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/events", 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} + events, _, err := client.Activity.ListRepositoryEvents("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListRepositoryEvents returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListRepositoryEvents_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListRepositoryEvents("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListIssueEventsForRepository(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", 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} + events, _, err := client.Activity.ListIssueEventsForRepository("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListIssueEventsForRepository returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListIssueEventsForRepository returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListIssueEventsForRepository_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListIssueEventsForRepository("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForRepoNetwork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/networks/o/r/events", 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} + events, _, err := client.Activity.ListEventsForRepoNetwork("o", "r", opt) + if err != nil { + t.Errorf("Activities.ListEventsForRepoNetwork returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForRepoNetwork returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForRepoNetwork_invalidOwner(t *testing.T) { + _, _, err := client.Activity.ListEventsForRepoNetwork("%", "%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsForOrganization(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/events", 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} + events, _, err := client.Activity.ListEventsForOrganization("o", opt) + if err != nil { + t.Errorf("Activities.ListEventsForOrganization returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsForOrganization_invalidOrg(t *testing.T) { + _, _, err := client.Activity.ListEventsForOrganization("%", nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsPerformedByUser_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events", 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} + events, _, err := client.Activity.ListEventsPerformedByUser("u", false, opt) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_publicOnly(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsPerformedByUser("u", true, nil) + if err != nil { + t.Errorf("Events.ListPerformedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListPerformedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsPerformedByUser_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListEventsPerformedByUser("%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListEventsRecievedByUser_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events", 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} + events, _, err := client.Activity.ListEventsRecievedByUser("u", false, opt) + if err != nil { + t.Errorf("Events.ListRecievedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListRecievedUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsRecievedByUser_publicOnly(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/received_events/public", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"},{"id":"2"}]`) + }) + + events, _, err := client.Activity.ListEventsRecievedByUser("u", true, nil) + if err != nil { + t.Errorf("Events.ListRecievedByUser returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Events.ListRecievedByUser returned %+v, want %+v", events, want) + } +} + +func TestActivityService_ListEventsRecievedByUser_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListEventsRecievedByUser("%", false, nil) + testURLParseError(t, err) +} + +func TestActivityService_ListUserEventsForOrganization(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/events/orgs/o", 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} + events, _, err := client.Activity.ListUserEventsForOrganization("o", "u", opt) + if err != nil { + t.Errorf("Activities.ListUserEventsForOrganization returned error: %v", err) + } + + want := []Event{{ID: String("1")}, {ID: String("2")}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Activities.ListUserEventsForOrganization returned %+v, want %+v", events, want) + } +} + +func TestActivity_EventPayload_typed(t *testing.T) { + raw := []byte(`{"type": "PushEvent","payload":{"push_id": 1}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := &PushEvent{PushID: Int(1)} + if !reflect.DeepEqual(event.Payload(), want) { + t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want) + } +} + +// TestEvent_Payload_untyped checks that unrecognized events are parsed to an +// interface{} value (instead of being discarded or throwing an error), for +// forward compatibility with new event types. +func TestActivity_EventPayload_untyped(t *testing.T) { + raw := []byte(`{"type": "UnrecognizedEvent","payload":{"field": "val"}}`) + var event *Event + if err := json.Unmarshal(raw, &event); err != nil { + t.Fatalf("Unmarshal Event returned error: %v", err) + } + + want := map[string]interface{}{"field": "val"} + if !reflect.DeepEqual(event.Payload(), want) { + t.Errorf("Event Payload returned %+v, want %+v", event.Payload(), want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go new file mode 100644 index 00000000000..786df98a9eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications.go @@ -0,0 +1,224 @@ +// Copyright 2014 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" + "time" +) + +// Notification identifies a GitHub notification for a user. +type Notification struct { + ID *string `json:"id,omitempty"` + Repository *Repository `json:"repository,omitempty"` + Subject *NotificationSubject `json:"subject,omitempty"` + + // Reason identifies the event that triggered the notification. + // + // GitHub API Docs: https://developer.github.com/v3/activity/notifications/#notification-reasons + Reason *string `json:"reason,omitempty"` + + Unread *bool `json:"unread,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + LastReadAt *time.Time `json:"last_read_at,omitempty"` + URL *string `json:"url,omitempty"` +} + +// NotificationSubject identifies the subject of a notification. +type NotificationSubject struct { + Title *string `json:"title,omitempty"` + URL *string `json:"url,omitempty"` + LatestCommentURL *string `json:"latest_comment_url,omitempty"` + Type *string `json:"type,omitempty"` +} + +// NotificationListOptions specifies the optional parameters to the +// ActivityService.ListNotifications method. +type NotificationListOptions struct { + All bool `url:"all,omitempty"` + Participating bool `url:"participating,omitempty"` + Since time.Time `url:"since,omitempty"` +} + +// ListNotifications lists all notifications for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications +func (s *ActivityService) ListNotifications(opt *NotificationListOptions) ([]Notification, *Response, error) { + u := fmt.Sprintf("notifications") + 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 + } + + var notifications []Notification + resp, err := s.client.Do(req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, err +} + +// ListRepositoryNotifications lists all notifications in a given repository +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository +func (s *ActivityService) ListRepositoryNotifications(owner, repo string, opt *NotificationListOptions) ([]Notification, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + 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 + } + + var notifications []Notification + resp, err := s.client.Do(req, ¬ifications) + if err != nil { + return nil, resp, err + } + + return notifications, resp, err +} + +type markReadOptions struct { + LastReadAt time.Time `url:"last_read_at,omitempty"` +} + +// MarkNotificationsRead marks all notifications up to lastRead as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-as-read +func (s *ActivityService) MarkNotificationsRead(lastRead time.Time) (*Response, error) { + u := fmt.Sprintf("notifications") + u, err := addOptions(u, markReadOptions{lastRead}) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// MarkRepositoryNotificationsRead marks all notifications up to lastRead in +// the specified repository as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository +func (s *ActivityService) MarkRepositoryNotificationsRead(owner, repo string, lastRead time.Time) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) + u, err := addOptions(u, markReadOptions{lastRead}) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// GetThread gets the specified notification thread. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread +func (s *ActivityService) GetThread(id string) (*Notification, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + notification := new(Notification) + resp, err := s.client.Do(req, notification) + if err != nil { + return nil, resp, err + } + + return notification, resp, err +} + +// MarkThreadRead marks the specified thread as read. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read +func (s *ActivityService) MarkThreadRead(id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v", id) + + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// GetThreadSubscription checks to see if the authenticated user is subscribed +// to a thread. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription +func (s *ActivityService) GetThreadSubscription(id string) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// SetThreadSubscription sets the subscription for the specified thread for the +// authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription +func (s *ActivityService) SetThreadSubscription(id string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// DeleteThreadSubscription deletes the subscription for the specified thread +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription +func (s *ActivityService) DeleteThreadSubscription(id string) (*Response, error) { + u := fmt.Sprintf("notifications/threads/%v/subscription", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go new file mode 100644 index 00000000000..829e118e998 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_notifications_test.go @@ -0,0 +1,203 @@ +// Copyright 2014 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 TestActivityService_ListNotification(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "all": "true", + "participating": "true", + "since": "2006-01-02T15:04:05Z", + }) + + fmt.Fprint(w, `[{"id":"1", "subject":{"title":"t"}}]`) + }) + + opt := &NotificationListOptions{ + All: true, + Participating: true, + Since: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), + } + notifications, _, err := client.Activity.ListNotifications(opt) + if err != nil { + t.Errorf("Activity.ListNotifications returned error: %v", err) + } + + want := []Notification{{ID: String("1"), Subject: &NotificationSubject{Title: String("t")}}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_ListRepositoryNotification(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":"1"}]`) + }) + + notifications, _, err := client.Activity.ListRepositoryNotifications("o", "r", nil) + if err != nil { + t.Errorf("Activity.ListRepositoryNotifications returned error: %v", err) + } + + want := []Notification{{ID: String("1")}} + if !reflect.DeepEqual(notifications, want) { + t.Errorf("Activity.ListRepositoryNotifications returned %+v, want %+v", notifications, want) + } +} + +func TestActivityService_MarkNotificationsRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testFormValues(t, r, values{ + "last_read_at": "2006-01-02T15:04:05Z", + }) + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkNotificationsRead(time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_MarkRepositoryNotificationsRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/notifications", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + testFormValues(t, r, values{ + "last_read_at": "2006-01-02T15:04:05Z", + }) + + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkRepositoryNotificationsRead("o", "r", time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)) + if err != nil { + t.Errorf("Activity.MarkRepositoryNotificationsRead returned error: %v", err) + } +} + +func TestActivityService_GetThread(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":"1"}`) + }) + + notification, _, err := client.Activity.GetThread("1") + if err != nil { + t.Errorf("Activity.GetThread returned error: %v", err) + } + + want := &Notification{ID: String("1")} + if !reflect.DeepEqual(notification, want) { + t.Errorf("Activity.GetThread returned %+v, want %+v", notification, want) + } +} + +func TestActivityService_MarkThreadRead(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + w.WriteHeader(http.StatusResetContent) + }) + + _, err := client.Activity.MarkThreadRead("1") + if err != nil { + t.Errorf("Activity.MarkThreadRead returned error: %v", err) + } +} + +func TestActivityService_GetThreadSubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetThreadSubscription("1") + if err != nil { + t.Errorf("Activity.GetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_SetThreadSubscription(t *testing.T) { + setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetThreadSubscription("1", input) + if err != nil { + t.Errorf("Activity.SetThreadSubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetThreadSubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteThreadSubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/notifications/threads/1/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteThreadSubscription("1") + if err != nil { + t.Errorf("Activity.DeleteThreadSubscription returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go new file mode 100644 index 00000000000..982f24d7170 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_star.go @@ -0,0 +1,114 @@ +// 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" + +// ListStargazers lists people who have starred the specified repo. +// +// GitHub API Docs: https://developer.github.com/v3/activity/starring/#list-stargazers +func (s *ActivityService) ListStargazers(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/stargazers", owner, repo) + 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 + } + + stargazers := new([]User) + resp, err := s.client.Do(req, stargazers) + if err != nil { + return nil, resp, err + } + + return *stargazers, resp, err +} + +// ActivityListStarredOptions specifies the optional parameters to the +// ActivityService.ListStarred method. +type ActivityListStarredOptions struct { + // How to sort the repository list. Possible values are: created, updated, + // pushed, full_name. Default is "full_name". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Possible values are: asc, desc. + // Default is "asc" when sort is "full_name", otherwise default is "desc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// ListStarred lists all the repos starred by a user. Passing the empty string +// will list the starred repositories for the authenticated user. +// +// 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) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/starred", user) + } else { + u = "user/starred" + } + 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 + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// IsStarred checks if a repository is starred by authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository +func (s *ActivityService) IsStarred(owner, repo string) (bool, *Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Star a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository +func (s *ActivityService) Star(owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// Unstar a repository as the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository +func (s *ActivityService) Unstar(owner, repo string) (*Response, error) { + u := fmt.Sprintf("user/starred/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go new file mode 100644 index 00000000000..ae33b93cffa --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_star_test.go @@ -0,0 +1,167 @@ +// 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 TestActivityService_ListStargazers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stargazers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + stargazers, _, err := client.Activity.ListStargazers("o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListStargazers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(stargazers, want) { + t.Errorf("Activity.ListStargazers returned %+v, want %+v", stargazers, want) + } +} + +func TestActivityService_ListStarred_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + repos, _, err := client.Activity.ListStarred("", nil) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/starred", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "created", + "direction": "asc", + "page": "2", + }) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &ActivityListStarredOptions{"created", "asc", ListOptions{Page: 2}} + repos, _, err := client.Activity.ListStarred("u", opt) + if err != nil { + t.Errorf("Activity.ListStarred returned error: %v", err) + } + + want := []Repository{{ID: Int(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Activity.ListStarred returned %+v, want %+v", repos, want) + } +} + +func TestActivityService_ListStarred_invalidUser(t *testing.T) { + _, _, err := client.Activity.ListStarred("%", nil) + testURLParseError(t, err) +} + +func TestActivityService_IsStarred_hasStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + star, _, err := client.Activity.IsStarred("o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := true; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_noStar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + star, _, err := client.Activity.IsStarred("o", "r") + if err != nil { + t.Errorf("Activity.IsStarred returned error: %v", err) + } + if want := false; star != want { + t.Errorf("Activity.IsStarred returned %+v, want %+v", star, want) + } +} + +func TestActivityService_IsStarred_invalidID(t *testing.T) { + _, _, err := client.Activity.IsStarred("%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Star(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Activity.Star("o", "r") + if err != nil { + t.Errorf("Activity.Star returned error: %v", err) + } +} + +func TestActivityService_Star_invalidID(t *testing.T) { + _, err := client.Activity.Star("%", "%") + testURLParseError(t, err) +} + +func TestActivityService_Unstar(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/starred/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Activity.Unstar("o", "r") + if err != nil { + t.Errorf("Activity.Unstar returned error: %v", err) + } +} + +func TestActivityService_Unstar_invalidID(t *testing.T) { + _, err := client.Activity.Unstar("%", "%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go new file mode 100644 index 00000000000..150cf66cb1d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching.go @@ -0,0 +1,131 @@ +// Copyright 2014 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" + +// Subscription identifies a repository or thread subscription. +type Subscription struct { + Subscribed *bool `json:"subscribed,omitempty"` + Ignored *bool `json:"ignored,omitempty"` + Reason *string `json:"reason,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + URL *string `json:"url,omitempty"` + + // only populated for repository subscriptions + RepositoryURL *string `json:"repository_url,omitempty"` + + // only populated for thread subscriptions + ThreadURL *string `json:"thread_url,omitempty"` +} + +// ListWatchers lists watchers of a particular repo. +// +// GitHub API Docs: http://developer.github.com/v3/activity/watching/#list-watchers +func (s *ActivityService) ListWatchers(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscribers", owner, repo) + 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 + } + + watchers := new([]User) + resp, err := s.client.Do(req, watchers) + if err != nil { + return nil, resp, err + } + + return *watchers, resp, err +} + +// ListWatched lists the repositories the specified user is watching. Passing +// the empty string will fetch watched repos for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched +func (s *ActivityService) ListWatched(user string) ([]Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/subscriptions", user) + } else { + u = "user/subscriptions" + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + watched := new([]Repository) + resp, err := s.client.Do(req, watched) + if err != nil { + return nil, resp, err + } + + return *watched, resp, err +} + +// GetRepositorySubscription returns the subscription for the specified +// repository for the authenticated user. If the authenticated user is not +// watching the repository, a nil Subscription is returned. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#get-a-repository-subscription +func (s *ActivityService) GetRepositorySubscription(owner, repo string) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + // if it's just a 404, don't return that as an error + _, err = parseBoolResponse(err) + return nil, resp, err + } + + return sub, resp, err +} + +// SetRepositorySubscription sets the subscription for the specified repository +// for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#set-a-repository-subscription +func (s *ActivityService) SetRepositorySubscription(owner, repo string, subscription *Subscription) (*Subscription, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + + req, err := s.client.NewRequest("PUT", u, subscription) + if err != nil { + return nil, nil, err + } + + sub := new(Subscription) + resp, err := s.client.Do(req, sub) + if err != nil { + return nil, resp, err + } + + return sub, resp, err +} + +// DeleteRepositorySubscription deletes the subscription for the specified +// repository for the authenticated user. +// +// GitHub API Docs: https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription +func (s *ActivityService) DeleteRepositorySubscription(owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/subscription", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go new file mode 100644 index 00000000000..8046ee21732 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/activity_watching_test.go @@ -0,0 +1,177 @@ +// Copyright 2014 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 TestActivityService_ListWatchers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscribers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + watchers, _, err := client.Activity.ListWatchers("o", "r", &ListOptions{Page: 2}) + if err != nil { + t.Errorf("Activity.ListWatchers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(watchers, want) { + t.Errorf("Activity.ListWatchers returned %+v, want %+v", watchers, want) + } +} + +func TestActivityService_ListWatched_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched("") + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_ListWatched_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/subscriptions", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + watched, _, err := client.Activity.ListWatched("u") + if err != nil { + t.Errorf("Activity.ListWatched returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(watched, want) { + t.Errorf("Activity.ListWatched returned %+v, want %+v", watched, want) + } +} + +func TestActivityService_GetRepositorySubscription_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"subscribed":true}`) + }) + + sub, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Subscribed: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + sub, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.GetRepositorySubscription returned error: %v", err) + } + + var want *Subscription + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.GetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_GetRepositorySubscription_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusBadRequest) + }) + + _, _, err := client.Activity.GetRepositorySubscription("o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } +} + +func TestActivityService_SetRepositorySubscription(t *testing.T) { + setup() + defer teardown() + + input := &Subscription{Subscribed: Bool(true)} + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + v := new(Subscription) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `{"ignored":true}`) + }) + + sub, _, err := client.Activity.SetRepositorySubscription("o", "r", input) + if err != nil { + t.Errorf("Activity.SetRepositorySubscription returned error: %v", err) + } + + want := &Subscription{Ignored: Bool(true)} + if !reflect.DeepEqual(sub, want) { + t.Errorf("Activity.SetRepositorySubscription returned %+v, want %+v", sub, want) + } +} + +func TestActivityService_DeleteRepositorySubscription(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/subscription", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Activity.DeleteRepositorySubscription("o", "r") + if err != nil { + t.Errorf("Activity.DeleteRepositorySubscription returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/doc.go b/Godeps/_workspace/src/github.com/google/go-github/github/doc.go new file mode 100644 index 00000000000..9e48242d219 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/doc.go @@ -0,0 +1,137 @@ +// 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 provides a client for using the GitHub API. + +Construct a new GitHub client, then use the various services on the client to +access different parts of the GitHub API. For example: + + client := github.NewClient(nil) + + // list all organizations for user "willnorris" + orgs, _, err := client.Organizations.List("willnorris", nil) + +Set optional parameters for an API method by passing an Options object. + + // list recently updated repositories for org "github" + opt := &github.RepositoryListByOrgOptions{Sort: "updated"} + repos, _, err := client.Repositories.ListByOrg("github", opt) + +The services of a client divide the API into logical chunks and correspond to +the structure of the GitHub API documentation at +http://developer.github.com/v3/. + +Authentication + +The go-github library does not directly handle authentication. Instead, when +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 golang.org/x/oauth2 +library, but you can always use any other library that provides an http.Client. +If you have an OAuth2 access token (for example, a personal API token), you can +use it with the oauth2 library using: + + import "golang.org/x/oauth2" + + // tokenSource is an oauth2.TokenSource which returns a static access token + type tokenSource struct { + token *oauth2.Token + } + + // Token implements the oauth2.TokenSource interface + func (t *tokenSource) Token() (*oauth2.Token, error){ + return t.token, nil + } + + func main() { + ts := &tokenSource{ + &oauth2.Token{AccessToken: "... your access token ..."}, + } + + tc := oauth2.NewClient(oauth2.NoContext, ts) + + client := github.NewClient(tc) + + // list all repositories for the authenticated user + repos, _, err := client.Repositories.List("", nil) + } + +Note that when using an authenticated Client, all calls made by the client will +include the specified OAuth token. Therefore, authenticated clients should +almost never be shared between different users. + +Rate Limiting + +GitHub imposes a rate limit on all API clients. Unauthenticated clients are +limited to 60 requests per hour, while authenticated clients can make up to +5,000 requests per hour. To receive the higher rate limit when making calls +that are not issued on behalf of a user, use the +UnauthenticatedRateLimitedTransport. + +The Rate field on a client tracks the rate limit information based on the most +recent API call. This is updated on every call, but may be out of date if it's +been some time since the last API call and other clients have made subsequent +requests since then. You can always call RateLimit() directly to get the most +up-to-date rate limit data for the client. + +Learn more about GitHub rate limiting at +http://developer.github.com/v3/#rate-limiting. + +Conditional Requests + +The GitHub API has good support for conditional requests which will help +prevent you from burning through your rate limit, as well as help speed up your +application. go-github does not handle conditional requests directly, but is +instead designed to work with a caching http.Transport. We recommend using +https://github.com/gregjones/httpcache, which can be used in conjuction with +https://github.com/sourcegraph/apiproxy to provide additional flexibility and +control of caching rules. + +Learn more about GitHub conditional requests at +https://developer.github.com/v3/#conditional-requests. + +Creating and Updating Resources + +All structs for GitHub resources use pointer values for all non-repeated fields. +This allows distinguishing between unset fields and those set to a zero-value. +Helper functions have been provided to easily create these pointers for string, +bool, and int values. For example: + + // create a new private repository named "foo" + repo := &github.Repository{ + Name: github.String("foo"), + Private: github.Bool(true), + } + client.Repositories.Create("", repo) + +Users who have worked with protocol buffers should find this pattern familiar. + +Pagination + +All requests for resource collections (repos, pull requests, issues, etc) +support pagination. Pagination options are described in the +ListOptions struct and passed to the list methods directly or as an +embedded type of a more specific list options struct (for example +PullRequestListOptions). Pages information is available via Response struct. + + opt := &github.RepositoryListByOrgOptions{ + ListOptions: github.ListOptions{PerPage: 10}, + } + // get all pages of results + var allRepos []github.Repository + for { + repos, resp, err := client.Repositories.ListByOrg("github", opt) + if err != nil { + return err + } + allRepos = append(allRepos, repos...) + if resp.NextPage == 0 { + break + } + opt.ListOptions.Page = resp.NextPage + } + +*/ +package github diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gists.go b/Godeps/_workspace/src/github.com/google/go-github/github/gists.go new file mode 100644 index 00000000000..14bf5dc50a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gists.go @@ -0,0 +1,281 @@ +// 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" + "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"` + Owner *User `json:"owner,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"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (g Gist) String() string { + return Stringify(g) +} + +// 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 *string `json:"filename,omitempty"` + RawURL *string `json:"raw_url,omitempty"` + Content *string `json:"content,omitempty"` +} + +func (g GistFile) String() string { + return Stringify(g) +} + +// 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 `url:"since,omitempty"` + + ListOptions +} + +// 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, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/gists", user) + } else { + u = "gists" + } + 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 + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, err +} + +// ListAll lists all public gists. +// +// GitHub API docs: http://developer.github.com/v3/gists/#list-gists +func (s *GistsService) ListAll(opt *GistListOptions) ([]Gist, *Response, error) { + u, err := addOptions("gists/public", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, 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, *Response, error) { + u, err := addOptions("gists/starred", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gists := new([]Gist) + resp, err := s.client.Do(req, gists) + if err != nil { + return nil, resp, err + } + + return *gists, resp, 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, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + 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 +} + +// Get 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. +// +// GitHub API docs: http://developer.github.com/v3/gists/#create-a-gist +func (s *GistsService) Create(gist *Gist) (*Gist, *Response, error) { + u := "gists" + req, err := s.client.NewRequest("POST", u, gist) + if err != nil { + return nil, nil, err + } + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, 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, *Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("PATCH", u, gist) + if err != nil { + return nil, nil, err + } + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} + +// Delete a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#delete-a-gist +func (s *GistsService) Delete(id string) (*Response, error) { + u := fmt.Sprintf("gists/%v", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// 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) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// 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) (*Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// IsStarred 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) IsStarred(id string) (bool, *Response, error) { + u := fmt.Sprintf("gists/%v/star", id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + starred, err := parseBoolResponse(err) + return starred, resp, err +} + +// Fork a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/#fork-a-gist +func (s *GistsService) Fork(id string) (*Gist, *Response, error) { + u := fmt.Sprintf("gists/%v/forks", id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + g := new(Gist) + resp, err := s.client.Do(req, g) + if err != nil { + return nil, resp, err + } + + return g, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go b/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go new file mode 100644 index 00000000000..c5c21bde669 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments.go @@ -0,0 +1,118 @@ +// 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" + "time" +) + +// GistComment represents a Gist comment. +type GistComment struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +func (g GistComment) String() string { + return Stringify(g) +} + +// ListComments lists all comments for a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#list-comments-on-a-gist +func (s *GistsService) ListComments(gistID string, opt *ListOptions) ([]GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + 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 + } + + comments := new([]GistComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment retrieves a single comment from a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#get-a-single-comment +func (s *GistsService) GetComment(gistID string, commentID int) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// CreateComment creates a comment for a gist. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#create-a-comment +func (s *GistsService) CreateComment(gistID string, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments", gistID) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment edits an existing gist comment. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#edit-a-comment +func (s *GistsService) EditComment(gistID string, commentID int, comment *GistComment) (*GistComment, *Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(GistComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a gist comment. +// +// GitHub API docs: http://developer.github.com/v3/gists/comments/#delete-a-comment +func (s *GistsService) DeleteComment(gistID string, commentID int) (*Response, error) { + u := fmt.Sprintf("gists/%v/comments/%v", gistID, commentID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go new file mode 100644 index 00000000000..b2bbf23f706 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gists_comments_test.go @@ -0,0 +1,155 @@ +// 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" +) + +func TestGistsService_ListComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id": 1}]`) + }) + + opt := &ListOptions{Page: 2} + comments, _, err := client.Gists.ListComments("1", opt) + + if err != nil { + t.Errorf("Gists.Comments returned error: %v", err) + } + + want := []GistComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Gists.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestGistsService_ListComments_invalidID(t *testing.T) { + _, _, err := client.Gists.ListComments("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id": 1}`) + }) + + comment, _, err := client.Gists.GetComment("1", 2) + + if err != nil { + t.Errorf("Gists.GetComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_GetComment_invalidID(t *testing.T) { + _, _, err := client.Gists.GetComment("%", 1) + testURLParseError(t, err) +} + +func TestGistsService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &GistComment{ID: Int(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + 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}`) + }) + + comment, _, err := client.Gists.CreateComment("1", input) + if err != nil { + t.Errorf("Gists.CreateComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_CreateComment_invalidID(t *testing.T) { + _, _, err := client.Gists.CreateComment("%", nil) + testURLParseError(t, err) +} + +func TestGistsService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &GistComment{ID: Int(1), Body: String("b")} + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + v := new(GistComment) + 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}`) + }) + + comment, _, err := client.Gists.EditComment("1", 2, input) + if err != nil { + t.Errorf("Gists.EditComment returned error: %v", err) + } + + want := &GistComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Gists.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestGistsService_EditComment_invalidID(t *testing.T) { + _, _, err := client.Gists.EditComment("%", 1, nil) + testURLParseError(t, err) +} + +func TestGistsService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gists/1/comments/2", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Gists.DeleteComment("1", 2) + if err != nil { + t.Errorf("Gists.Delete returned error: %v", err) + } +} + +func TestGistsService_DeleteComment_invalidID(t *testing.T) { + _, err := client.Gists.DeleteComment("%", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go new file mode 100644 index 00000000000..573120129ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gists_test.go @@ -0,0 +1,411 @@ +// 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_specifiedUser(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{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_authenticatedUser(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{{ID: String("1")}} + if !reflect.DeepEqual(gists, want) { + t.Errorf("Gists.List returned %+v, want %+v", gists, want) + } +} + +func TestGistsService_List_invalidUser(t *testing.T) { + _, _, err := client.Gists.List("%", nil) + testURLParseError(t, err) +} + +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{{ID: String("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{{ID: String("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: String("1")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Get returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Get_invalidID(t *testing.T) { + _, _, err := client.Gists.Get("%") + 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) { + setup() + defer teardown() + + input := &Gist{ + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Content: String("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: String("1"), + Description: String("Gist description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("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: String("New description"), + Files: map[GistFilename]GistFile{ + "new.txt": {Content: String("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: String("1"), + Description: String("new description"), + Public: Bool(false), + Files: map[GistFilename]GistFile{ + "test.txt": {Filename: String("test.txt")}, + "new.txt": {Filename: String("new.txt")}, + }, + } + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Edit returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Edit_invalidID(t *testing.T) { + _, _, err := client.Gists.Edit("%", nil) + testURLParseError(t, err) +} + +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_Delete_invalidID(t *testing.T) { + _, err := client.Gists.Delete("%") + testURLParseError(t, 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_Star_invalidID(t *testing.T) { + _, err := client.Gists.Star("%") + testURLParseError(t, 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_Unstar_invalidID(t *testing.T) { + _, err := client.Gists.Unstar("%") + testURLParseError(t, err) +} + +func TestGistsService_IsStarred_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.IsStarred("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_IsStarred_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.IsStarred("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_IsStarred_invalidID(t *testing.T) { + _, _, err := client.Gists.IsStarred("%") + testURLParseError(t, err) +} + +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: String("2")} + if !reflect.DeepEqual(gist, want) { + t.Errorf("Gists.Fork returned %+v, want %+v", gist, want) + } +} + +func TestGistsService_Fork_invalidID(t *testing.T) { + _, _, err := client.Gists.Fork("%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git.go b/Godeps/_workspace/src/github.com/google/go-github/github/git.go new file mode 100644 index 00000000000..a80e55b9bb6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git.go @@ -0,0 +1,14 @@ +// 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 + +// GitService handles communication with the git data related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/git/ +type GitService struct { + client *Client +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go new file mode 100644 index 00000000000..133780b1764 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs.go @@ -0,0 +1,47 @@ +// 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" + +// Blob represents a blob object. +type Blob struct { + Content *string `json:"content,omitempty"` + Encoding *string `json:"encoding,omitempty"` + SHA *string `json:"sha,omitempty"` + Size *int `json:"size,omitempty"` + URL *string `json:"url,omitempty"` +} + +// GetBlob fetchs a blob from a repo given a SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/blobs/#get-a-blob +func (s *GitService) GetBlob(owner string, repo string, sha string) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + blob := new(Blob) + resp, err := s.client.Do(req, blob) + return blob, resp, err +} + +// CreateBlob creates a blob object. +// +// GitHub API docs: http://developer.github.com/v3/git/blobs/#create-a-blob +func (s *GitService) CreateBlob(owner string, repo string, blob *Blob) (*Blob, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/blobs", owner, repo) + req, err := s.client.NewRequest("POST", u, blob) + if err != nil { + return nil, nil, err + } + + t := new(Blob) + resp, err := s.client.Do(req, t) + return t, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go new file mode 100644 index 00000000000..994549f2c97 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_blobs_test.go @@ -0,0 +1,92 @@ +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" +) + +func TestGitService_GetBlob(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/blobs/s", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content" + }`) + }) + + blob, _, err := client.Git.GetBlob("o", "r", "s") + if err != nil { + t.Errorf("Git.GetBlob returned error: %v", err) + } + + want := Blob{ + SHA: String("s"), + Content: String("blob content"), + } + + if !reflect.DeepEqual(*blob, want) { + t.Errorf("Blob.Get returned %+v, want %+v", *blob, want) + } +} + +func TestGitService_GetBlob_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetBlob("%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_CreateBlob(t *testing.T) { + setup() + defer teardown() + + input := &Blob{ + SHA: String("s"), + Content: String("blob content"), + Encoding: String("utf-8"), + Size: Int(12), + } + + mux.HandleFunc("/repos/o/r/git/blobs", func(w http.ResponseWriter, r *http.Request) { + v := new(Blob) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := input + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateBlob request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "s", + "content": "blob content", + "encoding": "utf-8", + "size": 12 + }`) + }) + + blob, _, err := client.Git.CreateBlob("o", "r", input) + if err != nil { + t.Errorf("Git.CreateBlob returned error: %v", err) + } + + want := input + + if !reflect.DeepEqual(*blob, *want) { + t.Errorf("Git.CreateBlob returned %+v, want %+v", *blob, *want) + } +} + +func TestGitService_CreateBlob_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateBlob("%", "%", &Blob{}) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go new file mode 100644 index 00000000000..6584b777e0b --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_commits.go @@ -0,0 +1,112 @@ +// 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" + "time" +) + +// Commit represents a GitHub commit. +type Commit struct { + SHA *string `json:"sha,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *Tree `json:"tree,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Stats *CommitStats `json:"stats,omitempty"` + URL *string `json:"url,omitempty"` + + // CommentCount is the number of GitHub comments on the commit. This + // is only populated for requests that fetch GitHub data like + // Pulls.ListCommits, Repositories.ListCommits, etc. + CommentCount *int `json:"comment_count,omitempty"` +} + +func (c Commit) String() string { + return Stringify(c) +} + +// CommitAuthor represents the author or committer of a commit. The commit +// author may not correspond to a GitHub User. +type CommitAuthor struct { + Date *time.Time `json:"date,omitempty"` + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` +} + +func (c CommitAuthor) String() string { + return Stringify(c) +} + +// GetCommit fetchs the Commit object for a given SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/commits/#get-a-commit +func (s *GitService) GetCommit(owner string, repo string, sha string) (*Commit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/commits/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// createCommit represents the body of a CreateCommit request. +type createCommit struct { + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` + Message *string `json:"message,omitempty"` + Tree *string `json:"tree,omitempty"` + Parents []string `json:"parents,omitempty"` +} + +// CreateCommit creates a new commit in a repository. +// +// The commit.Committer is optional and will be filled with the commit.Author +// data if omitted. If the commit.Author is omitted, it will be filled in with +// the authenticated user’s information and the current date. +// +// GitHub API docs: http://developer.github.com/v3/git/commits/#create-a-commit +func (s *GitService) CreateCommit(owner string, repo string, commit *Commit) (*Commit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo) + + body := &createCommit{} + if commit != nil { + parents := make([]string, len(commit.Parents)) + for i, parent := range commit.Parents { + parents[i] = *parent.SHA + } + + body = &createCommit{ + Author: commit.Author, + Committer: commit.Committer, + Message: commit.Message, + Tree: commit.Tree.SHA, + Parents: parents, + } + } + + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + c := new(Commit) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go new file mode 100644 index 00000000000..538f5236068 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_commits_test.go @@ -0,0 +1,82 @@ +// 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" +) + +func TestGitService_GetCommit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"sha":"s","message":"m","author":{"name":"n"}}`) + }) + + commit, _, err := client.Git.GetCommit("o", "r", "s") + if err != nil { + t.Errorf("Git.GetCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s"), Message: String("m"), Author: &CommitAuthor{Name: String("n")}} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.GetCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_GetCommit_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetCommit("%", "%", "%") + testURLParseError(t, err) +} + +func TestGitService_CreateCommit(t *testing.T) { + setup() + defer teardown() + + input := &Commit{ + Message: String("m"), + Tree: &Tree{SHA: String("t")}, + Parents: []Commit{{SHA: String("p")}}, + } + + mux.HandleFunc("/repos/o/r/git/commits", func(w http.ResponseWriter, r *http.Request) { + v := new(createCommit) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + + want := &createCommit{ + Message: input.Message, + Tree: String("t"), + Parents: []string{"p"}, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Request body = %+v, want %+v", v, want) + } + fmt.Fprint(w, `{"sha":"s"}`) + }) + + commit, _, err := client.Git.CreateCommit("o", "r", input) + if err != nil { + t.Errorf("Git.CreateCommit returned error: %v", err) + } + + want := &Commit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Git.CreateCommit returned %+v, want %+v", commit, want) + } +} + +func TestGitService_CreateCommit_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateCommit("%", "%", nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go new file mode 100644 index 00000000000..3d2f6c8a34d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_refs.go @@ -0,0 +1,162 @@ +// 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" + "strings" +) + +// Reference represents a GitHub reference. +type Reference struct { + Ref *string `json:"ref"` + URL *string `json:"url"` + Object *GitObject `json:"object"` +} + +func (r Reference) String() string { + return Stringify(r) +} + +// GitObject represents a Git object. +type GitObject struct { + Type *string `json:"type"` + SHA *string `json:"sha"` + URL *string `json:"url"` +} + +func (o GitObject) String() string { + return Stringify(o) +} + +// createRefRequest represents the payload for creating a reference. +type createRefRequest struct { + Ref *string `json:"ref"` + SHA *string `json:"sha"` +} + +// updateRefRequest represents the payload for updating a reference. +type updateRefRequest struct { + SHA *string `json:"sha"` + Force *bool `json:"force"` +} + +// GetRef fetches the Reference object for a given Git ref. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#get-a-reference +func (s *GitService) GetRef(owner string, repo string, ref string) (*Reference, *Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// ReferenceListOptions specifies optional parameters to the +// GitService.ListRefs method. +type ReferenceListOptions struct { + Type string `url:"-"` + + ListOptions +} + +// ListRefs lists all refs in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#get-all-references +func (s *GitService) ListRefs(owner, repo string, opt *ReferenceListOptions) ([]Reference, *Response, error) { + var u string + if opt != nil && opt.Type != "" { + u = fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, opt.Type) + } else { + u = fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + } + 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 + } + + var rs []Reference + resp, err := s.client.Do(req, &rs) + if err != nil { + return nil, resp, err + } + + return rs, resp, err +} + +// CreateRef creates a new ref in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#create-a-reference +func (s *GitService) CreateRef(owner string, repo string, ref *Reference) (*Reference, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/refs", owner, repo) + req, err := s.client.NewRequest("POST", u, &createRefRequest{ + // back-compat with previous behavior that didn't require 'refs/' prefix + Ref: String("refs/" + strings.TrimPrefix(*ref.Ref, "refs/")), + SHA: ref.Object.SHA, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// UpdateRef updates an existing ref in a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#update-a-reference +func (s *GitService) UpdateRef(owner string, repo string, ref *Reference, force bool) (*Reference, *Response, error) { + refPath := strings.TrimPrefix(*ref.Ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, refPath) + req, err := s.client.NewRequest("PATCH", u, &updateRefRequest{ + SHA: ref.Object.SHA, + Force: &force, + }) + if err != nil { + return nil, nil, err + } + + r := new(Reference) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// DeleteRef deletes a ref from a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/refs/#delete-a-reference +func (s *GitService) DeleteRef(owner string, repo string, ref string) (*Response, error) { + ref = strings.TrimPrefix(ref, "refs/") + u := fmt.Sprintf("repos/%v/%v/git/refs/%v", owner, repo, ref) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go new file mode 100644 index 00000000000..e66bf54afa0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_refs_test.go @@ -0,0 +1,280 @@ +// 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" +) + +func TestGitService_GetRef(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.GetRef("o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.GetRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.GetRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + if _, _, err := client.Git.GetRef("o", "r", "heads/b"); err != nil { + t.Errorf("Git.GetRef returned error: %v", err) + } +} + +func TestGitService_ListRefs(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, ` + [ + { + "ref": "refs/heads/branchA", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchA", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }, + { + "ref": "refs/heads/branchB", + "url": "https://api.github.com/repos/o/r/git/refs/heads/branchB", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + } + ]`) + }) + + refs, _, err := client.Git.ListRefs("o", "r", nil) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []Reference{ + { + Ref: String("refs/heads/branchA"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchA"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + { + Ref: String("refs/heads/branchB"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/branchB"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }, + } + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_ListRefs_options(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"ref": "r"}]`) + }) + + opt := &ReferenceListOptions{Type: "t", ListOptions: ListOptions{Page: 2}} + refs, _, err := client.Git.ListRefs("o", "r", opt) + if err != nil { + t.Errorf("Git.ListRefs returned error: %v", err) + } + + want := []Reference{{Ref: String("r")}} + if !reflect.DeepEqual(refs, want) { + t.Errorf("Git.ListRefs returned %+v, want %+v", refs, want) + } +} + +func TestGitService_CreateRef(t *testing.T) { + setup() + defer teardown() + + args := &createRefRequest{ + Ref: String("refs/heads/b"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + } + + mux.HandleFunc("/repos/o/r/git/refs", func(w http.ResponseWriter, r *http.Request) { + v := new(createRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "POST") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.CreateRef("o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.CreateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.CreateRef("o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + }) + if err != nil { + t.Errorf("Git.CreateRef returned error: %v", err) + } +} + +func TestGitService_UpdateRef(t *testing.T) { + setup() + defer teardown() + + args := &updateRefRequest{ + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + Force: Bool(true), + } + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + v := new(updateRefRequest) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PATCH") + if !reflect.DeepEqual(v, args) { + t.Errorf("Request body = %+v, want %+v", v, args) + } + fmt.Fprint(w, ` + { + "ref": "refs/heads/b", + "url": "https://api.github.com/repos/o/r/git/refs/heads/b", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } + }`) + }) + + ref, _, err := client.Git.UpdateRef("o", "r", &Reference{ + Ref: String("refs/heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } + + want := &Reference{ + Ref: String("refs/heads/b"), + URL: String("https://api.github.com/repos/o/r/git/refs/heads/b"), + Object: &GitObject{ + Type: String("commit"), + SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd"), + URL: String("https://api.github.com/repos/o/r/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"), + }, + } + if !reflect.DeepEqual(ref, want) { + t.Errorf("Git.UpdateRef returned %+v, want %+v", ref, want) + } + + // without 'refs/' prefix + _, _, err = client.Git.UpdateRef("o", "r", &Reference{ + Ref: String("heads/b"), + Object: &GitObject{SHA: String("aa218f56b14c9653891f9e74264a383fa43fefbd")}, + }, true) + if err != nil { + t.Errorf("Git.UpdateRef returned error: %v", err) + } +} + +func TestGitService_DeleteRef(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/refs/heads/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Git.DeleteRef("o", "r", "refs/heads/b") + if err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } + + // without 'refs/' prefix + if _, err := client.Git.DeleteRef("o", "r", "heads/b"); err != nil { + t.Errorf("Git.DeleteRef returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go new file mode 100644 index 00000000000..7b53f5cc65d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_tags.go @@ -0,0 +1,73 @@ +// 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" +) + +// Tag represents a tag object. +type Tag struct { + Tag *string `json:"tag,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + Message *string `json:"message,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` + Object *GitObject `json:"object,omitempty"` +} + +// createTagRequest represents the body of a CreateTag request. This is mostly +// identical to Tag with the exception that the object SHA and Type are +// top-level fields, rather than being nested inside a JSON object. +type createTagRequest struct { + Tag *string `json:"tag,omitempty"` + Message *string `json:"message,omitempty"` + Object *string `json:"object,omitempty"` + Type *string `json:"type,omitempty"` + Tagger *CommitAuthor `json:"tagger,omitempty"` +} + +// GetTag fetchs a tag from a repo given a SHA. +// +// GitHub API docs: http://developer.github.com/v3/git/tags/#get-a-tag +func (s *GitService) GetTag(owner string, repo string, sha string) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags/%v", owner, repo, sha) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + tag := new(Tag) + resp, err := s.client.Do(req, tag) + return tag, resp, err +} + +// CreateTag creates a tag object. +// +// GitHub API docs: http://developer.github.com/v3/git/tags/#create-a-tag-object +func (s *GitService) CreateTag(owner string, repo string, tag *Tag) (*Tag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/tags", owner, repo) + + // convert Tag into a createTagRequest + tagRequest := &createTagRequest{ + Tag: tag.Tag, + Message: tag.Message, + Tagger: tag.Tagger, + } + if tag.Object != nil { + tagRequest.Object = tag.Object.SHA + tagRequest.Type = tag.Object.Type + } + + req, err := s.client.NewRequest("POST", u, tagRequest) + if err != nil { + return nil, nil, err + } + + t := new(Tag) + resp, err := s.client.Do(req, t) + return t, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go new file mode 100644 index 00000000000..fb41bf38efa --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_tags_test.go @@ -0,0 +1,68 @@ +// 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" +) + +func TestGitService_GetTag(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/tags/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.GetTag("o", "r", "s") + + if err != nil { + t.Errorf("Git.GetTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} + +func TestGitService_CreateTag(t *testing.T) { + setup() + defer teardown() + + input := &createTagRequest{Tag: String("t"), Object: String("s")} + + mux.HandleFunc("/repos/o/r/git/tags", func(w http.ResponseWriter, r *http.Request) { + v := new(createTagRequest) + 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, `{"tag": "t"}`) + }) + + tag, _, err := client.Git.CreateTag("o", "r", &Tag{ + Tag: input.Tag, + Object: &GitObject{SHA: input.Object}, + }) + if err != nil { + t.Errorf("Git.CreateTag returned error: %v", err) + } + + want := &Tag{Tag: String("t")} + if !reflect.DeepEqual(tag, want) { + t.Errorf("Git.GetTag returned %+v, want %+v", tag, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go new file mode 100644 index 00000000000..9efa4b3806b --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_trees.go @@ -0,0 +1,89 @@ +// 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" + +// Tree represents a GitHub tree. +type Tree struct { + SHA *string `json:"sha,omitempty"` + Entries []TreeEntry `json:"tree,omitempty"` +} + +func (t Tree) String() string { + return Stringify(t) +} + +// TreeEntry represents the contents of a tree structure. TreeEntry can +// represent either a blob, a commit (in the case of a submodule), or another +// tree. +type TreeEntry struct { + SHA *string `json:"sha,omitempty"` + Path *string `json:"path,omitempty"` + Mode *string `json:"mode,omitempty"` + Type *string `json:"type,omitempty"` + Size *int `json:"size,omitempty"` + Content *string `json:"content,omitempty"` +} + +func (t TreeEntry) String() string { + return Stringify(t) +} + +// GetTree fetches the Tree object for a given sha hash from a repository. +// +// GitHub API docs: http://developer.github.com/v3/git/trees/#get-a-tree +func (s *GitService) GetTree(owner string, repo string, sha string, recursive bool) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees/%v", owner, repo, sha) + if recursive { + u += "?recursive=1" + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// createTree represents the body of a CreateTree request. +type createTree struct { + BaseTree string `json:"base_tree,omitempty"` + Entries []TreeEntry `json:"tree"` +} + +// CreateTree creates a new tree in a repository. If both a tree and a nested +// path modifying that tree are specified, it will overwrite the contents of +// that tree with the new path contents and write a new tree out. +// +// GitHub API docs: http://developer.github.com/v3/git/trees/#create-a-tree +func (s *GitService) CreateTree(owner string, repo string, baseTree string, entries []TreeEntry) (*Tree, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/git/trees", owner, repo) + + body := &createTree{ + BaseTree: baseTree, + Entries: entries, + } + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + t := new(Tree) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go new file mode 100644 index 00000000000..99ec4f34cc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/git_trees_test.go @@ -0,0 +1,189 @@ +// 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" +) + +func TestGitService_GetTree(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/git/trees/s", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{ + "sha": "s", + "tree": [ { "type": "blob" } ] + }`) + }) + + tree, _, err := client.Git.GetTree("o", "r", "s", true) + if err != nil { + t.Errorf("Git.GetTree returned error: %v", err) + } + + want := Tree{ + SHA: String("s"), + Entries: []TreeEntry{ + { + Type: String("blob"), + }, + }, + } + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Tree.Get returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_GetTree_invalidOwner(t *testing.T) { + _, _, err := client.Git.GetTree("%", "%", "%", false) + testURLParseError(t, err) +} + +func TestGitService_CreateTree(t *testing.T) { + setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "cd8274d15fa3ae2ab983129fb037999f264ba9a7", + "tree": [ + { + "path": "file.rb", + "mode": "100644", + "type": "blob", + "size": 132, + "sha": "7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree("o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("cd8274d15fa3ae2ab983129fb037999f264ba9a7"), + []TreeEntry{ + { + Path: String("file.rb"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(132), + SHA: String("7c258a9869f33c1e1e1f74fbb32f07c86cb5a75b"), + }, + }, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_Content(t *testing.T) { + setup() + defer teardown() + + input := []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Content: String("file content"), + }, + } + + mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) { + v := new(createTree) + json.NewDecoder(r.Body).Decode(v) + + if m := "POST"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + + want := &createTree{ + BaseTree: "b", + Entries: input, + } + if !reflect.DeepEqual(v, want) { + t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want) + } + + fmt.Fprint(w, `{ + "sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11", + "url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11", + "tree": [ + { + "mode": "100644", + "type": "blob", + "sha": "aad8feacf6f8063150476a7b2bd9770f2794c08b", + "path": "content.md", + "size": 12, + "url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b" + } + ] + }`) + }) + + tree, _, err := client.Git.CreateTree("o", "r", "b", input) + if err != nil { + t.Errorf("Git.CreateTree returned error: %v", err) + } + + want := Tree{ + String("5c6780ad2c68743383b740fd1dab6f6a33202b11"), + []TreeEntry{ + { + Path: String("content.md"), + Mode: String("100644"), + Type: String("blob"), + Size: Int(12), + SHA: String("aad8feacf6f8063150476a7b2bd9770f2794c08b"), + }, + }, + } + + if !reflect.DeepEqual(*tree, want) { + t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want) + } +} + +func TestGitService_CreateTree_invalidOwner(t *testing.T) { + _, _, err := client.Git.CreateTree("%", "%", "", nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/github.go b/Godeps/_workspace/src/github.com/google/go-github/github/github.go new file mode 100644 index 00000000000..034adac8117 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/github.go @@ -0,0 +1,581 @@ +// 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 ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "time" + + "github.com/google/go-querystring/query" +) + +const ( + libraryVersion = "0.1" + defaultBaseURL = "https://api.github.com/" + uploadBaseURL = "https://uploads.github.com/" + userAgent = "go-github/" + libraryVersion + + headerRateLimit = "X-RateLimit-Limit" + headerRateRemaining = "X-RateLimit-Remaining" + headerRateReset = "X-RateLimit-Reset" + + mediaTypeV3 = "application/vnd.github.v3+json" + defaultMediaType = "application/octet-stream" + + // Media Type values to access preview APIs + + // https://developer.github.com/changes/2015-03-09-licenses-api/ + mediaTypeLicensesPreview = "application/vnd.github.drax-preview+json" +) + +// A Client manages communication with the GitHub API. +type Client struct { + // HTTP client used to communicate with the API. + client *http.Client + + // Base URL for API requests. Defaults to the public GitHub API, but can be + // set to a domain endpoint to use with GitHub Enterprise. BaseURL should + // always be specified with a trailing slash. + BaseURL *url.URL + + // Base URL for uploading files. + UploadURL *url.URL + + // User agent used when communicating with the GitHub API. + UserAgent string + + // Rate specifies the current rate limit for the client as determined by the + // most recent API call. If the client is used in a multi-user application, + // this rate may not always be up-to-date. Call RateLimit() to check the + // current rate. + Rate Rate + + // Services used for talking to different parts of the GitHub API. + Activity *ActivityService + Gists *GistsService + Git *GitService + Gitignores *GitignoresService + Issues *IssuesService + Organizations *OrganizationsService + PullRequests *PullRequestsService + Repositories *RepositoriesService + Search *SearchService + Users *UsersService + Licenses *LicensesService +} + +// ListOptions specifies the optional parameters to various List methods that +// support pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int `url:"page,omitempty"` + + // For paginated result sets, the number of results to include per page. + PerPage int `url:"per_page,omitempty"` +} + +// UploadOptions specifies the parameters to methods that support uploads. +type UploadOptions struct { + Name string `url:"name,omitempty"` +} + +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + return u.String(), nil +} + +// NewClient returns a new GitHub API client. If a nil httpClient is +// provided, http.DefaultClient will be used. To use API methods which require +// authentication, provide an http.Client that will perform the authentication +// for you (such as that provided by the golang.org/x/oauth2 library). +func NewClient(httpClient *http.Client) *Client { + if httpClient == nil { + httpClient = http.DefaultClient + } + baseURL, _ := url.Parse(defaultBaseURL) + uploadURL, _ := url.Parse(uploadBaseURL) + + c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent, UploadURL: uploadURL} + c.Activity = &ActivityService{client: c} + c.Gists = &GistsService{client: c} + c.Git = &GitService{client: c} + c.Gitignores = &GitignoresService{client: c} + c.Issues = &IssuesService{client: c} + c.Organizations = &OrganizationsService{client: c} + c.PullRequests = &PullRequestsService{client: c} + c.Repositories = &RepositoriesService{client: c} + c.Search = &SearchService{client: c} + c.Users = &UsersService{client: c} + c.Licenses = &LicensesService{client: c} + return c +} + +// NewRequest creates an API request. A relative URL can be provided in urlStr, +// in which case it is resolved relative to the BaseURL of the Client. +// Relative URLs should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.BaseURL.ResolveReference(rel) + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", mediaTypeV3) + if c.UserAgent != "" { + req.Header.Add("User-Agent", c.UserAgent) + } + return req, nil +} + +// NewUploadRequest creates an upload request. A relative URL can be provided in +// urlStr, in which case it is resolved relative to the UploadURL of the Client. +// Relative URLs should always be specified without a preceding slash. +func (c *Client) NewUploadRequest(urlStr string, reader io.Reader, size int64, mediaType string) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.UploadURL.ResolveReference(rel) + req, err := http.NewRequest("POST", u.String(), reader) + if err != nil { + return nil, err + } + req.ContentLength = size + + if len(mediaType) == 0 { + mediaType = defaultMediaType + } + req.Header.Add("Content-Type", mediaType) + req.Header.Add("Accept", mediaTypeV3) + req.Header.Add("User-Agent", c.UserAgent) + return req, nil +} + +// Response is a GitHub API response. This wraps the standard http.Response +// returned from GitHub and provides convenient access to things like +// pagination links. +type Response struct { + *http.Response + + // These fields provide the page values for paginating through a set of + // results. Any or all of these may be set to the zero value for + // responses that are not part of a paginated set, or for which there + // are no additional pages. + + NextPage int + PrevPage int + FirstPage int + LastPage int + + Rate +} + +// newResponse creats a new Response for the provided http.Response. +func newResponse(r *http.Response) *Response { + response := &Response{Response: r} + response.populatePageValues() + response.populateRate() + return response +} + +// populatePageValues parses the HTTP Link response headers and populates the +// various pagination link values in the Reponse. +func (r *Response) populatePageValues() { + if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 { + for _, link := range strings.Split(links[0], ",") { + segments := strings.Split(strings.TrimSpace(link), ";") + + // link must at least have href and rel + if len(segments) < 2 { + continue + } + + // ensure href is properly formatted + if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") { + continue + } + + // try to pull out page parameter + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + page := url.Query().Get("page") + if page == "" { + continue + } + + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.NextPage, _ = strconv.Atoi(page) + case `rel="prev"`: + r.PrevPage, _ = strconv.Atoi(page) + case `rel="first"`: + r.FirstPage, _ = strconv.Atoi(page) + case `rel="last"`: + r.LastPage, _ = strconv.Atoi(page) + } + + } + } + } +} + +// populateRate parses the rate related headers and populates the response Rate. +func (r *Response) populateRate() { + if limit := r.Header.Get(headerRateLimit); limit != "" { + r.Rate.Limit, _ = strconv.Atoi(limit) + } + if remaining := r.Header.Get(headerRateRemaining); remaining != "" { + r.Rate.Remaining, _ = strconv.Atoi(remaining) + } + if reset := r.Header.Get(headerRateReset); reset != "" { + if v, _ := strconv.ParseInt(reset, 10, 64); v != 0 { + r.Rate.Reset = Timestamp{time.Unix(v, 0)} + } + } +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + response := newResponse(resp) + + c.Rate = response.Rate + + err = CheckResponse(resp) + if err != nil { + // even though there was an error, we still return the response + // in case the caller wants to inspect it further + return response, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + io.Copy(w, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(v) + } + } + return response, err +} + +/* +An ErrorResponse reports one or more errors caused by an API request. + +GitHub API docs: http://developer.github.com/v3/#client-errors +*/ +type ErrorResponse struct { + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // error message + Errors []Error `json:"errors"` // more detail on individual errors +} + +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v %+v", + r.Response.Request.Method, sanitizeURL(r.Response.Request.URL), + 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. +These are the possible validation error codes: + + missing: + resource does not exist + missing_field: + a required field on a resource has not been set + invalid: + the formatting of a field is invalid + already_exists: + another resource has the same valid as this field + +GitHub API docs: http://developer.github.com/v3/#client-errors +*/ +type Error struct { + Resource string `json:"resource"` // resource on which the error occurred + Field string `json:"field"` // field on which the error occurred + Code string `json:"code"` // validation error code +} + +func (e *Error) Error() string { + return fmt.Sprintf("%v error caused by %v field on %v resource", + e.Code, e.Field, e.Resource) +} + +// CheckResponse checks the API response for errors, and returns them if +// present. A response is considered an error if it has a status code outside +// the 200 range. API error responses are expected to have either no response +// body, or a JSON response body that maps to ErrorResponse. Any other +// response body will be silently ignored. +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + json.Unmarshal(data, errorResponse) + } + return errorResponse +} + +// parseBoolResponse determines the boolean result from a GitHub API response. +// Several GitHub API methods return boolean responses indicated by the HTTP +// status code in the response (true indicated by a 204, false indicated by a +// 404). This helper function will determine that result and hide the 404 +// error if present. Any other error will be returned through as-is. +func parseBoolResponse(err error) (bool, error) { + if err == nil { + return true, nil + } + + if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound { + // Simply false. In this one case, we do not pass the error through. + return false, nil + } + + // some other real error occurred + return false, err +} + +// Rate represents the rate limit for the current client. +type Rate struct { + // The number of requests per hour the client is currently limited to. + Limit int `json:"limit"` + + // The number of remaining requests the client can make this hour. + Remaining int `json:"remaining"` + + // The time at which the current rate limit will reset. + Reset Timestamp `json:"reset"` +} + +func (r Rate) String() string { + return Stringify(r) +} + +// RateLimits represents the rate limits for the current client. +type RateLimits struct { + // The rate limit for non-search API requests. Unauthenticated + // requests are limited to 60 per hour. Authenticated requests are + // limited to 5,000 per hour. + Core *Rate `json:"core"` + + // The rate limit for search API requests. Unauthenticated requests + // are limited to 5 requests per minutes. Authenticated requests are + // limited to 20 per minute. + // + // GitHub API docs: https://developer.github.com/v3/search/#rate-limit + Search *Rate `json:"search"` +} + +func (r RateLimits) String() string { + return Stringify(r) +} + +// RateLimit is deprecated. Use RateLimits instead. +func (c *Client) RateLimit() (*Rate, *Response, error) { + limits, resp, err := c.RateLimits() + if limits == nil { + return nil, nil, err + } + + return limits.Core, resp, err +} + +// RateLimits returns the rate limits for the current client. +func (c *Client) RateLimits() (*RateLimits, *Response, error) { + req, err := c.NewRequest("GET", "rate_limit", nil) + if err != nil { + return nil, nil, err + } + + response := new(struct { + Resources *RateLimits `json:"resources"` + }) + resp, err := c.Do(req, response) + if err != nil { + return nil, nil, err + } + + return response.Resources, resp, err +} + +/* +UnauthenticatedRateLimitedTransport allows you to make unauthenticated calls +that need to use a higher rate limit associated with your OAuth application. + + t := &github.UnauthenticatedRateLimitedTransport{ + ClientID: "your app's client ID", + ClientSecret: "your app's client secret", + } + client := github.NewClient(t.Client()) + +This will append the querystring params client_id=xxx&client_secret=yyy to all +requests. + +See http://developer.github.com/v3/#unauthenticated-rate-limited-requests for +more information. +*/ +type UnauthenticatedRateLimitedTransport struct { + // ClientID is the GitHub OAuth client ID of the current application, which + // can be found by selecting its entry in the list at + // https://github.com/settings/applications. + ClientID string + + // ClientSecret is the GitHub OAuth client secret of the current + // application. + ClientSecret string + + // Transport is the underlying HTTP transport to use when making requests. + // It will default to http.DefaultTransport if nil. + Transport http.RoundTripper +} + +// RoundTrip implements the RoundTripper interface. +func (t *UnauthenticatedRateLimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if t.ClientID == "" { + return nil, errors.New("t.ClientID is empty") + } + if t.ClientSecret == "" { + return nil, errors.New("t.ClientSecret is empty") + } + + // To set extra querystring params, we must make a copy of the Request so + // that we don't modify the Request we were given. This is required by the + // specification of http.RoundTripper. + req = cloneRequest(req) + q := req.URL.Query() + q.Set("client_id", t.ClientID) + q.Set("client_secret", t.ClientSecret) + req.URL.RawQuery = q.Encode() + + // Make the HTTP request. + return t.transport().RoundTrip(req) +} + +// Client returns an *http.Client that makes requests which are subject to the +// rate limit of your OAuth application. +func (t *UnauthenticatedRateLimitedTransport) Client() *http.Client { + return &http.Client{Transport: t} +} + +func (t *UnauthenticatedRateLimitedTransport) transport() http.RoundTripper { + if t.Transport != nil { + return t.Transport + } + return http.DefaultTransport +} + +// cloneRequest returns a clone of the provided *http.Request. The clone is a +// shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header) + for k, s := range r.Header { + r2.Header[k] = s + } + return r2 +} + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + p := new(string) + *p = v + return p +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/github_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/github_test.go new file mode 100644 index 00000000000..05c817b6582 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/github_test.go @@ -0,0 +1,679 @@ +// 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 ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "reflect" + "strings" + "testing" + "time" +) + +var ( + // mux is the HTTP request multiplexer used with the test server. + mux *http.ServeMux + + // client is the GitHub client being tested. + client *Client + + // server is a test HTTP server used to provide mock API responses. + server *httptest.Server +) + +// setup sets up a test HTTP server along with a github.Client that is +// configured to talk to that test server. Tests should register handlers on +// mux which provide mock responses for the API method being tested. +func setup() { + // test server + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + // github client configured to use test server + client = NewClient(nil) + url, _ := url.Parse(server.URL) + client.BaseURL = url + client.UploadURL = url +} + +// teardown closes the test HTTP server. +func teardown() { + server.Close() +} + +// openTestFile creates a new file with the given name and content for testing. +// In order to ensure the exact file name, this function will create a new temp +// directory, and create the file in that directory. It is the caller's +// responsibility to remove the directy and its contents when no longer needed. +func openTestFile(name, content string) (file *os.File, dir string, err error) { + dir, err = ioutil.TempDir("", "go-github") + if err != nil { + return nil, dir, err + } + + file, err = os.OpenFile(path.Join(dir, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + return nil, dir, err + } + + fmt.Fprint(file, content) + + // close and re-open the file to keep file.Stat() happy + file.Close() + file, err = os.Open(file.Name()) + if err != nil { + return nil, dir, err + } + + return file, dir, err +} + +func testMethod(t *testing.T, r *http.Request, want string) { + if got := r.Method; got != want { + t.Errorf("Request method: %v, want %v", got, want) + } +} + +type values map[string]string + +func testFormValues(t *testing.T, r *http.Request, values values) { + want := url.Values{} + for k, v := range values { + want.Add(k, v) + } + + r.ParseForm() + if got := r.Form; !reflect.DeepEqual(got, want) { + t.Errorf("Request parameters: %v, want %v", got, want) + } +} + +func testHeader(t *testing.T, r *http.Request, header string, want string) { + if got := r.Header.Get(header); got != want { + t.Errorf("Header.Get(%q) returned %s, want %s", header, got, want) + } +} + +func testURLParseError(t *testing.T, err error) { + if err == nil { + t.Errorf("Expected error to be returned") + } + if err, ok := err.(*url.Error); !ok || err.Op != "parse" { + t.Errorf("Expected URL parse error, got %+v", err) + } +} + +func testBody(t *testing.T, r *http.Request, want string) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Error reading request body: %v", err) + } + if got := string(b); got != want { + t.Errorf("request Body is %s, want %s", got, want) + } +} + +// Helper function to test that a value is marshalled to JSON as expected. +func testJSONMarshal(t *testing.T, v interface{}, want string) { + j, err := json.Marshal(v) + if err != nil { + t.Errorf("Unable to marshal JSON for %v", v) + } + + w := new(bytes.Buffer) + err = json.Compact(w, []byte(want)) + if err != nil { + t.Errorf("String is not valid json: %s", want) + } + + if w.String() != string(j) { + t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w) + } + + // now go the other direction and make sure things unmarshal as expected + u := reflect.ValueOf(v).Interface() + if err := json.Unmarshal([]byte(want), u); err != nil { + t.Errorf("Unable to unmarshal JSON for %v", want) + } + + if !reflect.DeepEqual(v, u) { + t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v) + } +} + +func TestNewClient(t *testing.T) { + c := NewClient(nil) + + if got, want := c.BaseURL.String(), defaultBaseURL; got != want { + t.Errorf("NewClient BaseURL is %v, want %v", got, want) + } + if got, want := c.UserAgent, userAgent; got != want { + t.Errorf("NewClient UserAgent is %v, want %v", got, want) + } +} + +func TestNewRequest(t *testing.T) { + c := NewClient(nil) + + inURL, outURL := "/foo", defaultBaseURL+"foo" + inBody, outBody := &User{Login: String("l")}, `{"login":"l"}`+"\n" + req, _ := c.NewRequest("GET", inURL, inBody) + + // test that relative URL was expanded + if got, want := req.URL.String(), outURL; got != want { + t.Errorf("NewRequest(%q) URL is %v, want %v", inURL, got, want) + } + + // test that body was JSON encoded + body, _ := ioutil.ReadAll(req.Body) + if got, want := string(body), outBody; got != want { + t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want) + } + + // test that default user-agent is attached to the request + if got, want := req.Header.Get("User-Agent"), c.UserAgent; got != want { + t.Errorf("NewRequest() User-Agent is %v, want %v", got, want) + } +} + +func TestNewRequest_invalidJSON(t *testing.T) { + c := NewClient(nil) + + type T struct { + A map[int]interface{} + } + _, err := c.NewRequest("GET", "/", &T{}) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*json.UnsupportedTypeError); !ok { + t.Errorf("Expected a JSON error; got %#v.", err) + } +} + +func TestNewRequest_badURL(t *testing.T) { + c := NewClient(nil) + _, err := c.NewRequest("GET", ":", nil) + testURLParseError(t, err) +} + +// ensure that no User-Agent header is set if the client's UserAgent is empty. +// This caused a problem with Google's internal http client. +func TestNewRequest_emptyUserAgent(t *testing.T) { + c := NewClient(nil) + c.UserAgent = "" + req, err := c.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if _, ok := req.Header["User-Agent"]; ok { + t.Fatal("constructed request contains unexpected User-Agent header") + } +} + +// If a nil body is passed to github.NewRequest, make sure that nil is also +// passed to http.NewRequest. In most cases, passing an io.Reader that returns +// no content is fine, since there is no difference between an HTTP request +// body that is an empty string versus one that is not set at all. However in +// certain cases, intermediate systems may treat these differently resulting in +// subtle errors. +func TestNewRequest_emptyBody(t *testing.T) { + c := NewClient(nil) + req, err := c.NewRequest("GET", "/", nil) + if err != nil { + t.Fatalf("NewRequest returned unexpected error: %v", err) + } + if req.Body != nil { + t.Fatalf("constructed request contains a non-nil Body") + } +} + +func TestResponse_populatePageValues(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`; rel="first",` + + ` ; rel="prev",` + + ` ; rel="next",` + + ` ; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 1; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 2; want != got { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 4; want != got { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 5; want != got { + t.Errorf("response.LastPage: %v, want %v", got, want) + } +} + +func TestResponse_populatePageValues_invalid(t *testing.T) { + r := http.Response{ + Header: http.Header{ + "Link": {`,` + + `; rel="first",` + + `https://api.github.com/?page=2; rel="prev",` + + `; rel="next",` + + `; rel="last"`, + }, + }, + } + + response := newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } + if got, want := response.PrevPage, 0; got != want { + t.Errorf("response.PrevPage: %v, want %v", got, want) + } + if got, want := response.NextPage, 0; got != want { + t.Errorf("response.NextPage: %v, want %v", got, want) + } + if got, want := response.LastPage, 0; got != want { + t.Errorf("response.LastPage: %v, want %v", got, want) + } + + // more invalid URLs + r = http.Response{ + Header: http.Header{ + "Link": {`; rel="first"`}, + }, + } + + response = newResponse(&r) + if got, want := response.FirstPage, 0; got != want { + t.Errorf("response.FirstPage: %v, want %v", got, want) + } +} + +func TestDo(t *testing.T) { + setup() + defer teardown() + + type foo struct { + A string + } + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"A":"a"}`) + }) + + req, _ := client.NewRequest("GET", "/", nil) + body := new(foo) + client.Do(req, body) + + want := &foo{"a"} + if !reflect.DeepEqual(body, want) { + t.Errorf("Response body = %v, want %v", body, want) + } +} + +func TestDo_httpError(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", "/", nil) + _, err := client.Do(req, nil) + + if err == nil { + t.Error("Expected HTTP 400 error.") + } +} + +// Test handling of an error caused by the internal http client's Do() +// function. A redirect loop is pretty unlikely to occur within the GitHub +// API, but does allow us to exercise the right code path. +func TestDo_redirectLoop(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/", http.StatusFound) + }) + + req, _ := client.NewRequest("GET", "/", nil) + _, err := client.Do(req, nil) + + if err == nil { + t.Error("Expected error to be returned.") + } + if err, ok := err.(*url.Error); !ok { + t.Errorf("Expected a URL error; got %#v.", err) + } +} + +func TestDo_rateLimit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add(headerRateLimit, "60") + w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") + }) + + if got, want := client.Rate.Limit, 0; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Limit, 0; got != want { + t.Errorf("Client rate remaining = %v, got %v", got, want) + } + if !client.Rate.Reset.IsZero() { + t.Errorf("Client rate reset not initialized to zero value") + } + + req, _ := client.NewRequest("GET", "/", nil) + client.Do(req, nil) + + if got, want := client.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } +} + +// ensure rate limit is still parsed, even for error responses +func TestDo_rateLimit_errorResponse(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Add(headerRateLimit, "60") + w.Header().Add(headerRateRemaining, "59") + w.Header().Add(headerRateReset, "1372700873") + http.Error(w, "Bad Request", 400) + }) + + req, _ := client.NewRequest("GET", "/", nil) + client.Do(req, nil) + + if got, want := client.Rate.Limit, 60; got != want { + t.Errorf("Client rate limit = %v, want %v", got, want) + } + if got, want := client.Rate.Remaining, 59; got != want { + t.Errorf("Client rate remaining = %v, want %v", got, want) + } + reset := time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC) + if client.Rate.Reset.UTC() != reset { + t.Errorf("Client rate reset = %v, want %v", client.Rate.Reset, reset) + } +} + +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) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader(`{"message":"m", + "errors": [{"resource": "r", "field": "f", "code": "c"}]}`)), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + Message: "m", + Errors: []Error{{Resource: "r", Field: "f", Code: "c"}}, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +// ensure that we properly handle API errors that do not contain a response body +func TestCheckResponse_noBody(t *testing.T) { + res := &http.Response{ + Request: &http.Request{}, + StatusCode: http.StatusBadRequest, + Body: ioutil.NopCloser(strings.NewReader("")), + } + err := CheckResponse(res).(*ErrorResponse) + + if err == nil { + t.Errorf("Expected error response.") + } + + want := &ErrorResponse{ + Response: res, + } + if !reflect.DeepEqual(err, want) { + t.Errorf("Error = %#v, want %#v", err, want) + } +} + +func TestParseBooleanResponse_true(t *testing.T) { + result, err := parseBoolResponse(nil) + + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := true; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_false(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusNotFound}} + result, err := parseBoolResponse(v) + + if err != nil { + t.Errorf("parseBoolResponse returned error: %+v", err) + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestParseBooleanResponse_error(t *testing.T) { + v := &ErrorResponse{Response: &http.Response{StatusCode: http.StatusBadRequest}} + result, err := parseBoolResponse(v) + + if err == nil { + t.Errorf("Expected error to be returned.") + } + + if want := false; result != want { + t.Errorf("parseBoolResponse returned %+v, want: %+v", result, want) + } +} + +func TestErrorResponse_Error(t *testing.T) { + res := &http.Response{Request: &http.Request{}} + err := ErrorResponse{Message: "m", Response: res} + if err.Error() == "" { + t.Errorf("Expected non-empty ErrorResponse.Error()") + } +} + +func TestError_Error(t *testing.T) { + err := Error{} + if err.Error() == "" { + t.Errorf("Expected non-empty Error.Error()") + } +} + +func TestRateLimit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + //fmt.Fprint(w, `{"resources":{"core": {"limit":2,"remaining":1,"reset":1372700873}}}`) + fmt.Fprint(w, `{"resources":{ + "core": {"limit":2,"remaining":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"reset":1372700874} + }}`) + }) + + rate, _, err := client.RateLimit() + if err != nil { + t.Errorf("Rate limit returned error: %v", err) + } + + want := &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()}, + } + if !reflect.DeepEqual(rate, want) { + t.Errorf("RateLimit returned %+v, want %+v", rate, want) + } +} + +func TestRateLimits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/rate_limit", func(w http.ResponseWriter, r *http.Request) { + if m := "GET"; m != r.Method { + t.Errorf("Request method = %v, want %v", r.Method, m) + } + fmt.Fprint(w, `{"resources":{ + "core": {"limit":2,"remaining":1,"reset":1372700873}, + "search": {"limit":3,"remaining":2,"reset":1372700874} + }}`) + }) + + rate, _, err := client.RateLimits() + if err != nil { + t.Errorf("RateLimits returned error: %v", err) + } + + want := &RateLimits{ + Core: &Rate{ + Limit: 2, + Remaining: 1, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 53, 0, time.UTC).Local()}, + }, + Search: &Rate{ + Limit: 3, + Remaining: 2, + Reset: Timestamp{time.Date(2013, 7, 1, 17, 47, 54, 0, time.UTC).Local()}, + }, + } + if !reflect.DeepEqual(rate, want) { + t.Errorf("RateLimits returned %+v, want %+v", rate, want) + } +} + +func TestUnauthenticatedRateLimitedTransport(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + var v, want string + q := r.URL.Query() + if v, want = q.Get("client_id"), "id"; v != want { + t.Errorf("OAuth Client ID = %v, want %v", v, want) + } + if v, want = q.Get("client_secret"), "secret"; v != want { + t.Errorf("OAuth Client Secret = %v, want %v", v, want) + } + }) + + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + unauthedClient := NewClient(tp.Client()) + unauthedClient.BaseURL = client.BaseURL + req, _ := unauthedClient.NewRequest("GET", "/", nil) + unauthedClient.Do(req, nil) +} + +func TestUnauthenticatedRateLimitedTransport_missingFields(t *testing.T) { + // missing ClientID + tp := &UnauthenticatedRateLimitedTransport{ + ClientSecret: "secret", + } + _, err := tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } + + // missing ClientSecret + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + } + _, err = tp.RoundTrip(nil) + if err == nil { + t.Errorf("Expected error to be returned") + } +} + +func TestUnauthenticatedRateLimitedTransport_transport(t *testing.T) { + // default transport + tp := &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + } + if tp.transport() != http.DefaultTransport { + t.Errorf("Expected http.DefaultTransport to be used.") + } + + // custom transport + tp = &UnauthenticatedRateLimitedTransport{ + ClientID: "id", + ClientSecret: "secret", + Transport: &http.Transport{}, + } + if tp.transport() == http.DefaultTransport { + t.Errorf("Expected custom transport to be used.") + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go b/Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go new file mode 100644 index 00000000000..31d59025592 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gitignore.go @@ -0,0 +1,63 @@ +// 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" + +// GitignoresService provides access to the gitignore related functions in the +// GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/gitignore/ +type GitignoresService struct { + client *Client +} + +// Gitignore represents a .gitignore file as returned by the GitHub API. +type Gitignore struct { + Name *string `json:"name,omitempty"` + Source *string `json:"source,omitempty"` +} + +func (g Gitignore) String() string { + return Stringify(g) +} + +// List all available Gitignore templates. +// +// http://developer.github.com/v3/gitignore/#listing-available-templates +func (s GitignoresService) List() ([]string, *Response, error) { + req, err := s.client.NewRequest("GET", "gitignore/templates", nil) + if err != nil { + return nil, nil, err + } + + availableTemplates := new([]string) + resp, err := s.client.Do(req, availableTemplates) + if err != nil { + return nil, resp, err + } + + return *availableTemplates, resp, err +} + +// Get a Gitignore by name. +// +// http://developer.github.com/v3/gitignore/#get-a-single-template +func (s GitignoresService) Get(name string) (*Gitignore, *Response, error) { + u := fmt.Sprintf("gitignore/templates/%v", name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + gitignore := new(Gitignore) + resp, err := s.client.Do(req, gitignore) + if err != nil { + return nil, resp, err + } + + return gitignore, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go new file mode 100644 index 00000000000..6d49d00fa46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/gitignore_test.go @@ -0,0 +1,58 @@ +// 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 TestGitignoresService_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `["C", "Go"]`) + }) + + available, _, err := client.Gitignores.List() + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := []string{"C", "Go"} + if !reflect.DeepEqual(available, want) { + t.Errorf("Gitignores.List returned %+v, want %+v", available, want) + } +} + +func TestGitignoresService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/gitignore/templates/name", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"Name","source":"template source"}`) + }) + + gitignore, _, err := client.Gitignores.Get("name") + if err != nil { + t.Errorf("Gitignores.List returned error: %v", err) + } + + want := &Gitignore{Name: String("Name"), Source: String("template source")} + if !reflect.DeepEqual(gitignore, want) { + t.Errorf("Gitignores.Get returned %+v, want %+v", gitignore, want) + } +} + +func TestGitignoresService_Get_invalidTemplate(t *testing.T) { + _, _, err := client.Gitignores.Get("%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues.go new file mode 100644 index 00000000000..1d51a3df0cd --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues.go @@ -0,0 +1,261 @@ +// 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" + "time" +) + +// IssuesService handles communication with the issue related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/issues/ +type IssuesService struct { + client *Client +} + +// Issue represents a GitHub issue on a repository. +type Issue struct { + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + Labels []Label `json:"labels,omitempty"` + Assignee *User `json:"assignee,omitempty"` + Comments *int `json:"comments,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Milestone *Milestone `json:"milestone,omitempty"` + PullRequestLinks *PullRequestLinks `json:"pull_request,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (i Issue) String() string { + return Stringify(i) +} + +// IssueRequest represents a request to create/edit an issue. +// It is separate from Issue above because otherwise Labels +// and Assignee fail to serialize to the correct JSON. +type IssueRequest struct { + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + Labels *[]string `json:"labels,omitempty"` + Assignee *string `json:"assignee,omitempty"` + State *string `json:"state,omitempty"` + Milestone *int `json:"milestone,omitempty"` +} + +// IssueListOptions specifies the optional parameters to the IssuesService.List +// and IssuesService.ListByOrg methods. +type IssueListOptions struct { + // Filter specifies which issues to list. Possible values are: assigned, + // created, mentioned, subscribed, all. Default is "assigned". + Filter string `url:"filter,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed. Default is "open". + State string `url:"state,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,comma,omitempty"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "assigned". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// PullRequestLinks object is added to the Issue object when it's an issue included +// in the IssueCommentEvent webhook payload, if the webhooks is fired by a comment on a PR +type PullRequestLinks struct { + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DiffURL *string `json:"diff_url,omitempty"` + PatchURL *string `json:"patch_url,omitempty"` +} + +// List the issues for the authenticated user. If all is true, list issues +// across all the user's visible repositories including owned, member, and +// organization repositories; if false, list only owned and member +// repositories. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) List(all bool, opt *IssueListOptions) ([]Issue, *Response, error) { + var u string + if all { + u = "issues" + } else { + u = "user/issues" + } + return s.listIssues(u, opt) +} + +// ListByOrg fetches the issues in the specified organization for the +// authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues +func (s *IssuesService) ListByOrg(org string, opt *IssueListOptions) ([]Issue, *Response, error) { + u := fmt.Sprintf("orgs/%v/issues", org) + return s.listIssues(u, opt) +} + +func (s *IssuesService) listIssues(u string, opt *IssueListOptions) ([]Issue, *Response, error) { + 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 + } + + issues := new([]Issue) + resp, err := s.client.Do(req, issues) + if err != nil { + return nil, resp, err + } + + return *issues, resp, err +} + +// IssueListByRepoOptions specifies the optional parameters to the +// IssuesService.ListByRepo method. +type IssueListByRepoOptions struct { + // Milestone limits issues for the specified milestone. Possible values are + // a milestone number, "none" for issues with no milestone, "*" for issues + // with any milestone. + Milestone string `url:"milestone,omitempty"` + + // State filters issues based on their state. Possible values are: open, + // closed. Default is "open". + State string `url:"state,omitempty"` + + // Assignee filters issues based on their assignee. Possible values are a + // user name, "none" for issues that are not assigned, "*" for issues with + // any assigned user. + Assignee string `url:"assignee,omitempty"` + + // Assignee filters issues based on their creator. + Creator string `url:"creator,omitempty"` + + // Assignee filters issues to those mentioned a specific user. + Mentioned string `url:"mentioned,omitempty"` + + // Labels filters issues based on their label. + Labels []string `url:"labels,omitempty,comma"` + + // Sort specifies how to sort issues. Possible values are: created, updated, + // and comments. Default value is "assigned". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort issues. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` + + // Since filters issues by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListByRepo lists the issues for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/#list-issues-for-a-repository +func (s *IssuesService) ListByRepo(owner string, repo string, opt *IssueListByRepoOptions) ([]Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + 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 + } + + issues := new([]Issue) + resp, err := s.client.Do(req, issues) + if err != nil { + return nil, resp, err + } + + return *issues, resp, err +} + +// Get a single issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/#get-a-single-issue +func (s *IssuesService) Get(owner string, repo string, number int) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + issue := new(Issue) + resp, err := s.client.Do(req, issue) + if err != nil { + return nil, resp, err + } + + return issue, resp, err +} + +// Create a new issue on the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/#create-an-issue +func (s *IssuesService) Create(owner string, repo string, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues", owner, repo) + req, err := s.client.NewRequest("POST", u, issue) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} + +// Edit an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/#edit-an-issue +func (s *IssuesService) Edit(owner string, repo string, number int, issue *IssueRequest) (*Issue, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, issue) + if err != nil { + return nil, nil, err + } + + i := new(Issue) + resp, err := s.client.Do(req, i) + if err != nil { + return nil, resp, err + } + + return i, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go new file mode 100644 index 00000000000..6338c22ecaa --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees.go @@ -0,0 +1,46 @@ +// 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" + +// ListAssignees fetches all available assignees (owners and collaborators) to +// which issues may be assigned. +// +// GitHub API docs: http://developer.github.com/v3/issues/assignees/#list-assignees +func (s *IssuesService) ListAssignees(owner string, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees", owner, repo) + 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 + } + assignees := new([]User) + resp, err := s.client.Do(req, assignees) + if err != nil { + return nil, resp, err + } + + return *assignees, resp, err +} + +// IsAssignee checks if a user is an assignee for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/assignees/#check-assignee +func (s *IssuesService) IsAssignee(owner string, repo string, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/assignees/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + resp, err := s.client.Do(req, nil) + assignee, err := parseBoolResponse(err) + return assignee, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go new file mode 100644 index 00000000000..63e024d31a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_assignees_test.go @@ -0,0 +1,98 @@ +// 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 TestIssuesService_ListAssignees(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assignees, _, err := client.Issues.ListAssignees("o", "r", opt) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(assignees, want) { + t.Errorf("Issues.ListAssignees returned %+v, want %+v", assignees, want) + } +} + +func TestIssuesService_ListAssignees_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListAssignees("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_IsAssignee_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := true; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err != nil { + t.Errorf("Issues.IsAssignee returned error: %v", err) + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/assignees/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + assignee, _, err := client.Issues.IsAssignee("o", "r", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; assignee != want { + t.Errorf("Issues.IsAssignee returned %+v, want %+v", assignee, want) + } +} + +func TestIssuesService_IsAssignee_invalidOwner(t *testing.T) { + _, _, err := client.Issues.IsAssignee("%", "r", "u") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go new file mode 100644 index 00000000000..db48e144f64 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments.go @@ -0,0 +1,138 @@ +// 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" + "time" +) + +// IssueComment represents a comment left on an issue. +type IssueComment struct { + ID *int `json:"id,omitempty"` + Body *string `json:"body,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_url,omitempty"` +} + +func (i IssueComment) String() string { + return Stringify(i) +} + +// IssueListCommentsOptions specifies the optional parameters to the +// IssuesService.ListComments method. +type IssueListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified issue. Specifying an issue +// number of 0 will return all comments on all issues for the repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#list-comments-on-an-issue +func (s *IssuesService) ListComments(owner string, repo string, number int, opt *IssueListCommentsOptions) ([]IssueComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/issues/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + } + 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 + } + comments := new([]IssueComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment fetches the specified issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#get-a-single-comment +func (s *IssuesService) GetComment(owner string, repo string, id int) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + comment := new(IssueComment) + resp, err := s.client.Do(req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, err +} + +// CreateComment creates a new comment on the specified issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#create-a-comment +func (s *IssuesService) CreateComment(owner string, repo string, number int, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/comments", owner, repo, number) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment updates an issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#edit-a-comment +func (s *IssuesService) EditComment(owner string, repo string, id int, comment *IssueComment) (*IssueComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + c := new(IssueComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes an issue comment. +// +// GitHub API docs: http://developer.github.com/v3/issues/comments/#delete-a-comment +func (s *IssuesService) DeleteComment(owner string, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go new file mode 100644 index 00000000000..697f4380fa1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_comments_test.go @@ -0,0 +1,184 @@ +// 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 TestIssuesService_ListComments_allIssues(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &IssueListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + comments, _, err := client.Issues.ListComments("o", "r", 0, opt) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []IssueComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_specificIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + comments, _, err := client.Issues.ListComments("o", "r", 1, nil) + if err != nil { + t.Errorf("Issues.ListComments returned error: %v", err) + } + + want := []IssueComment{{ID: Int(1)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Issues.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestIssuesService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListComments("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Issues.GetComment("o", "r", 1) + if err != nil { + t.Errorf("Issues.GetComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_GetComment_invalidOrg(t *testing.T) { + _, _, err := client.Issues.GetComment("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + 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}`) + }) + + comment, _, err := client.Issues.CreateComment("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.CreateComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_CreateComment_invalidOrg(t *testing.T) { + _, _, err := client.Issues.CreateComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &IssueComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueComment) + 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}`) + }) + + comment, _, err := client.Issues.EditComment("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.EditComment returned error: %v", err) + } + + want := &IssueComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Issues.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestIssuesService_EditComment_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("Issues.DeleteComments returned error: %v", err) + } +} + +func TestIssuesService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteComment("%", "r", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go new file mode 100644 index 00000000000..0c720aa152d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_events.go @@ -0,0 +1,127 @@ +// Copyright 2014 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" + "time" +) + +// IssueEvent represents an event that occurred around an Issue or Pull Request. +type IssueEvent struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // The User that generated this event. + Actor *User `json:"actor,omitempty"` + + // Event identifies the actual type of Event that occurred. Possible + // values are: + // + // closed + // The issue was closed by the actor. When the commit_id is + // present, it identifies the commit that closed the issue using + // “closes / fixes #NN” syntax. + // + // reopened + // The issue was reopened by the actor. + // + // subscribed + // The actor subscribed to receive notifications for an issue. + // + // merged + // The issue was merged by the actor. The commit_id attribute is the SHA1 of the HEAD commit that was merged. + // + // referenced + // The issue was referenced from a commit message. The commit_id attribute is the commit SHA1 of where that happened. + // + // mentioned + // The actor was @mentioned in an issue body. + // + // assigned + // The issue was assigned to the actor. + // + // head_ref_deleted + // The pull request’s branch was deleted. + // + // head_ref_restored + // The pull request’s branch was restored. + Event *string `json:"event,omitempty"` + + // The SHA of the commit that referenced this commit, if applicable. + CommitID *string `json:"commit_id,omitempty"` + + CreatedAt *time.Time `json:"created_at,omitempty"` + Issue *Issue `json:"issue,omitempty"` +} + +// ListIssueEvents lists events for the specified issue. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-an-issue +func (s *IssuesService) ListIssueEvents(owner, repo string, number int, opt *ListOptions) ([]IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/events", owner, repo, number) + 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 + } + + var events []IssueEvent + resp, err := s.client.Do(req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, err +} + +// ListRepositoryEvents lists events for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-a-repository +func (s *IssuesService) ListRepositoryEvents(owner, repo string, opt *ListOptions) ([]IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) + 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 + } + + var events []IssueEvent + resp, err := s.client.Do(req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, err +} + +// GetEvent returns the specified issue event. +// +// GitHub API docs: https://developer.github.com/v3/issues/events/#get-a-single-event +func (s *IssuesService) GetEvent(owner, repo string, id int) (*IssueEvent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/events/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + event := new(IssueEvent) + resp, err := s.client.Do(req, event) + if err != nil { + return nil, resp, err + } + + return event, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go new file mode 100644 index 00000000000..f90b64a7115 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_events_test.go @@ -0,0 +1,86 @@ +// Copyright 2014 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 TestIssuesService_ListIssueEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListIssueEvents("o", "r", 1, opt) + + if err != nil { + t.Errorf("Issues.ListIssueEvents returned error: %v", err) + } + + want := []IssueEvent{{ID: Int(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListIssueEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_ListRepositoryEvents(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1, PerPage: 2} + events, _, err := client.Issues.ListRepositoryEvents("o", "r", opt) + + if err != nil { + t.Errorf("Issues.ListRepositoryEvents returned error: %v", err) + } + + want := []IssueEvent{{ID: Int(1)}} + if !reflect.DeepEqual(events, want) { + t.Errorf("Issues.ListRepositoryEvents returned %+v, want %+v", events, want) + } +} + +func TestIssuesService_GetEvent(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/events/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + event, _, err := client.Issues.GetEvent("o", "r", 1) + + if err != nil { + t.Errorf("Issues.GetEvent returned error: %v", err) + } + + want := &IssueEvent{ID: Int(1)} + if !reflect.DeepEqual(event, want) { + t.Errorf("Issues.GetEvent returned %+v, want %+v", event, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go new file mode 100644 index 00000000000..88f9f3ff96a --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels.go @@ -0,0 +1,222 @@ +// 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" + +// Label represents a GitHub label on an Issue +type Label struct { + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Color *string `json:"color,omitempty"` +} + +func (l Label) String() string { + return fmt.Sprint(*l.Name) +} + +// ListLabels lists all labels for a repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) ListLabels(owner string, repo string, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + 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 + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} + +// GetLabel gets a single label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-a-single-label +func (s *IssuesService) GetLabel(owner string, repo string, name string) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + label := new(Label) + resp, err := s.client.Do(req, label) + if err != nil { + return nil, resp, err + } + + return label, resp, err +} + +// CreateLabel creates a new label on the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#create-a-label +func (s *IssuesService) CreateLabel(owner string, repo string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels", owner, repo) + req, err := s.client.NewRequest("POST", u, label) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// EditLabel edits a label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#update-a-label +func (s *IssuesService) EditLabel(owner string, repo string, name string, label *Label) (*Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("PATCH", u, label) + if err != nil { + return nil, nil, err + } + + l := new(Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return l, resp, err +} + +// DeleteLabel deletes a label. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#delete-a-label +func (s *IssuesService) DeleteLabel(owner string, repo string, name string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/labels/%v", owner, repo, name) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListLabelsByIssue lists all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) ListLabelsByIssue(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + 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 + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} + +// AddLabelsToIssue adds labels to an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository +func (s *IssuesService) AddLabelsToIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("POST", u, labels) + if err != nil { + return nil, nil, err + } + + l := new([]Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return *l, resp, err +} + +// RemoveLabelForIssue removes a label for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue +func (s *IssuesService) RemoveLabelForIssue(owner string, repo string, number int, label string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels/%v", owner, repo, number, label) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ReplaceLabelsForIssue replaces all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue +func (s *IssuesService) ReplaceLabelsForIssue(owner string, repo string, number int, labels []string) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("PUT", u, labels) + if err != nil { + return nil, nil, err + } + + l := new([]Label) + resp, err := s.client.Do(req, l) + if err != nil { + return nil, resp, err + } + + return *l, resp, err +} + +// RemoveLabelsForIssue removes all labels for an issue. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue +func (s *IssuesService) RemoveLabelsForIssue(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%d/labels", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListLabelsForMilestone lists labels for every issue in a milestone. +// +// GitHub API docs: http://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone +func (s *IssuesService) ListLabelsForMilestone(owner string, repo string, number int, opt *ListOptions) ([]Label, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d/labels", owner, repo, number) + 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 + } + + labels := new([]Label) + resp, err := s.client.Do(req, labels) + if err != nil { + return nil, resp, err + } + + return *labels, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go new file mode 100644 index 00000000000..2243eb0ee5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_labels_test.go @@ -0,0 +1,313 @@ +// 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" +) + +func TestIssuesService_ListLabels(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabels("o", "r", opt) + if err != nil { + t.Errorf("Issues.ListLabels returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabels returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabels_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabels("%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetLabel(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u", "name": "n", "color": "c"}`) + }) + + label, _, err := client.Issues.GetLabel("o", "r", "n") + if err != nil { + t.Errorf("Issues.GetLabel returned error: %v", err) + } + + want := &Label{URL: String("u"), Name: String("n"), Color: String("c")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.GetLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_GetLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.GetLabel("%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_CreateLabel(t *testing.T) { + setup() + defer teardown() + + input := &Label{Name: String("n")} + + mux.HandleFunc("/repos/o/r/labels", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + 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, `{"url":"u"}`) + }) + + label, _, err := client.Issues.CreateLabel("o", "r", input) + if err != nil { + t.Errorf("Issues.CreateLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.CreateLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_CreateLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.CreateLabel("%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditLabel(t *testing.T) { + setup() + defer teardown() + + input := &Label{Name: String("z")} + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + v := new(Label) + 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"}`) + }) + + label, _, err := client.Issues.EditLabel("o", "r", "n", input) + if err != nil { + t.Errorf("Issues.EditLabel returned error: %v", err) + } + + want := &Label{URL: String("u")} + if !reflect.DeepEqual(label, want) { + t.Errorf("Issues.EditLabel returned %+v, want %+v", label, want) + } +} + +func TestIssuesService_EditLabel_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditLabel("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteLabel(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/labels/n", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteLabel("o", "r", "n") + if err != nil { + t.Errorf("Issues.DeleteLabel returned error: %v", err) + } +} + +func TestIssuesService_DeleteLabel_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteLabel("%", "%", "%") + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsByIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsByIssue("o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsByIssue returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsByIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsByIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabelsByIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_AddLabelsToIssue(t *testing.T) { + setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + 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, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.AddLabelsToIssue("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.AddLabelsToIssue returned error: %v", err) + } + + want := []Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.AddLabelsToIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_AddLabelsToIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.AddLabelsToIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelForIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels/l", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.RemoveLabelForIssue("o", "r", 1, "l") + if err != nil { + t.Errorf("Issues.RemoveLabelForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelForIssue_invalidOwner(t *testing.T) { + _, err := client.Issues.RemoveLabelForIssue("%", "%", 1, "%") + testURLParseError(t, err) +} + +func TestIssuesService_ReplaceLabelsForIssue(t *testing.T) { + setup() + defer teardown() + + input := []string{"a", "b"} + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "PUT") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", v, input) + } + + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + labels, _, err := client.Issues.ReplaceLabelsForIssue("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.ReplaceLabelsForIssue returned error: %v", err) + } + + want := []Label{{URL: String("u")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ReplaceLabelsForIssue returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ReplaceLabelsForIssue_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ReplaceLabelsForIssue("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_RemoveLabelsForIssue(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.RemoveLabelsForIssue("o", "r", 1) + if err != nil { + t.Errorf("Issues.RemoveLabelsForIssue returned error: %v", err) + } +} + +func TestIssuesService_RemoveLabelsForIssue_invalidOwner(t *testing.T) { + _, err := client.Issues.RemoveLabelsForIssue("%", "%", 1) + testURLParseError(t, err) +} + +func TestIssuesService_ListLabelsForMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1/labels", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name": "a"},{"name": "b"}]`) + }) + + opt := &ListOptions{Page: 2} + labels, _, err := client.Issues.ListLabelsForMilestone("o", "r", 1, opt) + if err != nil { + t.Errorf("Issues.ListLabelsForMilestone returned error: %v", err) + } + + want := []Label{{Name: String("a")}, {Name: String("b")}} + if !reflect.DeepEqual(labels, want) { + t.Errorf("Issues.ListLabelsForMilestone returned %+v, want %+v", labels, want) + } +} + +func TestIssuesService_ListLabelsForMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListLabelsForMilestone("%", "%", 1, nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go new file mode 100644 index 00000000000..d5fd8aecc9f --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones.go @@ -0,0 +1,140 @@ +// Copyright 2014 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" + "time" +) + +// Milestone represents a Github repository milestone. +type Milestone struct { + URL *string `json:"url,omitempty"` + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + OpenIssues *int `json:"open_issues,omitempty"` + ClosedIssues *int `json:"closed_issues,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + DueOn *time.Time `json:"due_on,omitempty"` +} + +func (m Milestone) String() string { + return Stringify(m) +} + +// MilestoneListOptions specifies the optional parameters to the +// IssuesService.ListMilestones method. +type MilestoneListOptions struct { + // State filters milestones based on their state. Possible values are: + // open, closed. Default is "open". + State string `url:"state,omitempty"` + + // Sort specifies how to sort milestones. Possible values are: due_date, completeness. + // Default value is "due_date". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort milestones. Possible values are: asc, desc. + // Default is "asc". + Direction string `url:"direction,omitempty"` +} + +// ListMilestones lists all milestones for a repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository +func (s *IssuesService) ListMilestones(owner string, repo string, opt *MilestoneListOptions) ([]Milestone, *Response, error) { + u := fmt.Sprintf("/repos/%v/%v/milestones", owner, repo) + 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 + } + + milestones := new([]Milestone) + resp, err := s.client.Do(req, milestones) + if err != nil { + return nil, resp, err + } + + return *milestones, resp, err +} + +// GetMilestone gets a single milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#get-a-single-milestone +func (s *IssuesService) GetMilestone(owner string, repo string, number int) (*Milestone, *Response, error) { + u := fmt.Sprintf("/repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + milestone := new(Milestone) + resp, err := s.client.Do(req, milestone) + if err != nil { + return nil, resp, err + } + + return milestone, resp, err +} + +// CreateMilestone creates a new milestone on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#create-a-milestone +func (s *IssuesService) CreateMilestone(owner string, repo string, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("/repos/%v/%v/milestones", owner, repo) + req, err := s.client.NewRequest("POST", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// EditMilestone edits a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#update-a-milestone +func (s *IssuesService) EditMilestone(owner string, repo string, number int, milestone *Milestone) (*Milestone, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, milestone) + if err != nil { + return nil, nil, err + } + + m := new(Milestone) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} + +// DeleteMilestone deletes a milestone. +// +// GitHub API docs: https://developer.github.com/v3/issues/milestones/#delete-a-milestone +func (s *IssuesService) DeleteMilestone(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/milestones/%d", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go new file mode 100644 index 00000000000..817fffedd15 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_milestones_test.go @@ -0,0 +1,157 @@ +// Copyright 2014 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 TestIssuesService_ListMilestones(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "closed", + "sort": "due_date", + "direction": "asc", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &MilestoneListOptions{"closed", "due_date", "asc"} + milestones, _, err := client.Issues.ListMilestones("o", "r", opt) + if err != nil { + t.Errorf("IssuesService.ListMilestones returned error: %v", err) + } + + want := []Milestone{{Number: Int(1)}} + if !reflect.DeepEqual(milestones, want) { + t.Errorf("IssuesService.ListMilestones returned %+v, want %+v", milestones, want) + } +} + +func TestIssuesService_ListMilestones_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListMilestones("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_GetMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1}`) + }) + + milestone, _, err := client.Issues.GetMilestone("o", "r", 1) + if err != nil { + t.Errorf("IssuesService.GetMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.GetMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_GetMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.GetMilestone("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_CreateMilestone(t *testing.T) { + setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + 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, `{"number":1}`) + }) + + milestone, _, err := client.Issues.CreateMilestone("o", "r", input) + if err != nil { + t.Errorf("IssuesService.CreateMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.CreateMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_CreateMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.CreateMilestone("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_EditMilestone(t *testing.T) { + setup() + defer teardown() + + input := &Milestone{Title: String("t")} + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Milestone) + 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, `{"number":1}`) + }) + + milestone, _, err := client.Issues.EditMilestone("o", "r", 1, input) + if err != nil { + t.Errorf("IssuesService.EditMilestone returned error: %v", err) + } + + want := &Milestone{Number: Int(1)} + if !reflect.DeepEqual(milestone, want) { + t.Errorf("IssuesService.EditMilestone returned %+v, want %+v", milestone, want) + } +} + +func TestIssuesService_EditMilestone_invalidOwner(t *testing.T) { + _, _, err := client.Issues.EditMilestone("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestIssuesService_DeleteMilestone(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/milestones/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Issues.DeleteMilestone("o", "r", 1) + if err != nil { + t.Errorf("IssuesService.DeleteMilestone returned error: %v", err) + } +} + +func TestIssuesService_DeleteMilestone_invalidOwner(t *testing.T) { + _, err := client.Issues.DeleteMilestone("%", "r", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go new file mode 100644 index 00000000000..f69efd39983 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/issues_test.go @@ -0,0 +1,242 @@ +// 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 TestIssuesService_List_all(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "filter": "all", + "state": "closed", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + "page": "1", + "per_page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListOptions{ + "all", "closed", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{Page: 1, PerPage: 2}, + } + issues, _, err := client.Issues.List(true, opt) + + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_List_owned(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.List(false, nil) + if err != nil { + t.Errorf("Issues.List returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"number":1}]`) + }) + + issues, _, err := client.Issues.ListByOrg("o", nil) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByOrg_invalidOrg(t *testing.T) { + _, _, err := client.Issues.ListByOrg("%", nil) + testURLParseError(t, err) +} + +func TestIssuesService_ListByRepo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "milestone": "*", + "state": "closed", + "assignee": "a", + "creator": "c", + "mentioned": "m", + "labels": "a,b", + "sort": "updated", + "direction": "asc", + "since": "2002-02-10T15:30:00Z", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &IssueListByRepoOptions{ + "*", "closed", "a", "c", "m", []string{"a", "b"}, "updated", "asc", + time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions{0, 0}, + } + issues, _, err := client.Issues.ListByRepo("o", "r", opt) + if err != nil { + t.Errorf("Issues.ListByOrg returned error: %v", err) + } + + want := []Issue{{Number: Int(1)}} + if !reflect.DeepEqual(issues, want) { + t.Errorf("Issues.List returned %+v, want %+v", issues, want) + } +} + +func TestIssuesService_ListByRepo_invalidOwner(t *testing.T) { + _, _, err := client.Issues.ListByRepo("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"number":1, "labels": [{"url": "u", "name": "n", "color": "c"}]}`) + }) + + issue, _, err := client.Issues.Get("o", "r", 1) + if err != nil { + t.Errorf("Issues.Get returned error: %v", err) + } + + want := &Issue{ + Number: Int(1), + Labels: []Label{{ + URL: String("u"), + Name: String("n"), + Color: String("c"), + }}, + } + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Get returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Get_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Get("%", "r", 1) + testURLParseError(t, err) +} + +func TestIssuesService_Create(t *testing.T) { + setup() + defer teardown() + + input := &IssueRequest{ + Title: String("t"), + Body: String("b"), + Assignee: String("a"), + Labels: &[]string{"l1", "l2"}, + } + + mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + 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, `{"number":1}`) + }) + + issue, _, err := client.Issues.Create("o", "r", input) + if err != nil { + t.Errorf("Issues.Create returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Create returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Create_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Create("%", "r", nil) + testURLParseError(t, err) +} + +func TestIssuesService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &IssueRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { + v := new(IssueRequest) + 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, `{"number":1}`) + }) + + issue, _, err := client.Issues.Edit("o", "r", 1, input) + if err != nil { + t.Errorf("Issues.Edit returned error: %v", err) + } + + want := &Issue{Number: Int(1)} + if !reflect.DeepEqual(issue, want) { + t.Errorf("Issues.Edit returned %+v, want %+v", issue, want) + } +} + +func TestIssuesService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.Issues.Edit("%", "r", 1, nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/licenses.go b/Godeps/_workspace/src/github.com/google/go-github/github/licenses.go new file mode 100644 index 00000000000..d2a5a506691 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/licenses.go @@ -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 +} + +// Fetch 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 +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/licenses_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/licenses_test.go new file mode 100644 index 00000000000..72f6633b2bb --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/licenses_test.go @@ -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{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) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/misc.go b/Godeps/_workspace/src/github.com/google/go-github/github/misc.go new file mode 100644 index 00000000000..d501405b4e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/misc.go @@ -0,0 +1,193 @@ +// Copyright 2014 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 ( + "bytes" + "fmt" + "net/url" +) + +// MarkdownOptions specifies optional parameters to the Markdown method. +type MarkdownOptions struct { + // Mode identifies the rendering mode. Possible values are: + // markdown - render a document as plain Markdown, just like + // README files are rendered. + // + // gfm - to render a document as user-content, e.g. like user + // comments or issues are rendered. In GFM mode, hard line breaks are + // always taken into account, and issue and user mentions are linked + // accordingly. + // + // Default is "markdown". + Mode string + + // Context identifies the repository context. Only taken into account + // when rendering as "gfm". + Context string +} + +type markdownRequest struct { + Text *string `json:"text,omitempty"` + Mode *string `json:"mode,omitempty"` + Context *string `json:"context,omitempty"` +} + +// Markdown renders an arbitrary Markdown document. +// +// GitHub API docs: https://developer.github.com/v3/markdown/ +func (c *Client) Markdown(text string, opt *MarkdownOptions) (string, *Response, error) { + request := &markdownRequest{Text: String(text)} + if opt != nil { + if opt.Mode != "" { + request.Mode = String(opt.Mode) + } + if opt.Context != "" { + request.Context = String(opt.Context) + } + } + + req, err := c.NewRequest("POST", "markdown", request) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// ListEmojis returns the emojis available to use on GitHub. +// +// GitHub API docs: https://developer.github.com/v3/emojis/ +func (c *Client) ListEmojis() (map[string]string, *Response, error) { + req, err := c.NewRequest("GET", "emojis", nil) + if err != nil { + return nil, nil, err + } + + var emoji map[string]string + resp, err := c.Do(req, &emoji) + if err != nil { + return nil, resp, err + } + + return emoji, resp, nil +} + +// APIMeta represents metadata about the GitHub API. +type APIMeta struct { + // An Array of IP addresses in CIDR format specifying the addresses + // that incoming service hooks will originate from on GitHub.com. + Hooks []string `json:"hooks,omitempty"` + + // An Array of IP addresses in CIDR format specifying the Git servers + // for GitHub.com. + Git []string `json:"git,omitempty"` + + // Whether authentication with username and password is supported. + // (GitHub Enterprise instances using CAS or OAuth for authentication + // will return false. Features like Basic Authentication with a + // username and password, sudo mode, and two-factor authentication are + // not supported on these servers.) + VerifiablePasswordAuthentication *bool `json:"verifiable_password_authentication,omitempty"` +} + +// APIMeta returns information about GitHub.com, the service. Or, if you access +// this endpoint on your organization’s GitHub Enterprise installation, this +// endpoint provides information about that installation. +// +// GitHub API docs: https://developer.github.com/v3/meta/ +func (c *Client) APIMeta() (*APIMeta, *Response, error) { + req, err := c.NewRequest("GET", "meta", nil) + if err != nil { + return nil, nil, err + } + + meta := new(APIMeta) + resp, err := c.Do(req, meta) + if err != nil { + return nil, resp, err + } + + return meta, resp, nil +} + +// Octocat returns an ASCII art octocat with the specified message in a speech +// bubble. If message is empty, a random zen phrase is used. +func (c *Client) Octocat(message string) (string, *Response, error) { + u := "octocat" + if message != "" { + u = fmt.Sprintf("%s?s=%s", u, url.QueryEscape(message)) + } + + req, err := c.NewRequest("GET", u, nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + return buf.String(), resp, nil +} + +// Zen returns a random line from The Zen of GitHub. +// +// see also: http://warpspire.com/posts/taste/ +func (c *Client) Zen() (string, *Response, error) { + req, err := c.NewRequest("GET", "zen", nil) + if err != nil { + return "", nil, err + } + + buf := new(bytes.Buffer) + resp, err := c.Do(req, buf) + if err != nil { + return "", resp, err + } + + 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 +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go new file mode 100644 index 00000000000..438f28f34ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/misc_test.go @@ -0,0 +1,169 @@ +// Copyright 2014 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 TestMarkdown(t *testing.T) { + setup() + defer teardown() + + input := &markdownRequest{ + Text: String("# text #"), + Mode: String("gfm"), + Context: String("google/go-github"), + } + mux.HandleFunc("/markdown", func(w http.ResponseWriter, r *http.Request) { + v := new(markdownRequest) + 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, `

text

`) + }) + + md, _, err := client.Markdown("# text #", &MarkdownOptions{ + Mode: "gfm", + Context: "google/go-github", + }) + if err != nil { + t.Errorf("Markdown returned error: %v", err) + } + + if want := "

text

"; want != md { + t.Errorf("Markdown returned %+v, want %+v", md, want) + } +} + +func TestListEmojis(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/emojis", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"+1": "+1.png"}`) + }) + + emoji, _, err := client.ListEmojis() + if err != nil { + t.Errorf("ListEmojis returned error: %v", err) + } + + want := map[string]string{"+1": "+1.png"} + if !reflect.DeepEqual(want, emoji) { + t.Errorf("ListEmojis returned %+v, want %+v", emoji, want) + } +} + +func TestAPIMeta(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"hooks":["h"], "git":["g"], "verifiable_password_authentication": true}`) + }) + + meta, _, err := client.APIMeta() + if err != nil { + t.Errorf("APIMeta returned error: %v", err) + } + + want := &APIMeta{ + Hooks: []string{"h"}, + Git: []string{"g"}, + VerifiablePasswordAuthentication: Bool(true), + } + if !reflect.DeepEqual(want, meta) { + t.Errorf("APIMeta returned %+v, want %+v", meta, want) + } +} + +func TestOctocat(t *testing.T) { + setup() + defer teardown() + + input := "input" + output := "sample text" + + mux.HandleFunc("/octocat", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"s": input}) + w.Header().Set("Content-Type", "application/octocat-stream") + fmt.Fprint(w, output) + }) + + got, _, err := client.Octocat(input) + if err != nil { + t.Errorf("Octocat returned error: %v", err) + } + + if want := output; got != want { + t.Errorf("Octocat returned %+v, want %+v", got, want) + } +} + +func TestZen(t *testing.T) { + setup() + defer teardown() + + output := "sample text" + + mux.HandleFunc("/zen", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + fmt.Fprint(w, output) + }) + + got, _, err := client.Zen() + if err != nil { + t.Errorf("Zen returned error: %v", err) + } + + if want := output; 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) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs.go new file mode 100644 index 00000000000..7596873cbb9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs.go @@ -0,0 +1,137 @@ +// 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" + "time" +) + +// OrganizationsService provides access to the organization related functions +// in the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/orgs/ +type OrganizationsService struct { + client *Client +} + +// Organization represents a GitHub organization account. +type Organization struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + BillingEmail *string `json:"billing_email,omitempty"` + Type *string `json:"type,omitempty"` + Plan *Plan `json:"plan,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + MembersURL *string `json:"members_url,omitempty"` + PublicMembersURL *string `json:"public_members_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` +} + +func (o Organization) String() string { + return Stringify(o) +} + +// Plan represents the payment plan for an account. See plans at https://github.com/plans. +type Plan struct { + Name *string `json:"name,omitempty"` + Space *int `json:"space,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + PrivateRepos *int `json:"private_repos,omitempty"` +} + +func (p Plan) String() string { + return Stringify(p) +} + +// List the organizations for a user. Passing the empty string will list +// organizations for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#list-user-organizations +func (s *OrganizationsService) List(user string, opt *ListOptions) ([]Organization, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/orgs", user) + } else { + u = "user/orgs" + } + 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 + } + + orgs := new([]Organization) + resp, err := s.client.Do(req, orgs) + if err != nil { + return nil, resp, err + } + + return *orgs, resp, err +} + +// Get fetches an organization by name. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#get-an-organization +func (s *OrganizationsService) Get(org string) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", org) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + organization := new(Organization) + resp, err := s.client.Do(req, organization) + if err != nil { + return nil, resp, err + } + + return organization, resp, err +} + +// Edit an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/#edit-an-organization +func (s *OrganizationsService) Edit(name string, org *Organization) (*Organization, *Response, error) { + u := fmt.Sprintf("orgs/%v", name) + req, err := s.client.NewRequest("PATCH", u, org) + if err != nil { + return nil, nil, err + } + + o := new(Organization) + resp, err := s.client.Do(req, o) + if err != nil { + return nil, resp, err + } + + return o, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks.go new file mode 100644 index 00000000000..3e7ad40ff2a --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks_test.go new file mode 100644 index 00000000000..1ebc07d5a45 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_hooks_test.go @@ -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) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go new file mode 100644 index 00000000000..64dcfcc3d0a --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members.go @@ -0,0 +1,221 @@ +// 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" + +// Membership represents the status of a user's membership in an organization or team. +type Membership struct { + URL *string `json:"url,omitempty"` + + // State is the user's status within the organization or team. + // Possible values are: "active", "pending" + State *string `json:"state,omitempty"` + + // TODO(willnorris): add docs + Role *string `json:"role,omitempty"` + + // For organization membership, the API URL of the organization. + OrganizationURL *string `json:"organization_url,omitempty"` + + // For organization membership, the organization the membership is for. + Organization *Organization `json:"organization,omitempty"` + + // For organization membership, the user the membership is for. + User *User `json:"user,omitempty"` +} + +func (m Membership) String() string { + return Stringify(m) +} + +// ListMembersOptions specifies optional parameters to the +// OrganizationsService.ListMembers method. +type ListMembersOptions struct { + // If true (or if the authenticated user is not an owner of the + // organization), list only publicly visible members. + PublicOnly bool `url:"-"` + + // Filter members returned in the list. Possible values are: + // 2fa_disabled, all. Default is "all". + Filter string `url:"filter,omitempty"` + + ListOptions +} + +// ListMembers lists the members for an organization. If the authenticated +// user is an owner of the organization, this will return both concealed and +// public members, otherwise it will only return public members. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#members-list +func (s *OrganizationsService) ListMembers(org string, opt *ListMembersOptions) ([]User, *Response, error) { + var u string + if opt != nil && opt.PublicOnly { + u = fmt.Sprintf("orgs/%v/public_members", org) + } else { + u = fmt.Sprintf("orgs/%v/members", 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 + } + + members := new([]User) + resp, err := s.client.Do(req, members) + if err != nil { + return nil, resp, err + } + + return *members, resp, err +} + +// IsMember checks if a user is a member of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-membership +func (s *OrganizationsService) IsMember(org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// IsPublicMember checks if a user is a public member of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#check-public-membership +func (s *OrganizationsService) IsPublicMember(org, user string) (bool, *Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// RemoveMember removes a user from all teams of an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#remove-a-member +func (s *OrganizationsService) RemoveMember(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// PublicizeMembership publicizes a user's membership in an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#publicize-a-users-membership +func (s *OrganizationsService) PublicizeMembership(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ConcealMembership conceals a user's membership in an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/members/#conceal-a-users-membership +func (s *OrganizationsService) ConcealMembership(org, user string) (*Response, error) { + u := fmt.Sprintf("orgs/%v/public_members/%v", org, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListOrgMembershipsOptions specifies optional parameters to the +// OrganizationsService.ListOrgMemberships method. +type ListOrgMembershipsOptions struct { + // Filter memberships to include only those withe the specified state. + // Possible values are: "active", "pending". + State string `url:"state,omitempty"` + + ListOptions +} + +// ListOrgMemberships lists the organization memberships for the authenticated user. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#list-your-organization-memberships +func (s *OrganizationsService) ListOrgMemberships(opt *ListOrgMembershipsOptions) ([]Membership, *Response, error) { + u := "user/memberships/orgs" + 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 + } + + var memberships []Membership + resp, err := s.client.Do(req, &memberships) + if err != nil { + return nil, resp, err + } + + return memberships, resp, err +} + +// GetOrgMembership gets the membership for the authenticated user for the +// specified organization. +// +// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership +func (s *OrganizationsService) GetOrgMembership(org string) (*Membership, *Response, error) { + u := fmt.Sprintf("user/memberships/orgs/%v", org) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + membership := new(Membership) + resp, err := s.client.Do(req, membership) + if err != nil { + return nil, resp, err + } + + return membership, resp, err +} + +// EditOrgMembership edits the membership for the authenticated user for the +// specified organization. +// +// 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) { + u := fmt.Sprintf("user/memberships/orgs/%v", org) + req, err := s.client.NewRequest("PATCH", u, membership) + if err != nil { + return nil, nil, err + } + + m := new(Membership) + resp, err := s.client.Do(req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go new file mode 100644 index 00000000000..493488aa622 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_members_test.go @@ -0,0 +1,289 @@ +// 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" +) + +func TestOrganizationsService_ListMembers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "filter": "2fa_disabled", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{ + PublicOnly: false, + Filter: "2fa_disabled", + ListOptions: ListOptions{Page: 2}, + } + members, _, err := client.Organizations.ListMembers("o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_ListMembers_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.ListMembers("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_ListMembers_public(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListMembersOptions{PublicOnly: true} + members, _, err := client.Organizations.ListMembers("o", opt) + if err != nil { + t.Errorf("Organizations.ListMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsMember_notMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsMember("o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsMember_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.IsMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_IsPublicMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsPublicMember_notMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err != nil { + t.Errorf("Organizations.IsPublicMember returned error: %v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsPublicMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsPublicMember("o", "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsPublicMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsPublicMember_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.IsPublicMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveMember(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.RemoveMember("o", "u") + if err != nil { + t.Errorf("Organizations.RemoveMember returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveMember_invalidOrg(t *testing.T) { + _, err := client.Organizations.RemoveMember("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ListOrgMemberships(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "active", + "page": "2", + }) + fmt.Fprint(w, `[{"url":"u"}]`) + }) + + opt := &ListOrgMembershipsOptions{ + State: "active", + ListOptions: ListOptions{Page: 2}, + } + memberships, _, err := client.Organizations.ListOrgMemberships(opt) + if err != nil { + t.Errorf("Organizations.ListOrgMemberships returned error: %v", err) + } + + want := []Membership{{URL: String("u")}} + if !reflect.DeepEqual(memberships, want) { + t.Errorf("Organizations.ListOrgMemberships returned %+v, want %+v", memberships, want) + } +} + +func TestOrganizationsService_GetOrgMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/memberships/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u"}`) + }) + + membership, _, err := client.Organizations.GetOrgMembership("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(t *testing.T) { + setup() + defer teardown() + + input := &Membership{State: String("active")} + + mux.HandleFunc("/user/memberships/orgs/o", 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("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) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams.go new file mode 100644 index 00000000000..dd01a7b22d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams.go @@ -0,0 +1,317 @@ +// 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" + +// Team represents a team within a GitHub organization. Teams are used to +// manage access to an organization's repositories. +type Team struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + URL *string `json:"url,omitempty"` + Slug *string `json:"slug,omitempty"` + Permission *string `json:"permission,omitempty"` + MembersCount *int `json:"members_count,omitempty"` + ReposCount *int `json:"repos_count,omitempty"` + Organization *Organization `json:"organization,omitempty"` +} + +func (t Team) String() string { + return Stringify(t) +} + +// ListTeams lists all of the teams for an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-teams +func (s *OrganizationsService) ListTeams(org string, opt *ListOptions) ([]Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", 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 + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// GetTeam fetches a team by ID. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team +func (s *OrganizationsService) GetTeam(team int) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// CreateTeam creates a new team within an organization. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#create-team +func (s *OrganizationsService) CreateTeam(org string, team *Team) (*Team, *Response, error) { + u := fmt.Sprintf("orgs/%v/teams", org) + req, err := s.client.NewRequest("POST", u, team) + if err != nil { + return nil, nil, err + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// EditTeam edits a team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#edit-team +func (s *OrganizationsService) EditTeam(id int, team *Team) (*Team, *Response, error) { + u := fmt.Sprintf("teams/%v", id) + req, err := s.client.NewRequest("PATCH", u, team) + if err != nil { + return nil, nil, err + } + + t := new(Team) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// DeleteTeam deletes a team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#delete-team +func (s *OrganizationsService) DeleteTeam(team int) (*Response, error) { + u := fmt.Sprintf("teams/%v", team) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListTeamMembers lists all of the users who are members of the specified +// team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-members +func (s *OrganizationsService) ListTeamMembers(team int, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("teams/%v/members", team) + 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 + } + + members := new([]User) + resp, err := s.client.Do(req, members) + if err != nil { + return nil, resp, err + } + + return *members, resp, err +} + +// IsTeamMember checks if a user is a member of the specified team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#get-team-member +func (s *OrganizationsService) IsTeamMember(team int, user string) (bool, *Response, error) { + u := fmt.Sprintf("teams/%v/members/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + member, err := parseBoolResponse(err) + return member, resp, err +} + +// ListTeamRepos lists the repositories that the specified team has access to. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#list-team-repos +func (s *OrganizationsService) ListTeamRepos(team int, opt *ListOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("teams/%v/repos", team) + 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 + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// IsTeamRepo checks if a team manages the specified repository. +// +// 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) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + manages, err := parseBoolResponse(err) + return manages, resp, err +} + +// AddTeamRepo adds a repository to be managed by the specified team. The +// specified repository must be owned by the organization to which the team +// 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 +func (s *OrganizationsService) AddTeamRepo(team int, owner string, repo string) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// RemoveTeamRepo removes a repository from being managed by the specified +// team. Note that this does not delete the repository, it just removes it +// from the team. +// +// GitHub API docs: http://developer.github.com/v3/orgs/teams/#remove-team-repo +func (s *OrganizationsService) RemoveTeamRepo(team int, owner string, repo string) (*Response, error) { + u := fmt.Sprintf("teams/%v/repos/%v/%v", team, owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// ListUserTeams lists a user's teams +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams +func (s *OrganizationsService) ListUserTeams(opt *ListOptions) ([]Team, *Response, error) { + u := "user/teams" + 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 + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// GetTeamMembership returns the membership status for a user in a team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership +func (s *OrganizationsService) GetTeamMembership(team int, user string) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Membership) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// AddTeamMembership adds or invites a user to a team. +// +// In order to add a membership between a user and a team, the authenticated +// user must have 'admin' permissions to the team or be an owner of the +// organization that the team is associated with. +// +// If the user is already a part of the team's organization (meaning they're on +// at least one other team in the organization), this endpoint will add the +// user to the team. +// +// If the user is completely unaffiliated with the team's organization (meaning +// they're on none of the organization's teams), this endpoint will send an +// invitation to the user via email. This newly-created membership will be in +// the "pending" state until the user accepts the invitation, at which point +// the membership will transition to the "active" state and the user will be +// added as a member of the team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#add-team-membership +func (s *OrganizationsService) AddTeamMembership(team int, user string) (*Membership, *Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, nil, err + } + + t := new(Membership) + resp, err := s.client.Do(req, t) + if err != nil { + return nil, resp, err + } + + return t, resp, err +} + +// RemoveTeamMembership removes a user from a team. +// +// GitHub API docs: https://developer.github.com/v3/orgs/teams/#remove-team-membership +func (s *OrganizationsService) RemoveTeamMembership(team int, user string) (*Response, error) { + u := fmt.Sprintf("teams/%v/memberships/%v", team, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams_test.go new file mode 100644 index 00000000000..1b18b623f01 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_teams_test.go @@ -0,0 +1,474 @@ +// 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" +) + +func TestOrganizationsService_ListTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Organizations.ListTeams("o", opt) + if err != nil { + t.Errorf("Organizations.ListTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Organizations.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestOrganizationsService_ListTeams_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.ListTeams("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_GetTeam(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "name":"n", "url":"u", "slug": "s", "permission":"p"}`) + }) + + team, _, err := client.Organizations.GetTeam(1) + if err != nil { + t.Errorf("Organizations.GetTeam returned error: %v", err) + } + + want := &Team{ID: Int(1), Name: String("n"), URL: String("u"), Slug: String("s"), Permission: String("p")} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.GetTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_CreateTeam(t *testing.T) { + setup() + defer teardown() + + input := &Team{Name: String("n")} + + mux.HandleFunc("/orgs/o/teams", func(w http.ResponseWriter, r *http.Request) { + v := new(Team) + 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}`) + }) + + team, _, err := client.Organizations.CreateTeam("o", input) + if err != nil { + t.Errorf("Organizations.CreateTeam returned error: %v", err) + } + + want := &Team{ID: Int(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.CreateTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_CreateTeam_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.CreateTeam("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_EditTeam(t *testing.T) { + setup() + defer teardown() + + input := &Team{Name: String("n")} + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Team) + 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}`) + }) + + team, _, err := client.Organizations.EditTeam(1, input) + if err != nil { + t.Errorf("Organizations.EditTeam returned error: %v", err) + } + + want := &Team{ID: Int(1)} + if !reflect.DeepEqual(team, want) { + t.Errorf("Organizations.EditTeam returned %+v, want %+v", team, want) + } +} + +func TestOrganizationsService_DeleteTeam(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Organizations.DeleteTeam(1) + if err != nil { + t.Errorf("Organizations.DeleteTeam returned error: %v", err) + } +} + +func TestOrganizationsService_ListTeamMembers(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + members, _, err := client.Organizations.ListTeamMembers(1, opt) + if err != nil { + t.Errorf("Organizations.ListTeamMembers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListTeamMembers returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsTeamMember_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err != nil { + t.Errorf("Organizations.IsTeamMember returned error: %v", err) + } + if want := true; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 404 response is interpreted as "false" and not an error +func TestOrganizationsService_IsTeamMember_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err != nil { + t.Errorf("Organizations.IsTeamMember returned error: %+v", err) + } + if want := false; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +// ensure that a 400 response is interpreted as an actual error, and not simply +// as "false" like the above case of a 404 +func TestOrganizationsService_IsTeamMember_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + member, _, err := client.Organizations.IsTeamMember(1, "u") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; member != want { + t.Errorf("Organizations.IsTeamMember returned %+v, want %+v", member, want) + } +} + +func TestOrganizationsService_IsTeamMember_invalidUser(t *testing.T) { + _, _, err := client.Organizations.IsTeamMember(1, "%") + testURLParseError(t, err) +} + +func TestOrganizationsService_PublicizeMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.PublicizeMembership("o", "u") + if err != nil { + t.Errorf("Organizations.PublicizeMembership returned error: %v", err) + } +} + +func TestOrganizationsService_PublicizeMembership_invalidOrg(t *testing.T) { + _, err := client.Organizations.PublicizeMembership("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ConcealMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/public_members/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.ConcealMembership("o", "u") + if err != nil { + t.Errorf("Organizations.ConcealMembership returned error: %v", err) + } +} + +func TestOrganizationsService_ConcealMembership_invalidOrg(t *testing.T) { + _, err := client.Organizations.ConcealMembership("%", "u") + testURLParseError(t, err) +} + +func TestOrganizationsService_ListTeamRepos(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + members, _, err := client.Organizations.ListTeamRepos(1, opt) + if err != nil { + t.Errorf("Organizations.ListTeamRepos returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(members, want) { + t.Errorf("Organizations.ListTeamRepos returned %+v, want %+v", members, want) + } +} + +func TestOrganizationsService_IsTeamRepo_true(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.IsTeamRepo returned error: %v", err) + } + if want := true; managed != want { + t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) + } +} + +func TestOrganizationsService_IsTeamRepo_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.IsTeamRepo returned error: %v", err) + } + if want := false; managed != want { + t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) + } +} + +func TestOrganizationsService_IsTeamRepo_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + managed, _, err := client.Organizations.IsTeamRepo(1, "o", "r") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; managed != want { + t.Errorf("Organizations.IsTeamRepo returned %+v, want %+v", managed, want) + } +} + +func TestOrganizationsService_IsTeamRepo_invalidOwner(t *testing.T) { + _, _, err := client.Organizations.IsTeamRepo(1, "%", "r") + testURLParseError(t, err) +} + +func TestOrganizationsService_AddTeamRepo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.AddTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.AddTeamRepo returned error: %v", err) + } +} + +func TestOrganizationsService_AddTeamRepo_noAccess(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(422) + }) + + _, err := client.Organizations.AddTeamRepo(1, "o", "r") + if err == nil { + t.Errorf("Expcted error to be returned") + } +} + +func TestOrganizationsService_AddTeamRepo_invalidOwner(t *testing.T) { + _, err := client.Organizations.AddTeamRepo(1, "%", "r") + testURLParseError(t, err) +} + +func TestOrganizationsService_RemoveTeamRepo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveTeamRepo(1, "o", "r") + if err != nil { + t.Errorf("Organizations.RemoveTeamRepo returned error: %v", err) + } +} + +func TestOrganizationsService_RemoveTeamRepo_invalidOwner(t *testing.T) { + _, err := client.Organizations.RemoveTeamRepo(1, "%", "r") + testURLParseError(t, err) +} + +func TestOrganizationsService_GetTeamMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u", "state":"active"}`) + }) + + membership, _, err := client.Organizations.GetTeamMembership(1, "u") + if err != nil { + t.Errorf("Organizations.GetTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("active")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.GetTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_AddTeamMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{"url":"u", "state":"pending"}`) + }) + + membership, _, err := client.Organizations.AddTeamMembership(1, "u") + if err != nil { + t.Errorf("Organizations.AddTeamMembership returned error: %v", err) + } + + want := &Membership{URL: String("u"), State: String("pending")} + if !reflect.DeepEqual(membership, want) { + t.Errorf("Organizations.AddTeamMembership returned %+v, want %+v", membership, want) + } +} + +func TestOrganizationsService_RemoveTeamMembership(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/teams/1/memberships/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Organizations.RemoveTeamMembership(1, "u") + if err != nil { + t.Errorf("Organizations.RemoveTeamMembership returned error: %v", err) + } +} + +func TestOrganizationsService_ListUserTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "1"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 1} + teams, _, err := client.Organizations.ListUserTeams(opt) + if err != nil { + t.Errorf("Organizations.ListUserTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Organizations.ListUserTeams returned %+v, want %+v", teams, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/orgs_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_test.go new file mode 100644 index 00000000000..84ebc546833 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/orgs_test.go @@ -0,0 +1,120 @@ +// 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" +) + +func TestOrganizationsService_List_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/orgs", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + orgs, _, err := client.Organizations.List("", nil) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []Organization{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/orgs", 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} + orgs, _, err := client.Organizations.List("u", opt) + if err != nil { + t.Errorf("Organizations.List returned error: %v", err) + } + + want := []Organization{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(orgs, want) { + t.Errorf("Organizations.List returned %+v, want %+v", orgs, want) + } +} + +func TestOrganizationsService_List_invalidUser(t *testing.T) { + _, _, err := client.Organizations.List("%", nil) + testURLParseError(t, err) +} + +func TestOrganizationsService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1, "login":"l", "url":"u", "avatar_url": "a", "location":"l"}`) + }) + + org, _, err := client.Organizations.Get("o") + if err != nil { + t.Errorf("Organizations.Get returned error: %v", err) + } + + want := &Organization{ID: Int(1), Login: String("l"), URL: String("u"), AvatarURL: String("a"), Location: String("l")} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Get returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Get_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.Get("%") + testURLParseError(t, err) +} + +func TestOrganizationsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &Organization{Login: String("l")} + + mux.HandleFunc("/orgs/o", func(w http.ResponseWriter, r *http.Request) { + v := new(Organization) + 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}`) + }) + + org, _, err := client.Organizations.Edit("o", input) + if err != nil { + t.Errorf("Organizations.Edit returned error: %v", err) + } + + want := &Organization{ID: Int(1)} + if !reflect.DeepEqual(org, want) { + t.Errorf("Organizations.Edit returned %+v, want %+v", org, want) + } +} + +func TestOrganizationsService_Edit_invalidOrg(t *testing.T) { + _, _, err := client.Organizations.Edit("%", nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/pulls.go b/Godeps/_workspace/src/github.com/google/go-github/github/pulls.go new file mode 100644 index 00000000000..71cf2e2484e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/pulls.go @@ -0,0 +1,275 @@ +// 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" + "time" +) + +// PullRequestsService handles communication with the pull request related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/pulls/ +type PullRequestsService struct { + client *Client +} + +// PullRequest represents a GitHub pull request on a repository. +type PullRequest struct { + Number *int `json:"number,omitempty"` + State *string `json:"state,omitempty"` + Title *string `json:"title,omitempty"` + Body *string `json:"body,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + MergedAt *time.Time `json:"merged_at,omitempty"` + User *User `json:"user,omitempty"` + Merged *bool `json:"merged,omitempty"` + Mergeable *bool `json:"mergeable,omitempty"` + MergedBy *User `json:"merged_by,omitempty"` + Comments *int `json:"comments,omitempty"` + Commits *int `json:"commits,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + ChangedFiles *int `json:"changed_files,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + IssueURL *string `json:"issue_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"` + Base *PullRequestBranch `json:"base,omitempty"` +} + +func (p PullRequest) String() string { + return Stringify(p) +} + +// PullRequestBranch represents a base or head branch in a GitHub pull request. +type PullRequestBranch struct { + Label *string `json:"label,omitempty"` + Ref *string `json:"ref,omitempty"` + SHA *string `json:"sha,omitempty"` + Repo *Repository `json:"repo,omitempty"` + User *User `json:"user,omitempty"` +} + +// PullRequestListOptions specifies the optional parameters to the +// PullRequestsService.List method. +type PullRequestListOptions struct { + // State filters pull requests based on their state. Possible values are: + // open, closed. Default is "open". + State string `url:"state,omitempty"` + + // Head filters pull requests by head user and branch name in the format of: + // "user:ref-name". + Head string `url:"head,omitempty"` + + // Base filters pull requests by base branch name. + 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 +} + +// List the pull requests for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/pulls/#list-pull-requests +func (s *PullRequestsService) List(owner string, repo string, opt *PullRequestListOptions) ([]PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + 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 + } + + pulls := new([]PullRequest) + resp, err := s.client.Do(req, pulls) + if err != nil { + return nil, resp, err + } + + return *pulls, resp, err +} + +// Get a single pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request +func (s *PullRequestsService) Get(owner string, repo string, number int) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + pull := new(PullRequest) + resp, err := s.client.Do(req, pull) + if err != nil { + return nil, resp, err + } + + return pull, resp, err +} + +// NewPullRequest represents a new pull request to be created. +type NewPullRequest struct { + Title *string `json:"title,omitempty"` + Head *string `json:"head,omitempty"` + Base *string `json:"base,omitempty"` + Body *string `json:"body,omitempty"` + Issue *int `json:"issue,omitempty"` +} + +// Create a new pull request on the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#create-a-pull-request +func (s *PullRequestsService) Create(owner string, repo string, pull *NewPullRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls", owner, repo) + req, err := s.client.NewRequest("POST", u, pull) + if err != nil { + return nil, nil, err + } + + p := new(PullRequest) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// Edit a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#update-a-pull-request +func (s *PullRequestsService) Edit(owner string, repo string, number int, pull *PullRequest) (*PullRequest, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, pull) + if err != nil { + return nil, nil, err + } + + p := new(PullRequest) + resp, err := s.client.Do(req, p) + if err != nil { + return nil, resp, err + } + + return p, resp, err +} + +// ListCommits lists the commits in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request +func (s *PullRequestsService) ListCommits(owner string, repo string, number int, opt *ListOptions) ([]RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/commits", owner, repo, number) + 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 + } + + commits := new([]RepositoryCommit) + resp, err := s.client.Do(req, commits) + if err != nil { + return nil, resp, err + } + + return *commits, resp, err +} + +// ListFiles lists the files in a pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files +func (s *PullRequestsService) ListFiles(owner string, repo string, number int, opt *ListOptions) ([]CommitFile, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/files", owner, repo, number) + 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 + } + + commitFiles := new([]CommitFile) + resp, err := s.client.Do(req, commitFiles) + if err != nil { + return nil, resp, err + } + + return *commitFiles, resp, err +} + +// IsMerged checks if a pull request has been merged. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged +func (s *PullRequestsService) IsMerged(owner string, repo string, number int) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + merged, err := parseBoolResponse(err) + return merged, resp, err +} + +// PullRequestMergeResult represents the result of merging a pull request. +type PullRequestMergeResult struct { + SHA *string `json:"sha,omitempty"` + Merged *bool `json:"merged,omitempty"` + Message *string `json:"message,omitempty"` +} + +type pullRequestMergeRequest struct { + CommitMessage *string `json:"commit_message"` +} + +// Merge a pull request (Merge Button™). +// +// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade +func (s *PullRequestsService) Merge(owner string, repo string, number int, commitMessage string) (*PullRequestMergeResult, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) + + req, err := s.client.NewRequest("PUT", u, &pullRequestMergeRequest{ + CommitMessage: &commitMessage, + }) + + if err != nil { + return nil, nil, err + } + + mergeResult := new(PullRequestMergeResult) + resp, err := s.client.Do(req, mergeResult) + if err != nil { + return nil, resp, err + } + + return mergeResult, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments.go b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments.go new file mode 100644 index 00000000000..bfbad9af2d1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments.go @@ -0,0 +1,142 @@ +// 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" + "time" +) + +// PullRequestComment represents a comment left on a pull request. +type PullRequestComment struct { + ID *int `json:"id,omitempty"` + Body *string `json:"body,omitempty"` + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (p PullRequestComment) String() string { + return Stringify(p) +} + +// PullRequestListCommentsOptions specifies the optional parameters to the +// PullRequestsService.ListComments method. +type PullRequestListCommentsOptions struct { + // Sort specifies how to sort comments. Possible values are: created, updated. + Sort string `url:"sort,omitempty"` + + // Direction in which to sort comments. Possible values are: asc, desc. + Direction string `url:"direction,omitempty"` + + // Since filters comments by time. + Since time.Time `url:"since,omitempty"` + + ListOptions +} + +// ListComments lists all comments on the specified pull request. Specifying a +// pull request number of 0 will return all comments on all pull requests for +// the repository. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request +func (s *PullRequestsService) ListComments(owner string, repo string, number int, opt *PullRequestListCommentsOptions) ([]PullRequestComment, *Response, error) { + var u string + if number == 0 { + u = fmt.Sprintf("repos/%v/%v/pulls/comments", owner, repo) + } else { + u = fmt.Sprintf("repos/%v/%v/pulls/%d/comments", owner, repo, number) + } + 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 + } + + comments := new([]PullRequestComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// GetComment fetches the specified pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment +func (s *PullRequestsService) GetComment(owner string, repo string, number int) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comment := new(PullRequestComment) + resp, err := s.client.Do(req, comment) + if err != nil { + return nil, resp, err + } + + return comment, resp, err +} + +// CreateComment creates a new comment on the specified pull request. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#get-a-single-comment +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) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditComment updates a pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#edit-a-comment +func (s *PullRequestsService) EditComment(owner string, repo string, number int, comment *PullRequestComment) (*PullRequestComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(PullRequestComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a pull request comment. +// +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#delete-a-comment +func (s *PullRequestsService) DeleteComment(owner string, repo string, number int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%d", owner, repo, number) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments_test.go new file mode 100644 index 00000000000..7885ab15813 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_comments_test.go @@ -0,0 +1,189 @@ +// 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 TestPullRequestsService_ListComments_allPulls(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "updated", + "direction": "desc", + "since": "2002-02-10T15:30:00Z", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &PullRequestListCommentsOptions{ + Sort: "updated", + Direction: "desc", + Since: time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), + ListOptions: ListOptions{Page: 2}, + } + pulls, _, err := client.PullRequests.ListComments("o", "r", 0, opt) + + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []PullRequestComment{{ID: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_specificPull(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + pulls, _, err := client.PullRequests.ListComments("o", "r", 1, nil) + + if err != nil { + t.Errorf("PullRequests.ListComments returned error: %v", err) + } + + want := []PullRequestComment{{ID: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.ListComments returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.ListComments("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.PullRequests.GetComment("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.GetComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_GetComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.GetComment("%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/1/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + 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}`) + }) + + comment, _, err := client.PullRequests.CreateComment("o", "r", 1, input) + + if err != nil { + t.Errorf("PullRequests.CreateComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_CreateComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.CreateComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_EditComment(t *testing.T) { + setup() + defer teardown() + + input := &PullRequestComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequestComment) + 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}`) + }) + + comment, _, err := client.PullRequests.EditComment("o", "r", 1, input) + + if err != nil { + t.Errorf("PullRequests.EditComment returned error: %v", err) + } + + want := &PullRequestComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("PullRequests.EditComment returned %+v, want %+v", comment, want) + } +} + +func TestPullRequestsService_EditComment_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.EditComment("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.PullRequests.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("PullRequests.DeleteComment returned error: %v", err) + } +} + +func TestPullRequestsService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.PullRequests.DeleteComment("%", "r", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/pulls_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_test.go new file mode 100644 index 00000000000..6ac0ddb1723 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/pulls_test.go @@ -0,0 +1,365 @@ +// 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" +) + +func TestPullRequestsService_List(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "state": "closed", + "head": "h", + "base": "b", + "sort": "created", + "direction": "desc", + "page": "2", + }) + fmt.Fprint(w, `[{"number":1}]`) + }) + + opt := &PullRequestListOptions{"closed", "h", "b", "created", "desc", ListOptions{Page: 2}} + pulls, _, err := client.PullRequests.List("o", "r", opt) + + if err != nil { + t.Errorf("PullRequests.List returned error: %v", err) + } + + want := []PullRequest{{Number: Int(1)}} + if !reflect.DeepEqual(pulls, want) { + t.Errorf("PullRequests.List returned %+v, want %+v", pulls, want) + } +} + +func TestPullRequestsService_List_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.List("%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Get(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}`) + }) + + pull, _, err := client.PullRequests.Get("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Get_headAndBase(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,"head":{"ref":"r2","repo":{"id":2}},"base":{"ref":"r1","repo":{"id":1}}}`) + }) + + pull, _, err := client.PullRequests.Get("o", "r", 1) + + if err != nil { + t.Errorf("PullRequests.Get returned error: %v", err) + } + + want := &PullRequest{ + Number: Int(1), + Head: &PullRequestBranch{ + Ref: String("r2"), + Repo: &Repository{ID: Int(2)}, + }, + Base: &PullRequestBranch{ + Ref: String("r1"), + Repo: &Repository{ID: Int(1)}, + }, + } + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Get returned %+v, want %+v", pull, want) + } +} + +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) { + _, _, err := client.PullRequests.Get("%", "r", 1) + testURLParseError(t, err) +} + +func TestPullRequestsService_Create(t *testing.T) { + setup() + defer teardown() + + input := &NewPullRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/pulls", func(w http.ResponseWriter, r *http.Request) { + v := new(NewPullRequest) + 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, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Create("o", "r", input) + if err != nil { + t.Errorf("PullRequests.Create returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Create returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Create_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.Create("%", "r", nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &PullRequest{Title: String("t")} + + mux.HandleFunc("/repos/o/r/pulls/1", func(w http.ResponseWriter, r *http.Request) { + v := new(PullRequest) + 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, `{"number":1}`) + }) + + pull, _, err := client.PullRequests.Edit("o", "r", 1, input) + if err != nil { + t.Errorf("PullRequests.Edit returned error: %v", err) + } + + want := &PullRequest{Number: Int(1)} + if !reflect.DeepEqual(pull, want) { + t.Errorf("PullRequests.Edit returned %+v, want %+v", pull, want) + } +} + +func TestPullRequestsService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.PullRequests.Edit("%", "r", 1, nil) + testURLParseError(t, err) +} + +func TestPullRequestsService_ListCommits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "3", + "parents": [ + { + "sha": "2" + } + ] + }, + { + "sha": "2", + "parents": [ + { + "sha": "1" + } + ] + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commits, _, err := client.PullRequests.ListCommits("o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListCommits returned error: %v", err) + } + + want := []RepositoryCommit{ + { + SHA: String("3"), + Parents: []Commit{ + { + SHA: String("2"), + }, + }, + }, + { + SHA: String("2"), + Parents: []Commit{ + { + SHA: String("1"), + }, + }, + }, + } + if !reflect.DeepEqual(commits, want) { + t.Errorf("PullRequests.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestPullRequestsService_ListFiles(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/files", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, ` + [ + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "filename": "file1.txt", + "status": "added", + "additions": 103, + "deletions": 21, + "changes": 124, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + }, + { + "sha": "f61aebed695e2e4193db5e6dcb09b5b57875f334", + "filename": "file2.txt", + "status": "modified", + "additions": 5, + "deletions": 3, + "changes": 103, + "patch": "@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test" + } + ]`) + }) + + opt := &ListOptions{Page: 2} + commitFiles, _, err := client.PullRequests.ListFiles("o", "r", 1, opt) + if err != nil { + t.Errorf("PullRequests.ListFiles returned error: %v", err) + } + + want := []CommitFile{ + { + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Filename: String("file1.txt"), + Additions: Int(103), + Deletions: Int(21), + Changes: Int(124), + Status: String("added"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + { + SHA: String("f61aebed695e2e4193db5e6dcb09b5b57875f334"), + Filename: String("file2.txt"), + Additions: Int(5), + Deletions: Int(3), + Changes: Int(103), + Status: String("modified"), + Patch: String("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test"), + }, + } + + if !reflect.DeepEqual(commitFiles, want) { + t.Errorf("PullRequests.ListFiles returned %+v, want %+v", commitFiles, want) + } +} + +func TestPullRequestsService_IsMerged(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isMerged, _, err := client.PullRequests.IsMerged("o", "r", 1) + if err != nil { + t.Errorf("PullRequests.IsMerged returned error: %v", err) + } + + want := true + if !reflect.DeepEqual(isMerged, want) { + t.Errorf("PullRequests.IsMerged returned %+v, want %+v", isMerged, want) + } +} + +func TestPullRequestsService_Merge(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pulls/1/merge", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, ` + { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "merged": true, + "message": "Pull Request successfully merged" + }`) + }) + + merge, _, err := client.PullRequests.Merge("o", "r", 1, "merging pull request") + if err != nil { + t.Errorf("PullRequests.Merge returned error: %v", err) + } + + want := &PullRequestMergeResult{ + SHA: String("6dcb09b5b57875f334f61aebed695e2e4193db5e"), + Merged: Bool(true), + Message: String("Pull Request successfully merged"), + } + if !reflect.DeepEqual(merge, want) { + t.Errorf("PullRequests.Merge returned %+v, want %+v", merge, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos.go new file mode 100644 index 00000000000..ed9052c4e6b --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos.go @@ -0,0 +1,490 @@ +// 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" + +// RepositoriesService handles communication with the repository related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/repos/ +type RepositoriesService struct { + client *Client +} + +// Repository represents a GitHub repository. +type Repository struct { + ID *int `json:"id,omitempty"` + Owner *User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Description *string `json:"description,omitempty"` + Homepage *string `json:"homepage,omitempty"` + DefaultBranch *string `json:"default_branch,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PushedAt *Timestamp `json:"pushed_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CloneURL *string `json:"clone_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + MirrorURL *string `json:"mirror_url,omitempty"` + SSHURL *string `json:"ssh_url,omitempty"` + SVNURL *string `json:"svn_url,omitempty"` + Language *string `json:"language,omitempty"` + Fork *bool `json:"fork"` + ForksCount *int `json:"forks_count,omitempty"` + NetworkCount *int `json:"network_count,omitempty"` + OpenIssuesCount *int `json:"open_issues_count,omitempty"` + StargazersCount *int `json:"stargazers_count,omitempty"` + SubscribersCount *int `json:"subscribers_count,omitempty"` + WatchersCount *int `json:"watchers_count,omitempty"` + Size *int `json:"size,omitempty"` + AutoInit *bool `json:"auto_init,omitempty"` + Parent *Repository `json:"parent,omitempty"` + Source *Repository `json:"source,omitempty"` + Organization *Organization `json:"organization,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 + Private *bool `json:"private"` + HasIssues *bool `json:"has_issues"` + HasWiki *bool `json:"has_wiki"` + HasDownloads *bool `json:"has_downloads"` + // Creating an organization repository. Required for non-owners. + TeamID *int `json:"team_id"` + + // API URLs + URL *string `json:"url,omitempty"` + ArchiveURL *string `json:"archive_url,omitempty"` + AssigneesURL *string `json:"assignees_url,omitempty"` + BlobsURL *string `json:"blobs_url,omitempty"` + BranchesURL *string `json:"branches_url,omitempty"` + CollaboratorsURL *string `json:"collaborators_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + CommitsURL *string `json:"commits_url,omitempty"` + CompareURL *string `json:"compare_url,omitempty"` + ContentsURL *string `json:"contents_url,omitempty"` + ContributorsURL *string `json:"contributors_url,omitempty"` + DownloadsURL *string `json:"downloads_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ForksURL *string `json:"forks_url,omitempty"` + GitCommitsURL *string `json:"git_commits_url,omitempty"` + GitRefsURL *string `json:"git_refs_url,omitempty"` + GitTagsURL *string `json:"git_tags_url,omitempty"` + HooksURL *string `json:"hooks_url,omitempty"` + IssueCommentURL *string `json:"issue_comment_url,omitempty"` + IssueEventsURL *string `json:"issue_events_url,omitempty"` + IssuesURL *string `json:"issues_url,omitempty"` + KeysURL *string `json:"keys_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + LanguagesURL *string `json:"languages_url,omitempty"` + MergesURL *string `json:"merges_url,omitempty"` + MilestonesURL *string `json:"milestones_url,omitempty"` + NotificationsURL *string `json:"notifications_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + ReleasesURL *string `json:"releases_url,omitempty"` + StargazersURL *string `json:"stargazers_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + SubscribersURL *string `json:"subscribers_url,omitempty"` + SubscriptionURL *string `json:"subscription_url,omitempty"` + TagsURL *string `json:"tags_url,omitempty"` + TreesURL *string `json:"trees_url,omitempty"` + TeamsURL *string `json:"teams_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (r Repository) String() string { + return Stringify(r) +} + +// RepositoryListOptions specifies the optional parameters to the +// RepositoriesService.List method. +type RepositoryListOptions struct { + // Type of repositories to list. Possible values are: all, owner, public, + // private, member. Default is "all". + Type string `url:"type,omitempty"` + + // How to sort the repository list. Possible values are: created, updated, + // pushed, full_name. Default is "full_name". + Sort string `url:"sort,omitempty"` + + // Direction in which to sort repositories. Possible values are: asc, desc. + // Default is "asc" when sort is "full_name", otherwise default is "desc". + Direction string `url:"direction,omitempty"` + + ListOptions +} + +// List the repositories for a user. Passing the empty string will list +// repositories for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-user-repositories +func (s *RepositoriesService) List(user string, opt *RepositoryListOptions) ([]Repository, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/repos", user) + } else { + u = "user/repos" + } + 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 + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryListByOrgOptions specifies the optional parameters to the +// RepositoriesService.ListByOrg method. +type RepositoryListByOrgOptions struct { + // Type of repositories to list. Possible values are: all, public, private, + // forks, sources, member. Default is "all". + Type string `url:"type,omitempty"` + + ListOptions +} + +// ListByOrg lists the repositories for an organization. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-organization-repositories +func (s *RepositoriesService) ListByOrg(org string, opt *RepositoryListByOrgOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("orgs/%v/repos", 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 + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryListAllOptions specifies the optional parameters to the +// RepositoriesService.ListAll method. +type RepositoryListAllOptions struct { + // ID of the last repository seen + Since int `url:"since,omitempty"` + + ListOptions +} + +// ListAll lists all GitHub repositories in the order that they were created. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-all-public-repositories +func (s *RepositoriesService) ListAll(opt *RepositoryListAllOptions) ([]Repository, *Response, error) { + u, err := addOptions("repositories", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// Create a new repository. If an organization is specified, the new +// repository will be created under that org. If the empty string is +// specified, it will be created for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/repos/#create +func (s *RepositoriesService) Create(org string, repo *Repository) (*Repository, *Response, error) { + var u string + if org != "" { + u = fmt.Sprintf("orgs/%v/repos", org) + } else { + u = "user/repos" + } + + req, err := s.client.NewRequest("POST", u, repo) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// Get fetches a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#get +func (s *RepositoriesService) Get(owner, repo string) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + 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) + resp, err := s.client.Do(req, repository) + if err != nil { + return nil, resp, err + } + + return repository, resp, err +} + +// Edit updates a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#edit +func (s *RepositoriesService) Edit(owner, repo string, repository *Repository) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("PATCH", u, repository) + if err != nil { + return nil, nil, err + } + + r := new(Repository) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + + return r, resp, err +} + +// Delete a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#delete-a-repository +func (s *RepositoriesService) Delete(owner, repo string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v", owner, repo) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Contributor represents a repository contributor +type Contributor struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin"` + Contributions *int `json:"contributions,omitempty"` +} + +// ListContributorsOptions specifies the optional parameters to the +// RepositoriesService.ListContributors method. +type ListContributorsOptions struct { + // Include anonymous contributors in results or not + Anon string `url:"anon,omitempty"` + + ListOptions +} + +// ListContributors lists contributors for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-contributors +func (s *RepositoriesService) ListContributors(owner string, repository string, opt *ListContributorsOptions) ([]Contributor, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/contributors", owner, repository) + 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 + } + + contributor := new([]Contributor) + resp, err := s.client.Do(req, contributor) + if err != nil { + return nil, nil, err + } + + return *contributor, resp, err +} + +// ListLanguages lists languages for the specified repository. The returned map +// specifies the languages and the number of bytes of code written in that +// language. For example: +// +// { +// "C": 78769, +// "Python": 7769 +// } +// +// GitHub API Docs: http://developer.github.com/v3/repos/#list-languages +func (s *RepositoriesService) ListLanguages(owner string, repo string) (map[string]int, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/languages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + languages := make(map[string]int) + resp, err := s.client.Do(req, &languages) + if err != nil { + return nil, resp, err + } + + return languages, resp, err +} + +// ListTeams lists the teams for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-teams +func (s *RepositoriesService) ListTeams(owner string, repo string, opt *ListOptions) ([]Team, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/teams", owner, repo) + 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 + } + + teams := new([]Team) + resp, err := s.client.Do(req, teams) + if err != nil { + return nil, resp, err + } + + return *teams, resp, err +} + +// RepositoryTag represents a repository tag. +type RepositoryTag struct { + Name *string `json:"name,omitempty"` + Commit *Commit `json:"commit,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` +} + +// ListTags lists tags for the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#list-tags +func (s *RepositoriesService) ListTags(owner string, repo string, opt *ListOptions) ([]RepositoryTag, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/tags", owner, repo) + 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 + } + + tags := new([]RepositoryTag) + resp, err := s.client.Do(req, tags) + if err != nil { + return nil, resp, err + } + + return *tags, resp, err +} + +// Branch represents a repository branch +type Branch struct { + Name *string `json:"name,omitempty"` + Commit *Commit `json:"commit,omitempty"` +} + +// ListBranches lists branches for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/#list-branches +func (s *RepositoriesService) ListBranches(owner string, repo string, opt *ListOptions) ([]Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches", owner, repo) + 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 + } + + branches := new([]Branch) + resp, err := s.client.Do(req, branches) + if err != nil { + return nil, resp, err + } + + return *branches, resp, err +} + +// GetBranch gets the specified branch for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/#get-branch +func (s *RepositoriesService) GetBranch(owner, repo, branch string) (*Branch, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/branches/%v", owner, repo, branch) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + b := new(Branch) + resp, err := s.client.Do(req, b) + if err != nil { + return nil, resp, err + } + + return b, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators.go new file mode 100644 index 00000000000..3ad61622a49 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators.go @@ -0,0 +1,75 @@ +// 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" + +// ListCollaborators lists the Github users that have access to the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#list +func (s *RepositoriesService) ListCollaborators(owner, repo string, opt *ListOptions) ([]User, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators", owner, repo) + 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 + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// IsCollaborator checks whether the specified Github user has collaborator +// access to the given repo. +// Note: This will return false if the user is not a collaborator OR the user +// is not a GitHub user. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#get +func (s *RepositoriesService) IsCollaborator(owner, repo, user string) (bool, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + isCollab, err := parseBoolResponse(err) + return isCollab, resp, err +} + +// AddCollaborator adds the specified Github user as collaborator to the given repo. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#add-collaborator +func (s *RepositoriesService) AddCollaborator(owner, repo, user string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// RemoveCollaborator removes the specified Github user as collaborator from the given repo. +// Note: Does not return error if a valid user that is not a collaborator is removed. +// +// GitHub API docs: http://developer.github.com/v3/repos/collaborators/#remove-collaborator +func (s *RepositoriesService) RemoveCollaborator(owner, repo, user string) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators_test.go new file mode 100644 index 00000000000..de26ba97055 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_collaborators_test.go @@ -0,0 +1,123 @@ +// 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 TestRepositoriesService_ListCollaborators(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprintf(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &ListOptions{Page: 2} + users, _, err := client.Repositories.ListCollaborators("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCollaborators returned error: %v", err) + } + + want := []User{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Repositories.ListCollaborators returned %+v, want %+v", users, want) + } +} + +func TestRepositoriesService_ListCollaborators_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListCollaborators("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_IsCollaborator_True(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + isCollab, _, err := client.Repositories.IsCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if !isCollab { + t.Errorf("Repositories.IsCollaborator returned false, want true") + } +} + +func TestRepositoriesService_IsCollaborator_False(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + isCollab, _, err := client.Repositories.IsCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.IsCollaborator returned error: %v", err) + } + + if isCollab { + t.Errorf("Repositories.IsCollaborator returned true, want false") + } +} + +func TestRepositoriesService_IsCollaborator_invalidUser(t *testing.T) { + _, _, err := client.Repositories.IsCollaborator("%", "%", "%") + testURLParseError(t, err) +} + +func TestRepositoriesService_AddCollaborator(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.AddCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.AddCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_AddCollaborator_invalidUser(t *testing.T) { + _, err := client.Repositories.AddCollaborator("%", "%", "%") + testURLParseError(t, err) +} + +func TestRepositoriesService_RemoveCollaborator(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/collaborators/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Repositories.RemoveCollaborator("o", "r", "u") + if err != nil { + t.Errorf("Repositories.RemoveCollaborator returned error: %v", err) + } +} + +func TestRepositoriesService_RemoveCollaborator_invalidUser(t *testing.T) { + _, err := client.Repositories.RemoveCollaborator("%", "%", "%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments.go new file mode 100644 index 00000000000..2d090bb749f --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments.go @@ -0,0 +1,150 @@ +// 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" + "time" +) + +// RepositoryComment represents a comment for a commit, file, or line in a repository. +type RepositoryComment struct { + HTMLURL *string `json:"html_url,omitempty"` + URL *string `json:"url,omitempty"` + ID *int `json:"id,omitempty"` + CommitID *string `json:"commit_id,omitempty"` + User *User `json:"user,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + + // User-mutable fields + Body *string `json:"body"` + // User-initialized fields + Path *string `json:"path,omitempty"` + Position *int `json:"position,omitempty"` +} + +func (r RepositoryComment) String() string { + return Stringify(r) +} + +// ListComments lists all the comments for the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#list-commit-comments-for-a-repository +func (s *RepositoriesService) ListComments(owner, repo string, opt *ListOptions) ([]RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments", owner, repo) + 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 + } + + comments := new([]RepositoryComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// ListCommitComments lists all the comments for a given commit SHA. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit +func (s *RepositoriesService) ListCommitComments(owner, repo, sha string, opt *ListOptions) ([]RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + 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 + } + + comments := new([]RepositoryComment) + resp, err := s.client.Do(req, comments) + if err != nil { + return nil, resp, err + } + + return *comments, resp, err +} + +// CreateComment creates a comment for the given commit. +// Note: GitHub allows for comments to be created for non-existing files and positions. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#create-a-commit-comment +func (s *RepositoriesService) CreateComment(owner, repo, sha string, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/comments", owner, repo, sha) + req, err := s.client.NewRequest("POST", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// GetComment gets a single comment from a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#get-a-single-commit-comment +func (s *RepositoriesService) GetComment(owner, repo string, id int) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// UpdateComment updates the body of a single comment. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#update-a-commit-comment +func (s *RepositoriesService) UpdateComment(owner, repo string, id int, comment *RepositoryComment) (*RepositoryComment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("PATCH", u, comment) + if err != nil { + return nil, nil, err + } + + c := new(RepositoryComment) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// DeleteComment deletes a single comment from a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/comments/#delete-a-commit-comment +func (s *RepositoriesService) DeleteComment(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments_test.go new file mode 100644 index 00000000000..b5a8786a9ff --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_comments_test.go @@ -0,0 +1,180 @@ +// 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" +) + +func TestRepositoriesService_ListComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments", 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} + comments, _, err := client.Repositories.ListComments("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListComments returned error: %v", err) + } + + want := []RepositoryComment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListComments_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListComments("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListCommitComments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s/comments", 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} + comments, _, err := client.Repositories.ListCommitComments("o", "r", "s", opt) + if err != nil { + t.Errorf("Repositories.ListCommitComments returned error: %v", err) + } + + want := []RepositoryComment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(comments, want) { + t.Errorf("Repositories.ListCommitComments returned %+v, want %+v", comments, want) + } +} + +func TestRepositoriesService_ListCommitComments_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListCommitComments("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateComment(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/commits/s/comments", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + 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}`) + }) + + comment, _, err := client.Repositories.CreateComment("o", "r", "s", input) + if err != nil { + t.Errorf("Repositories.CreateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.CreateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_CreateComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateComment("%", "%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + comment, _, err := client.Repositories.GetComment("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.GetComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_GetComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetComment("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_UpdateComment(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryComment{Body: String("b")} + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryComment) + 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}`) + }) + + comment, _, err := client.Repositories.UpdateComment("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.UpdateComment returned error: %v", err) + } + + want := &RepositoryComment{ID: Int(1)} + if !reflect.DeepEqual(comment, want) { + t.Errorf("Repositories.UpdateComment returned %+v, want %+v", comment, want) + } +} + +func TestRepositoriesService_UpdateComment_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.UpdateComment("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteComment(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/comments/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteComment("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteComment returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteComment_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteComment("%", "%", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits.go new file mode 100644 index 00000000000..6401cb4ab8d --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits.go @@ -0,0 +1,168 @@ +// 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" + "time" +) + +// RepositoryCommit represents a commit in a repo. +// Note that it's wrapping a Commit, so author/committer information is in two places, +// but contain different details about them: in RepositoryCommit "github details", in Commit - "git details". +type RepositoryCommit struct { + SHA *string `json:"sha,omitempty"` + Commit *Commit `json:"commit,omitempty"` + Author *User `json:"author,omitempty"` + Committer *User `json:"committer,omitempty"` + Parents []Commit `json:"parents,omitempty"` + Message *string `json:"message,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + + // Details about how many changes were made in this commit. Only filled in during GetCommit! + Stats *CommitStats `json:"stats,omitempty"` + // Details about which files, and how this commit touched. Only filled in during GetCommit! + Files []CommitFile `json:"files,omitempty"` +} + +func (r RepositoryCommit) String() string { + return Stringify(r) +} + +// CommitStats represents the number of additions / deletions from a file in a given RepositoryCommit. +type CommitStats struct { + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Total *int `json:"total,omitempty"` +} + +func (c CommitStats) String() string { + return Stringify(c) +} + +// CommitFile represents a file modified in a commit. +type CommitFile struct { + SHA *string `json:"sha,omitempty"` + Filename *string `json:"filename,omitempty"` + Additions *int `json:"additions,omitempty"` + Deletions *int `json:"deletions,omitempty"` + Changes *int `json:"changes,omitempty"` + Status *string `json:"status,omitempty"` + Patch *string `json:"patch,omitempty"` +} + +func (c CommitFile) String() string { + return Stringify(c) +} + +// CommitsComparison is the result of comparing two commits. +// See CompareCommits() for details. +type CommitsComparison struct { + BaseCommit *RepositoryCommit `json:"base_commit,omitempty"` + MergeBaseCommit *RepositoryCommit `json:"merge_base_commit,omitempty"` + + // Head can be 'behind' or 'ahead' + Status *string `json:"status,omitempty"` + AheadBy *int `json:"ahead_by,omitempty"` + BehindBy *int `json:"behind_by,omitempty"` + TotalCommits *int `json:"total_commits,omitempty"` + + Commits []RepositoryCommit `json:"commits,omitempty"` + + Files []CommitFile `json:"files,omitempty"` +} + +func (c CommitsComparison) String() string { + return Stringify(c) +} + +// CommitsListOptions specifies the optional parameters to the +// RepositoriesService.ListCommits method. +type CommitsListOptions struct { + // SHA or branch to start listing Commits from. + SHA string `url:"sha,omitempty"` + + // Path that should be touched by the returned Commits. + Path string `url:"path,omitempty"` + + // Author of by which to filter Commits. + Author string `url:"author,omitempty"` + + // Since when should Commits be included in the response. + Since time.Time `url:"since,omitempty"` + + // Until when should Commits be included in the response. + Until time.Time `url:"until,omitempty"` + + ListOptions +} + +// ListCommits lists the commits of a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#list +func (s *RepositoriesService) ListCommits(owner, repo string, opt *CommitsListOptions) ([]RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits", owner, repo) + 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 + } + + commits := new([]RepositoryCommit) + resp, err := s.client.Do(req, commits) + if err != nil { + return nil, resp, err + } + + return *commits, resp, err +} + +// GetCommit fetches the specified commit, including all details about it. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/#get-a-single-commit +// See also: http://developer.github.com//v3/git/commits/#get-a-single-commit provides the same functionality +func (s *RepositoriesService) GetCommit(owner, repo, sha string) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, err +} + +// CompareCommits compares a range of commits with each other. +// todo: support media formats - https://github.com/google/go-github/issues/6 +// +// GitHub API docs: http://developer.github.com/v3/repos/commits/index.html#compare-two-commits +func (s *RepositoriesService) CompareCommits(owner, repo string, base, head string) (*CommitsComparison, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/compare/%v...%v", owner, repo, base, head) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + comp := new(CommitsComparison) + resp, err := s.client.Do(req, comp) + if err != nil { + return nil, resp, err + } + + return comp, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits_test.go new file mode 100644 index 00000000000..56ba8a5e044 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_commits_test.go @@ -0,0 +1,191 @@ +// 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" + "time" +) + +func TestRepositoriesService_ListCommits(t *testing.T) { + setup() + defer teardown() + + // given + mux.HandleFunc("/repos/o/r/commits", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, + values{ + "sha": "s", + "path": "p", + "author": "a", + "since": "2013-08-01T00:00:00Z", + "until": "2013-09-03T00:00:00Z", + }) + fmt.Fprintf(w, `[{"sha": "s"}]`) + }) + + opt := &CommitsListOptions{ + SHA: "s", + Path: "p", + Author: "a", + Since: time.Date(2013, time.August, 1, 0, 0, 0, 0, time.UTC), + Until: time.Date(2013, time.September, 3, 0, 0, 0, 0, time.UTC), + } + commits, _, err := client.Repositories.ListCommits("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListCommits returned error: %v", err) + } + + want := []RepositoryCommit{{SHA: String("s")}} + if !reflect.DeepEqual(commits, want) { + t.Errorf("Repositories.ListCommits returned %+v, want %+v", commits, want) + } +} + +func TestRepositoriesService_GetCommit(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/s", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "sha": "s", + "commit": { "message": "m" }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ], + "stats": { "additions": 104, "deletions": 4, "total": 108 }, + "files": [ + { + "filename": "f", + "additions": 10, + "deletions": 2, + "changes": 12, + "status": "s", + "raw_url": "r", + "blob_url": "b", + "patch": "p" + } + ] + }`) + }) + + commit, _, err := client.Repositories.GetCommit("o", "r", "s") + if err != nil { + t.Errorf("Repositories.GetCommit returned error: %v", err) + } + + want := &RepositoryCommit{ + SHA: String("s"), + Commit: &Commit{ + Message: String("m"), + }, + Author: &User{ + Login: String("l"), + }, + Committer: &User{ + Login: String("l"), + }, + Parents: []Commit{ + { + SHA: String("s"), + }, + }, + Stats: &CommitStats{ + Additions: Int(104), + Deletions: Int(4), + Total: Int(108), + }, + Files: []CommitFile{ + { + Filename: String("f"), + Additions: Int(10), + Deletions: Int(2), + Changes: Int(12), + Status: String("s"), + Patch: String("p"), + }, + }, + } + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.GetCommit returned \n%+v, want \n%+v", commit, want) + } +} + +func TestRepositoriesService_CompareCommits(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/compare/b...h", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprintf(w, `{ + "base_commit": { + "sha": "s", + "commit": { + "author": { "name": "n" }, + "committer": { "name": "n" }, + "message": "m", + "tree": { "sha": "t" } + }, + "author": { "login": "n" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + }, + "status": "s", + "ahead_by": 1, + "behind_by": 2, + "total_commits": 1, + "commits": [ + { + "sha": "s", + "commit": { "author": { "name": "n" } }, + "author": { "login": "l" }, + "committer": { "login": "l" }, + "parents": [ { "sha": "s" } ] + } + ], + "files": [ { "filename": "f" } ] + }`) + }) + + got, _, err := client.Repositories.CompareCommits("o", "r", "b", "h") + if err != nil { + t.Errorf("Repositories.CompareCommits returned error: %v", err) + } + + want := &CommitsComparison{ + Status: String("s"), + AheadBy: Int(1), + BehindBy: Int(2), + TotalCommits: Int(1), + BaseCommit: &RepositoryCommit{ + Commit: &Commit{ + Author: &CommitAuthor{Name: String("n")}, + }, + Author: &User{Login: String("l")}, + Committer: &User{Login: String("l")}, + Message: String("m"), + }, + Commits: []RepositoryCommit{ + { + SHA: String("s"), + }, + }, + Files: []CommitFile{ + { + Filename: String("f"), + }, + }, + } + + if reflect.DeepEqual(got, want) { + t.Errorf("Repositories.CompareCommits returned \n%+v, want \n%+v", got, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents.go new file mode 100644 index 00000000000..80776f2dac8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents.go @@ -0,0 +1,248 @@ +// 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. + +// Repository contents API methods. +// http://developer.github.com/v3/repos/contents/ + +package github + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "path" +) + +// RepositoryContent represents a file or directory in a github repository. +type RepositoryContent struct { + Type *string `json:"type,omitempty"` + Encoding *string `json:"encoding,omitempty"` + Size *int `json:"size,omitempty"` + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + Content *string `json:"content,omitempty"` + SHA *string `json:"sha,omitempty"` + URL *string `json:"url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + DownloadURL *string `json:"download_url,omitempty"` +} + +// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentResponse struct { + Content *RepositoryContent `json:"content,omitempty"` + Commit `json:"commit,omitempty"` +} + +// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile. +type RepositoryContentFileOptions struct { + Message *string `json:"message,omitempty"` + Content []byte `json:"content,omitempty"` + SHA *string `json:"sha,omitempty"` + Branch *string `json:"branch,omitempty"` + Author *CommitAuthor `json:"author,omitempty"` + Committer *CommitAuthor `json:"committer,omitempty"` +} + +// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA, +// branch, or tag +type RepositoryContentGetOptions struct { + Ref string `url:"ref,omitempty"` +} + +func (r RepositoryContent) String() string { + return Stringify(r) +} + +// Decode decodes the file content if it is base64 encoded. +func (r *RepositoryContent) Decode() ([]byte, error) { + if *r.Encoding != "base64" { + return nil, errors.New("cannot decode non-base64") + } + o, err := base64.StdEncoding.DecodeString(*r.Content) + if err != nil { + return nil, err + } + return o, nil +} + +// GetReadme gets the Readme file for the repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme +func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/readme", owner, repo) + 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 + } + readme := new(RepositoryContent) + resp, err := s.client.Do(req, readme) + if err != nil { + return nil, 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 +// (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 +// easy to distinguish between both result types and to mimic the API as much +// as possible, both result types will be returned but only one will contain a +// value and the other will be nil. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents +func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent, + directoryContent []*RepositoryContent, resp *Response, err error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + u, err = addOptions(u, opt) + if err != nil { + return nil, nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, nil, err + } + var rawJSON json.RawMessage + resp, err = s.client.Do(req, &rawJSON) + if err != nil { + return nil, nil, resp, err + } + fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent) + if fileUnmarshalError == nil { + return fileContent, nil, resp, fileUnmarshalError + } + directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent) + if directoryUnmarshalError == nil { + return nil, directoryContent, resp, directoryUnmarshalError + } + return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError) +} + +// CreateFile creates a new file in a repository at the given path and returns +// the commit and file metadata. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file +func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + createResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, createResponse) + if err != nil { + return nil, resp, err + } + return createResponse, resp, err +} + +// UpdateFile updates a file in a repository at the given path and returns the +// commit and file metadata. Requires the blob SHA of the file being updated. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file +func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + updateResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, updateResponse) + if err != nil { + return nil, resp, err + } + return updateResponse, resp, err +} + +// DeleteFile deletes a file from a repository and returns the commit. +// Requires the blob SHA of the file to be deleted. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file +func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) + req, err := s.client.NewRequest("DELETE", u, opt) + if err != nil { + return nil, nil, err + } + deleteResponse := new(RepositoryContentResponse) + resp, err := s.client.Do(req, deleteResponse) + if err != nil { + return nil, resp, err + } + return deleteResponse, resp, err +} + +// archiveFormat is used to define the archive type when calling GetArchiveLink. +type archiveFormat string + +const ( + // Tarball specifies an archive in gzipped tar format. + Tarball archiveFormat = "tarball" + + // Zipball specifies an archive in zip format. + Zipball archiveFormat = "zipball" +) + +// GetArchiveLink returns an URL to download a tarball or zipball archive for a +// repository. The archiveFormat can be specified by either the github.Tarball +// or github.Zipball constant. +// +// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link +func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat) + if opt != nil && opt.Ref != "" { + u += fmt.Sprintf("/%s", opt.Ref) + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + var resp *http.Response + // Use http.DefaultTransport if no custom Transport is configured + if s.client.client.Transport == nil { + resp, err = http.DefaultTransport.RoundTrip(req) + } else { + resp, err = s.client.client.Transport.RoundTrip(req) + } + if err != nil || resp.StatusCode != http.StatusFound { + return nil, newResponse(resp), err + } + parsedURL, err := url.Parse(resp.Header.Get("Location")) + return parsedURL, newResponse(resp), err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents_test.go new file mode 100644 index 00000000000..8ab3ecdaef8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_contents_test.go @@ -0,0 +1,304 @@ +package github + +import ( + "fmt" + "io/ioutil" + "net/http" + "reflect" + "testing" +) + +func TestDecode(t *testing.T) { + setup() + defer teardown() + r := RepositoryContent{Encoding: String("base64"), Content: String("aGVsbG8=")} + o, err := r.Decode() + if err != nil { + t.Errorf("Failed to decode content.") + } + want := "hello" + if string(o) != want { + t.Errorf("RepositoryContent.Decode returned %+v, want %+v", string(o), want) + } +} + +func TestDecodeBadEncoding(t *testing.T) { + setup() + defer teardown() + r := RepositoryContent{Encoding: String("bad")} + _, err := r.Decode() + if err == nil { + t.Errorf("Should fail to decode non-base64") + } +} + +func TestRepositoriesService_GetReadme(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/readme", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 5362, + "name": "README.md", + "path": "README.md" + }`) + }) + readme, _, err := client.Repositories.GetReadme("o", "r", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetReadme returned error: %v", err) + } + want := &RepositoryContent{Type: String("file"), Name: String("README.md"), Size: Int(5362), Encoding: String("base64"), Path: String("README.md")} + if !reflect.DeepEqual(readme, want) { + t.Errorf("Repositories.GetReadme returned %+v, want %+v", readme, want) + } +} + +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() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "type": "file", + "encoding": "base64", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }`) + }) + fileContents, _, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + 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")} + if !reflect.DeepEqual(fileContents, want) { + t.Errorf("Repositories.GetContents returned %+v, want %+v", fileContents, want) + } +} + +func TestRepositoriesService_GetContents_Directory(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{ + "type": "dir", + "name": "lib", + "path": "lib" + }, + { + "type": "file", + "size": 20678, + "name": "LICENSE", + "path": "LICENSE" + }]`) + }) + _, directoryContents, _, err := client.Repositories.GetContents("o", "r", "p", &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetContents returned error: %v", err) + } + want := []*RepositoryContent{{Type: String("dir"), Name: String("lib"), Path: String("lib")}, + {Type: String("file"), Name: String("LICENSE"), Size: Int(20678), Path: String("LICENSE")}} + if !reflect.DeepEqual(directoryContents, want) { + t.Errorf("Repositories.GetContents_Directory returned %+v, want %+v", directoryContents, want) + } +} + +func TestRepositoriesService_CreateFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + createResponse, _, err := client.Repositories.CreateFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.CreateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(createResponse, want) { + t.Errorf("Repositories.CreateFile returned %+v, want %+v", createResponse, want) + } +} + +func TestRepositoriesService_UpdateFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + fmt.Fprint(w, `{ + "content":{ + "name":"p" + }, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + content := []byte("c") + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + Content: content, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + updateResponse, _, err := client.Repositories.UpdateFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.UpdateFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: &RepositoryContent{Name: String("p")}, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(updateResponse, want) { + t.Errorf("Repositories.UpdateFile returned %+v, want %+v", updateResponse, want) + } +} + +func TestRepositoriesService_DeleteFile(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/contents/p", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + fmt.Fprint(w, `{ + "content": null, + "commit":{ + "message":"m", + "sha":"f5f369044773ff9c6383c087466d12adb6fa0828" + } + }`) + }) + message := "m" + sha := "f5f369044773ff9c6383c087466d12adb6fa0828" + repositoryContentsOptions := &RepositoryContentFileOptions{ + Message: &message, + SHA: &sha, + Committer: &CommitAuthor{Name: String("n"), Email: String("e")}, + } + deleteResponse, _, err := client.Repositories.DeleteFile("o", "r", "p", repositoryContentsOptions) + if err != nil { + t.Errorf("Repositories.DeleteFile returned error: %v", err) + } + want := &RepositoryContentResponse{ + Content: nil, + Commit: Commit{ + Message: String("m"), + SHA: String("f5f369044773ff9c6383c087466d12adb6fa0828"), + }, + } + if !reflect.DeepEqual(deleteResponse, want) { + t.Errorf("Repositories.DeleteFile returned %+v, want %+v", deleteResponse, want) + } +} + +func TestRepositoriesService_GetArchiveLink(t *testing.T) { + setup() + defer teardown() + mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Redirect(w, r, "http://github.com/a", http.StatusFound) + }) + url, resp, err := client.Repositories.GetArchiveLink("o", "r", Tarball, &RepositoryContentGetOptions{}) + if err != nil { + t.Errorf("Repositories.GetArchiveLink returned error: %v", err) + } + if resp.StatusCode != http.StatusFound { + t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound) + } + want := "http://github.com/a" + if url.String() != want { + t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments.go new file mode 100644 index 00000000000..77c79491d85 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments.go @@ -0,0 +1,162 @@ +// Copyright 2014 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" +) + +// Deployment represents a deployment in a repo +type Deployment struct { + URL *string `json:"url,omitempty"` + ID *int `json:"id,omitempty"` + SHA *string `json:"sha,omitempty"` + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + Payload json.RawMessage `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` + Creator *User `json:"creator,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"pushed_at,omitempty"` +} + +// DeploymentRequest represents a deployment request +type DeploymentRequest struct { + Ref *string `json:"ref,omitempty"` + Task *string `json:"task,omitempty"` + AutoMerge *bool `json:"auto_merge,omitempty"` + RequiredContexts *[]string `json:"required_contexts,omitempty"` + Payload *string `json:"payload,omitempty"` + Environment *string `json:"environment,omitempty"` + Description *string `json:"description,omitempty"` +} + +// DeploymentsListOptions specifies the optional parameters to the +// RepositoriesService.ListDeployments method. +type DeploymentsListOptions struct { + // SHA of the Deployment. + SHA string `url:"sha,omitempty"` + + // List deployments for a given ref. + Ref string `url:"ref,omitempty"` + + // List deployments for a given task. + Task string `url:"task,omitempty"` + + // List deployments for a given environment. + Environment string `url:"environment,omitempty"` + + ListOptions +} + +// ListDeployments lists the deployments of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployments +func (s *RepositoriesService) ListDeployments(owner, repo string, opt *DeploymentsListOptions) ([]Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + 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 + } + + deployments := new([]Deployment) + resp, err := s.client.Do(req, deployments) + if err != nil { + return nil, resp, err + } + + return *deployments, resp, err +} + +// CreateDeployment creates a new deployment for a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment +func (s *RepositoriesService) CreateDeployment(owner, repo string, request *DeploymentRequest) (*Deployment, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments", owner, repo) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + d := new(Deployment) + resp, err := s.client.Do(req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} + +// DeploymentStatus represents the status of a +// particular deployment. +type DeploymentStatus struct { + ID *int `json:"id,omitempty"` + State *string `json:"state,omitempty"` + Creator *User `json:"creator,omitempty"` + Description *string `json:"description,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"pushed_at,omitempty"` +} + +// DeploymentStatusRequest represents a deployment request +type DeploymentStatusRequest struct { + State *string `json:"state,omitempty"` + TargetURL *string `json:"target_url,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ListDeploymentStatuses lists the statuses of a given deployment of a repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#list-deployment-statuses +func (s *RepositoriesService) ListDeploymentStatuses(owner, repo string, deployment int, opt *ListOptions) ([]DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + 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 + } + + statuses := new([]DeploymentStatus) + resp, err := s.client.Do(req, statuses) + if err != nil { + return nil, resp, err + } + + return *statuses, resp, err +} + +// CreateDeploymentStatus creates a new status for a deployment. +// +// GitHub API docs: https://developer.github.com/v3/repos/deployments/#create-a-deployment-status +func (s *RepositoriesService) CreateDeploymentStatus(owner, repo string, deployment int, request *DeploymentStatusRequest) (*DeploymentStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/deployments/%v/statuses", owner, repo, deployment) + + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + d := new(DeploymentStatus) + resp, err := s.client.Do(req, d) + if err != nil { + return nil, resp, err + } + + return d, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments_test.go new file mode 100644 index 00000000000..161a07ccd8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_deployments_test.go @@ -0,0 +1,87 @@ +// Copyright 2014 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 TestRepositoriesService_ListDeployments(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"environment": "test"}) + fmt.Fprint(w, `[{"id":1}, {"id":2}]`) + }) + + opt := &DeploymentsListOptions{Environment: "test"} + deployments, _, err := client.Repositories.ListDeployments("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListDeployments returned error: %v", err) + } + + want := []Deployment{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(deployments, want) { + t.Errorf("Repositories.ListDeployments returned %+v, want %+v", deployments, want) + } +} + +func TestRepositoriesService_CreateDeployment(t *testing.T) { + setup() + defer teardown() + + input := &DeploymentRequest{Ref: String("1111"), Task: String("deploy")} + + mux.HandleFunc("/repos/o/r/deployments", func(w http.ResponseWriter, r *http.Request) { + v := new(DeploymentRequest) + 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, `{"ref": "1111", "task": "deploy"}`) + }) + + deployment, _, err := client.Repositories.CreateDeployment("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateDeployment returned error: %v", err) + } + + want := &Deployment{Ref: String("1111"), Task: String("deploy")} + if !reflect.DeepEqual(deployment, want) { + t.Errorf("Repositories.CreateDeployment returned %+v, want %+v", deployment, want) + } +} + +func TestRepositoriesService_ListDeploymentStatuses(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/deployments/1/statuses", 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} + statutses, _, err := client.Repositories.ListDeploymentStatuses("o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListDeploymentStatuses returned error: %v", err) + } + + want := []DeploymentStatus{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(statutses, want) { + t.Errorf("Repositories.ListDeploymentStatuses returned %+v, want %+v", statutses, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks.go new file mode 100644 index 00000000000..1fec8292c13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks.go @@ -0,0 +1,73 @@ +// 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" + +// RepositoryListForksOptions specifies the optional parameters to the +// RepositoriesService.ListForks method. +type RepositoryListForksOptions struct { + // How to sort the forks list. Possible values are: newest, oldest, + // watchers. Default is "newest". + Sort string `url:"sort,omitempty"` + + ListOptions +} + +// ListForks lists the forks of the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/forks/#list-forks +func (s *RepositoriesService) ListForks(owner, repo string, opt *RepositoryListForksOptions) ([]Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + 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 + } + + repos := new([]Repository) + resp, err := s.client.Do(req, repos) + if err != nil { + return nil, resp, err + } + + return *repos, resp, err +} + +// RepositoryCreateForkOptions specifies the optional parameters to the +// RepositoriesService.CreateFork method. +type RepositoryCreateForkOptions struct { + // The organization to fork the repository into. + Organization string `url:"organization,omitempty"` +} + +// CreateFork creates a fork of the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/forks/#list-forks +func (s *RepositoriesService) CreateFork(owner, repo string, opt *RepositoryCreateForkOptions) (*Repository, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/forks", owner, repo) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, nil, err + } + + fork := new(Repository) + resp, err := s.client.Do(req, fork) + if err != nil { + return nil, resp, err + } + + return fork, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks_test.go new file mode 100644 index 00000000000..965a0663931 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_forks_test.go @@ -0,0 +1,73 @@ +// 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 TestRepositoriesService_ListForks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "sort": "newest", + "page": "3", + }) + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + opt := &RepositoryListForksOptions{ + Sort: "newest", + ListOptions: ListOptions{Page: 3}, + } + repos, _, err := client.Repositories.ListForks("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListForks returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListForks returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListForks_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListForks("%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateFork(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/forks", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testFormValues(t, r, values{"organization": "o"}) + fmt.Fprint(w, `{"id":1}`) + }) + + opt := &RepositoryCreateForkOptions{Organization: "o"} + repo, _, err := client.Repositories.CreateFork("o", "r", opt) + if err != nil { + t.Errorf("Repositories.CreateFork returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.CreateFork returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_CreateFork_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateFork("%", "r", nil) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks.go new file mode 100644 index 00000000000..bc4c8c5ddb4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks.go @@ -0,0 +1,194 @@ +// 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" + "time" +) + +// WebHookPayload represents the data that is received from GitHub when a push +// event hook is triggered. The format of these payloads pre-date most of the +// GitHub v3 API, so there are lots of minor incompatibilities with the types +// defined in the rest of the API. Therefore, several types are duplicated +// here to account for these differences. +// +// GitHub API docs: https://help.github.com/articles/post-receive-hooks +type WebHookPayload struct { + After *string `json:"after,omitempty"` + Before *string `json:"before,omitempty"` + Commits []WebHookCommit `json:"commits,omitempty"` + Compare *string `json:"compare,omitempty"` + Created *bool `json:"created,omitempty"` + Deleted *bool `json:"deleted,omitempty"` + Forced *bool `json:"forced,omitempty"` + HeadCommit *WebHookCommit `json:"head_commit,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Ref *string `json:"ref,omitempty"` + Repo *Repository `json:"repository,omitempty"` +} + +func (w WebHookPayload) String() string { + return Stringify(w) +} + +// WebHookCommit represents the commit variant we receive from GitHub in a +// WebHookPayload. +type WebHookCommit struct { + Added []string `json:"added,omitempty"` + Author *WebHookAuthor `json:"author,omitempty"` + Committer *WebHookAuthor `json:"committer,omitempty"` + Distinct *bool `json:"distinct,omitempty"` + ID *string `json:"id,omitempty"` + Message *string `json:"message,omitempty"` + Modified []string `json:"modified,omitempty"` + Removed []string `json:"removed,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` +} + +func (w WebHookCommit) String() string { + return Stringify(w) +} + +// WebHookAuthor represents the author or committer of a commit, as specified +// in a WebHookCommit. The commit author may not correspond to a GitHub User. +type WebHookAuthor struct { + Email *string `json:"email,omitempty"` + Name *string `json:"name,omitempty"` + Username *string `json:"username,omitempty"` +} + +func (w WebHookAuthor) String() string { + return Stringify(w) +} + +// Hook represents a GitHub (web and service) hook for a repository. +type Hook struct { + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Name *string `json:"name,omitempty"` + Events []string `json:"events,omitempty"` + Active *bool `json:"active,omitempty"` + Config map[string]interface{} `json:"config,omitempty"` + ID *int `json:"id,omitempty"` +} + +func (h Hook) String() string { + return Stringify(h) +} + +// CreateHook creates a Hook for the specified repository. +// Name and Config are required fields. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#create-a-hook +func (s *RepositoriesService) CreateHook(owner, repo string, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + 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 +} + +// ListHooks lists all Hooks for the specified repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#list +func (s *RepositoriesService) ListHooks(owner, repo string, opt *ListOptions) ([]Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) + 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: http://developer.github.com/v3/repos/hooks/#get-single-hook +func (s *RepositoriesService) GetHook(owner, repo string, id int) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, 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 +} + +// EditHook updates a specified Hook. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#edit-a-hook +func (s *RepositoriesService) EditHook(owner, repo string, id int, hook *Hook) (*Hook, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, 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 +} + +// DeleteHook deletes a specified Hook. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#delete-a-hook +func (s *RepositoriesService) DeleteHook(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d", owner, repo, id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + 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. +// +// GitHub API docs: http://developer.github.com/v3/repos/hooks/#test-a-push-hook +func (s *RepositoriesService) TestHook(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/hooks/%d/tests", owner, repo, id) + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListServiceHooks is deprecated. Use Client.ListServiceHooks instead. +func (s *RepositoriesService) ListServiceHooks() ([]ServiceHook, *Response, error) { + return s.client.ListServiceHooks() +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks_test.go new file mode 100644 index 00000000000..c163a26b29f --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_hooks_test.go @@ -0,0 +1,187 @@ +// 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" +) + +func TestRepositoriesService_CreateHook(t *testing.T) { + setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/hooks", func(w http.ResponseWriter, r *http.Request) { + v := new(Hook) + 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}`) + }) + + hook, _, err := client.Repositories.CreateHook("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.CreateHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_CreateHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateHook("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListHooks(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/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.Repositories.ListHooks("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListHooks returned error: %v", err) + } + + want := []Hook{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(hooks, want) { + t.Errorf("Repositories.ListHooks returned %+v, want %+v", hooks, want) + } +} + +func TestRepositoriesService_ListHooks_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListHooks("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + hook, _, err := client.Repositories.GetHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.GetHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_GetHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetHook("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditHook(t *testing.T) { + setup() + defer teardown() + + input := &Hook{Name: String("t")} + + mux.HandleFunc("/repos/o/r/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.Repositories.EditHook("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditHook returned error: %v", err) + } + + want := &Hook{ID: Int(1)} + if !reflect.DeepEqual(hook, want) { + t.Errorf("Repositories.EditHook returned %+v, want %+v", hook, want) + } +} + +func TestRepositoriesService_EditHook_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.EditHook("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteHook(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteHook returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteHook_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteHook("%", "%", 1) + 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) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/hooks/1/tests", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + }) + + _, err := client.Repositories.TestHook("o", "r", 1) + if err != nil { + t.Errorf("Repositories.TestHook returned error: %v", err) + } +} + +func TestRepositoriesService_TestHook_invalidOwner(t *testing.T) { + _, err := client.Repositories.TestHook("%", "%", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys.go new file mode 100644 index 00000000000..0d12ec9a719 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys.go @@ -0,0 +1,108 @@ +// 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" + +// The Key type is defined in users_keys.go + +// ListKeys lists the deploy keys for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#list +func (s *RepositoriesService) ListKeys(owner string, repo string, opt *ListOptions) ([]Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + 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 + } + + keys := new([]Key) + resp, err := s.client.Do(req, keys) + if err != nil { + return nil, resp, err + } + + return *keys, resp, err +} + +// GetKey fetches a single deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#get +func (s *RepositoriesService) GetKey(owner string, repo string, id int) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, err +} + +// CreateKey adds a deploy key for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#create +func (s *RepositoriesService) CreateKey(owner string, repo string, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// EditKey edits a deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#edit +func (s *RepositoriesService) EditKey(owner string, repo string, id int, key *Key) (*Key, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteKey deletes a deploy key. +// +// GitHub API docs: http://developer.github.com/v3/repos/keys/#delete +func (s *RepositoriesService) DeleteKey(owner string, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys_test.go new file mode 100644 index 00000000000..dcf6c55e4ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_keys_test.go @@ -0,0 +1,153 @@ +// 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" +) + +func TestRepositoriesService_ListKeys(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Repositories.ListKeys("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Repositories.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestRepositoriesService_ListKeys_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListKeys("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Repositories.GetKey("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_GetKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.GetKey("%", "%", 1) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + 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}`) + }) + + key, _, err := client.Repositories.CreateKey("o", "r", input) + if err != nil { + t.Errorf("Repositories.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.GetKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_CreateKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateKey("%", "%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_EditKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + 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}`) + }) + + key, _, err := client.Repositories.EditKey("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Repositories.EditKey returned %+v, want %+v", key, want) + } +} + +func TestRepositoriesService_EditKey_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.EditKey("%", "%", 1, nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_DeleteKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteKey("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteKey returned error: %v", err) + } +} + +func TestRepositoriesService_DeleteKey_invalidOwner(t *testing.T) { + _, err := client.Repositories.DeleteKey("%", "%", 1) + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging.go new file mode 100644 index 00000000000..31f8313ea7e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging.go @@ -0,0 +1,37 @@ +// Copyright 2014 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" +) + +// RepositoryMergeRequest represents a request to merge a branch in a +// repository. +type RepositoryMergeRequest struct { + Base *string `json:"base,omitempty"` + Head *string `json:"head,omitempty"` + CommitMessage *string `json:"commit_message,omitempty"` +} + +// Merge a branch in the specified repository. +// +// GitHub API docs: https://developer.github.com/v3/repos/merging/#perform-a-merge +func (s *RepositoriesService) Merge(owner, repo string, request *RepositoryMergeRequest) (*RepositoryCommit, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/merges", owner, repo) + req, err := s.client.NewRequest("POST", u, request) + if err != nil { + return nil, nil, err + } + + commit := new(RepositoryCommit) + resp, err := s.client.Do(req, commit) + if err != nil { + return nil, resp, err + } + + return commit, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging_test.go new file mode 100644 index 00000000000..166c5e5204f --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_merging_test.go @@ -0,0 +1,47 @@ +// Copyright 2014 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 TestRepositoriesService_Merge(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryMergeRequest{ + Base: String("b"), + Head: String("h"), + CommitMessage: String("c"), + } + + mux.HandleFunc("/repos/o/r/merges", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryMergeRequest) + 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, `{"sha":"s"}`) + }) + + commit, _, err := client.Repositories.Merge("o", "r", input) + if err != nil { + t.Errorf("Repositories.Merge returned error: %v", err) + } + + want := &RepositoryCommit{SHA: String("s")} + if !reflect.DeepEqual(commit, want) { + t.Errorf("Repositories.Merge returned %+v, want %+v", commit, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages.go new file mode 100644 index 00000000000..2384eaf6be2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages.go @@ -0,0 +1,90 @@ +// Copyright 2014 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" + +// Pages represents a GitHub Pages site configuration. +type Pages struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + CNAME *string `json:"cname,omitempty"` + Custom404 *bool `json:"custom_404,omitempty"` +} + +// PagesError represents a build error for a GitHub Pages site. +type PagesError struct { + Message *string `json:"message,omitempty"` +} + +// PagesBuild represents the build information for a GitHub Pages site. +type PagesBuild struct { + URL *string `json:"url,omitempty"` + Status *string `json:"status,omitempty"` + Error *PagesError `json:"error,omitempty"` + Pusher *User `json:"pusher,omitempty"` + Commit *string `json:"commit,omitempty"` + Duration *int `json:"duration,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"created_at,omitempty"` +} + +// GetPagesInfo fetches information about a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#get-information-about-a-pages-site +func (s *RepositoriesService) GetPagesInfo(owner string, repo string) (*Pages, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + site := new(Pages) + resp, err := s.client.Do(req, site) + if err != nil { + return nil, resp, err + } + + return site, resp, err +} + +// ListPagesBuilds lists the builds for a GitHub Pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-pages-builds +func (s *RepositoriesService) ListPagesBuilds(owner string, repo string) ([]PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var pages []PagesBuild + resp, err := s.client.Do(req, &pages) + if err != nil { + return nil, resp, err + } + + return pages, resp, err +} + +// GetLatestPagesBuild fetches the latest build information for a GitHub pages site. +// +// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-latest-pages-build +func (s *RepositoriesService) GetLatestPagesBuild(owner string, repo string) (*PagesBuild, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pages/builds/latest", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + build := new(PagesBuild) + resp, err := s.client.Do(req, build) + if err != nil { + return nil, resp, err + } + + return build, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages_test.go new file mode 100644 index 00000000000..4cbc43a17cc --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_pages_test.go @@ -0,0 +1,73 @@ +// Copyright 2014 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 TestRepositoriesService_GetPagesInfo(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","cname":"c","custom_404":false}`) + }) + + page, _, err := client.Repositories.GetPagesInfo("o", "r") + if err != nil { + t.Errorf("Repositories.GetPagesInfo returned error: %v", err) + } + + want := &Pages{URL: String("u"), Status: String("s"), CNAME: String("c"), Custom404: Bool(false)} + if !reflect.DeepEqual(page, want) { + t.Errorf("Repositories.GetPagesInfo returned %+v, want %+v", page, want) + } +} + +func TestRepositoriesService_ListPagesBuilds(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"url":"u","status":"s","commit":"c"}]`) + }) + + pages, _, err := client.Repositories.ListPagesBuilds("o", "r") + if err != nil { + t.Errorf("Repositories.ListPagesBuilds returned error: %v", err) + } + + want := []PagesBuild{{URL: String("u"), Status: String("s"), Commit: String("c")}} + if !reflect.DeepEqual(pages, want) { + t.Errorf("Repositories.ListPagesBuilds returned %+v, want %+v", pages, want) + } +} + +func TestRepositoriesService_GetLatestPagesBuild(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/pages/builds/latest", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"url":"u","status":"s","commit":"c"}`) + }) + + build, _, err := client.Repositories.GetLatestPagesBuild("o", "r") + if err != nil { + t.Errorf("Repositories.GetLatestPagesBuild returned error: %v", err) + } + + want := &PagesBuild{URL: String("u"), Status: String("s"), Commit: String("c")} + if !reflect.DeepEqual(build, want) { + t.Errorf("Repositories.GetLatestPagesBuild returned %+v, want %+v", build, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases.go new file mode 100644 index 00000000000..1400114418e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases.go @@ -0,0 +1,258 @@ +// 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 ( + "errors" + "fmt" + "mime" + "os" + "path/filepath" +) + +// RepositoryRelease represents a GitHub release in a repository. +type RepositoryRelease struct { + ID *int `json:"id,omitempty"` + TagName *string `json:"tag_name,omitempty"` + TargetCommitish *string `json:"target_commitish,omitempty"` + Name *string `json:"name,omitempty"` + Body *string `json:"body,omitempty"` + Draft *bool `json:"draft,omitempty"` + Prerelease *bool `json:"prerelease,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + PublishedAt *Timestamp `json:"published_at,omitempty"` + URL *string `json:"url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + AssetsURL *string `json:"assets_url,omitempty"` + Assets []ReleaseAsset `json:"assets,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` + ZipballURL *string `json:"zipball_url,omitempty"` + TarballURL *string `json:"tarball_url,omitempty"` +} + +func (r RepositoryRelease) String() string { + return Stringify(r) +} + +// ReleaseAsset represents a Github release asset in a repository. +type ReleaseAsset struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + Name *string `json:"name,omitempty"` + Label *string `json:"label,omitempty"` + State *string `json:"state,omitempty"` + ContentType *string `json:"content_type,omitempty"` + Size *int `json:"size,omitempty"` + DownloadCount *int `json:"download_count,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + BrowserDownloadURL *string `json:"browser_download_url,omitempty"` + Uploader *User `json:"uploader,omitempty"` +} + +func (r ReleaseAsset) String() string { + return Stringify(r) +} + +// ListReleases lists the releases for a repository. +// +// GitHub API docs: http://developer.github.com/v3/repos/releases/#list-releases-for-a-repository +func (s *RepositoriesService) ListReleases(owner, repo string, opt *ListOptions) ([]RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + 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 + } + + releases := new([]RepositoryRelease) + resp, err := s.client.Do(req, releases) + if err != nil { + return nil, resp, err + } + return *releases, resp, err +} + +// GetRelease fetches 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) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + release := new(RepositoryRelease) + resp, err := s.client.Do(req, release) + if err != nil { + return nil, resp, err + } + return release, resp, err +} + +// CreateRelease adds a new release for a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#create-a-release +func (s *RepositoriesService) CreateRelease(owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) + + req, err := s.client.NewRequest("POST", u, release) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + return r, resp, err +} + +// EditRelease edits a repository release. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release +func (s *RepositoriesService) EditRelease(owner, repo string, id int, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + + r := new(RepositoryRelease) + resp, err := s.client.Do(req, r) + if err != nil { + return nil, resp, err + } + return r, resp, err +} + +// DeleteRelease delete a single release from a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#delete-a-release +func (s *RepositoriesService) DeleteRelease(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// ListReleaseAssets lists the release's assets. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#list-assets-for-a-release +func (s *RepositoriesService) ListReleaseAssets(owner, repo string, id int, opt *ListOptions) ([]ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + 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 + } + + assets := new([]ReleaseAsset) + resp, err := s.client.Do(req, assets) + if err != nil { + return nil, resp, nil + } + return *assets, resp, err +} + +// GetReleaseAsset fetches a single release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#get-a-single-release-asset +func (s *RepositoriesService) GetReleaseAsset(owner, repo string, id int) (*ReleaseAsset, *Response, 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, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, nil + } + return asset, resp, err +} + +// EditReleaseAsset edits a repository release asset. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#edit-a-release-asset +func (s *RepositoriesService) EditReleaseAsset(owner, repo string, id int, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("PATCH", u, release) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} + +// DeleteReleaseAsset delete a single release asset from a repository. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#delete-a-release-asset +func (s *RepositoriesService) DeleteReleaseAsset(owner, repo string, id int) (*Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(req, nil) +} + +// UploadReleaseAsset creates an asset by uploading a file into a release repository. +// To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. +// +// GitHub API docs : http://developer.github.com/v3/repos/releases/#upload-a-release-asset +func (s *RepositoriesService) UploadReleaseAsset(owner, repo string, id int, opt *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { + u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) + u, err := addOptions(u, opt) + if err != nil { + return nil, nil, err + } + + stat, err := file.Stat() + if err != nil { + return nil, nil, err + } + if stat.IsDir() { + return nil, nil, errors.New("the asset to upload can't be a directory") + } + + mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) + req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) + if err != nil { + return nil, nil, err + } + + asset := new(ReleaseAsset) + resp, err := s.client.Do(req, asset) + if err != nil { + return nil, resp, err + } + return asset, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases_test.go new file mode 100644 index 00000000000..17c670235fb --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_releases_test.go @@ -0,0 +1,237 @@ +// 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" + "os" + "reflect" + "testing" +) + +func TestRepositoriesService_ListReleases(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + releases, _, err := client.Repositories.ListReleases("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListReleases returned error: %v", err) + } + want := []RepositoryRelease{{ID: Int(1)}} + if !reflect.DeepEqual(releases, want) { + t.Errorf("Repositories.ListReleases returned %+v, want %+v", releases, want) + } +} + +func TestRepositoriesService_GetRelease(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + release, resp, err := client.Repositories.GetRelease("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetRelease returned error: %v\n%v", err, resp.Body) + } + + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.GetRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_CreateRelease(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryRelease{Name: String("v1.0")} + + mux.HandleFunc("/repos/o/r/releases", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryRelease) + 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}`) + }) + + release, _, err := client.Repositories.CreateRelease("o", "r", input) + if err != nil { + t.Errorf("Repositories.CreateRelease returned error: %v", err) + } + + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.CreateRelease returned %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_EditRelease(t *testing.T) { + setup() + defer teardown() + + input := &RepositoryRelease{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + v := new(RepositoryRelease) + 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}`) + }) + + release, _, err := client.Repositories.EditRelease("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditRelease returned error: %v", err) + } + want := &RepositoryRelease{ID: Int(1)} + if !reflect.DeepEqual(release, want) { + t.Errorf("Repositories.EditRelease returned = %+v, want %+v", release, want) + } +} + +func TestRepositoriesService_DeleteRelease(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteRelease("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteRelease returned error: %v", err) + } +} + +func TestRepositoriesService_ListReleaseAssets(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + assets, _, err := client.Repositories.ListReleaseAssets("o", "r", 1, opt) + if err != nil { + t.Errorf("Repositories.ListReleaseAssets returned error: %v", err) + } + want := []ReleaseAsset{{ID: Int(1)}} + if !reflect.DeepEqual(assets, want) { + t.Errorf("Repositories.ListReleaseAssets returned %+v, want %+v", assets, want) + } +} + +func TestRepositoriesService_GetReleaseAsset(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") + fmt.Fprint(w, `{"id":1}`) + }) + + asset, _, err := client.Repositories.GetReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.GetReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.GetReleaseAsset returned %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_EditReleaseAsset(t *testing.T) { + setup() + defer teardown() + + input := &ReleaseAsset{Name: String("n")} + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + v := new(ReleaseAsset) + 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}`) + }) + + asset, _, err := client.Repositories.EditReleaseAsset("o", "r", 1, input) + if err != nil { + t.Errorf("Repositories.EditReleaseAsset returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.EditReleaseAsset returned = %+v, want %+v", asset, want) + } +} + +func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/assets/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.DeleteReleaseAsset("o", "r", 1) + if err != nil { + t.Errorf("Repositories.DeleteReleaseAsset returned error: %v", err) + } +} + +func TestRepositoriesService_UploadReleaseAsset(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Content-Type", "text/plain; charset=utf-8") + testHeader(t, r, "Content-Length", "12") + testFormValues(t, r, values{"name": "n"}) + testBody(t, r, "Upload me !\n") + + fmt.Fprintf(w, `{"id":1}`) + }) + + file, dir, err := openTestFile("upload.txt", "Upload me !\n") + if err != nil { + t.Fatalf("Unable to create temp file: %v", err) + } + defer os.RemoveAll(dir) + + opt := &UploadOptions{Name: "n"} + asset, _, err := client.Repositories.UploadReleaseAsset("o", "r", 1, opt, file) + if err != nil { + t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err) + } + want := &ReleaseAsset{ID: Int(1)} + if !reflect.DeepEqual(asset, want) { + t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats.go new file mode 100644 index 00000000000..7c1de09e89a --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats.go @@ -0,0 +1,214 @@ +// Copyright 2014 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" + "time" +) + +// ContributorStats represents a contributor to a repository and their +// weekly contributions to a given repo. +type ContributorStats struct { + Author *Contributor `json:"author,omitempty"` + Total *int `json:"total,omitempty"` + Weeks []WeeklyStats `json:"weeks,omitempty"` +} + +func (c ContributorStats) String() string { + return Stringify(c) +} + +// WeeklyStats represents the number of additions, deletions and commits +// a Contributor made in a given week. +type WeeklyStats struct { + Week *Timestamp `json:"w,omitempty"` + Additions *int `json:"a,omitempty"` + Deletions *int `json:"d,omitempty"` + Commits *int `json:"c,omitempty"` +} + +func (w WeeklyStats) String() string { + return Stringify(w) +} + +// ListContributorsStats gets a repo's contributor list with additions, +// deletions and commit counts. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code of +// 202. This is because this is the status that github returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#contributors +func (s *RepositoriesService) ListContributorsStats(owner, repo string) ([]ContributorStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/contributors", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var contributorStats []ContributorStats + resp, err := s.client.Do(req, &contributorStats) + if err != nil { + return nil, resp, err + } + + return contributorStats, resp, err +} + +// WeeklyCommitActivity represents the weekly commit activity for a repository. +// The days array is a group of commits per day, starting on Sunday. +type WeeklyCommitActivity struct { + Days []int `json:"days,omitempty"` + Total *int `json:"total,omitempty"` + Week *Timestamp `json:"week,omitempty"` +} + +func (w WeeklyCommitActivity) String() string { + return Stringify(w) +} + +// ListCommitActivity returns the last year of commit activity +// grouped by week. The days array is a group of commits per day, +// starting on Sunday. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code of +// 202. This is because this is the status that github returns to signify that +// it is now computing the requested statistics. A follow up request, after a +// delay of a second or so, should result in a successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#commit-activity +func (s *RepositoriesService) ListCommitActivity(owner, repo string) ([]WeeklyCommitActivity, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/commit_activity", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeklyCommitActivity []WeeklyCommitActivity + resp, err := s.client.Do(req, &weeklyCommitActivity) + if err != nil { + return nil, resp, err + } + + return weeklyCommitActivity, resp, err +} + +// ListCodeFrequency returns a weekly aggregate of the number of additions and +// deletions pushed to a repository. Returned WeeklyStats will contain +// additiona and deletions, but not total commits. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#code-frequency +func (s *RepositoriesService) ListCodeFrequency(owner, repo string) ([]WeeklyStats, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/code_frequency", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var weeks [][]int + resp, err := s.client.Do(req, &weeks) + + // convert int slices into WeeklyStats + var stats []WeeklyStats + for _, week := range weeks { + if len(week) != 3 { + continue + } + stat := WeeklyStats{ + Week: &Timestamp{time.Unix(int64(week[0]), 0)}, + Additions: Int(week[1]), + Deletions: Int(week[2]), + } + stats = append(stats, stat) + } + + return stats, resp, err +} + +// RepositoryParticipation is the number of commits by everyone +// who has contributed to the repository (including the owner) +// as well as the number of commits by the owner themself. +type RepositoryParticipation struct { + All []int `json:"all,omitempty"` + Owner []int `json:"owner,omitempty"` +} + +func (r RepositoryParticipation) String() string { + return Stringify(r) +} + +// ListParticipation returns the total commit counts for the 'owner' +// and total commit counts in 'all'. 'all' is everyone combined, +// including the 'owner' in the last 52 weeks. If you’d like to get +// the commit counts for non-owners, you can subtract 'all' from 'owner'. +// +// The array order is oldest week (index 0) to most recent week. +// +// If this is the first time these statistics are requested for the given +// repository, this method will return a non-nil error and a status code +// of 202. This is because this is the status that github returns to +// signify that it is now computing the requested statistics. A follow +// up request, after a delay of a second or so, should result in a +// successful request. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#participation +func (s *RepositoriesService) ListParticipation(owner, repo string) (*RepositoryParticipation, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/participation", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + participation := new(RepositoryParticipation) + resp, err := s.client.Do(req, participation) + if err != nil { + return nil, resp, err + } + + return participation, resp, err +} + +// PunchCard respresents the number of commits made during a given hour of a +// day of thew eek. +type PunchCard struct { + Day *int // Day of the week (0-6: =Sunday - Saturday). + Hour *int // Hour of day (0-23). + Commits *int // Number of commits. +} + +// ListPunchCard returns the number of commits per hour in each day. +// +// GitHub API Docs: https://developer.github.com/v3/repos/statistics/#punch-card +func (s *RepositoriesService) ListPunchCard(owner, repo string) ([]PunchCard, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/stats/punch_card", owner, repo) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var results [][]int + resp, err := s.client.Do(req, &results) + + // convert int slices into Punchcards + var cards []PunchCard + for _, result := range results { + if len(result) != 3 { + continue + } + card := PunchCard{ + Day: Int(result[0]), + Hour: Int(result[1]), + Commits: Int(result[2]), + } + cards = append(cards, card) + } + + return cards, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats_test.go new file mode 100644 index 00000000000..3f9fab5ca00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_stats_test.go @@ -0,0 +1,210 @@ +// Copyright 2014 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" + "time" +) + +func TestRepositoriesService_ListContributorsStats(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "author": { + "id": 1 + }, + "total": 135, + "weeks": [ + { + "w": 1367712000, + "a": 6898, + "d": 77, + "c": 10 + } + ] + } +] +`) + }) + + stats, _, err := client.Repositories.ListContributorsStats("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListContributorsStats returned error: %v", err) + } + + want := []ContributorStats{ + { + Author: &Contributor{ + ID: Int(1), + }, + Total: Int(135), + Weeks: []WeeklyStats{ + { + Week: &Timestamp{time.Date(2013, 05, 05, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(6898), + Deletions: Int(77), + Commits: Int(10), + }, + }, + }, + } + + if !reflect.DeepEqual(stats, want) { + t.Errorf("RepositoriesService.ListContributorsStats returned %+v, want %+v", stats, want) + } +} + +func TestRepositoriesService_ListCommitActivity(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/commit_activity", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +[ + { + "days": [0, 3, 26, 20, 39, 1, 0], + "total": 89, + "week": 1336280400 + } +] +`) + }) + + activity, _, err := client.Repositories.ListCommitActivity("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCommitActivity returned error: %v", err) + } + + want := []WeeklyCommitActivity{ + { + Days: []int{0, 3, 26, 20, 39, 1, 0}, + Total: Int(89), + Week: &Timestamp{time.Date(2012, 05, 06, 05, 00, 00, 0, time.UTC).Local()}, + }, + } + + if !reflect.DeepEqual(activity, want) { + t.Errorf("RepositoriesService.ListCommitActivity returned %+v, want %+v", activity, want) + } +} + +func TestRepositoriesService_ListCodeFrequency(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/code_frequency", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[[1302998400, 1124, -435]]`) + }) + + code, _, err := client.Repositories.ListCodeFrequency("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListCodeFrequency returned error: %v", err) + } + + want := []WeeklyStats{{ + Week: &Timestamp{time.Date(2011, 04, 17, 00, 00, 00, 0, time.UTC).Local()}, + Additions: Int(1124), + Deletions: Int(-435), + }} + + if !reflect.DeepEqual(code, want) { + t.Errorf("RepositoriesService.ListCodeFrequency returned %+v, want %+v", code, want) + } +} + +func TestRepositoriesService_Participation(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/participation", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, ` +{ + "all": [ + 11,21,15,2,8,1,8,23,17,21,11,10,33, + 91,38,34,22,23,32,3,43,87,71,18,13,5, + 13,16,66,27,12,45,110,117,13,8,18,9,19, + 26,39,12,20,31,46,91,45,10,24,9,29,7 + ], + "owner": [ + 3,2,3,0,2,0,5,14,7,9,1,5,0, + 48,19,2,0,1,10,2,23,40,35,8,8,2, + 10,6,30,0,2,9,53,104,3,3,10,4,7, + 11,21,4,4,22,26,63,11,2,14,1,10,3 + ] +} +`) + }) + + participation, _, err := client.Repositories.ListParticipation("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListParticipation returned error: %v", err) + } + + want := &RepositoryParticipation{ + All: []int{ + 11, 21, 15, 2, 8, 1, 8, 23, 17, 21, 11, 10, 33, + 91, 38, 34, 22, 23, 32, 3, 43, 87, 71, 18, 13, 5, + 13, 16, 66, 27, 12, 45, 110, 117, 13, 8, 18, 9, 19, + 26, 39, 12, 20, 31, 46, 91, 45, 10, 24, 9, 29, 7, + }, + Owner: []int{ + 3, 2, 3, 0, 2, 0, 5, 14, 7, 9, 1, 5, 0, + 48, 19, 2, 0, 1, 10, 2, 23, 40, 35, 8, 8, 2, + 10, 6, 30, 0, 2, 9, 53, 104, 3, 3, 10, 4, 7, + 11, 21, 4, 4, 22, 26, 63, 11, 2, 14, 1, 10, 3, + }, + } + + if !reflect.DeepEqual(participation, want) { + t.Errorf("RepositoriesService.ListParticipation returned %+v, want %+v", participation, want) + } +} + +func TestRepositoriesService_ListPunchCard(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/stats/punch_card", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + fmt.Fprint(w, `[ + [0, 0, 5], + [0, 1, 43], + [0, 2, 21] + ]`) + }) + + card, _, err := client.Repositories.ListPunchCard("o", "r") + if err != nil { + t.Errorf("RepositoriesService.ListPunchCard returned error: %v", err) + } + + want := []PunchCard{ + {Day: Int(0), Hour: Int(0), Commits: Int(5)}, + {Day: Int(0), Hour: Int(1), Commits: Int(43)}, + {Day: Int(0), Hour: Int(2), Commits: Int(21)}, + } + + if !reflect.DeepEqual(card, want) { + t.Errorf("RepositoriesService.ListPunchCard returned %+v, want %+v", card, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses.go new file mode 100644 index 00000000000..0379a2b47e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses.go @@ -0,0 +1,128 @@ +// 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" + "time" +) + +// RepoStatus represents the status of a repository at a particular reference. +type RepoStatus struct { + ID *int `json:"id,omitempty"` + URL *string `json:"url,omitempty"` + + // State is the current state of the repository. Possible values are: + // pending, success, error, or failure. + State *string `json:"state,omitempty"` + + // TargetURL is the URL of the page representing this status. It will be + // linked from the GitHub UI to allow users to see the source of the status. + TargetURL *string `json:"target_url,omitempty"` + + // Description is a short high level summary of the status. + Description *string `json:"description,omitempty"` + + // A string label to differentiate this status from the statuses of other systems. + Context *string `json:"context,omitempty"` + + Creator *User `json:"creator,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +func (r RepoStatus) String() string { + return Stringify(r) +} + +// ListStatuses lists the statuses of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: http://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref +func (s *RepositoriesService) ListStatuses(owner, repo, ref string, opt *ListOptions) ([]RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/statuses", owner, repo, ref) + 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 + } + + statuses := new([]RepoStatus) + resp, err := s.client.Do(req, statuses) + if err != nil { + return nil, resp, err + } + + return *statuses, resp, err +} + +// CreateStatus creates a new status for a repository at the specified +// reference. Ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: http://developer.github.com/v3/repos/statuses/#create-a-status +func (s *RepositoriesService) CreateStatus(owner, repo, ref string, status *RepoStatus) (*RepoStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/statuses/%v", owner, repo, ref) + req, err := s.client.NewRequest("POST", u, status) + if err != nil { + return nil, nil, err + } + + repoStatus := new(RepoStatus) + resp, err := s.client.Do(req, repoStatus) + if err != nil { + return nil, resp, err + } + + return repoStatus, resp, err +} + +// CombinedStatus represents the combined status of a repository at a particular reference. +type CombinedStatus struct { + // State is the combined state of the repository. Possible values are: + // failture, pending, or success. + State *string `json:"state,omitempty"` + + Name *string `json:"name,omitempty"` + SHA *string `json:"sha,omitempty"` + TotalCount *int `json:"total_count,omitempty"` + Statuses []RepoStatus `json:"statuses,omitempty"` + + CommitURL *string `json:"commit_url,omitempty"` + RepositoryURL *string `json:"repository_url,omitempty"` +} + +func (s CombinedStatus) String() string { + return Stringify(s) +} + +// GetCombinedStatus returns the combined status of a repository at the specified +// reference. ref can be a SHA, a branch name, or a tag name. +// +// GitHub API docs: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref +func (s *RepositoriesService) GetCombinedStatus(owner, repo, ref string, opt *ListOptions) (*CombinedStatus, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/commits/%v/status", owner, repo, ref) + 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 + } + + status := new(CombinedStatus) + resp, err := s.client.Do(req, status) + if err != nil { + return nil, resp, err + } + + return status, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses_test.go new file mode 100644 index 00000000000..8b230528cec --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_statuses_test.go @@ -0,0 +1,96 @@ +// 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" +) + +func TestRepositoriesService_ListStatuses(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/statuses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + statuses, _, err := client.Repositories.ListStatuses("o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.ListStatuses returned error: %v", err) + } + + want := []RepoStatus{{ID: Int(1)}} + if !reflect.DeepEqual(statuses, want) { + t.Errorf("Repositories.ListStatuses returned %+v, want %+v", statuses, want) + } +} + +func TestRepositoriesService_ListStatuses_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListStatuses("%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_CreateStatus(t *testing.T) { + setup() + defer teardown() + + input := &RepoStatus{State: String("s"), TargetURL: String("t"), Description: String("d")} + + mux.HandleFunc("/repos/o/r/statuses/r", func(w http.ResponseWriter, r *http.Request) { + v := new(RepoStatus) + 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}`) + }) + + status, _, err := client.Repositories.CreateStatus("o", "r", "r", input) + if err != nil { + t.Errorf("Repositories.CreateStatus returned error: %v", err) + } + + want := &RepoStatus{ID: Int(1)} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.CreateStatus returned %+v, want %+v", status, want) + } +} + +func TestRepositoriesService_CreateStatus_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.CreateStatus("%", "r", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_GetCombinedStatus(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/commits/r/status", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `{"state":"success", "statuses":[{"id":1}]}`) + }) + + opt := &ListOptions{Page: 2} + status, _, err := client.Repositories.GetCombinedStatus("o", "r", "r", opt) + if err != nil { + t.Errorf("Repositories.GetCombinedStatus returned error: %v", err) + } + + want := &CombinedStatus{State: String("success"), Statuses: []RepoStatus{{ID: Int(1)}}} + if !reflect.DeepEqual(status, want) { + t.Errorf("Repositories.GetCombinedStatus returned %+v, want %+v", status, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/repos_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/repos_test.go new file mode 100644 index 00000000000..c489fcf4109 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/repos_test.go @@ -0,0 +1,407 @@ +// 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" +) + +func TestRepositoriesService_List_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1},{"id":2}]`) + }) + + repos, _, err := client.Repositories.List("", nil) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}, {ID: Int(2)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "type": "owner", + "sort": "created", + "direction": "asc", + "page": "2", + }) + + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListOptions{"owner", "created", "asc", ListOptions{Page: 2}} + repos, _, err := client.Repositories.List("u", opt) + if err != nil { + t.Errorf("Repositories.List returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.List returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_List_invalidUser(t *testing.T) { + _, _, err := client.Repositories.List("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListByOrg(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "type": "forks", + "page": "2", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListByOrgOptions{"forks", ListOptions{Page: 2}} + repos, _, err := client.Repositories.ListByOrg("o", opt) + if err != nil { + t.Errorf("Repositories.ListByOrg returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListByOrg returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_ListByOrg_invalidOrg(t *testing.T) { + _, _, err := client.Repositories.ListByOrg("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListAll(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "since": "1", + "page": "2", + "per_page": "3", + }) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &RepositoryListAllOptions{1, ListOptions{2, 3}} + repos, _, err := client.Repositories.ListAll(opt) + if err != nil { + t.Errorf("Repositories.ListAll returned error: %v", err) + } + + want := []Repository{{ID: Int(1)}} + if !reflect.DeepEqual(repos, want) { + t.Errorf("Repositories.ListAll returned %+v, want %+v", repos, want) + } +} + +func TestRepositoriesService_Create_user(t *testing.T) { + setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/user/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + 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}`) + }) + + repo, _, err := client.Repositories.Create("", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_org(t *testing.T) { + setup() + defer teardown() + + input := &Repository{Name: String("n")} + + mux.HandleFunc("/orgs/o/repos", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + 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}`) + }) + + repo, _, err := client.Repositories.Create("o", input) + if err != nil { + t.Errorf("Repositories.Create returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Create returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Create_invalidOrg(t *testing.T) { + _, _, err := client.Repositories.Create("%", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_Get(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + 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") + if err != nil { + t.Errorf("Repositories.Get returned error: %v", err) + } + + 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) { + t.Errorf("Repositories.Get returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Edit(t *testing.T) { + setup() + defer teardown() + + i := true + input := &Repository{HasIssues: &i} + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + v := new(Repository) + 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}`) + }) + + repo, _, err := client.Repositories.Edit("o", "r", input) + if err != nil { + t.Errorf("Repositories.Edit returned error: %v", err) + } + + want := &Repository{ID: Int(1)} + if !reflect.DeepEqual(repo, want) { + t.Errorf("Repositories.Edit returned %+v, want %+v", repo, want) + } +} + +func TestRepositoriesService_Delete(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Repositories.Delete("o", "r") + if err != nil { + t.Errorf("Repositories.Delete returned error: %v", err) + } +} + +func TestRepositoriesService_Get_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.Get("%", "r") + testURLParseError(t, err) +} + +func TestRepositoriesService_Edit_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.Edit("%", "r", nil) + testURLParseError(t, err) +} + +func TestRepositoriesService_ListContributors(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/contributors", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "anon": "true", + "page": "2", + }) + fmt.Fprint(w, `[{"contributions":42}]`) + }) + + opts := &ListContributorsOptions{Anon: "true", ListOptions: ListOptions{Page: 2}} + contributors, _, err := client.Repositories.ListContributors("o", "r", opts) + + if err != nil { + t.Errorf("Repositories.ListContributors returned error: %v", err) + } + + want := []Contributor{{Contributions: Int(42)}} + if !reflect.DeepEqual(contributors, want) { + t.Errorf("Repositories.ListContributors returned %+v, want %+v", contributors, want) + } +} + +func TestRepositoriesService_ListLanguages(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/languages", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"go":1}`) + }) + + languages, _, err := client.Repositories.ListLanguages("o", "r") + if err != nil { + t.Errorf("Repositories.ListLanguages returned error: %v", err) + } + + want := map[string]int{"go": 1} + if !reflect.DeepEqual(languages, want) { + t.Errorf("Repositories.ListLanguages returned %+v, want %+v", languages, want) + } +} + +func TestRepositoriesService_ListTeams(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/teams", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + teams, _, err := client.Repositories.ListTeams("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTeams returned error: %v", err) + } + + want := []Team{{ID: Int(1)}} + if !reflect.DeepEqual(teams, want) { + t.Errorf("Repositories.ListTeams returned %+v, want %+v", teams, want) + } +} + +func TestRepositoriesService_ListTags(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/tags", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"n", "commit" : {"sha" : "s", "url" : "u"}, "zipball_url": "z", "tarball_url": "t"}]`) + }) + + opt := &ListOptions{Page: 2} + tags, _, err := client.Repositories.ListTags("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListTags returned error: %v", err) + } + + want := []RepositoryTag{ + { + Name: String("n"), + Commit: &Commit{ + SHA: String("s"), + URL: String("u"), + }, + ZipballURL: String("z"), + TarballURL: String("t"), + }, + } + if !reflect.DeepEqual(tags, want) { + t.Errorf("Repositories.ListTags returned %+v, want %+v", tags, want) + } +} + +func TestRepositoriesService_ListBranches(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"name":"master", "commit" : {"sha" : "a57781", "url" : "https://api.github.com/repos/o/r/commits/a57781"}}]`) + }) + + opt := &ListOptions{Page: 2} + branches, _, err := client.Repositories.ListBranches("o", "r", opt) + if err != nil { + t.Errorf("Repositories.ListBranches returned error: %v", err) + } + + want := []Branch{{Name: String("master"), Commit: &Commit{SHA: String("a57781"), URL: String("https://api.github.com/repos/o/r/commits/a57781")}}} + if !reflect.DeepEqual(branches, want) { + t.Errorf("Repositories.ListBranches returned %+v, want %+v", branches, want) + } +} + +func TestRepositoriesService_GetBranch(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/branches/b", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"name":"n", "commit":{"sha":"s"}}`) + }) + + branch, _, err := client.Repositories.GetBranch("o", "r", "b") + if err != nil { + t.Errorf("Repositories.GetBranch returned error: %v", err) + } + + want := &Branch{Name: String("n"), Commit: &Commit{SHA: String("s")}} + if !reflect.DeepEqual(branch, want) { + t.Errorf("Repositories.GetBranch returned %+v, want %+v", branch, want) + } +} + +func TestRepositoriesService_ListLanguages_invalidOwner(t *testing.T) { + _, _, err := client.Repositories.ListLanguages("%", "%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/search.go b/Godeps/_workspace/src/github.com/google/go-github/github/search.go new file mode 100644 index 00000000000..d9e9b419a88 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/search.go @@ -0,0 +1,158 @@ +// 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" + + qs "github.com/google/go-querystring/query" +) + +// SearchService provides access to the search related functions +// in the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/search/ +type SearchService struct { + client *Client +} + +// SearchOptions specifies optional parameters to the SearchService methods. +type SearchOptions struct { + // How to sort the search results. Possible values are: + // - for repositories: stars, fork, updated + // - for code: indexed + // - for issues: comments, created, updated + // - for users: followers, repositories, joined + // + // Default is to sort by best match. + Sort string `url:"sort,omitempty"` + + // Sort order if sort parameter is provided. Possible values are: asc, + // desc. Default is desc. + Order string `url:"order,omitempty"` + + // Whether to retrieve text match metadata with a query + TextMatch bool `url:"-"` + + ListOptions +} + +// RepositoriesSearchResult represents the result of a repositories search. +type RepositoriesSearchResult struct { + Total *int `json:"total_count,omitempty"` + Repositories []Repository `json:"items,omitempty"` +} + +// Repositories searches repositories via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-repositories +func (s *SearchService) Repositories(query string, opt *SearchOptions) (*RepositoriesSearchResult, *Response, error) { + result := new(RepositoriesSearchResult) + resp, err := s.search("repositories", query, opt, result) + return result, resp, err +} + +// IssuesSearchResult represents the result of an issues search. +type IssuesSearchResult struct { + Total *int `json:"total_count,omitempty"` + Issues []Issue `json:"items,omitempty"` +} + +// Issues searches issues via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-issues +func (s *SearchService) Issues(query string, opt *SearchOptions) (*IssuesSearchResult, *Response, error) { + result := new(IssuesSearchResult) + resp, err := s.search("issues", query, opt, result) + return result, resp, err +} + +// UsersSearchResult represents the result of an issues search. +type UsersSearchResult struct { + Total *int `json:"total_count,omitempty"` + Users []User `json:"items,omitempty"` +} + +// Users searches users via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-users +func (s *SearchService) Users(query string, opt *SearchOptions) (*UsersSearchResult, *Response, error) { + result := new(UsersSearchResult) + resp, err := s.search("users", query, opt, result) + return result, resp, err +} + +// Match represents a single text match. +type Match struct { + Text *string `json:"text,omitempty"` + Indices []int `json:"indices,omitempty"` +} + +// TextMatch represents a text match for a SearchResult +type TextMatch struct { + ObjectURL *string `json:"object_url,omitempty"` + ObjectType *string `json:"object_type,omitempty"` + Property *string `json:"property,omitempty"` + Fragment *string `json:"fragment,omitempty"` + Matches []Match `json:"matches,omitempty"` +} + +func (tm TextMatch) String() string { + return Stringify(tm) +} + +// CodeSearchResult represents the result of an code search. +type CodeSearchResult struct { + Total *int `json:"total_count,omitempty"` + CodeResults []CodeResult `json:"items,omitempty"` +} + +// CodeResult represents a single search result. +type CodeResult struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` + SHA *string `json:"sha,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + Repository *Repository `json:"repository,omitempty"` + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (c CodeResult) String() string { + return Stringify(c) +} + +// Code searches code via various criteria. +// +// GitHub API docs: http://developer.github.com/v3/search/#search-code +func (s *SearchService) Code(query string, opt *SearchOptions) (*CodeSearchResult, *Response, error) { + result := new(CodeSearchResult) + resp, err := s.search("code", query, opt, result) + return result, resp, err +} + +// Helper function that executes search queries against different +// GitHub search types (repositories, code, issues, users) +func (s *SearchService) search(searchType string, query string, opt *SearchOptions, result interface{}) (*Response, error) { + params, err := qs.Values(opt) + if err != nil { + return nil, err + } + params.Add("q", query) + u := fmt.Sprintf("search/%s?%s", searchType, params.Encode()) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + + if opt.TextMatch { + // Accept header defaults to "application/vnd.github.v3+json" + // We change it here to fetch back text-match metadata + req.Header.Set("Accept", "application/vnd.github.v3.text-match+json") + } + + return s.client.Do(req, result) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/search_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/search_test.go new file mode 100644 index 00000000000..3cfd1624374 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/search_test.go @@ -0,0 +1,196 @@ +package github + +import ( + "fmt" + "net/http" + "reflect" + + "testing" +) + +func TestSearchService_Repositories(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/repositories", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Repositories("blah", opts) + if err != nil { + t.Errorf("Search.Repositories returned error: %v", err) + } + + want := &RepositoriesSearchResult{ + Total: Int(4), + Repositories: []Repository{{ID: Int(1)}, {ID: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Repositories returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Issues(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"number":1},{"number":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Issues("blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &IssuesSearchResult{ + Total: Int(4), + Issues: []Issue{{Number: Int(1)}, {Number: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Issues returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Users(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"id":1},{"id":2}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Users("blah", opts) + if err != nil { + t.Errorf("Search.Issues returned error: %v", err) + } + + want := &UsersSearchResult{ + Total: Int(4), + Users: []User{{ID: Int(1)}, {ID: Int(2)}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Users returned %+v, want %+v", result, want) + } +} + +func TestSearchService_Code(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{ + "q": "blah", + "sort": "forks", + "order": "desc", + "page": "2", + "per_page": "2", + }) + + fmt.Fprint(w, `{"total_count": 4, "items": [{"name":"1"},{"name":"2"}]}`) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} + result, _, err := client.Search.Code("blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + want := &CodeSearchResult{ + Total: Int(4), + CodeResults: []CodeResult{{Name: String("1")}, {Name: String("2")}}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} + +func TestSearchService_CodeTextMatch(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + + textMatchResponse := ` + { + "total_count": 1, + "items": [ + { + "name":"gopher1", + "text_matches": [ + { + "fragment": "I'm afraid my friend what you have found\nIs a gopher who lives to feed", + "matches": [ + { + "text": "gopher", + "indices": [ + 14, + 21 + ] + } + ] + } + ] + } + ] + } + ` + + fmt.Fprint(w, textMatchResponse) + }) + + opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}, TextMatch: true} + result, _, err := client.Search.Code("blah", opts) + if err != nil { + t.Errorf("Search.Code returned error: %v", err) + } + + wantedCodeResult := CodeResult{ + Name: String("gopher1"), + TextMatches: []TextMatch{{ + Fragment: String("I'm afraid my friend what you have found\nIs a gopher who lives to feed"), + Matches: []Match{{Text: String("gopher"), Indices: []int{14, 21}}}, + }, + }, + } + + want := &CodeSearchResult{ + Total: Int(1), + CodeResults: []CodeResult{wantedCodeResult}, + } + if !reflect.DeepEqual(result, want) { + t.Errorf("Search.Code returned %+v, want %+v", result, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/strings.go b/Godeps/_workspace/src/github.com/google/go-github/github/strings.go new file mode 100644 index 00000000000..38577236c30 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/strings.go @@ -0,0 +1,93 @@ +// 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 ( + "bytes" + "fmt" + "io" + + "reflect" +) + +var timestampType = reflect.TypeOf(Timestamp{}) + +// Stringify attempts to create a reasonable string representation of types in +// the GitHub library. It does things like resolve pointers to their values +// and omits struct fields with nil values. +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// stringifyValue was heavily inspired by the goprotobuf library. + +func stringifyValue(w io.Writer, val reflect.Value) { + if val.Kind() == reflect.Ptr && val.IsNil() { + w.Write([]byte("")) + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(w, `"%s"`, v) + case reflect.Slice: + w.Write([]byte{'['}) + for i := 0; i < v.Len(); i++ { + if i > 0 { + w.Write([]byte{' '}) + } + + stringifyValue(w, v.Index(i)) + } + + w.Write([]byte{']'}) + return + case reflect.Struct: + if v.Type().Name() != "" { + w.Write([]byte(v.Type().String())) + } + + // special handling of Timestamp values + if v.Type() == timestampType { + fmt.Fprintf(w, "{%s}", v.Interface()) + return + } + + w.Write([]byte{'{'}) + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + w.Write([]byte(", ")) + } else { + sep = true + } + + w.Write([]byte(v.Type().Field(i).Name)) + w.Write([]byte{':'}) + stringifyValue(w, fv) + } + + w.Write([]byte{'}'}) + default: + if v.CanInterface() { + fmt.Fprint(w, v.Interface()) + } + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/strings_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/strings_test.go new file mode 100644 index 00000000000..a393eb6cfc5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/strings_test.go @@ -0,0 +1,137 @@ +// 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" + "testing" + "time" +) + +func TestStringify(t *testing.T) { + var nilPointer *string + + var tests = []struct { + in interface{} + out string + }{ + // basic types + {"foo", `"foo"`}, + {123, `123`}, + {1.5, `1.5`}, + {false, `false`}, + { + []string{"a", "b"}, + `["a" "b"]`, + }, + { + struct { + A []string + }{nil}, + // nil slice is skipped + `{}`, + }, + { + struct { + A string + }{"foo"}, + // structs not of a named type get no prefix + `{A:"foo"}`, + }, + + // pointers + {nilPointer, ``}, + {String("foo"), `"foo"`}, + {Int(123), `123`}, + {Bool(false), `false`}, + { + []*string{String("a"), String("b")}, + `["a" "b"]`, + }, + + // actual GitHub structs + { + Timestamp{time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + &Timestamp{time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)}, + `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, + }, + { + User{ID: Int(123), Name: String("n")}, + `github.User{ID:123, Name:"n"}`, + }, + { + Repository{Owner: &User{ID: Int(123)}}, + `github.Repository{Owner:github.User{ID:123}}`, + }, + } + + for i, tt := range tests { + s := Stringify(tt.in) + if s != tt.out { + t.Errorf("%d. Stringify(%q) => %q, want %q", i, tt.in, s, tt.out) + } + } +} + +// Directly test the String() methods on various GitHub types. We don't do an +// exaustive test of all the various field types, since TestStringify() above +// takes care of that. Rather, we just make sure that Stringify() is being +// used to build the strings, which we do by verifying that pointers are +// stringified as their underlying value. +func TestString(t *testing.T) { + var tests = []struct { + in interface{} + out string + }{ + {CodeResult{Name: String("n")}, `github.CodeResult{Name:"n"}`}, + {CommitAuthor{Name: String("n")}, `github.CommitAuthor{Name:"n"}`}, + {CommitFile{SHA: String("s")}, `github.CommitFile{SHA:"s"}`}, + {CommitStats{Total: Int(1)}, `github.CommitStats{Total:1}`}, + {CommitsComparison{TotalCommits: Int(1)}, `github.CommitsComparison{TotalCommits:1}`}, + {Commit{SHA: String("s")}, `github.Commit{SHA:"s"}`}, + {Event{ID: String("1")}, `github.Event{ID:"1"}`}, + {GistComment{ID: Int(1)}, `github.GistComment{ID:1}`}, + {GistFile{Size: Int(1)}, `github.GistFile{Size:1}`}, + {Gist{ID: String("1")}, `github.Gist{ID:"1", Files:map[]}`}, + {GitObject{SHA: String("s")}, `github.GitObject{SHA:"s"}`}, + {Gitignore{Name: String("n")}, `github.Gitignore{Name:"n"}`}, + {Hook{ID: Int(1)}, `github.Hook{Config:map[], ID:1}`}, + {IssueComment{ID: Int(1)}, `github.IssueComment{ID:1}`}, + {Issue{Number: Int(1)}, `github.Issue{Number:1}`}, + {Key{ID: Int(1)}, `github.Key{ID:1}`}, + {Label{Name: String("l")}, "l"}, + {Organization{ID: Int(1)}, `github.Organization{ID:1}`}, + {PullRequestComment{ID: Int(1)}, `github.PullRequestComment{ID:1}`}, + {PullRequest{Number: Int(1)}, `github.PullRequest{Number:1}`}, + {PushEventCommit{SHA: String("s")}, `github.PushEventCommit{SHA:"s"}`}, + {PushEvent{PushID: Int(1)}, `github.PushEvent{PushID:1}`}, + {Reference{Ref: String("r")}, `github.Reference{Ref:"r"}`}, + {ReleaseAsset{ID: Int(1)}, `github.ReleaseAsset{ID:1}`}, + {RepoStatus{ID: Int(1)}, `github.RepoStatus{ID:1}`}, + {RepositoryComment{ID: Int(1)}, `github.RepositoryComment{ID:1}`}, + {RepositoryCommit{SHA: String("s")}, `github.RepositoryCommit{SHA:"s"}`}, + {RepositoryContent{Name: String("n")}, `github.RepositoryContent{Name:"n"}`}, + {RepositoryRelease{ID: Int(1)}, `github.RepositoryRelease{ID:1}`}, + {Repository{ID: Int(1)}, `github.Repository{ID:1}`}, + {Team{ID: Int(1)}, `github.Team{ID:1}`}, + {TreeEntry{SHA: String("s")}, `github.TreeEntry{SHA:"s"}`}, + {Tree{SHA: String("s")}, `github.Tree{SHA:"s"}`}, + {User{ID: Int(1)}, `github.User{ID:1}`}, + {WebHookAuthor{Name: String("n")}, `github.WebHookAuthor{Name:"n"}`}, + {WebHookCommit{ID: String("1")}, `github.WebHookCommit{ID:"1"}`}, + {WebHookPayload{Ref: String("r")}, `github.WebHookPayload{Ref:"r"}`}, + } + + for i, tt := range tests { + s := tt.in.(fmt.Stringer).String() + if s != tt.out { + t.Errorf("%d. String() => %q, want %q", i, tt.in, tt.out) + } + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/timestamp.go b/Godeps/_workspace/src/github.com/google/go-github/github/timestamp.go new file mode 100644 index 00000000000..a1c1554a30e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/timestamp.go @@ -0,0 +1,41 @@ +// 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 ( + "strconv" + "time" +) + +// Timestamp represents a time that can be unmarshalled from a JSON string +// formatted as either an RFC3339 or Unix timestamp. This is necessary for some +// fields since the GitHub API is inconsistent in how it represents times. All +// exported methods of time.Time can be called on Timestamp. +type Timestamp struct { + time.Time +} + +func (t Timestamp) String() string { + return t.Time.String() +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// Time is expected in RFC3339 or Unix format. +func (t *Timestamp) UnmarshalJSON(data []byte) (err error) { + str := string(data) + i, err := strconv.ParseInt(str, 10, 64) + if err == nil { + (*t).Time = time.Unix(i, 0) + } else { + (*t).Time, err = time.Parse(`"`+time.RFC3339+`"`, str) + } + return +} + +// Equal reports whether t and u are equal based on time.Equal +func (t Timestamp) Equal(u Timestamp) bool { + return t.Time.Equal(u.Time) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/timestamp_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/timestamp_test.go new file mode 100644 index 00000000000..12376c51a6e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/timestamp_test.go @@ -0,0 +1,181 @@ +// 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" + "testing" + "time" +) + +const ( + emptyTimeStr = `"0001-01-01T00:00:00Z"` + referenceTimeStr = `"2006-01-02T15:04:05Z"` + referenceUnixTimeStr = `1136214245` +) + +var ( + referenceTime = time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC) + unixOrigin = time.Unix(0, 0).In(time.UTC) +) + +func TestTimestamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + want string + wantErr bool + equal bool + }{ + {"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true}, + {"Empty", Timestamp{}, emptyTimeStr, false, true}, + {"Mismatch", Timestamp{}, referenceTimeStr, false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if (got == tc.want) != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimestamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want Timestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true}, + {"ReferenceUnix", `1136214245`, Timestamp{referenceTime}, false, true}, + {"Empty", emptyTimeStr, Timestamp{}, false, true}, + {"UnixStart", `0`, Timestamp{unixOrigin}, false, true}, + {"Mismatch", referenceTimeStr, Timestamp{}, false, false}, + {"MismatchUnix", `0`, Timestamp{}, false, false}, + {"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Equal(tc.want) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data Timestamp + }{ + {"Reference", Timestamp{referenceTime}}, + {"Empty", Timestamp{}}, + } + for _, tc := range testCases { + data, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got Timestamp + err = json.Unmarshal(data, &got) + if !got.Equal(tc.data) { + t.Errorf("%s: %+v != %+v", tc.desc, got, data) + } + } +} + +type WrappedTimestamp struct { + A int + Time Timestamp +} + +func TestWrappedTimstamp_Marshal(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + want string + wantErr bool + equal bool + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true}, + {"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true}, + {"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false}, + } + for _, tc := range testCases { + out, err := json.Marshal(tc.data) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + } + got := string(out) + equal := got == tc.want + if equal != tc.equal { + t.Errorf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_Unmarshal(t *testing.T) { + testCases := []struct { + desc string + data string + want WrappedTimestamp + wantErr bool + equal bool + }{ + {"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true}, + {"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true}, + {"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true}, + {"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false}, + {"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false}, + } + for _, tc := range testCases { + var got Timestamp + err := json.Unmarshal([]byte(tc.data), &got) + if gotErr := err != nil; gotErr != tc.wantErr { + t.Errorf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err) + continue + } + equal := got.Time.Equal(tc.want.Time.Time) + if equal != tc.equal { + t.Errorf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal) + } + } +} + +func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) { + testCases := []struct { + desc string + data WrappedTimestamp + }{ + {"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}}, + {"Empty", WrappedTimestamp{0, Timestamp{}}}, + } + for _, tc := range testCases { + bytes, err := json.Marshal(tc.data) + if err != nil { + t.Errorf("%s: Marshal err=%v", tc.desc, err) + } + var got WrappedTimestamp + err = json.Unmarshal(bytes, &got) + if !got.Time.Equal(tc.data.Time) { + t.Errorf("%s: %+v != %+v", tc.desc, got, tc.data) + } + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users.go b/Godeps/_workspace/src/github.com/google/go-github/github/users.go new file mode 100644 index 00000000000..bd68ac20209 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users.go @@ -0,0 +1,140 @@ +// 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" + +// UsersService handles communication with the user related +// methods of the GitHub API. +// +// GitHub API docs: http://developer.github.com/v3/users/ +type UsersService struct { + client *Client +} + +// User represents a GitHub user. +type User struct { + Login *string `json:"login,omitempty"` + ID *int `json:"id,omitempty"` + AvatarURL *string `json:"avatar_url,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + GravatarID *string `json:"gravatar_id,omitempty"` + Name *string `json:"name,omitempty"` + Company *string `json:"company,omitempty"` + Blog *string `json:"blog,omitempty"` + Location *string `json:"location,omitempty"` + Email *string `json:"email,omitempty"` + Hireable *bool `json:"hireable,omitempty"` + Bio *string `json:"bio,omitempty"` + PublicRepos *int `json:"public_repos,omitempty"` + PublicGists *int `json:"public_gists,omitempty"` + Followers *int `json:"followers,omitempty"` + Following *int `json:"following,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + UpdatedAt *Timestamp `json:"updated_at,omitempty"` + Type *string `json:"type,omitempty"` + SiteAdmin *bool `json:"site_admin,omitempty"` + TotalPrivateRepos *int `json:"total_private_repos,omitempty"` + OwnedPrivateRepos *int `json:"owned_private_repos,omitempty"` + PrivateGists *int `json:"private_gists,omitempty"` + DiskUsage *int `json:"disk_usage,omitempty"` + Collaborators *int `json:"collaborators,omitempty"` + Plan *Plan `json:"plan,omitempty"` + + // API URLs + URL *string `json:"url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + FollowingURL *string `json:"following_url,omitempty"` + FollowersURL *string `json:"followers_url,omitempty"` + GistsURL *string `json:"gists_url,omitempty"` + OrganizationsURL *string `json:"organizations_url,omitempty"` + ReceivedEventsURL *string `json:"received_events_url,omitempty"` + ReposURL *string `json:"repos_url,omitempty"` + StarredURL *string `json:"starred_url,omitempty"` + SubscriptionsURL *string `json:"subscriptions_url,omitempty"` + + // TextMatches is only populated from search results that request text matches + // See: search.go and https://developer.github.com/v3/search/#text-match-metadata + TextMatches []TextMatch `json:"text_matches,omitempty"` +} + +func (u User) String() string { + return Stringify(u) +} + +// Get fetches a user. Passing the empty string will fetch the authenticated +// user. +// +// GitHub API docs: http://developer.github.com/v3/users/#get-a-single-user +func (s *UsersService) Get(user string) (*User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v", user) + } else { + u = "user" + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, err +} + +// Edit the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/#update-the-authenticated-user +func (s *UsersService) Edit(user *User) (*User, *Response, error) { + u := "user" + req, err := s.client.NewRequest("PATCH", u, user) + if err != nil { + return nil, nil, err + } + + uResp := new(User) + resp, err := s.client.Do(req, uResp) + if err != nil { + return nil, resp, err + } + + return uResp, resp, err +} + +// UserListOptions specifies optional parameters to the UsersService.List +// method. +type UserListOptions struct { + // ID of the last user seen + Since int `url:"since,omitempty"` +} + +// ListAll lists all GitHub users. +// +// GitHub API docs: http://developer.github.com/v3/users/#get-all-users +func (s *UsersService) ListAll(opt *UserListOptions) ([]User, *Response, error) { + u, err := addOptions("users", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_administration.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_administration.go new file mode 100644 index 00000000000..dc1dcb89499 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_administration.go @@ -0,0 +1,64 @@ +// Copyright 2014 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" + +// PromoteSiteAdmin promotes a user to a site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#promote-an-ordinary-user-to-a-site-administrator +func (s *UsersService) PromoteSiteAdmin(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DemoteSiteAdmin demotes a user from site administrator of a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#demote-a-site-administrator-to-an-ordinary-user +func (s *UsersService) DemoteSiteAdmin(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/site_admin", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Suspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#suspend-a-user +func (s *UsersService) Suspend(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Unsuspend a user on a GitHub Enterprise instance. +// +// GitHub API docs: https://developer.github.com/v3/users/administration/#unsuspend-a-user +func (s *UsersService) Unsuspend(user string) (*Response, error) { + u := fmt.Sprintf("users/%v/suspended", user) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_administration_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_administration_test.go new file mode 100644 index 00000000000..d415f4d4aa5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_administration_test.go @@ -0,0 +1,71 @@ +// Copyright 2014 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 ( + "net/http" + "testing" +) + +func TestUsersService_PromoteSiteAdmin(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.PromoteSiteAdmin("u") + if err != nil { + t.Errorf("Users.PromoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_DemoteSiteAdmin(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/site_admin", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.DemoteSiteAdmin("u") + if err != nil { + t.Errorf("Users.DemoteSiteAdmin returned error: %v", err) + } +} + +func TestUsersService_Suspend(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Suspend("u") + if err != nil { + t.Errorf("Users.Suspend returned error: %v", err) + } +} + +func TestUsersService_Unsuspend(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/suspended", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + _, err := client.Users.Unsuspend("u") + if err != nil { + t.Errorf("Users.Unsuspend returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_emails.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_emails.go new file mode 100644 index 00000000000..755319123bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_emails.go @@ -0,0 +1,69 @@ +// 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 + +// UserEmail represents user's email address +type UserEmail struct { + Email *string `json:"email,omitempty"` + Primary *bool `json:"primary,omitempty"` + Verified *bool `json:"verified,omitempty"` +} + +// ListEmails lists all email addresses for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user +func (s *UsersService) ListEmails(opt *ListOptions) ([]UserEmail, *Response, error) { + u := "user/emails" + 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 + } + + emails := new([]UserEmail) + resp, err := s.client.Do(req, emails) + if err != nil { + return nil, resp, err + } + + return *emails, resp, err +} + +// AddEmails adds email addresses of the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#add-email-addresses +func (s *UsersService) AddEmails(emails []string) ([]UserEmail, *Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("POST", u, emails) + if err != nil { + return nil, nil, err + } + + e := new([]UserEmail) + resp, err := s.client.Do(req, e) + if err != nil { + return nil, resp, err + } + + return *e, resp, err +} + +// DeleteEmails deletes email addresses from authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/emails/#delete-email-addresses +func (s *UsersService) DeleteEmails(emails []string) (*Response, error) { + u := "user/emails" + req, err := s.client.NewRequest("DELETE", u, emails) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_emails_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_emails_test.go new file mode 100644 index 00000000000..7eb65086085 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_emails_test.go @@ -0,0 +1,94 @@ +// 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" +) + +func TestUsersService_ListEmails(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{ + "email": "user@example.com", + "verified": false, + "primary": true + }]`) + }) + + opt := &ListOptions{Page: 2} + emails, _, err := client.Users.ListEmails(opt) + if err != nil { + t.Errorf("Users.ListEmails returned error: %v", err) + } + + want := []UserEmail{{Email: String("user@example.com"), Verified: Bool(false), Primary: Bool(true)}} + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.ListEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_AddEmails(t *testing.T) { + setup() + defer teardown() + + input := []string{"new@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + 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, `[{"email":"old@example.com"}, {"email":"new@example.com"}]`) + }) + + emails, _, err := client.Users.AddEmails(input) + if err != nil { + t.Errorf("Users.AddEmails returned error: %v", err) + } + + want := []UserEmail{ + {Email: String("old@example.com")}, + {Email: String("new@example.com")}, + } + if !reflect.DeepEqual(emails, want) { + t.Errorf("Users.AddEmails returned %+v, want %+v", emails, want) + } +} + +func TestUsersService_DeleteEmails(t *testing.T) { + setup() + defer teardown() + + input := []string{"user@example.com"} + + mux.HandleFunc("/user/emails", func(w http.ResponseWriter, r *http.Request) { + v := new([]string) + json.NewDecoder(r.Body).Decode(v) + + testMethod(t, r, "DELETE") + if !reflect.DeepEqual(*v, input) { + t.Errorf("Request body = %+v, want %+v", *v, input) + } + }) + + _, err := client.Users.DeleteEmails(input) + if err != nil { + t.Errorf("Users.DeleteEmails returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_followers.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_followers.go new file mode 100644 index 00000000000..7ecbed9fdf8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_followers.go @@ -0,0 +1,116 @@ +// 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" + +// ListFollowers lists the followers for a user. Passing the empty string will +// fetch followers for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#list-followers-of-a-user +func (s *UsersService) ListFollowers(user string, opt *ListOptions) ([]User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/followers", user) + } else { + u = "user/followers" + } + 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 + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// ListFollowing lists the people that a user is following. Passing the empty +// string will list people the authenticated user is following. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#list-users-followed-by-another-user +func (s *UsersService) ListFollowing(user string, opt *ListOptions) ([]User, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following", user) + } else { + u = "user/following" + } + 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 + } + + users := new([]User) + resp, err := s.client.Do(req, users) + if err != nil { + return nil, resp, err + } + + return *users, resp, err +} + +// IsFollowing checks if "user" is following "target". Passing the empty +// string for "user" will check if the authenticated user is following "target". +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user +func (s *UsersService) IsFollowing(user, target string) (bool, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/following/%v", user, target) + } else { + u = fmt.Sprintf("user/following/%v", target) + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return false, nil, err + } + + resp, err := s.client.Do(req, nil) + following, err := parseBoolResponse(err) + return following, resp, err +} + +// Follow will cause the authenticated user to follow the specified user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#follow-a-user +func (s *UsersService) Follow(user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Unfollow will cause the authenticated user to unfollow the specified user. +// +// GitHub API docs: http://developer.github.com/v3/users/followers/#unfollow-a-user +func (s *UsersService) Unfollow(user string) (*Response, error) { + u := fmt.Sprintf("user/following/%v", user) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_followers_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_followers_test.go new file mode 100644 index 00000000000..f4d24578e53 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_followers_test.go @@ -0,0 +1,222 @@ +// 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 TestUsersService_ListFollowers_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowers("", opt) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/followers", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowers("u", nil) + if err != nil { + t.Errorf("Users.ListFollowers returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowers returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowers_invalidUser(t *testing.T) { + _, _, err := client.Users.ListFollowers("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_ListFollowing_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opts := &ListOptions{Page: 2} + users, _, err := client.Users.ListFollowing("", opts) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + users, _, err := client.Users.ListFollowing("u", nil) + if err != nil { + t.Errorf("Users.ListFollowing returned error: %v", err) + } + + want := []User{{ID: Int(1)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListFollowing returned %+v, want %+v", users, want) + } +} + +func TestUsersService_ListFollowing_invalidUser(t *testing.T) { + _, _, err := client.Users.ListFollowing("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_IsFollowing_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing("", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNoContent) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := true; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_false(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusNotFound) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err != nil { + t.Errorf("Users.IsFollowing returned error: %v", err) + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_error(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/following/t", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + http.Error(w, "BadRequest", http.StatusBadRequest) + }) + + following, _, err := client.Users.IsFollowing("u", "t") + if err == nil { + t.Errorf("Expected HTTP 400 response") + } + if want := false; following != want { + t.Errorf("Users.IsFollowing returned %+v, want %+v", following, want) + } +} + +func TestUsersService_IsFollowing_invalidUser(t *testing.T) { + _, _, err := client.Users.IsFollowing("%", "%") + testURLParseError(t, err) +} + +func TestUsersService_Follow(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + }) + + _, err := client.Users.Follow("u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Follow_invalidUser(t *testing.T) { + _, err := client.Users.Follow("%") + testURLParseError(t, err) +} + +func TestUsersService_Unfollow(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/following/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.Unfollow("u") + if err != nil { + t.Errorf("Users.Follow returned error: %v", err) + } +} + +func TestUsersService_Unfollow_invalidUser(t *testing.T) { + _, err := client.Users.Unfollow("%") + testURLParseError(t, err) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_keys.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_keys.go new file mode 100644 index 00000000000..dcbd773774c --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_keys.go @@ -0,0 +1,104 @@ +// 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" + +// Key represents a public SSH key used to authenticate a user or deploy script. +type Key struct { + ID *int `json:"id,omitempty"` + Key *string `json:"key,omitempty"` + URL *string `json:"url,omitempty"` + Title *string `json:"title,omitempty"` +} + +func (k Key) String() string { + return Stringify(k) +} + +// ListKeys lists the verified public keys for a user. Passing the empty +// string will fetch keys for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#list-public-keys-for-a-user +func (s *UsersService) ListKeys(user string, opt *ListOptions) ([]Key, *Response, error) { + var u string + if user != "" { + u = fmt.Sprintf("users/%v/keys", user) + } else { + u = "user/keys" + } + 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 + } + + keys := new([]Key) + resp, err := s.client.Do(req, keys) + if err != nil { + return nil, resp, err + } + + return *keys, resp, err +} + +// GetKey fetches a single public key. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#get-a-single-public-key +func (s *UsersService) GetKey(id int) (*Key, *Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + key := new(Key) + resp, err := s.client.Do(req, key) + if err != nil { + return nil, resp, err + } + + return key, resp, err +} + +// CreateKey adds a public key for the authenticated user. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#create-a-public-key +func (s *UsersService) CreateKey(key *Key) (*Key, *Response, error) { + u := "user/keys" + + req, err := s.client.NewRequest("POST", u, key) + if err != nil { + return nil, nil, err + } + + k := new(Key) + resp, err := s.client.Do(req, k) + if err != nil { + return nil, resp, err + } + + return k, resp, err +} + +// DeleteKey deletes a public key. +// +// GitHub API docs: http://developer.github.com/v3/users/keys/#delete-a-public-key +func (s *UsersService) DeleteKey(id int) (*Response, error) { + u := fmt.Sprintf("user/keys/%v", id) + + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_keys_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_keys_test.go new file mode 100644 index 00000000000..e47afd71dfe --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_keys_test.go @@ -0,0 +1,124 @@ +// 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" +) + +func TestUsersService_ListKeys_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"page": "2"}) + fmt.Fprint(w, `[{"id":1}]`) + }) + + opt := &ListOptions{Page: 2} + keys, _, err := client.Users.ListKeys("", opt) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u/keys", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `[{"id":1}]`) + }) + + keys, _, err := client.Users.ListKeys("u", nil) + if err != nil { + t.Errorf("Users.ListKeys returned error: %v", err) + } + + want := []Key{{ID: Int(1)}} + if !reflect.DeepEqual(keys, want) { + t.Errorf("Users.ListKeys returned %+v, want %+v", keys, want) + } +} + +func TestUsersService_ListKeys_invalidUser(t *testing.T) { + _, _, err := client.Users.ListKeys("%", nil) + testURLParseError(t, err) +} + +func TestUsersService_GetKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + key, _, err := client.Users.GetKey(1) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_CreateKey(t *testing.T) { + setup() + defer teardown() + + input := &Key{Key: String("k"), Title: String("t")} + + mux.HandleFunc("/user/keys", func(w http.ResponseWriter, r *http.Request) { + v := new(Key) + 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}`) + }) + + key, _, err := client.Users.CreateKey(input) + if err != nil { + t.Errorf("Users.GetKey returned error: %v", err) + } + + want := &Key{ID: Int(1)} + if !reflect.DeepEqual(key, want) { + t.Errorf("Users.GetKey returned %+v, want %+v", key, want) + } +} + +func TestUsersService_DeleteKey(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user/keys/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + }) + + _, err := client.Users.DeleteKey(1) + if err != nil { + t.Errorf("Users.DeleteKey returned error: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-github/github/users_test.go b/Godeps/_workspace/src/github.com/google/go-github/github/users_test.go new file mode 100644 index 00000000000..15ea3e83a84 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-github/github/users_test.go @@ -0,0 +1,150 @@ +// 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" +) + +func TestUser_marshall(t *testing.T) { + testJSONMarshal(t, &User{}, "{}") + + u := &User{ + Login: String("l"), + ID: Int(1), + URL: String("u"), + AvatarURL: String("a"), + GravatarID: String("g"), + Name: String("n"), + Company: String("c"), + Blog: String("b"), + Location: String("l"), + Email: String("e"), + Hireable: Bool(true), + PublicRepos: Int(1), + Followers: Int(1), + Following: Int(1), + CreatedAt: &Timestamp{referenceTime}, + } + want := `{ + "login": "l", + "id": 1, + "avatar_url": "a", + "gravatar_id": "g", + "name": "n", + "company": "c", + "blog": "b", + "location": "l", + "email": "e", + "hireable": true, + "public_repos": 1, + "followers": 1, + "following": 1, + "created_at": ` + referenceTimeStr + `, + "url": "u" + }` + testJSONMarshal(t, u, want) +} + +func TestUsersService_Get_authenticatedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get("") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_specifiedUser(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users/u", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"id":1}`) + }) + + user, _, err := client.Users.Get("u") + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Get returned %+v, want %+v", user, want) + } +} + +func TestUsersService_Get_invalidUser(t *testing.T) { + _, _, err := client.Users.Get("%") + testURLParseError(t, err) +} + +func TestUsersService_Edit(t *testing.T) { + setup() + defer teardown() + + input := &User{Name: String("n")} + + mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) { + v := new(User) + 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}`) + }) + + user, _, err := client.Users.Edit(input) + if err != nil { + t.Errorf("Users.Edit returned error: %v", err) + } + + want := &User{ID: Int(1)} + if !reflect.DeepEqual(user, want) { + t.Errorf("Users.Edit returned %+v, want %+v", user, want) + } +} + +func TestUsersService_ListAll(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"since": "1"}) + fmt.Fprint(w, `[{"id":2}]`) + }) + + opt := &UserListOptions{1} + users, _, err := client.Users.ListAll(opt) + if err != nil { + t.Errorf("Users.Get returned error: %v", err) + } + + want := []User{{ID: Int(2)}} + if !reflect.DeepEqual(users, want) { + t.Errorf("Users.ListAll returned %+v, want %+v", users, want) + } +} diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go new file mode 100644 index 00000000000..396d804f3ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode.go @@ -0,0 +1,307 @@ +// Copyright 2013 The Go 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 query implements encoding of structs into URL query parameters. +// +// As a simple example: +// +// type Options struct { +// Query string `url:"q"` +// ShowAll bool `url:"all"` +// Page int `url:"page"` +// } +// +// opt := Options{ "foo", true, 2 } +// v, _ := query.Values(opt) +// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +// +// The exact mapping between Go values and url.Values is described in the +// documentation for the Values() function. +package query + +import ( + "bytes" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) + +var encoderType = reflect.TypeOf(new(Encoder)).Elem() + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// Values returns the url.Values encoding of v. +// +// Values expects to be passed a struct, and traverses it recursively using the +// following encoding rules. +// +// Each exported struct field is encoded as a URL parameter unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option +// +// The empty values are false, 0, any nil pointer or interface value, any array +// slice, map, or string of length zero, and any time.Time that returns true +// for IsZero(). +// +// The URL parameter name defaults to the struct field name but can be +// specified in the struct field's tag value. The "url" key in the struct +// field's tag value is the key name, followed by an optional comma and +// options. For example: +// +// // Field is ignored by this package. +// Field int `url:"-"` +// +// // Field appears as URL parameter "myName". +// Field int `url:"myName"` +// +// // Field appears as URL parameter "myName" and the field is omitted if +// // its value is empty +// Field int `url:"myName,omitempty"` +// +// // Field appears as URL parameter "Field" (the default), but the field +// // is skipped if empty. Note the leading comma. +// Field int `url:",omitempty"` +// +// For encoding individual field values, the following type-dependent rules +// apply: +// +// Boolean values default to encoding as the strings "true" or "false". +// Including the "int" option signals that the field should be encoded as the +// strings "1" or "0". +// +// time.Time values default to encoding as RFC3339 timestamps. Including the +// "unix" option signals that the field should be encoded as a Unix time (see +// time.Unix()) +// +// Slice and Array values default to encoding as multiple URL values of the +// same name. Including the "comma" option signals that the field should be +// encoded as a single comma-delimited value. Including the "space" option +// similarly encodes the value as a single space-delimited string. Including +// the "brackets" option signals that the multiple URL values should have "[]" +// appended to the value name. +// +// Anonymous struct fields are usually encoded as if their inner exported +// fields were fields in the outer struct, subject to the standard Go +// visibility rules. An anonymous struct field with a name given in its URL +// tag is treated as having that name, rather than being anonymous. +// +// Non-nil pointer values are encoded as the value pointed to. +// +// Nested structs are encoded including parent fields in value names for +// scoping. e.g: +// +// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" +// +// All other values are encoded using their default string representation. +// +// Multiple fields that encode to the same URL parameter name will be included +// as multiple URL values of the same name. +func Values(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + var embedded []reflect.Value + + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if name == "" { + if sf.Anonymous && sv.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, sv) + continue + } + + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(encoderType) { + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + var del byte + if opts.Contains("comma") { + del = ',' + } else if opts.Contains("space") { + del = ' ' + } else if opts.Contains("brackets") { + name = name + "[]" + } + + if del != 0 { + s := new(bytes.Buffer) + first := true + for i := 0; i < sv.Len(); i++ { + if first { + first = false + } else { + s.WriteByte(del) + } + s.WriteString(valueString(sv.Index(i), opts)) + } + values.Add(name, s.String()) + } else { + for i := 0; i < sv.Len(); i++ { + values.Add(name, valueString(sv.Index(i), opts)) + } + } + continue + } + + if sv.Type() == timeType { + values.Add(name, valueString(sv, opts)) + continue + } + + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Kind() == reflect.Struct { + reflectValue(values, sv, name) + continue + } + + values.Add(name, valueString(sv, opts)) + } + + for _, f := range embedded { + if err := reflectValue(values, f, scope); err != nil { + return err + } + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + if v.Bool() { + return "1" + } + return "0" + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + return t.Format(time.RFC3339) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if v.Type() == timeType { + return v.Interface().(time.Time).IsZero() + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go new file mode 100644 index 00000000000..09efb0f5b14 --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/go-querystring/query/encode_test.go @@ -0,0 +1,286 @@ +// Copyright 2013 The Go 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 query + +import ( + "fmt" + "net/url" + "reflect" + "testing" + "time" +) + +type Nested struct { + A SubNested `url:"a"` + B *SubNested `url:"b"` + Ptr *SubNested `url:"ptr,omitempty"` +} + +type SubNested struct { + Value string `url:"value"` +} + +func TestValues_types(t *testing.T) { + str := "string" + strPtr := &str + + tests := []struct { + in interface{} + want url.Values + }{ + { + // basic primitives + struct { + A string + B int + C uint + D float32 + E bool + }{}, + url.Values{ + "A": {""}, + "B": {"0"}, + "C": {"0"}, + "D": {"0"}, + "E": {"false"}, + }, + }, + { + // pointers + struct { + A *string + B *int + C **string + }{A: strPtr, C: &strPtr}, + url.Values{ + "A": {str}, + "B": {""}, + "C": {str}, + }, + }, + { + // slices and arrays + struct { + A []string + B []string `url:",comma"` + C []string `url:",space"` + D [2]string + E [2]string `url:",comma"` + F [2]string `url:",space"` + G []*string `url:",space"` + H []bool `url:",int,space"` + I []string `url:",brackets"` + }{ + A: []string{"a", "b"}, + B: []string{"a", "b"}, + C: []string{"a", "b"}, + D: [2]string{"a", "b"}, + E: [2]string{"a", "b"}, + F: [2]string{"a", "b"}, + G: []*string{&str, &str}, + H: []bool{true, false}, + I: []string{"a", "b"}, + }, + url.Values{ + "A": {"a", "b"}, + "B": {"a,b"}, + "C": {"a b"}, + "D": {"a", "b"}, + "E": {"a,b"}, + "F": {"a b"}, + "G": {"string string"}, + "H": {"1 0"}, + "I[]": {"a", "b"}, + }, + }, + { + // other types + struct { + A time.Time + B time.Time `url:",unix"` + C bool `url:",int"` + D bool `url:",int"` + }{ + A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC), + C: true, + D: false, + }, + url.Values{ + "A": {"2000-01-01T12:34:56Z"}, + "B": {"946730096"}, + "C": {"1"}, + "D": {"0"}, + }, + }, + { + struct { + Nest Nested `url:"nest"` + }{ + Nested{ + A: SubNested{ + Value: "that", + }, + }, + }, + url.Values{ + "nest[a][value]": {"that"}, + "nest[b]": {""}, + }, + }, + { + struct { + Nest Nested `url:"nest"` + }{ + Nested{ + Ptr: &SubNested{ + Value: "that", + }, + }, + }, + url.Values{ + "nest[a][value]": {""}, + "nest[b]": {""}, + "nest[ptr][value]": {"that"}, + }, + }, + { + nil, + url.Values{}, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_omitEmpty(t *testing.T) { + str := "" + s := struct { + a string + A string + B string `url:",omitempty"` + C string `url:"-"` + D string `url:"omitempty"` // actually named omitempty, not an option + E *string `url:",omitempty"` + }{E: &str} + + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "A": {""}, + "omitempty": {""}, + "E": {""}, // E is included because the pointer is not empty, even though the string being pointed to is + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +type A struct { + B +} + +type B struct { + C string +} + +type D struct { + B + C string +} + +func TestValues_embeddedStructs(t *testing.T) { + tests := []struct { + in interface{} + want url.Values + }{ + { + A{B{C: "foo"}}, + url.Values{"C": {"foo"}}, + }, + { + D{B: B{C: "bar"}, C: "foo"}, + url.Values{"C": {"foo", "bar"}}, + }, + } + + for i, tt := range tests { + v, err := Values(tt.in) + if err != nil { + t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err) + } + + if !reflect.DeepEqual(tt.want, v) { + t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want) + } + } +} + +func TestValues_invalidInput(t *testing.T) { + _, err := Values("") + if err == nil { + t.Errorf("expected Values() to return an error on invalid input") + } +} + +type EncodedArgs []string + +func (m EncodedArgs) EncodeValues(key string, v *url.Values) error { + for i, arg := range m { + v.Set(fmt.Sprintf("%s.%d", key, i), arg) + } + return nil +} + +func TestValues_Marshaler(t *testing.T) { + s := struct { + Args EncodedArgs `url:"arg"` + }{[]string{"a", "b", "c"}} + v, err := Values(s) + if err != nil { + t.Errorf("Values(%q) returned error: %v", s, err) + } + + want := url.Values{ + "arg.0": {"a"}, + "arg.1": {"b"}, + "arg.2": {"c"}, + } + if !reflect.DeepEqual(want, v) { + t.Errorf("Values(%q) returned %v, want %v", s, v, want) + } +} + +func TestTagParsing(t *testing.T) { + name, opts := parseTag("field,foobar,foo") + if name != "field" { + t.Fatalf("name = %q, want field", name) + } + for _, tt := range []struct { + opt string + want bool + }{ + {"foobar", true}, + {"foo", true}, + {"bar", false}, + {"field", false}, + } { + if opts.Contains(tt.opt) != tt.want { + t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + } + } +} From 1375a181753a13c0761bd79bf9242915b184c543 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 6 May 2015 16:44:53 -0700 Subject: [PATCH 3/3] Update boilerplate. --- build/make-release-notes.sh | 2 +- contrib/release-notes/release-notes.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/make-release-notes.sh b/build/make-release-notes.sh index c8973a07347..0e2730639b1 100755 --- a/build/make-release-notes.sh +++ b/build/make-release-notes.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2015 Google Inc. All rights reserved. +# Copyright 2014 The Kubernetes Authors All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/contrib/release-notes/release-notes.go b/contrib/release-notes/release-notes.go index cd2be693647..ea42f657f8d 100644 --- a/contrib/release-notes/release-notes.go +++ b/contrib/release-notes/release-notes.go @@ -1,5 +1,5 @@ /* -Copyright 2015 Google Inc. All rights reserved. +Copyright 2014 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.