ably-rest 0.8.5 → 0.8.6

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/SPEC.md +1380 -631
  4. data/ably-rest.gemspec +11 -5
  5. data/lib/submodules/ably-ruby/.travis.yml +1 -1
  6. data/lib/submodules/ably-ruby/CHANGELOG.md +42 -48
  7. data/lib/submodules/ably-ruby/ably.gemspec +7 -1
  8. data/lib/submodules/ably-ruby/lib/ably.rb +2 -0
  9. data/lib/submodules/ably-ruby/lib/ably/auth.rb +155 -47
  10. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +2 -0
  11. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +2 -3
  12. data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +54 -0
  13. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +14 -4
  14. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +13 -7
  15. data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +1 -2
  16. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +3 -2
  17. data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +1 -3
  18. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +2 -2
  19. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +6 -0
  20. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +15 -4
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -0
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +10 -3
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +62 -6
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +58 -54
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +18 -5
  27. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +9 -1
  28. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +32 -14
  29. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  30. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  31. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +251 -11
  32. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +12 -2
  33. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +316 -24
  34. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +93 -1
  35. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
  36. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +284 -60
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +45 -6
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +4 -0
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +181 -49
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +13 -0
  41. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +222 -4
  42. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +132 -1
  43. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -28
  44. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +7 -7
  45. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +10 -0
  46. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +41 -17
  47. data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -0
  48. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +16 -0
  49. data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +60 -0
  50. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +45 -0
  51. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -1
  52. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +6 -5
  53. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +5 -1
  54. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +5 -1
  55. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +5 -1
  56. metadata +57 -13
@@ -53,7 +53,7 @@ describe Ably::Realtime::Client, :event_machine do
53
53
  connection.on(:connected) do
54
54
  expect(auth_params[:access_token]).to_not be_nil
55
55
  expect(auth_params[:key]).to be_nil
56
- expect(subject.auth.current_token_details).to be_nil
56
+ expect(subject.auth.current_token_details).to be_a(Ably::Models::TokenDetails)
57
57
  stop_reactor
58
58
  end
59
59
  end
@@ -113,6 +113,98 @@ describe Ably::Realtime::Client, :event_machine do
113
113
  stop_reactor
114
114
  end
115
115
  end
116
+
117
+ context 'when the returned token has a client_id' do
118
+ it "sets Auth#client_id to the new token's client_id immediately when connecting" do
119
+ subject.auth.authorise do
120
+ expect(subject.connection).to be_connecting
121
+ expect(subject.auth.client_id).to eql(client_id)
122
+ stop_reactor
123
+ end
124
+ end
125
+
126
+ it "sets Client#client_id to the new token's client_id immediately when connecting" do
127
+ subject.auth.authorise do
128
+ expect(subject.connection).to be_connecting
129
+ expect(subject.client_id).to eql(client_id)
130
+ stop_reactor
131
+ end
132
+ end
133
+ end
134
+
135
+ context 'with a wildcard client_id token' do
136
+ subject { auto_close Ably::Realtime::Client.new(client_options) }
137
+ let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id) }
138
+ let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
139
+ let(:auth_token_object) { rest_auth_client.auth.request_token(client_id: '*') }
140
+
141
+ context 'and an explicit client_id in ClientOptions' do
142
+ let(:client_id) { random_str }
143
+
144
+ it 'allows uses the explicit client_id in the connection' do
145
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
146
+ if protocol_message.action == :connected
147
+ expect(protocol_message.connection_details.client_id).to eql(client_id)
148
+ @valid_client_id = true
149
+ end
150
+ end
151
+ subject.connect do
152
+ EM.add_timer(0.5) { stop_reactor if @valid_client_id }
153
+ end
154
+ end
155
+ end
156
+
157
+ context 'and client_id omitted in ClientOptions' do
158
+ let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }) }
159
+
160
+ it 'uses the token provided clientId in the connection' do
161
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
162
+ if protocol_message.action == :connected
163
+ expect(protocol_message.connection_details.client_id).to eql('*')
164
+ @valid_client_id = true
165
+ end
166
+ end
167
+ subject.connect do
168
+ EM.add_timer(0.5) { stop_reactor if @valid_client_id }
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+
175
+ context 'with an invalid wildcard "*" :client_id' do
176
+ it 'raises an exception' do
177
+ expect { Ably::Realtime::Client.new(client_options.merge(key: api_key, client_id: '*')) }.to raise_error ArgumentError
178
+ stop_reactor
179
+ end
180
+ end
181
+ end
182
+
183
+ context 'realtime connection settings' do
184
+ context 'defaults' do
185
+ specify 'disconnected_retry_timeout is 15s' do
186
+ expect(subject.connection.defaults[:disconnected_retry_timeout]).to eql(15)
187
+ stop_reactor
188
+ end
189
+
190
+ specify 'suspended_retry_timeout is 30s' do
191
+ expect(subject.connection.defaults[:suspended_retry_timeout]).to eql(30)
192
+ stop_reactor
193
+ end
194
+ end
195
+
196
+ context 'overriden in ClientOptions' do
197
+ let(:client_options) { default_options.merge(disconnected_retry_timeout: 1, suspended_retry_timeout: 2) }
198
+
199
+ specify 'disconnected_retry_timeout is updated' do
200
+ expect(subject.connection.defaults[:disconnected_retry_timeout]).to eql(1)
201
+ stop_reactor
202
+ end
203
+
204
+ specify 'suspended_retry_timeout is updated' do
205
+ expect(subject.connection.defaults[:suspended_retry_timeout]).to eql(2)
206
+ stop_reactor
207
+ end
116
208
  end
