octokit 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -43,8 +43,8 @@ configuration) or as client instance methods.
43
43
  ```ruby
44
44
  # Provide authentication credentials
45
45
  Octokit.configure do |c|
46
- c.login 'defunkt'
47
- c.password 'c0d3b4ssssss!'
46
+ c.login = 'defunkt'
47
+ c.password = 'c0d3b4ssssss!'
48
48
  end
49
49
 
50
50
  # Fetch the current user
@@ -297,6 +297,7 @@ client instances based on unique configuration options. Breaking changes also
297
297
  include:
298
298
 
299
299
  * `:oauth_token` is now `:access_token`
300
+ * `:auto_traversal` is now `:auto_paginate`
300
301
  * `Hashie::Mash` has been removed. Responses now return a `Sawyer::Resource`
301
302
  object. This new type behaves mostly like a Ruby `Hash`, but does not fully
302
303
  support the `Hashie::Mash` API.
@@ -45,6 +45,9 @@ module Octokit
45
45
  # @option options [Array] :scopes A list of scopes that this authorization is in.
46
46
  # @option options [String] :note A note to remind you what the OAuth token is for.
47
47
  # @option options [String] :note_url A URL to remind you what app the OAuth token is for.
48
+ # @option options [Boolean] :idempotent If true, will return an existing authorization if one has already been created.
49
+ # @option options [String] :client_id Client Id we received when our application was registered with GitHub.
50
+ # @option options [String] :client_id Client Secret we received when our application was registered with GitHub.
48
51
  #
49
52
  # @return [Sawyer::Resource] A single authorization for the authenticated user
50
53
  # @see http://developer.github.com/v3/oauth/#scopes Available scopes
@@ -52,10 +55,21 @@ module Octokit
52
55
  # @example Create a new authorization for user ctshryock's project Zoidberg
53
56
  # client = Octokit::Client.new(:login => 'ctshryock', :password => 'secret')
54
57
  # client.create_authorization({:scopes => ["public_repo","gist"], :note => "Why not Zoidberg?", :note_url=> "https://en.wikipedia.org/wiki/Zoidberg"})
58
+ # @example Create a new OR return an existing authorization to be used by a specific client for user ctshryock's project Zoidberg
59
+ # client = Octokit::Client.new(:login => 'ctshryock', :password => 'secret')
60
+ # client.create_authorization({:idempotent => true, :client_id => 'xxxx', :client_secret => 'yyyy', :scopes => ["user"]})
55
61
  def create_authorization(options = {})
56
62
  # Techincally we can omit scopes as GitHub has a default, however the
57
63
  # API will reject us if we send a POST request with an empty body.
58
- post 'authorizations', options
64
+
65
+ if options.delete :idempotent
66
+ client_id, client_secret = fetch_client_id_and_secret(options)
67
+ raise ArgumentError.new("Client ID and Secret required for idempotent authorizations") unless client_id && client_secret
68
+
69
+ put "authorizations/clients/#{client_id}", options.merge(:client_secret => client_secret)
70
+ else
71
+ post 'authorizations', options
72
+ end
59
73
  end
60
74
 
61
75
  # Update an authorization for the authenticated user.
@@ -50,7 +50,11 @@ module Octokit
50
50
  options.merge!({
51
51
  :code => code,
52
52
  :client_id => app_id,
53
- :client_secret => app_secret
53
+ :client_secret => app_secret,
54
+ :headers => {
55
+ :content_type => 'application/json',
56
+ :accept => 'application/json'
57
+ }
54
58
  })
55
59
  post "#{web_endpoint}login/oauth/access_token", options
56
60
  end
@@ -115,5 +115,10 @@ module Octokit
115
115
  def options
116
116
  Hash[Octokit::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
117
117
  end
118
+
119
+ def fetch_client_id_and_secret(overrides = {})
120
+ opts = options.merge(overrides)
121
+ opts.values_at :client_id, :client_secret
122
+ end
118
123
  end
119
124
  end
@@ -8,12 +8,18 @@ module Octokit
8
8
  # @param [Hash] response HTTP response
9
9
  # @return [Octokit::Error]
10
10
  def self.from_response(response)
11
- status = response[:status].to_i
12
- body = response[:body].to_s
11
+ status = response[:status].to_i
12
+ body = response[:body].to_s
13
+ headers = response[:response_headers]
13
14
 
14
15
  if klass = case status
15
16
  when 400 then Octokit::BadRequest
16
- when 401 then Octokit::Unauthorized
17
+ when 401
18
+ if Octokit::OneTimePasswordRequired.required_header(headers)
19
+ Octokit::OneTimePasswordRequired
20
+ else
21
+ Octokit::Unauthorized
22
+ end
17
23
  when 403
18
24
  if body =~ /rate limit exceeded/i
19
25
  Octokit::TooManyRequests
@@ -100,6 +106,20 @@ module Octokit
100
106
  # Raised when GitHub returns a 401 HTTP status code
101
107
  class Unauthorized < Error; end
102
108
 
109
+ # Raised when GitHub returns a 401 HTTP status code
110
+ # and headers include "X-GitHub-OTP"
111
+ class OneTimePasswordRequired < Error
112
+ HEADER = /required; (?<delivery>\w+)/i
113
+
114
+ def self.required_header(headers)
115
+ HEADER.match headers['X-GitHub-OTP'].to_s
116
+ end
117
+
118
+ def password_delivery
119
+ @password_delivery ||= self.class.required_header(@response[:response_headers])[:delivery]
120
+ end
121
+ end
122
+
103
123
  # Raised when GitHub returns a 403 HTTP status code
104
124
  class Forbidden < Error; end
105
125
 
@@ -2,6 +2,6 @@ module Octokit
2
2
 
3
3
  # Current version
4
4
  # @return [String]
5
- VERSION = "2.0.0".freeze
5
+ VERSION = "2.1.0".freeze
6
6
 
7
7
  end
@@ -0,0 +1 @@
1
+ {"http_interactions":[{"request":{"method":"put","uri":"https://<GITHUB_LOGIN>:<GITHUB_PASSWORD>@api.github.com/authorizations/clients/<GITHUB_CLIENT_ID>","body":{"encoding":"UTF-8","base64_string":"eyJjbGllbnRfaWQiOiI8R0lUSFVCX0NMSUVOVF9JRD4iLCJjbGllbnRfc2Vj\ncmV0IjoiPEdJVEhVQl9DTElFTlRfU0VDUkVUPiIsInNjb3BlcyI6WyJnaXN0\nIl19\n"},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0"]}},"response":{"status":{"code":201,"message":"Created"},"headers":{"Server":["GitHub.com"],"Date":["Thu, 29 Aug 2013 19:36:24 GMT"],"Content-Type":["application/json; charset=utf-8"],"Status":["201 Created"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4991"],"X-Ratelimit-Reset":["1377808149"],"Location":["https://api.github.com/authorizations/3459491"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Content-Length":["336"],"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":["*"],"Etag":["\"123a8d275487ebdcf01ff056e6a7e178\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Github-Request-Id":["0eac2c58-1f44-4ee3-b754-12409a31bad8"]},"body":{"encoding":"US-ASCII","base64_string":"eyJpZCI6MzQ1OTQ5MSwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9h\ndXRob3JpemF0aW9ucy8zNDU5NDkxIiwiYXBwIjp7Im5hbWUiOiJPY3Rva2l0\nIERldmVsb3BtZW50IiwidXJsIjoiaHR0cDovL29jdG9raXQuZGV2IiwiY2xp\nZW50X2lkIjoiPEdJVEhVQl9DTElFTlRfSUQ+In0sInRva2VuIjoiNjFhZmNi\nZDM0YmY2NTIyOWU5MDJiMTBiNGYwNGE0NWRkMGJhODI1NSIsIm5vdGUiOm51\nbGwsIm5vdGVfdXJsIjpudWxsLCJjcmVhdGVkX2F0IjoiMjAxMy0wOC0yOVQx\nOTozNjoyNFoiLCJ1cGRhdGVkX2F0IjoiMjAxMy0wOC0yOVQxOTozNjoyNFoi\nLCJzY29wZXMiOlsiZ2lzdCJdfQ==\n"},"http_version":null},"recorded_at":"Thu, 29 Aug 2013 19:36:24 GMT"}],"recorded_with":"VCR 2.4.0"}
@@ -0,0 +1 @@
1
+ {"http_interactions":[{"request":{"method":"put","uri":"https://<GITHUB_LOGIN>:<GITHUB_PASSWORD>@api.github.com/authorizations/clients/<GITHUB_CLIENT_ID>","body":{"encoding":"UTF-8","base64_string":"eyJjbGllbnRfaWQiOiI8R0lUSFVCX0NMSUVOVF9JRD4iLCJjbGllbnRfc2Vj\ncmV0IjoiPEdJVEhVQl9DTElFTlRfU0VDUkVUPiJ9\n"},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Server":["GitHub.com"],"Date":["Thu, 29 Aug 2013 19:36:24 GMT"],"Content-Type":["application/json; charset=utf-8"],"Status":["200 OK"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4990"],"X-Ratelimit-Reset":["1377808149"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Content-Length":["336"],"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":["*"],"Etag":["\"123a8d275487ebdcf01ff056e6a7e178\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Github-Request-Id":["9e4afd9a-8f33-47a8-a0cd-8601d1779724"],"Vary":["Accept-Encoding"]},"body":{"encoding":"US-ASCII","base64_string":"eyJpZCI6MzQ1OTQ5MSwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9h\ndXRob3JpemF0aW9ucy8zNDU5NDkxIiwiYXBwIjp7Im5hbWUiOiJPY3Rva2l0\nIERldmVsb3BtZW50IiwidXJsIjoiaHR0cDovL29jdG9raXQuZGV2IiwiY2xp\nZW50X2lkIjoiPEdJVEhVQl9DTElFTlRfSUQ+In0sInRva2VuIjoiNjFhZmNi\nZDM0YmY2NTIyOWU5MDJiMTBiNGYwNGE0NWRkMGJhODI1NSIsIm5vdGUiOm51\nbGwsIm5vdGVfdXJsIjpudWxsLCJjcmVhdGVkX2F0IjoiMjAxMy0wOC0yOVQx\nOTozNjoyNFoiLCJ1cGRhdGVkX2F0IjoiMjAxMy0wOC0yOVQxOTozNjoyNFoi\nLCJzY29wZXMiOlsiZ2lzdCJdfQ==\n"},"http_version":null},"recorded_at":"Thu, 29 Aug 2013 19:36:24 GMT"},{"request":{"method":"put","uri":"https://<GITHUB_LOGIN>:<GITHUB_PASSWORD>@api.github.com/authorizations/clients/<GITHUB_CLIENT_ID>","body":{"encoding":"UTF-8","base64_string":"eyJjbGllbnRfaWQiOiI8R0lUSFVCX0NMSUVOVF9JRD4iLCJjbGllbnRfc2Vj\ncmV0IjoiPEdJVEhVQl9DTElFTlRfU0VDUkVUPiJ9\n"},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0"]}},"response":{"status":{"code":200,"message":"OK"},"headers":{"Server":["GitHub.com"],"Date":["Thu, 29 Aug 2013 19:36:24 GMT"],"Content-Type":["application/json; charset=utf-8"],"Status":["200 OK"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4989"],"X-Ratelimit-Reset":["1377808149"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Content-Length":["336"],"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":["*"],"Etag":["\"123a8d275487ebdcf01ff056e6a7e178\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Github-Request-Id":["0132512e-b1c7-41f0-b662-e3cdb67abd92"],"Vary":["Accept-Encoding"]},"body":{"encoding":"US-ASCII","base64_string":"eyJpZCI6MzQ1OTQ5MSwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9h\ndXRob3JpemF0aW9ucy8zNDU5NDkxIiwiYXBwIjp7Im5hbWUiOiJPY3Rva2l0\nIERldmVsb3BtZW50IiwidXJsIjoiaHR0cDovL29jdG9raXQuZGV2IiwiY2xp\nZW50X2lkIjoiPEdJVEhVQl9DTElFTlRfSUQ+In0sInRva2VuIjoiNjFhZmNi\nZDM0YmY2NTIyOWU5MDJiMTBiNGYwNGE0NWRkMGJhODI1NSIsIm5vdGUiOm51\nbGwsIm5vdGVfdXJsIjpudWxsLCJjcmVhdGVkX2F0IjoiMjAxMy0wOC0yOVQx\nOTozNjoyNFoiLCJ1cGRhdGVkX2F0IjoiMjAxMy0wOC0yOVQxOTozNjoyNFoi\nLCJzY29wZXMiOlsiZ2lzdCJdfQ==\n"},"http_version":null},"recorded_at":"Thu, 29 Aug 2013 19:36:24 GMT"}],"recorded_with":"VCR 2.4.0"}
@@ -0,0 +1 @@
1
+ {"http_interactions":[{"request":{"method":"post","uri":"https://<GITHUB_LOGIN>:<GITHUB_PASSWORD>@api.github.com/authorizations","body":{"encoding":"UTF-8","base64_string":"e30=\n"},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0"]}},"response":{"status":{"code":201,"message":"Created"},"headers":{"Server":["GitHub.com"],"Date":["Thu, 29 Aug 2013 19:36:23 GMT"],"Content-Type":["application/json; charset=utf-8"],"Status":["201 Created"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4993"],"X-Ratelimit-Reset":["1377808149"],"Location":["https://api.github.com/authorizations/3459488"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Content-Length":["365"],"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":["*"],"Etag":["\"1444ccd8d64cae453cd92448ff936d37\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Github-Request-Id":["a27f469a-1e1c-49ab-9080-829e48d3430f"]},"body":{"encoding":"US-ASCII","base64_string":"eyJpZCI6MzQ1OTQ4OCwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9h\ndXRob3JpemF0aW9ucy8zNDU5NDg4IiwiYXBwIjp7Im5hbWUiOiJHaXRIdWIg\nQVBJIiwidXJsIjoiaHR0cDovL2RldmVsb3Blci5naXRodWIuY29tL3YzL29h\ndXRoLyNvYXV0aC1hdXRob3JpemF0aW9ucy1hcGkiLCJjbGllbnRfaWQiOiIw\nMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LCJ0b2tlbiI6IjdlMTY5NmNlM2JlOGRl\nMGQ1ZTA4ODE1MzgzNTNmMTYzYTZhNjZhMmMiLCJub3RlIjpudWxsLCJub3Rl\nX3VybCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMTMtMDgtMjlUMTk6MzY6MjNa\nIiwidXBkYXRlZF9hdCI6IjIwMTMtMDgtMjlUMTk6MzY6MjNaIiwic2NvcGVz\nIjpbXX0=\n"},"http_version":null},"recorded_at":"Thu, 29 Aug 2013 19:36:23 GMT"},{"request":{"method":"post","uri":"https://<GITHUB_LOGIN>:<GITHUB_PASSWORD>@api.github.com/authorizations","body":{"encoding":"UTF-8","base64_string":"e30=\n"},"headers":{"Accept":["application/vnd.github.beta+json"],"User-Agent":["Octokit Ruby Gem 2.0.0"]}},"response":{"status":{"code":201,"message":"Created"},"headers":{"Server":["GitHub.com"],"Date":["Thu, 29 Aug 2013 19:36:23 GMT"],"Content-Type":["application/json; charset=utf-8"],"Status":["201 Created"],"X-Ratelimit-Limit":["5000"],"X-Ratelimit-Remaining":["4992"],"X-Ratelimit-Reset":["1377808149"],"Location":["https://api.github.com/authorizations/3459490"],"X-Github-Media-Type":["github.beta; format=json"],"X-Content-Type-Options":["nosniff"],"Content-Length":["365"],"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":["*"],"Etag":["\"39f0b27375ce598f0cf13230ea91b0d9\""],"Cache-Control":["max-age=0, private, must-revalidate"],"X-Github-Request-Id":["2574b4a5-0b8a-46eb-8571-8858eb8a1b0c"]},"body":{"encoding":"US-ASCII","base64_string":"eyJpZCI6MzQ1OTQ5MCwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9h\ndXRob3JpemF0aW9ucy8zNDU5NDkwIiwiYXBwIjp7Im5hbWUiOiJHaXRIdWIg\nQVBJIiwidXJsIjoiaHR0cDovL2RldmVsb3Blci5naXRodWIuY29tL3YzL29h\ndXRoLyNvYXV0aC1hdXRob3JpemF0aW9ucy1hcGkiLCJjbGllbnRfaWQiOiIw\nMDAwMDAwMDAwMDAwMDAwMDAwMCJ9LCJ0b2tlbiI6IjM0ZjRlM2M3N2VjZTZm\nMzFmOTY5MTNhMzRlNDMyYTE2NWI4Yjg1OWUiLCJub3RlIjpudWxsLCJub3Rl\nX3VybCI6bnVsbCwiY3JlYXRlZF9hdCI6IjIwMTMtMDgtMjlUMTk6MzY6MjNa\nIiwidXBkYXRlZF9hdCI6IjIwMTMtMDgtMjlUMTk6MzY6MjNaIiwic2NvcGVz\nIjpbXX0=\n"},"http_version":null},"recorded_at":"Thu, 29 Aug 2013 19:36:23 GMT"}],"recorded_with":"VCR 2.4.0"}
@@ -30,6 +30,12 @@ VCR.configure do |c|
30
30
  c.filter_sensitive_data("<<ACCESS_TOKEN>>") do
31
31
  ENV['OCTOKIT_TEST_GITHUB_TOKEN']
32
32
  end
33
+ c.filter_sensitive_data("<GITHUB_CLIENT_ID>") do
34
+ ENV['OCTOKIT_TEST_GITHUB_CLIENT_ID']
35
+ end
36
+ c.filter_sensitive_data("<GITHUB_CLIENT_SECRET>") do
37
+ ENV['OCTOKIT_TEST_GITHUB_CLIENT_SECRET']
38
+ end
33
39
  c.default_cassette_options = {
34
40
  :serialize_with => :json,
35
41
  # TODO: Track down UTF-8 issue and remove
@@ -53,6 +59,14 @@ def test_github_token
53
59
  ENV.fetch 'OCTOKIT_TEST_GITHUB_TOKEN'
54
60
  end
55
61
 
62
+ def test_github_client_id
63
+ ENV.fetch 'OCTOKIT_TEST_GITHUB_CLIENT_ID'
64
+ end
65
+
66
+ def test_github_client_secret
67
+ ENV.fetch 'OCTOKIT_TEST_GITHUB_CLIENT_SECRET'
68
+ end
69
+
56
70
  def stub_delete(url)
57
71
  stub_request(:delete, github_url(url))
58
72
  end
@@ -8,18 +8,54 @@ describe Octokit::Client::Authorizations do
8
8
  end
9
9
 
10
10
  describe ".create_authorization", :vcr do
11
- it "creates an API authorization" do
12
- authorization = @client.create_authorization
13
- expect(authorization.app.name).to_not be_nil
14
- assert_requested :post, basic_github_url("/authorizations")
11
+ context 'without :idempotent => true' do
12
+ it "creates an API authorization" do
13
+ authorization = @client.create_authorization
14
+ expect(authorization.app.name).to_not be_nil
15
+ assert_requested :post, basic_github_url("/authorizations")
16
+ end
17
+
18
+ it "creates a new API authorization each time" do
19
+ first_authorization = @client.create_authorization
20
+ second_authorization = @client.create_authorization
21
+ expect(first_authorization.id).to_not eq second_authorization.id
22
+ end
23
+
24
+ it "creates a new authorization with options" do
25
+ info = {
26
+ :scopes => ["gist"],
27
+ }
28
+ authorization = @client.create_authorization info
29
+ expect(authorization.scopes).to be_kind_of Array
30
+ assert_requested :post, basic_github_url("/authorizations")
31
+ end
15
32
  end
16
- it "creates a new authorization with options" do
17
- info = {
18
- :scopes => ["gist"],
19
- }
20
- authorization = @client.create_authorization info
21
- expect(authorization.scopes).to be_kind_of Array
22
- assert_requested :post, basic_github_url("/authorizations")
33
+
34
+ context 'with :idempotent => true' do
35
+ subject do
36
+ lambda do |info = {}|
37
+ @client.create_authorization({
38
+ :idempotent => true,
39
+ :client_id => test_github_client_id,
40
+ :client_secret => test_github_client_secret
41
+ }.merge(info))
42
+ end
43
+ end
44
+
45
+ it "creates a new authorization with options" do
46
+ info = {
47
+ :scopes => ["gist"],
48
+ }
49
+ authorization = subject.call info
50
+ expect(authorization.scopes).to be_kind_of Array
51
+ assert_requested :put, basic_github_url("/authorizations/clients/#{test_github_client_id}")
52
+ end
53
+
54
+ it 'returns an existing API authorization if one already exists' do
55
+ first_authorization = subject.call
56
+ second_authorization = subject.call
57
+ expect(first_authorization.id).to eql second_authorization.id
58
+ end
23
59
  end
24
60
  end # .create_authorization
25
61
 
@@ -198,14 +198,17 @@ describe Octokit::Client::Users do
198
198
  end
199
199
  end # .subscriptions
200
200
 
201
- describe '.access_token' do
201
+ describe '.exchange_code_for_token' do
202
202
  it 'returns the access_token' do
203
203
  VCR.turn_off!
204
- stub_post("https://github.com/login/oauth/access_token").
205
- to_return(json_response("web_flow_token.json"))
204
+ post = stub_post("https://github.com/login/oauth/access_token").
205
+ with(:headers => {
206
+ :accept => "application/json",
207
+ :content_type => "application/json"
208
+ }).to_return(json_response("web_flow_token.json"))
206
209
  response = Octokit.exchange_code_for_token('code', 'id_here', 'secret_here')
207
210
  expect(response.access_token).to eq 'this_be_ye_token/use_it_wisely'
208
- assert_requested :post, "https://github.com/login/oauth/access_token"
211
+ assert_requested post
209
212
  VCR.turn_on!
210
213
  end
211
214
  end # .access_token
@@ -480,4 +480,34 @@ describe Octokit::Client do
480
480
  end
481
481
  end
482
482
 
483
+ it "knows the difference between unauthorized and needs OTP" do
484
+ stub_get('/authorizations').to_return(:status => 401)
485
+ expect { Octokit.get('/authorizations') }.to raise_error Octokit::Unauthorized
486
+
487
+ stub_get('/authorizations/1').to_return \
488
+ :status => 401,
489
+ :headers => {
490
+ :content_type => "application/json",
491
+ "X-GitHub-OTP" => "required; sms"
492
+ },
493
+ :body => {:message => "Must specify two-factor authentication OTP code."}.to_json
494
+ expect { Octokit.get('/authorizations/1') }.to raise_error Octokit::OneTimePasswordRequired
495
+ end
496
+
497
+ it "knows the password delivery mechanism when needs OTP" do
498
+ stub_get('/authorizations/1').to_return \
499
+ :status => 401,
500
+ :headers => {
501
+ :content_type => "application/json",
502
+ "X-GitHub-OTP" => "required; app"
503
+ },
504
+ :body => {:message => "Must specify two-factor authentication OTP code."}.to_json
505
+
506
+ begin
507
+ Octokit.get('/authorizations/1')
508
+ rescue Octokit::OneTimePasswordRequired => otp_error
509
+ expect(otp_error.password_delivery).to eql 'app'
510
+ end
511
+ end
512
+
483
513
  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
4
+ version: 2.1.0
5
5
  prerelease:
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-08-23 00:00:00.000000000 Z
14
+ date: 2013-09-03 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -111,8 +111,11 @@ files:
111
111
  - spec/cassettes/Octokit_Client/error_handling/raises_on_404.json
112
112
  - spec/cassettes/Octokit_Client_Authorizations/_authorization/returns_a_single_authorization.json
113
113
  - spec/cassettes/Octokit_Client_Authorizations/_authorizations/lists_existing_authorizations.json
114
- - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/creates_a_new_authorization_with_options.json
115
- - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/creates_an_API_authorization.json
114
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/with_idempotent_true/creates_a_new_authorization_with_options.json
115
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/with_idempotent_true/returns_an_existing_API_authorization_if_one_already_exists.json
116
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_a_new_API_authorization_each_time.json
117
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_a_new_authorization_with_options.json
118
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_an_API_authorization.json
116
119
  - spec/cassettes/Octokit_Client_Authorizations/_scopes/checks_the_scopes_on_a_one-off_token.json
117
120
  - spec/cassettes/Octokit_Client_Authorizations/_scopes/checks_the_scopes_on_the_current_token.json
118
121
  - spec/cassettes/Octokit_Client_Authorizations/_update_authorization/updates_and_existing_authorization.json
@@ -455,8 +458,11 @@ test_files:
455
458
  - spec/cassettes/Octokit_Client/error_handling/raises_on_404.json
456
459
  - spec/cassettes/Octokit_Client_Authorizations/_authorization/returns_a_single_authorization.json
457
460
  - spec/cassettes/Octokit_Client_Authorizations/_authorizations/lists_existing_authorizations.json
458
- - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/creates_a_new_authorization_with_options.json
459
- - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/creates_an_API_authorization.json
461
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/with_idempotent_true/creates_a_new_authorization_with_options.json
462
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/with_idempotent_true/returns_an_existing_API_authorization_if_one_already_exists.json
463
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_a_new_API_authorization_each_time.json
464
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_a_new_authorization_with_options.json
465
+ - spec/cassettes/Octokit_Client_Authorizations/_create_authorization/without_idempotent_true/creates_an_API_authorization.json
460
466
  - spec/cassettes/Octokit_Client_Authorizations/_scopes/checks_the_scopes_on_a_one-off_token.json
461
467
  - spec/cassettes/Octokit_Client_Authorizations/_scopes/checks_the_scopes_on_the_current_token.json
462
468
  - spec/cassettes/Octokit_Client_Authorizations/_update_authorization/updates_and_existing_authorization.json