restforce 4.2.1 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +14 -9
  3. data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
  4. data/.github/dependabot.yml +19 -0
  5. data/.rubocop.yml +5 -4
  6. data/CHANGELOG.md +96 -0
  7. data/CONTRIBUTING.md +21 -1
  8. data/Dockerfile +31 -0
  9. data/Gemfile +10 -7
  10. data/README.md +94 -21
  11. data/UPGRADING.md +38 -0
  12. data/docker-compose.yml +7 -0
  13. data/lib/restforce/abstract_client.rb +1 -0
  14. data/lib/restforce/collection.rb +27 -4
  15. data/lib/restforce/concerns/api.rb +2 -2
  16. data/lib/restforce/concerns/base.rb +2 -2
  17. data/lib/restforce/concerns/caching.rb +7 -0
  18. data/lib/restforce/concerns/composite_api.rb +104 -0
  19. data/lib/restforce/concerns/picklists.rb +2 -2
  20. data/lib/restforce/concerns/streaming.rb +1 -3
  21. data/lib/restforce/config.rb +4 -1
  22. data/lib/restforce/error_code.rb +647 -0
  23. data/lib/restforce/file_part.rb +24 -0
  24. data/lib/restforce/mash.rb +8 -3
  25. data/lib/restforce/middleware/caching.rb +1 -1
  26. data/lib/restforce/middleware/logger.rb +8 -7
  27. data/lib/restforce/middleware/raise_error.rb +3 -4
  28. data/lib/restforce/middleware.rb +2 -0
  29. data/lib/restforce/version.rb +1 -1
  30. data/lib/restforce.rb +6 -7
  31. data/restforce.gemspec +11 -20
  32. data/spec/fixtures/sobject/list_view_results_success_response.json +151 -0
  33. data/spec/integration/abstract_client_spec.rb +41 -32
  34. data/spec/integration/data/client_spec.rb +6 -2
  35. data/spec/spec_helper.rb +24 -1
  36. data/spec/support/client_integration.rb +7 -7
  37. data/spec/support/concerns.rb +1 -1
  38. data/spec/support/fixture_helpers.rb +1 -3
  39. data/spec/support/middleware.rb +1 -2
  40. data/spec/unit/collection_spec.rb +38 -2
  41. data/spec/unit/concerns/api_spec.rb +11 -11
  42. data/spec/unit/concerns/authentication_spec.rb +6 -6
  43. data/spec/unit/concerns/caching_spec.rb +26 -0
  44. data/spec/unit/concerns/composite_api_spec.rb +143 -0
  45. data/spec/unit/concerns/connection_spec.rb +2 -2
  46. data/spec/unit/concerns/streaming_spec.rb +4 -4
  47. data/spec/unit/config_spec.rb +1 -1
  48. data/spec/unit/error_code_spec.rb +61 -0
  49. data/spec/unit/mash_spec.rb +5 -0
  50. data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +4 -4
  51. data/spec/unit/middleware/authentication/password_spec.rb +2 -2
  52. data/spec/unit/middleware/authentication/token_spec.rb +2 -2
  53. data/spec/unit/middleware/authentication_spec.rb +11 -5
  54. data/spec/unit/middleware/gzip_spec.rb +2 -2
  55. data/spec/unit/middleware/raise_error_spec.rb +29 -10
  56. data/spec/unit/signed_request_spec.rb +1 -1
  57. metadata +41 -125
  58. data/lib/restforce/upload_io.rb +0 -9
@@ -11,9 +11,10 @@ module Restforce
11
11
  # appropriate Restforce::Collection, Restforce::SObject and
12
12
  # Restforce::Mash objects.
13
13
  def build(val, client)
14
- if val.is_a?(Array)
14
+ case val
15
+ when Array
15
16
  val.collect { |a_val| self.build(a_val, client) }
16
- elsif val.is_a?(Hash)
17
+ when Hash
17
18
  self.klass(val).new(val, client)
18
19
  else
19
20
  val
@@ -28,7 +29,7 @@ module Restforce
28
29
  # of sobject records.
29
30
  Restforce::Collection
30
31
  elsif val.key? 'attributes'
31
- case (val['attributes']['type'])
32
+ case val.dig('attributes', 'type')
32
33
  when "Attachment"
33
34
  Restforce::Attachment