117
209
  end
118
210
  end
@@ -53,19 +53,17 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
53
53
  end
54
54
 
55
55
  context 'automatic connection retry' do
56
- let(:client_failure_options) { default_options.merge(log_level: :none) }
57
-
58
56
  context 'with invalid WebSocket host' do
59
57
  let(:retry_every_for_tests) { 0.2 }
60
58
  let(:max_time_in_state_for_tests) { 0.6 }
61
59
 
62
- before do
63
- # Reconfigure client library retry periods and timeouts so that tests run quickly
64
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
65
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
66
- disconnected: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
67
- suspended: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
68
- )
60
+ let(:client_failure_options) do
61
+ default_options.merge(
62
+ log_level: :none,
63
+ disconnected_retry_timeout: retry_every_for_tests,
64
+ suspended_retry_timeout: retry_every_for_tests,
65
+ connection_state_ttl: max_time_in_state_for_tests
66
+ )
69
67
  end
70
68
 
71
69
  # retry immediately after failure, then one retry every :retry_every_for_tests
@@ -110,6 +108,27 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
110
108
  end
111
109
  end
112
110
 
111
+ context 'for the first time' do
112
+ let(:client_options) do
113
+ default_options.merge(realtime_host: 'non.existent.host', disconnected_retry_timeout: 2, log_level: :error)
114
+ end
115
+
116
+ it 'reattempts connection immediately and then waits disconnected_retry_timeout for a subsequent attempt' do
117
+ expect(connection.defaults[:disconnected_retry_timeout]).to eql(2)
118
+ connection.once(:disconnected) do
119
+ started_at = Time.now.to_f
120
+ connection.once(:disconnected) do
121
+ expect(Time.now.to_f - started_at).to be < 1
122
+ started_at = Time.now.to_f
123
+ connection.once(:disconnected) do
124
+ expect(Time.now.to_f - started_at).to be > 2
125
+ stop_reactor
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
113
132
  describe '#close' do
114
133
  it 'transitions connection state to :closed' do
115
134
  connection.on(:connected) { raise 'Connection should not have reached :connected state' }
@@ -130,25 +149,50 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
130
149
  end
131
150
 
132
151
  context 'when connection state is :suspended' do
133
- it 'enters the failed state after multiple attempts if the max_time_in_state is set' do
152
+ it 'stays in the suspended state after any number of reconnection attempts' do
134
153
  connection.on(:connected) { raise 'Connection should not have reached :connected state' }
