google-api-client 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6 -0
- data/Gemfile +28 -0
- data/Gemfile.lock +61 -0
- data/README.md +3 -0
- data/Rakefile +1 -1
- data/lib/google/api_client.rb +93 -40
- data/lib/google/api_client/batch.rb +296 -0
- data/lib/google/api_client/client_secrets.rb +2 -2
- data/lib/google/api_client/discovery/api.rb +15 -0
- data/lib/google/api_client/discovery/method.rb +40 -16
- data/lib/google/api_client/errors.rb +4 -0
- data/lib/google/api_client/reference.rb +12 -2
- data/lib/google/api_client/result.rb +1 -1
- data/lib/google/api_client/service_account.rb +134 -0
- data/lib/google/api_client/version.rb +1 -1
- data/spec/fixtures/files/sample.txt +33 -0
- data/spec/google/api_client/batch_spec.rb +237 -0
- data/spec/google/api_client/discovery_spec.rb +189 -1
- data/spec/google/api_client/media_spec.rb +136 -0
- data/spec/google/api_client/result_spec.rb +150 -0
- data/spec/google/api_client/service_account_spec.rb +58 -0
- data/spec/google/api_client_spec.rb +30 -0
- data/tasks/gem.rake +3 -3
- data/tasks/spec.rake +3 -3
- data/tasks/wiki.rake +1 -1
- metadata +122 -117
- data/tasks/rdoc.rake +0 -30
@@ -39,7 +39,7 @@ module Google
|
|
39
39
|
search_path = File.expand_path(File.join(search_path, '..'))
|
40
40
|
end
|
41
41
|
end
|
42
|
-
data = File.open(filename, 'r') { |file| MultiJson.
|
42
|
+
data = File.open(filename, 'r') { |file| MultiJson.load(file.read) }
|
43
43
|
return self.new(data)
|
44
44
|
end
|
45
45
|
|
@@ -77,7 +77,7 @@ module Google
|
|
77
77
|
)
|
78
78
|
|
79
79
|
def to_json
|
80
|
-
return MultiJson.
|
80
|
+
return MultiJson.dump({
|
81
81
|
self.flow => ({
|
82
82
|
'client_id' => self.client_id,
|
83
83
|
'client_secret' => self.client_secret,
|
@@ -171,6 +171,21 @@ module Google
|
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
|
+
##
|
175
|
+
# Returns the base URI for batch calls to this service.
|
176
|
+
#
|
177
|
+
# @return [Addressable::URI] The base URI that methods are joined to.
|
178
|
+
def batch_path
|
179
|
+
if @discovery_document['batchPath']
|
180
|
+
return @batch_path ||= (
|
181
|
+
self.document_base.join(Addressable::URI.parse('/' +
|
182
|
+
@discovery_document['batchPath']))
|
183
|
+
).normalize
|
184
|
+
else
|
185
|
+
return nil
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
174
189
|
##
|
175
190
|
# A list of schemas available for this version of the API.
|
176
191
|
#
|
@@ -28,6 +28,8 @@ module Google
|
|
28
28
|
##
|
29
29
|
# Creates a description of a particular method.
|
30
30
|
#
|
31
|
+
# @param [Google::APIClient::API] api
|
32
|
+
# The API this method belongs to.
|
31
33
|
# @param [Addressable::URI] method_base
|
32
34
|
# The base URI for the service.
|
33
35
|
# @param [String] method_name
|
@@ -43,6 +45,12 @@ module Google
|
|
43
45
|
@discovery_document = discovery_document
|
44
46
|
end
|
45
47
|
|
48
|
+
##
|
49
|
+
# Returns the API this method belongs to.
|
50
|
+
#
|
51
|
+
# @return [Google::APIClient::API] The API this method belongs to.
|
52
|
+
attr_reader :api
|
53
|
+
|
46
54
|
##
|
47
55
|
# Returns the identifier for the method.
|
48
56
|
#
|
@@ -111,7 +119,7 @@ module Google
|
|
111
119
|
return nil
|
112
120
|
end
|
113
121
|
end
|
114
|
-
|
122
|
+
|
115
123
|
##
|
116
124
|
# Returns the Schema object for the method's request, if any.
|
117
125
|
#
|
@@ -183,7 +191,7 @@ module Google
|
|
183
191
|
end
|
184
192
|
case upload_type.last
|
185
193
|
when 'media', 'multipart', 'resumable'
|
186
|
-
uri = self.media_upload.uri_template.expand(parameters)
|
194
|
+
uri = self.media_upload.uri_template.expand(parameters)
|
187
195
|
else
|
188
196
|
raise ArgumentException, "Invalid uploadType '#{upload_type}'"
|
189
197
|
end
|
@@ -232,7 +240,7 @@ module Google
|
|
232
240
|
req.body = body
|
233
241
|
end
|
234
242
|
end
|
235
|
-
|
243
|
+
|
236
244
|
|
237
245
|
##
|
238
246
|
# Returns a <code>Hash</code> of the parameter descriptions for
|
@@ -304,20 +312,36 @@ module Google
|
|
304
312
|
"Missing required parameters: #{missing_variables.join(', ')}."
|
305
313
|
end
|
306
314
|
parameters.each do |k, v|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
if
|
316
|
-
|
317
|
-
|
315
|
+
# Handle repeated parameters.
|
316
|
+
if self.parameter_descriptions[k] &&
|
317
|
+
self.parameter_descriptions[k]['repeated'] &&
|
318
|
+
v.kind_of?(Array)
|
319
|
+
# If this is a repeated parameter and we've got an array as a
|
320
|
+
# value, just provide the whole array to the loop below.
|
321
|
+
items = v
|
322
|
+
else
|
323
|
+
# If this is not a repeated parameter, or if it is but we're
|
324
|
+
# being given a single value, wrap the value in an array, so that
|
325
|
+
# the loop below still works for the single element.
|
326
|
+
items = [v]
|
327
|
+
end
|
328
|
+
|
329
|
+
items.each do |item|
|
330
|
+
if self.parameter_descriptions[k]
|
331
|
+
enum = self.parameter_descriptions[k]['enum']
|
332
|
+
if enum && !enum.include?(item)
|
318
333
|
raise ArgumentError,
|
319
|
-
"Parameter '#{k}' has an invalid value: #{
|
320
|
-
"Must
|
334
|
+
"Parameter '#{k}' has an invalid value: #{item}. " +
|
335
|
+
"Must be one of #{enum.inspect}."
|
336
|
+
end
|
337
|
+
pattern = self.parameter_descriptions[k]['pattern']
|
338
|
+
if pattern
|
339
|
+
regexp = Regexp.new("^#{pattern}$")
|
340
|
+
if item !~ regexp
|
341
|
+
raise ArgumentError,
|
342
|
+
"Parameter '#{k}' has an invalid value: #{item}. " +
|
343
|
+
"Must match: /^#{pattern}$/."
|
344
|
+
end
|
321
345
|
end
|
322
346
|
end
|
323
347
|
end
|
@@ -21,6 +21,7 @@ require 'addressable/uri'
|
|
21
21
|
require 'stringio'
|
22
22
|
require 'google/api_client/discovery'
|
23
23
|
|
24
|
+
# TODO - needs some serious cleanup
|
24
25
|
|
25
26
|
module Google
|
26
27
|
class APIClient
|
@@ -35,6 +36,7 @@ module Google
|
|
35
36
|
@version = options[:version] || 'v1'
|
36
37
|
|
37
38
|
self.connection = options[:connection] || Faraday.default_connection
|
39
|
+
self.authorization = options[:authorization]
|
38
40
|
self.api_method = options[:api_method]
|
39
41
|
self.parameters = options[:parameters] || {}
|
40
42
|
# These parameters are handled differently because they're not
|
@@ -42,7 +44,6 @@ module Google
|
|
42
44
|
self.parameters['key'] ||= options[:key] if options[:key]
|
43
45
|
self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
|
44
46
|
self.headers = options[:headers] || {}
|
45
|
-
|
46
47
|
if options[:media]
|
47
48
|
self.media = options[:media]
|
48
49
|
upload_type = parameters['uploadType'] || parameters['upload_type']
|
@@ -103,7 +104,7 @@ module Google
|
|
103
104
|
|
104
105
|
def serialize_body(body)
|
105
106
|
return body.to_json if body.respond_to?(:to_json)
|
106
|
-
return MultiJson.
|
107
|
+
return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
|
107
108
|
raise TypeError, 'Could not convert body object to JSON.' +
|
108
109
|
'Must respond to :to_json or :to_hash.'
|
109
110
|
end
|
@@ -116,6 +117,14 @@ module Google
|
|
116
117
|
@media = (media)
|
117
118
|
end
|
118
119
|
|
120
|
+
def authorization
|
121
|
+
return @authorization
|
122
|
+
end
|
123
|
+
|
124
|
+
def authorization=(new_authorization)
|
125
|
+
@authorization = new_authorization
|
126
|
+
end
|
127
|
+
|
119
128
|
def connection
|
120
129
|
return @connection
|
121
130
|
end
|
@@ -257,6 +266,7 @@ module Google
|
|
257
266
|
options[:headers] = self.headers
|
258
267
|
options[:body] = self.body
|
259
268
|
options[:connection] = self.connection
|
269
|
+
options[:authorization] = self.authorization unless self.authorization.nil?
|
260
270
|
return options
|
261
271
|
end
|
262
272
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright 2010 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'jwt'
|
16
|
+
require 'signet/oauth_2/client'
|
17
|
+
|
18
|
+
module Google
|
19
|
+
class APIClient
|
20
|
+
##
|
21
|
+
# Helper for loading keys from the PKCS12 files downloaded when
|
22
|
+
# setting up service accounts at the APIs Console.
|
23
|
+
module PKCS12
|
24
|
+
|
25
|
+
##
|
26
|
+
# Loads a key from PKCS12 file, assuming a single private key
|
27
|
+
# is present.
|
28
|
+
#
|
29
|
+
# @param [String] keyfile
|
30
|
+
# Path of the PKCS12 file to load. If not a path to an actual file,
|
31
|
+
# assumes the string is the content of the file itself.
|
32
|
+
# @param [String] passphrase
|
33
|
+
# Passphrase for unlocking the private key
|
34
|
+
#
|
35
|
+
# @return [OpenSSL::PKey] The private key for signing assertions.
|
36
|
+
def self.load_key(keyfile, passphrase)
|
37
|
+
begin
|
38
|
+
if File.exists?(keyfile)
|
39
|
+
content = File.read(keyfile)
|
40
|
+
else
|
41
|
+
content = keyfile
|
42
|
+
end
|
43
|
+
pkcs12 = OpenSSL::PKCS12.new(content, passphrase)
|
44
|
+
return pkcs12.key
|
45
|
+
rescue OpenSSL::PKCS12::PKCS12Error
|
46
|
+
raise ArgumentError.new("Invalid keyfile or passphrase")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Generates access tokens using the JWT assertion profile. Requires a
|
53
|
+
# service account & access to the private key.
|
54
|
+
class JWTAsserter
|
55
|
+
attr_accessor :issuer, :expiry
|
56
|
+
attr_reader :scope
|
57
|
+
attr_writer :key
|
58
|
+
|
59
|
+
##
|
60
|
+
# Initializes the asserter for a service account.
|
61
|
+
#
|
62
|
+
# @param [String] issuer
|
63
|
+
# Name/ID of the client issuing the assertion
|
64
|
+
# @param [String or Array] scope
|
65
|
+
# Scopes to authorize. May be a space delimited string or array of strings
|
66
|
+
# @param [OpenSSL::PKey]
|
67
|
+
# RSA private key for signing assertions
|
68
|
+
def initialize(issuer, scope, key)
|
69
|
+
self.issuer = issuer
|
70
|
+
self.scope = scope
|
71
|
+
self.expiry = 60 # 1 min default
|
72
|
+
self.key = key
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Set the scopes to authorize
|
77
|
+
#
|
78
|
+
# @param [String or Array] scope
|
79
|
+
# Scopes to authorize. May be a space delimited string or array of strings
|
80
|
+
def scope=(new_scope)
|
81
|
+
case new_scope
|
82
|
+
when Array
|
83
|
+
@scope = new_scope.join(' ')
|
84
|
+
when String
|
85
|
+
@scope = new_scope
|
86
|
+
when nil
|
87
|
+
@scope = ''
|
88
|
+
else
|
89
|
+
raise TypeError, "Expected Array or String, got #{new_scope.class}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Builds & signs the assertion.
|
95
|
+
#
|
96
|
+
# @param [String] person
|
97
|
+
# Email address of a user, if requesting a token to act on their behalf
|
98
|
+
# @return [String] Encoded JWT
|
99
|
+
def to_jwt(person=nil)
|
100
|
+
now = Time.new
|
101
|
+
assertion = {
|
102
|
+
"iss" => @issuer,
|
103
|
+
"scope" => self.scope,
|
104
|
+
"aud" => "https://accounts.google.com/o/oauth2/token",
|
105
|
+
"exp" => (now + expiry).to_i,
|
106
|
+
"iat" => now.to_i
|
107
|
+
}
|
108
|
+
assertion['prn'] = person unless person.nil?
|
109
|
+
return JWT.encode(assertion, @key, "RS256")
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Request a new access token.
|
114
|
+
#
|
115
|
+
# @param [String] person
|
116
|
+
# Email address of a user, if requesting a token to act on their behalf
|
117
|
+
# @param [Hash] options
|
118
|
+
# Pass through to Signet::OAuth2::Client.fetch_access_token
|
119
|
+
# @return [Signet::OAuth2::Client] Access token
|
120
|
+
#
|
121
|
+
# @see Signet::OAuth2::Client.fetch_access_token
|
122
|
+
def authorize(person = nil, options={})
|
123
|
+
assertion = self.to_jwt(person)
|
124
|
+
authorization = Signet::OAuth2::Client.new(
|
125
|
+
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token'
|
126
|
+
)
|
127
|
+
authorization.grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
128
|
+
authorization.extension_parameters = { :assertion => assertion }
|
129
|
+
authorization.fetch_access_token!(options)
|
130
|
+
return authorization
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus posuere urna bibendum diam vulputate fringilla. Fusce elementum fermentum justo id aliquam. Integer vel felis ut arcu elementum lacinia. Duis congue urna eget nisl dapibus tristique molestie turpis sollicitudin. Vivamus in justo quam. Proin condimentum mollis tortor at molestie. Cras luctus, nunc a convallis iaculis, est risus consequat nisi, sit amet sollicitudin metus mi a urna. Aliquam accumsan, massa quis condimentum varius, sapien massa faucibus nibh, a dignissim magna nibh a lacus. Nunc aliquet, nunc ac pulvinar consectetur, sapien lacus hendrerit enim, nec dapibus lorem mi eget risus. Praesent vitae justo eget dolor blandit ullamcorper. Duis id nibh vitae sem aliquam vehicula et ac massa. In neque elit, molestie pulvinar viverra at, vestibulum quis velit.
|
2
|
+
|
3
|
+
Mauris sit amet placerat enim. Duis vel tellus ac dui auctor tincidunt id nec augue. Donec ut blandit turpis. Mauris dictum urna id urna vestibulum accumsan. Maecenas sagittis urna vitae erat facilisis gravida. Phasellus tellus augue, commodo ut iaculis vitae, interdum ut dolor. Proin at dictum lorem. Quisque pellentesque neque ante, vitae rutrum elit. Pellentesque sit amet erat orci. Praesent justo diam, tristique eu tempus ut, vestibulum eget dui. Maecenas et elementum justo. Cras a augue a elit porttitor placerat eget ut magna.
|
4
|
+
|
5
|
+
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam adipiscing tellus in arcu bibendum volutpat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed laoreet faucibus tristique. Duis metus eros, molestie eget dignissim in, imperdiet fermentum nulla. Vestibulum laoreet lorem eu justo vestibulum lobortis. Praesent pharetra leo vel mauris rhoncus commodo sollicitudin ante auctor. Ut sagittis, tortor nec placerat rutrum, neque ipsum cursus nisl, ut lacinia magna risus ac risus. Sed volutpat commodo orci, sodales fermentum dui accumsan eu. Donec egestas ullamcorper elit at condimentum. In euismod sodales posuere. Nullam lacinia tempus molestie. Etiam vitae ullamcorper dui. Fusce congue suscipit arcu, at consectetur diam gravida id. Quisque augue urna, commodo eleifend volutpat vitae, tincidunt ac ligula. Curabitur eget orci nisl, vel placerat ipsum.
|
6
|
+
|
7
|
+
Curabitur rutrum euismod nisi, consectetur varius tortor condimentum non. Pellentesque rhoncus nisi eu purus ultricies suscipit. Morbi ante nisi, varius nec molestie bibendum, pharetra quis enim. Proin eget nunc ante. Cras aliquam enim vel nunc laoreet ut facilisis nunc interdum. Fusce libero ipsum, posuere eget blandit quis, bibendum vitae quam. Integer dictum faucibus lacus eget facilisis. Duis adipiscing tortor magna, vel tincidunt risus. In non augue eu nisl sodales cursus vel eget nisi. Maecenas dignissim lectus elementum eros fermentum gravida et eget leo. Aenean quis cursus arcu. Mauris posuere purus non diam mattis vehicula. Integer nec orci velit.
|
8
|
+
|
9
|
+
Integer ac justo ac magna adipiscing condimentum vitae tincidunt dui. Morbi augue arcu, blandit nec interdum sit amet, condimentum vel nisl. Nulla vehicula tincidunt laoreet. Aliquam ornare elementum urna, sed vehicula magna porta id. Vestibulum dictum ultrices tortor sit amet tincidunt. Praesent bibendum, metus vel volutpat interdum, nisl nunc cursus libero, vel congue ligula mi et felis. Nulla mollis elementum nulla, in accumsan risus consequat at. Suspendisse potenti. Vestibulum enim lorem, dignissim ut porta vestibulum, porta eget mi. Fusce a elit ac dui sodales gravida. Pellentesque sed elit at dui dapibus mattis a non arcu.
|
10
|
+
|
11
|
+
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In nec posuere augue. Praesent non suscipit arcu. Sed nibh risus, lacinia ut molestie vitae, tristique eget turpis. Sed pretium volutpat arcu, non rutrum leo volutpat sed. Maecenas quis neque nisl, sit amet ornare dolor. Nulla pharetra pulvinar tellus sed eleifend. Aliquam eget mattis nulla. Nulla dictum vehicula velit, non facilisis lorem volutpat id. Fusce scelerisque sem vitae purus dapibus lobortis. Mauris ac turpis nec nibh consequat porttitor. Ut sit amet iaculis lorem. Vivamus blandit erat ac odio venenatis fringilla a sit amet ante. Quisque ut urna sed augue laoreet sagittis.
|
12
|
+
|
13
|
+
Integer nisl urna, bibendum id lobortis in, tempor non velit. Fusce sed volutpat quam. Suspendisse eu placerat purus. Maecenas quis feugiat lectus. Sed accumsan malesuada dui, a pretium purus facilisis quis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc ac purus id lacus malesuada placerat et in nunc. Ut imperdiet tincidunt est, at consectetur augue egestas hendrerit. Pellentesque eu erat a dui dignissim adipiscing. Integer quis leo non felis placerat eleifend. Fusce luctus mi a lorem mattis eget accumsan libero posuere. Sed pellentesque, odio id pharetra tempus, enim quam placerat metus, auctor aliquam elit mi facilisis quam. Nam at velit et eros rhoncus accumsan.
|
14
|
+
|
15
|
+
Donec tellus diam, fringilla ac viverra fringilla, rhoncus sit amet purus. Cras et ligula sed nibh tempor gravida. Aliquam id tempus mauris. Ut convallis quam sed arcu varius eget mattis magna tincidunt. Aliquam et suscipit est. Sed metus augue, tristique sed accumsan eget, euismod et augue. Nam augue sapien, placerat vel facilisis eu, tempor id risus. Aliquam mollis egestas mi. Fusce scelerisque convallis mauris quis blandit. Mauris nec ante id lacus sagittis tincidunt ornare vehicula dui. Curabitur tristique mattis nunc, vel cursus libero viverra feugiat. Suspendisse at sapien velit, a lacinia dolor. Vivamus in est non odio feugiat lacinia sodales ut magna.
|
16
|
+
|
17
|
+
Donec interdum ligula id ipsum dapibus consectetur. Pellentesque vitae posuere ligula. Morbi rhoncus bibendum eleifend. Suspendisse fringilla nunc at elit malesuada vitae ullamcorper lorem laoreet. Suspendisse a ante at ipsum iaculis cursus. Duis accumsan ligula quis nibh luctus pretium. Duis ultrices scelerisque dolor, et vulputate lectus commodo ut.
|
18
|
+
|
19
|
+
Vestibulum ac tincidunt lorem. Vestibulum lorem massa, dictum a scelerisque ut, convallis vitae eros. Morbi ipsum nisl, lacinia non tempor nec, lobortis id diam. Fusce quis magna nunc. Proin ultricies congue justo sed mattis. Vestibulum sit amet arcu tellus. Quisque ultricies porta massa iaculis vehicula. Vestibulum sollicitudin tempor urna vel sodales. Pellentesque ultricies tellus vel metus porta nec iaculis sapien mollis. Maecenas ullamcorper, metus eget imperdiet sagittis, odio orci dapibus neque, in vulputate nunc nibh non libero. Donec velit quam, lobortis quis tempus a, hendrerit id arcu.
|
20
|
+
|
21
|
+
Donec nec ante at tortor dignissim mattis. Curabitur vehicula tincidunt magna id sagittis. Proin euismod dignissim porta. Curabitur non turpis purus, in rutrum nulla. Nam turpis nulla, tincidunt et hendrerit non, posuere nec enim. Curabitur leo enim, lobortis ut placerat id, condimentum nec massa. In bibendum, lectus sit amet molestie commodo, felis massa rutrum nisl, ac fermentum ligula lacus in ipsum.
|
22
|
+
|
23
|
+
Pellentesque mi nulla, scelerisque vitae tempus id, consequat a augue. Quisque vel nisi sit amet ipsum faucibus laoreet sed vitae lorem. Praesent nunc tortor, volutpat ac commodo non, pharetra sed neque. Curabitur nec felis at mi blandit aliquet eu ornare justo. Mauris dignissim purus quis nisl porttitor interdum. Aenean id ipsum enim, blandit commodo justo. Quisque facilisis elit quis velit commodo scelerisque lobortis sapien condimentum. Cras sit amet porttitor velit. Praesent nec tempor arcu.
|
24
|
+
|
25
|
+
Donec varius mi adipiscing elit semper vel feugiat ipsum dictum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non quam nisl, ac mattis justo. Vestibulum sed massa eget velit tristique auctor ut ac sapien. Curabitur aliquet ligula eget dui ornare at scelerisque mauris faucibus. Vestibulum id mauris metus, sed vestibulum nibh. Nulla egestas dictum blandit. Mauris vitae nibh at dui mollis lobortis. Phasellus sem leo, euismod at fringilla quis, mollis in nibh. Aenean vel lacus et elit pharetra elementum. Aliquam at ligula id sem bibendum volutpat. Pellentesque quis elit a massa dapibus viverra ut et lorem. Donec nulla eros, iaculis nec commodo vel, suscipit sit amet tortor. Integer tempor, elit at viverra imperdiet, velit sapien laoreet nunc, id laoreet ligula risus vel risus. Nullam sed tortor metus.
|
26
|
+
|
27
|
+
In nunc orci, tempor vulputate pretium vel, suscipit quis risus. Suspendisse accumsan facilisis felis eget posuere. Donec a faucibus felis. Proin nibh erat, sollicitudin quis vestibulum id, tincidunt quis justo. In sed purus eu nisi dignissim condimentum. Sed mattis dapibus lorem id vulputate. Suspendisse nec elit a augue interdum consequat quis id magna. In eleifend aliquam tempor. In in lacus augue.
|
28
|
+
|
29
|
+
Ut euismod sollicitudin lorem, id aliquam magna dictum sed. Nunc fringilla lobortis nisi sed consectetur. Nulla facilisi. Aenean nec lobortis augue. Curabitur ullamcorper dapibus libero, vel pellentesque arcu sollicitudin non. Praesent varius, turpis nec sollicitudin bibendum, elit tortor rhoncus lacus, gravida luctus leo nisi in felis. Ut metus eros, molestie non faucibus vel, condimentum ac elit.
|
30
|
+
|
31
|
+
Suspendisse nisl justo, lacinia sit amet interdum nec, tincidunt placerat urna. Suspendisse potenti. In et odio sed purus malesuada cursus sed nec lectus. Cras commodo, orci sit amet hendrerit iaculis, nunc urna facilisis tellus, vel laoreet odio nulla quis nibh. Maecenas ut justo ut lacus posuere sodales. Vestibulum facilisis fringilla diam at volutpat. Proin a hendrerit urna. Aenean placerat pulvinar arcu, sit amet lobortis neque eleifend in. Aenean risus nulla, facilisis ut tincidunt vitae, fringilla at ligula. Praesent eleifend est at sem lacinia auctor. Nulla ornare nunc in erat laoreet blandit.
|
32
|
+
|
33
|
+
Suspendisse pharetra leo ac est porta consequat. Nunc sem nibh, gravida vel aliquam a, ornare in tortor. Nulla vel sapien et felis placerat pellentesque id scelerisque nisl. Praesent et posuere.
|
@@ -0,0 +1,237 @@
|
|
1
|
+
# Copyright 2012 Google Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'spec_helper'
|
16
|
+
|
17
|
+
require 'google/api_client'
|
18
|
+
require 'google/api_client/version'
|
19
|
+
|
20
|
+
describe Google::APIClient::BatchRequest do
|
21
|
+
before do
|
22
|
+
@client = Google::APIClient.new
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should raise an error if making an empty batch request' do
|
26
|
+
batch = Google::APIClient::BatchRequest.new
|
27
|
+
|
28
|
+
(lambda do
|
29
|
+
@client.execute(batch)
|
30
|
+
end).should raise_error(Google::APIClient::BatchError)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'with the discovery API' do
|
34
|
+
before do
|
35
|
+
@client.authorization = nil
|
36
|
+
@discovery = @client.discovered_api('discovery', 'v1')
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'with two valid requests' do
|
40
|
+
before do
|
41
|
+
@call1 = {
|
42
|
+
:api_method => @discovery.apis.get_rest,
|
43
|
+
:parameters => {
|
44
|
+
'api' => 'adsense',
|
45
|
+
'version' => 'v1'
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
@call2 = {
|
50
|
+
:api_method => @discovery.apis.get_rest,
|
51
|
+
:parameters => {
|
52
|
+
'api' => 'discovery',
|
53
|
+
'version' => 'v1'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should execute both when using a global callback' do
|
59
|
+
block_called = 0
|
60
|
+
ids = ['first_call', 'second_call']
|
61
|
+
expected_ids = ids.clone
|
62
|
+
batch = Google::APIClient::BatchRequest.new do |result|
|
63
|
+
block_called += 1
|
64
|
+
result.status.should == 200
|
65
|
+
expected_ids.should include(result.response.call_id)
|
66
|
+
expected_ids.delete(result.response.call_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
batch.add(@call1, ids[0])
|
70
|
+
batch.add(@call2, ids[1])
|
71
|
+
|
72
|
+
@client.execute(batch)
|
73
|
+
block_called.should == 2
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should execute both when using individual callbacks' do
|
77
|
+
batch = Google::APIClient::BatchRequest.new
|
78
|
+
|
79
|
+
call1_returned, call2_returned = false, false
|
80
|
+
batch.add(@call1) do |result|
|
81
|
+
call1_returned = true
|
82
|
+
result.status.should == 200
|
83
|
+
end
|
84
|
+
batch.add(@call2) do |result|
|
85
|
+
call2_returned = true
|
86
|
+
result.status.should == 200
|
87
|
+
end
|
88
|
+
|
89
|
+
@client.execute(batch)
|
90
|
+
call1_returned.should == true
|
91
|
+
call2_returned.should == true
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should raise an error if using the same call ID more than once' do
|
95
|
+
batch = Google::APIClient::BatchRequest.new
|
96
|
+
|
97
|
+
(lambda do
|
98
|
+
batch.add(@call1, 'my_id')
|
99
|
+
batch.add(@call2, 'my_id')
|
100
|
+
end).should raise_error(Google::APIClient::BatchError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'with a valid request and an invalid one' do
|
105
|
+
before do
|
106
|
+
@call1 = {
|
107
|
+
:api_method => @discovery.apis.get_rest,
|
108
|
+
:parameters => {
|
109
|
+
'api' => 'adsense',
|
110
|
+
'version' => 'v1'
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
@call2 = {
|
115
|
+
:api_method => @discovery.apis.get_rest,
|
116
|
+
:parameters => {
|
117
|
+
'api' => 0,
|
118
|
+
'version' => 1
|
119
|
+
}
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should execute both when using a global callback' do
|
124
|
+
block_called = 0
|
125
|
+
ids = ['first_call', 'second_call']
|
126
|
+
expected_ids = ids.clone
|
127
|
+
batch = Google::APIClient::BatchRequest.new do |result|
|
128
|
+
block_called += 1
|
129
|
+
expected_ids.should include(result.response.call_id)
|
130
|
+
expected_ids.delete(result.response.call_id)
|
131
|
+
if result.response.call_id == ids[0]
|
132
|
+
result.status.should == 200
|
133
|
+
else
|
134
|
+
result.status.should >= 400
|
135
|
+
result.status.should < 500
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
batch.add(@call1, ids[0])
|
140
|
+
batch.add(@call2, ids[1])
|
141
|
+
|
142
|
+
@client.execute(batch)
|
143
|
+
block_called.should == 2
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should execute both when using individual callbacks' do
|
147
|
+
batch = Google::APIClient::BatchRequest.new
|
148
|
+
|
149
|
+
call1_returned, call2_returned = false, false
|
150
|
+
batch.add(@call1) do |result|
|
151
|
+
call1_returned = true
|
152
|
+
result.status.should == 200
|
153
|
+
end
|
154
|
+
batch.add(@call2) do |result|
|
155
|
+
call2_returned = true
|
156
|
+
result.status.should >= 400
|
157
|
+
result.status.should < 500
|
158
|
+
end
|
159
|
+
|
160
|
+
@client.execute(batch)
|
161
|
+
call1_returned.should == true
|
162
|
+
call2_returned.should == true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'with the calendar API' do
|
168
|
+
before do
|
169
|
+
@client.authorization = nil
|
170
|
+
@calendar = @client.discovered_api('calendar', 'v3')
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'with two valid requests' do
|
174
|
+
before do
|
175
|
+
event1 = {
|
176
|
+
'summary' => 'Appointment 1',
|
177
|
+
'location' => 'Somewhere',
|
178
|
+
'start' => {
|
179
|
+
'dateTime' => '2011-01-01T10:00:00.000-07:00'
|
180
|
+
},
|
181
|
+
'end' => {
|
182
|
+
'dateTime' => '2011-01-01T10:25:00.000-07:00'
|
183
|
+
},
|
184
|
+
'attendees' => [
|
185
|
+
{
|
186
|
+
'email' => 'myemail@mydomain.tld'
|
187
|
+
}
|
188
|
+
]
|
189
|
+
}
|
190
|
+
|
191
|
+
event2 = {
|
192
|
+
'summary' => 'Appointment 2',
|
193
|
+
'location' => 'Somewhere as well',
|
194
|
+
'start' => {
|
195
|
+
'dateTime' => '2011-01-02T10:00:00.000-07:00'
|
196
|
+
},
|
197
|
+
'end' => {
|
198
|
+
'dateTime' => '2011-01-02T10:25:00.000-07:00'
|
199
|
+
},
|
200
|
+
'attendees' => [
|
201
|
+
{
|
202
|
+
'email' => 'myemail@mydomain.tld'
|
203
|
+
}
|
204
|
+
]
|
205
|
+
}
|
206
|
+
|
207
|
+
@call1 = {
|
208
|
+
:api_method => @calendar.events.insert,
|
209
|
+
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
|
210
|
+
:body => JSON.dump(event1),
|
211
|
+
:headers => {'Content-Type' => 'application/json'}
|
212
|
+
}
|
213
|
+
|
214
|
+
@call2 = {
|
215
|
+
:api_method => @calendar.events.insert,
|
216
|
+
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
|
217
|
+
:body => JSON.dump(event2),
|
218
|
+
:headers => {'Content-Type' => 'application/json'}
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should convert to a correct HTTP request' do
|
223
|
+
batch = Google::APIClient::BatchRequest.new { |result| }
|
224
|
+
batch.add(@call1, '1').add(@call2, '2')
|
225
|
+
method, uri, headers, body = batch.to_http_request
|
226
|
+
boundary = Google::APIClient::BatchRequest::BATCH_BOUNDARY
|
227
|
+
method.to_s.downcase.should == 'post'
|
228
|
+
uri.to_s.should == 'https://www.googleapis.com/batch'
|
229
|
+
headers.should == {
|
230
|
+
"Content-Type"=>"multipart/mixed; boundary=#{boundary}"
|
231
|
+
}
|
232
|
+
expected_body = /--#{Regexp.escape(boundary)}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+1>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events +HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call1[:body])}\n\n--#{boundary}\nContent-Type: +application\/http\nContent-ID: +<[\w-]+\+2>\n\nPOST +https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/myemail@mydomain.tld\/events HTTP\/1.1\nContent-Type: +application\/json\n\n#{Regexp.escape(@call2[:body])}\n\n--#{Regexp.escape(boundary)}--/
|
233
|
+
body.gsub("\r", "").should =~ expected_body
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|