34
35
  when "Document"
@@ -55,6 +56,9 @@ module Restforce
55
56
  self.class.new(self, @client, self.default)
56
57
  end
57
58
 
59
+ # The #convert_value method and its signature are part of Hashie::Mash's API, so we
60
+ # can't unilaterally decide to change `duping` to be a keyword argument
61
+ # rubocop:disable Style/OptionalBooleanParameter
58
62
  def convert_value(val, duping = false)
59
63
  case val
60
64
  when self.class
@@ -68,5 +72,6 @@ module Restforce
68
72
  val
69
73
  end
70
74
  end
75
+ # rubocop:enable Style/OptionalBooleanParameter
71
76
  end
72
77
  end
@@ -18,7 +18,7 @@ module Restforce
18
18
  end
19
19
 
20
20
  def use_cache?
21
- @options.fetch(:use_cache, true)
21
+ @options[:use_cache]
22
22
  end
23
23
 
24
24
  def hashed_auth_header(env)
@@ -11,7 +11,7 @@ module Restforce
11
11
  @options = options
12
12
  @logger = logger || begin
13
13
  require 'logger'
14
- ::Logger.new(STDOUT)
14
+ ::Logger.new($stdout)
15
15
  end
16
16
  end
17
17
 
@@ -20,9 +20,9 @@ module Restforce
20
20
  def call(env)
21
21
  debug('request') do
22
22
  dump url: env[:url].to_s,
23
- method: env[:method],
24
- headers: env[:request_headers],
25
- body: env[:body]
23
+ method: env[:method],
24
+ headers: env[:request_headers],
25
+ body: env[:body]
26
26
  end
27
27
  super
28
28
  end
@@ -30,13 +30,14 @@ module Restforce
30
30
  def on_complete(env)
31
31
  debug('response') do
32
32
  dump status: env[:status].to_s,
33
- headers: env[:response_headers],
34
- body: env[:body]
33
+ headers: env[:response_headers],
34
+ body: env[:body]
35
35
  end
36
36
  end
37
37
 
38
38
  def dump(hash)
39
- "\n" + hash.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
39
+ dumped_pairs = hash.map { |k, v| " #{k}: #{v.inspect}" }.join("\n")
40
+ "\n#{dumped_pairs}"
40
41
  end
41
42
  end
42
43
  end
@@ -11,9 +11,9 @@ module Restforce
11
11
  response_values
12
12
  )
13
13
  when 401
14
- raise Restforce::UnauthorizedError, message
14
+ raise Restforce::UnauthorizedError.new(message, response_values)
15
15
  when 404
16
- raise Restforce::NotFoundError, message
16
+ raise Restforce::NotFoundError.new(message, response_values)
17
17
  when 413
