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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +14 -9
- data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
- data/.github/dependabot.yml +19 -0
- data/.rubocop.yml +5 -4
- data/CHANGELOG.md +96 -0
- data/CONTRIBUTING.md +21 -1
- data/Dockerfile +31 -0
- data/Gemfile +10 -7
- data/README.md +94 -21
- data/UPGRADING.md +38 -0
- data/docker-compose.yml +7 -0
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/collection.rb +27 -4
- data/lib/restforce/concerns/api.rb +2 -2
- data/lib/restforce/concerns/base.rb +2 -2
- data/lib/restforce/concerns/caching.rb +7 -0
- data/lib/restforce/concerns/composite_api.rb +104 -0
- data/lib/restforce/concerns/picklists.rb +2 -2
- data/lib/restforce/concerns/streaming.rb +1 -3
- data/lib/restforce/config.rb +4 -1
- data/lib/restforce/error_code.rb +647 -0
- data/lib/restforce/file_part.rb +24 -0
- data/lib/restforce/mash.rb +8 -3
- data/lib/restforce/middleware/caching.rb +1 -1
- data/lib/restforce/middleware/logger.rb +8 -7
- data/lib/restforce/middleware/raise_error.rb +3 -4
- data/lib/restforce/middleware.rb +2 -0
- data/lib/restforce/version.rb +1 -1
- data/lib/restforce.rb +6 -7
- data/restforce.gemspec +11 -20
- data/spec/fixtures/sobject/list_view_results_success_response.json +151 -0
- data/spec/integration/abstract_client_spec.rb +41 -32
- data/spec/integration/data/client_spec.rb +6 -2
- data/spec/spec_helper.rb +24 -1
- data/spec/support/client_integration.rb +7 -7
- data/spec/support/concerns.rb +1 -1
- data/spec/support/fixture_helpers.rb +1 -3
- data/spec/support/middleware.rb +1 -2
- data/spec/unit/collection_spec.rb +38 -2
- data/spec/unit/concerns/api_spec.rb +11 -11
- data/spec/unit/concerns/authentication_spec.rb +6 -6
- data/spec/unit/concerns/caching_spec.rb +26 -0
- data/spec/unit/concerns/composite_api_spec.rb +143 -0
- data/spec/unit/concerns/connection_spec.rb +2 -2
- data/spec/unit/concerns/streaming_spec.rb +4 -4
- data/spec/unit/config_spec.rb +1 -1
- data/spec/unit/error_code_spec.rb +61 -0
- data/spec/unit/mash_spec.rb +5 -0
- data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +4 -4
- data/spec/unit/middleware/authentication/password_spec.rb +2 -2
- data/spec/unit/middleware/authentication/token_spec.rb +2 -2
- data/spec/unit/middleware/authentication_spec.rb +11 -5
- data/spec/unit/middleware/gzip_spec.rb +2 -2
- data/spec/unit/middleware/raise_error_spec.rb +29 -10
- data/spec/unit/signed_request_spec.rb +1 -1
- metadata +41 -125
- data/lib/restforce/upload_io.rb +0 -9
data/lib/restforce/mash.rb
CHANGED
@@ -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
|
-
|
14
|
+
case val
|
15
|
+
when Array
|
15
16
|
val.collect { |a_val| self.build(a_val, client) }
|
16
|
-
|
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 (
|
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
|
@@ -11,7 +11,7 @@ module Restforce
|
|
11
11
|
@options = options
|
12
12
|
@logger = logger || begin
|
13
13
|
require 'logger'
|
14
|
-
::Logger.new(
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
34
|
-
|
33
|
+
headers: env[:response_headers],
|
34
|
+
body: env[:body]
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def dump(hash)
|
39
|
-
|
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,
|
14
|
+
raise Restforce::UnauthorizedError.new(message, response_values)
|
15
15
|
when 404
|
16
|
-
raise Restforce::NotFoundError,
|
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
|
-
|
60
|
-
Restforce::ErrorCode.const_get(constant_name)
|
59
|
+
Restforce::ErrorCode.get_exception_class(error_code)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
data/lib/restforce/middleware.rb
CHANGED
data/lib/restforce/version.rb
CHANGED
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 :
|
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(
|
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
|
-
|
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"
|
7
|
-
gem.email = ["
|
8
|
-
gem.description = 'A lightweight
|
9
|
-
gem.summary = 'A lightweight
|
10
|
-
gem.homepage = "
|
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.
|
26
|
+
gem.required_ruby_version = '>= 2.6'
|
26
27
|
|
27
|
-
gem.add_dependency 'faraday', '
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<=
|
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
|
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
|
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
|
94
|
+
# rubocop:enable Layout/LineLength
|
95
95
|
|
96
96
|
subject do
|
97
97
|
client.create('Account', Name: 'Foobar',
|
98
|
-
Blob: Restforce::
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
360
|
-
|
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
|
-
|
412
|
-
|
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
|
-
|
429
|
-
|
437
|
+
body: fixture('expired_session_response'),
|
438
|
+
headers: { 'Content-Type' => 'application/json' }).then.
|
430
439
|
to_return(status: 200,
|
431
|
-
|
432
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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")]
|
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
|
-
|
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
|
-
|
44
|
-
|
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)
|
data/spec/support/concerns.rb
CHANGED
@@ -22,9 +22,7 @@ module FixtureHelpers
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def stub_login_request(*)
|
25
|
-
|
26
|
-
|
27
|
-
stub
|
25
|
+
stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
|
28
26
|
end
|
29
27
|
|
30
28
|
def fixture(filename)
|
data/spec/support/middleware.rb
CHANGED