ably-rest 0.9.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
@@ -117,7 +117,7 @@ describe Ably::Realtime::Client, :event_machine do
|
|
117
117
|
context 'when the returned token has a client_id' do
|
118
118
|
it "sets Auth#client_id to the new token's client_id immediately when connecting" do
|
119
119
|
subject.auth.authorize do
|
120
|
-
expect(subject.connection).to
|
120
|
+
expect(subject.connection).to be_connected
|
121
121
|
expect(subject.auth.client_id).to eql(client_id)
|
122
122
|
stop_reactor
|
123
123
|
end
|
@@ -125,7 +125,7 @@ describe Ably::Realtime::Client, :event_machine do
|
|
125
125
|
|
126
126
|
it "sets Client#client_id to the new token's client_id immediately when connecting" do
|
127
127
|
subject.auth.authorize do
|
128
|
-
expect(subject.connection).to
|
128
|
+
expect(subject.connection).to be_connected
|
129
129
|
expect(subject.client_id).to eql(client_id)
|
130
130
|
stop_reactor
|
131
131
|
end
|
@@ -13,6 +13,9 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
13
13
|
let(:client) do
|
14
14
|
auto_close Ably::Realtime::Client.new(client_options)
|
15
15
|
end
|
16
|
+
let(:rest_client) do
|
17
|
+
Ably::Rest::Client.new(default_options)
|
18
|
+
end
|
16
19
|
|
17
20
|
context 'authentication failure' do
|
18
21
|
let(:client_options) do
|
@@ -50,6 +53,152 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
50
53
|
end
|
51
54
|
end
|
52
55
|
end
|
56
|
+
|
57
|
+
context 'with auth_url' do
|
58
|
+
context 'opening a new connection' do
|
59
|
+
context 'request fails due to network failure' do
|
60
|
+
let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: "http://#{random_str}.domain.will.never.resolve.to/path", log_level: :fatal) }
|
61
|
+
|
62
|
+
specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
|
63
|
+
states = Hash.new { |hash, key| hash[key] = [] }
|
64
|
+
|
65
|
+
connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
|
66
|
+
|
67
|
+
connection.on do |connection_state|
|
68
|
+
states[connection_state.current.to_sym] << Time.now
|
69
|
+
if states[:disconnected].count == 2 && connection_state.current == :disconnected
|
70
|
+
expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
|
71
|
+
expect(connection.error_reason.message).to match(/auth_url/)
|
72
|
+
EventMachine.add_timer(2) do
|
73
|
+
expect(states.keys).to include(:connecting, :disconnected)
|
74
|
+
expect(states[:connecting].count).to eql(2)
|
75
|
+
expect(states[:connected].count).to eql(0)
|
76
|
+
stop_reactor
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'request fails due to invalid content', :webmock do
|
84
|
+
let(:auth_endpoint) { "http://#{random_str}.domain.will.never.resolve.to/authenticate" }
|
85
|
+
let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: auth_endpoint, log_level: :fatal) }
|
86
|
+
|
87
|
+
before do
|
88
|
+
stub_request(:get, auth_endpoint).
|
89
|
+
to_return(:status => 200, :body => "", :headers => { "Content-type" => "text/html" })
|
90
|
+
end
|
91
|
+
|
92
|
+
specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
|
93
|
+
states = Hash.new { |hash, key| hash[key] = [] }
|
94
|
+
|
95
|
+
connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
|
96
|
+
|
97
|
+
connection.on do |connection_state|
|
98
|
+
states[connection_state.current.to_sym] << Time.now
|
99
|
+
if states[:disconnected].count == 2 && connection_state.current == :disconnected
|
100
|
+
expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
|
101
|
+
expect(connection.error_reason.message).to match(/auth_url/)
|
102
|
+
expect(connection.error_reason.message).to match(/Content Type.*not supported/)
|
103
|
+
EventMachine.add_timer(2) do
|
104
|
+
expect(states.keys).to include(:connecting, :disconnected)
|
105
|
+
expect(states[:connecting].count).to eql(2)
|
106
|
+
expect(states[:connected].count).to eql(0)
|
107
|
+
stop_reactor
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'existing CONNECTED connection' do
|
116
|
+
context 'authorize request failure leaves connection in existing condition' do
|
117
|
+
let(:auth_options) { { auth_url: "http://#{random_str}.domain.will.never.resolve.to/path" } }
|
118
|
+
let(:client_options) { default_options.merge(use_token_auth: true, log_level: :fatal) }
|
119
|
+
|
120
|
+
specify 'the connection remains in the CONNECTED state and authorize fails (#RSA4c, #RSA4c1, #RSA4c3)' do
|
121
|
+
connection.once(:connected) do
|
122
|
+
connection.on { raise "State should not change and should stay connected" }
|
123
|
+
|
124
|
+
client.auth.authorize(nil, auth_options).tap do |deferrable|
|
125
|
+
deferrable.callback { raise "Authorize should not succeed" }
|
126
|
+
deferrable.errback do |err|
|
127
|
+
expect(err).to be_a(Ably::Exceptions::ConnectionError)
|
128
|
+
expect(err.message).to match(/auth_url/)
|
129
|
+
|
130
|
+
EventMachine.add_timer(1) do
|
131
|
+
expect(connection).to be_connected
|
132
|
+
connection.off
|
133
|
+
stop_reactor
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'with auth_callback' do
|
144
|
+
context 'opening a new connection' do
|
145
|
+
context 'when callback fails due to an exception' do
|
146
|
+
let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: Proc.new { raise "Cannot issue token" }, log_level: :fatal) }
|
147
|
+
|
148
|
+
it 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
|
149
|
+
states = Hash.new { |hash, key| hash[key] = [] }
|
150
|
+
|
151
|
+
connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
|
152
|
+
|
153
|
+
connection.on do |connection_state|
|
154
|
+
states[connection_state.current.to_sym] << Time.now
|
155
|
+
if states[:disconnected].count == 2 && connection_state.current == :disconnected
|
156
|
+
expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
|
157
|
+
expect(connection.error_reason.message).to match(/auth_callback/)
|
158
|
+
EventMachine.add_timer(2) do
|
159
|
+
expect(states.keys).to include(:connecting, :disconnected)
|
160
|
+
expect(states[:connecting].count).to eql(2)
|
161
|
+
expect(states[:connected].count).to eql(0)
|
162
|
+
stop_reactor
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'existing CONNECTED connection' do
|
170
|
+
context 'when callback fails due to the request taking longer than realtime_request_timeout' do
|
171
|
+
let(:request_timeout) { 3 }
|
172
|
+
let(:client_options) { default_options.merge(
|
173
|
+
realtime_request_timeout: request_timeout,
|
174
|
+
use_token_auth: true,
|
175
|
+
log_level: :fatal)
|
176
|
+
}
|
177
|
+
let(:auth_options) { { auth_callback: Proc.new { sleep 10 }, } }
|
178
|
+
|
179
|
+
it 'the authorization request fails as configured in the realtime_request_timeout (#RSA4c, #RSA4c1, #RSA4c3)' do
|
180
|
+
connection.once(:connected) do
|
181
|
+
connection.on { raise "State should not change and should stay connected" }
|
182
|
+
|
183
|
+
client.auth.authorize(nil, auth_options).tap do |deferrable|
|
184
|
+
deferrable.callback { raise "Authorize should not succeed" }
|
185
|
+
deferrable.errback do |err|
|
186
|
+
expect(err).to be_a(Ably::Exceptions::ConnectionError)
|
187
|
+
expect(err.message).to match(/auth_callback/)
|
188
|
+
|
189
|
+
EventMachine.add_timer(1) do
|
190
|
+
expect(connection).to be_connected
|
191
|
+
connection.off
|
192
|
+
stop_reactor
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
53
202
|
end
|
54
203
|
|
55
204
|
context 'automatic connection retry' do
|
@@ -100,7 +249,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
100
249
|
connection.once(:suspended) do
|
101
250
|
expect(connection.state).to eq(:suspended)
|
102
251
|
|
103
|
-
expect(state_changes[:connecting]).to eql(expected_retry_attempts)
|
252
|
+
expect(state_changes[:connecting]).to eql(expected_retry_attempts + 1) # allow for initial connecting attempt
|
104
253
|
expect(state_changes[:disconnected]).to eql(expected_retry_attempts)
|
105
254
|
|
106
255
|
expect(time_passed).to be > max_time_in_state_for_tests
|
@@ -213,7 +362,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
213
362
|
|
214
363
|
context 'when connection state is :failed' do
|
215
364
|
describe '#close' do
|
216
|
-
it 'will not transition state to :close and
|
365
|
+
it 'will not transition state to :close and fails with an InvalidStateChange exception' do
|
217
366
|
connection.on(:connected) { raise 'Connection should not have reached :connected state' }
|
218
367
|
|
219
368
|
connection.once(:suspended) do
|
@@ -222,8 +371,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
222
371
|
|
223
372
|
connection.once(:failed) do
|
224
373
|
expect(connection.state).to eq(:failed)
|
225
|
-
|
226
|
-
|
374
|
+
connection.close.errback do |error|
|
375
|
+
expect(error).to be_a(Ably::Exceptions::InvalidStateChange)
|
376
|
+
expect(error.message).to match(/Unable to transition from failed => closing/)
|
377
|
+
stop_reactor
|
378
|
+
end
|
227
379
|
end
|
228
380
|
end
|
229
381
|
end
|
@@ -457,7 +609,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
457
609
|
end
|
458
610
|
|
459
611
|
context 'after successfully reconnecting and resuming' do
|
460
|
-
it 'retains connection_id and updates the connection_key' do
|
612
|
+
it 'retains connection_id and updates the connection_key (#RTN15e, #RTN16d)' do
|
461
613
|
connection.once(:connected) do
|
462
614
|
previous_connection_id = connection.id
|
463
615
|
connection.transport.close_connection_after_writing
|
@@ -472,8 +624,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
472
624
|
end
|
473
625
|
end
|
474
626
|
|
475
|
-
it '
|
476
|
-
emitted_error = nil
|
627
|
+
it 'includes the error received in the connection state change from Ably but leaves the channels attached' do
|
477
628
|
channel.attach do
|
478
629
|
connection.transport.close_connection_after_writing
|
479
630
|
|
@@ -487,19 +638,15 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
487
638
|
Ably::Realtime::Client::IncomingMessageDispatcher.new(client, connection)
|
488
639
|
end
|
489
640
|
|
490
|
-
connection.once(:connected) do
|
641
|
+
connection.once(:connected) do |connection_state_change|
|
491
642
|
EM.add_timer(0.5) do
|
492
|
-
expect(
|
493
|
-
expect(
|
643
|
+
expect(connection_state_change.reason).to be_a(Ably::Exceptions::Standard)
|
644
|
+
expect(connection_state_change.reason.message).to match(/Injected error/)
|
494
645
|
expect(connection.error_reason).to be_a(Ably::Exceptions::Standard)
|
495
646
|
expect(channel).to be_attached
|
496
647
|
stop_reactor
|
497
648
|
end
|
498
649
|
end
|
499
|
-
|
500
|
-
connection.once(:error) do |error|
|
501
|
-
emitted_error = error
|
502
|
-
end
|
503
650
|
end
|
504
651
|
end
|
505
652
|
|
@@ -541,7 +688,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
541
688
|
|
542
689
|
channel.attach do
|
543
690
|
publishing_client_channel.attach do
|
544
|
-
connection.transport.
|
691
|
+
connection.transport.unsafe_off # remove all event handlers that detect socket connection state has changed
|
545
692
|
connection.transport.close_connection_after_writing
|
546
693
|
|
547
694
|
publishing_client_channel.publish('event', 'message') do
|
@@ -563,6 +710,36 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
563
710
|
end
|
564
711
|
end
|
565
712
|
end
|
713
|
+
|
714
|
+
it 'retains the client_serial (#RTN15c2, #RTN15c3)' do
|
715
|
+
last_message = nil
|
716
|
+
channel = client.channels.get("foo")
|
717
|
+
|
718
|
+
connection.once(:connected) do
|
719
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
720
|
+
if protocol_message.action == :message
|
721
|
+
last_message = protocol_message
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
channel.publish("first") do
|
726
|
+
expect(last_message.message_serial).to eql(0)
|
727
|
+
channel.publish("second") do
|
728
|
+
expect(last_message.message_serial).to eql(1)
|
729
|
+
connection.once(:connected) do
|
730
|
+
channel.publish("first on resumed connection") do
|
731
|
+
# Message serial reset after failed resume
|
732
|
+
expect(last_message.message_serial).to eql(2)
|
733
|
+
stop_reactor
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
# simulate connection dropped to re-establish web socket
|
738
|
+
connection.transition_state_machine :disconnected
|
739
|
+
end
|
740
|
+
end
|
741
|
+
end
|
742
|
+
end
|
566
743
|
end
|
567
744
|
|
568
745
|
context 'when failing to resume' do
|
@@ -589,36 +766,159 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
589
766
|
end
|
590
767
|
end
|
591
768
|
|
592
|
-
it '
|
769
|
+
it 'issue a reattach for all attached channels and fail all message awaiting an ACK (#RTN15c3)' do
|
593
770
|
channel_count = 10
|
594
771
|
channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
|
595
772
|
when_all(*channels.map(&:attach)) do
|
596
|
-
|
773
|
+
attached_channels = []
|
774
|
+
reattaching_channels = []
|
775
|
+
attach_protocol_messages = []
|
776
|
+
failed_messages = []
|
777
|
+
|
597
778
|
channels.each do |channel|
|
598
|
-
channel.
|
779
|
+
channel.publish("foo").errback do
|
780
|
+
failed_messages << channel
|
781
|
+
end
|
782
|
+
channel.on(:attaching) do |channel_state_change|
|
599
783
|
error = channel_state_change.reason
|
600
784
|
expect(error.message).to match(/Unable to recover connection/i)
|
601
|
-
|
602
|
-
|
603
|
-
|
785
|
+
reattaching_channels << channel
|
786
|
+
end
|
787
|
+
channel.on(:attached) do
|
788
|
+
attached_channels << channel
|
789
|
+
next unless attached_channels.count == channel_count
|
790
|
+
expect(reattaching_channels.count).to eql(channel_count)
|
791
|
+
expect(failed_messages.count).to eql(channel_count)
|
792
|
+
expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
|
793
|
+
stop_reactor
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
797
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
798
|
+
if protocol_message.action == :attach
|
799
|
+
attach_protocol_messages << protocol_message.channel
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
kill_connection_transport_and_prevent_valid_resume
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
it 'issue a reattach for all attaching channels and fail all queued messages (#RTN15c3)' do
|
808
|
+
channel_count = 10
|
809
|
+
channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
|
810
|
+
|
811
|
+
channels.map(&:attach)
|
812
|
+
|
813
|
+
attached_channels = []
|
814
|
+
attach_protocol_messages = []
|
815
|
+
failed_messages = []
|
816
|
+
|
817
|
+
channels.each do |channel|
|
818
|
+
channel.publish("foo").errback do
|
819
|
+
failed_messages << channel
|
820
|
+
end
|
821
|
+
|
822
|
+
channel.on(:attached) do |state_change|
|
823
|
+
attached_channels << channel
|
824
|
+
expect(state_change).to_not be_resumed
|
825
|
+
next unless attached_channels.count == channel_count
|
826
|
+
expect(failed_messages.count).to eql(channel_count)
|
827
|
+
expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
|
828
|
+
stop_reactor
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
833
|
+
if protocol_message.action == :attach
|
834
|
+
attach_protocol_messages << protocol_message.channel
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
client.connection.once(:connected) do
|
839
|
+
kill_connection_transport_and_prevent_valid_resume
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'issue a attach for all suspended channels (#RTN15c3)' do
|
844
|
+
channel_count = 10
|
845
|
+
channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
|
846
|
+
|
847
|
+
when_all(*channels.map(&:attach)) do
|
848
|
+
# Force all channels into a suspended state
|
849
|
+
channels.map do |channel|
|
850
|
+
channel.transition_state_machine! :suspended
|
851
|
+
expect(channel).to be_suspended
|
852
|
+
end
|
853
|
+
|
854
|
+
attached_channels = []
|
855
|
+
reattaching_channels = []
|
856
|
+
attach_protocol_messages = []
|
857
|
+
|
858
|
+
channels.each do |channel|
|
859
|
+
channel.on(:attaching) do
|
860
|
+
reattaching_channels << channel
|
861
|
+
end
|
862
|
+
channel.on(:attached) do
|
863
|
+
attached_channels << channel
|
864
|
+
next unless attached_channels.count == channel_count
|
865
|
+
expect(reattaching_channels.count).to eql(channel_count)
|
866
|
+
expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
|
604
867
|
stop_reactor
|
605
868
|
end
|
606
869
|
end
|
607
870
|
|
871
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
872
|
+
if protocol_message.action == :attach
|
873
|
+
attach_protocol_messages << protocol_message.channel
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
608
877
|
kill_connection_transport_and_prevent_valid_resume
|
609
878
|
end
|
610
879
|
end
|
611
880
|
|
612
|
-
it '
|
881
|
+
it 'sets the error reason on each channel' do
|
613
882
|
channel.attach do
|
883
|
+
channel.on(:attaching) do |state_change|
|
884
|
+
expect(state_change.reason.message).to match(/Unable to recover connection/i)
|
885
|
+
expect(state_change.reason.code).to eql(80008)
|
886
|
+
expect(channel.error_reason.code).to eql(80008)
|
887
|
+
|
888
|
+
channel.on(:attached) do |state_change|
|
889
|
+
stop_reactor
|
890
|
+
end
|
891
|
+
end
|
614
892
|
kill_connection_transport_and_prevent_valid_resume
|
615
893
|
end
|
894
|
+
end
|
616
895
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
896
|
+
it 'resets the client_serial (#RTN15c3)' do
|
897
|
+
last_message = nil
|
898
|
+
channel = client.channels.get("foo")
|
899
|
+
|
900
|
+
connection.once(:connected) do
|
901
|
+
connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
902
|
+
if protocol_message.action == :message
|
903
|
+
last_message = protocol_message
|
904
|
+
end
|
905
|
+
end
|
906
|
+
|
907
|
+
channel.publish("first") do
|
908
|
+
expect(last_message.message_serial).to eql(0)
|
909
|
+
channel.publish("second") do
|
910
|
+
expect(last_message.message_serial).to eql(1)
|
911
|
+
connection.once(:connected) do
|
912
|
+
channel.publish("first on new connection") do
|
913
|
+
# Message serial reset after failed resume
|
914
|
+
expect(last_message.message_serial).to eql(0)
|
915
|
+
stop_reactor
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
kill_connection_transport_and_prevent_valid_resume
|
920
|
+
end
|
921
|
+
end
|
622
922
|
end
|
623
923
|
end
|
624
924
|
end
|
@@ -649,6 +949,137 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
649
949
|
end
|
650
950
|
end
|
651
951
|
end
|
952
|
+
|
953
|
+
context 'when an ERROR protocol message is received' do
|
954
|
+
%w(connecting connected).each do |state|
|
955
|
+
state = state.to_sym
|
956
|
+
context "whilst #{state}" do
|
957
|
+
context 'with a token error code in the range 40140 <= code < 40150 (#RTN14b)' do
|
958
|
+
let(:client_options) { default_options.merge(use_token_auth: true) }
|
959
|
+
|
960
|
+
it 'triggers a re-authentication' do
|
961
|
+
connection.once(state) do
|
962
|
+
current_token = client.auth.current_token_details
|
963
|
+
|
964
|
+
error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i, error: { code: 40140 })
|
965
|
+
connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
|
966
|
+
|
967
|
+
connection.once(:connected) do
|
968
|
+
expect(client.auth.current_token_details).to_not eql(current_token)
|
969
|
+
stop_reactor
|
970
|
+
end
|
971
|
+
end
|
972
|
+
end
|
973
|
+
end
|
974
|
+
|
975
|
+
context 'with an error code indicating an error other than a token failure (#RTN14g, #RTN15i)' do
|
976
|
+
it 'causes the connection to fail' do
|
977
|
+
connection.once(state) do
|
978
|
+
connection.once(:failed) do
|
979
|
+
stop_reactor
|
980
|
+
end
|
981
|
+
|
982
|
+
error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i, error: { code: 50000 })
|
983
|
+
connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
|
984
|
+
end
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
context 'with no error code indicating an error other than a token failure (#RTN14g, #RTN15i)' do
|
989
|
+
it 'causes the connection to fail' do
|
990
|
+
connection.once(state) do
|
991
|
+
connection.once(:failed) do
|
992
|
+
stop_reactor
|
993
|
+
end
|
994
|
+
|
995
|
+
error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i)
|
996
|
+
connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
|
997
|
+
end
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
context "whilst resuming" do
|
1005
|
+
context "with a token error code in the region 40140 <= code < 40150 (#{}RTN15c5)" do
|
1006
|
+
before do
|
1007
|
+
stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
|
1008
|
+
stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
let!(:four_second_token) {
|
1012
|
+
rest_client.auth.request_token(ttl: 4).token
|
1013
|
+
}
|
1014
|
+
|
1015
|
+
let!(:normal_token) {
|
1016
|
+
rest_client.auth.request_token.token
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
let(:client_options) do
|
1020
|
+
default_options.merge(auth_callback: Proc.new do
|
1021
|
+
@auth_requests ||= 0
|
1022
|
+
@auth_requests += 1
|
1023
|
+
|
1024
|
+
case @auth_requests
|
1025
|
+
when 1
|
1026
|
+
four_second_token
|
1027
|
+
when 2
|
1028
|
+
normal_token
|
1029
|
+
end
|
1030
|
+
end)
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
it 'triggers a re-authentication and then resumes the connection' do
|
1034
|
+
connection.once(:connected) do
|
1035
|
+
connection_id = connection.id
|
1036
|
+
|
1037
|
+
connecting_attempts = 0
|
1038
|
+
connection.on(:connecting) { connecting_attempts += 1 }
|
1039
|
+
|
1040
|
+
connection.once(:connected) do
|
1041
|
+
expect(@auth_requests).to eql(2) # initial + reconnect fails due to expiry & then obtains new token
|
1042
|
+
expect(connecting_attempts).to eql(2) # reconnect with failed token, then reconnect with successful token
|
1043
|
+
expect(connection.id).to eql(connection_id)
|
1044
|
+
stop_reactor
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
# Prevent token expired DISCONNECTED arriving on the transport
|
1048
|
+
# Instead we want to let the client lib catch a transport closed event
|
1049
|
+
# Then attempt to reconnect with an expired token
|
1050
|
+
connection.transport.__incoming_protocol_msgbus__.unsubscribe
|
1051
|
+
|
1052
|
+
EventMachine.next_tick do
|
1053
|
+
# Lock the EventMachine for 4 seconds until the token has expired
|
1054
|
+
sleep 5
|
1055
|
+
|
1056
|
+
# Simulate an abrupt disconnection which will in turn resume but with an expired token
|
1057
|
+
connection.transport.close_connection_after_writing
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
end
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
context 'with any other error (#RTN15c4)' do
|
1065
|
+
it 'moves the connection to the failed state' do
|
1066
|
+
channel = client.channels.get("foo")
|
1067
|
+
channel.attach do
|
1068
|
+
connection.once(:failed) do |state_change|
|
1069
|
+
expect(state_change.reason.code).to eql(40400)
|
1070
|
+
expect(connection.error_reason.code).to eql(40400)
|
1071
|
+
expect(channel).to be_failed
|
1072
|
+
expect(channel.error_reason.code).to eql(40400)
|
1073
|
+
stop_reactor
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
allow(client.rest_client.auth).to receive(:key).and_return("invalid.key:secret")
|
1077
|
+
|
1078
|
+
# Simulate an abrupt disconnection which will in turn resume with an invalid key
|
1079
|
+
connection.transport.close_connection_after_writing
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
end
|
652
1083
|
end
|
653
1084
|
|
654
1085
|
describe 'fallback host feature' do
|