18
18
  raise Restforce::EntityTooLargeError.new(
19
19
  "413: Request Entity Too Large",
@@ -56,8 +56,7 @@ module Restforce
56
56
  def exception_class_for_error_code(error_code)
57
57
  return Restforce::ResponseError unless ERROR_CODE_MATCHER.match?(error_code)
58
58
 
59
- constant_name = error_code.split('_').map(&:capitalize).join.to_sym
60
- Restforce::ErrorCode.const_get(constant_name)
59
+ Restforce::ErrorCode.get_exception_class(error_code)
61
60
  end
62
61
  end
63
62
  end
@@ -16,6 +16,8 @@ module Restforce
16
16
  autoload :CustomHeaders, 'restforce/middleware/custom_headers'
17
17
 
18
18
  def initialize(app, client, options)
19
+ super(app)
20
+
19
21
  @app = app
20
22
  @client = client
21
23
  @options = options
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Restforce
4
- VERSION = '4.2.1'
4
+ VERSION = '5.2.4'
5
5
  end
data/lib/restforce.rb CHANGED
@@ -15,7 +15,8 @@ module Restforce
15
15
  autoload :Middleware, 'restforce/middleware'
16
16
  autoload :Attachment, 'restforce/attachment'
17
17
  autoload :Document, 'restforce/document'
18
- autoload :UploadIO, 'restforce/upload_io'
18
+ autoload :FilePart, 'restforce/file_part'
19
+ autoload :UploadIO, 'restforce/file_part' # Deprecated
19
20
  autoload :SObject, 'restforce/sobject'
20
21
  autoload :Client, 'restforce/client'
21
22
  autoload :Mash, 'restforce/mash'
@@ -31,6 +32,7 @@ module Restforce
31
32
  autoload :Base, 'restforce/concerns/base'
32
33
  autoload :API, 'restforce/concerns/api'
33
34
  autoload :BatchAPI, 'restforce/concerns/batch_api'
35
+ autoload :CompositeAPI, 'restforce/concerns/composite_api'
34
36
  end
35
37
 
36
38
  module Data
@@ -44,9 +46,10 @@ module Restforce
44
46
  Error = Class.new(StandardError)
45
47
  ServerError = Class.new(Error)
46
48
  AuthenticationError = Class.new(Error)
47
- UnauthorizedError = Class.new(Error)
49
+ UnauthorizedError = Class.new(Faraday::ClientError)
48
50
  APIVersionError = Class.new(Error)
49
51
  BatchAPIError = Class.new(Error)
52
+ CompositeAPIError = Class.new(Error)
50
53
 
51
54
  # Inherit from Faraday::ResourceNotFound for backwards-compatibility
52
55
  # Consumers of this library that rescue and handle Faraday::ResourceNotFound
@@ -60,11 +63,7 @@ module Restforce
60
63
  MatchesMultipleError= Class.new(ResponseError)
61
64
  EntityTooLargeError = Class.new(ResponseError)
62
65
 
63
- module ErrorCode
64
- def self.const_missing(constant_name)
65
- const_set constant_name, Class.new(ResponseError)
66
- end
67
- end
66
+ require 'restforce/error_code'
68
67
 
69
68
  class << self
70
69
  # Alias for Restforce::Data::Client.new
data/restforce.gemspec CHANGED
@@ -3,11 +3,11 @@
3
3
  require File.expand_path('lib/restforce/version', __dir__)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
- gem.authors = ["Eric J. Holmes", "Tim Rogers"]
7
- gem.email = ["eric@ejholmes.net", "tim@gocardless.com"]
8
- gem.description = 'A lightweight ruby client for the Salesforce REST API.'
9
- gem.summary = 'A lightweight ruby client for the Salesforce REST API.'
10
- gem.homepage = "http://restforce.org/"
6
+ gem.authors = ["Tim Rogers", "Eric J. Holmes"]
7
+ gem.email = ["me@timrogers.co.uk", "eric@ejholmes.net"]
8
+ gem.description = 'A lightweight Ruby client for the Salesforce REST API'
9
+ gem.summary = 'A lightweight Ruby client for the Salesforce REST API'
10
+ gem.homepage = "https://restforce.github.io/"
11
11
  gem.license = "MIT"
12
12
 
13
13
  gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
@@ -19,23 +19,14 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.metadata = {
21
21
  'source_code_uri' => 'https://github.com/restforce/restforce',
22
- 'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
22
+ 'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md',
23
+ 'rubygems_mfa_required' => 'true'
23
24
  }
24
25
 
25
- gem.required_ruby_version = '>= 2.4'
26
+ gem.required_ruby_version = '>= 2.6'
26
27
 
27
- gem.add_dependency 'faraday', '<= 1.0', '>= 0.9.0'
28
- gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 1.0']
29
-
30
- gem.add_dependency 'json', '>= 1.7.5'
28
+ gem.add_dependency 'faraday', '< 1.9.0', '>= 0.9.0'
29
+ gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
30
+ gem.add_dependency 'hashie', '>= 1.2.0', '< 6.0'
31
31
  gem.add_dependency 'jwt', ['>= 1.5.6']
32
-
33
- gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
34
-
35
- gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
36
- gem.add_development_dependency 'rspec', '~> 2.14.0'
37
- gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
38
- gem.add_development_dependency 'rubocop', '~> 0.77.0'
39
- gem.add_development_dependency 'simplecov', '~> 0.17.1'
40
- gem.add_development_dependency 'webmock', '~> 3.7.6'
41
32
  end
@@ -0,0 +1,151 @@
1
+ {
2
+ "columns" : [ {
3
+ "ascendingLabel" : "Z-A",
4
+ "descendingLabel" : "A-Z",
5
+ "fieldNameOrPath" : "Name",
6
+ "hidden" : false,
7
+ "label" : "Account Name",
8
+ "selectListItem" : "Name",
9
+ "sortDirection" : "ascending",
10
+ "sortIndex" : 0,
11
+ "sortable" : true,
12
+ "type" : "string"
13
+ }, {
14
+ "ascendingLabel" : "Z-A",
15
+ "descendingLabel" : "A-Z",
16
+ "fieldNameOrPath" : "Site",
17
+ "hidden" : false,
18
+ "label" : "Account Site",
19
+ "selectListItem" : "Site",
20
+ "sortDirection" : null,
21
+ "sortIndex" : null,
22
+ "sortable" : true,
23
+ "type" : "string"
24
+ }, {
25
+ "ascendingLabel" : "Z-A",
26
+ "descendingLabel" : "A-Z",
27
+ "fieldNameOrPath" : "BillingState",
28
+ "hidden" : false,
29
+ "label" : "Billing State/Province",
30
+ "selectListItem" : "BillingState",
31
+ "sortDirection" : null,
32
+ "sortIndex" : null,
33
+ "sortable" : true,
34
+ "type" : "string"
35
+ }, {
36
+ "ascendingLabel" : "9-0",
37
+ "descendingLabel" : "0-9",
38
+ "fieldNameOrPath" : "Phone",
39
+ "hidden" : false,
40
+ "label" : "Phone",
41
+ "selectListItem" : "Phone",
42
+ "sortDirection" : null,
43
+ "sortIndex" : null,
44
+ "sortable" : true,
45
+ "type" : "phone"
46
+ }, {
47
+ "ascendingLabel" : "Low to High",
48
+ "descendingLabel" : "High to Low",
49
+ "fieldNameOrPath" : "Type",
50
+ "hidden" : false,
51
+ "label" : "Type",
52
+ "selectListItem" : "toLabel(Type)",
53
+ "sortDirection" : null,
54
+ "sortIndex" : null,
55
+ "sortable" : true,
56
+ "type" : "picklist"
57
+ }, {
58
+ "ascendingLabel" : "Z-A",
59
+ "descendingLabel" : "A-Z",
60
+ "fieldNameOrPath" : "Owner.Alias",
61
+ "hidden" : false,
62
+ "label" : "Account Owner Alias",
63
+ "selectListItem" : "Owner.Alias",
64
+ "sortDirection" : null,
65
+ "sortIndex" : null,
66
+ "sortable" : true,
67
+ "type" : "string"
68
+ }, {
69
+ "ascendingLabel" : null,
70
+ "descendingLabel" : null,
71
+ "fieldNameOrPath" : "Id",
72
+ "hidden" : true,
73
+ "label" : "Account ID",
74
+ "selectListItem" : "Id",
75
+ "sortDirection" : null,
76
+ "sortIndex" : null,
77
+ "sortable" : false,
78
+ "type" : "id"
79
+ }, {
80
+ "ascendingLabel" : null,
81
+ "descendingLabel" : null,
82
+ "fieldNameOrPath" : "CreatedDate",
83
+ "hidden" : true,
84
+ "label" : "Created Date",
85
+ "selectListItem" : "CreatedDate",
86
+ "sortDirection" : null,
87
+ "sortIndex" : null,
88
+ "sortable" : false,
89
+ "type" : "datetime"
90
+ }, {
91
+ "ascendingLabel" : null,
92
+ "descendingLabel" : null,
93
+ "fieldNameOrPath" : "LastModifiedDate",
94
+ "hidden" : true,
95
+ "label" : "Last Modified Date",
96
+ "selectListItem" : "LastModifiedDate",
97
+ "sortDirection" : null,
98
+ "sortIndex" : null,
99
+ "sortable" : false,
100
+ "type" : "datetime"
101
+ }, {
102
+ "ascendingLabel" : null,
103
+ "descendingLabel" : null,
104
+ "fieldNameOrPath" : "SystemModstamp",
105
+ "hidden" : true,
106
+ "label" : "System Modstamp",
107
+ "selectListItem" : "SystemModstamp",
108
+ "sortDirection" : null,
109
+ "sortIndex" : null,
110
+ "sortable" : false,
111
+ "type" : "datetime"
112
+ } ],
113
+ "developerName" : "MyAccounts",
114
+ "done" : true,
115
+ "id" : "00BD0000005WcCN",
116
+ "label" : "My Accounts",
117
+ "records" : [ {
118
+ "columns" : [ {
119
+ "fieldNameOrPath" : "Name",
120
+ "value" : "Burlington Textiles Corp of America"
121
+ }, {
122
+ "fieldNameOrPath" : "Site",
123
+ "value" : null
124
+ }, {
125
+ "fieldNameOrPath" : "BillingState",
126
+ "value" : "NC"
127
+ }, {
128
+ "fieldNameOrPath" : "Phone",
129
+ "value" : "(336) 222-7000"
130
+ }, {
131
+ "fieldNameOrPath" : "Type",
132
+ "value" : "Customer - Direct"
133
+ }, {
134
+ "fieldNameOrPath" : "Owner.Alias",
135
+ "value" : "TUser"
136
+ }, {
137
+ "fieldNameOrPath" : "Id",
138
+ "value" : "001D000000JliSTIAZ"
139
+ }, {
140
+ "fieldNameOrPath" : "CreatedDate",
141
+ "value" : "Fri Aug 01 21:15:46 GMT 2014"
142
+ }, {
143
+ "fieldNameOrPath" : "LastModifiedDate",
144
+ "value" : "Fri Aug 01 21:15:46 GMT 2014"
145
+ }, {
146
+ "fieldNameOrPath" : "SystemModstamp",
147
+ "value" : "Fri Aug 01 21:15:46 GMT 2014"
148
+ } ]
149
+ } ],
150
+ "size" : 1
151
+ }
@@ -86,22 +86,34 @@ shared_examples_for Restforce::AbstractClient do
86
86
  end
87
87
 
88
88
  context 'with multipart' do
89
- # rubocop:disable Metrics/LineLength
89
+ # rubocop:disable Layout/LineLength
90
90
  requests 'sobjects/Account',
91
91
  method: :post,
92
- with_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),
92
+ with_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),
93
93
  fixture: 'sobject/create_success_response'
