octokit 2.0.0.rc2 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -112,6 +112,8 @@ implementation, you will be responsible for providing patches in a timely
112
112
  fashion. If critical issues for a particular implementation exist at the time
113
113
  of a major release, support for that Ruby version may be dropped.
114
114
 
115
+ [travis]: https://travis-ci.org/octokit/octokit.rb
116
+
115
117
  ## Versioning
116
118
 
117
119
  This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Violations
@@ -104,7 +104,7 @@ module Octokit
104
104
  # @example
105
105
  # @client.org_repos('github', {:type => 'private'})
106
106
  def organization_repositories(org, options = {})
107
- get "orgs/#{org}/repos", options
107
+ paginate "orgs/#{org}/repos", options
108
108
  end
109
109
  alias :org_repositories :organization_repositories
110
110
  alias :org_repos :organization_repositories
@@ -125,7 +125,7 @@ module Octokit
125
125
  # @example
126
126
  # Octokit.org_members('github')
127
127
  def organization_members(org, options = {})
128
- get "orgs/#{org}/members", options
128
+ paginate "orgs/#{org}/members", options
129
129
  end
130
130
  alias :org_members :organization_members
131
131
 
@@ -11,7 +11,7 @@ module Octokit
11
11
  # @param query [String] Search term and qualifiers
12
12
  # @param options [Hash] Sort and pagination options
13
13
  # @option options [String] :sort Sort field
14
- # @option options [String] :direction Sort direction (asc or desc)
14
+ # @option options [String] :order Sort order (asc or desc)
15
15
  # @option options [Fixnum] :page Page of paginated results
16
16
  # @option options [Fixnum] :per_page Number of items per page
17
17
  # @return [Sawyer::Resource] Search results object
@@ -25,7 +25,7 @@ module Octokit
25
25
  # @param query [String] Search term and qualifiers
26
26
  # @param options [Hash] Sort and pagination options
27
27
  # @option options [String] :sort Sort field
28
- # @option options [String] :direction Sort direction (asc or desc)
28
+ # @option options [String] :order Sort order (asc or desc)
29
29
  # @option options [Fixnum] :page Page of paginated results
30
30
  # @option options [Fixnum] :per_page Number of items per page
31
31
  # @return [Sawyer::Resource] Search results object
@@ -39,7 +39,7 @@ module Octokit
39
39
  # @param query [String] Search term and qualifiers
40
40
  # @param options [Hash] Sort and pagination options
41
41
  # @option options [String] :sort Sort field
42
- # @option options [String] :direction Sort direction (asc or desc)
42
+ # @option options [String] :order Sort order (asc or desc)
43
43
  # @option options [Fixnum] :page Page of paginated results
44
44
  # @option options [Fixnum] :per_page Number of items per page
45
45
  # @return [Sawyer::Resource] Search results object
@@ -54,7 +54,7 @@ module Octokit
54
54
  # @param query [String] Search term and qualifiers
55
55
  # @param options [Hash] Sort and pagination options
56
56
  # @option options [String] :sort Sort field
57
- # @option options [String] :direction Sort direction (asc or desc)
57
+ # @option options [String] :order Sort order (asc or desc)
58
58
  # @option options [Fixnum] :page Page of paginated results
59
59
  # @option options [Fixnum] :per_page Number of items per page
60
60
  # @return [Sawyer::Resource] Search results object
@@ -11,7 +11,7 @@ module Octokit
11
11
  # @param repo [String, Repository, Hash] A GitHub repository
12
12
  # @param sha [String] The SHA1 for the commit
13
13
  # @return [Array<Sawyer::Resource>] A list of statuses
14
- # @see http://developer.github.com/v3/repos/status
14
+ # @see http://developer.github.com/v3/repos/statuses
15
15
  def statuses(repo, sha, options = {})
16
16
  get "repos/#{Repository.new(repo)}/statuses/#{sha}", options
17
17
  end
@@ -23,7 +23,7 @@ module Octokit
23
23
  # @param sha [String] The SHA1 for the commit
24
24
  # @param state [String] The state: pending, success, failure, error
25
25
  # @return [Sawyer::Resource] A status
26
- # @see http://developer.github.com/v3/repos/status
26
+ # @see http://developer.github.com/v3/repos/statuses
27
27
  def create_status(repo, sha, state, options = {})
28
28
  options.merge!(:state => state)
29
29
  post "repos/#{Repository.new(repo)}/statuses/#{sha}", options
@@ -106,7 +106,8 @@ module Octokit
106
106
  get "users/#{user}/following", options
107
107
  end
108
108
 
