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 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
- **Log Output**
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
- I, [2012-09-11T21:54:00.488991 #24032] INFO -- : post https://login.salesforce.com/services/oauth2/token
352
- D, [2012-09-11T21:54:00.489078 #24032] DEBUG -- request:
353
- I, [2012-09-11T21:54:00.997295 #24032] INFO -- Status: 200
354
- D, [2012-09-11T21:54:00.997391 #24032] DEBUG -- response headers: server: ""
355
- content-type: "application/json; charset=UTF-8"
356
- transfer-encoding: "chunked"
357
- date: "Wed, 12 Sep 2012 04:53:59 GMT"
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
@@ -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
@@ -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 - 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).
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 - 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).
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 - The oauth client id to use. Needed for both
21
- # password and oauth authentication
22
- # :client_secret - The oauth client secret to use.
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 - The String hostname to use during
25
- # authentication requests (default: 'login.salesforce.com').
24
+ # :host - The String hostname to use during
25
+ # authentication requests (default: 'login.salesforce.com').
26
26
  #
27
- # :api_version - The String REST api version to use (default: '24.0')
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
- encryped_secret, payload = message.split('.')
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
- connection.builder.handlers.find { |handler| handler == Restforce::Middleware::Mashify }
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
@@ -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'
@@ -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 = 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 (ie. A status of 401 is returned). The middleware
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
@@ -17,7 +17,7 @@ module Restforce
17
17
  end
18
18
 
19
19
  def body
20
- @body ||= JSON.parse(@env[:body])
20
+ JSON.parse(@env[:body])
21
21
  end
22
22
  end
23
23
  end
@@ -1,3 +1,3 @@
1
1
  module Restforce
2
- VERSION = '0.1.3'
2
+ VERSION = '0.1.4'
3
3
  end
@@ -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, with: 'sobject/describe_sobjects_success_response'
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', with: 'sobject/sobject_describe_success_response'
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', with: 'sobject/query_success_response'
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', with: 'sobject/search_success_response'
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', with: 'sobject/org_query_response'
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', with: 'sobject/create_success_response', method: :post, body: "{\"Name\":\"Foobar\"}"
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', with: 'sobject/create_success_response', method: :post, 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)
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', with: 'sobject/delete_error_response', method: :patch, body: "{\"Name\":\"Foobar\"}", status: 404
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', with: 'sobject/delete_error_response', method: :patch, body: "{\"Name\":\"Foobar\"}", status: 404
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', method: :patch, body: "{\"Name\":\"Foobar\"}"
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', method: :patch, body: "{\"Name\":\"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', method: :patch, body: "{\"Name\":\"Foobar\"}", with: 'sobject/upsert_created_success_response'
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', with: 'sobject/delete_error_response', method: :delete, status: 404
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', with: 'sobject/delete_error_response', method: :delete, status: 404
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 = stub_request(:post, "https://login.salesforce.com/services/oauth2/token").
322
- with(:body => "grant_type=password&client_id=client_id&client_secret=" \
323
- "client_secret&username=foo&password=barsecurity_token").
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', with: 'sobject/query_success_response'
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', status: 401, with: 'expired_session_response')
404
- @query_request = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
405
- .with(:body => "grant_type=password&client_id=client_id&client_secret=" \
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(:status => 200, :body => fixture(:auth_success_response))
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 = stub_request(:get, /query\?q=SELECT%20some,%20fields%20FROM%20object/)
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 = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
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.send(:connection).builder.delete(Restforce::Middleware::Mashify)
533
+ client.middleware.delete(Restforce::Middleware::Mashify)
489
534
  end
490
535
 
491
536
  describe Restforce::Client do
@@ -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
- stub_request(:post, "https://login.salesforce.com/services/oauth2/token").
20
- with(:body => "grant_type=password&client_id=client_id&client_secret=" \
21
- "client_secret&username=foo&password=barsecurity_token").
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
- stub_request(:post, "https://login.salesforce.com/services/oauth2/token").
27
- with(:body => "grant_type=password&client_id=client_id&client_secret=" \
28
- "client_secret&username=foo&password=barsecurity_token").
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
- stub_request(:post, "https://login.salesforce.com/services/oauth2/token").
18
- with(:body => "grant_type=refresh_token&refresh_token=refresh_token&" \
19
- "client_id=client_id&client_secret=client_secret").
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
- stub_request(:post, "https://login.salesforce.com/services/oauth2/token").
25
- with(:body => "grant_type=refresh_token&refresh_token=refresh_token&" \
26
- "client_id=client_id&client_secret=client_secret").
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
- :method => :get,
6
- :status => 200,
7
- :api_version => '24.0',
8
- :with => nil
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(:body => options[:body]) if options[:body]
13
- stub = stub.to_return(:status => options[:status], :body => options[:with] ? fixture(options[:with]) : '')
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.3
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-25 00:00:00.000000000 Z
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