restforce 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of restforce might be problematic. Click here for more details.
- data/README.md +13 -20
- data/lib/restforce.rb +13 -0
- data/lib/restforce/client.rb +42 -25
- data/lib/restforce/config.rb +3 -0
- data/lib/restforce/middleware.rb +12 -16
- data/lib/restforce/middleware/authentication.rb +9 -4
- data/lib/restforce/middleware/gzip.rb +31 -0
- data/lib/restforce/middleware/raise_error.rb +1 -1
- data/lib/restforce/version.rb +1 -1
- data/spec/lib/client_spec.rb +72 -27
- data/spec/lib/config_spec.rb +3 -3
- data/spec/lib/middleware/authentication/password_spec.rb +6 -8
- data/spec/lib/middleware/authentication/token_spec.rb +6 -8
- data/spec/lib/middleware/gzip_spec.rb +81 -0
- data/spec/support/fixture_helpers.rb +17 -6
- metadata +5 -2
data/README.md
CHANGED
@@ -11,6 +11,7 @@ It attempts to solve a couple of key issues that the databasedotcom gem has been
|
|
11
11
|
* Remove the need to materialize constants.
|
12
12
|
* Support for the Streaming API
|
13
13
|
* Support for blob data types.
|
14
|
+
* Support for GZIP compression.
|
14
15
|
* A clean and modular architecture using [Faraday middleware](https://github.com/technoweenie/faraday)
|
15
16
|
* Support for decoding [Force.com Canvas](http://www.salesforce.com/us/developer/docs/platform_connectpre/canvas_framework.pdf) signed requests. (NEW!)
|
16
17
|
|
@@ -345,27 +346,19 @@ Restforce.log = true
|
|
345
346
|
client = Restforce.new.query('select Id, Name from Account')
|
346
347
|
```
|
347
348
|
|
348
|
-
|
349
|
+
Another awesome feature about restforce is that, because it is based on
|
350
|
+
Faraday, you can insert your own middleware. For example, if you were using
|
351
|
+
Restforce in a rails app, you can setup custom logging using
|
352
|
+
ActiveSupport::Notifications:
|
349
353
|
|
350
|
-
```
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
connection: "close"
|
359
|
-
D, [2012-09-11T21:54:00.997431 #24032] DEBUG -- response body: { ... }
|
360
|
-
I, [2012-09-11T21:54:00.998985 #24032] INFO -- : get https://na9.salesforce.com/services/data/v24.0/query?q=select+Id%2C+Name+from+Account
|
361
|
-
D, [2012-09-11T21:54:00.999040 #24032] DEBUG -- request: Authorization: "OAuth token"
|
362
|
-
I, [2012-09-11T21:54:01.622874 #24032] INFO -- Status: 200
|
363
|
-
D, [2012-09-11T21:54:01.623001 #24032] DEBUG -- response headers: server: ""
|
364
|
-
content-type: "application/json; charset=UTF-8"
|
365
|
-
transfer-encoding: "chunked"
|
366
|
-
date: "Wed, 12 Sep 2012 04:54:00 GMT"
|
367
|
-
connection: "close"
|
368
|
-
D, [2012-09-11T21:54:01.623058 #24032] DEBUG -- response body: { ... }
|
354
|
+
```ruby
|
355
|
+
client = Restforce.new
|
356
|
+
client.middleware.insert_after Restforce::Middleware::InstanceURL, FaradayMiddleware::Instrumentation
|
357
|
+
|
358
|
+
# config/initializers/notifications.rb
|
359
|
+
ActiveSupport::Notifications.subscribe('request.faraday') do |name, start, finish, id, payload|
|
360
|
+
Rails.logger.debug(['notification:', name, "#{(finish - start) * 1000}ms", payload[:status]].join(" "))
|
361
|
+
end
|
369
362
|
```
|
370
363
|
|
371
364
|
## Contributing
|
data/lib/restforce.rb
CHANGED
@@ -24,6 +24,19 @@ module Restforce
|
|
24
24
|
def new(options = {})
|
25
25
|
Restforce::Client.new(options)
|
26
26
|
end
|
27
|
+
|
28
|
+
# Public: Decodes a signed request received from Force.com Canvas.
|
29
|
+
#
|
30
|
+
# message - The POST message containing the signed request from Salesforce.
|
31
|
+
# client_secret - The oauth client secret used to encrypt the message.
|
32
|
+
#
|
33
|
+
# Returns the Hash context if the message is valid.
|
34
|
+
def decode_signed_request(message, client_secret)
|
35
|
+
encryped_secret, payload = message.split('.')
|
36
|
+
digest = OpenSSL::Digest::Digest.new('sha256')
|
37
|
+
signature = Base64.encode64(OpenSSL::HMAC.hexdigest(digest, client_secret, payload))
|
38
|
+
JSON.parse(Base64.decode64(payload)) if encryped_secret == signature
|
39
|
+
end
|
27
40
|
end
|
28
41
|
|
29
42
|
class AuthenticationError < StandardError; end
|
data/lib/restforce/client.rb
CHANGED
@@ -3,28 +3,35 @@ module Restforce
|
|
3
3
|
# Public: Creates a new client instance
|
4
4
|
#
|
5
5
|
# options - A hash of options to be passed in (default: {}).
|
6
|
-
# :username
|
7
|
-
# :password
|
8
|
-
# :security_token
|
9
|
-
#
|
6
|
+
# :username - The String username to use (required for password authentication).
|
7
|
+
# :password - The String password to use (required for password authentication).
|
8
|
+
# :security_token - The String security token to use
|
9
|
+
# (required for password authentication).
|
10
10
|
#
|
11
|
-
# :oauth_token
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# :refresh_token
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# :instance_url
|
18
|
-
#
|
11
|
+
# :oauth_token - The String oauth access token to authenticate api
|
12
|
+
# calls (required unless password
|
13
|
+
# authentication is used).
|
14
|
+
# :refresh_token - The String refresh token to obtain fresh
|
15
|
+
# oauth access tokens (required if oauth
|
16
|
+
# authentication is used).
|
17
|
+
# :instance_url - The String base url for all api requests
|
18
|
+
# (required if oauth authentication is used).
|
19
19
|
#
|
20
|
-
# :client_id
|
21
|
-
#
|
22
|
-
# :client_secret
|
20
|
+
# :client_id - The oauth client id to use. Needed for both
|
21
|
+
# password and oauth authentication
|
22
|
+
# :client_secret - The oauth client secret to use.
|
23
23
|
#
|
24
|
-
# :host
|
25
|
-
#
|
24
|
+
# :host - The String hostname to use during
|
25
|
+
# authentication requests (default: 'login.salesforce.com').
|
26
26
|
#
|
27
|
-
# :api_version
|
27
|
+
# :api_version - The String REST api version to use (default: '24.0')
|
28
|
+
#
|
29
|
+
# :authentication_retries - The number of times that client
|
30
|
+
# should attempt to reauthenticate
|
31
|
+
# before raising an exception (default: 3).
|
32
|
+
#
|
33
|
+
# :compress - Set to true to have Salesforce compress the
|
34
|
+
# response (default: false).
|
28
35
|
#
|
29
36
|
# Examples
|
30
37
|
#
|
@@ -51,7 +58,7 @@ module Restforce
|
|
51
58
|
def initialize(options = {})
|
52
59
|
raise 'Please specify a hash of options' unless options.is_a?(Hash)
|
53
60
|
@options = {}.tap do |options|
|
54
|
-
[:username, :password, :security_token, :client_id, :client_secret, :host,
|
61
|
+
[:username, :password, :security_token, :client_id, :client_secret, :host, :compress,
|
55
62
|
:api_version, :oauth_token, :refresh_token, :instance_url, :cache, :authentication_retries].each do |option|
|
56
63
|
options[option] = Restforce.configuration.send option
|
57
64
|
end
|
@@ -289,10 +296,7 @@ module Restforce
|
|
289
296
|
# Returns the Hash context if the message is valid.
|
290
297
|
def decode_signed_request(message)
|
291
298
|
raise 'client_secret not set' unless @options[:client_secret]
|
292
|
-
|
293
|
-
digest = OpenSSL::Digest::Digest.new('sha256')
|
294
|
-
signature = Base64.encode64(OpenSSL::HMAC.hexdigest(digest, @options[:client_secret], payload))
|
295
|
-
JSON.parse(Base64.decode64(payload)) if encryped_secret == signature
|
299
|
+
Restforce.decode_signed_request(message, @options[:client_secret])
|
296
300
|
end
|
297
301
|
|
298
302
|
# Public: Helper methods for performing arbitrary actions against the API using
|
@@ -338,6 +342,19 @@ module Restforce
|
|
338
342
|
end
|
339
343
|
end
|
340
344
|
|
345
|
+
# Public: The Faraday::Builder instance used for the middleware stack. This
|
346
|
+
# can be used to insert an custom middleware.
|
347
|
+
#
|
348
|
+
# Examples
|
349
|
+
#
|
350
|
+
# # Add the instrumentation middleware for Rails.
|
351
|
+
# client.middleware.use FaradayMiddleware::Instrumentation
|
352
|
+
#
|
353
|
+
# Returns the Faraday::Builder for the Faraday connection.
|
354
|
+
def middleware
|
355
|
+
connection.builder
|
356
|
+
end
|
357
|
+
|
341
358
|
private
|
342
359
|
|
343
360
|
# Internal: Returns a path to an api endpoint
|
@@ -364,9 +381,9 @@ module Restforce
|
|
364
381
|
builder.use FaradayMiddleware::FollowRedirects
|
365
382
|
builder.use Restforce::Middleware::RaiseError
|
366
383
|
builder.use Restforce::Middleware::Logger, Restforce.configuration.logger, @options if Restforce.log?
|
384
|
+
builder.use Restforce::Middleware::Gzip, self, @options
|
367
385
|
builder.adapter Faraday.default_adapter
|
368
386
|
end
|
369
|
-
@connection
|
370
387
|
end
|
371
388
|
|
372
389
|
# Internal: Determines what middleware will be used based on the options provided
|
@@ -404,7 +421,7 @@ module Restforce
|
|
404
421
|
# Internal: Returns true if the middlware stack includes the
|
405
422
|
# Restforce::Middleware::Mashify middleware.
|
406
423
|
def mashify?
|
407
|
-
|
424
|
+
middleware.handlers.index(Restforce::Middleware::Mashify)
|
408
425
|
end
|
409
426
|
|
410
427
|
# Internal: Errors that should be rescued from in non-bang methods
|
data/lib/restforce/config.rb
CHANGED
@@ -63,6 +63,9 @@ module Restforce
|
|
63
63
|
# The number of times reauthentication should be tried before failing.
|
64
64
|
attr_accessor :authentication_retries
|
65
65
|
|
66
|
+
# Set to true if you want responses from Salesforce to be gzip compressed.
|
67
|
+
attr_accessor :compress
|
68
|
+
|
66
69
|
def initialize
|
67
70
|
@api_version ||= '24.0'
|
68
71
|
@host ||= 'login.salesforce.com'
|
data/lib/restforce/middleware.rb
CHANGED
@@ -1,33 +1,29 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Base class that all middleware can extend. Provides some convenient helper
|
4
3
|
# functions.
|
5
4
|
class Middleware < Faraday::Middleware
|
5
|
+
autoload :RaiseError, 'restforce/middleware/raise_error'
|
6
|
+
autoload :Authentication, 'restforce/middleware/authentication'
|
7
|
+
autoload :Authorization, 'restforce/middleware/authorization'
|
8
|
+
autoload :InstanceURL, 'restforce/middleware/instance_url'
|
9
|
+
autoload :Multipart, 'restforce/middleware/multipart'
|
10
|
+
autoload :Mashify, 'restforce/middleware/mashify'
|
11
|
+
autoload :Caching, 'restforce/middleware/caching'
|
12
|
+
autoload :Logger, 'restforce/middleware/logger'
|
13
|
+
autoload :Gzip, 'restforce/middleware/gzip'
|
6
14
|
|
7
15
|
def initialize(app, client, options)
|
8
|
-
@app
|
9
|
-
@client = client
|
10
|
-
@options = options
|
16
|
+
@app, @client, @options = app, client, options
|
11
17
|
end
|
12
18
|
|
19
|
+
# Internal: Proxy to the client.
|
13
20
|
def client
|
14
21
|
@client
|
15
22
|
end
|
16
23
|
|
24
|
+
# Internal: Proxy to the client's faraday connection.
|
17
25
|
def connection
|
18
26
|
client.send(:connection)
|
19
27
|
end
|
20
|
-
|
21
28
|
end
|
22
29
|
end
|
23
|
-
|
24
|
-
require 'restforce/middleware/raise_error'
|
25
|
-
require 'restforce/middleware/authentication'
|
26
|
-
require 'restforce/middleware/authentication/password'
|
27
|
-
require 'restforce/middleware/authentication/token'
|
28
|
-
require 'restforce/middleware/authorization'
|
29
|
-
require 'restforce/middleware/instance_url'
|
30
|
-
require 'restforce/middleware/mashify'
|
31
|
-
require 'restforce/middleware/multipart'
|
32
|
-
require 'restforce/middleware/caching'
|
33
|
-
require 'restforce/middleware/logger'
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Faraday middleware that allows for on the fly authentication of requests.
|
4
|
-
# When a request fails (
|
3
|
+
# When a request fails (a status of 401 is returned), the middleware
|
5
4
|
# will attempt to either reauthenticate (username and password) or refresh
|
6
5
|
# the oauth access token (if a refresh token is present).
|
7
6
|
class Middleware::Authentication < Restforce::Middleware
|
7
|
+
autoload :Password, 'restforce/middleware/authentication/password'
|
8
|
+
autoload :Token, 'restforce/middleware/authentication/token'
|
8
9
|
|
10
|
+
# Rescue from 401's, authenticate then raise the error again so the client
|
11
|
+
# can reissue the request.
|
9
12
|
def call(env)
|
10
13
|
@app.call(env)
|
11
14
|
rescue Restforce::UnauthorizedError
|
@@ -13,6 +16,7 @@ module Restforce
|
|
13
16
|
raise
|
14
17
|
end
|
15
18
|
|
19
|
+
# Internal: Performs the authentication and returns the response body.
|
16
20
|
def authenticate!
|
17
21
|
response = connection.post '/services/oauth2/token' do |req|
|
18
22
|
req.body = URI.encode_www_form params
|
@@ -23,10 +27,12 @@ module Restforce
|
|
23
27
|
response.body
|
24
28
|
end
|
25
29
|
|
30
|
+
# Internal: The params to post to the OAuth service.
|
26
31
|
def params
|
27
32
|
raise 'not implemented'
|
28
33
|
end
|
29
34
|
|
35
|
+
# Internal: Faraday connection to use when sending an authentication request.
|
30
36
|
def connection
|
31
37
|
@connection ||= Faraday.new(:url => "https://#{@options[:host]}") do |builder|
|
32
38
|
builder.use Restforce::Middleware::Mashify, nil, @options
|
@@ -36,10 +42,9 @@ module Restforce
|
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
45
|
+
# Internal: The parsed error response.
|
39
46
|
def error_message(response)
|
40
47
|
"#{response.body['error']}: #{response.body['error_description']}"
|
41
48
|
end
|
42
|
-
|
43
49
|
end
|
44
|
-
|
45
50
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module Restforce
|
4
|
+
# Middleware to uncompress GZIP compressed responses from Salesforce.
|
5
|
+
class Middleware::Gzip < Restforce::Middleware
|
6
|
+
ACCEPT_ENCODING_HEADER = 'Accept-Encoding'.freeze
|
7
|
+
CONTENT_ENCODING_HEADER = 'Content-Encoding'.freeze
|
8
|
+
ENCODING = 'gzip'.freeze
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
env[:request_headers][ACCEPT_ENCODING_HEADER] = ENCODING if @options[:compress]
|
12
|
+
@app.call(env).on_complete do |environment|
|
13
|
+
on_complete(environment)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_complete(env)
|
18
|
+
env[:body] = decompress(env[:body]) if gzipped?(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Returns true if the response is gzipped.
|
22
|
+
def gzipped?(env)
|
23
|
+
env[:response_headers][CONTENT_ENCODING_HEADER] == ENCODING
|
24
|
+
end
|
25
|
+
|
26
|
+
# Internal: Decompresses a gzipped string.
|
27
|
+
def decompress(body)
|
28
|
+
Zlib::GzipReader.new(StringIO.new(body)).read
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/restforce/version.rb
CHANGED
data/spec/lib/client_spec.rb
CHANGED
@@ -70,7 +70,8 @@ shared_examples_for 'methods' do
|
|
70
70
|
describe '.describe' do
|
71
71
|
context 'with no arguments' do
|
72
72
|
before do
|
73
|
-
@request = stub_api_request :sobjects,
|
73
|
+
@request = stub_api_request :sobjects,
|
74
|
+
with: 'sobject/describe_sobjects_success_response'
|
74
75
|
end
|
75
76
|
|
76
77
|
after do
|
@@ -83,7 +84,8 @@ shared_examples_for 'methods' do
|
|
83
84
|
|
84
85
|
context 'with an argument' do
|
85
86
|
before do
|
86
|
-
@request = stub_api_request 'sobjects/Whizbang/describe',
|
87
|
+
@request = stub_api_request 'sobjects/Whizbang/describe',
|
88
|
+
with: 'sobject/sobject_describe_success_response'
|
87
89
|
end
|
88
90
|
|
89
91
|
after do
|
@@ -97,7 +99,8 @@ shared_examples_for 'methods' do
|
|
97
99
|
|
98
100
|
describe '.query' do
|
99
101
|
before do
|
100
|
-
@request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
102
|
+
@request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
103
|
+
with: 'sobject/query_success_response'
|
101
104
|
end
|
102
105
|
|
103
106
|
after do
|
@@ -110,7 +113,8 @@ shared_examples_for 'methods' do
|
|
110
113
|
|
111
114
|
describe '.search' do
|
112
115
|
before do
|
113
|
-
@request = stub_api_request 'search\?q=FIND%20%7Bbar%7D',
|
116
|
+
@request = stub_api_request 'search\?q=FIND%20%7Bbar%7D',
|
117
|
+
with: 'sobject/search_success_response'
|
114
118
|
end
|
115
119
|
|
116
120
|
after do
|
@@ -124,7 +128,8 @@ shared_examples_for 'methods' do
|
|
124
128
|
|
125
129
|
describe '.org_id' do
|
126
130
|
before do
|
127
|
-
@request = stub_api_request 'query\?q=select%20id%20from%20Organization',
|
131
|
+
@request = stub_api_request 'query\?q=select%20id%20from%20Organization',
|
132
|
+
with: 'sobject/org_query_response'
|
128
133
|
end
|
129
134
|
|
130
135
|
after do
|
@@ -138,7 +143,10 @@ shared_examples_for 'methods' do
|
|
138
143
|
describe '.create' do
|
139
144
|
context 'without multipart' do
|
140
145
|
before do
|
141
|
-
@request = stub_api_request 'sobjects/Account',
|
146
|
+
@request = stub_api_request 'sobjects/Account',
|
147
|
+
with: 'sobject/create_success_response',
|
148
|
+
method: :post,
|
149
|
+
body: "{\"Name\":\"Foobar\"}"
|
142
150
|
end
|
143
151
|
|
144
152
|
after do
|
@@ -151,7 +159,10 @@ shared_examples_for 'methods' do
|
|
151
159
|
|
152
160
|
context 'with multipart' do
|
153
161
|
before do
|
154
|
-
@request = stub_api_request 'sobjects/Account',
|
162
|
+
@request = stub_api_request 'sobjects/Account',
|
163
|
+
with: 'sobject/create_success_response',
|
164
|
+
method: :post,
|
165
|
+
body: %r(----boundary_string\r\nContent-Disposition: form-data; name=\"entity_content\";\r\nContent-Type: application/json\r\n\r\n{\"Name\":\"Foobar\"}\r\n----boundary_string\r\nContent-Disposition: form-data; name=\"Blob\"; filename=\"blob.jpg\"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary)
|
155
166
|
end
|
156
167
|
|
157
168
|
after do
|
@@ -166,7 +177,11 @@ shared_examples_for 'methods' do
|
|
166
177
|
describe '.update!' do
|
167
178
|
context 'with invalid Id' do
|
168
179
|
before do
|
169
|
-
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
180
|
+
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
181
|
+
with: 'sobject/delete_error_response',
|
182
|
+
method: :patch,
|
183
|
+
body: "{\"Name\":\"Foobar\"}",
|
184
|
+
status: 404
|
170
185
|
end
|
171
186
|
|
172
187
|
after do
|
@@ -186,7 +201,11 @@ shared_examples_for 'methods' do
|
|
186
201
|
|
187
202
|
context 'with invalid Id' do
|
188
203
|
before do
|
189
|
-
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
204
|
+
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
205
|
+
with: 'sobject/delete_error_response',
|
206
|
+
method: :patch,
|
207
|
+
body: "{\"Name\":\"Foobar\"}",
|
208
|
+
status: 404
|
190
209
|
end
|
191
210
|
|
192
211
|
after do
|
@@ -199,7 +218,9 @@ shared_examples_for 'methods' do
|
|
199
218
|
|
200
219
|
context 'with success' do
|
201
220
|
before do
|
202
|
-
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
221
|
+
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
222
|
+
method: :patch,
|
223
|
+
body: "{\"Name\":\"Foobar\"}"
|
203
224
|
end
|
204
225
|
|
205
226
|
after do
|
@@ -221,7 +242,9 @@ shared_examples_for 'methods' do
|
|
221
242
|
describe '.upsert!' do
|
222
243
|
context 'when updated' do
|
223
244
|
before do
|
224
|
-
@request = stub_api_request 'sobjects/Account/External__c/foobar',
|
245
|
+
@request = stub_api_request 'sobjects/Account/External__c/foobar',
|
246
|
+
method: :patch,
|
247
|
+
body: "{\"Name\":\"Foobar\"}"
|
225
248
|
end
|
226
249
|
|
227
250
|
after do
|
@@ -241,7 +264,10 @@ shared_examples_for 'methods' do
|
|
241
264
|
|
242
265
|
context 'when created' do
|
243
266
|
before do
|
244
|
-
@request = stub_api_request 'sobjects/Account/External__c/foobar',
|
267
|
+
@request = stub_api_request 'sobjects/Account/External__c/foobar',
|
268
|
+
method: :patch,
|
269
|
+
body: "{\"Name\":\"Foobar\"}",
|
270
|
+
with: 'sobject/upsert_created_success_response'
|
245
271
|
end
|
246
272
|
|
247
273
|
after do
|
@@ -265,7 +291,10 @@ shared_examples_for 'methods' do
|
|
265
291
|
|
266
292
|
context 'with invalid Id' do
|
267
293
|
before do
|
268
|
-
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
294
|
+
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
295
|
+
with: 'sobject/delete_error_response',
|
296
|
+
method: :delete,
|
297
|
+
status: 404
|
269
298
|
end
|
270
299
|
|
271
300
|
after do
|
@@ -293,7 +322,10 @@ shared_examples_for 'methods' do
|
|
293
322
|
|
294
323
|
context 'with invalid Id' do
|
295
324
|
before do
|
296
|
-
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
325
|
+
@request = stub_api_request 'sobjects/Account/001D000000INjVe',
|
326
|
+
with: 'sobject/delete_error_response',
|
327
|
+
method: :delete,
|
328
|
+
status: 404
|
297
329
|
end
|
298
330
|
|
299
331
|
after do
|
@@ -318,10 +350,9 @@ shared_examples_for 'methods' do
|
|
318
350
|
|
319
351
|
describe '.authenticate!' do
|
320
352
|
before do
|
321
|
-
@request =
|
322
|
-
|
323
|
-
|
324
|
-
to_return(:status => 200, :body => fixture(:auth_success_response))
|
353
|
+
@request = stub_login_request(body: "grant_type=password&client_id=client_id&client_secret=" \
|
354
|
+
"client_secret&username=foo&password=barsecurity_token")
|
355
|
+
.to_return(status: 200, body: fixture(:auth_success_response))
|
325
356
|
end
|
326
357
|
|
327
358
|
after do
|
@@ -360,11 +391,25 @@ shared_examples_for 'methods' do
|
|
360
391
|
end
|
361
392
|
end
|
362
393
|
|
394
|
+
describe '.middleware' do
|
395
|
+
subject { client.middleware }
|
396
|
+
it { should eq client.send(:connection).builder }
|
397
|
+
|
398
|
+
context 'adding middleware' do
|
399
|
+
before do
|
400
|
+
client.middleware.use FaradayMiddleware::Instrumentation
|
401
|
+
end
|
402
|
+
|
403
|
+
its(:handlers) { should include FaradayMiddleware::Instrumentation }
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
363
407
|
describe '.without_caching' do
|
364
408
|
let(:cache) { double('cache') }
|
365
409
|
|
366
410
|
before do
|
367
|
-
@request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
411
|
+
@request = stub_api_request 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
412
|
+
with: 'sobject/query_success_response'
|
368
413
|
cache.should_receive(:fetch).never
|
369
414
|
end
|
370
415
|
|
@@ -400,11 +445,12 @@ shared_examples_for 'methods' do
|
|
400
445
|
describe 'authentication retries' do
|
401
446
|
context 'when retries reaches 0' do
|
402
447
|
before do
|
403
|
-
@auth_request = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object',
|
404
|
-
|
405
|
-
|
448
|
+
@auth_request = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object',
|
449
|
+
status: 401,
|
450
|
+
with: 'expired_session_response')
|
451
|
+
@query_request = stub_login_request(body: "grant_type=password&client_id=client_id&client_secret=" \
|
406
452
|
"client_secret&username=foo&password=barsecurity_token")
|
407
|
-
.to_return(:
|
453
|
+
.to_return(status: 200, body: fixture(:auth_success_response))
|
408
454
|
end
|
409
455
|
|
410
456
|
subject { client.query('SELECT some, fields FROM object') }
|
@@ -430,13 +476,12 @@ shared_examples_for 'methods' do
|
|
430
476
|
let(:cache) { MockCache.new }
|
431
477
|
|
432
478
|
before do
|
433
|
-
@query =
|
479
|
+
@query = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object')
|
434
480
|
.with(headers: { 'Authorization' => "OAuth #{oauth_token}" })
|
435
481
|
.to_return(status: 401, body: fixture('expired_session_response')).then
|
436
482
|
.to_return(status: 200, body: fixture('sobject/query_success_response'))
|
437
483
|
|
438
|
-
@login =
|
439
|
-
.with(:body => "grant_type=password&client_id=client_id&client_secret=" \
|
484
|
+
@login = stub_login_request(body: "grant_type=password&client_id=client_id&client_secret=" \
|
440
485
|
"client_secret&username=foo&password=barsecurity_token")
|
441
486
|
.to_return(status: 200, body: fixture(:auth_success_response))
|
442
487
|
end
|
@@ -485,7 +530,7 @@ end
|
|
485
530
|
|
486
531
|
describe 'without mashify middleware' do
|
487
532
|
before do
|
488
|
-
client.
|
533
|
+
client.middleware.delete(Restforce::Middleware::Mashify)
|
489
534
|
end
|
490
535
|
|
491
536
|
describe Restforce::Client do
|
data/spec/lib/config_spec.rb
CHANGED
@@ -15,15 +15,15 @@ describe Restforce do
|
|
15
15
|
its(:host) { should eq 'login.salesforce.com' }
|
16
16
|
its(:authentication_retries) { should eq 3 }
|
17
17
|
[:username, :password, :security_token, :client_id, :client_secret,
|
18
|
-
:oauth_token, :refresh_token, :instance_url].each do |attr|
|
18
|
+
:oauth_token, :refresh_token, :instance_url, :compress].each do |attr|
|
19
19
|
its(attr) { should be_nil }
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#configure' do
|
25
|
-
[:username, :password, :security_token, :client_id, :client_secret,
|
26
|
-
:oauth_token, :refresh_token, :instance_url, :api_version, :host].each do |attr|
|
25
|
+
[:username, :password, :security_token, :client_id, :client_secret, :compress,
|
26
|
+
:oauth_token, :refresh_token, :instance_url, :api_version, :host, :authentication_retries].each do |attr|
|
27
27
|
it "allows #{attr} to be set" do
|
28
28
|
Restforce.configure do |config|
|
29
29
|
config.send("#{attr}=", 'foobar')
|
@@ -16,17 +16,15 @@ describe Restforce::Middleware::Authentication::Password do
|
|
16
16
|
|
17
17
|
it_behaves_like 'authentication middleware' do
|
18
18
|
let(:success_request) do
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
to_return(:status => 200, :body => fixture(:auth_success_response))
|
19
|
+
stub_login_request(body: "grant_type=password&client_id=client_id&client_secret=" \
|
20
|
+
"client_secret&username=foo&password=barsecurity_token")
|
21
|
+
.to_return(status: 200, body: fixture(:auth_success_response))
|
23
22
|
end
|
24
23
|
|
25
24
|
let(:fail_request) do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
to_return(:status => 400, :body => fixture(:auth_error_response))
|
25
|
+
stub_login_request(body: "grant_type=password&client_id=client_id&client_secret=" \
|
26
|
+
"client_secret&username=foo&password=barsecurity_token")
|
27
|
+
.to_return(status: 400, body: fixture(:auth_error_response))
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
@@ -14,17 +14,15 @@ describe Restforce::Middleware::Authentication::Token do
|
|
14
14
|
|
15
15
|
it_behaves_like 'authentication middleware' do
|
16
16
|
let(:success_request) do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
to_return(:status => 200, :body => fixture(:auth_success_response))
|
17
|
+
stub_login_request(body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
18
|
+
"client_id=client_id&client_secret=client_secret")
|
19
|
+
.to_return(:status => 200, :body => fixture(:auth_success_response))
|
21
20
|
end
|
22
21
|
|
23
22
|
let(:fail_request) do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
to_return(:status => 400, :body => fixture(:refresh_error_response))
|
23
|
+
stub_login_request(body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
24
|
+
"client_id=client_id&client_secret=client_secret")
|
25
|
+
.to_return(:status => 400, :body => fixture(:refresh_error_response))
|
28
26
|
end
|
29
27
|
end
|
30
28
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Restforce::Middleware::Gzip do
|
4
|
+
let(:app) { double('app') }
|
5
|
+
let(:env) { { request_headers: {}, response_headers: {} } }
|
6
|
+
let(:options) { { oauth_token: 'token' } }
|
7
|
+
let(:middleware) { described_class.new app, nil, options }
|
8
|
+
|
9
|
+
# Return a gzipped string.
|
10
|
+
def gzip(str)
|
11
|
+
StringIO.new.tap do |io|
|
12
|
+
gz = Zlib::GzipWriter.new(io)
|
13
|
+
gz.write(str)
|
14
|
+
gz.close
|
15
|
+
end.string
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'request' do
|
19
|
+
before do
|
20
|
+
app.should_receive(:on_complete) { middleware.on_complete(env) }
|
21
|
+
app.should_receive(:call).and_return(app)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when :compress is false' do
|
25
|
+
it 'does not add the Accept-Encoding header' do
|
26
|
+
middleware.call(env)
|
27
|
+
env[:request_headers]['Accept-Encoding'].should be_nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when :compress is true' do
|
32
|
+
before do
|
33
|
+
options[:compress] = true
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'adds the Accept-Encoding header' do
|
37
|
+
middleware.call(env)
|
38
|
+
env[:request_headers]['Accept-Encoding'].should eq 'gzip'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'response' do
|
44
|
+
before do
|
45
|
+
app.should_receive(:on_complete) { middleware.on_complete(env) }
|
46
|
+
app.should_receive(:call) do
|
47
|
+
env[:body] = gzip fixture('sobject/query_success_response')
|
48
|
+
env[:response_headers]['Content-Encoding'] = 'gzip'
|
49
|
+
app
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'decompresses the response body' do
|
54
|
+
middleware.call(env)
|
55
|
+
env[:body].should eq fixture('sobject/query_success_response')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.decompress' do
|
60
|
+
let(:body) { gzip fixture('sobject/query_success_response') }
|
61
|
+
|
62
|
+
subject { middleware.decompress(body) }
|
63
|
+
it { should eq fixture('sobject/query_success_response') }
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '.gzipped?' do
|
67
|
+
subject { middleware.gzipped?(env) }
|
68
|
+
|
69
|
+
context 'when gzipped' do
|
70
|
+
before do
|
71
|
+
env[:response_headers]['Content-Encoding'] = 'gzip'
|
72
|
+
end
|
73
|
+
|
74
|
+
it { should be_true }
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when not gzipped' do
|
78
|
+
it { should be_false }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -2,15 +2,26 @@ module FixtureHelpers
|
|
2
2
|
|
3
3
|
def stub_api_request(endpoint, options = {})
|
4
4
|
options = {
|
5
|
-
:
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
5
|
+
method: :get,
|
6
|
+
status: 200,
|
7
|
+
api_version: '24.0',
|
8
|
+
with: nil
|
9
9
|
}.merge(options)
|
10
10
|
|
11
11
|
stub = stub_request(options[:method], %r{/services/data/v#{options[:api_version]}/#{endpoint}})
|
12
|
-
stub = stub.with(:
|
13
|
-
stub = stub.to_return(:
|
12
|
+
stub = stub.with(body: options[:body]) if options[:body]
|
13
|
+
stub = stub.to_return(status: options[:status], body: fixture(options[:with])) if options[:with]
|
14
|
+
stub
|
15
|
+
end
|
16
|
+
|
17
|
+
def stub_login_request(options = {})
|
18
|
+
options = {
|
19
|
+
body: nil
|
20
|
+
}.merge(options)
|
21
|
+
|
22
|
+
stub = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
|
23
|
+
stub = stub.with(body: options[:body]) if options[:body]
|
24
|
+
stub
|
14
25
|
end
|
15
26
|
|
16
27
|
def fixture(f)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restforce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-09-
|
12
|
+
date: 2012-09-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -180,6 +180,7 @@ files:
|
|
180
180
|
- lib/restforce/middleware/authentication/token.rb
|
181
181
|
- lib/restforce/middleware/authorization.rb
|
182
182
|
- lib/restforce/middleware/caching.rb
|
183
|
+
- lib/restforce/middleware/gzip.rb
|
183
184
|
- lib/restforce/middleware/instance_url.rb
|
184
185
|
- lib/restforce/middleware/logger.rb
|
185
186
|
- lib/restforce/middleware/mashify.rb
|
@@ -228,6 +229,7 @@ files:
|
|
228
229
|
- spec/lib/middleware/authentication/token_spec.rb
|
229
230
|
- spec/lib/middleware/authentication_spec.rb
|
230
231
|
- spec/lib/middleware/authorization_spec.rb
|
232
|
+
- spec/lib/middleware/gzip_spec.rb
|
231
233
|
- spec/lib/middleware/instance_url_spec.rb
|
232
234
|
- spec/lib/middleware/logger_spec.rb
|
233
235
|
- spec/lib/middleware/mashify_spec.rb
|
@@ -301,6 +303,7 @@ test_files:
|
|
301
303
|
- spec/lib/middleware/authentication/token_spec.rb
|
302
304
|
- spec/lib/middleware/authentication_spec.rb
|
303
305
|
- spec/lib/middleware/authorization_spec.rb
|
306
|
+
- spec/lib/middleware/gzip_spec.rb
|
304
307
|
- spec/lib/middleware/instance_url_spec.rb
|
305
308
|
- spec/lib/middleware/logger_spec.rb
|
306
309
|
- spec/lib/middleware/mashify_spec.rb
|