restforce 1.5.3 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +60 -0
- data/.rubocop_todo.yml +117 -0
- data/.travis.yml +3 -5
- data/CHANGELOG.md +6 -0
- data/README.md +2 -0
- data/Rakefile +1 -1
- data/lib/restforce/abstract_client.rb +1 -1
- data/lib/restforce/attachment.rb +1 -3
- data/lib/restforce/concerns/api.rb +22 -17
- data/lib/restforce/concerns/authentication.rb +4 -3
- data/lib/restforce/concerns/base.rb +42 -31
- data/lib/restforce/concerns/caching.rb +1 -3
- data/lib/restforce/concerns/canvas.rb +0 -2
- data/lib/restforce/concerns/connection.rb +18 -12
- data/lib/restforce/concerns/picklists.rb +10 -10
- data/lib/restforce/concerns/streaming.rb +9 -4
- data/lib/restforce/concerns/verbs.rb +0 -2
- data/lib/restforce/config.rb +19 -13
- data/lib/restforce/data/client.rb +8 -1
- data/lib/restforce/mash.rb +6 -10
- data/lib/restforce/middleware.rb +3 -1
- data/lib/restforce/middleware/authentication.rb +21 -7
- data/lib/restforce/middleware/authentication/password.rb +5 -9
- data/lib/restforce/middleware/authentication/token.rb +4 -8
- data/lib/restforce/middleware/authorization.rb +0 -3
- data/lib/restforce/middleware/caching.rb +4 -4
- data/lib/restforce/middleware/instance_url.rb +3 -5
- data/lib/restforce/middleware/logger.rb +7 -7
- data/lib/restforce/middleware/mashify.rb +2 -3
- data/lib/restforce/middleware/multipart.rb +15 -8
- data/lib/restforce/middleware/raise_error.rb +2 -1
- data/lib/restforce/patches/parts.rb +2 -3
- data/lib/restforce/signed_request.rb +2 -2
- data/lib/restforce/sobject.rb +1 -3
- data/lib/restforce/tooling/client.rb +1 -3
- data/lib/restforce/upload_io.rb +1 -3
- data/lib/restforce/version.rb +1 -1
- data/restforce.gemspec +7 -4
- data/spec/integration/abstract_client_spec.rb +123 -75
- data/spec/integration/data/client_spec.rb +24 -12
- data/spec/spec_helper.rb +2 -2
- data/spec/support/client_integration.rb +23 -17
- data/spec/support/concerns.rb +2 -2
- data/spec/support/event_machine.rb +3 -3
- data/spec/support/fixture_helpers.rb +16 -12
- data/spec/support/middleware.rb +16 -8
- data/spec/unit/abstract_client_spec.rb +1 -1
- data/spec/unit/attachment_spec.rb +2 -1
- data/spec/unit/collection_spec.rb +13 -4
- data/spec/unit/concerns/api_spec.rb +15 -13
- data/spec/unit/concerns/authentication_spec.rb +20 -17
- data/spec/unit/concerns/base_spec.rb +2 -2
- data/spec/unit/concerns/caching_spec.rb +2 -2
- data/spec/unit/concerns/canvas_spec.rb +3 -3
- data/spec/unit/concerns/connection_spec.rb +6 -7
- data/spec/unit/concerns/streaming_spec.rb +8 -8
- data/spec/unit/config_spec.rb +6 -6
- data/spec/unit/data/client_spec.rb +1 -1
- data/spec/unit/mash_spec.rb +1 -1
- data/spec/unit/middleware/authentication/password_spec.rb +14 -12
- data/spec/unit/middleware/authentication/token_spec.rb +12 -10
- data/spec/unit/middleware/authentication_spec.rb +15 -11
- data/spec/unit/middleware/authorization_spec.rb +1 -1
- data/spec/unit/middleware/gzip_spec.rb +1 -1
- data/spec/unit/middleware/instance_url_spec.rb +2 -2
- data/spec/unit/middleware/mashify_spec.rb +2 -2
- data/spec/unit/middleware/raise_error_spec.rb +23 -7
- data/spec/unit/sobject_spec.rb +16 -9
- data/spec/unit/tooling/client_spec.rb +1 -1
- metadata +23 -5
@@ -1,20 +1,16 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Authentication middleware used if username and password flow is used
|
4
3
|
class Middleware::Authentication::Password < Restforce::Middleware::Authentication
|
5
|
-
|
6
4
|
def params
|
7
|
-
{ :
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
5
|
+
{ grant_type: 'password',
|
6
|
+
client_id: @options[:client_id],
|
7
|
+
client_secret: @options[:client_secret],
|
8
|
+
username: @options[:username],
|
9
|
+
password: password }
|
12
10
|
end
|
13
11
|
|
14
12
|
def password
|
15
13
|
"#{@options[:password]}#{@options[:security_token]}"
|
16
14
|
end
|
17
|
-
|
18
15
|
end
|
19
|
-
|
20
16
|
end
|
@@ -1,15 +1,11 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Authentication middleware used if oauth_token and refresh_token are set
|
4
3
|
class Middleware::Authentication::Token < Restforce::Middleware::Authentication
|
5
|
-
|
6
4
|
def params
|
7
|
-
{ :
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
5
|
+
{ grant_type: 'refresh_token',
|
6
|
+
refresh_token: @options[:refresh_token],
|
7
|
+
client_id: @options[:client_id],
|
8
|
+
client_secret: @options[:client_secret] }
|
11
9
|
end
|
12
|
-
|
13
10
|
end
|
14
|
-
|
15
11
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Piece of middleware that simply injects the OAuth token into the request
|
4
3
|
# headers.
|
5
4
|
class Middleware::Authorization < Restforce::Middleware
|
@@ -13,7 +12,5 @@ module Restforce
|
|
13
12
|
def token
|
14
13
|
@options[:oauth_token]
|
15
14
|
end
|
16
|
-
|
17
15
|
end
|
18
|
-
|
19
16
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Restforce
|
2
2
|
class Middleware::Caching < FaradayMiddleware::Caching
|
3
|
-
|
4
3
|
def call(env)
|
5
4
|
expire(cache_key(env)) unless use_cache?
|
6
5
|
super
|
@@ -13,12 +12,13 @@ module Restforce
|
|
13
12
|
# We don't want to cache requests for different clients, so append the
|
14
13
|
# oauth token to the cache key.
|
15
14
|
def cache_key(env)
|
16
|
-
super(env) +
|
15
|
+
super(env) +
|
16
|
+
env[:request_headers][Restforce::Middleware::Authorization::AUTH_HEADER].
|
17
|
+
gsub(/\s/, '')
|
17
18
|
end
|
18
19
|
|
19
20
|
def use_cache?
|
20
|
-
!@options.
|
21
|
+
!@options.key?(:use_cache) || @options[:use_cache]
|
21
22
|
end
|
22
|
-
|
23
23
|
end
|
24
24
|
end
|
@@ -1,18 +1,16 @@
|
|
1
1
|
module Restforce
|
2
|
-
|
3
2
|
# Middleware which asserts that the instance_url is always set
|
4
3
|
class Middleware::InstanceURL < Restforce::Middleware
|
5
|
-
|
6
4
|
def call(env)
|
7
5
|
# If the connection url_prefix isn't set, we must not be authenticated.
|
8
|
-
raise Restforce::UnauthorizedError,
|
6
|
+
raise Restforce::UnauthorizedError,
|
7
|
+
'Connection prefix not set' unless url_prefix_set?
|
8
|
+
|
9
9
|
@app.call(env)
|
10
10
|
end
|
11
11
|
|
12
12
|
def url_prefix_set?
|
13
13
|
!!(connection.url_prefix && connection.url_prefix.host)
|
14
14
|
end
|
15
|
-
|
16
15
|
end
|
17
|
-
|
18
16
|
end
|
@@ -17,19 +17,19 @@ module Restforce
|
|
17
17
|
|
18
18
|
def call(env)
|
19
19
|
debug('request') do
|
20
|
-
dump :
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
20
|
+
dump url: env[:url].to_s,
|
21
|
+
method: env[:method],
|
22
|
+
headers: env[:request_headers],
|
23
|
+
body: env[:body]
|
24
24
|
end
|
25
25
|
super
|
26
26
|
end
|
27
27
|
|
28
28
|
def on_complete(env)
|
29
29
|
debug('response') do
|
30
|
-
dump :
|
31
|
-
:
|
32
|
-
:
|
30
|
+
dump status: env[:status].to_s,
|
31
|
+
headers: env[:response_headers],
|
32
|
+
body: env[:body]
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -2,10 +2,9 @@ module Restforce
|
|
2
2
|
# Middleware the converts sobject records from JSON into Restforce::SObject objects
|
3
3
|
# and collections of records into Restforce::Collection objects.
|
4
4
|
class Middleware::Mashify < Restforce::Middleware
|
5
|
-
|
6
5
|
def call(env)
|
7
|
-
@app.call(env).on_complete do |
|
8
|
-
|
6
|
+
@app.call(env).on_complete do |completed_env|
|
7
|
+
completed_env[:body] = Restforce::Mash.build(completed_env[:body], client)
|
9
8
|
end
|
10
9
|
end
|
11
10
|
end
|
@@ -2,7 +2,7 @@ module Restforce
|
|
2
2
|
class Middleware::Multipart < Faraday::Request::UrlEncoded
|
3
3
|
self.mime_type = 'multipart/form-data'.freeze
|
4
4
|
DEFAULT_BOUNDARY = "--boundary_string".freeze
|
5
|
-
JSON_CONTENT_TYPE = {"Content-Type" => "application/json"}
|
5
|
+
JSON_CONTENT_TYPE = { "Content-Type" => "application/json" }
|
6
6
|
|
7
7
|
def call(env)
|
8
8
|
match_content_type(env) do |params|
|
@@ -16,8 +16,8 @@ module Restforce
|
|
16
16
|
|
17
17
|
def process_request?(env)
|
18
18
|
type = request_type(env)
|
19
|
-
env[:body].respond_to?(:each_key)
|
20
|
-
(type.empty?
|
19
|
+
env[:body].respond_to?(:each_key) && !env[:body].empty? && (
|
20
|
+
(type.empty? && has_multipart?(env[:body])) ||
|
21
21
|
type == self.class.mime_type
|
22
22
|
)
|
23
23
|
end
|
@@ -26,7 +26,7 @@ module Restforce
|
|
26
26
|
# string is an enum in 1.8, returning list of itself
|
27
27
|
if obj.respond_to?(:each) && !obj.is_a?(String)
|
28
28
|
(obj.respond_to?(:values) ? obj.values : obj).each do |val|
|
29
|
-
return true if
|
29
|
+
return true if val.respond_to?(:content_type) || has_multipart?(val)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
false
|
@@ -37,18 +37,25 @@ module Restforce
|
|
37
37
|
parts = []
|
38
38
|
|
39
39
|
# Fields
|
40
|
-
parts << Faraday::Parts::Part.new(
|
40
|
+
parts << Faraday::Parts::Part.new(
|
41
|
+
boundary,
|
42
|
+
'entity_content',
|
43
|
+
params.reject { |k, v| v.respond_to? :content_type }.to_json,
|
44
|
+
JSON_CONTENT_TYPE
|
45
|
+
)
|
41
46
|
|
42
47
|
# Files
|
43
|
-
params.each do |k,v|
|
44
|
-
parts << Faraday::Parts::Part.new(boundary,
|
48
|
+
params.each do |k, v|
|
49
|
+
parts << Faraday::Parts::Part.new(boundary,
|
50
|
+
k.to_s,
|
51
|
+
v) if v.respond_to? :content_type
|
45
52
|
end
|
46
53
|
|
47
54
|
parts << Faraday::Parts::EpiloguePart.new(boundary)
|
48
55
|
|
49
56
|
body = Faraday::CompositeReadIO.new(parts)
|
50
57
|
env[:request_headers]['Content-Length'] = body.length.to_s
|
51
|
-
|
58
|
+
body
|
52
59
|
end
|
53
60
|
end
|
54
61
|
end
|
@@ -8,7 +8,8 @@ module Restforce
|
|
8
8
|
when 401
|
9
9
|
raise Restforce::UnauthorizedError, message
|
10
10
|
when 413
|
11
|
-
raise Faraday::Error::ClientError.new("HTTP 413 - Request Entity Too Large",
|
11
|
+
raise Faraday::Error::ClientError.new("HTTP 413 - Request Entity Too Large",
|
12
|
+
env[:response])
|
12
13
|
when 400...600
|
13
14
|
raise Faraday::Error::ClientError.new(message, env[:response])
|
14
15
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Faraday
|
2
2
|
module Parts
|
3
|
-
|
4
3
|
module Part
|
5
4
|
def self.new(boundary, name, value, headers = {})
|
6
5
|
headers ||= {}
|
@@ -21,8 +20,8 @@ module Faraday
|
|
21
20
|
def build_part(boundary, name, value, headers = {})
|
22
21
|
part = ''
|
23
22
|
part << "--#{boundary}\r\n"
|
24
|
-
part << "Content-Disposition: form-data; name=\"#{name
|
25
|
-
part << "Content-Type: #{headers[
|
23
|
+
part << "Content-Disposition: form-data; name=\"#{name}\"\r\n"
|
24
|
+
part << "Content-Type: #{headers['Content-Type']}\r\n" if headers["Content-Type"]
|
26
25
|
part << "\r\n"
|
27
26
|
part << "#{value}\r\n"
|
28
27
|
end
|
@@ -28,7 +28,8 @@ module Restforce
|
|
28
28
|
JSON.parse(Base64.decode64(payload))
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
private
|
32
|
+
|
32
33
|
attr_reader :client_secret, :signature, :payload
|
33
34
|
|
34
35
|
def split_components(signed_request)
|
@@ -51,6 +52,5 @@ module Restforce
|
|
51
52
|
OpenSSL::Digest
|
52
53
|
end
|
53
54
|
end
|
54
|
-
|
55
55
|
end
|
56
56
|
end
|
data/lib/restforce/sobject.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module Restforce
|
2
2
|
class SObject < Restforce::Mash
|
3
|
-
|
4
3
|
def sobject_type
|
5
4
|
self.attributes['type']
|
6
5
|
end
|
@@ -58,12 +57,11 @@ module Restforce
|
|
58
57
|
self.Id
|
59
58
|
end
|
60
59
|
|
61
|
-
|
60
|
+
private
|
62
61
|
|
63
62
|
def ensure_id
|
64
63
|
return true if self.Id?
|
65
64
|
raise ArgumentError, 'You need to query the Id for the record first.'
|
66
65
|
end
|
67
|
-
|
68
66
|
end
|
69
67
|
end
|
data/lib/restforce/upload_io.rb
CHANGED
data/lib/restforce/version.rb
CHANGED
data/restforce.gemspec
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
require File.expand_path('../lib/restforce/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ["Eric J. Holmes"]
|
6
|
-
gem.email = ["eric@ejholmes.net"]
|
7
|
-
gem.description = %q{A lightweight ruby client for the Salesforce REST
|
8
|
-
gem.summary = %q{A lightweight ruby client for the Salesforce REST
|
5
|
+
gem.authors = ["Eric J. Holmes", "Tim Rogers"]
|
6
|
+
gem.email = ["eric@ejholmes.net", "tim@gocardless.com"]
|
7
|
+
gem.description = %q{A lightweight ruby client for the Salesforce REST API.}
|
8
|
+
gem.summary = %q{A lightweight ruby client for the Salesforce REST API.}
|
9
9
|
gem.homepage = "https://github.com/ejholmes/restforce"
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
@@ -15,6 +15,8 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Restforce::VERSION
|
17
17
|
|
18
|
+
gem.required_ruby_version = '>= 1.9.3'
|
19
|
+
|
18
20
|
gem.add_dependency 'faraday', '~> 0.9.0'
|
19
21
|
gem.add_dependency 'faraday_middleware', '>= 0.8.8'
|
20
22
|
gem.add_dependency 'json', ['>= 1.7.5', '< 1.9.0']
|
@@ -23,5 +25,6 @@ Gem::Specification.new do |gem|
|
|
23
25
|
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
24
26
|
gem.add_development_dependency 'webmock', '~> 1.13.0'
|
25
27
|
gem.add_development_dependency 'simplecov', '~> 0.7.1'
|
28
|
+
gem.add_development_dependency 'rubocop', '~> 0.31.0'
|
26
29
|
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
27
30
|
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
shared_examples_for Restforce::AbstractClient do
|
4
|
-
|
5
4
|
describe '.list_sobjects' do
|
6
|
-
requests :sobjects, :
|
5
|
+
requests :sobjects, fixture: 'sobject/describe_sobjects_success_response'
|
7
6
|
|
8
7
|
subject { client.list_sobjects }
|
9
8
|
it { should be_an Array }
|
@@ -12,14 +11,15 @@ shared_examples_for Restforce::AbstractClient do
|
|
12
11
|
|
13
12
|
describe '.describe' do
|
14
13
|
context 'with no arguments' do
|
15
|
-
requests :sobjects, :
|
14
|
+
requests :sobjects, fixture: 'sobject/describe_sobjects_success_response'
|
16
15
|
|
17
16
|
subject { client.describe }
|
18
17
|
it { should be_an Array }
|
19
18
|
end
|
20
19
|
|
21
20
|
context 'with an argument' do
|
22
|
-
requests 'sobjects/Whizbang/describe',
|
21
|
+
requests 'sobjects/Whizbang/describe',
|
22
|
+
fixture: 'sobject/sobject_describe_success_response'
|
23
23
|
|
24
24
|
subject { client.describe('Whizbang') }
|
25
25
|
its(['name']) { should eq 'Whizbang' }
|
@@ -27,14 +27,15 @@ shared_examples_for Restforce::AbstractClient do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
describe '.query' do
|
30
|
-
requests 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
30
|
+
requests 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
31
|
+
fixture: 'sobject/query_success_response'
|
31
32
|
|
32
33
|
subject { client.query('SELECT some, fields FROM object') }
|
33
34
|
it { should be_an Enumerable }
|
34
35
|
end
|
35
36
|
|
36
37
|
describe '.search' do
|
37
|
-
requests 'search\?q=FIND%20%7Bbar%7D', :
|
38
|
+
requests 'search\?q=FIND%20%7Bbar%7D', fixture: 'sobject/search_success_response'
|
38
39
|
|
39
40
|
subject { client.search('FIND {bar}') }
|
40
41
|
it { should be_an Array }
|
@@ -42,7 +43,8 @@ shared_examples_for Restforce::AbstractClient do
|
|
42
43
|
end
|
43
44
|
|
44
45
|
describe '.org_id' do
|
45
|
-
requests 'query\?q=select%20id%20from%20Organization',
|
46
|
+
requests 'query\?q=select%20id%20from%20Organization',
|
47
|
+
fixture: 'sobject/org_query_response'
|
46
48
|
|
47
49
|
subject { client.org_id }
|
48
50
|
it { should eq '00Dx0000000BV7z' }
|
@@ -51,21 +53,30 @@ shared_examples_for Restforce::AbstractClient do
|
|
51
53
|
describe '.create' do
|
52
54
|
context 'without multipart' do
|
53
55
|
requests 'sobjects/Account',
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
method: :post,
|
57
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
58
|
+
fixture: 'sobject/create_success_response'
|
57
59
|
|
58
|
-
subject { client.create('Account', :
|
60
|
+
subject { client.create('Account', Name: 'Foobar') }
|
59
61
|
it { should eq 'some_id' }
|
60
62
|
end
|
61
63
|
|
62
64
|
context 'with multipart' do
|
65
|
+
# rubocop:disable Metrics/LineLength
|
63
66
|
requests 'sobjects/Account',
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
+
method: :post,
|
68
|
+
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),
|
69
|
+
fixture: 'sobject/create_success_response'
|
70
|
+
# rubocop:enable Metrics/LineLength
|
71
|
+
|
72
|
+
subject do
|
73
|
+
client.create('Account', Name: 'Foobar',
|
74
|
+
Blob: Restforce::UploadIO.new(
|
75
|
+
File.expand_path('../../fixtures/blob.jpg', __FILE__),
|
76
|
+
'image/jpeg'
|
77
|
+
))
|
78
|
+
end
|
67
79
|
|
68
|
-
subject { client.create('Account', :Name => 'Foobar', :Blob => Restforce::UploadIO.new(File.expand_path('../../fixtures/blob.jpg', __FILE__), 'image/jpeg')) }
|
69
80
|
it { should eq 'some_id' }
|
70
81
|
end
|
71
82
|
end
|
@@ -73,41 +84,49 @@ shared_examples_for Restforce::AbstractClient do
|
|
73
84
|
describe '.update!' do
|
74
85
|
context 'with invalid Id' do
|
75
86
|
requests 'sobjects/Account/001D000000INjVe',
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
method: :patch,
|
88
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
89
|
+
status: 404,
|
90
|
+
fixture: 'sobject/delete_error_response'
|
91
|
+
|
92
|
+
subject do
|
93
|
+
lambda do
|
94
|
+
client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar')
|
95
|
+
end
|
96
|
+
end
|
80
97
|
|
81
|
-
subject { lambda { client.update!('Account', :Id => '001D000000INjVe', :Name => 'Foobar') } }
|
82
98
|
it { should raise_error Faraday::Error::ResourceNotFound }
|
83
99
|
end
|
84
100
|
end
|
85
101
|
|
86
102
|
describe '.update' do
|
87
103
|
context 'with missing Id' do
|
88
|
-
subject { lambda { client.update('Account', :
|
104
|
+
subject { lambda { client.update('Account', Name: 'Foobar') } }
|
89
105
|
it { should raise_error ArgumentError, 'Id field missing from attrs.' }
|
90
106
|
end
|
91
107
|
|
92
108
|
context 'with invalid Id' do
|
93
109
|
requests 'sobjects/Account/001D000000INjVe',
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
method: :patch,
|
111
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
112
|
+
status: 404,
|
113
|
+
fixture: 'sobject/delete_error_response'
|
98
114
|
|
99
|
-
subject { client.update('Account', :
|
115
|
+
subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') }
|
100
116
|
it { should be_false }
|
101
117
|
end
|
102
118
|
|
103
119
|
context 'with success' do
|
104
120
|
requests 'sobjects/Account/001D000000INjVe',
|
105
|
-
|
106
|
-
|
121
|
+
method: :patch,
|
122
|
+
with_body: "{\"Name\":\"Foobar\"}"
|
107
123
|
|
108
124
|
[:Id, :id, 'Id', 'id'].each do |key|
|
109
125
|
context "with #{key.inspect} as the key" do
|
110
|
-
subject
|
126
|
+
subject do
|
127
|
+
client.update('Account', key => '001D000000INjVe', :Name => 'Foobar')
|
128
|
+
end
|
129
|
+
|
111
130
|
it { should be_true }
|
112
131
|
end
|
113
132
|
end
|
@@ -117,29 +136,41 @@ shared_examples_for Restforce::AbstractClient do
|
|
117
136
|
describe '.upsert!' do
|
118
137
|
context 'when updated' do
|
119
138
|
requests 'sobjects/Account/External__c/foobar',
|
120
|
-
|
121
|
-
|
139
|
+
method: :patch,
|
140
|
+
with_body: "{\"Name\":\"Foobar\"}"
|
122
141
|
|
123
142
|
context 'with symbol external Id key' do
|
124
|
-
subject
|
143
|
+
subject do
|
144
|
+
client.upsert!('Account', 'External__c', External__c: 'foobar',
|
145
|
+
Name: 'Foobar')
|
146
|
+
end
|
147
|
+
|
125
148
|
it { should be_true }
|
126
149
|
end
|
127
150
|
|
128
151
|
context 'with string external Id key' do
|
129
|
-
subject
|
152
|
+
subject do
|
153
|
+
client.upsert!('Account', 'External__c', 'External__c' => 'foobar',
|
154
|
+
'Name' => 'Foobar')
|
155
|
+
end
|
156
|
+
|
130
157
|
it { should be_true }
|
131
158
|
end
|
132
159
|
end
|
133
160
|
|
134
161
|
context 'when created' do
|
135
162
|
requests 'sobjects/Account/External__c/foobar',
|
136
|
-
|
137
|
-
|
138
|
-
|
163
|
+
method: :patch,
|
164
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
165
|
+
fixture: 'sobject/upsert_created_success_response'
|
139
166
|
|
140
167
|
[:External__c, 'External__c', :external__c, 'external__c'].each do |key|
|
141
168
|
context "with #{key.inspect} as the external id" do
|
142
|
-
subject
|
169
|
+
subject do
|
170
|
+
client.upsert!('Account', 'External__c', key => 'foobar',
|
171
|
+
:Name => 'Foobar')
|
172
|
+
end
|
173
|
+
|
143
174
|
it { should eq 'foo' }
|
144
175
|
end
|
145
176
|
end
|
@@ -151,16 +182,16 @@ shared_examples_for Restforce::AbstractClient do
|
|
151
182
|
|
152
183
|
context 'with invalid Id' do
|
153
184
|
requests 'sobjects/Account/001D000000INjVe',
|
154
|
-
|
155
|
-
|
156
|
-
|
185
|
+
fixture: 'sobject/delete_error_response',
|
186
|
+
method: :delete,
|
187
|
+
status: 404
|
157
188
|
|
158
189
|
subject { lambda { destroy! } }
|
159
190
|
it { should raise_error Faraday::Error::ResourceNotFound }
|
160
191
|
end
|
161
192
|
|
162
193
|
context 'with success' do
|
163
|
-
requests 'sobjects/Account/001D000000INjVe', :
|
194
|
+
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
164
195
|
|
165
196
|
it { should be_true }
|
166
197
|
end
|
@@ -171,15 +202,15 @@ shared_examples_for Restforce::AbstractClient do
|
|
171
202
|
|
172
203
|
context 'with invalid Id' do
|
173
204
|
requests 'sobjects/Account/001D000000INjVe',
|
174
|
-
|
175
|
-
|
176
|
-
|
205
|
+
fixture: 'sobject/delete_error_response',
|
206
|
+
method: :delete,
|
207
|
+
status: 404
|
177
208
|
|
178
209
|
it { should be_false }
|
179
210
|
end
|
180
211
|
|
181
212
|
context 'with success' do
|
182
|
-
requests 'sobjects/Account/001D000000INjVe', :
|
213
|
+
requests 'sobjects/Account/001D000000INjVe', method: :delete
|
183
214
|
|
184
215
|
it { should be_true }
|
185
216
|
end
|
@@ -188,7 +219,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
188
219
|
describe '.find' do
|
189
220
|
context 'with no external id passed' do
|
190
221
|
requests 'sobjects/Account/001D000000INjVe',
|
191
|
-
|
222
|
+
fixture: 'sobject/sobject_find_success_response'
|
192
223
|
|
193
224
|
subject { client.find('Account', '001D000000INjVe') }
|
194
225
|
it { should be_a Hash }
|
@@ -196,7 +227,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
196
227
|
|
197
228
|
context 'when an external id is passed' do
|
198
229
|
requests 'sobjects/Account/External_Field__c/1234',
|
199
|
-
|
230
|
+
fixture: 'sobject/sobject_find_success_response'
|
200
231
|
|
201
232
|
subject { client.find('Account', '1234', 'External_Field__c') }
|
202
233
|
it { should be_a Hash }
|
@@ -207,14 +238,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
207
238
|
context 'when no external id is specified' do
|
208
239
|
context 'when no select list is specified' do
|
209
240
|
requests 'sobjects/Account/1234',
|
210
|
-
|
241
|
+
fixture: 'sobject/sobject_select_success_response'
|
211
242
|
|
212
243
|
subject { client.select('Account', '1234', nil, nil) }
|
213
244
|
it { should be_a Hash }
|
214
245
|
end
|
215
246
|
context 'when select list is specified' do
|
216
247
|
requests 'sobjects/Account/1234\?fields=External_Field__c',
|
217
|
-
|
248
|
+
fixture: 'sobject/sobject_select_success_response'
|
218
249
|
|
219
250
|
subject { client.select('Account', '1234', ['External_Field__c']) }
|
220
251
|
it { should be_a Hash }
|
@@ -224,7 +255,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
224
255
|
context 'when an external id is specified' do
|
225
256
|
context 'when no select list is specified' do
|
226
257
|
requests 'sobjects/Account/External_Field__c/1234',
|
227
|
-
|
258
|
+
fixture: 'sobject/sobject_select_success_response'
|
228
259
|
|
229
260
|
subject { client.select('Account', '1234', nil, 'External_Field__c') }
|
230
261
|
it { should be_a Hash }
|
@@ -232,9 +263,12 @@ shared_examples_for Restforce::AbstractClient do
|
|
232
263
|
|
233
264
|
context 'when select list is specified' do
|
234
265
|
requests 'sobjects/Account/External_Field__c/1234\?fields=External_Field__c',
|
235
|
-
|
266
|
+
fixture: 'sobject/sobject_select_success_response'
|
267
|
+
|
268
|
+
subject do
|
269
|
+
client.select('Account', '1234', ['External_Field__c'], 'External_Field__c')
|
270
|
+
end
|
236
271
|
|
237
|
-
subject { client.select('Account', '1234', ['External_Field__c'], 'External_Field__c') }
|
238
272
|
it { should be_a Hash }
|
239
273
|
end
|
240
274
|
end
|
@@ -245,9 +279,11 @@ shared_examples_for Restforce::AbstractClient do
|
|
245
279
|
|
246
280
|
context 'when successful' do
|
247
281
|
before do
|
248
|
-
@request = stub_login_request(
|
249
|
-
"
|
250
|
-
|
282
|
+
@request = stub_login_request(
|
283
|
+
with_body: "grant_type=password&client_id=client_id" \
|
284
|
+
"&client_secret=client_secret&username=foo" \
|
285
|
+
"&password=barsecurity_token"
|
286
|
+
).to_return(status: 200, body: fixture(:auth_success_response))
|
251
287
|
end
|
252
288
|
|
253
289
|
after do
|
@@ -262,14 +298,16 @@ shared_examples_for Restforce::AbstractClient do
|
|
262
298
|
client.stub(:authentication_middleware).and_return(nil)
|
263
299
|
end
|
264
300
|
|
265
|
-
|
266
|
-
|
301
|
+
it "raises an error" do
|
302
|
+
expect { authenticate! }.to raise_error Restforce::AuthenticationError,
|
303
|
+
'No authentication middleware present'
|
304
|
+
end
|
267
305
|
end
|
268
306
|
end
|
269
307
|
|
270
308
|
describe '.without_caching' do
|
271
309
|
requests 'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
272
|
-
|
310
|
+
fixture: 'sobject/query_success_response'
|
273
311
|
|
274
312
|
before do
|
275
313
|
cache.should_receive(:delete).and_call_original
|
@@ -284,12 +322,17 @@ shared_examples_for Restforce::AbstractClient do
|
|
284
322
|
describe 'authentication retries' do
|
285
323
|
context 'when retries reaches 0' do
|
286
324
|
before do
|
287
|
-
@auth_request = stub_api_request(
|
288
|
-
|
289
|
-
:
|
290
|
-
|
291
|
-
|
292
|
-
|
325
|
+
@auth_request = stub_api_request(
|
326
|
+
'query\?q=SELECT%20some,%20fields%20FROM%20object',
|
327
|
+
status: 401,
|
328
|
+
fixture: 'expired_session_response'
|
329
|
+
)
|
330
|
+
|
331
|
+
@query_request = stub_login_request(
|
332
|
+
with_body: "grant_type=password&client_id=client_id" \
|
333
|
+
"&client_secret=client_secret&username=foo&" \
|
334
|
+
"password=barsecurity_token"
|
335
|
+
).to_return(status: 200, body: fixture(:auth_success_response))
|
293
336
|
end
|
294
337
|
|
295
338
|
subject { lambda { client.query('SELECT some, fields FROM object') } }
|
@@ -302,13 +345,18 @@ shared_examples_for Restforce::AbstractClient do
|
|
302
345
|
|
303
346
|
before do
|
304
347
|
@query = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object').
|
305
|
-
with(:
|
306
|
-
to_return(:
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
348
|
+
with(headers: { 'Authorization' => "OAuth #{oauth_token}" }).
|
349
|
+
to_return(status: 401,
|
350
|
+
body: fixture('expired_session_response'),
|
351
|
+
headers: { 'Content-Type' => 'application/json' }).then.
|
352
|
+
to_return(status: 200,
|
353
|
+
body: fixture('sobject/query_success_response'),
|
354
|
+
headers: { 'Content-Type' => 'application/json' })
|
355
|
+
|
356
|
+
@login = stub_login_request(
|
357
|
+
with_body: "grant_type=password&client_id=client_id&client_secret=" \
|
358
|
+
"client_secret&username=foo&password=barsecurity_token"
|
359
|
+
).to_return(status: 200, body: fixture(:auth_success_response))
|
312
360
|
end
|
313
361
|
|
314
362
|
after do
|
@@ -329,16 +377,16 @@ describe Restforce::AbstractClient do
|
|
329
377
|
context 'with pagination' do
|
330
378
|
subject { client.query('SELECT some, fields FROM object').next_page }
|
331
379
|
|
332
|
-
requests 'query\?q', :
|
333
|
-
requests 'query/01gD', :
|
380
|
+
requests 'query\?q', fixture: 'sobject/query_paginated_first_page_response'
|
381
|
+
requests 'query/01gD', fixture: 'sobject/query_paginated_last_page_response'
|
334
382
|
|
335
383
|
it { should be_a Restforce::Collection }
|
336
|
-
its('first.Text_Label') { should eq 'Last Page'}
|
384
|
+
its('first.Text_Label') { should eq 'Last Page' }
|
337
385
|
end
|
338
386
|
end
|
339
387
|
end
|
340
388
|
|
341
|
-
describe 'without mashify', :
|
389
|
+
describe 'without mashify', mashify: false do
|
342
390
|
it_behaves_like Restforce::AbstractClient
|
343
391
|
end
|
344
392
|
end
|