├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.rdoc ├── Rakefile ├── github-v3-api-get-token ├── github-v3-api.gemspec ├── lib ├── github-v3-api.rb ├── github_v3_api.rb └── github_v3_api │ ├── entity.rb │ ├── issue.rb │ ├── issues_api.rb │ ├── org.rb │ ├── orgs_api.rb │ ├── repo.rb │ ├── repos_api.rb │ ├── user.rb │ ├── users_api.rb │ └── version.rb └── spec ├── github_v3_api_spec.rb ├── issue_spec.rb ├── issues_api_spec.rb ├── org_spec.rb ├── orgs_api_spec.rb ├── repo_spec.rb ├── repos_api_spec.rb ├── spec_helper.rb ├── user_api_spec.rb └── user_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in github-v3-api.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | github-v3-api (0.5.0) 5 | json 6 | rest-client 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | diff-lcs (1.2.5) 12 | hashie (3.3.1) 13 | json (1.8.1) 14 | mime-types (2.3) 15 | netrc (0.7.7) 16 | omniauth (1.2.2) 17 | hashie (>= 1.2, < 4) 18 | rack (~> 1.0) 19 | rack (1.5.2) 20 | rack-protection (1.5.3) 21 | rack 22 | rake (10.3.2) 23 | rest-client (1.7.2) 24 | mime-types (>= 1.16, < 3.0) 25 | netrc (~> 0.7) 26 | rspec (3.1.0) 27 | rspec-core (~> 3.1.0) 28 | rspec-expectations (~> 3.1.0) 29 | rspec-mocks (~> 3.1.0) 30 | rspec-core (3.1.5) 31 | rspec-support (~> 3.1.0) 32 | rspec-expectations (3.1.2) 33 | diff-lcs (>= 1.2.0, < 2.0) 34 | rspec-support (~> 3.1.0) 35 | rspec-mocks (3.1.2) 36 | rspec-support (~> 3.1.0) 37 | rspec-support (3.1.1) 38 | sinatra (1.4.5) 39 | rack (~> 1.4) 40 | rack-protection (~> 1.4) 41 | tilt (~> 1.3, >= 1.3.4) 42 | tilt (1.4.1) 43 | 44 | PLATFORMS 45 | ruby 46 | 47 | DEPENDENCIES 48 | bundler (~> 1.3) 49 | github-v3-api! 50 | omniauth 51 | rake 52 | rspec 53 | sinatra 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 John Wilger 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = github-v3-api 2 | 3 | This library provides Ruby object access to the GitHub v3 API. It's designed to 4 | play friendly with web apps that use OAuth2 via GitHub and need to access github 5 | repositories without needing to configure or store a specific user's GitHub 6 | credentials in the application itself. 7 | 8 | == CLI Testing 9 | 10 | Because this library requires an OAuth2 access token from GitHub, you will need 11 | to obtain such a token in order to do command-line testing of the library via 12 | IRB. The source for this gem includes a simple sinatra web application that can 13 | get an access token for you. You will need to create a GitHub application first 14 | by visiting https://github.com/account/applications/new. For the URL and 15 | Callback URL options, enter http://localhost:4567 and 16 | http://localhost:4567/auth/github/callback respectively (include "http://" in 17 | front of both; RDoc formatting seems to strip that bit out of the rendered 18 | documentation.) After creating the application, your client ID and client secret 19 | will be displayed, and you will use those values to run the script. 20 | 21 | Then just run: 22 | 23 | OAUTH_GITHUB_CLIENT_ID={client_id} \ 24 | OAUTH_GITHUB_CLIENT_SECRET={client_secret} \ 25 | ./github-v3-api-get-token 26 | 27 | and point your web browser at http://localhost:4567. You will be prompted to 28 | authorize your app at GitHub. If you allow it, you will then be presented with 29 | an access token that you can then copy and paste in where needed. 30 | 31 | == Contributing to github-v3-api 32 | 33 | * Check out the latest master to make sure the feature hasn't been implemented 34 | or the bug hasn't been fixed yet 35 | * Check out the issue tracker to make sure someone already hasn't requested it 36 | and/or contributed it 37 | * Fork the project 38 | * Start a feature/bugfix branch 39 | * Commit and push until you are happy with your contribution 40 | * Make sure to add tests for it. This is important so I don't break it in a 41 | future version unintentionally. 42 | * Please try not to mess with the Rakefile, version, or history. If you want to 43 | have your own version, or is otherwise necessary, that is fine, but please 44 | isolate to its own commit so I can cherry-pick around it. 45 | 46 | == Copyright 47 | 48 | Copyright (c) 2011 John Wilger. See LICENSE.txt for 49 | further details. 50 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /github-v3-api-get-token: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | puts < 'user,repo,gist' 37 | end 38 | 39 | get '/' do 40 | redirect '/auth/github' 41 | end 42 | 43 | get '/auth/github/callback' do 44 | token = request.env['omniauth.auth']['credentials']['token'] 45 | "Your GitHub Access Token is #{token}" 46 | end 47 | -------------------------------------------------------------------------------- /github-v3-api.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'github_v3_api/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "github-v3-api" 8 | spec.version = GitHubV3API::VERSION 9 | spec.authors = ["John Wilger"] 10 | spec.email = ["johnwilger@gmail.com"] 11 | spec.description = %q{Ponies} 12 | spec.summary = %q{Ruby Client for the GitHub v3 API} 13 | spec.homepage = "http://github.com/jwilger/github-v3-api" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.3" 22 | spec.add_development_dependency "rake" 23 | spec.add_runtime_dependency(%q, [">= 0"]) 24 | spec.add_runtime_dependency(%q, [">= 0"]) 25 | spec.add_development_dependency(%q, [">= 0"]) 26 | spec.add_development_dependency(%q, [">= 0"]) 27 | spec.add_development_dependency(%q, [">= 0"]) 28 | end 29 | -------------------------------------------------------------------------------- /lib/github-v3-api.rb: -------------------------------------------------------------------------------- 1 | # Because I stupidly named the gem with dashes :-( 2 | require 'github_v3_api' 3 | -------------------------------------------------------------------------------- /lib/github_v3_api.rb: -------------------------------------------------------------------------------- 1 | require 'rest-client' 2 | require 'json' 3 | require 'github_v3_api/entity' 4 | require 'github_v3_api/issues_api' 5 | require 'github_v3_api/issue' 6 | require 'github_v3_api/users_api' 7 | require 'github_v3_api/user' 8 | require 'github_v3_api/orgs_api' 9 | require 'github_v3_api/org' 10 | require 'github_v3_api/repos_api' 11 | require 'github_v3_api/repo' 12 | 13 | # This is the main entry-point to the GitHub v3 API. 14 | # 15 | # example: 16 | # 17 | # api = GitHubV3API.new('users_github_oath2_access_token') 18 | # 19 | # # access the GitHub Orgs API 20 | # api.orgs 21 | # #=> an instance of GitHubV3API::OrgsAPI 22 | # 23 | class GitHubV3API 24 | # Raised when an API request returns a 404 error 25 | NotFound = Class.new(RuntimeError) 26 | 27 | # Raised when an API request uses an invalid access token 28 | Unauthorized = Class.new(RuntimeError) 29 | 30 | # Raised when an API request is missing required data 31 | MissingRequiredData = Class.new(RuntimeError) 32 | 33 | # Returns a GitHubV3API instance that is able to access github with the 34 | # +access_token+ owner's authorization. 35 | # 36 | # +access_token+:: an OAuth2 access token from GitHub 37 | def initialize(access_token, api_url='https://api.github.com', header={}) 38 | @access_token = access_token 39 | @api_url = api_url 40 | @header = {:accept => :json, 41 | :authorization => "token #{@access_token}", 42 | :user_agent => "rubygem-github-v3-api"} 43 | @header.merge!(header) if header.is_a?(Hash) 44 | end 45 | 46 | # Entry-point for access to the GitHub Users API 47 | # 48 | # Returns an instance of GitHubV3API::UserAPI that will use the access_token 49 | # associated with this instance. 50 | def users 51 | UsersAPI.new(self) 52 | end 53 | 54 | # Entry-point for access to the GitHub Orgs API 55 | # 56 | # Returns an instance of GitHubV3API::OrgsAPI that will use the access_token 57 | # associated with this instance. 58 | def orgs 59 | OrgsAPI.new(self) 60 | end 61 | 62 | # Entry-point for access to the GitHub Repos API 63 | # 64 | # Returns an instance of GitHubV3API::ReposAPI that will use the access_token 65 | # associated with this instance. 66 | def repos 67 | ReposAPI.new(self) 68 | end 69 | 70 | # Entry-point for access to the GitHub Issues API 71 | # 72 | # Returns an instance of GitHubV3API::IssuesAPI that will use the access_token 73 | # associated with this instance 74 | def issues 75 | IssuesAPI.new(self) 76 | end 77 | 78 | def get(path, params={}) #:nodoc: 79 | result = RestClient.get(@api_url + path, 80 | @header.merge({:params => params})) 81 | result_data = JSON.parse(result) 82 | # check for pagination 83 | link = result.headers[:link] 84 | if link then 85 | re_relnext = %r!<#{@api_url}([^>]*)>; *rel="next"! 86 | relnext_path = link.match re_relnext 87 | if relnext_path && relnext_path[1] then 88 | next_data = self.get(relnext_path[1], params) 89 | result_data += next_data 90 | end 91 | end 92 | result_data 93 | rescue RestClient::Unauthorized 94 | raise Unauthorized, "The access token is invalid according to GitHub" 95 | end 96 | 97 | def post(path, params={}) #:nodoc: 98 | result = RestClient.post(@api_url + path, JSON.generate(params), 99 | @header) 100 | JSON.parse(result) 101 | rescue RestClient::Unauthorized 102 | raise Unauthorized, "The access token is invalid according to GitHub" 103 | end 104 | 105 | def patch(path, params={}) #:nodoc: 106 | result = RestClient.post(@api_url + path, JSON.generate(params), 107 | @header) 108 | JSON.parse(result) 109 | rescue RestClient::Unauthorized 110 | raise Unauthorized, "The access token is invalid according to GitHub" 111 | end 112 | 113 | def delete(path) #:nodoc: 114 | result = RestClient.delete(@api_url + path, 115 | @header) 116 | JSON.parse(result) 117 | rescue RestClient::Unauthorized 118 | raise Unauthorized, "The access token is invalid according to GitHub" 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/github_v3_api/entity.rb: -------------------------------------------------------------------------------- 1 | class GitHubV3API 2 | # This is the base class used for value objects returned by the API. See 3 | # descendent classes for more details. 4 | class Entity 5 | def self.new_with_all_data(api, data) #:nodoc: 6 | entity = allocate 7 | entity.initialize_fetched(api, data) 8 | entity 9 | end 10 | 11 | # +api+:: an instance of the API class associated with the subclass of 12 | # Entity being instantiated. 13 | # +data+:: a Hash with keys corresponding to the data fields for the 14 | # subclass of Entity being instantiated 15 | def initialize(api, data) 16 | @api = api 17 | @data = data 18 | end 19 | 20 | def initialize_fetched(api, data) #:nodoc: 21 | initialize(api, data) 22 | @fetched = true 23 | end 24 | 25 | def [](key) #:nodoc: 26 | if @data[key].nil? && !@fetched 27 | fetch_data 28 | end 29 | @data[key] 30 | end 31 | 32 | def self.attr_reader(*fields) #:nodoc: 33 | fields.each do |field| 34 | define_method field do 35 | self[field.to_s] 36 | end 37 | end 38 | end 39 | 40 | protected 41 | 42 | # Provides access to the raw data hash for subclasses. 43 | def data 44 | @data 45 | end 46 | 47 | private 48 | 49 | def fetch_data #:nodoc: 50 | result = @api.get(*natural_key) 51 | @data = result.data 52 | @fetched = true 53 | end 54 | 55 | # Provides access to the api object for subclasses 56 | def api 57 | @api 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/github_v3_api/issue.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Represents a single GitHub Issue and provides access to its data attributes. 4 | class Issue < Entity 5 | attr_reader :url, :html_url, :number, :state, :title, :body, :user, 6 | :labels, :assignee, :milestone, :comments, :pull_request, 7 | :closed_at, :created_at, :updated_at 8 | 9 | 10 | end 11 | end -------------------------------------------------------------------------------- /lib/github_v3_api/issues_api.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Provides access to the GitHub Issues API (http://developer.github.com/v3/issues/) 4 | # 5 | # example: 6 | # 7 | # api = GitHubV3API.new(ACCESS_TOKEN) 8 | # 9 | # # get list of your issues 10 | # my_issues = api.issues.list 11 | # #=> returns an array of GitHubV3API::Issue instances 12 | # 13 | # # get list of issues for a repo 14 | # repo_issues = api.issues.list({:user => 'octocat', :repo => 'hello-world'}) 15 | # #=> returns an array of GitHubV3API::Issue instances 16 | # 17 | # issue = api.issues.get('octocat', 'hello-world', '1234') 18 | # #=> returns an instance of GitHubV3API::Issue 19 | # 20 | # issue.title 21 | # #=> 'omgbbq' 22 | # 23 | class IssuesAPI 24 | # Typically not used directly. Use GitHubV3API#issues instead. 25 | # 26 | # +connection+:: an instance of GitHubV3API 27 | def initialize(connection) 28 | @connection = connection 29 | end 30 | 31 | # Returns an array of GitHubV3API::Issue instances representing a 32 | # user's issues or issues for a repo 33 | def list(options=nil, params={}) 34 | path = if options && options[:user] && options[:repo] 35 | "/repos/#{options[:user]}/#{options[:repo]}/issues" 36 | else 37 | '/issues' 38 | end 39 | 40 | @connection.get(path, params).map do |issue_data| 41 | GitHubV3API::Issue.new(self, issue_data) 42 | end 43 | end 44 | 45 | # Returns a GitHubV3API::Issue instance for the specified +user+, 46 | # +repo_name+, and +id+. 47 | # 48 | # +user+:: the string ID of the user, e.g. "octocat" 49 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 50 | # +id+:: the integer ID of the issue, e.g. 42 51 | def get(user, repo_name, id, params={}) 52 | issue_data = @connection.get("/repos/#{user}/#{repo_name}/issues/#{id.to_s}", params) 53 | GitHubV3API::Issue.new_with_all_data(self, issue_data) 54 | rescue RestClient::ResourceNotFound 55 | raise NotFound, "The issue #{user}/#{repo_name}/issues/#{id} does not exist or is not visible to the user." 56 | end 57 | 58 | # Returns a GitHubV3API::Issue instance representing the issue 59 | # that it creates 60 | # 61 | # +user+:: the string ID of the user, e.g. "octocat" 62 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 63 | # +data+:: the hash DATA with attributes for the issue, e.g. {:title => "omgbbq"} 64 | def create(user, repo_name, data={}) 65 | raise MissingRequiredData, "Title is required to create a new issue" unless data[:title] 66 | issue_data = @connection.post("/repos/#{user}/#{repo_name}/issues", data) 67 | GitHubV3API::Issue.new_with_all_data(self, issue_data) 68 | end 69 | 70 | # Returns a GitHubV3API::Issue instance representing the issue 71 | # that it updated 72 | # 73 | # +user+:: the string ID of the user, e.g. "octocat" 74 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 75 | # +id+:: the integer ID of the issue, e.g. 42 76 | # +data+:: the hash with attributes for the issue, e.g. {:body => "lol, wtf"} 77 | def update(user, repo_name, id, data={}) 78 | issue_data = @connection.patch("/repos/#{user}/#{repo_name}/issues/#{id.to_s}", data) 79 | GitHubV3API::Issue.new_with_all_data(self, issue_data) 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /lib/github_v3_api/org.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Represents a single GitHub Org and provides access to its data attributes. 4 | class Org < Entity 5 | attr_reader :avatar_url, :billing_email, :blog, :collaborators, 6 | :company, :created_at, :disk_usage, :email, :followers, :following, 7 | :html_url, :id, :location, :login, :name, :owned_private_repos, :plan, 8 | :private_gists, :private_repos, :public_gists, :public_repos, :space, 9 | :total_private_repos, :type, :url 10 | 11 | # Returns an array of GitHubV3API::Repo instances representing the repos 12 | # that belong to this org 13 | def repos 14 | api.list_repos(login) 15 | end 16 | 17 | # Returns an array of GitHubV3API::User instances representing the users 18 | # who are members of the organization 19 | def members 20 | api.list_members(login) 21 | end 22 | 23 | # Returns an array of GitHubV3API::User instances representing the users 24 | # who are public members of the organization 25 | def public_members 26 | api.list_public_members(login) 27 | end 28 | 29 | private 30 | 31 | def natural_key 32 | [data['login']] 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/github_v3_api/orgs_api.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Provides access to the GitHub Orgs API (http://developer.github.com/v3/orgs/) 4 | # 5 | # example: 6 | # 7 | # api = GitHubV3API.new(ACCESS_TOKEN) 8 | # 9 | # # get list of all orgs to which the user belongs 10 | # orgs = api.orgs.list 11 | # #=> returns an array of GitHubV3API::Org instances 12 | # 13 | # an_org = api.orgs.get('github') 14 | # #=> returns an instance of GitHubV3API::Org 15 | # 16 | # an_org.name 17 | # #=> 'GitHub' 18 | # 19 | class OrgsAPI 20 | # Typically not used directly. Use GitHubV3API#orgs instead. 21 | # 22 | # +connection+:: an instance of GitHubV3API 23 | def initialize(connection) 24 | @connection = connection 25 | end 26 | 27 | # Returns an array of GitHubV3API::Org instances representing the 28 | # public and private orgs to which the current user belongs. 29 | def list 30 | @connection.get('/user/orgs').map do |org_data| 31 | GitHubV3API::Org.new(self, org_data) 32 | end 33 | end 34 | 35 | # Returns a GitHubV3API::Org instance for the specified +org_login+. 36 | # 37 | # +org_login+:: the string ID of the organization, e.g. "github" 38 | def get(org_login) 39 | org_data = @connection.get("/orgs/#{org_login}") 40 | GitHubV3API::Org.new_with_all_data(self, org_data) 41 | end 42 | 43 | # Returns an array of GitHubV3API::Repo instances representing the repos 44 | # that belong to the specified org. 45 | # 46 | # +org_login+:: the string ID of the organization, e.g. "github" 47 | def list_repos(org_login) 48 | @connection.get("/orgs/#{org_login}/repos").map do |repo_data| 49 | GitHubV3API::Repo.new(@connection.repos, repo_data) 50 | end 51 | end 52 | 53 | # Returns an array of GitHubV3API::User instances representing the members 54 | # who belong to the specified organization. 55 | # 56 | # +org_login+:: the string ID of the organization, e.g. "github" 57 | def list_members(org_login) 58 | @connection.get("/orgs/#{org_login}/members").map do |user_data| 59 | GitHubV3API::User.new(@connection.users, user_data) 60 | end 61 | end 62 | 63 | # Returns an array of GitHubV3API::User instances representing the members 64 | # who belong to the specified organization who have publicly identified 65 | # themselves as members of this organization. 66 | # 67 | # +org_login+:: the string ID of the organization, e.g. "github" 68 | def list_public_members(org_login) 69 | @connection.get("/orgs/#{org_login}/public_members").map do |user_data| 70 | GitHubV3API::User.new(@connection.users, user_data) 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/github_v3_api/repo.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Represents a single GitHub Repo and provides access to its data attributes. 4 | class Repo < Entity 5 | attr_reader :created_at, :description, :fork, :forks, :has_downloads, 6 | :has_issues, :has_wiki, :homepage, :html_url, :language, :master_branch, 7 | :name, :open_issues, :organization, :owner, :parent, :private, :pushed_at, 8 | :size, :source, :url, :watchers 9 | 10 | def owner_login 11 | owner['login'] 12 | end 13 | 14 | def list_collaborators 15 | api.list_collaborators(owner_login, name) 16 | end 17 | 18 | def list_watchers 19 | api.list_watchers(owner_login, name) 20 | end 21 | 22 | def list_forks 23 | api.list_forks(owner_login, name) 24 | end 25 | 26 | private 27 | 28 | def natural_key 29 | [data['owner']['login'], data['name']] 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/github_v3_api/repos_api.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Provides access to the GitHub Repos API (http://developer.github.com/v3/repos/) 4 | # 5 | # example: 6 | # 7 | # api = GitHubV3API.new(ACCESS_TOKEN) 8 | # 9 | # # get list of all of the user's public and private repos 10 | # repos = api.repos.list 11 | # #=> returns an array of GitHubV3API::Repo instances 12 | # 13 | # repo = api.repos.get('octocat', 'hello-world') 14 | # #=> returns an instance of GitHubV3API::Repo 15 | # 16 | # repo.name 17 | # #=> 'hello-world' 18 | # 19 | class ReposAPI 20 | # Typically not used directly. Use GitHubV3API#repos instead. 21 | # 22 | # +connection+:: an instance of GitHubV3API 23 | def initialize(connection) 24 | @connection = connection 25 | end 26 | 27 | def public_repos 28 | @connection.get('/repositories').map do |repo_data| 29 | GitHubV3API::Repo.new(self, repo_data) 30 | end 31 | end 32 | 33 | 34 | # Returns an array of GitHubV3API::Repo instances representing the 35 | # user's public and private repos 36 | def list 37 | @connection.get('/user/repos').map do |repo_data| 38 | GitHubV3API::Repo.new(self, repo_data) 39 | end 40 | end 41 | 42 | # Returns a GitHubV3API::Repo instance for the specified +user+ and 43 | # +repo_name+. 44 | # 45 | # +user+:: the string ID of the user, e.g. "octocat" 46 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 47 | def get(user, repo_name) 48 | org_data = @connection.get("/repos/#{user}/#{repo_name}") 49 | GitHubV3API::Repo.new_with_all_data(self, org_data) 50 | rescue RestClient::ResourceNotFound 51 | raise NotFound, "The repository #{user}/#{repo_name} does not exist or is not visible to the user." 52 | end 53 | 54 | # Returns an array of GitHubV3API::User instances for the collaborators of the 55 | # specified +user+ and +repo_name+. 56 | # 57 | # +user+:: the string ID of the user, e.g. "octocat" 58 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 59 | def list_collaborators(user, repo_name) 60 | @connection.get("/repos/#{user}/#{repo_name}/collaborators").map do |user_data| 61 | GitHubV3API::User.new(@connection.users, user_data) 62 | end 63 | end 64 | 65 | # Returns an array of GitHubV3API::User instances containing the users who are 66 | # watching the repository specified by +user+ and +repo_name+. 67 | # 68 | # +user+:: the string ID of the user, e.g. "octocat" 69 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 70 | def list_watchers(user, repo_name) 71 | @connection.get("/repos/#{user}/#{repo_name}/watchers").map do |user_data| 72 | GitHubV3API::User.new(@connection.users, user_data) 73 | end 74 | end 75 | 76 | # Returns an array of GitHubV3API::Repo instances containing the repositories 77 | # which were forked from the repository specified by +user+ and +repo_name+. 78 | # 79 | # +user+:: the string ID of the user, e.g. "octocat" 80 | # +repo_name+:: the string ID of the repository, e.g. "hello-world" 81 | def list_forks(user, repo_name) 82 | @connection.get("/repos/#{user}/#{repo_name}/forks").map do |repo_data| 83 | GitHubV3API::Repo.new(self, repo_data) 84 | end 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/github_v3_api/user.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Represents a single GitHub User and provides access to its data attributes. 4 | class User < Entity 5 | attr_reader :login, :id, :avatar_url, :gravatar_id, :url, :name, :company, 6 | :blog, :location, :email, :hireable, :bio, :public_repos, :public_gists, 7 | :followers, :following, :html_url, :created_at, :type, :total_private_repos, 8 | :owned_private_repos, :private_gists, :disk_usage, :collaborators 9 | private 10 | 11 | def natural_key 12 | [data['login']] 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/github_v3_api/users_api.rb: -------------------------------------------------------------------------------- 1 | # See GitHubV3API documentation in lib/github_v3_api.rb 2 | class GitHubV3API 3 | # Provides access to the GitHub Users API (http://developer.github.com/v3/users/) 4 | # 5 | # example: 6 | # 7 | # api = GitHubV3API.new(ACCESS_TOKEN) 8 | # 9 | # # get list of logged-in user 10 | # a_user = api.current 11 | # #=> returns an instance of GitHubV3API::User 12 | # 13 | # a_user.login 14 | # #=> 'jwilger' 15 | # 16 | class UsersAPI 17 | # Typically not used directly. Use GitHubV3API#users instead. 18 | # 19 | # +connection+:: an instance of GitHubV3API 20 | def initialize(connection) 21 | @connection = connection 22 | end 23 | 24 | # Returns a single GitHubV3API::User instance representing the 25 | # currently logged in user 26 | def current 27 | user_data = @connection.get("/user") 28 | GitHubV3API::User.new(self, user_data) 29 | end 30 | 31 | # Returns a GitHubV3API::User instance for the specified +username+. 32 | # 33 | # +username+:: the string login of the user, e.g. "octocat" 34 | def get(username) 35 | user_data = @connection.get("/users/#{username}") 36 | GitHubV3API::User.new_with_all_data(self, user_data) 37 | end 38 | 39 | # Returns an array of all GitHubV3API::User instances in the server. 40 | def all 41 | @connection.get("/users").map do |user_data| 42 | GitHubV3API::User.new_with_all_data(self, user_data) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/github_v3_api/version.rb: -------------------------------------------------------------------------------- 1 | class GitHubV3API 2 | VERSION = '0.5.0' 3 | end 4 | -------------------------------------------------------------------------------- /spec/github_v3_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API do 4 | let(:auth_token) { 'abcde' } 5 | let(:test_header) { {:accept => :json, 6 | :authorization => "token #{auth_token}", 7 | :user_agent => 'rubygem-github-v3-api'} } 8 | 9 | it 'is initialized with an OAuth2 access token' do 10 | lambda { GitHubV3API.new(auth_token) }.should_not raise_error 11 | end 12 | 13 | describe '#orgs' do 14 | it 'returns an instance of GitHubV3API::OrgsAPI' do 15 | api = GitHubV3API.new(auth_token) 16 | api.orgs.should be_kind_of GitHubV3API::OrgsAPI 17 | end 18 | end 19 | 20 | describe '#repos' do 21 | it 'returns an instance of GitHubV3API::ReposAPI' do 22 | api = GitHubV3API.new(auth_token) 23 | api.repos.should be_kind_of GitHubV3API::ReposAPI 24 | end 25 | end 26 | 27 | describe "#issues" do 28 | it "returns an instance of GitHubV3API::IssuesAPI" do 29 | api = GitHubV3API.new(auth_token) 30 | api.issues.should be_kind_of GitHubV3API::IssuesAPI 31 | end 32 | end 33 | 34 | describe '#get' do 35 | it 'does a get request to the specified path at the GitHub API server and adds the access token' do 36 | rcs = String.new('[]') 37 | allow(rcs).to receive(:headers) { {} } 38 | RestClient.should_receive(:get) \ 39 | .with('https://api.github.com/some/path', test_header.merge({:params => {}})) \ 40 | .and_return(rcs) 41 | api = GitHubV3API.new(auth_token) 42 | api.get('/some/path') 43 | end 44 | 45 | it 'returns the result of parsing the result body as JSON' do 46 | rcs = String.new('[{"foo": "bar"}]') 47 | allow(rcs).to receive(:headers) { {} } 48 | allow(RestClient).to receive(:get) { rcs } 49 | api = GitHubV3API.new(auth_token) 50 | api.get('/something').should == [{"foo" => "bar"}] 51 | end 52 | 53 | it "follows a pagination link from the http headers" do 54 | headers_next = { :link => 'Link: ; rel="next", ; rel="last"' } 55 | headers_last = { :link => 'Link: ; rel="previous", ; rel="last"' } 56 | rcs_next = String.new('[]') 57 | allow(rcs_next).to receive(:headers) { headers_next } 58 | rcs_last = String.new('[]') 59 | allow(rcs_last).to receive(:headers) { headers_last } 60 | 61 | RestClient.should_receive(:get) \ 62 | .with('https://api.github.com/some/path', test_header.merge({:params => {}})) \ 63 | .and_return(rcs_next) 64 | RestClient.should_receive(:get) \ 65 | .with('https://api.github.com/some/nextpath', test_header.merge({:params => {}})) \ 66 | .and_return(rcs_last) 67 | api = GitHubV3API.new(auth_token) 68 | api.get('/some/path') 69 | end 70 | 71 | it 'raises GitHubV3API::Unauthorized instead of RestClient::Unauthorized' do 72 | allow(RestClient).to receive(:get) { raise(RestClient::Unauthorized) } 73 | api = GitHubV3API.new(auth_token) 74 | lambda { api.get('/something') }.should raise_error(GitHubV3API::Unauthorized) 75 | end 76 | end 77 | 78 | describe "#post" do 79 | it "does a post request to the specified path at the github server, adds token, and payload as json" do 80 | data = {:title => 'omgbbq'} 81 | json = JSON.generate(data) 82 | RestClient.should_receive(:post) \ 83 | .with('https://api.github.com/some/path', json, test_header) \ 84 | .and_return('{}') 85 | api = GitHubV3API.new(auth_token) 86 | api.post('/some/path', data) 87 | end 88 | end 89 | 90 | describe "#patch" do 91 | it "does a post request to the specified path at the github server, adds token, and payload as json" do 92 | data = {:title => 'omgbbq'} 93 | json = JSON.generate(data) 94 | RestClient.should_receive(:post) \ 95 | .with('https://api.github.com/some/path', json, test_header) \ 96 | .and_return('{}') 97 | api = GitHubV3API.new(auth_token) 98 | api.patch('/some/path', data) 99 | end 100 | end 101 | 102 | describe "#delete" do 103 | it 'does a delete request to the specified path at the GitHub API server and adds the access token' do 104 | RestClient.should_receive(:delete) \ 105 | .with('https://api.github.com/some/path', test_header) \ 106 | .and_return('{}') 107 | api = GitHubV3API.new(auth_token) 108 | api.delete('/some/path') 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/issue_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::Issue do 4 | describe 'attr_readers' do 5 | it 'should define attr_readers that pull values from the issue data' do 6 | fields = %w(url html_url number state title body user labels assignee 7 | milestone comments pull_request closed_at created_at updated_at) 8 | fields.each do |f| 9 | repo = GitHubV3API::Issue.new_with_all_data(double('api'), {f.to_s => 'foo'}) 10 | repo.methods.should include(f.to_sym) 11 | repo.send(f).should == 'foo' 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/issues_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::IssuesAPI do 4 | describe "#list" do 5 | context "options = nil" do 6 | it "returns issues for a user" do 7 | connection = double(GitHubV3API) 8 | connection.should_receive(:get).with('/issues', {}).and_return([:issue_hash1, :issue_hash2]) 9 | api = GitHubV3API::IssuesAPI.new(connection) 10 | GitHubV3API::Issue.should_receive(:new).with(api, :issue_hash1).and_return(:issue1) 11 | GitHubV3API::Issue.should_receive(:new).with(api, :issue_hash2).and_return(:issue2) 12 | issues = api.list 13 | issues.should == [:issue1, :issue2] 14 | end 15 | end 16 | 17 | context "options = {:user => 'octocat', :repo => 'hello-world'}" do 18 | it "returns issues for a repo" do 19 | connection = double(GitHubV3API) 20 | connection.should_receive(:get).with("/repos/octocat/hello-world/issues", {}).and_return([:issue_hash1, :issue_hash2]) 21 | api = GitHubV3API::IssuesAPI.new(connection) 22 | GitHubV3API::Issue.should_receive(:new).with(api, :issue_hash1).and_return(:issue1) 23 | GitHubV3API::Issue.should_receive(:new).with(api, :issue_hash2).and_return(:issue2) 24 | issues = api.list({:user => 'octocat', :repo => 'hello-world'}) 25 | issues.should == [:issue1, :issue2] 26 | end 27 | end 28 | end 29 | 30 | describe '#get' do 31 | it 'returns a fully-hydrated Issue object for the specified user, repo, and issue id' do 32 | connection = double(GitHubV3API) 33 | connection.should_receive(:get).with('/repos/octocat/hello-world/issues/1234', {}).and_return(:issue_hash) 34 | api = GitHubV3API::IssuesAPI.new(connection) 35 | GitHubV3API::Issue.should_receive(:new_with_all_data).with(api, :issue_hash).and_return(:issue) 36 | api.get('octocat', 'hello-world', 1234).should == :issue 37 | end 38 | 39 | it 'raises GitHubV3API::NotFound instead of a RestClient::ResourceNotFound' do 40 | connection = double(GitHubV3API) 41 | connection.should_receive(:get) \ 42 | .and_raise(RestClient::ResourceNotFound) 43 | api = GitHubV3API::IssuesAPI.new(connection) 44 | lambda { api.get('octocat', 'hello-world', 4321) }.should raise_error(GitHubV3API::NotFound) 45 | end 46 | end 47 | 48 | describe "#create" do 49 | it 'returns a fully-hydrated Issue object for the specified user, repo, and issue that was created' do 50 | connection = double(GitHubV3API) 51 | data = {:title => "omgbbq"} 52 | connection.should_receive(:post).with('/repos/octocat/hello-world/issues', data).and_return(:issue_hash) 53 | api = GitHubV3API::IssuesAPI.new(connection) 54 | GitHubV3API::Issue.should_receive(:new_with_all_data).with(api, :issue_hash).and_return(:issue) 55 | api.create('octocat', 'hello-world', data).should == :issue 56 | end 57 | 58 | it "raises GitHubV3API::MissingRequiredData when data[:title] is missing" do 59 | connection = double(GitHubV3API) 60 | connection.should_not_receive(:post) 61 | api = GitHubV3API::IssuesAPI.new(connection) 62 | lambda { api.create('octocat', 'hello-world', {}) }.should raise_error(GitHubV3API::MissingRequiredData) 63 | end 64 | end 65 | 66 | describe "#update" do 67 | it 'returns a fully-hydrated Issue object for the specified user, repo, and issue that was updated' do 68 | connection = double(GitHubV3API) 69 | data = {:body => "lol, wtf"} 70 | connection.should_receive(:patch).with('/repos/octocat/hello-world/issues/1234', data).and_return(:issue_hash) 71 | api = GitHubV3API::IssuesAPI.new(connection) 72 | GitHubV3API::Issue.should_receive(:new_with_all_data).with(api, :issue_hash).and_return(:issue) 73 | api.update('octocat', 'hello-world', 1234, data).should == :issue 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/org_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::Org do 4 | describe 'attr_readers' do 5 | it 'should define attr_readers that pull values from the org data' do 6 | fields = %w(avatar_url billing_email blog collaborators 7 | company created_at disk_usage email followers following 8 | html_url id location login name owned_private_repos plan 9 | private_gists private_repos public_gists public_repos space 10 | total_private_repos type url) 11 | fields.each do |f| 12 | org = GitHubV3API::Org.new_with_all_data(double('api'), {f.to_s => 'foo'}) 13 | org.methods.should include(f.to_sym) 14 | org.send(f).should == 'foo' 15 | end 16 | end 17 | end 18 | 19 | describe '#[]' do 20 | it 'returns the org data for the specified key' do 21 | api = double(GitHubV3API::OrgsAPI) 22 | api.should_receive(:get).with('github') \ 23 | .and_return(GitHubV3API::Org.new(api, 'login' => 'github', 'company' => 'GitHub')) 24 | org = GitHubV3API::Org.new(api, 'login' => 'github') 25 | org['company'].should == 'GitHub' 26 | end 27 | 28 | it 'only fetches the data once' do 29 | api = double(GitHubV3API::OrgsAPI) 30 | api.should_receive(:get).once.with('github') \ 31 | .and_return(GitHubV3API::Org.new(api, 'login' => 'github', 'company' => 'GitHub')) 32 | org = GitHubV3API::Org.new(api, 'login' => 'github') 33 | org['login'].should == 'github' 34 | org['company'].should == 'GitHub' 35 | org['foo'].should be_nil 36 | end 37 | end 38 | 39 | describe '#repos' do 40 | it 'returns an array of Repo objects that belong to this org' do 41 | api = double(GitHubV3API::OrgsAPI) 42 | api.should_receive(:list_repos).with('github').and_return([:repo1, :repo2]) 43 | org = GitHubV3API::Org.new_with_all_data(api, 'login' => 'github') 44 | org.repos.should == [:repo1, :repo2] 45 | end 46 | end 47 | 48 | describe '#members'do 49 | it 'returns an array of User objects who are members of this org' do 50 | api = double(GitHubV3API::OrgsAPI) 51 | api.should_receive(:list_members).with('github').and_return([:user1, :user2]) 52 | 53 | org = GitHubV3API::Org.new_with_all_data(api, 'login' => 'github') 54 | org.members.should == [:user1, :user2] 55 | end 56 | end 57 | 58 | describe '#public_members'do 59 | it 'returns an array of User objects who are public members of this org' do 60 | api = double(GitHubV3API::OrgsAPI) 61 | api.should_receive(:list_public_members).with('github').and_return([:user1, :user2]) 62 | 63 | org = GitHubV3API::Org.new_with_all_data(api, 'login' => 'github') 64 | org.public_members.should == [:user1, :user2] 65 | end 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /spec/orgs_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::OrgsAPI do 4 | describe '#list' do 5 | it 'returns the public and private orgs for the authenticated user' do 6 | connection = double(GitHubV3API) 7 | connection.should_receive(:get).with('/user/orgs').and_return([:org_hash1, :org_hash2]) 8 | api = GitHubV3API::OrgsAPI.new(connection) 9 | GitHubV3API::Org.should_receive(:new).with(api, :org_hash1).and_return(:org1) 10 | GitHubV3API::Org.should_receive(:new).with(api, :org_hash2).and_return(:org2) 11 | orgs = api.list 12 | orgs.should == [:org1, :org2] 13 | end 14 | end 15 | 16 | describe '#get' do 17 | it 'returns a fully-hydrated Org object for the specified org login' do 18 | connection = double(GitHubV3API) 19 | connection.should_receive(:get).with('/orgs/octocat').and_return(:org_hash) 20 | api = GitHubV3API::OrgsAPI.new(connection) 21 | GitHubV3API::Org.should_receive(:new_with_all_data).with(api, :org_hash).and_return(:org) 22 | api.get('octocat').should == :org 23 | end 24 | end 25 | 26 | describe '#list_repos' do 27 | it 'returns the public and private repos for the specified org' do 28 | connection = double(GitHubV3API, :repos => :repos_api) 29 | connection.should_receive(:get) \ 30 | .with('/orgs/github/repos') \ 31 | .and_return([:repo1_hash, :repo2_hash]) 32 | GitHubV3API::Repo.should_receive(:new) \ 33 | .with(:repos_api, :repo1_hash) \ 34 | .and_return(:repo1) 35 | GitHubV3API::Repo.should_receive(:new) \ 36 | .with(:repos_api, :repo2_hash) \ 37 | .and_return(:repo2) 38 | api = GitHubV3API::OrgsAPI.new(connection) 39 | api.list_repos('github').should == [:repo1, :repo2] 40 | end 41 | end 42 | 43 | describe '#list_members' do 44 | it 'returns the list of members for the specified org' do 45 | connection = double(GitHubV3API, :users => :users_api) 46 | connection.should_receive(:get).once.with('/orgs/github/members') \ 47 | .and_return([:user_hash1, :user_hash2]) 48 | 49 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash1) \ 50 | .and_return(:user1) 51 | 52 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash2) \ 53 | .and_return(:user2) 54 | 55 | api = GitHubV3API::OrgsAPI.new(connection) 56 | api.list_members('github').should == [:user1, :user2] 57 | end 58 | end 59 | 60 | describe '#list_public_members' do 61 | it 'returns the list of public members for the specified org' do 62 | connection = double(GitHubV3API, :users => :users_api) 63 | connection.should_receive(:get).once.with('/orgs/github/public_members') \ 64 | .and_return([:user_hash1, :user_hash2]) 65 | 66 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash1) \ 67 | .and_return(:user1) 68 | 69 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash2) \ 70 | .and_return(:user2) 71 | 72 | api = GitHubV3API::OrgsAPI.new(connection) 73 | api.list_public_members('github').should == [:user1, :user2] 74 | end 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /spec/repo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::Repo do 4 | describe 'attr_readers' do 5 | it 'should define attr_readers that pull values from the repo data' do 6 | fields = %w(created_at description fork forks has_downloads has_issues 7 | has_wiki homepage html_url language master_branch name open_issues 8 | organization owner parent private pushed_at size source url watchers) 9 | fields.each do |f| 10 | repo = GitHubV3API::Repo.new_with_all_data(double('api'), {f.to_s => 'foo'}) 11 | repo.methods.should include(f.to_sym) 12 | repo.send(f).should == 'foo' 13 | end 14 | end 15 | end 16 | 17 | describe '#[]' do 18 | it 'returns the repo data for the specified key' do 19 | api = double(GitHubV3API::ReposAPI) 20 | repo = GitHubV3API::Repo \ 21 | .new_with_all_data(api, 'name' => 'hello-world', 'owner' => {'login' => 'octocat'}) 22 | repo['name'].should == 'hello-world' 23 | end 24 | 25 | it 'only fetches the data once' do 26 | api = double(GitHubV3API::ReposAPI) 27 | api.should_receive(:get).once.with('octocat', 'hello-world') \ 28 | .and_return(GitHubV3API::Repo.new(api, 'name' => 'hello-world', 'owner' => {'login' => 'octocat'}, 'private' => true)) 29 | repo = GitHubV3API::Repo.new(api, 'name' => 'hello-world', 'owner' => {'login' => 'octocat'}) 30 | repo['name'].should == 'hello-world' 31 | repo['owner'].should == {'login' => 'octocat'} 32 | repo['private'].should == true 33 | end 34 | end 35 | 36 | describe '#owner_login' do 37 | it 'returns the login name of the repo owner' do 38 | api = double(GitHubV3API::ReposAPI) 39 | repo = GitHubV3API::Repo.new_with_all_data(api, 'owner' => {'login' => 'octocat'}) 40 | repo.owner_login.should == 'octocat' 41 | end 42 | end 43 | 44 | describe '#list_collaborators' do 45 | it 'returns an array of User objects who are collaborating on this repo' do 46 | api = double(GitHubV3API::ReposAPI) 47 | api.should_receive(:list_collaborators).once.with( 48 | 'octocat', 'hello-world').and_return([:user1, :user2, :user3]) 49 | 50 | repo = GitHubV3API::Repo.new(api, 'name' => 'hello-world', 51 | 'owner' => {'login' => 'octocat'}) 52 | repo.list_collaborators.should == [:user1, :user2, :user3] 53 | end 54 | end 55 | 56 | describe '#list_watchers' do 57 | it 'returns an array of User objects who are watching this repo' do 58 | api = double(GitHubV3API::ReposAPI) 59 | api.should_receive(:list_watchers).once.with( 60 | 'octocat', 'hello-world').and_return([:user1, :user2, :user3]) 61 | 62 | repo = GitHubV3API::Repo.new(api, 'name' => 'hello-world', 63 | 'owner' => {'login' => 'octocat'}) 64 | repo.list_watchers.should == [:user1, :user2, :user3] 65 | end 66 | end 67 | 68 | describe '#list_forks' do 69 | it 'returns an array of Repo objects which were forked from this repo' do 70 | api = double(GitHubV3API::ReposAPI) 71 | api.should_receive(:list_forks).once.with( 72 | 'octocat', 'hello-world').and_return([:repo1, :repo2, :repo3]) 73 | 74 | repo = GitHubV3API::Repo.new(api, 'name' => 'hello-world', 75 | 'owner' => {'login' => 'octocat'}) 76 | repo.list_forks.should == [:repo1, :repo2, :repo3] 77 | end 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /spec/repos_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::ReposAPI do 4 | describe '#list' do 5 | it 'returns the public and private repos for the authenticated user' do 6 | connection = double(GitHubV3API) 7 | connection.should_receive(:get).with('/user/repos').and_return([:repo_hash1, :repo_hash2]) 8 | api = GitHubV3API::ReposAPI.new(connection) 9 | GitHubV3API::Repo.should_receive(:new).with(api, :repo_hash1).and_return(:repo1) 10 | GitHubV3API::Repo.should_receive(:new).with(api, :repo_hash2).and_return(:repo2) 11 | repos = api.list 12 | repos.should == [:repo1, :repo2] 13 | end 14 | end 15 | 16 | describe '#get' do 17 | it 'returns a fully-hydrated Repo object for the specified user and repo name' do 18 | connection = double(GitHubV3API) 19 | connection.should_receive(:get).with('/repos/octocat/hello-world').and_return(:repo_hash) 20 | api = GitHubV3API::ReposAPI.new(connection) 21 | GitHubV3API::Repo.should_receive(:new_with_all_data).with(api, :repo_hash).and_return(:repo) 22 | api.get('octocat', 'hello-world').should == :repo 23 | end 24 | 25 | it 'raises GitHubV3API::NotFound instead of a RestClient::ResourceNotFound' do 26 | connection = double(GitHubV3API) 27 | connection.should_receive(:get) \ 28 | .and_raise(RestClient::ResourceNotFound) 29 | api = GitHubV3API::ReposAPI.new(connection) 30 | lambda { api.get('octocat', 'hello-world') }.should raise_error(GitHubV3API::NotFound) 31 | end 32 | end 33 | 34 | describe "#list_collaborators" do 35 | it 'returns a list of Users who are collaborating on the specified repo' do 36 | connection = double(GitHubV3API) 37 | connection.should_receive(:get).with( 38 | '/repos/octocat/hello-world/collaborators').and_return([:user_hash1, :user_hash2]) 39 | connection.should_receive(:users).twice.and_return(:users_api) 40 | api = GitHubV3API::ReposAPI.new(connection) 41 | 42 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash1).and_return(:user1) 43 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash2).and_return(:user2) 44 | 45 | collaborators = api.list_collaborators('octocat', 'hello-world') 46 | collaborators.should == [:user1, :user2] 47 | end 48 | end 49 | 50 | describe "#list_watchers" do 51 | it 'returns a list of Users who are watching the specified repo' do 52 | connection = double(GitHubV3API) 53 | connection.should_receive(:get).with( 54 | '/repos/octocat/hello-world/watchers').and_return([:user_hash1, :user_hash2]) 55 | connection.should_receive(:users).twice.and_return(:users_api) 56 | api = GitHubV3API::ReposAPI.new(connection) 57 | 58 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash1).and_return(:user1) 59 | GitHubV3API::User.should_receive(:new).with(:users_api, :user_hash2).and_return(:user2) 60 | 61 | watchers = api.list_watchers('octocat', 'hello-world') 62 | watchers.should == [:user1, :user2] 63 | end 64 | end 65 | 66 | describe "#list_forks" do 67 | it 'returns a list of Repos which were forked from the specified repo' do 68 | connection = double(GitHubV3API) 69 | connection.should_receive(:get).with( 70 | '/repos/octocat/hello-world/forks').and_return([:repo_hash1, :repo_hash2]) 71 | api = GitHubV3API::ReposAPI.new(connection) 72 | 73 | GitHubV3API::Repo.should_receive(:new).with(api, :repo_hash1).and_return(:fork1) 74 | GitHubV3API::Repo.should_receive(:new).with(api, :repo_hash2).and_return(:fork2) 75 | 76 | forks = api.list_forks('octocat', 'hello-world') 77 | forks.should == [:fork1, :fork2] 78 | end 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'github_v3_api' 5 | 6 | RSpec.configure do |config| 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/user_api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::UsersAPI do 4 | describe '#current' do 5 | it 'returns the user data for the authenticated user' do 6 | connection = double(GitHubV3API) 7 | connection.should_receive(:get).with('/user').and_return(:user_hash1) 8 | api = GitHubV3API::UsersAPI.new(connection) 9 | GitHubV3API::User.should_receive(:new).with(api, :user_hash1).and_return(:user1) 10 | user = api.current 11 | user.should == :user1 12 | end 13 | end 14 | 15 | describe '#get' do 16 | it 'returns a fully-hydrated User object for the specified user login' do 17 | connection = double(GitHubV3API) 18 | connection.should_receive(:get).with('/users/octocat').and_return(:user_hash) 19 | api = GitHubV3API::UsersAPI.new(connection) 20 | GitHubV3API::User.should_receive(:new_with_all_data).with(api, :user_hash).and_return(:user) 21 | api.get('octocat').should == :user 22 | end 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /spec/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe GitHubV3API::User do 4 | describe 'attr_readers' do 5 | it 'should define attr_readers that pull values from the user data' do 6 | fields = %w(login id avatar_url gravatar_id url name company blog location 7 | email hireable bio public_repos public_gists followers following 8 | html_url created_at type total_private_repos owned_private_repos 9 | private_gists disk_usage collaborators) 10 | fields.each do |f| 11 | user = GitHubV3API::User.new_with_all_data(double('api'), {f.to_s => 'foo'}) 12 | user.methods.should include(f.to_sym) 13 | user.send(f).should == 'foo' 14 | end 15 | end 16 | end 17 | 18 | describe '#[]' do 19 | it 'returns the user data for the specified key' do 20 | api = double(GitHubV3API::UsersAPI) 21 | api.should_receive(:get).with('github') \ 22 | .and_return(GitHubV3API::User.new(api, 'login' => 'github', 'name' => 'GitHub')) 23 | user = GitHubV3API::User.new(api, 'login' => 'github') 24 | user['name'].should == 'GitHub' 25 | end 26 | 27 | it 'only fetches the data once' do 28 | api = double(GitHubV3API::UsersAPI) 29 | api.should_receive(:get).once.with('github') \ 30 | .and_return(GitHubV3API::User.new(api, 'login' => 'github', 'name' => 'GitHub')) 31 | user = GitHubV3API::User.new(api, 'login' => 'github') 32 | user['login'].should == 'github' 33 | user['name'].should == 'GitHub' 34 | user['foo'].should be_nil 35 | end 36 | end 37 | 38 | end 39 | --------------------------------------------------------------------------------