restforce 3.1.0 → 4.2.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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +9 -9
- data/.rubocop.yml +10 -12
- data/.rubocop_todo.yml +128 -81
- data/CHANGELOG.md +31 -1
- data/Gemfile +2 -1
- data/README.md +36 -19
- data/lib/restforce.rb +22 -1
- data/lib/restforce/abstract_client.rb +1 -0
- data/lib/restforce/attachment.rb +1 -0
- data/lib/restforce/concerns/api.rb +10 -7
- data/lib/restforce/concerns/authentication.rb +10 -0
- data/lib/restforce/concerns/base.rb +2 -0
- data/lib/restforce/concerns/batch_api.rb +87 -0
- data/lib/restforce/concerns/canvas.rb +1 -0
- data/lib/restforce/concerns/connection.rb +3 -3
- data/lib/restforce/concerns/picklists.rb +2 -1
- data/lib/restforce/concerns/streaming.rb +23 -10
- data/lib/restforce/config.rb +4 -0
- data/lib/restforce/document.rb +1 -0
- data/lib/restforce/middleware/authentication.rb +7 -3
- data/lib/restforce/middleware/authentication/jwt_bearer.rb +38 -0
- data/lib/restforce/middleware/instance_url.rb +1 -1
- data/lib/restforce/middleware/multipart.rb +1 -0
- data/lib/restforce/middleware/raise_error.rb +24 -8
- 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/restforce.gemspec +9 -8
- data/spec/fixtures/test_private.key +27 -0
- data/spec/integration/abstract_client_spec.rb +44 -3
- data/spec/support/fixture_helpers.rb +2 -2
- data/spec/unit/concerns/api_spec.rb +1 -1
- data/spec/unit/concerns/authentication_spec.rb +35 -0
- data/spec/unit/concerns/batch_api_spec.rb +107 -0
- data/spec/unit/concerns/streaming_spec.rb +58 -30
- data/spec/unit/middleware/authentication/jwt_bearer_spec.rb +62 -0
- data/spec/unit/middleware/authentication_spec.rb +21 -0
- data/spec/unit/middleware/raise_error_spec.rb +33 -12
- data/spec/unit/sobject_spec.rb +2 -5
- metadata +55 -34
|
@@ -6,23 +6,30 @@ module Restforce
|
|
|
6
6
|
@env = env
|
|
7
7
|
case env[:status]
|
|
8
8
|
when 300
|
|
9
|
-
raise
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
raise Restforce::MatchesMultipleError.new(
|
|
10
|
+
"300: The external ID provided matches more than one record",
|
|
11
|
+
response_values
|
|
12
|
+
)
|
|
12
13
|
when 401
|
|
13
14
|
raise Restforce::UnauthorizedError, message
|
|
14
15
|
when 404
|
|
15
|
-
raise
|
|
16
|
+
raise Restforce::NotFoundError, message
|
|
16
17
|
when 413
|
|
17
|
-
raise
|
|
18
|
-
|
|
18
|
+
raise Restforce::EntityTooLargeError.new(
|
|
19
|
+
"413: Request Entity Too Large",
|
|
20
|
+
response_values
|
|
21
|
+
)
|
|
19
22
|
when 400...600
|
|
20
|
-
|
|
23
|
+
klass = exception_class_for_error_code(body['errorCode'])
|
|
24
|
+
raise klass.new(message, response_values)
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def message
|
|
25
|
-
"#{body['errorCode']}: #{body['message']}"
|
|
29
|
+
message = "#{body['errorCode']}: #{body['message']}"
|
|
30
|
+
message << "\nRESPONSE: #{JSON.dump(@env[:body])}"
|
|
31
|
+
rescue StandardError
|
|
32
|
+
message # if JSON.dump fails, return message without extra detail
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
def body
|
|
@@ -43,5 +50,14 @@ module Restforce
|
|
|
43
50
|
body: @env[:body]
|
|
44
51
|
}
|
|
45
52
|
end
|
|
53
|
+
|
|
54
|
+
ERROR_CODE_MATCHER = /\A[A-Z_]+\z/.freeze
|
|
55
|
+
|
|
56
|
+
def exception_class_for_error_code(error_code)
|
|
57
|
+
return Restforce::ResponseError unless ERROR_CODE_MATCHER.match?(error_code)
|
|
58
|
+
|
|
59
|
+
constant_name = error_code.split('_').map(&:capitalize).join.to_sym
|
|
60
|
+
Restforce::ErrorCode.const_get(constant_name)
|
|
61
|
+
end
|
|
46
62
|
end
|
|
47
63
|
end
|
data/lib/restforce/sobject.rb
CHANGED
data/lib/restforce/version.rb
CHANGED
data/restforce.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require File.expand_path('
|
|
3
|
+
require File.expand_path('lib/restforce/version', __dir__)
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |gem|
|
|
6
6
|
gem.authors = ["Eric J. Holmes", "Tim Rogers"]
|
|
@@ -22,19 +22,20 @@ Gem::Specification.new do |gem|
|
|
|
22
22
|
'changelog_uri' => 'https://github.com/restforce/restforce/blob/master/CHANGELOG.md'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
gem.required_ruby_version = '>= 2.
|
|
25
|
+
gem.required_ruby_version = '>= 2.4'
|
|
26
26
|
|
|
27
27
|
gem.add_dependency 'faraday', '<= 1.0', '>= 0.9.0'
|
|
28
28
|
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 1.0']
|
|
29
29
|
|
|
30
30
|
gem.add_dependency 'json', '>= 1.7.5'
|
|
31
|
+
gem.add_dependency 'jwt', ['>= 1.5.6']
|
|
31
32
|
|
|
32
|
-
gem.add_dependency 'hashie',
|
|
33
|
+
gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
|
|
33
34
|
|
|
34
|
-
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
|
35
|
-
gem.add_development_dependency 'webmock', '~> 3.4.0'
|
|
36
|
-
gem.add_development_dependency 'simplecov', '~> 0.15.0'
|
|
37
|
-
gem.add_development_dependency 'rubocop', '~> 0.50.0'
|
|
38
|
-
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
|
|
39
35
|
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
|
36
|
+
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
|
37
|
+
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
|
|
38
|
+
gem.add_development_dependency 'rubocop', '~> 0.77.0'
|
|
39
|
+
gem.add_development_dependency 'simplecov', '~> 0.17.1'
|
|
40
|
+
gem.add_development_dependency 'webmock', '~> 3.7.6'
|
|
40
41
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
2
|
+
MIIEpAIBAAKCAQEAy3KYqxZIgVDgFwdA+OQcKMJQu3iUTlyCSk9b3RLBOudnvk8u
|
|
3
|
+
n0ShtKkOKB4b4RZeedcrlKESoak/6NS+M7CDemRT0EagqUiz/ZsZxB2KUp7au+d8
|
|
4
|
+
0KWX99/loBjDttuon8ITDw2WFC9X0+TZqfsXcQ0iV1/9Sf8WHShd8ZqShjJBlEvf
|
|
5
|
+
7u7VdNW8dXrl+4cvpPzspVxg6jVotEpmp875jmGRvshgx0iz0jtfAyxaaKStITC6
|
|
6
|
+
MxufVNDgIYQDl6queh8b9noDLtt17Eq6YnropYN1hOjaLtoLBP7AN2gsXG7N3vqC
|
|
7
|
+
JG619W9X4zCmKztv4oGjymInrS2msC2J02dNGQIDAQABAoIBAAurTARsJ8Z7DA9m
|
|
8
|
+
FBzygIb59kV6eg8wkSyP9rXscHbfdPzeb88k0Z2aILy+VV0IumyEofRJdNce7RJ+
|
|
9
|
+
uVYfprrrbD9C/c4X5HMEZWrxQtDQWb1zXp5dESVfiz0ujnM7kCVxrUQsxFHuETyP
|
|
10
|
+
IMj2JPcQCMs4L0ACSJNtkE3eTs8xko5kwDHZGiLTi5jD1bLgaHl1A+9CTU8LosTy
|
|
11
|
+
hEIrNSZfNidDPU4QSbwoElYZxpDMSbtyHaIk1WHz7zLzWoogK3x5AIQh64wWAQVd
|
|
12
|
+
zzlp2j2jSM7oQ9j+k1aNiUBdDoRX53jmaIwE/1WDW/LT33qAoqRw+5qHeLRoRcfu
|
|
13
|
+
3uj/WI0CgYEA6wnpIUhqqWT+febhXtCr1mAJlAJpzUUQncN6Zk0Kj/kE1V52OqgL
|
|
14
|
+
gtOactII7J3+0zK7KGptqseTank0ghmGNdRBQ7+1JTQhpjLrCm/huKDhl+sBk95u
|
|
15
|
+
opxw/ZTwMFYPwsmZlFcy4uWRjtI+QzaV+2Xk5JF57H8vUiX/+XqseQcCgYEA3Zdw
|
|
16
|
+
zVHpcVPlyiXCbSvwb9IYXiJaQl/Rg96Klxah3MZNyRRKe5IoKUTJwEDuQ1MAHrve
|
|
17
|
+
cWrNLcXhX6r/PzIXSSLe71wgwpn7UcaqWzZJqqN7OIGEeTzYWbB6tGhse7Dw7tWB
|
|
18
|
+
hRkQSE0LPzZqboHz5msRM02sa61qiI5+ASJvIN8CgYEAvT+IoEzv3R89ruBVPQPm
|
|
19
|
+
KMHBVJSw3iArJex8xJxp0c0fMDJUHhyq0BdTd/pYRzVcNm/VtNAlJ2p07zlSpyKo
|
|
20
|
+
JvWV61gUIjWclnbPO+MkK4YWvzzxUz+5c2NlszjWQQU6wYuUBpZDmeBg2E++5F2y
|
|
21
|
+
W+8KY2QjeOJbltiUCCvXbccCgYEAqARYB5aARumyZqBS16xlVqQazeWGQqWcmzx2
|
|
22
|
+
ITGL8XZ7LGgyQZgE06XQw/F3t5yLjsIsXBr7ECXmST/C4gv9E/tYxm04edV/dfYI
|
|
23
|
+
3bhACx6CI8owxCyabwcdQwWam/8B8FX7KwxiCDBCwt9ju/7VDHVKSXgvsEWBbaF9
|
|
24
|
+
cSbG1EkCgYBZFztTUnD/cLMcvLUegN0K+6Qa3x3nRSrlrJ+v51mU1X8G8qNyFO67
|
|
25
|
+
gUq9h4xbCl4Z5ZTuFKXwPM4XaMzfYdrWNS2zl5IG14FXS077GhDKe062b9mFoxtm
|
|
26
|
+
aViCit4Hm8xpLTS8x9KB7yYAiF9sR/GklW1SUCIqnpL9JShkhzjfZw==
|
|
27
|
+
-----END RSA PRIVATE KEY-----
|
|
@@ -96,7 +96,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
96
96
|
subject do
|
|
97
97
|
client.create('Account', Name: 'Foobar',
|
|
98
98
|
Blob: Restforce::UploadIO.new(
|
|
99
|
-
File.expand_path('
|
|
99
|
+
File.expand_path('../fixtures/blob.jpg', __dir__),
|
|
100
100
|
'image/jpeg'
|
|
101
101
|
))
|
|
102
102
|
end
|
|
@@ -125,7 +125,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
125
125
|
|
|
126
126
|
it {
|
|
127
127
|
should raise_error(
|
|
128
|
-
Faraday::
|
|
128
|
+
Faraday::ResourceNotFound,
|
|
129
129
|
"#{error.first['errorCode']}: #{error.first['message']}"
|
|
130
130
|
)
|
|
131
131
|
}
|
|
@@ -209,6 +209,24 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
209
209
|
end
|
|
210
210
|
end
|
|
211
211
|
end
|
|
212
|
+
|
|
213
|
+
context 'when created with a space in the id' do
|
|
214
|
+
requests 'sobjects/Account/External__c/foo%20bar',
|
|
215
|
+
method: :patch,
|
|
216
|
+
with_body: "{\"Name\":\"Foobar\"}",
|
|
217
|
+
fixture: 'sobject/upsert_created_success_response'
|
|
218
|
+
|
|
219
|
+
[:External__c, 'External__c', :external__c, 'external__c'].each do |key|
|
|
220
|
+
context "with #{key.inspect} as the external id" do
|
|
221
|
+
subject do
|
|
222
|
+
client.upsert!('Account', 'External__c', key => 'foo bar',
|
|
223
|
+
:Name => 'Foobar')
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it { should eq 'foo' }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
212
230
|
end
|
|
213
231
|
|
|
214
232
|
describe '.destroy!' do
|
|
@@ -221,7 +239,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
221
239
|
status: 404
|
|
222
240
|
|
|
223
241
|
subject { lambda { destroy! } }
|
|
224
|
-
it { should raise_error Faraday::
|
|
242
|
+
it { should raise_error Faraday::ResourceNotFound }
|
|
225
243
|
end
|
|
226
244
|
|
|
227
245
|
context 'with success' do
|
|
@@ -229,6 +247,13 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
229
247
|
|
|
230
248
|
it { should be_true }
|
|
231
249
|
end
|
|
250
|
+
|
|
251
|
+
context 'with a space in the id' do
|
|
252
|
+
subject(:destroy!) { client.destroy!('Account', '001D000000 INjVe') }
|
|
253
|
+
requests 'sobjects/Account/001D000000%20INjVe', method: :delete
|
|
254
|
+
|
|
255
|
+
it { should be_true }
|
|
256
|
+
end
|
|
232
257
|
end
|
|
233
258
|
|
|
234
259
|
describe '.destroy' do
|
|
@@ -266,6 +291,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
266
291
|
subject { client.find('Account', '1234', 'External_Field__c') }
|
|
267
292
|
it { should be_a Hash }
|
|
268
293
|
end
|
|
294
|
+
|
|
295
|
+
context 'with a space in an external id' do
|
|
296
|
+
requests 'sobjects/Account/External_Field__c/12%2034',
|
|
297
|
+
fixture: 'sobject/sobject_find_success_response'
|
|
298
|
+
|
|
299
|
+
subject { client.find('Account', '12 34', 'External_Field__c') }
|
|
300
|
+
it { should be_a Hash }
|
|
301
|
+
end
|
|
269
302
|
end
|
|
270
303
|
|
|
271
304
|
describe '.select' do
|
|
@@ -284,6 +317,14 @@ shared_examples_for Restforce::AbstractClient do
|
|
|
284
317
|
subject { client.select('Account', '1234', ['External_Field__c']) }
|
|
285
318
|
it { should be_a Hash }
|
|
286
319
|
end
|
|
320
|
+
|
|
321
|
+
context 'with a space in the id' do
|
|
322
|
+
requests 'sobjects/Account/12%2034',
|
|
323
|
+
fixture: 'sobject/sobject_select_success_response'
|
|
324
|
+
|
|
325
|
+
subject { client.select('Account', '12 34', nil, nil) }
|
|
326
|
+
it { should be_a Hash }
|
|
327
|
+
end
|
|
287
328
|
end
|
|
288
329
|
|
|
289
330
|
context 'when an external id is specified' do
|
|
@@ -274,7 +274,7 @@ describe Restforce::Concerns::API do
|
|
|
274
274
|
end
|
|
275
275
|
|
|
276
276
|
it 'rescues exceptions' do
|
|
277
|
-
[Faraday::
|
|
277
|
+
[Faraday::ClientError].each do |exception_klass|
|
|
278
278
|
client.should_receive(:"#{method}!").
|
|
279
279
|
with(*args).
|
|
280
280
|
and_raise(exception_klass.new(nil))
|
|
@@ -52,6 +52,16 @@ describe Restforce::Concerns::Authentication do
|
|
|
52
52
|
|
|
53
53
|
it { should eq Restforce::Middleware::Authentication::Token }
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
context 'when jwt option is provided' do
|
|
57
|
+
before do
|
|
58
|
+
client.stub username_password?: false
|
|
59
|
+
client.stub oauth_refresh?: false
|
|
60
|
+
client.stub jwt?: true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it { should eq Restforce::Middleware::Authentication::JWTBearer }
|
|
64
|
+
end
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
describe '.username_password?' do
|
|
@@ -100,4 +110,29 @@ describe Restforce::Concerns::Authentication do
|
|
|
100
110
|
it { should_not be_true }
|
|
101
111
|
end
|
|
102
112
|
end
|
|
113
|
+
|
|
114
|
+
describe '.jwt?' do
|
|
115
|
+
subject { client.jwt? }
|
|
116
|
+
let(:options) do
|
|
117
|
+
{}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
before do
|
|
121
|
+
client.stub options: options
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context 'when jwt options are provided' do
|
|
125
|
+
let(:options) do
|
|
126
|
+
{ jwt_key: 'JWT_PRIVATE_KEY',
|
|
127
|
+
username: 'foo',
|
|
128
|
+
client_id: 'client' }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it { should be_true }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'when jwt options are not provided' do
|
|
135
|
+
it { should_not be_true }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
103
138
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Restforce::Concerns::BatchAPI do
|
|
6
|
+
let(:endpoint) { 'composite/batch' }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
client.should_receive(:options).and_return(api_version: 34.0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
shared_examples_for 'batched requests' do
|
|
13
|
+
it '#create' do
|
|
14
|
+
client.
|
|
15
|
+
should_receive(:api_post).
|
|
16
|
+
with(endpoint, { batchRequests: [
|
|
17
|
+
{ method: 'POST', url: 'v34.0/sobjects/Object', richInput: { name: 'test' } }
|
|
18
|
+
], haltOnError: halt_on_error }.to_json).
|
|
19
|
+
and_return(response)
|
|
20
|
+
|
|
21
|
+
client.send(method) do |subrequests|
|
|
22
|
+
subrequests.create('Object', name: 'test')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it '#update' do
|
|
27
|
+
client.
|
|
28
|
+
should_receive(:api_post).
|
|
29
|
+
with(endpoint, { batchRequests: [
|
|
30
|
+
{ method: 'PATCH', url: "v34.0/sobjects/Object/123", richInput: {
|
|
31
|
+
name: 'test'
|
|
32
|
+
} }
|
|
33
|
+
], haltOnError: halt_on_error }.to_json).
|
|
34
|
+
and_return(response)
|
|
35
|
+
|
|
36
|
+
client.send(method) do |subrequests|
|
|
37
|
+
subrequests.update('Object', id: '123', name: 'test')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it '#destroy' do
|
|
42
|
+
client.
|
|
43
|
+
should_receive(:api_post).
|
|
44
|
+
with(endpoint, { batchRequests: [
|
|
45
|
+
{ method: 'DELETE', url: "v34.0/sobjects/Object/123" }
|
|
46
|
+
], haltOnError: halt_on_error }.to_json).
|
|
47
|
+
and_return(response)
|
|
48
|
+
|
|
49
|
+
client.send(method) do |subrequests|
|
|
50
|
+
subrequests.destroy('Object', '123')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it '#upsert' do
|
|
55
|
+
client.
|
|
56
|
+
should_receive(:api_post).
|
|
57
|
+
with(endpoint, { batchRequests: [
|
|
58
|
+
{ method: 'PATCH', url: 'v34.0/sobjects/Object/extIdField__c/456', richInput: {
|
|
59
|
+
name: 'test'
|
|
60
|
+
} }
|
|
61
|
+
], haltOnError: halt_on_error }.to_json).
|
|
62
|
+
and_return(response)
|
|
63
|
+
|
|
64
|
+
client.send(method) do |subrequests|
|
|
65
|
+
subrequests.upsert('Object', 'extIdField__c',
|
|
66
|
+
extIdField__c: '456', name: 'test')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'multiple subrequests' do
|
|
71
|
+
client.
|
|
72
|
+
should_receive(:api_post).
|
|
73
|
+
with(endpoint, { batchRequests: [
|
|
74
|
+
{ method: 'POST', url: 'v34.0/sobjects/Object', richInput: {
|
|
75
|
+
name: 'test'
|
|
76
|
+
} },
|
|
77
|
+
{ method: 'PATCH', url: "v34.0/sobjects/Object/123", richInput: {
|
|
78
|
+
name: 'test'
|
|
79
|
+
} },
|
|
80
|
+
{ method: 'DELETE', url: "v34.0/sobjects/Object/123" }
|
|
81
|
+
], haltOnError: halt_on_error }.to_json).
|
|
82
|
+
and_return(response)
|
|
83
|
+
|
|
84
|
+
client.send(method) do |subrequests|
|
|
85
|
+
subrequests.create('Object', name: 'test')
|
|
86
|
+
subrequests.update('Object', id: '123', name: 'test')
|
|
87
|
+
subrequests.destroy('Object', '123')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#batch' do
|
|
93
|
+
let(:method) { :batch }
|
|
94
|
+
let(:halt_on_error) { false }
|
|
95
|
+
let(:response) { double('Faraday::Response', body: { 'results' => [] }) }
|
|
96
|
+
it_behaves_like 'batched requests'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe '#batch!' do
|
|
100
|
+
let(:method) { :batch! }
|
|
101
|
+
let(:halt_on_error) { true }
|
|
102
|
+
let(:response) {
|
|
103
|
+
double('Faraday::Response', body: { 'hasErrors' => false, 'results' => [] })
|
|
104
|
+
}
|
|
105
|
+
it_behaves_like 'batched requests'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -4,18 +4,19 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Restforce::Concerns::Streaming, event_machine: true do
|
|
6
6
|
describe '.subscribe' do
|
|
7
|
-
let(:channels)
|
|
8
|
-
|
|
7
|
+
let(:channels) do
|
|
8
|
+
['/topic/topic1', '/event/MyCustomEvent__e', '/data/ChangeEvents']
|
|
9
|
+
end
|
|
9
10
|
let(:subscribe_block) { lambda { 'subscribe' } }
|
|
10
11
|
let(:faye_double) { double('Faye') }
|
|
11
12
|
|
|
12
13
|
it 'subscribes to the topics with faye' do
|
|
13
14
|
faye_double.
|
|
14
15
|
should_receive(:subscribe).
|
|
15
|
-
with(
|
|
16
|
+
with(channels, &subscribe_block)
|
|
16
17
|
client.stub faye: faye_double
|
|
17
18
|
|
|
18
|
-
client.
|
|
19
|
+
client.subscription(channels, &subscribe_block)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
context "replay_handlers" do
|
|
@@ -25,24 +26,51 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
it 'registers nil handlers when no replay option is given' do
|
|
28
|
-
client.
|
|
29
|
-
client.replay_handlers.should eq(
|
|
29
|
+
client.subscription(channels, &subscribe_block)
|
|
30
|
+
client.replay_handlers.should eq(
|
|
31
|
+
'/topic/topic1' => nil,
|
|
32
|
+
'/event/MyCustomEvent__e' => nil,
|
|
33
|
+
'/data/ChangeEvents' => nil
|
|
34
|
+
)
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
it 'registers a replay_handler for each channel given' do
|
|
33
|
-
client.
|
|
34
|
-
client.replay_handlers.should eq(
|
|
38
|
+
client.subscription(channels, replay: -2, &subscribe_block)
|
|
39
|
+
client.replay_handlers.should eq(
|
|
40
|
+
'/topic/topic1' => -2,
|
|
41
|
+
'/event/MyCustomEvent__e' => -2,
|
|
42
|
+
'/data/ChangeEvents' => -2
|
|
43
|
+
)
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
it 'replaces earlier handlers in subsequent calls' do
|
|
38
|
-
client.
|
|
39
|
-
|
|
47
|
+
client.subscription(
|
|
48
|
+
['/topic/channel1', '/topic/channel2'],
|
|
49
|
+
replay: 2,
|
|
50
|
+
&subscribe_block
|
|
51
|
+
)
|
|
52
|
+
client.subscription(
|
|
53
|
+
['/topic/channel2', '/topic/channel3'],
|
|
54
|
+
replay: 3,
|
|
55
|
+
&subscribe_block
|
|
56
|
+
)
|
|
57
|
+
|
|
40
58
|
client.replay_handlers.should eq(
|
|
41
|
-
'channel1' => 2,
|
|
42
|
-
'channel2' => 3,
|
|
43
|
-
'channel3' => 3
|
|
59
|
+
'/topic/channel1' => 2,
|
|
60
|
+
'/topic/channel2' => 3,
|
|
61
|
+
'/topic/channel3' => 3
|
|
44
62
|
)
|
|
45
63
|
end
|
|
64
|
+
|
|
65
|
+
context 'backwards compatibility' do
|
|
66
|
+
it 'it assumes channels are push topics' do
|
|
67
|
+
client.subscribe(%w[channel1 channel2], replay: -2, &subscribe_block)
|
|
68
|
+
client.replay_handlers.should eq(
|
|
69
|
+
'/topic/channel1' => -2,
|
|
70
|
+
'/topic/channel2' => -2
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
46
74
|
end
|
|
47
75
|
end
|
|
48
76
|
|
|
@@ -87,41 +115,41 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
|
87
115
|
let(:extension) { Restforce::Concerns::Streaming::ReplayExtension.new(handlers) }
|
|
88
116
|
|
|
89
117
|
it 'sends nil without a specified handler' do
|
|
90
|
-
output = subscribe(extension, to: "channel1")
|
|
118
|
+
output = subscribe(extension, to: "/topic/channel1")
|
|
91
119
|
read_replay(output).should eq('/topic/channel1' => nil)
|
|
92
120
|
end
|
|
93
121
|
|
|
94
122
|
it 'with a scalar replay id' do
|
|
95
|
-
handlers['channel1'] = -2
|
|
96
|
-
output = subscribe(extension, to: "channel1")
|
|
123
|
+
handlers['/topic/channel1'] = -2
|
|
124
|
+
output = subscribe(extension, to: "/topic/channel1")
|
|
97
125
|
read_replay(output).should eq('/topic/channel1' => -2)
|
|
98
126
|
end
|
|
99
127
|
|
|
100
128
|
it 'with a hash' do
|
|
101
|
-
hash_handler = { 'channel1' => -1, 'channel2' => -2 }
|
|
129
|
+
hash_handler = { '/topic/channel1' => -1, '/topic/channel2' => -2 }
|
|
102
130
|
|
|
103
|
-
handlers['channel1'] = hash_handler
|
|
104
|
-
handlers['channel2'] = hash_handler
|
|
131
|
+
handlers['/topic/channel1'] = hash_handler
|
|
132
|
+
handlers['/topic/channel2'] = hash_handler
|
|
105
133
|
|
|
106
|
-
output = subscribe(extension, to: "channel1")
|
|
134
|
+
output = subscribe(extension, to: "/topic/channel1")
|
|
107
135
|
read_replay(output).should eq('/topic/channel1' => -1)
|
|
108
136
|
|
|
109
|
-
output = subscribe(extension, to: "channel2")
|
|
137
|
+
output = subscribe(extension, to: "/topic/channel2")
|
|
110
138
|
read_replay(output).should eq('/topic/channel2' => -2)
|
|
111
139
|
end
|
|
112
140
|
|
|
113
141
|
it 'with an object' do
|
|
114
142
|
custom_handler = double('custom_handler')
|
|
115
143
|
custom_handler.should_receive(:[]).and_return(123)
|
|
116
|
-
handlers['channel1'] = custom_handler
|
|
144
|
+
handlers['/topic/channel1'] = custom_handler
|
|
117
145
|
|
|
118
|
-
output = subscribe(extension, to: "channel1")
|
|
146
|
+
output = subscribe(extension, to: "/topic/channel1")
|
|
119
147
|
read_replay(output).should eq('/topic/channel1' => 123)
|
|
120
148
|
end
|
|
121
149
|
|
|
122
150
|
it 'remembers the last replayId' do
|
|
123
|
-
handler = { 'channel1' => 41 }
|
|
124
|
-
handlers['channel1'] = handler
|
|
151
|
+
handler = { '/topic/channel1' => 41 }
|
|
152
|
+
handlers['/topic/channel1'] = handler
|
|
125
153
|
message = {
|
|
126
154
|
'channel' => '/topic/channel1',
|
|
127
155
|
'data' => {
|
|
@@ -130,12 +158,12 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
extension.incoming(message, ->(m) {})
|
|
133
|
-
handler.should eq('channel1' => 42)
|
|
161
|
+
handler.should eq('/topic/channel1' => 42)
|
|
134
162
|
end
|
|
135
163
|
|
|
136
164
|
it 'when an incoming message has no replayId' do
|
|
137
|
-
handler = { 'channel1' => 41 }
|
|
138
|
-
handlers['channel1'] = handler
|
|
165
|
+
handler = { '/topic/channel1' => 41 }
|
|
166
|
+
handlers['/topic/channel1'] = handler
|
|
139
167
|
|
|
140
168
|
message = {
|
|
141
169
|
'channel' => '/topic/channel1',
|
|
@@ -143,7 +171,7 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
|
143
171
|
}
|
|
144
172
|
|
|
145
173
|
extension.incoming(message, ->(m) {})
|
|
146
|
-
handler.should eq('channel1' => 41)
|
|
174
|
+
handler.should eq('/topic/channel1' => 41)
|
|
147
175
|
end
|
|
148
176
|
|
|
149
177
|
private
|
|
@@ -152,7 +180,7 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
|
152
180
|
output = nil
|
|
153
181
|
message = {
|
|
154
182
|
'channel' => '/meta/subscribe',
|
|
155
|
-
'subscription' =>
|
|
183
|
+
'subscription' => options[:to]
|
|
156
184
|
}
|
|
157
185
|
extension.outgoing(message, ->(m) {
|
|
158
186
|
output = m
|