octokit 2.0.0 → 2.1.0

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
@@ -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