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.
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