ably 0.8.15 → 1.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/.travis.yml +6 -4
- data/CHANGELOG.md +6 -2
- data/README.md +5 -1
- data/SPEC.md +1473 -852
- data/ably.gemspec +11 -8
- data/lib/ably/auth.rb +90 -53
- data/lib/ably/exceptions.rb +37 -8
- data/lib/ably/logger.rb +10 -1
- data/lib/ably/models/auth_details.rb +42 -0
- data/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/ably/models/connection_details.rb +6 -3
- data/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +17 -1
- data/lib/ably/models/message_encoders/base.rb +103 -82
- data/lib/ably/models/message_encoders/base64.rb +1 -1
- data/lib/ably/models/presence_message.rb +16 -1
- data/lib/ably/models/protocol_message.rb +20 -3
- data/lib/ably/models/token_details.rb +11 -1
- data/lib/ably/models/token_request.rb +16 -6
- data/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/ably/modules/encodeable.rb +51 -12
- data/lib/ably/modules/enum.rb +17 -7
- data/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/ably/modules/model_common.rb +13 -21
- data/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/ably/modules/state_machine.rb +2 -4
- data/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/ably/realtime.rb +2 -0
- data/lib/ably/realtime/auth.rb +102 -42
- data/lib/ably/realtime/channel.rb +68 -26
- data/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/ably/realtime/client.rb +18 -3
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/ably/realtime/connection.rb +108 -49
- data/lib/ably/realtime/connection/connection_manager.rb +167 -61
- data/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/ably/realtime/presence.rb +70 -45
- data/lib/ably/realtime/presence/members_map.rb +201 -36
- data/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +5 -5
- data/lib/ably/rest/client.rb +31 -27
- data/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/ably/rest/presence.rb +2 -2
- data/lib/ably/util/pub_sub.rb +1 -1
- data/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/ably/version.rb +2 -2
- data/spec/acceptance/realtime/auth_spec.rb +470 -111
- data/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/spec/acceptance/realtime/channel_spec.rb +1017 -168
- data/spec/acceptance/realtime/client_spec.rb +6 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/spec/acceptance/realtime/connection_spec.rb +424 -105
- data/spec/acceptance/realtime/message_spec.rb +52 -23
- data/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/spec/acceptance/realtime/presence_spec.rb +1110 -96
- data/spec/acceptance/rest/auth_spec.rb +222 -59
- data/spec/acceptance/rest/base_spec.rb +1 -1
- data/spec/acceptance/rest/channel_spec.rb +1 -2
- data/spec/acceptance/rest/client_spec.rb +104 -48
- data/spec/acceptance/rest/message_spec.rb +42 -15
- data/spec/acceptance/rest/presence_spec.rb +4 -11
- data/spec/rspec_config.rb +2 -1
- data/spec/shared/client_initializer_behaviour.rb +2 -2
- data/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/spec/spec_helper.rb +4 -2
- data/spec/support/debug_failure_helper.rb +20 -4
- data/spec/support/event_machine_helper.rb +32 -1
- data/spec/unit/auth_spec.rb +4 -11
- data/spec/unit/logger_spec.rb +28 -2
- data/spec/unit/models/auth_details_spec.rb +49 -0
- data/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/spec/unit/models/connection_details_spec.rb +12 -1
- data/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -1
- data/spec/unit/models/message_spec.rb +153 -0
- data/spec/unit/models/presence_message_spec.rb +192 -0
- data/spec/unit/models/protocol_message_spec.rb +64 -6
- data/spec/unit/models/token_details_spec.rb +75 -0
- data/spec/unit/models/token_request_spec.rb +74 -0
- data/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/spec/unit/modules/enum_spec.rb +69 -0
- data/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/spec/unit/realtime/client_spec.rb +1 -1
- data/spec/unit/realtime/connection_spec.rb +8 -5
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/spec/unit/realtime/presence_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +1 -1
- data/spec/unit/util/crypto_spec.rb +3 -3
- metadata +22 -19
@@ -62,7 +62,7 @@ describe Ably::Realtime::Client, :event_machine do
|
|
62
62
|
context 'with valid :key and :use_token_auth option set to true' do
|
63
63
|
let(:client_options) { default_options.merge(use_token_auth: true) }
|
64
64
|
|
65
|
-
it 'automatically
|
65
|
+
it 'automatically authorizes on connect and generates a token' do
|
66
66
|
connection.on(:connected) do
|
67
67
|
expect(subject.auth.current_token_details).to_not be_nil
|
68
68
|
expect(auth_params[:access_token]).to_not be_nil
|
@@ -116,16 +116,16 @@ describe Ably::Realtime::Client, :event_machine do
|
|
116
116
|
|
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
|
-
subject.auth.
|
120
|
-
expect(subject.connection).to
|
119
|
+
subject.auth.authorize do
|
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
|
124
124
|
end
|
125
125
|
|
126
126
|
it "sets Client#client_id to the new token's client_id immediately when connecting" do
|
127
|
-
subject.auth.
|
128
|
-
expect(subject.connection).to
|
127
|
+
subject.auth.authorize do
|
128
|
+
expect(subject.connection).to be_connected
|
129
129
|
expect(subject.client_id).to eql(client_id)
|
130
130
|
stop_reactor
|
131
131
|
end
|
@@ -259,7 +259,7 @@ describe Ably::Realtime::Client, :event_machine do
|
|
259
259
|
|
260
260
|
it 'provides paging' do
|
261
261
|
10.times do
|
262
|
-
subject.rest_client.request(:post, "/channels/#{channel_name}/publish", {}, { 'name'
|
262
|
+
subject.rest_client.request(:post, "/channels/#{channel_name}/publish", {}, { 'name' => 'test' })
|
263
263
|
end
|
264
264
|
|
265
265
|
subject.request(:get, "/channels/#{channel_name}/messages", { limit: 2 }).callback do |response|
|
@@ -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
|