octokit 2.0.0.rc2 → 2.0.0.rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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