google-api-client 0.4.7 → 0.5.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/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
|