94
- # rubocop:enable Metrics/LineLength
94
+ # rubocop:enable Layout/LineLength
95
95
 
96
96
  subject do
97
97
  client.create('Account', Name: 'Foobar',
98
- Blob: Restforce::UploadIO.new(
98
+ Blob: Restforce::FilePart.new(
99
99
  File.expand_path('../fixtures/blob.jpg', __dir__),
100
100
  'image/jpeg'
101
101
  ))
102
102
  end
103
103
 
104
104
  it { should eq 'some_id' }
105
+
106
+ context 'with deprecated UploadIO' do
107
+ subject do
108
+ client.create('Account', Name: 'Foobar',
109
+ Blob: Restforce::UploadIO.new(
110
+ File.expand_path('../fixtures/blob.jpg', __dir__),
111
+ 'image/jpeg'
112
+ ))
113
+ end
114
+
115
+ it { should eq 'some_id' }
116
+ end
105
117
  end
106
118
  end
107
119
 
@@ -117,18 +129,15 @@ shared_examples_for Restforce::AbstractClient do
117
129
  JSON.parse(fixture('sobject/delete_error_response'))
118
130
  end
119
131
 
120
- subject do
121
- lambda do
122
- client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar')
123
- end
124
- end
132
+ it "raises Faraday::ResourceNotFound" do
133
+ expect { client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar') }.
134
+ to raise_error do |exception|
135
+ expect(exception).to be_a(Faraday::ResourceNotFound)
125
136
 
126
- it {
127
- should raise_error(
128
- Faraday::ResourceNotFound,
129
- "#{error.first['errorCode']}: #{error.first['message']}"
130
- )
131
- }
137
+ expect(exception.message).
138
+ to start_with("#{error.first['errorCode']}: #{error.first['message']}")
139
+ end
140
+ end
132
141
  end
133
142
  end
134
143
 
@@ -146,7 +155,7 @@ shared_examples_for Restforce::AbstractClient do
146
155
  fixture: 'sobject/delete_error_response'
147
156
 
148
157
  subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') }
149
- it { should be_false }
158
+ it { should be false }
150
159
  end
151
160
 
152
161
  context 'with success' do
@@ -160,7 +169,7 @@ shared_examples_for Restforce::AbstractClient do
160
169
  client.update('Account', key => '001D000000INjVe', :Name => 'Foobar')
161
170
  end
162
171
 
163
- it { should be_true }
172
+ it { should be true }
164
173
  end
165
174
  end
166
175
  end
@@ -179,7 +188,7 @@ shared_examples_for Restforce::AbstractClient do
179
188
  Name: 'Foobar')
180
189
  end
181
190
 
182
- it { should be_true }
191
+ it { should be true }
183
192
  end
184
193
 
185
194
  context 'with string external Id key' do
@@ -188,7 +197,7 @@ shared_examples_for Restforce::AbstractClient do
188
197
  'Name' => 'Foobar')