109
- # Check if you are following a user.
109
+ # Check if you are following a user. Alternatively, check if a given user
110
+ # is following a target user.
110
111
  #
111
112
  # Requries an authenticated client.
112
113
  #
@@ -119,12 +120,17 @@ module Octokit
119
120
  # @see http://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user
120
121
  # @example
121
122
  # @client.follows?('pengwynn')
123
+ # @example
124
+ # @client.follows?('catsby', 'pengwynn')
122
125
  def follows?(*args)
123
126
  target = args.pop
124
127
  user = args.first
125
- user ||= login
126
- return if user.nil?
127
- boolean_from_response :get, "user/following/#{target}"
128
+ if user.nil?
129
+ url = "user/following/#{target}"
130
+ else
131
+ url = "users/#{user}/following/#{target}"
132
+ end
133
+ boolean_from_response :get, url
128
134
  end
129
135
 
130
136
  # Follow a user.
@@ -26,7 +26,7 @@ module Octokit
26
26
  # @return [String] GitHub username for Basic Authentication
27
27
  # @!attribute middleware
28
28
  # @see https://github.com/lostisland/faraday
29
- # @return [Faraday::RackBuilder] Configure middleware for Faraday
29
+ # @return [Faraday::Builder] Configure middleware for Faraday
30
30
  # @!attribute netrc
31
31
  # @return [Boolean] Instruct Octokit to get credentials from .netrc file
32
32
  # @!attribute netrc_file
@@ -19,7 +19,7 @@ module Octokit
19
19
  WEB_ENDPOINT = "https://github.com".freeze
20
20
 
21
21
  # Default Faraday middleware stack
22
- MIDDLEWARE = Faraday::RackBuilder.new do |builder|
22
+ MIDDLEWARE = Faraday::Builder.new do |builder|
23
23
  builder.use Octokit::Response::RaiseError
24
24
  builder.adapter Faraday.default_adapter
25
25
  end
@@ -1,18 +1,54 @@
1
1
  module Octokit
2
2
  # Custom error class for rescuing from all GitHub errors
3
3
  class Error < StandardError
4
+
5
+ # Returns the appropriate Octokit::Error sublcass based
6
+ # on status and response message
7
+ #
8
+ # @param [Hash]
9
+ # @returns [Octokit::Error]
10
+ def self.from_response(response)
11
+ status = response[:status].to_i
12
+ body = response[:body].to_s
13
+
14
+ if klass = case status
15
+ when 400 then Octokit::BadRequest
16
+ when 401 then Octokit::Unauthorized
17
+ when 403
18
+ if body =~ /rate limit exceeded/i
19
+ Octokit::TooManyRequests
20
+ elsif body =~ /login attempts exceeded/i
21
+ Octokit::TooManyLoginAttempts
22
+ else
23
+ Octokit::Forbidden
24
+ end
25
+ when 404 then Octokit::NotFound
26
+ when 406 then Octokit::NotAcceptable
27
+ when 422 then Octokit::UnprocessableEntity
28
+ when 500 then Octokit::InternalServerError
29
+ when 501 then Octokit::NotImplemented
30
+ when 502 then Octokit::BadGateway
31
+ when 503 then Octokit::ServiceUnavailable
32
+ end
33
+ klass.new(response)
34
+ end
35
+ end
36
+
4
37
  def initialize(response=nil)
5
38
  @response = response
6
39
  super(build_error_message)
7
40
  end
8
41
 
9
- # private
10
- def response_body
11
- @response_body ||=
42
+ private
43
+
44
+ def data
45
+ @data ||=
12
46
  if (body = @response[:body]) && !body.empty?
13
- if body.is_a?(String)
14
- serializer = Sawyer::Serializer.new(Sawyer::Serializer.any_json)
15
- serializer.decode(body)
47
+ if body.is_a?(String) &&
48
+ @response[:response_headers] &&
49
+ @response[:response_headers][:content_type] =~ /json/
50
+
51
+ Sawyer::Agent.serializer.decode(body)
16
52
  else
17
53
  body
18
54
  end
@@ -21,19 +57,40 @@ module Octokit
21
57
  end
22
58
  end
23
59
 
24
- # private
60
+ def response_message
61
+ case data
62
+ when Hash
63
+ data[:message]
64
+ when String
65
+ data
66
+ end
67
+ end
68
+
69
+ def response_error
70
+ "Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
71
+ end
72
+
73
+ def response_error_summary
74
+ return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
75
+
76
+ summary = "\nError summary:\n"
77
+ summary << data[:errors].map do |hash|
78
+ hash.map { |k,v| " #{k}: #{v}" }
79
+ end.join("\n")
80
+
81
+ summary
82
+ end
83
+
25
84
  def build_error_message