135
154
 
136
155
  connection.once(:suspended) do
137
156
  count_state_changes && start_timer
138
157
 
139
- connection.on(:failed) do
140
- expect(connection.state).to eq(:failed)
158
+ EventMachine.add_timer((retry_every_for_tests + 0.1) * 10) do
159
+ expect(connection.state).to eq(:suspended)
141
160
 
142
- expect(state_changes[:connecting]).to eql(expected_retry_attempts)
143
- expect(state_changes[:suspended]).to eql(expected_retry_attempts)
161
+ expect(state_changes[:connecting]).to be >= 10
162
+ expect(state_changes[:suspended]).to be >= 10
144
163
  expect(state_changes[:disconnected]).to eql(0)
145
164
 
146
- expect(time_passed).to be > max_time_in_state_for_tests
147
165
  stop_reactor
148
166
  end
149
167
  end
150
168
  end
151
169
 
170
+ context 'for the first time' do
171
+ let(:client_options) do
172
+ default_options.merge(suspended_retry_timeout: 2, connection_state_ttl: 0, log_level: :error)
173
+ end
174
+
175
+ it 'waits suspended_retry_timeout before attempting to reconnect' do
176
+ expect(client.connection.defaults[:suspended_retry_timeout]).to eql(2)
177
+ connection.once(:connected) do
178
+ connection.transition_state_machine :suspended
179
+ allow(connection).to receive(:current_host).and_return('does.not.exist.com')
180
+
181
+ started_at = Time.now.to_f
182
+ connection.once(:connecting) do
183
+ expect(Time.now.to_f - started_at).to be > 1.75
184
+ started_at = Time.now.to_f
185
+ connection.once(:connecting) do
186
+ expect(Time.now.to_f - started_at).to be > 1.75
187
+ connection.once(:suspended) do
188
+ stop_reactor
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
152
196
  describe '#close' do
153
197
  it 'transitions connection state to :closed' do
154
198
  connection.on(:connected) { raise 'Connection should not have reached :connected state' }
@@ -172,6 +216,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
172
216
  it 'will not transition state to :close and raises a InvalidStateChange exception' do
173
217
  connection.on(:connected) { raise 'Connection should not have reached :connected state' }
174
218
 
219
+ connection.once(:suspended) do
220
+ connection.transition_state_machine :failed
221
+ end
222
+
175
223
  connection.once(:failed) do
176
224
  expect(connection.state).to eq(:failed)
177
225
  expect { connection.close }.to raise_error Ably::Exceptions::InvalidStateChange, /Unable to transition from failed => closing/
@@ -190,6 +238,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
190
238
  expect(connection.error_reason.code).to eql(80000)
191
239
  stop_reactor
192
240
  end
241
+
242
+ connection.once(:suspended) do |connection_state_change|
243
+ connection.transition_state_machine :failed, reason: connection_state_change.reason
244
+ end
193
245
  end
194
246
  end
195
247
 
@@ -216,12 +268,16 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
216
268
  end
217
269
 
218
270
  describe '#connect' do
219
- let(:timeouts) { Ably::Realtime::Connection::ConnectionManager::TIMEOUTS }
271
+ let(:timeout) { 1.5 }
220
272
 
221
- before do
222
- stub_const 'Ably::Realtime::Connection::ConnectionManager::TIMEOUTS',
223
- Ably::Realtime::Connection::ConnectionManager::TIMEOUTS.merge(open: 1.5)
273
+ let(:client_options) do
274
+ default_options.merge(
275
+ log_level: :none,
276
+ realtime_request_timeout: timeout
277
+ )
278
+ end
224
279
 
280
+ before do
225
281
  connection.on(:connected) { raise "Connection should not open in this test as CONNECTED ProtocolMessage is never received" }
226
282
 
227
283
  connection.once(:connecting) do
@@ -231,13 +287,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
231
287
  end
232
288
 
233
289
  context 'connection opening times out' do
234
- let(:client_options) { client_failure_options }
235
-
236
290
  it 'attempts to reconnect' do
237
291
  started_at = Time.now
