restforce 4.0.0 → 5.0.0
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 +4 -4
- data/.circleci/config.yml +9 -9
- data/.github/ISSUE_TEMPLATE/unhandled-salesforce-error.md +17 -0
- data/.rubocop.yml +4 -3
- data/.rubocop_todo.yml +2 -2
- data/CHANGELOG.md +36 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -1
- data/README.md +71 -33
- data/UPGRADING.md +38 -0
- data/lib/restforce.rb +11 -13
- data/lib/restforce/collection.rb +5 -0
- data/lib/restforce/concerns/api.rb +1 -1
- data/lib/restforce/concerns/authentication.rb +10 -0
- data/lib/restforce/concerns/base.rb +2 -0
- data/lib/restforce/concerns/caching.rb +7 -0
- data/lib/restforce/concerns/connection.rb +3 -3
- data/lib/restforce/concerns/streaming.rb +22 -7
- data/lib/restforce/config.rb +6 -0
- data/lib/restforce/error_code.rb +406 -0
- data/lib/restforce/file_part.rb +24 -0
- data/lib/restforce/mash.rb +1 -1
- data/lib/restforce/middleware/authentication.rb +7 -3
- data/lib/restforce/middleware/authentication/jwt_bearer.rb +38 -0
- data/lib/restforce/middleware/caching.rb +1 -1
- data/lib/restforce/middleware/instance_url.rb +1 -1
- data/lib/restforce/middleware/raise_error.rb +3 -4
- data/lib/restforce/version.rb +1 -1
- data/restforce.gemspec +14 -14
- data/spec/fixtures/test_private.key +27 -0
- data/spec/integration/abstract_client_spec.rb +18 -6
- data/spec/spec_helper.rb +14 -1
- data/spec/support/fixture_helpers.rb +1 -3
- data/spec/unit/collection_spec.rb +18 -0
- data/spec/unit/concerns/api_spec.rb +1 -1
- data/spec/unit/concerns/authentication_spec.rb +35 -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 +58 -30
- 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_spec.rb +27 -4
- data/spec/unit/middleware/raise_error_spec.rb +19 -10
- data/spec/unit/signed_request_spec.rb +1 -1
- data/spec/unit/sobject_spec.rb +2 -5
- metadata +41 -31
- data/lib/restforce/upload_io.rb +0 -9
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'faraday/file_part'
|
5
|
+
rescue LoadError
|
6
|
+
require 'faraday/upload_io'
|
7
|
+
end
|
8
|
+
|
9
|
+
module Restforce
|
10
|
+
if defined?(::Faraday::FilePart)
|
11
|
+
FilePart = Faraday::FilePart
|
12
|
+
|
13
|
+
# Deprecated
|
14
|
+
UploadIO = Faraday::FilePart
|
15
|
+
else
|
16
|
+
# Handle pre-1.0 versions of faraday
|
17
|
+
FilePart = Faraday::UploadIO
|
18
|
+
UploadIO = Faraday::UploadIO
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# This patch is only needed with multipart-post < 2.0.0
|
23
|
+
# 2.0.0 was released in 2013.
|
24
|
+
require 'restforce/patches/parts' unless Parts::Part.method(:new).arity.abs == 4
|
data/lib/restforce/mash.rb
CHANGED
@@ -6,8 +6,9 @@ module Restforce
|
|
6
6
|
# will attempt to either reauthenticate (username and password) or refresh
|
7
7
|
# the oauth access token (if a refresh token is present).
|
8
8
|
class Middleware::Authentication < Restforce::Middleware
|
9
|
-
autoload :Password,
|
10
|
-
autoload :Token,
|
9
|
+
autoload :Password, 'restforce/middleware/authentication/password'
|
10
|
+
autoload :Token, 'restforce/middleware/authentication/token'
|
11
|
+
autoload :JWTBearer, 'restforce/middleware/authentication/jwt_bearer'
|
11
12
|
|
12
13
|
# Rescue from 401's, authenticate then raise the error again so the client
|
13
14
|
# can reissue the request.
|
@@ -62,7 +63,10 @@ module Restforce
|
|
62
63
|
|
63
64
|
# Internal: The parsed error response.
|
64
65
|
def error_message(response)
|
65
|
-
|
66
|
+
return response.status.to_s unless response.body
|
67
|
+
|
68
|
+
"#{response.body['error']}: #{response.body['error_description']} " \
|
69
|
+
"(#{response.status})"
|
66
70
|
end
|
67
71
|
|
68
72
|
# Featured detect form encoding.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jwt'
|
4
|
+
|
5
|
+
module Restforce
|
6
|
+
class Middleware
|
7
|
+
class Authentication
|
8
|
+
class JWTBearer < Restforce::Middleware::Authentication
|
9
|
+
def params
|
10
|
+
{
|
11
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
12
|
+
assertion: jwt_bearer_token
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def jwt_bearer_token
|
19
|
+
JWT.encode claim_set, private_key, 'RS256'
|
20
|
+
end
|
21
|
+
|
22
|
+
def claim_set
|
23
|
+
{
|
24
|
+
iss: @options[:client_id],
|
25
|
+
sub: @options[:username],
|
26
|
+
aud: @options[:host],
|
27
|
+
iat: Time.now.utc.to_i,
|
28
|
+
exp: Time.now.utc.to_i + 180
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def private_key
|
33
|
+
OpenSSL::PKey::RSA.new(@options[:jwt_key])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -11,9 +11,9 @@ module Restforce
|
|
11
11
|
response_values
|
12
12
|
)
|
13
13
|
when 401
|
14
|
-
raise Restforce::UnauthorizedError,
|
14
|
+
raise Restforce::UnauthorizedError.new(message, response_values)
|
15
15
|
when 404
|
16
|
-
raise Restforce::NotFoundError,
|
16
|
+
raise Restforce::NotFoundError.new(message, response_values)
|
17
17
|
when 413
|
18
18
|
raise Restforce::EntityTooLargeError.new(
|
19
19
|
"413: Request Entity Too Large",
|
@@ -56,8 +56,7 @@ module Restforce
|
|
56
56
|
def exception_class_for_error_code(error_code)
|
57
57
|
return Restforce::ResponseError unless ERROR_CODE_MATCHER.match?(error_code)
|
58
58
|
|
59
|
-
|
60
|
-
Restforce::ErrorCode.const_get(constant_name)
|
59
|
+
Restforce::ErrorCode.get_exception_class(error_code)
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
data/lib/restforce/version.rb
CHANGED
data/restforce.gemspec
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
require File.expand_path('lib/restforce/version', __dir__)
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
|
-
gem.authors = ["Eric J. Holmes"
|
7
|
-
gem.email = ["
|
8
|
-
gem.description = 'A lightweight
|
9
|
-
gem.summary = 'A lightweight
|
10
|
-
gem.homepage = "
|
6
|
+
gem.authors = ["Tim Rogers", "Eric J. Holmes"]
|
7
|
+
gem.email = ["me@timrogers.co.uk", "eric@ejholmes.net"]
|
8
|
+
gem.description = 'A lightweight Ruby client for the Salesforce REST API'
|
9
|
+
gem.summary = 'A lightweight Ruby client for the Salesforce REST API'
|
10
|
+
gem.homepage = "https://restforce.github.io/"
|
11
11
|
gem.license = "MIT"
|
12
12
|
|
13
13
|
gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
@@ -22,19 +22,19 @@ 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.5'
|
26
26
|
|
27
|
-
gem.add_dependency 'faraday', '<=
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<=
|
27
|
+
gem.add_dependency 'faraday', '<= 2.0', '>= 0.9.0'
|
28
|
+
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '<= 2.0']
|
29
29
|
|
30
|
-
gem.add_dependency '
|
30
|
+
gem.add_dependency 'jwt', ['>= 1.5.6']
|
31
31
|
|
32
|
-
gem.add_dependency 'hashie',
|
32
|
+
gem.add_dependency 'hashie', '>= 1.2.0', '< 5.0'
|
33
33
|
|
34
34
|
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
35
35
|
gem.add_development_dependency 'rspec', '~> 2.14.0'
|
36
|
-
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.
|
37
|
-
gem.add_development_dependency 'rubocop', '~> 0.
|
38
|
-
gem.add_development_dependency 'simplecov', '~> 0.
|
39
|
-
gem.add_development_dependency 'webmock', '~> 3.
|
36
|
+
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.4.1'
|
37
|
+
gem.add_development_dependency 'rubocop', '~> 0.87.1'
|
38
|
+
gem.add_development_dependency 'simplecov', '~> 0.18.2'
|
39
|
+
gem.add_development_dependency 'webmock', '~> 3.8.3'
|
40
40
|
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-----
|
@@ -86,22 +86,34 @@ shared_examples_for Restforce::AbstractClient do
|
|
86
86
|
end
|
87
87
|
|
88
88
|
context 'with multipart' do
|
89
|
-
# rubocop:disable
|
89
|
+
# rubocop:disable Layout/LineLength
|
90
90
|
requests 'sobjects/Account',
|
91
91
|
method: :post,
|
92
|
-
with_body: %r(----boundary_string\r\nContent-Disposition: form-data; name
|
92
|
+
with_body: %r(----boundary_string\r\nContent-Disposition: form-data; name="entity_content"\r\nContent-Type: application/json\r\n\r\n{"Name":"Foobar"}\r\n----boundary_string\r\nContent-Disposition: form-data; name="Blob"; filename="blob.jpg"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary),
|
93
93
|
fixture: 'sobject/create_success_response'
|
94
|
-
# rubocop:enable
|
94
|
+
# rubocop:enable Layout/LineLength
|
95
95
|
|
96
96
|
subject do
|
97
97
|
client.create('Account', Name: 'Foobar',
|
98
|
-
Blob: Restforce::
|
98
|
+
Blob: Restforce::FilePart.new(
|
99
99
|
File.expand_path('../fixtures/blob.jpg', __dir__),
|
100
100
|
'image/jpeg'
|
101
101
|
))
|
102
102
|
end
|
103
103
|
|
104
104
|
it { should eq 'some_id' }
|
105
|
+
|
106
|
+
context 'with deprecated UploadIO' do
|
107
|
+
subject do
|
108
|
+
client.create('Account', Name: 'Foobar',
|
109
|
+
Blob: Restforce::UploadIO.new(
|
110
|
+
File.expand_path('../fixtures/blob.jpg', __dir__),
|
111
|
+
'image/jpeg'
|
112
|
+
))
|
113
|
+
end
|
114
|
+
|
115
|
+
it { should eq 'some_id' }
|
116
|
+
end
|
105
117
|
end
|
106
118
|
end
|
107
119
|
|
@@ -125,7 +137,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
125
137
|
|
126
138
|
it {
|
127
139
|
should raise_error(
|
128
|
-
Faraday::
|
140
|
+
Faraday::ResourceNotFound,
|
129
141
|
"#{error.first['errorCode']}: #{error.first['message']}"
|
130
142
|
)
|
131
143
|
}
|
@@ -239,7 +251,7 @@ shared_examples_for Restforce::AbstractClient do
|
|
239
251
|
status: 404
|
240
252
|
|
241
253
|
subject { lambda { destroy! } }
|
242
|
-
it { should raise_error Faraday::
|
254
|
+
it { should raise_error Faraday::ResourceNotFound }
|
243
255
|
end
|
244
256
|
|
245
257
|
context 'with success' do
|
data/spec/spec_helper.rb
CHANGED
@@ -15,8 +15,21 @@ RSpec.configure do |config|
|
|
15
15
|
config.order = 'random'
|
16
16
|
config.filter_run focus: true
|
17
17
|
config.run_all_when_everything_filtered = true
|
18
|
+
|
19
|
+
original_stderr = $stderr
|
20
|
+
original_stdout = $stdout
|
21
|
+
config.before(:all) do
|
22
|
+
# Redirect stderr and stdout
|
23
|
+
$stderr = File.open(File::NULL, "w")
|
24
|
+
$stdout = File.open(File::NULL, "w")
|
25
|
+
end
|
26
|
+
config.after(:all) do
|
27
|
+
$stderr = original_stderr
|
28
|
+
$stdout = original_stdout
|
29
|
+
end
|
18
30
|
end
|
19
31
|
|
20
32
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
21
33
|
# in spec/support/ and its subdirectories.
|
22
|
-
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
|
34
|
+
paths = Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")]
|
35
|
+
paths.sort.each { |f| require f }
|
@@ -22,9 +22,7 @@ module FixtureHelpers
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def stub_login_request(*)
|
25
|
-
|
26
|
-
|
27
|
-
stub
|
25
|
+
stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
|
28
26
|
end
|
29
27
|
|
30
28
|
def fixture(filename)
|
@@ -62,4 +62,22 @@ describe Restforce::Collection do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
65
|
+
|
66
|
+
describe '#empty?' do
|
67
|
+
subject(:empty?) do
|
68
|
+
described_class.new(JSON.parse(fixture(sobject_fixture)), client).empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with size 1' do
|
72
|
+
let(:sobject_fixture) { 'sobject/query_success_response' }
|
73
|
+
|
74
|
+
it { should be_false }
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with size 0' do
|
78
|
+
let(:sobject_fixture) { 'sobject/query_empty_response' }
|
79
|
+
|
80
|
+
it { should be_true }
|
81
|
+
end
|
82
|
+
end
|
65
83
|
end
|
@@ -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
|
@@ -28,4 +28,30 @@ describe Restforce::Concerns::Caching do
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
describe '.with_caching' do
|
33
|
+
let(:options) { double('Options') }
|
34
|
+
|
35
|
+
before do
|
36
|
+
client.stub options: options
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'runs the block with caching enabled' do
|
40
|
+
options.should_receive(:[]=).with(:use_cache, true)
|
41
|
+
options.should_receive(:[]=).with(:use_cache, false)
|
42
|
+
expect { |b| client.with_caching(&b) }.to yield_control
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when an exception is raised' do
|
46
|
+
it 'ensures the :use_cache is set to false' do
|
47
|
+
options.should_receive(:[]=).with(:use_cache, true)
|
48
|
+
options.should_receive(:[]=).with(:use_cache, false)
|
49
|
+
expect {
|
50
|
+
client.with_caching do
|
51
|
+
raise 'Foo'
|
52
|
+
end
|
53
|
+
}.to raise_error 'Foo'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
31
57
|
end
|
@@ -73,9 +73,9 @@ describe Restforce::Concerns::Connection do
|
|
73
73
|
Restforce.stub(log?: true)
|
74
74
|
end
|
75
75
|
|
76
|
-
it "must always be used
|
76
|
+
it "must always be used as the last handler" do
|
77
77
|
client.middleware.handlers.reverse.index(Restforce::Middleware::Logger).
|
78
|
-
should eq
|
78
|
+
should eq 0
|
79
79
|
end
|
80
80
|
end
|
81
81
|
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
|