26
- return nil if @response.nil?
85
+ return nil if @response.nil?
27
86
 
28
- message = if response_body
29
- ": #{response_body['error'] || response_body['message'] || ''}"
30
- else
31
- ''
32
- end
33
- errors = unless message.empty?
34
- response_body['errors'] ? ": #{response_body['errors'].map{|e|e['message']}.join(', ')}" : ''
35
- end
36
- "#{@response[:method].to_s.upcase} #{@response[:url].to_s}: #{@response[:status]}#{message}#{errors}"
87
+ message = "#{@response[:method].to_s.upcase} "
88
+ message << "#{@response[:url].to_s}: "
89
+ message << "#{@response[:status]} - "
90
+ message << "#{response_message}" unless response_message.nil?
91
+ message << "#{response_error}" unless response_error.nil?
92
+ message << "#{response_error_summary}" unless response_error_summary.nil?
93
+ message
37
94
  end
38
95
  end
39
96
 
@@ -46,6 +103,14 @@ module Octokit
46
103
  # Raised when GitHub returns a 403 HTTP status code
47
104
  class Forbidden < Error; end
48
105
 
106
+ # Raised when GitHub returns a 403 HTTP status code
107
+ # and body matches 'rate limit exceeded'
108
+ class TooManyRequests < Forbidden; end
109
+
110
+ # Raised when GitHub returns a 403 HTTP status code
111
+ # and body matches 'login attempts exceeded'
112
+ class TooManyLoginAttempts < Forbidden; end
113
+
49
114
  # Raised when GitHub returns a 404 HTTP status code
50
115
  class NotFound < Error; end
51
116
 
@@ -9,26 +9,12 @@ module Octokit
9
9
  # HTTP status codes returned by the API
10
10
  class RaiseError < Faraday::Response::Middleware
11
11
 
12
- # Status code to error mappings
13
- # @private
14
- ERROR_MAP = {
15
- 400 => Octokit::BadRequest,
16
- 401 => Octokit::Unauthorized,
17
- 403 => Octokit::Forbidden,
18
- 404 => Octokit::NotFound,
19
- 406 => Octokit::NotAcceptable,
20
- 422 => Octokit::UnprocessableEntity,
21
- 500 => Octokit::InternalServerError,
22
- 501 => Octokit::NotImplemented,
23
- 502 => Octokit::BadGateway,
24
- 503 => Octokit::ServiceUnavailable
25
- }
26
-
27
12
  private
28
13
 
29
14
  def on_complete(response)
30
- key = response[:status].to_i
31
- raise ERROR_MAP[key].new(response) if ERROR_MAP.has_key? key
15
+ if error = Octokit::Error.from_response(response)
16
+ raise error
17
+ end
32
18
  end
33
19
  end
34
20
  end
@@ -2,6 +2,6 @@ module Octokit
2
2
 
3
3
  # Current version
4
4
  # @return [String]
5
- VERSION = "2.0.0.rc2".freeze
5
+ VERSION = "2.0.0.rc3".freeze
6
6
 
7
7
  end
@@ -5,7 +5,7 @@ require 'octokit/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.add_development_dependency 'bundler', '~> 1.0'
8
- spec.add_dependency 'sawyer', '~> 0.1.0'
8
+ spec.add_dependency 'sawyer', '~> 0.3.0'
9
9
  spec.authors = ["Wynn Netherland", "Erik Michaels-Ober", "Clint Shryock"]
10
10
  spec.description = %q{Simple wrapper for the GitHub API}
11
11
  spec.email = ['wynn.netherland@gmail.com', 'sferik@gmail.com', 'clint@ctshryock.com']