238
292
 
239
293
  connection.once(:disconnected) do
240
- expect(Time.now.to_f - started_at.to_f).to be > timeouts.fetch(:open)
294
+ expect(Time.now.to_f - started_at.to_f).to be > timeout
241
295
  connection.once(:connecting) do
242
296
  stop_reactor
243
297
  end
@@ -246,21 +300,15 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
246
300
  connection.connect
247
301
  end
248
302
 
249
- it 'calls the errback of the returned Deferrable object when first connection attempt fails' do
250
- connection.connect.errback do |error|
251
- expect(connection.state).to eq(:disconnected)
252
- stop_reactor
253
- end
254
- end
255
-
256
303
  context 'when retry intervals are stubbed to attempt reconnection quickly' do
257
- before do
258
- # Reconfigure client library retry periods and timeouts so that tests run quickly
259
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
260
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
261
- disconnected: { retry_every: 0.1, max_time_in_state: 0.2 },
262
- suspended: { retry_every: 0.1, max_time_in_state: 0.2 },
263
- )
304
+ let(:client_options) do
305
+ default_options.merge(
306
+ log_level: :error,
307
+ disconnected_retry_timeout: 0.1,
308
+ suspended_retry_timeout: 0.1,
309
+ connection_state_ttl: 0.2,
310
+ realtime_host: 'non.existent.host'
311
+ )
264
312
  end
265
313
 
266
314
  it 'never calls the provided success block', em_timeout: 10 do
@@ -268,8 +316,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
268
316
  raise 'success block should not have been called'
269
317
  end
270
318
 
271
- connection.once(:failed) do
272
- stop_reactor
319
+ connection.once(:suspended) do
320
+ connection.once(:suspended) do
321
+ stop_reactor
322
+ end
273
323
  end
274
324
  end
275
325
  end
@@ -315,14 +365,16 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
315
365
  context 'and subsequently fails to reconnect' do
316
366
  let(:retry_every) { 1.5 }
317
367
 
318
- before do
319
- # Reconfigure client library retry periods and timeouts so that tests run quickly
320
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
321
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
322
- disconnected: { retry_every: retry_every, max_time_in_state: 60 })
368
+ let(:client_options) do
369
+ default_options.merge(
370
+ log_level: :none,
371
+ disconnected_retry_timeout: retry_every,
372
+ suspended_retry_timeout: retry_every,
373
+ connection_state_ttl: 60
374
+ )
323
375
  end
324
376
 
325
- it "retries every #{Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG[:disconnected][:retry_every]} seconds" do
377
+ it "retries every #{Ably::Realtime::Connection::DEFAULTS.fetch(:disconnected_retry_timeout)} seconds" do
326
378
  fail_if_suspended_or_failed
327
379
 
328
380
  stubbed_first_attempt = false
@@ -382,26 +434,52 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
382
434
  end
383
435
 
384
436
  context 'after successfully reconnecting and resuming' do
385
- it 'retains connection_id and connection_key' do
386
- previous_connection_id = nil
387
- previous_connection_key = nil
388
-
437
+ it 'retains connection_id and updates the connection_key' do
389
438
  connection.once(:connected) do
390
- previous_connection_id = connection.id
391
- previous_connection_key = connection.key
439
+ previous_connection_id = connection.id
392
440
  connection.transport.close_connection_after_writing
393
441
 
442
+ expect(connection).to receive(:configure_new).with(previous_connection_id, anything, anything).and_call_original
443
+
394
444
  connection.once(:connected) do
395
- # Connection key left part should match new connection key left part i.e.
396
- # wVIsgTHAB1UvXh7z-1991d8586 becomes wVIsgTHAB1UvXh7z-1990d8586 after resume
397
- expect(connection.key[/^\w{5,}-/, 0]).to_not be_nil
398
- expect(connection.key[/^\w{5,}-/, 0]).to eql(previous_connection_key[/^\w{5,}-/, 0])
445
+ expect(connection.key).to_not be_nil
399
446
  expect(connection.id).to eql(previous_connection_id)
400
447
  stop_reactor
