restforce 2.5.4 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +56 -0
- data/.rubocop.yml +27 -14
- data/.rubocop_todo.yml +128 -81
- data/CHANGELOG.md +37 -3
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +4 -2
- data/Guardfile +3 -1
- data/LICENSE +1 -1
- data/README.md +120 -19
- data/Rakefile +2 -1
- data/lib/restforce.rb +23 -1
- data/lib/restforce/abstract_client.rb +3 -0
- data/lib/restforce/attachment.rb +3 -0
- data/lib/restforce/client.rb +2 -0
- data/lib/restforce/collection.rb +3 -1
- data/lib/restforce/concerns/api.rb +20 -14
- data/lib/restforce/concerns/authentication.rb +2 -0
- data/lib/restforce/concerns/base.rb +2 -0
- data/lib/restforce/concerns/batch_api.rb +87 -0
- data/lib/restforce/concerns/caching.rb +4 -2
- data/lib/restforce/concerns/canvas.rb +3 -0
- data/lib/restforce/concerns/connection.rb +26 -20
- data/lib/restforce/concerns/picklists.rb +9 -6
- data/lib/restforce/concerns/streaming.rb +60 -1
- data/lib/restforce/concerns/verbs.rb +3 -1
- data/lib/restforce/config.rb +4 -1
- data/lib/restforce/data/client.rb +2 -0
- data/lib/restforce/document.rb +3 -0
- data/lib/restforce/mash.rb +2 -0
- data/lib/restforce/middleware.rb +2 -0
- data/lib/restforce/middleware/authentication.rb +8 -6
- data/lib/restforce/middleware/authentication/password.rb +2 -0
- data/lib/restforce/middleware/authentication/token.rb +2 -0
- data/lib/restforce/middleware/authorization.rb +3 -1
- data/lib/restforce/middleware/caching.rb +3 -1
- data/lib/restforce/middleware/custom_headers.rb +2 -0
- data/lib/restforce/middleware/gzip.rb +5 -3
- data/lib/restforce/middleware/instance_url.rb +7 -3
- data/lib/restforce/middleware/logger.rb +2 -0
- data/lib/restforce/middleware/mashify.rb +2 -0
- data/lib/restforce/middleware/multipart.rb +8 -4
- data/lib/restforce/middleware/raise_error.rb +26 -8
- data/lib/restforce/patches/parts.rb +2 -0
- data/lib/restforce/signed_request.rb +3 -0
- data/lib/restforce/sobject.rb +3 -0
- data/lib/restforce/tooling/client.rb +5 -3
- data/lib/restforce/upload_io.rb +2 -0
- data/lib/restforce/version.rb +3 -1
- data/restforce.gemspec +19 -12
- data/spec/fixtures/sobject/sobject_describe_success_response.json +48 -1
- data/spec/integration/abstract_client_spec.rb +51 -7
- data/spec/integration/data/client_spec.rb +24 -5
- data/spec/spec_helper.rb +2 -0
- data/spec/support/client_integration.rb +2 -0
- data/spec/support/concerns.rb +2 -0
- data/spec/support/event_machine.rb +2 -0
- data/spec/support/fixture_helpers.rb +4 -2
- data/spec/support/matchers.rb +2 -0
- data/spec/support/middleware.rb +3 -1
- data/spec/support/mock_cache.rb +4 -2
- data/spec/unit/abstract_client_spec.rb +2 -0
- data/spec/unit/attachment_spec.rb +2 -0
- data/spec/unit/collection_spec.rb +5 -3
- data/spec/unit/concerns/api_spec.rb +40 -11
- data/spec/unit/concerns/authentication_spec.rb +4 -2
- data/spec/unit/concerns/base_spec.rb +2 -0
- data/spec/unit/concerns/batch_api_spec.rb +107 -0
- data/spec/unit/concerns/caching_spec.rb +2 -0
- data/spec/unit/concerns/canvas_spec.rb +3 -1
- data/spec/unit/concerns/connection_spec.rb +5 -3
- data/spec/unit/concerns/streaming_spec.rb +115 -1
- data/spec/unit/config_spec.rb +10 -8
- data/spec/unit/data/client_spec.rb +2 -0
- data/spec/unit/document_spec.rb +2 -0
- data/spec/unit/mash_spec.rb +3 -1
- data/spec/unit/middleware/authentication/password_spec.rb +2 -0
- data/spec/unit/middleware/authentication/token_spec.rb +2 -0
- data/spec/unit/middleware/authentication_spec.rb +3 -1
- data/spec/unit/middleware/authorization_spec.rb +2 -0
- data/spec/unit/middleware/custom_headers_spec.rb +3 -1
- data/spec/unit/middleware/gzip_spec.rb +4 -2
- data/spec/unit/middleware/instance_url_spec.rb +2 -0
- data/spec/unit/middleware/logger_spec.rb +2 -0
- data/spec/unit/middleware/mashify_spec.rb +3 -1
- data/spec/unit/middleware/raise_error_spec.rb +34 -11
- data/spec/unit/signed_request_spec.rb +2 -0
- data/spec/unit/sobject_spec.rb +5 -3
- data/spec/unit/tooling/client_spec.rb +2 -0
- metadata +38 -20
- data/.travis.yml +0 -16
- data/Gemfile.travis +0 -8
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Concerns::Connection do
|
@@ -34,7 +36,7 @@ describe Restforce::Concerns::Connection do
|
|
34
36
|
describe 'with mashify not specified' do
|
35
37
|
it 'includes the Mashify middleware' do
|
36
38
|
client.middleware.handlers.index(Restforce::Middleware::Mashify).
|
37
|
-
|
39
|
+
should_not be_nil
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
@@ -45,7 +47,7 @@ describe Restforce::Concerns::Connection do
|
|
45
47
|
|
46
48
|
it 'includes the Mashify middleware' do
|
47
49
|
client.middleware.handlers.index(Restforce::Middleware::Mashify).
|
48
|
-
|
50
|
+
should_not be_nil
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
@@ -56,7 +58,7 @@ describe Restforce::Concerns::Connection do
|
|
56
58
|
|
57
59
|
it 'does not include the Mashify middleware' do
|
58
60
|
client.middleware.handlers.index(Restforce::Middleware::Mashify).
|
59
|
-
|
61
|
+
should be_nil
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Concerns::Streaming, event_machine: true do
|
4
6
|
describe '.subscribe' do
|
5
|
-
let(:channels) { %w
|
7
|
+
let(:channels) { %w[channel1 channel2] }
|
6
8
|
let(:topics) { channels.map { |c| "/topic/#{c}" } }
|
7
9
|
let(:subscribe_block) { lambda { 'subscribe' } }
|
8
10
|
let(:faye_double) { double('Faye') }
|
@@ -15,6 +17,33 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
15
17
|
|
16
18
|
client.subscribe(channels, &subscribe_block)
|
17
19
|
end
|
20
|
+
|
21
|
+
context "replay_handlers" do
|
22
|
+
before {
|
23
|
+
faye_double.should_receive(:subscribe).at_least(1)
|
24
|
+
client.stub faye: faye_double
|
25
|
+
}
|
26
|
+
|
27
|
+
it 'registers nil handlers when no replay option is given' do
|
28
|
+
client.subscribe(channels, &subscribe_block)
|
29
|
+
client.replay_handlers.should eq('channel1' => nil, 'channel2' => nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'registers a replay_handler for each channel given' do
|
33
|
+
client.subscribe(channels, replay: -2, &subscribe_block)
|
34
|
+
client.replay_handlers.should eq('channel1' => -2, 'channel2' => -2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'replaces earlier handlers in subsequent calls' do
|
38
|
+
client.subscribe(%w[channel1 channel2], replay: 2, &subscribe_block)
|
39
|
+
client.subscribe(%w[channel2 channel3], replay: 3, &subscribe_block)
|
40
|
+
client.replay_handlers.should eq(
|
41
|
+
'channel1' => 2,
|
42
|
+
'channel2' => 3,
|
43
|
+
'channel3' => 3
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
18
47
|
end
|
19
48
|
|
20
49
|
describe '.faye' do
|
@@ -40,6 +69,8 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
40
69
|
faye_double.should_receive(:set_header).with('Authorization', 'OAuth secret2')
|
41
70
|
faye_double.should_receive(:bind).with('transport:down').and_yield
|
42
71
|
faye_double.should_receive(:bind).with('transport:up').and_yield
|
72
|
+
faye_double.should_receive(:add_extension).with \
|
73
|
+
kind_of(Restforce::Concerns::Streaming::ReplayExtension)
|
43
74
|
subject
|
44
75
|
end
|
45
76
|
end
|
@@ -50,4 +81,87 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
50
81
|
end
|
51
82
|
end
|
52
83
|
end
|
84
|
+
|
85
|
+
describe Restforce::Concerns::Streaming::ReplayExtension do
|
86
|
+
let(:handlers) { {} }
|
87
|
+
let(:extension) { Restforce::Concerns::Streaming::ReplayExtension.new(handlers) }
|
88
|
+
|
89
|
+
it 'sends nil without a specified handler' do
|
90
|
+
output = subscribe(extension, to: "channel1")
|
91
|
+
read_replay(output).should eq('/topic/channel1' => nil)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'with a scalar replay id' do
|
95
|
+
handlers['channel1'] = -2
|
96
|
+
output = subscribe(extension, to: "channel1")
|
97
|
+
read_replay(output).should eq('/topic/channel1' => -2)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'with a hash' do
|
101
|
+
hash_handler = { 'channel1' => -1, 'channel2' => -2 }
|
102
|
+
|
103
|
+
handlers['channel1'] = hash_handler
|
104
|
+
handlers['channel2'] = hash_handler
|
105
|
+
|
106
|
+
output = subscribe(extension, to: "channel1")
|
107
|
+
read_replay(output).should eq('/topic/channel1' => -1)
|
108
|
+
|
109
|
+
output = subscribe(extension, to: "channel2")
|
110
|
+
read_replay(output).should eq('/topic/channel2' => -2)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'with an object' do
|
114
|
+
custom_handler = double('custom_handler')
|
115
|
+
custom_handler.should_receive(:[]).and_return(123)
|
116
|
+
handlers['channel1'] = custom_handler
|
117
|
+
|
118
|
+
output = subscribe(extension, to: "channel1")
|
119
|
+
read_replay(output).should eq('/topic/channel1' => 123)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'remembers the last replayId' do
|
123
|
+
handler = { 'channel1' => 41 }
|
124
|
+
handlers['channel1'] = handler
|
125
|
+
message = {
|
126
|
+
'channel' => '/topic/channel1',
|
127
|
+
'data' => {
|
128
|
+
'event' => { 'replayId' => 42 }
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
extension.incoming(message, ->(m) {})
|
133
|
+
handler.should eq('channel1' => 42)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'when an incoming message has no replayId' do
|
137
|
+
handler = { 'channel1' => 41 }
|
138
|
+
handlers['channel1'] = handler
|
139
|
+
|
140
|
+
message = {
|
141
|
+
'channel' => '/topic/channel1',
|
142
|
+
'data' => {}
|
143
|
+
}
|
144
|
+
|
145
|
+
extension.incoming(message, ->(m) {})
|
146
|
+
handler.should eq('channel1' => 41)
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def subscribe(extension, options = {})
|
152
|
+
output = nil
|
153
|
+
message = {
|
154
|
+
'channel' => '/meta/subscribe',
|
155
|
+
'subscription' => "/topic/#{options[:to]}"
|
156
|
+
}
|
157
|
+
extension.outgoing(message, ->(m) {
|
158
|
+
output = m
|
159
|
+
})
|
160
|
+
output
|
161
|
+
end
|
162
|
+
|
163
|
+
def read_replay(message)
|
164
|
+
message.fetch('ext', {})['replay']
|
165
|
+
end
|
166
|
+
end
|
53
167
|
end
|
data/spec/unit/config_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce do
|
@@ -25,9 +27,9 @@ describe Restforce do
|
|
25
27
|
its(:authentication_retries) { should eq 3 }
|
26
28
|
its(:adapter) { should eq Faraday.default_adapter }
|
27
29
|
its(:ssl) { should eq({}) }
|
28
|
-
[
|
29
|
-
|
30
|
-
|
30
|
+
%i[username password security_token client_id client_secret
|
31
|
+
oauth_token refresh_token instance_url compress timeout
|
32
|
+
proxy_uri authentication_callback mashify request_headers].each do |attr|
|
31
33
|
its(attr) { should be_nil }
|
32
34
|
end
|
33
35
|
end
|
@@ -42,7 +44,7 @@ describe Restforce do
|
|
42
44
|
'SALESFORCE_PROXY_URI' => 'proxy',
|
43
45
|
'SALESFORCE_HOST' => 'test.host.com',
|
44
46
|
'SALESFORCE_API_VERSION' => '37.0' }.
|
45
|
-
|
47
|
+
each { |var, value| ENV.stub(:[]).with(var).and_return(value) }
|
46
48
|
end
|
47
49
|
|
48
50
|
its(:username) { should eq 'foo' }
|
@@ -57,10 +59,10 @@ describe Restforce do
|
|
57
59
|
end
|
58
60
|
|
59
61
|
describe '#configure' do
|
60
|
-
[
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
%i[username password security_token client_id client_secret compress
|
63
|
+
timeout oauth_token refresh_token instance_url api_version host mashify
|
64
|
+
authentication_retries proxy_uri authentication_callback ssl
|
65
|
+
request_headers log_level logger].each do |attr|
|
64
66
|
it "allows #{attr} to be set" do
|
65
67
|
Restforce.configure do |config|
|
66
68
|
config.send("#{attr}=", 'foobar')
|
data/spec/unit/document_spec.rb
CHANGED
data/spec/unit/mash_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Mash do
|
@@ -6,7 +8,7 @@ describe Restforce::Mash do
|
|
6
8
|
|
7
9
|
context 'when array' do
|
8
10
|
let(:input) { [{ foo: 'hello' }, { bar: 'world' }] }
|
9
|
-
it { should
|
11
|
+
it { should(be_all { |obj| expect(obj).to be_a Restforce::Mash }) }
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Middleware::Authentication do
|
@@ -58,7 +60,7 @@ describe Restforce::Middleware::Authentication do
|
|
58
60
|
should include FaradayMiddleware::ParseJson,
|
59
61
|
Faraday::Adapter::NetHttp
|
60
62
|
}
|
61
|
-
its(:handlers) { should_not include Restforce::Middleware::Logger
|
63
|
+
its(:handlers) { should_not include Restforce::Middleware::Logger }
|
62
64
|
end
|
63
65
|
|
64
66
|
context 'with logging enabled' do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Middleware::CustomHeaders do
|
@@ -13,7 +15,7 @@ describe Restforce::Middleware::CustomHeaders do
|
|
13
15
|
context 'when :request_headers are not a Hash' do
|
14
16
|
let(:options) { { request_headers: 'bad header' } }
|
15
17
|
|
16
|
-
it { should_not
|
18
|
+
it { should_not(change { env[:request_headers] }) }
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Middleware::Gzip do
|
@@ -29,7 +31,7 @@ describe Restforce::Middleware::Gzip do
|
|
29
31
|
end
|
30
32
|
|
31
33
|
context 'when :compress is false' do
|
32
|
-
it { should_not
|
34
|
+
it { should_not(change { env[:request_headers]['Accept-Encoding'] }) }
|
33
35
|
end
|
34
36
|
|
35
37
|
context 'when :compress is true' do
|
@@ -37,7 +39,7 @@ describe Restforce::Middleware::Gzip do
|
|
37
39
|
options[:compress] = true
|
38
40
|
end
|
39
41
|
|
40
|
-
it { should
|
42
|
+
it { should(change { env[:request_headers]['Accept-Encoding'] }.to('gzip')) }
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Middleware::Mashify do
|
4
6
|
let(:env) { { body: JSON.parse(fixture('sobject/query_success_response')) } }
|
5
7
|
subject(:middleware) {
|
6
|
-
described_class.new(lambda {|env|
|
8
|
+
described_class.new(lambda { |env|
|
7
9
|
Faraday::Response.new(env)
|
8
10
|
}, client, options)
|
9
11
|
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe Restforce::Middleware::RaiseError do
|
@@ -11,34 +13,46 @@ describe Restforce::Middleware::RaiseError do
|
|
11
13
|
context 'when the status code is 404' do
|
12
14
|
let(:status) { 404 }
|
13
15
|
|
14
|
-
it
|
15
|
-
expect { on_complete }.to raise_error
|
16
|
+
it 'raises Restforce::NotFoundError' do
|
17
|
+
expect { on_complete }.to raise_error Restforce::NotFoundError,
|
16
18
|
'INVALID_FIELD: error_message'
|
17
19
|
end
|
20
|
+
|
21
|
+
it 'raises an error that inherits from Faraday::Error::ResourceNotFound' do
|
22
|
+
expect { on_complete }.to raise_error Faraday::Error::ResourceNotFound
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
context 'when the status code is 300' do
|
21
27
|
let(:status) { 300 }
|
22
28
|
|
23
|
-
it
|
24
|
-
expect { on_complete }.to raise_error
|
29
|
+
it 'raises Restforce::MatchesMultipleError' do
|
30
|
+
expect { on_complete }.to raise_error Restforce::MatchesMultipleError,
|
25
31
|
/300: The external ID provided/
|
26
32
|
end
|
33
|
+
|
34
|
+
it 'raises an error that inherits from Faraday::Error::ClientError' do
|
35
|
+
expect { on_complete }.to raise_error Faraday::Error::ClientError
|
36
|
+
end
|
27
37
|
end
|
28
38
|
|
29
39
|
context 'when the status code is 400' do
|
30
40
|
let(:status) { 400 }
|
31
41
|
|
32
|
-
it "raises an error" do
|
33
|
-
expect { on_complete }.to raise_error
|
42
|
+
it "raises an error derived from the response's errorCode" do
|
43
|
+
expect { on_complete }.to raise_error Restforce::ErrorCode::InvalidField,
|
34
44
|
'INVALID_FIELD: error_message'
|
35
45
|
end
|
46
|
+
|
47
|
+
it 'raises an error that inherits from Faraday::Error::ClientError' do
|
48
|
+
expect { on_complete }.to raise_error Faraday::Error::ClientError
|
49
|
+
end
|
36
50
|
end
|
37
51
|
|
38
52
|
context 'when the status code is 401' do
|
39
53
|
let(:status) { 401 }
|
40
54
|
|
41
|
-
it
|
55
|
+
it 'raises Restforce::UnauthorizedError' do
|
42
56
|
expect { on_complete }.to raise_error Restforce::UnauthorizedError,
|
43
57
|
'INVALID_FIELD: error_message'
|
44
58
|
end
|
@@ -47,17 +61,26 @@ describe Restforce::Middleware::RaiseError do
|
|
47
61
|
context 'when the status code is 413' do
|
48
62
|
let(:status) { 413 }
|
49
63
|
|
50
|
-
it
|
51
|
-
expect { on_complete }.to raise_error
|
64
|
+
it 'raises Restforce::EntityTooLargeError' do
|
65
|
+
expect { on_complete }.to raise_error Restforce::EntityTooLargeError,
|
52
66
|
'413: Request Entity Too Large'
|
53
67
|
end
|
68
|
+
|
69
|
+
it 'raises an error that inherits from Faraday::Error::ClientError' do
|
70
|
+
expect { on_complete }.to raise_error Faraday::Error::ClientError
|
71
|
+
end
|
54
72
|
end
|
55
73
|
|
56
74
|
context 'when status is 400+ and body is a string' do
|
57
75
|
let(:body) { 'An error occured' }
|
58
|
-
let(:status) {
|
76
|
+
let(:status) { 400 }
|
77
|
+
|
78
|
+
it 'raises a generic Restforce::ResponseError' do
|
79
|
+
expect { on_complete }.to raise_error Restforce::ResponseError,
|
80
|
+
"(error code missing): #{body}"
|
81
|
+
end
|
59
82
|
|
60
|
-
it 'raises an error
|
83
|
+
it 'raises an error that inherits from Faraday::Error::ClientError' do
|
61
84
|
expect { on_complete }.to raise_error Faraday::Error::ClientError,
|
62
85
|
"(error code missing): #{body}"
|
63
86
|
end
|