google-api-client 0.4.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +80 -0
- data/README.md +152 -45
- data/Rakefile +2 -2
- data/bin/google-api +2 -9
- data/lib/compat/multi_json.rb +2 -3
- data/lib/google/api_client.rb +87 -278
- data/lib/google/api_client/auth/jwt_asserter.rb +139 -0
- data/lib/google/api_client/auth/pkcs12.rb +48 -0
- data/lib/google/api_client/batch.rb +164 -136
- data/lib/google/api_client/client_secrets.rb +45 -1
- data/lib/google/api_client/discovery/api.rb +7 -8
- data/lib/google/api_client/discovery/method.rb +20 -27
- data/lib/google/api_client/discovery/resource.rb +16 -10
- data/lib/google/api_client/discovery/schema.rb +2 -0
- data/lib/google/api_client/media.rb +76 -64
- data/lib/google/api_client/reference.rb +7 -285
- data/lib/google/api_client/request.rb +336 -0
- data/lib/google/api_client/result.rb +147 -55
- data/lib/google/api_client/service_account.rb +2 -120
- data/lib/google/api_client/version.rb +2 -3
- data/spec/google/api_client/batch_spec.rb +9 -10
- data/spec/google/api_client/discovery_spec.rb +184 -114
- data/spec/google/api_client/media_spec.rb +27 -39
- data/spec/google/api_client/result_spec.rb +30 -11
- data/spec/google/api_client/service_account_spec.rb +38 -6
- data/spec/google/api_client_spec.rb +48 -32
- data/spec/spec_helper.rb +46 -0
- data/tasks/gem.rake +1 -0
- metadata +36 -70
@@ -71,68 +71,56 @@ describe Google::APIClient::ResumableUpload do
|
|
71
71
|
@file = File.expand_path('files/sample.txt', fixtures_path)
|
72
72
|
@media = Google::APIClient::UploadIO.new(@file, 'text/plain')
|
73
73
|
@uploader = Google::APIClient::ResumableUpload.new(
|
74
|
-
|
75
|
-
@
|
76
|
-
'https://www.googleapis.com/upload/drive/v1/files/12345')
|
74
|
+
:media => @media,
|
75
|
+
:api_method => @drive.files.insert,
|
76
|
+
:uri => 'https://www.googleapis.com/upload/drive/v1/files/12345')
|
77
77
|
end
|
78
78
|
|
79
79
|
it 'should consider 20x status as complete' do
|
80
|
-
|
81
|
-
@uploader.
|
80
|
+
request = @uploader.to_http_request
|
81
|
+
@uploader.process_http_response(mock_result(200))
|
82
82
|
@uploader.complete?.should == true
|
83
83
|
end
|
84
84
|
|
85
85
|
it 'should consider 30x status as incomplete' do
|
86
|
-
|
87
|
-
@uploader.
|
86
|
+
request = @uploader.to_http_request
|
87
|
+
@uploader.process_http_response(mock_result(308))
|
88
88
|
@uploader.complete?.should == false
|
89
89
|
@uploader.expired?.should == false
|
90
90
|
end
|
91
91
|
|
92
92
|
it 'should consider 40x status as fatal' do
|
93
|
-
|
94
|
-
@uploader.
|
93
|
+
request = @uploader.to_http_request
|
94
|
+
@uploader.process_http_response(mock_result(404))
|
95
95
|
@uploader.expired?.should == true
|
96
96
|
end
|
97
97
|
|
98
98
|
it 'should detect changes to location' do
|
99
|
-
|
100
|
-
@uploader.
|
101
|
-
@uploader.
|
99
|
+
request = @uploader.to_http_request
|
100
|
+
@uploader.process_http_response(mock_result(308, 'location' => 'https://www.googleapis.com/upload/drive/v1/files/abcdef'))
|
101
|
+
@uploader.uri.to_s.should == 'https://www.googleapis.com/upload/drive/v1/files/abcdef'
|
102
102
|
end
|
103
103
|
|
104
|
-
it 'should resume from the saved range reported by the server' do
|
105
|
-
api_client = mock('api')
|
106
|
-
api_client.should_receive(:execute).and_return(mock_result(308, 'range' => '0-99'))
|
107
|
-
api_client.should_receive(:execute).with(
|
108
|
-
hash_including(:headers => hash_including(
|
109
|
-
"Content-Range" => "bytes 100-299/#{@media.length}",
|
110
|
-
"Content-Length" => "200"
|
111
|
-
))).and_return(mock_result(308))
|
112
|
-
|
104
|
+
it 'should resume from the saved range reported by the server' do
|
113
105
|
@uploader.chunk_size = 200
|
114
|
-
@uploader.
|
115
|
-
@uploader.
|
106
|
+
@uploader.to_http_request # Send bytes 0-199, only 0-99 saved
|
107
|
+
@uploader.process_http_response(mock_result(308, 'range' => '0-99'))
|
108
|
+
method, url, headers, body = @uploader.to_http_request # Send bytes 100-299
|
109
|
+
headers['Content-Range'].should == "bytes 100-299/#{@media.length}"
|
110
|
+
headers['Content-length'].should == "200"
|
116
111
|
end
|
117
112
|
|
118
113
|
it 'should resync the offset after 5xx errors' do
|
119
|
-
api_client = mock('api')
|
120
|
-
api_client.should_receive(:execute).and_return(mock_result(500))
|
121
|
-
api_client.should_receive(:execute).with(
|
122
|
-
hash_including(:headers => hash_including(
|
123
|
-
"Content-Range" => "bytes */#{@media.length}",
|
124
|
-
"Content-Length" => "0"
|
125
|
-
))).and_return(mock_result(308, 'range' => '0-99'))
|
126
|
-
api_client.should_receive(:execute).with(
|
127
|
-
hash_including(:headers => hash_including(
|
128
|
-
"Content-Range" => "bytes 100-299/#{@media.length}",
|
129
|
-
"Content-Length" => "200"
|
130
|
-
))).and_return(mock_result(308))
|
131
|
-
|
132
114
|
@uploader.chunk_size = 200
|
133
|
-
@uploader.
|
134
|
-
@uploader.
|
135
|
-
|
115
|
+
@uploader.to_http_request
|
116
|
+
@uploader.process_http_response(mock_result(500)) # Invalidates range
|
117
|
+
method, url, headers, body = @uploader.to_http_request # Resync
|
118
|
+
headers['Content-Range'].should == "bytes */#{@media.length}"
|
119
|
+
headers['Content-length'].should == "0"
|
120
|
+
@uploader.process_http_response(mock_result(308, 'range' => '0-99'))
|
121
|
+
method, url, headers, body = @uploader.to_http_request # Send next chunk at correct range
|
122
|
+
headers['Content-Range'].should == "bytes 100-299/#{@media.length}"
|
123
|
+
headers['Content-length'].should == "200"
|
136
124
|
end
|
137
125
|
|
138
126
|
def mock_result(status, headers = {})
|
@@ -32,7 +32,7 @@ describe Google::APIClient::Result do
|
|
32
32
|
'maxResults' => 20
|
33
33
|
}
|
34
34
|
})
|
35
|
-
@request = @reference.
|
35
|
+
@request = @reference.to_http_request
|
36
36
|
|
37
37
|
# Response stub
|
38
38
|
@response = stub("response")
|
@@ -61,12 +61,12 @@ describe Google::APIClient::Result do
|
|
61
61
|
"nextLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN",
|
62
62
|
"title": "Plus Public Activity Feed for ",
|
63
63
|
"updated": "2012-04-23T00:00:00.000Z",
|
64
|
-
"id": "
|
64
|
+
"id": "123456790",
|
65
65
|
"items": []
|
66
66
|
}
|
67
67
|
END_OF_STRING
|
68
68
|
)
|
69
|
-
@result = Google::APIClient::Result.new(@reference, @
|
69
|
+
@result = Google::APIClient::Result.new(@reference, @response)
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'should indicate a successful response' do
|
@@ -78,10 +78,11 @@ describe Google::APIClient::Result do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
it 'should escape the next page token when calling next_page' do
|
81
|
+
pending("This is caused by Faraday's encoding of query parameters.")
|
81
82
|
reference = @result.next_page
|
82
83
|
Hash[reference.parameters].should include('pageToken')
|
83
84
|
Hash[reference.parameters]['pageToken'].should == 'NEXT+PAGE+TOKEN'
|
84
|
-
url = reference.
|
85
|
+
url = reference.to_env(Faraday.default_connection)[:url]
|
85
86
|
url.to_s.should include('pageToken=NEXT%2BPAGE%2BTOKEN')
|
86
87
|
end
|
87
88
|
|
@@ -102,8 +103,7 @@ describe Google::APIClient::Result do
|
|
102
103
|
'https://www.googleapis.com/plus/v1/people/foo/activities/public?' +
|
103
104
|
'maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN'
|
104
105
|
@result.data.title.should == 'Plus Public Activity Feed for '
|
105
|
-
@result.data.id.should ==
|
106
|
-
'tag:google.com,2010:/plus/people/foo/activities/public'
|
106
|
+
@result.data.id.should == "123456790"
|
107
107
|
@result.data.items.should be_empty
|
108
108
|
end
|
109
109
|
end
|
@@ -118,12 +118,12 @@ describe Google::APIClient::Result do
|
|
118
118
|
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
|
119
119
|
"title": "Plus Public Activity Feed for ",
|
120
120
|
"updated": "2012-04-23T00:00:00.000Z",
|
121
|
-
"id": "
|
121
|
+
"id": "123456790",
|
122
122
|
"items": []
|
123
123
|
}
|
124
124
|
END_OF_STRING
|
125
125
|
)
|
126
|
-
@result = Google::APIClient::Result.new(@reference, @
|
126
|
+
@result = Google::APIClient::Result.new(@reference, @response)
|
127
127
|
end
|
128
128
|
|
129
129
|
it 'should not return a next page token' do
|
@@ -143,8 +143,7 @@ describe Google::APIClient::Result do
|
|
143
143
|
@result.data.selfLink.should ==
|
144
144
|
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
|
145
145
|
@result.data.title.should == 'Plus Public Activity Feed for '
|
146
|
-
@result.data.id.should ==
|
147
|
-
'tag:google.com,2010:/plus/people/foo/activities/public'
|
146
|
+
@result.data.id.should == "123456790"
|
148
147
|
@result.data.items.should be_empty
|
149
148
|
end
|
150
149
|
end
|
@@ -169,7 +168,7 @@ describe Google::APIClient::Result do
|
|
169
168
|
END_OF_STRING
|
170
169
|
)
|
171
170
|
@response.stub(:status).and_return(400)
|
172
|
-
@result = Google::APIClient::Result.new(@reference, @
|
171
|
+
@result = Google::APIClient::Result.new(@reference, @response)
|
173
172
|
end
|
174
173
|
|
175
174
|
it 'should return error status correctly' do
|
@@ -179,7 +178,27 @@ describe Google::APIClient::Result do
|
|
179
178
|
it 'should return the correct error message' do
|
180
179
|
@result.error_message.should == 'Parse Error'
|
181
180
|
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'with 204 No Content response' do
|
184
|
+
before do
|
185
|
+
@response.stub(:body).and_return('')
|
186
|
+
@response.stub(:status).and_return(204)
|
187
|
+
@response.stub(:headers).and_return({})
|
188
|
+
@result = Google::APIClient::Result.new(@reference, @response)
|
189
|
+
end
|
182
190
|
|
191
|
+
it 'should indicate no data is available' do
|
192
|
+
@result.data?.should be_false
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should return nil for data' do
|
196
|
+
@result.data.should == nil
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should return nil for media_type' do
|
200
|
+
@result.media_type.should == nil
|
201
|
+
end
|
183
202
|
end
|
184
203
|
end
|
185
204
|
end
|
@@ -17,6 +17,7 @@ require 'spec_helper'
|
|
17
17
|
require 'google/api_client'
|
18
18
|
|
19
19
|
describe Google::APIClient::JWTAsserter do
|
20
|
+
include ConnectionHelpers
|
20
21
|
|
21
22
|
before do
|
22
23
|
@key = OpenSSL::PKey::RSA.new 2048
|
@@ -33,7 +34,7 @@ describe Google::APIClient::JWTAsserter do
|
|
33
34
|
end
|
34
35
|
|
35
36
|
it 'should send valid access token request' do
|
36
|
-
|
37
|
+
conn = stub_connection do |stub|
|
37
38
|
stub.post('/o/oauth2/token') do |env|
|
38
39
|
params = Addressable::URI.form_unencode(env[:body])
|
39
40
|
JWT.decode(params.assoc("assertion").last, @key.public_key)
|
@@ -45,14 +46,45 @@ describe Google::APIClient::JWTAsserter do
|
|
45
46
|
}']
|
46
47
|
end
|
47
48
|
end
|
48
|
-
connection = Faraday.new(:url => 'https://accounts.google.com') do |builder|
|
49
|
-
builder.adapter(:test, stubs)
|
50
|
-
end
|
51
|
-
|
52
49
|
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
|
53
|
-
auth = asserter.authorize(nil, { :connection =>
|
50
|
+
auth = asserter.authorize(nil, { :connection => conn })
|
54
51
|
auth.should_not == nil?
|
55
52
|
auth.access_token.should == "1/abcdef1234567890"
|
53
|
+
conn.verify
|
56
54
|
end
|
55
|
+
|
56
|
+
it 'should be refreshable' do
|
57
|
+
conn = stub_connection do |stub|
|
58
|
+
stub.post('/o/oauth2/token') do |env|
|
59
|
+
params = Addressable::URI.form_unencode(env[:body])
|
60
|
+
JWT.decode(params.assoc("assertion").last, @key.public_key)
|
61
|
+
params.assoc("grant_type").should == ['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer']
|
62
|
+
[200, {}, '{
|
63
|
+
"access_token" : "1/abcdef1234567890",
|
64
|
+
"token_type" : "Bearer",
|
65
|
+
"expires_in" : 3600
|
66
|
+
}']
|
67
|
+
end
|
68
|
+
stub.post('/o/oauth2/token') do |env|
|
69
|
+
params = Addressable::URI.form_unencode(env[:body])
|
70
|
+
JWT.decode(params.assoc("assertion").last, @key.public_key)
|
71
|
+
params.assoc("grant_type").should == ['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer']
|
72
|
+
[200, {}, '{
|
73
|
+
"access_token" : "1/0987654321fedcba",
|
74
|
+
"token_type" : "Bearer",
|
75
|
+
"expires_in" : 3600
|
76
|
+
}']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
|
80
|
+
auth = asserter.authorize(nil, { :connection => conn })
|
81
|
+
auth.should_not == nil?
|
82
|
+
auth.access_token.should == "1/abcdef1234567890"
|
83
|
+
|
84
|
+
auth.fetch_access_token!(:connection => conn)
|
85
|
+
auth.access_token.should == "1/0987654321fedcba"
|
86
|
+
|
87
|
+
conn.verify
|
88
|
+
end
|
57
89
|
end
|
58
90
|
|
@@ -14,17 +14,15 @@
|
|
14
14
|
|
15
15
|
require 'spec_helper'
|
16
16
|
|
17
|
-
gem 'faraday', '~> 0.8.1'
|
18
17
|
require 'faraday'
|
19
18
|
require 'faraday/utils'
|
20
|
-
|
21
|
-
gem 'signet', '~> 0.4.0'
|
22
19
|
require 'signet/oauth_1/client'
|
23
|
-
|
24
20
|
require 'google/api_client'
|
25
21
|
require 'google/api_client/version'
|
26
22
|
|
27
23
|
shared_examples_for 'configurable user agent' do
|
24
|
+
include ConnectionHelpers
|
25
|
+
|
28
26
|
it 'should allow the user agent to be modified' do
|
29
27
|
client.user_agent = 'Custom User Agent/1.2.3'
|
30
28
|
client.user_agent.should == 'Custom User Agent/1.2.3'
|
@@ -38,18 +36,14 @@ shared_examples_for 'configurable user agent' do
|
|
38
36
|
it 'should not allow the user agent to be used with bogus values' do
|
39
37
|
(lambda do
|
40
38
|
client.user_agent = 42
|
41
|
-
client.
|
42
|
-
['GET', 'http://www.google.com/', [], []]
|
43
|
-
)
|
39
|
+
client.execute(:uri=>'http://www.google.com/')
|
44
40
|
end).should raise_error(TypeError)
|
45
41
|
end
|
46
42
|
|
47
43
|
it 'should transmit a User-Agent header when sending requests' do
|
48
44
|
client.user_agent = 'Custom User Agent/1.2.3'
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
45
|
+
|
46
|
+
conn = stub_connection do |stub|
|
53
47
|
stub.get('/') do |env|
|
54
48
|
headers = env[:request_headers]
|
55
49
|
headers.should have_key('User-Agent')
|
@@ -57,15 +51,14 @@ shared_examples_for 'configurable user agent' do
|
|
57
51
|
[200, {}, ['']]
|
58
52
|
end
|
59
53
|
end
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
client.transmit(:request => request, :connection => connection)
|
64
|
-
stubs.verify_stubbed_calls
|
54
|
+
client.execute(:uri=>'http://www.google.com/', :connection => conn)
|
55
|
+
conn.verify
|
65
56
|
end
|
66
57
|
end
|
67
58
|
|
68
59
|
describe Google::APIClient do
|
60
|
+
include ConnectionHelpers
|
61
|
+
|
69
62
|
let(:client) { Google::APIClient.new }
|
70
63
|
|
71
64
|
it 'should make its version number available' do
|
@@ -76,11 +69,18 @@ describe Google::APIClient do
|
|
76
69
|
Signet::OAuth2::Client.should === client.authorization
|
77
70
|
end
|
78
71
|
|
79
|
-
|
80
|
-
|
72
|
+
describe 'configure for no authentication' do
|
73
|
+
before do
|
74
|
+
client.authorization = nil
|
75
|
+
end
|
76
|
+
it_should_behave_like 'configurable user agent'
|
77
|
+
end
|
78
|
+
|
81
79
|
describe 'configured for OAuth 1' do
|
82
80
|
before do
|
83
81
|
client.authorization = :oauth_1
|
82
|
+
client.authorization.token_credential_key = 'abc'
|
83
|
+
client.authorization.token_credential_secret = '123'
|
84
84
|
end
|
85
85
|
|
86
86
|
it 'should use the default OAuth1 client configuration' do
|
@@ -101,6 +101,7 @@ describe Google::APIClient do
|
|
101
101
|
describe 'configured for OAuth 2' do
|
102
102
|
before do
|
103
103
|
client.authorization = :oauth_2
|
104
|
+
client.authorization.access_token = '12345'
|
104
105
|
end
|
105
106
|
|
106
107
|
# TODO
|
@@ -109,31 +110,46 @@ describe Google::APIClient do
|
|
109
110
|
|
110
111
|
describe 'when executing requests' do
|
111
112
|
before do
|
113
|
+
@prediction = client.discovered_api('prediction', 'v1.2')
|
112
114
|
client.authorization = :oauth_2
|
113
|
-
@connection =
|
114
|
-
|
115
|
-
|
116
|
-
env[:request_headers]['Authorization'].should == 'Bearer 12345'
|
117
|
-
end
|
115
|
+
@connection = stub_connection do |stub|
|
116
|
+
stub.post('/prediction/v1.2/training?data=12345') do |env|
|
117
|
+
env[:request_headers]['Authorization'].should == 'Bearer 12345'
|
118
118
|
end
|
119
|
-
builder.adapter(:test, stubs)
|
120
119
|
end
|
121
120
|
end
|
122
|
-
|
121
|
+
|
122
|
+
after do
|
123
|
+
@connection.verify
|
124
|
+
end
|
125
|
+
|
123
126
|
it 'should use default authorization' do
|
124
127
|
client.authorization.access_token = "12345"
|
125
|
-
client.execute(
|
126
|
-
|
127
|
-
|
128
|
+
client.execute(
|
129
|
+
:api_method => @prediction.training.insert,
|
130
|
+
:parameters => {'data' => '12345'},
|
131
|
+
:connection => @connection
|
132
|
+
)
|
128
133
|
end
|
129
134
|
|
130
135
|
it 'should use request scoped authorization when provided' do
|
131
136
|
client.authorization.access_token = "abcdef"
|
132
137
|
new_auth = Signet::OAuth2::Client.new(:access_token => '12345')
|
133
|
-
client.execute(
|
134
|
-
|
135
|
-
|
136
|
-
|
138
|
+
client.execute(
|
139
|
+
:api_method => @prediction.training.insert,
|
140
|
+
:parameters => {'data' => '12345'},
|
141
|
+
:authorization => new_auth,
|
142
|
+
:connection => @connection
|
143
|
+
)
|
137
144
|
end
|
145
|
+
|
146
|
+
it 'should accept options in array style execute' do
|
147
|
+
client.authorization.access_token = "abcdef"
|
148
|
+
new_auth = Signet::OAuth2::Client.new(:access_token => '12345')
|
149
|
+
client.execute(
|
150
|
+
@prediction.training.insert, {'data' => '12345'}, '', {},
|
151
|
+
{ :authorization => new_auth, :connection => @connection }
|
152
|
+
)
|
153
|
+
end
|
138
154
|
end
|
139
155
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,52 @@ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
|
|
2
2
|
$LOAD_PATH.uniq!
|
3
3
|
|
4
4
|
require 'rspec'
|
5
|
+
require 'faraday'
|
6
|
+
|
7
|
+
module Faraday
|
8
|
+
class Connection
|
9
|
+
def verify
|
10
|
+
if app.kind_of?(Faraday::Adapter::Test)
|
11
|
+
app.stubs.verify_stubbed_calls
|
12
|
+
else
|
13
|
+
raise TypeError, "Expected test adapter"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ConnectionHelpers
|
20
|
+
def stub_connection(&block)
|
21
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
22
|
+
block.call(stub)
|
23
|
+
end
|
24
|
+
connection = Faraday.new do |builder|
|
25
|
+
builder.adapter(:test, stubs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module JSONMatchers
|
31
|
+
class EqualsJson
|
32
|
+
def initialize(expected)
|
33
|
+
@expected = JSON.parse(expected)
|
34
|
+
end
|
35
|
+
def matches?(target)
|
36
|
+
@target = JSON.parse(target)
|
37
|
+
@target.eql?(@expected)
|
38
|
+
end
|
39
|
+
def failure_message
|
40
|
+
"expected #{@target.inspect} to be #{@expected}"
|
41
|
+
end
|
42
|
+
def negative_failure_message
|
43
|
+
"expected #{@target.inspect} not to be #{@expected}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def be_json(expected)
|
48
|
+
EqualsJson.new(expected)
|
49
|
+
end
|
50
|
+
end
|
5
51
|
|
6
52
|
RSpec.configure do |config|
|
7
53
|
end
|