401
448
  end
402
449
  end
403
450
  end
404
451
 
452
+ it 'emits any error received from Ably but leaves the channels attached' do
453
+ emitted_error = nil
454
+ channel.attach do
455
+ connection.transport.close_connection_after_writing
456
+
457
+ connection.once(:connecting) do
458
+ connection.__incoming_protocol_msgbus__.unsubscribe
459
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
460
+ allow(protocol_message).to receive(:error).and_return(Ably::Exceptions::Standard.new('Injected error'))
461
+ end
462
+ # Create a new message dispatcher that subscribes to ProtocolMessages after the previous subscription allowing us
463
+ # to modify the ProtocolMessage
464
+ Ably::Realtime::Client::IncomingMessageDispatcher.new(client, connection)
465
+ end
466
+
467
+ connection.once(:connected) do
468
+ EM.add_timer(0.5) do
469
+ expect(emitted_error).to be_a(Ably::Exceptions::Standard)
470
+ expect(emitted_error.message).to match(/Injected error/)
471
+ expect(connection.error_reason).to be_a(Ably::Exceptions::Standard)
472
+ expect(channel).to be_attached
473
+ stop_reactor
474
+ end
475
+ end
476
+
477
+ connection.once(:error) do |error|
478
+ emitted_error = error
479
+ end
480
+ end
481
+ end
482
+
405
483
  it 'retains channel subscription state' do
406
484
  messages_received = false
407
485
 
@@ -527,27 +605,28 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
527
605
  end
528
606
 
529
607
  describe 'fallback host feature' do
530
- let(:retry_every_for_tests) { 0.1 }
531
- let(:max_time_in_state_for_tests) { 0.3 }
532
-
533
- before do
534
- # Reconfigure client library retry periods and timeouts so that tests run quickly
535
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
536
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
537
- disconnected: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
538
- suspended: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
539
- )
608
+ let(:retry_every_for_tests) { 0.2 }
609
+ let(:max_time_in_state_for_tests) { 0.59 }
610
+
611
+ let(:timeout_options) do
612
+ default_options.merge(
613
+ environment: :production,
614
+ log_level: :none,
615
+ disconnected_retry_timeout: retry_every_for_tests,
616
+ suspended_retry_timeout: retry_every_for_tests,
617
+ connection_state_ttl: max_time_in_state_for_tests
618
+ )
540
619
  end
541
620
 
542
621
  # Retry immediately and then wait retry_every before every subsequent attempt
543
- let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round }
622
+ let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round }
544
623
 
545
624
  let(:retry_count_for_one_state) { 1 + expected_retry_attempts } # initial connect then disconnected
546
- let(:retry_count_for_all_states) { 1 + expected_retry_attempts * 2 } # initial connection, disconnected & then suspended
625
+ let(:retry_count_for_all_states) { 1 + expected_retry_attempts + 1 } # initial connection, disconnected & then one suspended attempt
547
626
 
548
627
  context 'with custom realtime websocket host option' do
549
628
  let(:expected_host) { 'this.host.does.not.exist' }
550
- let(:client_options) { default_options.merge(realtime_host: expected_host, :environment => :production, log_level: :none) }
629
+ let(:client_options) { timeout_options.merge(realtime_host: expected_host) }
551
630
 
552
631
  it 'never uses a fallback host' do
553
632
  expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host|
@@ -555,15 +634,17 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
555
634
  raise EventMachine::ConnectionError
556
635
  end
557
636
 
558
- connection.on(:failed) do
559
- stop_reactor
637
+ connection.once(:suspended) do
638
+ connection.once(:suspended) do
639
+ stop_reactor
640
+ end
560
641
  end
561
642
  end
562
643
  end
563
644
 
564
645
  context 'with custom realtime websocket port option' do
565
646
  let(:custom_port) { 666}
566
- let(:client_options) { default_options.merge(tls_port: custom_port, :environment => :production, log_level: :none) }
647
+ let(:client_options) { timeout_options.merge(tls_port: custom_port) }
567
648
 
568
649
  it 'never uses a fallback host' do