189
198
  end
190
199
 
191
- it { should be_true }
200
+ it { should be true }
192
201
  end
193
202
  end
194
203
 
@@ -245,14 +254,14 @@ shared_examples_for Restforce::AbstractClient do
245
254
  context 'with success' do
246
255
  requests 'sobjects/Account/001D000000INjVe', method: :delete
247
256
 
248
- it { should be_true }
257
+ it { should be true }
249
258
  end
250
259
 
251
260
  context 'with a space in the id' do
252
261
  subject(:destroy!) { client.destroy!('Account', '001D000000 INjVe') }
253
262
  requests 'sobjects/Account/001D000000%20INjVe', method: :delete
254
263
 
255
- it { should be_true }
264
+ it { should be true }
256
265
  end
257
266
  end
258
267
 
@@ -265,13 +274,13 @@ shared_examples_for Restforce::AbstractClient do
265
274
  method: :delete,
266
275
  status: 404
267
276
 
268
- it { should be_false }
277
+ it { should be false }
269
278
  end
270
279
 
271
280
  context 'with success' do
272
281
  requests 'sobjects/Account/001D000000INjVe', method: :delete
273
282
 
274
- it { should be_true }
283
+ it { should be true }
275
284
  end
276
285
  end
277
286
 
@@ -356,8 +365,8 @@ shared_examples_for Restforce::AbstractClient do
356
365
  before do