@@ -0,0 +1 @@
1
+ {"http_interactions":[{"request":{"method":"get","uri":"https://api.github.com/users/sferik/following/pengwynn","body":{"encoding":"US-ASCII","base64_string":""},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0.rc2"],"Authorization":["token <<ACCESS_TOKEN>>"],"Accept-Encoding":["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"]}},"response":{"status":{"code":204,"message":"No Content"},"headers":{"Server":["GitHub.com"],"Date":["Tue, 13 Aug 2013 14:34:49 GMT"],"Status":["204 No Content"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4997"],"X-Ratelimit-Reset":["1376407750"],"X-Oauth-Scopes":["repo, user, gist, delete_repo"],"X-Accepted-Oauth-Scopes":["user, user:email, user:follow, site_admin"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Access-Control-Allow-Credentials":["true"],"Access-Control-Expose-Headers":["ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes"],"Access-Control-Allow-Origin":["*"],"Vary":["Accept-Encoding"]},"body":{"encoding":"US-ASCII","base64_string":""},"http_version":null},"recorded_at":"Tue, 13 Aug 2013 14:34:50 GMT"}],"recorded_with":"VCR 2.4.0"}
@@ -7,6 +7,7 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
7
  ]
8
8
  SimpleCov.start
9
9
 
10
+ require 'json'
10
11
  require 'octokit'
11
12
  require 'rspec'
12
13
  require 'webmock/rspec'
@@ -32,9 +32,9 @@ describe Octokit::Client::LegacySearch do
32
32
  it "should not raise URI::InvalidURIError and returns success" do
33
33
  VCR.eject_cassette
34
34
  VCR.turn_off!
35
- stub_get("https://api.github.com/legacy/user/search/follower:>0")
36
- expect { @client.legacy_search_users("follower:>0") }.to_not raise_error
37
- assert_requested :get, github_url("/legacy/user/search/follower:%3E0")
35
+ stub_get("https://api.github.com/legacy/user/search/followers:>0")
36
+ expect { @client.legacy_search_users("followers:>0") }.to_not raise_error(URI::InvalidURIError)
37
+ assert_requested :get, github_url("/legacy/user/search/followers:%3E0")
38
38
  VCR.turn_on!
39
39
  end
40
40
  end # .legacy_searcy_users
@@ -65,11 +65,16 @@ describe Octokit::Client::Users do
65
65
  end
66
66
  end # .following
67
67
 
68
- describe ".follows?", :vcr do
68
+ describe ".follows?", :vcr, :match_requests_on => [:uri] do
69
69
  it "checks if the authenticated user follows another" do
70
70
  follows = @client.follows?("sferik")
71
71
  assert_requested :get, github_url("/user/following/sferik")
72
72
  end
73
+
74
+ it "checks if given user is following target user" do
75
+ follows = @client.follows?("sferik", "pengwynn")
76
+ assert_requested :get, github_url("/users/sferik/following/pengwynn")
77
+ end
73
78
  end # .follows?
74
79
 
75
80
  describe ".follow", :vcr do
@@ -1,4 +1,5 @@
1
1
  require 'helper'
2
+ require 'json'
2
3
 
3
4
  describe Octokit::Client do
4
5
 
@@ -250,6 +251,7 @@ describe Octokit::Client do
250
251
 
251
252
  describe ".root" do
252
253
  it "fetches the API root" do
254
+ Octokit.reset!
253
255
  VCR.use_cassette 'root' do
254
256
  root = Octokit.client.root
255
257
  expect(root.rels[:issues].href).to eq "https://api.github.com/issues"
@@ -367,30 +369,103 @@ describe Octokit::Client do
367
369
  end
368
370
 
369
371
  context "error handling" do
370
- before(:each) do
372
+ before do
371
373
  Octokit.reset!
374
+ VCR.turn_off!
375
+ end
376
+
377
+ after do
378
+ VCR.turn_on!
372
379
  end
373
380
 
374
- it "raises on 404", :vcr do
375
- expect { Octokit.get('/user') }.to raise_error Octokit::Unauthorized
381
+ it "raises on 404" do
382
+ stub_get('/booya').to_return(:status => 404)
383
+ expect { Octokit.get('/booya') }.to raise_error Octokit::NotFound
376
384
  end
377
385
 
378
386
  it "raises on 500" do
379
- VCR.turn_off!
380
387
  stub_get('/boom').to_return(:status => 500)
381
388
  expect { Octokit.get('/boom') }.to raise_error Octokit::InternalServerError
382
- VCR.turn_on!
383
389
  end
384
390
 
385
- it "includes an error message" do
391
+ it "includes a message" do
386
392
  stub_get('/boom').
387
- to_return(:status => 422, :body => '{"message":"No repository found for hub.topic: https://github.com/joshk/not_existing_project/events/push"}')
393
+ to_return \
394
+ :status => 422,
395
+ :headers => {
396
+ :content_type => "application/json",
397
+ },
398
+ :body => {:message => "No repository found for hubtopic"}.to_json
388
399
  begin
389
400
  Octokit.get('/boom')
390
401
  rescue Octokit::UnprocessableEntity => e
391
- expect(e.message).to include "GET https://api.github.com/boom: 422"
402
+ expect(e.message).to include \
403
+ "GET https://api.github.com/boom: 422 - No repository found"
392
404
  end
393
405
  end
406
+
407
+ it "includes an error" do
408
+ stub_get('/boom').
409
+ to_return \
410
+ :status => 422,
411
+ :headers => {
412
+ :content_type => "application/json",
413
+ },
414
+ :body => {:error => "No repository found for hubtopic"}.to_json
415
+ begin
416
+ Octokit.get('/boom')
417
+ rescue Octokit::UnprocessableEntity => e
418
+ expect(e.message).to include \
419
+ "GET https://api.github.com/boom: 422 - Error: No repository found"
420
+ end
421
+ end
422
+
423
+ it "includes an error summary" do
424
+ stub_get('/boom').
425
+ to_return \
426
+ :status => 422,
427
+ :headers => {
428
+ :content_type => "application/json",
429
+ },
430
+ :body => {
431
+ :message => "Validation Failed",
432
+ :errors => [
433
+ :resource => "Issue",
434
+ :field => "title",
435
+ :code => "missing_field"
436
+ ]
437
+ }.to_json
438
+ begin
439
+ Octokit.get('/boom')
440
+ rescue Octokit::UnprocessableEntity => e
441
+ expect(e.message).to include \
442
+ "GET https://api.github.com/boom: 422 - Validation Failed"
443
+ expect(e.message).to include " resource: Issue"
444
+ expect(e.message).to include " field: title"
445
+ expect(e.message).to include " code: missing_field"
446
+ end
447
+ end
448
+
449
+ it "knows the difference between Forbidden and rate limiting" do
450
+ stub_get('/some/admin/stuffs').to_return(:status => 403)
451
+ expect { Octokit.get('/some/admin/stuffs') }.to raise_error Octokit::Forbidden
452
+
453
+ stub_get('/users/mojomobo').to_return \
454
+ :status => 403,
455
+ :headers => {
456
+ :content_type => "application/json",
457
+ },
458
+ :body => {:message => "API rate limit exceeded"}.to_json
459
+ expect { Octokit.get('/users/mojomobo') }.to raise_error Octokit::TooManyRequests
460
+
461
+ stub_get('/user').to_return \
462
+ :status => 403,
463
+ :headers => {
464
+ :content_type => "application/json",
465
+ },
466
+ :body => {:message => "Maximum number of login attempts exceeded"}.to_json
467
+ expect { Octokit.get('/user') }.to raise_error Octokit::TooManyLoginAttempts
468
+ end
394
469
  end
395
470
 
396
471
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: octokit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc2
4
+ version: 2.0.0.rc3
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-07-19 00:00:00.000000000 Z
14
+ date: 2013-08-14 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -36,7 +36,7 @@ dependencies:
36
36
  requirements:
37
37
  - - ~>
38
38
  - !ruby/object:Gem::Version
39
- version: 0.1.0
39
+ version: 0.3.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,7 +44,7 @@ dependencies:
44
44
  requirements:
45
45
  - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: 0.1.0
47
+ version: 0.3.0
48
48
  description: Simple wrapper for the GitHub API
49
49
  email:
50
50
  - wynn.netherland@gmail.com
@@ -360,6 +360,7 @@ files:
360
360
  - spec/cassettes/Octokit_Client_Users/_followers/returns_the_authenticated_user_s_followers.json
361
361
  - spec/cassettes/Octokit_Client_Users/_following/returns_following_for_a_user.json
362
362
  - spec/cassettes/Octokit_Client_Users/_following/returns_the_authenticated_user_s_following.json
363
+ - spec/cassettes/Octokit_Client_Users/_follows_/checks_if_given_user_is_following_target_user.json
363
364
  - spec/cassettes/Octokit_Client_Users/_follows_/checks_if_the_authenticated_user_follows_another.json
364
365
  - spec/cassettes/Octokit_Client_Users/_keys/returns_public_keys_for_the_authenticated_user.json
365
366
  - spec/cassettes/Octokit_Client_Users/_remove_email/removes_an_email_address.json
@@ -703,6 +704,7 @@ test_files:
703
704
  - spec/cassettes/Octokit_Client_Users/_followers/returns_the_authenticated_user_s_followers.json
704
705
  - spec/cassettes/Octokit_Client_Users/_following/returns_following_for_a_user.json
705
706
  - spec/cassettes/Octokit_Client_Users/_following/returns_the_authenticated_user_s_following.json
707
+ - spec/cassettes/Octokit_Client_Users/_follows_/checks_if_given_user_is_following_target_user.json
706
708
  - spec/cassettes/Octokit_Client_Users/_follows_/checks_if_the_authenticated_user_follows_another.json
707
709
  - spec/cassettes/Octokit_Client_Users/_keys/returns_public_keys_for_the_authenticated_user.json
708
710
  - spec/cassettes/Octokit_Client_Users/_remove_email/removes_an_email_address.json