restforce 3.0.1 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +9 -9
  3. data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
  4. data/.github/dependabot.yml +19 -0
  5. data/.rubocop.yml +13 -14
  6. data/.rubocop_todo.yml +128 -81
  7. data/CHANGELOG.md +107 -1
  8. data/CONTRIBUTING.md +21 -1
  9. data/Dockerfile +31 -0
  10. data/Gemfile +10 -6
  11. data/README.md +168 -31
  12. data/UPGRADING.md +38 -0
  13. data/docker-compose.yml +7 -0
  14. data/lib/restforce/abstract_client.rb +1 -0
  15. data/lib/restforce/attachment.rb +1 -0
  16. data/lib/restforce/collection.rb +7 -2
  17. data/lib/restforce/concerns/api.rb +10 -7
  18. data/lib/restforce/concerns/authentication.rb +10 -0
  19. data/lib/restforce/concerns/base.rb +4 -2
  20. data/lib/restforce/concerns/batch_api.rb +87 -0
  21. data/lib/restforce/concerns/caching.rb +7 -0
  22. data/lib/restforce/concerns/canvas.rb +1 -0
  23. data/lib/restforce/concerns/connection.rb +3 -3
  24. data/lib/restforce/concerns/picklists.rb +4 -3
  25. data/lib/restforce/concerns/streaming.rb +73 -3
  26. data/lib/restforce/config.rb +8 -1
  27. data/lib/restforce/document.rb +1 -0
  28. data/lib/restforce/error_code.rb +638 -0
  29. data/lib/restforce/file_part.rb +24 -0
  30. data/lib/restforce/mash.rb +8 -3
  31. data/lib/restforce/middleware/authentication/jwt_bearer.rb +38 -0
  32. data/lib/restforce/middleware/authentication.rb +7 -3
  33. data/lib/restforce/middleware/caching.rb +1 -1
  34. data/lib/restforce/middleware/instance_url.rb +1 -1
  35. data/lib/restforce/middleware/logger.rb +8 -7
  36. data/lib/restforce/middleware/multipart.rb +1 -0
  37. data/lib/restforce/middleware/raise_error.rb +24 -9
  38. data/lib/restforce/middleware.rb +2 -0
  39. data/lib/restforce/signed_request.rb +1 -0
  40. data/lib/restforce/sobject.rb +1 -0
  41. data/lib/restforce/tooling/client.rb +3 -3
  42. data/lib/restforce/version.rb +1 -1
  43. data/lib/restforce.rb +21 -3
  44. data/restforce.gemspec +11 -20
  45. data/spec/fixtures/test_private.key +27 -0
  46. data/spec/integration/abstract_client_spec.rb +83 -33
  47. data/spec/integration/data/client_spec.rb +6 -2
  48. data/spec/spec_helper.rb +24 -1
  49. data/spec/support/client_integration.rb +7 -7
  50. data/spec/support/concerns.rb +1 -1
  51. data/spec/support/fixture_helpers.rb +3 -5
  52. data/spec/support/middleware.rb +1 -2
  53. data/spec/unit/collection_spec.rb +20 -2
  54. data/spec/unit/concerns/api_spec.rb +12 -12
  55. data/spec/unit/concerns/authentication_spec.rb +39 -4
  56. data/spec/unit/concerns/batch_api_spec.rb +107 -0
  57. data/spec/unit/concerns/caching_spec.rb +26 -0
  58. data/spec/unit/concerns/connection_spec.rb +2 -2
  59. data/spec/unit/concerns/streaming_spec.rb +144 -4
  60. data/spec/unit/config_spec.rb +1 -1
  61. data/spec/unit/error_code_spec.rb +61 -0
  62. data/spec/unit/mash_spec.rb +5 -0
  63. data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +62 -0
  64. data/spec/unit/middleware/authentication/password_spec.rb +2 -2
  65. data/spec/unit/middleware/authentication/token_spec.rb +2 -2
  66. data/spec/unit/middleware/authentication_spec.rb +31 -4
  67. data/spec/unit/middleware/gzip_spec.rb +2 -2
  68. data/spec/unit/middleware/raise_error_spec.rb +57 -17
  69. data/spec/unit/signed_request_spec.rb +1 -1
  70. data/spec/unit/sobject_spec.rb +2 -5
  71. metadata +39 -108
  72. 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
@@ -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
- "&username=foo&password=barsecurity_token"
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
- "&username=foo&password=barsecurity_token"
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
- "client_id=client_id&client_secret=client_secret"
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
- "client_id=client_id&client_secret=client_secret"
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, Faraday::Adapter::NetHttp
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, Faraday::Adapter::Typhoeus
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 be_true }
61
+ it { should be true }
62
62
  end
63
63
 
64
64
  context 'when not gzipped' do
65
- it { should be_false }
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 "raises an error" do
17
- expect { on_complete }.to raise_error Faraday::Error::ResourceNotFound,
18
- 'INVALID_FIELD: error_message'
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 "raises an error" do
26
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
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 Faraday::Error::ClientError,
36
- 'INVALID_FIELD: error_message'
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 "raises an error" do
44
- expect { on_complete }.to raise_error Restforce::UnauthorizedError,
45
- 'INVALID_FIELD: error_message'
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 "raises an error" do
53
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
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) { 404 }
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 an error with a non-existing error code' do
63
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
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
@@ -6,7 +6,7 @@ describe Restforce::SignedRequest do
6
6
  let(:client_secret) { 'foo' }
7
7
  let(:digest) do
8
8
  if RUBY_VERSION < '2.1'
9
- OpenSSL::Digest::Digest.new('sha256')
9
+ OpenSSL::Digest.new('Digest', 'sha256')
10
10
  else
11
11
  OpenSSL::Digest.new('sha256')
12
12
  end
@@ -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 "raises an error" do
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