357
366
  @request = stub_login_request(
358
367
  with_body: "grant_type=password&client_id=client_id" \
359
- "&client_secret=client_secret&username=foo" \
360
- "&password=barsecurity_token"
368
+ "&client_secret=client_secret&username=foo" \
369
+ "&password=barsecurity_token"
361
370
  ).to_return(status: 200, body: fixture(:auth_success_response))
362
371
  end
363
372
 
@@ -408,8 +417,8 @@ shared_examples_for Restforce::AbstractClient do
408
417
 
409
418
  @query_request = stub_login_request(
410
419
  with_body: "grant_type=password&client_id=client_id" \
411
- "&client_secret=client_secret&username=foo&" \
412
- "password=barsecurity_token"
420
+ "&client_secret=client_secret&username=foo&" \
421
+ "password=barsecurity_token"
413
422
  ).to_return(status: 200, body: fixture(:auth_success_response))
414
423
  end
415
424
 
@@ -425,15 +434,15 @@ shared_examples_for Restforce::AbstractClient do
425
434
  @query = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object').
426
435
  with(headers: { 'Authorization' => "OAuth #{oauth_token}" }).
427
436
  to_return(status: 401,
428
- body: fixture('expired_session_response'),
429
- headers: { 'Content-Type' => 'application/json' }).then.
437
+ body: fixture('expired_session_response'),
438
+ headers: { 'Content-Type' => 'application/json' }).then.
430
439
  to_return(status: 200,
431
- body: fixture('sobject/query_success_response'),
432
- headers: { 'Content-Type' => 'application/json' })
440
+ body: fixture('sobject/query_success_response'),
441
+ headers: { 'Content-Type' => 'application/json' })
433
442
 
434
443
  @login = stub_login_request(
435
444
  with_body: "grant_type=password&client_id=client_id&client_secret=" \
436
- "client_secret&username=foo&password=barsecurity_token"
445
+ "client_secret&username=foo&password=barsecurity_token"
437
446
  ).to_return(status: 200, body: fixture(:auth_success_response))
438
447
  end
439
448
 
@@ -98,17 +98,21 @@ shared_examples_for Restforce::Data::Client do
98
98
  end
99
99
 
100
100
  describe '.subscribe', event_machine: true do
101
+ let(:faye_double) { double('Faye') }
102
+
101
103
  context 'when given a single pushtopic' do
