restforce 3.0.0 → 3.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/.rubocop.yml +9 -11
- data/.rubocop_todo.yml +128 -81
- data/CHANGELOG.md +25 -0
- data/Gemfile +1 -1
- data/README.md +99 -4
- 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/batch_api.rb +87 -0
- data/lib/restforce/concerns/canvas.rb +1 -0
- data/lib/restforce/concerns/picklists.rb +1 -0
- data/lib/restforce/concerns/streaming.rb +58 -1
- data/lib/restforce/config.rb +1 -0
- data/lib/restforce/document.rb +1 -0
- 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/lib/restforce.rb +21 -1
- data/restforce.gemspec +6 -6
- data/spec/integration/abstract_client_spec.rb +42 -1
- data/spec/support/fixture_helpers.rb +2 -2
- data/spec/unit/concerns/api_spec.rb +10 -0
- data/spec/unit/concerns/batch_api_spec.rb +107 -0
- data/spec/unit/concerns/streaming_spec.rb +112 -0
- data/spec/unit/middleware/raise_error_spec.rb +32 -11
- metadata +32 -30
@@ -9,7 +9,8 @@ module Restforce
|
|
9
9
|
# block - A block to run when a new message is received.
|
10
10
|
#
|
11
11
|
# Returns a Faye::Subscription
|
12
|
-
def subscribe(channels, &block)
|
12
|
+
def subscribe(channels, options = {}, &block)
|
13
|
+
Array(channels).each { |channel| replay_handlers[channel] = options[:replay] }
|
13
14
|
faye.subscribe Array(channels).map { |channel| "/topic/#{channel}" }, &block
|
14
15
|
end
|
15
16
|
|
@@ -32,6 +33,62 @@ module Restforce
|
|
32
33
|
client.bind 'transport:up' do
|
33
34
|
Restforce.log "[COMETD UP]"
|
34
35
|
end
|
36
|
+
|
37
|
+
client.add_extension ReplayExtension.new(replay_handlers)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def replay_handlers
|
42
|
+
@_replay_handlers ||= {}
|
43
|
+
end
|
44
|
+
|
45
|
+
class ReplayExtension
|
46
|
+
def initialize(replay_handlers)
|
47
|
+
@replay_handlers = replay_handlers
|
48
|
+
end
|
49
|
+
|
50
|
+
def incoming(message, callback)
|
51
|
+
callback.call(message).tap do
|
52
|
+
channel = message.fetch('channel').gsub('/topic/', '')
|
53
|
+
replay_id = message.fetch('data', {}).fetch('event', {})['replayId']
|
54
|
+
|
55
|
+
handler = @replay_handlers[channel]
|
56
|
+
if !replay_id.nil? && !handler.nil? && handler.respond_to?(:[]=)
|
57
|
+
# remember the last replay_id for this channel
|
58
|
+
handler[channel] = replay_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def outgoing(message, callback)
|
64
|
+
# Leave non-subscribe messages alone
|
65
|
+
return callback.call(message) unless message['channel'] == '/meta/subscribe'
|
66
|
+
|
67
|
+
channel = message['subscription'].gsub('/topic/', '')
|
68
|
+
|
69
|
+
# Set the replay value for the channel
|
70
|
+
message['ext'] ||= {}
|
71
|
+
message['ext']['replay'] = {
|
72
|
+
"/topic/#{channel}" => replay_id(channel)
|
73
|
+
}
|
74
|
+
|
75
|
+
# Carry on and send the message to the server
|
76
|
+
callback.call message
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def replay_id(channel)
|
82
|
+
handler = @replay_handlers[channel]
|
83
|
+
if handler.is_a?(Integer)
|
84
|
+
handler # treat it as a scalar
|
85
|
+
elsif handler.respond_to?(:[])
|
86
|
+
# Ask for the latest replayId for this channel
|
87
|
+
handler[channel]
|
88
|
+
else
|
89
|
+
# Just pass it along
|
90
|
+
handler
|
91
|
+
end
|
35
92
|
end
|
36
93
|
end
|
37
94
|
end
|
data/lib/restforce/config.rb
CHANGED
data/lib/restforce/document.rb
CHANGED
@@ -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/lib/restforce.rb
CHANGED
@@ -29,6 +29,7 @@ module Restforce
|
|
29
29
|
autoload :Verbs, 'restforce/concerns/verbs'
|
30
30
|
autoload :Base, 'restforce/concerns/base'
|
31
31
|
autoload :API, 'restforce/concerns/api'
|
32
|
+
autoload :BatchAPI, 'restforce/concerns/batch_api'
|
32
33
|
end
|
33
34
|
|
34
35
|
module Data
|
@@ -44,6 +45,25 @@ module Restforce
|
|
44
45
|
AuthenticationError = Class.new(Error)
|
45
46
|
UnauthorizedError = Class.new(Error)
|
46
47
|
APIVersionError = Class.new(Error)
|
48
|
+
BatchAPIError = Class.new(Error)
|
49
|
+
|
50
|
+
# Inherit from Faraday::Error::ResourceNotFound for backwards-compatibility
|
51
|
+
# Consumers of this library that rescue and handle Faraday::Error::ResourceNotFound
|
52
|
+
# can continue to do so.
|
53
|
+
NotFoundError = Class.new(Faraday::Error::ResourceNotFound)
|
54
|
+
|
55
|
+
# Inherit from Faraday::Error::ClientError for backwards-compatibility
|
56
|
+
# Consumers of this library that rescue and handle Faraday::Error::ClientError
|
57
|
+
# can continue to do so.
|
58
|
+
ResponseError = Class.new(Faraday::Error::ClientError)
|
59
|
+
MatchesMultipleError= Class.new(ResponseError)
|
60
|
+
EntityTooLargeError = Class.new(ResponseError)
|
61
|
+
|
62
|
+
module ErrorCode
|
63
|
+
def self.const_missing(constant_name)
|
64
|
+
const_set constant_name, Class.new(ResponseError)
|
65
|
+
end
|
66
|
+
end
|
47
67
|
|
48
68
|
class << self
|
49
69
|
# Alias for Restforce::Data::Client.new
|
@@ -74,7 +94,7 @@ module Restforce
|
|
74
94
|
self
|
75
95
|
end
|
76
96
|
end
|
77
|
-
Object.
|
97
|
+
Object.include Restforce::CoreExtensions unless Object.respond_to? :tap
|
78
98
|
end
|
79
99
|
|
80
100
|
if ENV['PROXY_URI']
|
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"]
|
@@ -25,16 +25,16 @@ Gem::Specification.new do |gem|
|
|
25
25
|
gem.required_ruby_version = '>= 2.3'
|
26
26
|
|
27
27
|
gem.add_dependency 'faraday', '<= 1.0', '>= 0.9.0'
|
28
|
-
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '
|
28
|
+
gem.add_dependency 'faraday_middleware', ['>= 0.8.8', '< 1.0']
|
29
29
|
|
30
30
|
gem.add_dependency 'json', '>= 1.7.5'
|
31
31
|
|
32
32
|
gem.add_dependency 'hashie', ['>= 1.2.0', '< 4.0']
|
33
33
|
|
34
|
+
gem.add_development_dependency 'faye' unless RUBY_PLATFORM == 'java'
|
34
35
|
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
36
|
gem.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
|
39
|
-
gem.add_development_dependency '
|
37
|
+
gem.add_development_dependency 'rubocop', '~> 0.75.0'
|
38
|
+
gem.add_development_dependency 'simplecov', '~> 0.17.1'
|
39
|
+
gem.add_development_dependency 'webmock', '~> 3.7.6'
|
40
40
|
end
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -350,6 +350,16 @@ describe Restforce::Concerns::API do
|
|
350
350
|
and_return(response)
|
351
351
|
expect(result).to be_true
|
352
352
|
end
|
353
|
+
|
354
|
+
context 'and the response body is a string' do
|
355
|
+
it 'returns true' do
|
356
|
+
response.stub(:body) { '' }
|
357
|
+
client.should_receive(:api_patch).
|
358
|
+
with('sobjects/Whizbang/External_ID__c/1234', {}).
|
359
|
+
and_return(response)
|
360
|
+
expect(result).to be_true
|
361
|
+
end
|
362
|
+
end
|
353
363
|
end
|
354
364
|
|
355
365
|
context 'when the record is found and created' do
|
@@ -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
|
@@ -17,6 +17,33 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
17
17
|
|
18
18
|
client.subscribe(channels, &subscribe_block)
|
19
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
|
20
47
|
end
|
21
48
|
|
22
49
|
describe '.faye' do
|
@@ -42,6 +69,8 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
42
69
|
faye_double.should_receive(:set_header).with('Authorization', 'OAuth secret2')
|
43
70
|
faye_double.should_receive(:bind).with('transport:down').and_yield
|
44
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)
|
45
74
|
subject
|
46
75
|
end
|
47
76
|
end
|
@@ -52,4 +81,87 @@ describe Restforce::Concerns::Streaming, event_machine: true do
|
|
52
81
|
end
|
53
82
|
end
|
54
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
|
55
167
|
end
|