restforce 3.0.1 → 5.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Restforce::ErrorCode do
|
6
|
+
describe "mapping of error codes to classes" do
|
7
|
+
subject(:error_exception_classes) { described_class::ERROR_EXCEPTION_CLASSES }
|
8
|
+
|
9
|
+
let(:exception_classes) do
|
10
|
+
described_class.constants.
|
11
|
+
map { |constant_name| described_class.const_get(constant_name) }.
|
12
|
+
select { |constant| constant.is_a?(Class) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "maps all defined exception classes to an error code" do
|
16
|
+
exception_classes.each do |exception_class|
|
17
|
+
expect(error_exception_classes.values).to include(exception_class)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "maps all error codes to a defined exception class" do
|
22
|
+
error_exception_classes.each_value do |mapped_exception_class|
|
23
|
+
expect(exception_classes).to include(mapped_exception_class)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.get_exception_class' do
|
29
|
+
context 'when a non-existent error code is looked up' do
|
30
|
+
let(:new_error_code) { 'ANOTHER_NEW_ERROR_CODE' }
|
31
|
+
subject { described_class.get_exception_class(new_error_code) }
|
32
|
+
|
33
|
+
it { should be Restforce::ResponseError }
|
34
|
+
|
35
|
+
it 'outputs a warning' do
|
36
|
+
expect(Warning).to receive(:warn)
|
37
|
+
subject
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when a known error code is looked up' do
|
42
|
+
let(:existing_error_code) { "ALL_OR_NONE_OPERATION_ROLLED_BACK" }
|
43
|
+
let(:existing_error) { described_class::AllOrNoneOperationRolledBack }
|
44
|
+
|
45
|
+
subject do
|
46
|
+
described_class.get_exception_class(existing_error_code)
|
47
|
+
end
|
48
|
+
|
49
|
+
it { should < Restforce::ResponseError }
|
50
|
+
|
51
|
+
it 'returns existing error' do
|
52
|
+
should be(existing_error)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not output a warning' do
|
56
|
+
expect(Warning).to_not receive(:warn)
|
57
|
+
subject
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/unit/mash_spec.rb
CHANGED
@@ -33,6 +33,11 @@ describe Restforce::Mash do
|
|
33
33
|
let(:input) { { 'attributes' => { 'type' => 'Document' } } }
|
34
34
|
it { should eq Restforce::Document }
|
35
35
|
end
|
36
|
+
|
37
|
+
context 'when the attributes value is nil' do
|
38
|
+
let(:input) { { 'attributes' => nil } }
|
39
|
+
it { should eq Restforce::SObject }
|
40
|
+
end
|
36
41
|
end
|
37
42
|
|
38
43
|
context 'else' do
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
describe Restforce::Middleware::Authentication::JWTBearer do
|
5
|
+
let(:jwt_key) { File.read('spec/fixtures/test_private.key') }
|
6
|
+
|
7
|
+
let(:options) do
|
8
|
+
{ host: 'login.salesforce.com',
|
9
|
+
client_id: 'client_id',
|
10
|
+
username: 'foo',
|
11
|
+
jwt_key: jwt_key,
|
12
|
+
instance_url: 'https://na1.salesforce.com',
|
13
|
+
adapter: :net_http }
|
14
|
+
end
|
15
|
+
|
16
|
+
it_behaves_like 'authentication middleware' do
|
17
|
+
let(:success_request) do
|
18
|
+
stub_login_request(
|
19
|
+
body: "grant_type=grant_type—urn:ietf:params:oauth:grant-type:jwt-bearer&" \
|
20
|
+
"assertion=abc1234567890"
|
21
|
+
).to_return(status: 200, body: fixture(:auth_success_response))
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:fail_request) do
|
25
|
+
stub_login_request(
|
26
|
+
body: "grant_type=grant_type—urn:ietf:params:oauth:grant-type:jwt-bearer&" \
|
27
|
+
"assertion=abc1234567890"
|
28
|
+
).to_return(status: 400, body: fixture(:refresh_error_response))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'allows jwt_key as string' do
|
33
|
+
let(:jwt_key) do
|
34
|
+
File.read('spec/fixtures/test_private.key')
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:options) do
|
38
|
+
{ host: 'login.salesforce.com',
|
39
|
+
client_id: 'client_id',
|
40
|
+
username: 'foo',
|
41
|
+
jwt_key: jwt_key,
|
42
|
+
instance_url: 'https://na1.salesforce.com',
|
43
|
+
adapter: :net_http }
|
44
|
+
end
|
45
|
+
|
46
|
+
it_behaves_like 'authentication middleware' do
|
47
|
+
let(:success_request) do
|
48
|
+
stub_login_request(
|
49
|
+
body: "grant_type=grant_type—urn:ietf:params:oauth:grant-type:jwt-bearer&" \
|
50
|
+
"assertion=abc1234567890"
|
51
|
+
).to_return(status: 200, body: fixture(:auth_success_response))
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:fail_request) do
|
55
|
+
stub_login_request(
|
56
|
+
body: "grant_type=grant_type—urn:ietf:params:oauth:grant-type:jwt-bearer&" \
|
57
|
+
"assertion=abc1234567890"
|
58
|
+
).to_return(status: 400, body: fixture(:refresh_error_response))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -17,14 +17,14 @@ describe Restforce::Middleware::Authentication::Password do
|
|
17
17
|
let(:success_request) do
|
18
18
|
stub_login_request(
|
19
19
|
body: "grant_type=password&client_id=client_id&client_secret=client_secret" \
|
20
|
-
|
20
|
+
"&username=foo&password=barsecurity_token"
|
21
21
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
22
22
|
end
|
23
23
|
|
24
24
|
let(:fail_request) do
|
25
25
|
stub_login_request(
|
26
26
|
body: "grant_type=password&client_id=client_id&client_secret=client_secret" \
|
27
|
-
|
27
|
+
"&username=foo&password=barsecurity_token"
|
28
28
|
).to_return(status: 400, body: fixture(:auth_error_response))
|
29
29
|
end
|
30
30
|
end
|
@@ -15,14 +15,14 @@ describe Restforce::Middleware::Authentication::Token do
|
|
15
15
|
let(:success_request) do
|
16
16
|
stub_login_request(
|
17
17
|
body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
18
|
-
|
18
|
+
"client_id=client_id&client_secret=client_secret"
|
19
19
|
).to_return(status: 200, body: fixture(:auth_success_response))
|
20
20
|
end
|
21
21
|
|
22
22
|
let(:fail_request) do
|
23
23
|
stub_login_request(
|
24
24
|
body: "grant_type=refresh_token&refresh_token=refresh_token&" \
|
25
|
-
|
25
|
+
"client_id=client_id&client_secret=client_secret"
|
26
26
|
).to_return(status: 400, body: fixture(:refresh_error_response))
|
27
27
|
end
|
28
28
|
end
|
@@ -8,7 +8,9 @@ describe Restforce::Middleware::Authentication do
|
|
8
8
|
proxy_uri: 'https://not-a-real-site.com',
|
9
9
|
authentication_retries: retries,
|
10
10
|
adapter: :net_http,
|
11
|
+
# rubocop:disable Naming/VariableNumber
|
11
12
|
ssl: { version: :TLSv1_2 } }
|
13
|
+
# rubocop:enable Naming/VariableNumber
|
12
14
|
end
|
13
15
|
|
14
16
|
describe '.authenticate!' do
|
@@ -57,10 +59,10 @@ describe Restforce::Middleware::Authentication do
|
|
57
59
|
end
|
58
60
|
|
59
61
|
its(:handlers) {
|
60
|
-
should include FaradayMiddleware::ParseJson
|
61
|
-
Faraday::Adapter::NetHttp
|
62
|
+
should include FaradayMiddleware::ParseJson
|
62
63
|
}
|
63
64
|
its(:handlers) { should_not include Restforce::Middleware::Logger }
|
65
|
+
its(:adapter) { should eq Faraday::Adapter::NetHttp }
|
64
66
|
end
|
65
67
|
|
66
68
|
context 'with logging enabled' do
|
@@ -70,8 +72,9 @@ describe Restforce::Middleware::Authentication do
|
|
70
72
|
|
71
73
|
its(:handlers) {
|
72
74
|
should include FaradayMiddleware::ParseJson,
|
73
|
-
Restforce::Middleware::Logger
|
75
|
+
Restforce::Middleware::Logger
|
74
76
|
}
|
77
|
+
its(:adapter) { should eq Faraday::Adapter::NetHttp }
|
75
78
|
end
|
76
79
|
|
77
80
|
context 'with specified adapter' do
|
@@ -80,13 +83,37 @@ describe Restforce::Middleware::Authentication do
|
|
80
83
|
end
|
81
84
|
|
82
85
|
its(:handlers) {
|
83
|
-
should include FaradayMiddleware::ParseJson
|
86
|
+
should include FaradayMiddleware::ParseJson
|
84
87
|
}
|
88
|
+
its(:adapter) { should eq Faraday::Adapter::Typhoeus }
|
85
89
|
end
|
86
90
|
end
|
87
91
|
|
88
92
|
it "should have SSL config set" do
|
93
|
+
# rubocop:disable Naming/VariableNumber
|
89
94
|
connection.ssl[:version].should eq(:TLSv1_2)
|
95
|
+
# rubocop:enable Naming/VariableNumber
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '.error_message' do
|
100
|
+
context 'when response.body is present' do
|
101
|
+
let(:response) {
|
102
|
+
Faraday::Response.new(
|
103
|
+
response_body: { 'error' => 'error', 'error_description' => 'description' },
|
104
|
+
status: 401
|
105
|
+
)
|
106
|
+
}
|
107
|
+
|
108
|
+
subject { middleware.error_message(response) }
|
109
|
+
it { should eq "error: description (401)" }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'when response.body is nil' do
|
113
|
+
let(:response) { Faraday::Response.new(status: 401) }
|
114
|
+
|
115
|
+
subject { middleware.error_message(response) }
|
116
|
+
it { should eq "401" }
|
90
117
|
end
|
91
118
|
end
|
92
119
|
end
|
@@ -58,11 +58,11 @@ describe Restforce::Middleware::Gzip do
|
|
58
58
|
env[:response_headers]['Content-Encoding'] = 'gzip'
|
59
59
|
end
|
60
60
|
|
61
|
-
it { should
|
61
|
+
it { should be true }
|
62
62
|
end
|
63
63
|
|
64
64
|
context 'when not gzipped' do
|
65
|
-
it { should
|
65
|
+
it { should be false }
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -13,55 +13,95 @@ describe Restforce::Middleware::RaiseError do
|
|
13
13
|
context 'when the status code is 404' do
|
14
14
|
let(:status) { 404 }
|
15
15
|
|
16
|
-
it
|
17
|
-
expect { on_complete }.to raise_error
|
18
|
-
|
16
|
+
it 'raises Restforce::NotFoundError' do
|
17
|
+
expect { on_complete }.to raise_error do |error|
|
18
|
+
expect(error).to be_a Restforce::NotFoundError
|
19
|
+
expect(error.message).to start_with("INVALID_FIELD: error_message")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises an error that inherits from Faraday::ResourceNotFound' do
|
24
|
+
expect { on_complete }.to raise_error Faraday::ResourceNotFound
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
28
|
context 'when the status code is 300' do
|
23
29
|
let(:status) { 300 }
|
24
30
|
|
25
|
-
it
|
26
|
-
expect { on_complete }.to raise_error
|
31
|
+
it 'raises Restforce::MatchesMultipleError' do
|
32
|
+
expect { on_complete }.to raise_error Restforce::MatchesMultipleError,
|
27
33
|
/300: The external ID provided/
|
28
34
|
end
|
35
|
+
|
36
|
+
it 'raises an error that inherits from Faraday::ClientError' do
|
37
|
+
expect { on_complete }.to raise_error Faraday::ClientError
|
38
|
+
end
|
29
39
|
end
|
30
40
|
|
31
41
|
context 'when the status code is 400' do
|
32
42
|
let(:status) { 400 }
|
33
43
|
|
34
|
-
it "raises an error" do
|
35
|
-
expect { on_complete }.to raise_error
|
36
|
-
|
44
|
+
it "raises an error derived from the response's errorCode" do
|
45
|
+
expect { on_complete }.to raise_error do |error|
|
46
|
+
expect(error).to be_a Restforce::ErrorCode::InvalidField
|
47
|
+
expect(error.message).to start_with("INVALID_FIELD: error_message")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'raises an error that inherits from Faraday::ClientError' do
|
52
|
+
expect { on_complete }.to raise_error Faraday::ClientError
|
37
53
|
end
|
38
54
|
end
|
39
55
|
|
40
56
|
context 'when the status code is 401' do
|
41
57
|
let(:status) { 401 }
|
42
58
|
|
43
|
-
it
|
44
|
-
expect { on_complete }.to raise_error
|
45
|
-
|
59
|
+
it 'raises Restforce::UnauthorizedError' do
|
60
|
+
expect { on_complete }.to raise_error do |error|
|
61
|
+
expect(error).to be_a Restforce::UnauthorizedError
|
62
|
+
expect(error.message).to start_with("INVALID_FIELD: error_message")
|
63
|
+
end
|
46
64
|
end
|
47
65
|
end
|
48
66
|
|
49
67
|
context 'when the status code is 413' do
|
50
68
|
let(:status) { 413 }
|
51
69
|
|
52
|
-
it
|
53
|
-
expect { on_complete }.to raise_error
|
70
|
+
it 'raises Restforce::EntityTooLargeError' do
|
71
|
+
expect { on_complete }.to raise_error Restforce::EntityTooLargeError,
|
54
72
|
'413: Request Entity Too Large'
|
55
73
|
end
|
74
|
+
|
75
|
+
it 'raises an error that inherits from Faraday::ClientError' do
|
76
|
+
expect { on_complete }.to raise_error Faraday::ClientError
|
77
|
+
end
|
56
78
|
end
|
57
79
|
|
58
80
|
context 'when status is 400+ and body is a string' do
|
59
81
|
let(:body) { 'An error occured' }
|
60
|
-
let(:status) {
|
82
|
+
let(:status) { 400 }
|
83
|
+
|
84
|
+
it 'raises a generic Restforce::ResponseError' do
|
85
|
+
expect { on_complete }.to raise_error do |error|
|
86
|
+
expect(error).to be_a Restforce::ResponseError
|
87
|
+
expect(error.message).to start_with("(error code missing): An error occured")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'raises an error that inherits from Faraday::ClientError' do
|
92
|
+
expect { on_complete }.to raise_error do |error|
|
93
|
+
expect(error).to be_a Faraday::ClientError
|
94
|
+
expect(error.message).to start_with("(error code missing): An error occured")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when error code is not already defined' do
|
100
|
+
let(:body) { { 'errorCode' => 'SOMETHING_UNDEFINED' } }
|
101
|
+
let(:status) { 400 }
|
61
102
|
|
62
|
-
it 'raises
|
63
|
-
expect { on_complete }.to raise_error
|
64
|
-
"(error code missing): #{body}"
|
103
|
+
it 'raises a generic Restforce::ResponseError' do
|
104
|
+
expect { on_complete }.to raise_error Restforce::ResponseError
|
65
105
|
end
|
66
106
|
end
|
67
107
|
end
|
data/spec/unit/sobject_spec.rb
CHANGED
@@ -45,13 +45,10 @@ describe Restforce::SObject do
|
|
45
45
|
destroy: :destroy,
|
46
46
|
destroy!: :destroy! }.each do |method, receiver|
|
47
47
|
describe ".#{method}" do
|
48
|
-
subject(:send_method) { sobject.send(method) }
|
48
|
+
subject(:send_method) { lambda { sobject.send(method) } }
|
49
49
|
|
50
50
|
context 'when an Id was not queried' do
|
51
|
-
it
|
52
|
-
expect { send_method }.to raise_error ArgumentError,
|
53
|
-
/need to query the Id for the record/
|
54
|
-
end
|
51
|
+
it { should raise_error ArgumentError, /need to query the Id for the record/ }
|
55
52
|
end
|
56
53
|
|
57
54
|
context 'when an Id is present' do
|