102
104
  it 'subscribes to the pushtopic' do
103
- client.faye.should_receive(:subscribe).with(['/topic/PushTopic'])
105
+ faye_double.should_receive(:subscribe).with(['/topic/PushTopic'])
106
+ client.stub faye: faye_double
104
107
  client.subscribe('PushTopic')
105
108
  end
106
109
  end
107
110
 
108
111
  context 'when given an array of pushtopics' do
109
112
  it 'subscribes to each pushtopic' do
110
- client.faye.should_receive(:subscribe).with(['/topic/PushTopic1',
113
+ faye_double.should_receive(:subscribe).with(['/topic/PushTopic1',
111
114
  '/topic/PushTopic2'])
115
+ client.stub faye: faye_double
112
116
  client.subscribe(%w[PushTopic1 PushTopic2])
113
117
  end
114
118
  end
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,8 @@ Bundler.require :default, :test
8
8
  require 'faye' unless RUBY_PLATFORM == 'java'
9
9
 
10
10
  require 'webmock/rspec'
11
+ require 'rspec/collection_matchers'
12
+ require 'rspec/its'
11
13
 
12
14
  WebMock.disable_net_connect!
13
15
 
@@ -15,8 +17,29 @@ RSpec.configure do |config|
15
17
  config.order = 'random'
16
18
  config.filter_run focus: true
17
19
  config.run_all_when_everything_filtered = true
20
+
21
+ original_stderr = $stderr
22
+ original_stdout = $stdout
23
+ config.before(:all) do
24
+ # Redirect stderr and stdout
25
+ $stderr = File.open(File::NULL, "w")
26
+ $stdout = File.open(File::NULL, "w")
27
+ end
28
+ config.after(:all) do
29
+ $stderr = original_stderr
30
+ $stdout = original_stdout
31
+ end
32
+
33
+ config.expect_with :rspec do |expectations|
34
+ expectations.syntax = %i[expect should]
35
+ end
36
+
37
+ config.mock_with :rspec do |mocks|
38
+ mocks.syntax = %i[expect should]
39
+ end
18
40
  end
19
41
 
20
42
  # Requires supporting ruby files with custom matchers and macros, etc,
21
43
  # in spec/support/ and its subdirectories.
22
- Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |f| require f }
44
+ paths = Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
45
+ paths.sort.each { |f| require f }
@@ -5,7 +5,7 @@ module ClientIntegrationExampleGroup
5
5
  base.class_eval do
6
6
  let(:oauth_token) do
7
7
  '00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6P' \
8
- 'E3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs'
8
+ 'E3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs'
9
9
  end
10
10
 
11
11
  let(:refresh_token) { 'refresh' }
@@ -39,13 +39,13 @@ module ClientIntegrationExampleGroup
39
39
  end
40
40
 
41
41
  RSpec.configure do |config|
42
+ describes = lambda do |described|
43
+ described <= Restforce::AbstractClient
44
+ end
45
+
42
46
  config.include self,
43
- example_group: {
44
- describes: lambda do |described|
45
- described <= Restforce::AbstractClient
46
- end,
47
- file_path: %r{spec/integration}
48
- }
47
+ file_path: %r{spec/integration},
48
+ describes: describes
49
49
 
50
50
  config.before mashify: false do
51
51
  base_options.merge!(mashify: false)
@@ -15,6 +15,6 @@ module ConcernsExampleGroup
15
15
  end
16
16
 
17
17
  RSpec.configure do |config|
18
- config.include self, example_group: { file_path: %r{spec/unit/concerns} }
18
+ config.include self, file_path: %r{spec/unit/concerns}
19
19
  end
20
20
  end
@@ -22,9 +22,7 @@ module FixtureHelpers
22
22
  end
23
23
 
24
24
  def stub_login_request(*)
25
- stub = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
26
-
27
- stub
25
+ stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
28
26
  end
29
27
 
30
28
  def fixture(filename)
@@ -19,8 +19,7 @@ module MiddlewareExampleGroup
19
19
  end
20
20
 
21
21
  RSpec.configure do |config|
22
- config.include self,
23
- example_group: { file_path: %r{spec/unit/middleware} }
22
+ config.include self, file_path: %r{spec/unit/middleware}
24
23
  end
25
24
  end
26
25