569
650
  expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host, port|
@@ -571,8 +652,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
571
652
  raise EventMachine::ConnectionError
572
653
  end
573
654
 
574
- connection.on(:failed) do
575
- stop_reactor
655
+ connection.once(:suspended) do
656
+ connection.once(:suspended) do
657
+ stop_reactor
658
+ end
576
659
  end
577
660
  end
578
661
  end
@@ -580,7 +663,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
580
663
  context 'with non-production environment' do
581
664
  let(:environment) { 'sandbox' }
582
665
  let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
583
- let(:client_options) { default_options.merge(environment: environment, log_level: :none) }
666
+ let(:client_options) { timeout_options.merge(environment: environment) }
584
667
 
585
668
  it 'never uses a fallback host' do
586
669
  expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host|
@@ -588,8 +671,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
588
671
  raise EventMachine::ConnectionError
589
672
  end
590
673
 
591
- connection.on(:failed) do
592
- stop_reactor
674
+ connection.once(:suspended) do
675
+ connection.once(:suspended) do
676
+ stop_reactor
677
+ end
593
678
  end
594
679
  end
595
680
  end
@@ -601,7 +686,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
601
686
  end
602
687
 
603
688
  let(:expected_host) { Ably::Realtime::Client::DOMAIN }
604
- let(:client_options) { default_options.merge(environment: nil, log_level: :none) }
689
+ let(:client_options) { timeout_options.merge(environment: nil) }
605
690
 
606
691
  let(:fallback_hosts_used) { Array.new }
607
692
 
@@ -616,8 +701,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
616
701
  raise EventMachine::ConnectionError
617
702
  end
618
703
 
619
- connection.on(:failed) do
620
- stop_reactor
704
+ connection.once(:suspended) do
705
+ connection.once(:suspended) do
706
+ stop_reactor
707
+ end
621
708
  end
622
709
  end
623
710
  end
@@ -629,8 +716,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
629
716
 
630
717
  it 'uses a fallback host on every subsequent disconnected attempt until suspended' do
631
718
  request = 0
632
- # Expect retry attempts + 1 attempt for the next state
633
- expect(EventMachine).to receive(:connect).exactly(retry_count_for_one_state + 1).times do |host|
719
+ expect(EventMachine).to receive(:connect).exactly(retry_count_for_one_state).times do |host|
634
720
  if request == 0
635
721
  expect(host).to eql(expected_host)
636
722
  else
@@ -640,7 +726,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
640
726
  raise EventMachine::ConnectionError
641
727
  end
642
728
 
643
- connection.on(:suspended) do
729
+ connection.once(:suspended) do
644
730
  fallback_hosts_used.pop # remove suspended attempt host
645
731
  expect(fallback_hosts_used.uniq).to match_array(custom_hosts)
646
732
  stop_reactor
@@ -649,20 +735,25 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
649
735
 
650
736
  it 'uses the primary host when suspended, and a fallback host on every subsequent suspended attempt' do
651
737
  request = 0
652
- expect(EventMachine).to receive(:connect).exactly(retry_count_for_all_states).times do |host|
738
+ expect(EventMachine).to receive(:connect).at_least(:once) do |host|
653
739
  if request == 0 || request == expected_retry_attempts + 1
654
740
  expect(host).to eql(expected_host)
655
741
  else
656
742
  expect(custom_hosts).to include(host)
657
- fallback_hosts_used << host
743
+ fallback_hosts_used << host if @suspended
658
744
  end
659
745
  request += 1
660
746
  raise EventMachine::ConnectionError
661
747
  end
662
748
 
663
- connection.on(:failed) do
664
- expect(fallback_hosts_used.uniq).to match_array(custom_hosts)
665
- stop_reactor
749
+ connection.on(:suspended) do
750
+ @suspended ||= 0
751
+ @suspended += 1
752
+
753
+ if @suspended > 3
754
+ expect(fallback_hosts_used.uniq).to match_array(custom_hosts)
755
+ stop_reactor
756
+ end
666
757
  end
667
758
  end
668
759
  end