restforce 3.0.1 → 5.1.1
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 +5 -5
- data/.circleci/config.yml +9 -9
- data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
- data/.github/dependabot.yml +19 -0
- data/.rubocop.yml +13 -14
- data/.rubocop_todo.yml +128 -81
- data/CHANGELOG.md +107 -1
- data/CONTRIBUTING.md +21 -1
- data/Dockerfile +31 -0
- data/Gemfile +10 -6
- data/README.md +168 -31
- data/UPGRADING.md +38 -0
- data/docker-compose.yml +7 -0
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/attachment.rb +1 -0
- data/lib/restforce/collection.rb +7 -2
- data/lib/restforce/concerns/api.rb +10 -7
- data/lib/restforce/concerns/authentication.rb +10 -0
- data/lib/restforce/concerns/base.rb +4 -2
- data/lib/restforce/concerns/batch_api.rb +87 -0
- data/lib/restforce/concerns/caching.rb +7 -0
- data/lib/restforce/concerns/canvas.rb +1 -0
- data/lib/restforce/concerns/connection.rb +3 -3
- data/lib/restforce/concerns/picklists.rb +4 -3
- data/lib/restforce/concerns/streaming.rb +73 -3
- data/lib/restforce/config.rb +8 -1
- data/lib/restforce/document.rb +1 -0
- data/lib/restforce/error_code.rb +638 -0
- data/lib/restforce/file_part.rb +24 -0
- data/lib/restforce/mash.rb +8 -3
- data/lib/restforce/middleware/authentication/jwt_bearer.rb +38 -0
- data/lib/restforce/middleware/authentication.rb +7 -3
- data/lib/restforce/middleware/caching.rb +1 -1
- data/lib/restforce/middleware/instance_url.rb +1 -1
- data/lib/restforce/middleware/logger.rb +8 -7
- data/lib/restforce/middleware/multipart.rb +1 -0
- data/lib/restforce/middleware/raise_error.rb +24 -9
- data/lib/restforce/middleware.rb +2 -0
- data/lib/restforce/signed_request.rb +1 -0
- data/lib/restforce/sobject.rb +1 -0
- data/lib/restforce/tooling/client.rb +3 -3
- data/lib/restforce/version.rb +1 -1
- data/lib/restforce.rb +21 -3
- data/restforce.gemspec +11 -20
- data/spec/fixtures/test_private.key +27 -0
- data/spec/integration/abstract_client_spec.rb +83 -33
- 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 +3 -5
- data/spec/support/middleware.rb +1 -2
- data/spec/unit/collection_spec.rb +20 -2
- data/spec/unit/concerns/api_spec.rb +12 -12
- data/spec/unit/concerns/authentication_spec.rb +39 -4
- data/spec/unit/concerns/batch_api_spec.rb +107 -0
- data/spec/unit/concerns/caching_spec.rb +26 -0
- data/spec/unit/concerns/connection_spec.rb +2 -2
- data/spec/unit/concerns/streaming_spec.rb +144 -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 +62 -0
- 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 +31 -4
- data/spec/unit/middleware/gzip_spec.rb +2 -2
- data/spec/unit/middleware/raise_error_spec.rb +57 -17
- data/spec/unit/signed_request_spec.rb +1 -1
- data/spec/unit/sobject_spec.rb +2 -5
- metadata +39 -108
- 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
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
|
5
|
+
module Restforce
|
6
|
+
class Middleware
|
7
|
+
class Authentication
|
8
|
+
class JWTBearer < Restforce::Middleware::Authentication
|
9
|
+
def params
|
10
|
+
{
|
11
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
12
|
+
assertion: jwt_bearer_token
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def jwt_bearer_token
|
19
|
+
JWT.encode claim_set, private_key, 'RS256'
|
20
|
+
end
|
21
|
+
|
22
|
+
def claim_set
|
23
|
+
{
|
24
|
+
iss: @options[:client_id],
|
25
|
+
sub: @options[:username],
|
26
|
+
aud: @options[:host],
|
27
|
+
iat: Time.now.utc.to_i,
|
28
|
+
exp: Time.now.utc.to_i + 180
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def private_key
|
33
|
+
OpenSSL::PKey::RSA.new(@options[:jwt_key])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -6,8 +6,9 @@ module Restforce
|
|
6
6
|
# will attempt to either reauthenticate (username and password) or refresh
|
7
7
|
# the oauth access token (if a refresh token is present).
|
8
8
|
class Middleware::Authentication < Restforce::Middleware
|
9
|
-
autoload :Password,
|
10
|
-
autoload :Token,
|
9
|
+
autoload :Password, 'restforce/middleware/authentication/password'
|
10
|
+
autoload :Token, 'restforce/middleware/authentication/token'
|
11
|
+
autoload :JWTBearer, 'restforce/middleware/authentication/jwt_bearer'
|
11
12
|
|
12
13
|
# Rescue from 401's, authenticate then raise the error again so the client
|
13
14
|
# can reissue the request.
|
@@ -62,7 +63,10 @@ module Restforce
|
|
62
63
|
|
63
64
|
# Internal: The parsed error response.
|
64
65
|
def error_message(response)
|
65
|
-
|
66
|
+
return response.status.to_s unless response.body
|
67
|
+
|
68
|
+
"#{response.body['error']}: #{response.body['error_description']} " \
|
69
|
+
"(#{response.status})"
|
66
70
|
end
|
67
71
|
|
68
72
|
# Featured detect form encoding.
|
@@ -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
|
@@ -6,23 +6,30 @@ module Restforce
|
|
6
6
|
@env = env
|
7
7
|
case env[:status]
|
8
8
|
when 300
|
9
|
-
raise
|
10
|
-
|
11
|
-
|
9
|
+
raise Restforce::MatchesMultipleError.new(
|
10
|
+
"300: The external ID provided matches more than one record",
|
11
|
+
response_values
|
12
|
+
)
|
12
13
|
when 401
|
13
|
-
raise Restforce::UnauthorizedError,
|
14
|
+
raise Restforce::UnauthorizedError.new(message, response_values)
|
14
15
|
when 404
|
15
|
-
raise
|
16
|
+
raise Restforce::NotFoundError.new(message, response_values)
|
16
17
|
when 413
|
17
|
-
raise
|
18
|
-
|
18
|
+
raise Restforce::EntityTooLargeError.new(
|
19
|
+
"413: Request Entity Too Large",
|
20
|
+
response_values
|
21
|
+
)
|
19
22
|
when 400...600
|
20
|
-
|
23
|
+
klass = exception_class_for_error_code(body['errorCode'])
|
24
|
+
raise klass.new(message, response_values)
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
28
|
def message
|
25
|
-
"#{body['errorCode']}: #{body['message']}"
|
29
|
+
message = "#{body['errorCode']}: #{body['message']}"
|
30
|
+
message << "\nRESPONSE: #{JSON.dump(@env[:body])}"
|
31
|
+
rescue StandardError
|
32
|
+
message # if JSON.dump fails, return message without extra detail
|
26
33
|
end
|
27
34
|
|
28
35
|
def body
|
@@ -43,5 +50,13 @@ module Restforce
|
|
43
50
|
body: @env[:body]
|
44
51
|
}
|
45
52
|
end
|
53
|
+
|
54
|
+
ERROR_CODE_MATCHER = /\A[A-Z_]+\z/.freeze
|
55
|
+
|
56
|
+
def exception_class_for_error_code(error_code)
|
57
|
+
return Restforce::ResponseError unless ERROR_CODE_MATCHER.match?(error_code)
|
58
|
+
|
59
|
+
Restforce::ErrorCode.get_exception_class(error_code)
|
60
|
+
end
|
46
61
|
end
|
47
62
|
end
|
data/lib/restforce/middleware.rb
CHANGED
data/lib/restforce/sobject.rb
CHANGED
data/lib/restforce/version.rb
CHANGED
data/lib/restforce.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'faraday'
|
4
4
|
require 'faraday_middleware'
|
5
5
|
require 'json'
|
6
|
+
require 'jwt'
|
6
7
|
|
7
8
|
require 'restforce/version'
|
8
9
|
require 'restforce/config'
|
@@ -14,7 +15,8 @@ module Restforce
|
|
14
15
|
autoload :Middleware, 'restforce/middleware'
|
15
16
|
autoload :Attachment, 'restforce/attachment'
|
16
17
|
autoload :Document, 'restforce/document'
|
17
|
-
autoload :
|
18
|
+
autoload :FilePart, 'restforce/file_part'
|
19
|
+
autoload :UploadIO, 'restforce/file_part' # Deprecated
|
18
20
|
autoload :SObject, 'restforce/sobject'
|
19
21
|
autoload :Client, 'restforce/client'
|
20
22
|
autoload :Mash, 'restforce/mash'
|
@@ -29,6 +31,7 @@ module Restforce
|
|
29
31
|
autoload :Verbs, 'restforce/concerns/verbs'
|
30
32
|
autoload :Base, 'restforce/concerns/base'
|
31
33
|
autoload :API, 'restforce/concerns/api'
|
34
|
+
autoload :BatchAPI, 'restforce/concerns/batch_api'
|
32
35
|
end
|
33
36
|
|
34
37
|
module Data
|
@@ -42,8 +45,23 @@ module Restforce
|
|
42
45
|
Error = Class.new(StandardError)
|
43
46
|
ServerError = Class.new(Error)
|
44
47
|
AuthenticationError = Class.new(Error)
|
45
|
-
UnauthorizedError = Class.new(
|
48
|
+
UnauthorizedError = Class.new(Faraday::ClientError)
|
46
49
|
APIVersionError = Class.new(Error)
|
50
|
+
BatchAPIError = Class.new(Error)
|
51
|
+
|
52
|
+
# Inherit from Faraday::ResourceNotFound for backwards-compatibility
|
53
|
+
# Consumers of this library that rescue and handle Faraday::ResourceNotFound
|
54
|
+
# can continue to do so.
|
55
|
+
NotFoundError = Class.new(Faraday::ResourceNotFound)
|
56
|
+
|
57
|
+
# Inherit from Faraday::ClientError for backwards-compatibility
|
58
|
+
# Consumers of this library that rescue and handle Faraday::ClientError
|
59
|
+
# can continue to do so.
|
60
|
+
ResponseError = Class.new(Faraday::ClientError)
|
61
|
+
MatchesMultipleError= Class.new(ResponseError)
|
62
|
+
EntityTooLargeError = Class.new(ResponseError)
|
63
|
+
|
64
|
+
require 'restforce/error_code'
|
47
65
|
|
48
66
|
class << self
|
49
67
|
# Alias for Restforce::Data::Client.new
|
@@ -74,7 +92,7 @@ module Restforce
|
|
74
92
|
self
|
75
93
|
end
|
76
94
|
end
|
77
|
-
Object.
|
95
|
+
Object.include Restforce::CoreExtensions unless Object.respond_to? :tap
|
78
96
|
end
|
79
97
|
|
80
98
|
if ENV['PROXY_URI']
|
data/restforce.gemspec
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path('
|
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)
|
@@ -22,19 +22,10 @@ Gem::Specification.new do |gem|
|
|
22
22
|
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
|
23
23
|
}
|
24
24
|
|
25
|
-
gem.required_ruby_version = '>= 2.
|
25
|
+
gem.required_ruby_version = '>= 2.6'
|
26
26
|
|
27
|
-
gem.add_dependency 'faraday', '<=
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<=
|
29
|
-
|
30
|
-
gem.add_dependency '
|
31
|
-
|
32
|
-
gem.add_dependency 'hashie', ['>= 1.2.0', '< 4.0']
|
33
|
-
|
34
|
-
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
35
|
-
gem.add_development_dependency 'webmock', '~> 3.4.0'
|
36
|
-
gem.add_development_dependency 'simplecov', '~> 0.15.0'
|
37
|
-
gem.add_development_dependency 'rubocop', '~> 0.50.0'
|
38
|
-
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
|
39
|
-
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
27
|
+
gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
|
28
|
+
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
|
29
|
+
gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
|
30
|
+
gem.add_dependency 'jwt', ['>= 1.5.6']
|
40
31
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpAIBAAKCAQEAy3KYqxZIgVDgFwdA+OQcKMJQu3iUTlyCSk9b3RLBOudnvk8u
|
3
|
+
n0ShtKkOKB4b4RZeedcrlKESoak/6NS+M7CDemRT0EagqUiz/ZsZxB2KUp7au+d8
|
4
|
+
0KWX99/loBjDttuon8ITDw2WFC9X0+TZqfsXcQ0iV1/9Sf8WHShd8ZqShjJBlEvf
|
5
|
+
7u7VdNW8dXrl+4cvpPzspVxg6jVotEpmp875jmGRvshgx0iz0jtfAyxaaKStITC6
|
6
|
+
MxufVNDgIYQDl6queh8b9noDLtt17Eq6YnropYN1hOjaLtoLBP7AN2gsXG7N3vqC
|
7
|
+
JG619W9X4zCmKztv4oGjymInrS2msC2J02dNGQIDAQABAoIBAAurTARsJ8Z7DA9m
|
8
|
+
FBzygIb59kV6eg8wkSyP9rXscHbfdPzeb88k0Z2aILy+VV0IumyEofRJdNce7RJ+
|
9
|
+
uVYfprrrbD9C/c4X5HMEZWrxQtDQWb1zXp5dESVfiz0ujnM7kCVxrUQsxFHuETyP
|
10
|
+
IMj2JPcQCMs4L0ACSJNtkE3eTs8xko5kwDHZGiLTi5jD1bLgaHl1A+9CTU8LosTy
|
11
|
+
hEIrNSZfNidDPU4QSbwoElYZxpDMSbtyHaIk1WHz7zLzWoogK3x5AIQh64wWAQVd
|
12
|
+
zzlp2j2jSM7oQ9j+k1aNiUBdDoRX53jmaIwE/1WDW/LT33qAoqRw+5qHeLRoRcfu
|
13
|
+
3uj/WI0CgYEA6wnpIUhqqWT+febhXtCr1mAJlAJpzUUQncN6Zk0Kj/kE1V52OqgL
|
14
|
+
gtOactII7J3+0zK7KGptqseTank0ghmGNdRBQ7+1JTQhpjLrCm/huKDhl+sBk95u
|
15
|
+
opxw/ZTwMFYPwsmZlFcy4uWRjtI+QzaV+2Xk5JF57H8vUiX/+XqseQcCgYEA3Zdw
|
16
|
+
zVHpcVPlyiXCbSvwb9IYXiJaQl/Rg96Klxah3MZNyRRKe5IoKUTJwEDuQ1MAHrve
|
17
|
+
cWrNLcXhX6r/PzIXSSLe71wgwpn7UcaqWzZJqqN7OIGEeTzYWbB6tGhse7Dw7tWB
|
18
|
+
hRkQSE0LPzZqboHz5msRM02sa61qiI5+ASJvIN8CgYEAvT+IoEzv3R89ruBVPQPm
|
19
|
+
KMHBVJSw3iArJex8xJxp0c0fMDJUHhyq0BdTd/pYRzVcNm/VtNAlJ2p07zlSpyKo
|
20
|
+
JvWV61gUIjWclnbPO+MkK4YWvzzxUz+5c2NlszjWQQU6wYuUBpZDmeBg2E++5F2y
|
21
|
+
W+8KY2QjeOJbltiUCCvXbccCgYEAqARYB5aARumyZqBS16xlVqQazeWGQqWcmzx2
|
22
|
+
ITGL8XZ7LGgyQZgE06XQw/F3t5yLjsIsXBr7ECXmST/C4gv9E/tYxm04edV/dfYI
|
23
|
+
3bhACx6CI8owxCyabwcdQwWam/8B8FX7KwxiCDBCwt9ju/7VDHVKSXgvsEWBbaF9
|
24
|
+
cSbG1EkCgYBZFztTUnD/cLMcvLUegN0K+6Qa3x3nRSrlrJ+v51mU1X8G8qNyFO67
|
25
|
+
gUq9h4xbCl4Z5ZTuFKXwPM4XaMzfYdrWNS2zl5IG14FXS077GhDKe062b9mFoxtm
|
26
|
+
aViCit4Hm8xpLTS8x9KB7yYAiF9sR/GklW1SUCIqnpL9JShkhzjfZw==
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -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::
|
99
|
-
File.expand_path('
|
98
|
+
Blob: Restforce::FilePart.new(
|
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
|
|
@@ -209,6 +218,24 @@ shared_examples_for Restforce::AbstractClient do
|
|
209
218
|
end
|
210
219
|
end
|
211
220
|
end
|
221
|
+
|
222
|
+
context 'when created with a space in the id' do
|
223
|
+
requests 'sobjects/Account/External__c/foo%20bar',
|
224
|
+
method: :patch,
|
225
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
226
|
+
fixture: 'sobject/upsert_created_success_response'
|
227
|
+
|
228
|
+
[:External__c, 'External__c', :external__c, 'external__c'].each do |key|
|
229
|
+
context "with #{key.inspect} as the external id" do
|
230
|
+
subject do
|
231
|
+
client.upsert!('Account', 'External__c', key => 'foo bar',
|
232
|
+
:Name => 'Foobar')
|
233
|
+
end
|
234
|
+
|
235
|
+
it { should eq 'foo' }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
212
239
|
end
|
213
240
|
|
214
241
|
describe '.destroy!' do
|
@@ -221,13 +248,20 @@ shared_examples_for Restforce::AbstractClient do
|
|
221
248
|
status: 404
|
222
249
|
|
223
250
|
subject { lambda { destroy! } }
|
224
|
-
it { should raise_error Faraday::
|
251
|
+
it { should raise_error Faraday::ResourceNotFound }
|
225
252
|
end
|
226
253
|
|
227
254
|
context 'with success' do
|
228
255
|
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
229
256
|
|
230
|
-
it { should
|
257
|
+
it { should be true }
|
258
|
+
end
|
259
|
+
|
260
|
+
context 'with a space in the id' do
|
261
|
+
subject(:destroy!) { client.destroy!('Account', '001D000000 INjVe') }
|
262
|
+
requests 'sobjects/Account/001D000000%20INjVe', method: :delete
|
263
|
+
|
264
|
+
it { should be true }
|
231
265
|
end
|
232
266
|
end
|
233
267
|
|
@@ -240,13 +274,13 @@ shared_examples_for Restforce::AbstractClient do
|
|
240
274
|
method: :delete,
|
241
275
|
status: 404
|
242
276
|
|
243
|
-
it { should
|
277
|
+
it { should be false }
|
244
278
|
end
|
245
279
|
|
246
280
|
context 'with success' do
|
247
281
|
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
248
282
|
|
249
|
-
it { should
|
283
|
+
it { should be true }
|
250
284
|
end
|
251
285
|
end
|
252
286
|
|
@@ -266,6 +300,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
266
300
|
subject { client.find('Account', '1234', 'External_Field__c') }
|
267
301
|
it { should be_a Hash }
|
268
302
|
end
|
303
|
+
|
304
|
+
context 'with a space in an external id' do
|
305
|
+
requests 'sobjects/Account/External_Field__c/12%2034',
|
306
|
+
fixture: 'sobject/sobject_find_success_response'
|
307
|
+
|
308
|
+
subject { client.find('Account', '12 34', 'External_Field__c') }
|
309
|
+
it { should be_a Hash }
|
310
|
+
end
|
269
311
|
end
|
270
312
|
|
271
313
|
describe '.select' do
|
@@ -284,6 +326,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
284
326
|
subject { client.select('Account', '1234', ['External_Field__c']) }
|
285
327
|
it { should be_a Hash }
|
286
328
|
end
|
329
|
+
|
330
|
+
context 'with a space in the id' do
|
331
|
+
requests 'sobjects/Account/12%2034',
|
332
|
+
fixture: 'sobject/sobject_select_success_response'
|
333
|
+
|
334
|
+
subject { client.select('Account', '12 34', nil, nil) }
|
335
|
+
it { should be_a Hash }
|
336
|
+
end
|
287
337
|
end
|
288
338
|
|
289
339
|
context 'when an external id is specified' do
|
@@ -315,8 +365,8 @@ shared_examples_for Restforce::AbstractClient do
|
|
315
365
|
before do
|
316
366
|
@request = stub_login_request(
|
317
367
|
with_body: "grant_type=password&client_id=client_id" \
|
318
|
-
|
319
|
-
|
368
|
+
"&client_secret=client_secret&username=foo" \
|
369
|
+
"&password=barsecurity_token"
|
320
370
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
321
371
|
end
|
322
372
|
|
@@ -367,8 +417,8 @@ shared_examples_for Restforce::AbstractClient do
|
|
367
417
|
|
368
418
|
@query_request = stub_login_request(
|
369
419
|
with_body: "grant_type=password&client_id=client_id" \
|
370
|
-
|
371
|
-
|
420
|
+
"&client_secret=client_secret&username=foo&" \
|
421
|
+
"password=barsecurity_token"
|
372
422
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
373
423
|
end
|
374
424
|
|
@@ -384,15 +434,15 @@ shared_examples_for Restforce::AbstractClient do
|
|
384
434
|
@query = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object').
|
385
435
|
with(headers: { 'Authorization' => "OAuth #{oauth_token}" }).
|
386
436
|
to_return(status: 401,
|
387
|
-
|
388
|
-
|
437
|
+
body: fixture('expired_session_response'),
|
438
|
+
headers: { 'Content-Type' => 'application/json' }).then.
|
389
439
|
to_return(status: 200,
|
390
|
-
|
391
|
-
|
440
|
+
body: fixture('sobject/query_success_response'),
|
441
|
+
headers: { 'Content-Type' => 'application/json' })
|
392
442
|
|
393
443
|
@login = stub_login_request(
|
394
444
|
with_body: "grant_type=password&client_id=client_id&client_secret=" \
|
395
|
-
|
445
|
+
"client_secret&username=foo&password=barsecurity_token"
|
396
446
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
397
447
|
end
|
398
448
|
|