ably 1.0.5 → 1.0.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably/auth.rb +4 -4
  5. data/lib/ably/logger.rb +1 -1
  6. data/lib/ably/models/idiomatic_ruby_wrapper.rb +8 -8
  7. data/lib/ably/models/message.rb +6 -4
  8. data/lib/ably/models/presence_message.rb +6 -4
  9. data/lib/ably/modules/async_wrapper.rb +2 -2
  10. data/lib/ably/modules/conversions.rb +1 -1
  11. data/lib/ably/modules/encodeable.rb +1 -1
  12. data/lib/ably/modules/event_emitter.rb +2 -2
  13. data/lib/ably/modules/safe_deferrable.rb +1 -1
  14. data/lib/ably/modules/safe_yield.rb +1 -1
  15. data/lib/ably/modules/state_emitter.rb +5 -5
  16. data/lib/ably/realtime/auth.rb +1 -1
  17. data/lib/ably/realtime/channel.rb +3 -3
  18. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  19. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +3 -2
  20. data/lib/ably/realtime/connection.rb +11 -6
  21. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  22. data/lib/ably/realtime/presence.rb +3 -3
  23. data/lib/ably/realtime/presence/members_map.rb +6 -6
  24. data/lib/ably/rest/channel.rb +2 -2
  25. data/lib/ably/rest/client.rb +20 -12
  26. data/lib/ably/version.rb +1 -1
  27. data/spec/acceptance/realtime/auth_spec.rb +13 -37
  28. data/spec/acceptance/realtime/channel_history_spec.rb +7 -1
  29. data/spec/acceptance/realtime/channel_spec.rb +3 -3
  30. data/spec/acceptance/realtime/client_spec.rb +2 -2
  31. data/spec/acceptance/realtime/connection_failures_spec.rb +221 -7
  32. data/spec/acceptance/realtime/connection_spec.rb +13 -21
  33. data/spec/acceptance/realtime/message_spec.rb +2 -2
  34. data/spec/acceptance/realtime/presence_history_spec.rb +12 -3
  35. data/spec/acceptance/realtime/presence_spec.rb +10 -10
  36. data/spec/acceptance/rest/auth_spec.rb +21 -48
  37. data/spec/acceptance/rest/client_spec.rb +193 -68
  38. data/spec/shared/client_initializer_behaviour.rb +1 -9
  39. data/spec/spec_helper.rb +2 -0
  40. data/spec/support/event_emitter_helper.rb +31 -0
  41. data/spec/support/event_machine_helper.rb +1 -1
  42. data/spec/support/test_logger_helper.rb +42 -0
  43. data/spec/unit/logger_spec.rb +1 -9
  44. data/spec/unit/modules/async_wrapper_spec.rb +2 -2
  45. data/spec/unit/modules/event_emitter_spec.rb +3 -3
  46. data/spec/unit/modules/state_emitter_spec.rb +10 -10
  47. data/spec/unit/realtime/channel_spec.rb +1 -1
  48. data/spec/unit/realtime/connection_spec.rb +1 -1
  49. data/spec/unit/realtime/presence_spec.rb +1 -1
  50. data/spec/unit/rest/channel_spec.rb +22 -0
  51. data/spec/unit/util/pub_sub_spec.rb +3 -3
  52. metadata +26 -8
@@ -71,7 +71,7 @@ module Ably::Realtime
71
71
 
72
72
  ensure_channel_attached(deferrable) do
73
73
  if entering?
74
- once_or_if(STATE.Entered, else: proc { |args| deferrable_fail deferrable, *args }) do
74
+ once_or_if(STATE.Entered, else: lambda { |args| deferrable_fail deferrable, *args }) do
75
75
  deferrable_succeed deferrable, &success_block
76
76
  end
77
77
  else
@@ -132,7 +132,7 @@ module Ably::Realtime
132
132
 
133
133
  ensure_channel_attached(deferrable) do
134
134
  if leaving?
135
- once_or_if(STATE.Left, else: proc { |error|deferrable_fail deferrable, *args }) do
135
+ once_or_if(STATE.Left, else: lambda { |error|deferrable_fail deferrable, *args }) do
136
136
  deferrable_succeed deferrable, &success_block
137
137
  end
138
138
  else
@@ -310,7 +310,7 @@ module Ably::Realtime
310
310
  # @api private
311
311
  def __incoming_msgbus__
312
312
  @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
313
- coerce_into: Proc.new { |event| Ably::Models::ProtocolMessage::ACTION(event) }
313
+ coerce_into: lambda { |event| Ably::Models::ProtocolMessage::ACTION(event) }
314
314
  )
315
315
  end
316
316
 
@@ -94,7 +94,7 @@ module Ably::Realtime
94
94
  wait_for_sync = options.fetch(:wait_for_sync, true)
95
95
  deferrable = Ably::Util::SafeDeferrable.new(logger)
96
96
 
97
- result_block = proc do
97
+ result_block = lambda do
98
98
  present_members.tap do |members|
99
99
  members.keep_if { |member| member.connection_id == options[:connection_id] } if options[:connection_id]
100
100
  members.keep_if { |member| member.client_id == options[:client_id] } if options[:client_id]
@@ -110,17 +110,17 @@ module Ably::Realtime
110
110
  # Must be defined before subsequent procs reference this callback
111
111
  reset_callbacks = nil
112
112
 
113
- in_sync_callback = proc do
114
- reset_callbacks
113
+ in_sync_callback = lambda do
114
+ reset_callbacks.call if reset_callbacks
115
115
  result_block.call
116
116
  end
117
117
 
118
- failed_callback = proc do |error|
119
- reset_callbacks
118
+ failed_callback = lambda do |error|
119
+ reset_callbacks.call if reset_callbacks
120
120
  deferrable.fail error
121
121
  end
122
122
 
123
- reset_callbacks = proc do
123
+ reset_callbacks = lambda do
124
124
  off(&in_sync_callback)
125
125
  off(&failed_callback)
126
126
  channel.off(&failed_callback)
@@ -22,7 +22,7 @@ module Ably
22
22
  # @option channel_options [Hash,Ably::Models::CipherParams] :cipher A hash of options or a {Ably::Models::CipherParams} to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +:cipher+ options
23
23
  #
24
24
  def initialize(client, name, channel_options = {})
25
- ensure_utf_8 :name, name
25
+ name = (ensure_utf_8 :name, name)
26
26
 
27
27
  update_options channel_options
28
28
  @client = client
@@ -58,7 +58,7 @@ module Ably
58
58
  messages = if name.kind_of?(Enumerable)
59
59
  name
60
60
  else
61
- ensure_utf_8 :name, name, allow_nil: true
61
+ name = ensure_utf_8(:name, name, allow_nil: true)
62
62
  ensure_supported_payload data
63
63
  [{ name: name, data: data }.merge(attributes)]
64
64
  end
@@ -439,22 +439,15 @@ module Ably
439
439
  max_retry_duration = http_defaults.fetch(:max_retry_duration)
440
440
  requested_at = Time.now
441
441
  retry_count = 0
442
- request_id = nil
443
- if add_request_ids
444
- params = if params.nil?
445
- {}
446
- else
447
- params.dup
448
- end
449
- request_id = SecureRandom.urlsafe_base64(10)
450
- params[:request_id] = request_id
451
- end
442
+ retry_sequence_id = nil
443
+ request_id = SecureRandom.urlsafe_base64(10) if add_request_ids
452
444
 
453
445
  begin
454
446
  use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
455
447
 
456
448
  connection(use_fallback: use_fallback).send(method, path, params) do |request|
457
449
  if add_request_ids
450
+ request.params[:request_id] = request_id
458
451
  request.options.context = {} if request.options.context.nil?
459
452
  request.options.context[:request_id] = request_id
460
453
  end
@@ -466,15 +459,30 @@ module Ably
466
459
  end
467
460
  end
468
461
  end
462
+ end.tap do
463
+ if retry_count > 0
464
+ logger.warn do
465
+ "Ably::Rest::Client - Request SUCCEEDED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
466
+ " #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
467
+ end
468
+ end
469
469
  end
470
470
 
471
471
  rescue Faraday::TimeoutError, Faraday::ClientError, Ably::Exceptions::ServerError => error
472
+ retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
472
473
  time_passed = Time.now - requested_at
474
+
473
475
  if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
474
476
  retry_count += 1
475
- logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}" }
477
+ logger.warn { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed (seq ##{retry_sequence_id}): #{error}" }
476
478
  retry
477
479
  end
480
+
481
+ logger.error do
482
+ "Ably::Rest::Client - Request FAILED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
483
+ " #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
484
+ end
485
+
478
486
  case error
479
487
  when Faraday::TimeoutError
480
488
  raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, 80014, error, { request_id: request_id })
@@ -549,7 +557,7 @@ module Ably
549
557
  setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
550
558
 
551
559
  # Set Faraday's HTTP adapter
552
- builder.adapter Faraday.default_adapter
560
+ builder.adapter :excon
553
561
  end
554
562
  end
555
563
 
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.0.5'
2
+ VERSION = '1.0.6'
3
3
  PROTOCOL_VERSION = '1.0'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -206,7 +206,7 @@ describe Ably::Realtime::Auth, :event_machine do
206
206
 
207
207
  context 'with a slow auth callback response' do
208
208
  let(:auth_callback) do
209
- Proc.new do
209
+ lambda do |token_params|
210
210
  sleep pause
211
211
  rest_auth_client.auth.request_token
212
212
  end
@@ -214,7 +214,7 @@ describe Ably::Realtime::Auth, :event_machine do
214
214
 
215
215
  it 'asynchronously authenticates' do
216
216
  timers_called = 0
217
- block = Proc.new do
217
+ block = lambda do
218
218
  timers_called += 1
219
219
  EventMachine.add_timer(0.5, &block)
220
220
  end
@@ -230,7 +230,7 @@ describe Ably::Realtime::Auth, :event_machine do
230
230
 
231
231
  context 'when implicitly called, with an explicit ClientOptions client_id' do
232
232
  let(:client_id) { random_str }
233
- let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }, client_id: client_id, log_level: :none) }
233
+ let(:client_options) { default_options.merge(auth_callback: lambda { |token_params| auth_token_object }, client_id: client_id, log_level: :none) }
234
234
  let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key, client_id: 'invalid')) }
235
235
 
236
236
  context 'and an incompatible client_id in a TokenDetails object passed to the auth callback' do
@@ -268,7 +268,7 @@ describe Ably::Realtime::Auth, :event_machine do
268
268
 
269
269
  context 'when explicitly called, with an explicit ClientOptions client_id' do
270
270
  let(:auth_proc) do
271
- Proc.new do
271
+ lambda do |token_params|
272
272
  if !@requested
273
273
  @requested = true
274
274
  valid_auth_token
@@ -304,18 +304,18 @@ describe Ably::Realtime::Auth, :event_machine do
304
304
  let(:rest_client) { Ably::Rest::Client.new(default_options) }
305
305
  let(:client_publisher) { auto_close Ably::Realtime::Client.new(default_options) }
306
306
  let(:basic_capability) { JSON.dump("foo" => ["subscribe"]) }
307
- let(:basic_token_cb) { Proc.new do
307
+ let(:basic_token_cb) { lambda do |token_params|
308
308
  rest_client.auth.create_token_request({ capability: basic_capability })
309
309
  end }
310
310
  let(:upgraded_capability) { JSON.dump({ "foo" => ["subscribe", "publish"] }) }
311
- let(:upgraded_token_cb) { Proc.new do
311
+ let(:upgraded_token_cb) { lambda do |token_params|
312
312
  rest_client.auth.create_token_request({ capability: upgraded_capability })
313
313
  end }
314
- let(:identified_token_cb) { Proc.new do
314
+ let(:identified_token_cb) { lambda do |token_params|
315
315
  rest_client.auth.create_token_request({ client_id: 'bob' })
316
316
  end }
317
317
  let(:downgraded_capability) { JSON.dump({ "bar" => ["subscribe"] }) }
318
- let(:downgraded_token_cb) { Proc.new do
318
+ let(:downgraded_token_cb) { lambda do |token_params|
319
319
  rest_client.auth.create_token_request({ capability: downgraded_capability })
320
320
  end }
321
321
 
@@ -589,7 +589,7 @@ describe Ably::Realtime::Auth, :event_machine do
589
589
  context 'when client is identified' do
590
590
  let(:client_options) { default_options.merge(auth_callback: basic_token_cb, log_level: :none) }
591
591
 
592
- let(:basic_token_cb) { Proc.new do
592
+ let(:basic_token_cb) { lambda do |token_params|
593
593
  rest_client.auth.create_token_request({ client_id: 'mike', capability: basic_capability })
594
594
  end }
595
595
 
@@ -612,7 +612,7 @@ describe Ably::Realtime::Auth, :event_machine do
612
612
  connection_failed = false
613
613
 
614
614
  client.connection.once(:connected) do
615
- client.auth.authorize(nil, auth_callback: Proc.new { 'invalid.token:will.cause.failure' }).tap do |deferrable|
615
+ client.auth.authorize(nil, auth_callback: lambda { |token_params| 'invalid.token:will.cause.failure' }).tap do |deferrable|
616
616
  deferrable.errback do |error|
617
617
  EventMachine.add_timer(0.2) do
618
618
  expect(connection_failed).to eql(true)
@@ -638,7 +638,7 @@ describe Ably::Realtime::Auth, :event_machine do
638
638
 
639
639
  it 'calls the error callback of authorize and leaves the connection intact (#RSA4c3)' do
640
640
  client.connection.once(:connected) do
641
- client.auth.authorize(nil, auth_callback: Proc.new { raise 'Exception raised' }).errback do |error|
641
+ client.auth.authorize(nil, auth_callback: lambda { |token_params| raise 'Exception raised' }).errback do |error|
642
642
  EventMachine.add_timer(0.2) do
643
643
  expect(connection).to be_connected
644
644
  expect(error.message).to match(/Exception raised/i)
@@ -1014,33 +1014,9 @@ describe Ably::Realtime::Auth, :event_machine do
1014
1014
  end
1015
1015
  end
1016
1016
 
1017
- context 'deprecated #authorise' do
1017
+ context 'deprecated #authorise', :prevent_log_stubbing do
1018
1018
  let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, use_token_auth: true) }
1019
- let(:custom_logger) do
1020
- Class.new do
1021
- def initialize
1022
- @messages = []
1023
- end
1024
-
1025
- [:fatal, :error, :warn, :info, :debug].each do |severity|
1026
- define_method severity do |message|
1027
- @messages << [severity, message]
1028
- end
1029
- end
1030
-
1031
- def logs
1032
- @messages
1033
- end
1034
-
1035
- def level
1036
- 1
1037
- end
1038
-
1039
- def level=(new_level)
1040
- end
1041
- end
1042
- end
1043
- let(:custom_logger_object) { custom_logger.new }
1019
+ let(:custom_logger_object) { TestLogger.new }
1044
1020
 
1045
1021
  it 'logs a deprecation warning (#RSA10l)' do
1046
1022
  client.auth.authorise
@@ -77,7 +77,13 @@ describe Ably::Realtime::Channel, '#history', :event_machine do
77
77
  limit.times do |index|
78
78
  expect(next_page.items[index].data).to eql("history#{index + limit}")
79
79
  end
80
- expect(next_page).to be_last
80
+ if next_page.has_next?
81
+ next_page.next do |last|
82
+ expect(last.items.length).to eql(0)
83
+ end
84
+ else
85
+ expect(next_page).to be_last
86
+ end
81
87
 
82
88
  stop_reactor
83
89
  end
@@ -1342,7 +1342,7 @@ describe Ably::Realtime::Channel, :event_machine do
1342
1342
 
1343
1343
  context 'many times with different event names' do
1344
1344
  it 'filters events accordingly to each callback' do
1345
- click_callback = proc { |message| messages << message }
1345
+ click_callback = lambda { |message| messages << message }
1346
1346
 
1347
1347
  channel.subscribe('click', &click_callback)
1348
1348
  channel.subscribe('move', &click_callback)
@@ -1958,7 +1958,7 @@ describe Ably::Realtime::Channel, :event_machine do
1958
1958
  specify 'all queued messages fail with NACK (#RTL11)' do
1959
1959
  channel.attach do
1960
1960
  # Move to disconnected
1961
- disconnect_transport_proc = Proc.new do
1961
+ disconnect_transport_proc = lambda do
1962
1962
  if connection.transport
1963
1963
  connection.transport.close_connection_after_writing
1964
1964
  else
@@ -2090,7 +2090,7 @@ describe Ably::Realtime::Channel, :event_machine do
2090
2090
  it 'will move to the SUSPENDED state and then attempt to ATTACH with the ATTACHING state (#RTL13b)' do
2091
2091
  connection.once(:connected) do
2092
2092
  # Prevent any incoming or outgoing ATTACH/ATTACHED message from Ably
2093
- prevent_protocol_messages_proc = Proc.new do
2093
+ prevent_protocol_messages_proc = lambda do
2094
2094
  if client.connection.transport
2095
2095
  client.connection.transport.__incoming_protocol_msgbus__.unsubscribe
2096
2096
  client.connection.transport.__outgoing_protocol_msgbus__.unsubscribe
@@ -134,7 +134,7 @@ describe Ably::Realtime::Client, :event_machine do
134
134
 
135
135
  context 'with a wildcard client_id token' do
136
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) }
137
+ let(:client_options) { default_options.merge(auth_callback: lambda { |token_params| auth_token_object }, client_id: client_id) }
138
138
  let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
139
139
  let(:auth_token_object) { rest_auth_client.auth.request_token(client_id: '*') }
140
140
 
@@ -155,7 +155,7 @@ describe Ably::Realtime::Client, :event_machine do
155
155
  end
156
156
 
157
157
  context 'and client_id omitted in ClientOptions' do
158
- let(:client_options) { default_options.merge(auth_callback: Proc.new { auth_token_object }) }
158
+ let(:client_options) { default_options.merge(auth_callback: lambda { |token_params| auth_token_object }) }
159
159
 
160
160
  it 'uses the token provided clientId in the connection' do
161
161
  connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
@@ -143,7 +143,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
143
143
  context 'with auth_callback' do
144
144
  context 'opening a new connection' do
145
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) }
146
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: lambda { |token_params| raise "Cannot issue token" }, log_level: :fatal) }
147
147
 
148
148
  it 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
149
149
  states = Hash.new { |hash, key| hash[key] = [] }
@@ -174,7 +174,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
174
174
  use_token_auth: true,
175
175
  log_level: :fatal)
176
176
  }
177
- let(:auth_options) { { auth_callback: Proc.new { sleep 10 }, } }
177
+ let(:auth_options) { { auth_callback: lambda { |token_params| sleep 10 }, } }
178
178
 
179
179
  it 'the authorization request fails as configured in the realtime_request_timeout (#RSA4c, #RSA4c1, #RSA4c3)' do
180
180
  connection.once(:connected) do
@@ -514,6 +514,216 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
514
514
  end
515
515
  end
516
516
 
517
+ context 'connection state freshness is monitored' do
518
+ it 'resumes connections when disconnected within the connection_state_ttl period (#RTN15g)' do
519
+ connection.once(:connected) do
520
+ connection_id = connection.id
521
+ reconnected_with_resume = false
522
+
523
+ # Make sure the next connect has the resume param
524
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
525
+ url = args[4]
526
+ uri = URI.parse(url)
527
+ expect(CGI::parse(uri.query)['resume'][0]).to_not be_empty
528
+ reconnected_with_resume = true
529
+ original.call(*args, &block)
530
+ end
531
+
532
+ connection.once(:disconnected) do
533
+ disconnected_at = Time.now
534
+
535
+ connection.once(:connecting) do
536
+ expect(Time.now.to_f - disconnected_at.to_f).to be < connection.connection_state_ttl
537
+ connection.once(:connected) do |state_change|
538
+ expect(connection.id).to eql(connection_id)
539
+ expect(reconnected_with_resume).to be_truthy
540
+ stop_reactor
541
+ end
542
+ end
543
+ end
544
+
545
+ connection.transport.unbind
546
+ end
547
+ end
548
+
549
+ context 'when connection_state_ttl period has passed since being disconnected' do
550
+ let(:client_options) do
551
+ default_options.merge(
552
+ disconnected_retry_timeout: 4,
553
+ suspended_retry_timeout: 8,
554
+ max_connection_state_ttl: 2,
555
+ )
556
+ end
557
+
558
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl period has passed (#RTN15g)' do
559
+ connection.once(:connected) do
560
+ connection_id = connection.id
561
+ resumed_with_clean_connection = false
562
+
563
+ connection.once(:disconnected) do
564
+ disconnected_at = Time.now
565
+
566
+ connection.once(:connecting) do
567
+ connection.once(:disconnected) do
568
+ # Make sure the next connect does not have the resume param
569
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
570
+ url = args[4]
571
+ uri = URI.parse(url)
572
+ expect(CGI::parse(uri.query)['resume']).to be_empty
573
+ resumed_with_clean_connection = true
574
+ original.call(*args, &block)
575
+ end
576
+
577
+ allow(connection.details).to receive(:max_idle_interval).and_return(0)
578
+ connection.__incoming_protocol_msgbus__.plugin_listeners
579
+
580
+ connection.once(:connecting) do
581
+ expect(Time.now.to_f - disconnected_at.to_f).to be > connection.connection_state_ttl
582
+ connection.once(:connected) do |state_change|
583
+ expect(connection.id).to_not eql(connection_id)
584
+ expect(resumed_with_clean_connection).to be_truthy
585
+ stop_reactor
586
+ end
587
+ end
588
+ end
589
+
590
+ # Disconnect the transport and trigger a new disconnected state
591
+ wait_until(lambda { connection.transport }) do
592
+ connection.transport.unbind
593
+ end
594
+ end
595
+
596
+ connection.__incoming_protocol_msgbus__.unplug_listeners
597
+ end
598
+
599
+ connection.transport.unbind
600
+ end
601
+ end
602
+ end
603
+
604
+ context 'when connection_state_ttl period has passed since last activity on the connection' do
605
+ let(:client_options) do
606
+ default_options.merge(
607
+ max_connection_state_ttl: 2,
608
+ )
609
+ end
610
+
611
+ it 'does not clear the local connection state when the connection_state_ttl period has passed since last activity, but the idle timeout has not passed (#RTN15g1, #RTN15g2)' do
612
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
613
+
614
+ connection.once(:connected) do
615
+ connection_id = connection.id
616
+ resumed_connection = false
617
+
618
+ connection.once(:disconnected) do
619
+ disconnected_at = Time.now
620
+
621
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(connection.connection_state_ttl + 1)
622
+
623
+ # Make sure the next connect does not have the resume param
624
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
625
+ url = args[4]
626
+ uri = URI.parse(url)
627
+ expect(CGI::parse(uri.query)['resume']).to_not be_empty
628
+ resumed_connection = true
629
+ original.call(*args, &block)
630
+ end
631
+
632
+ connection.once(:connecting) do
633
+ connection.once(:connected) do |state_change|
634
+ expect(connection.id).to eql(connection_id)
635
+ expect(resumed_connection).to be_truthy
636
+ stop_reactor
637
+ end
638
+ end
639
+ end
640
+
641
+ connection.transport.unbind
642
+ end
643
+ end
644
+
645
+ it 'clears the local connection state and uses a new connection when the connection_state_ttl + max_idle_interval period has passed since last activity (#RTN15g1, #RTN15g2)' do
646
+ expect(connection.connection_state_ttl).to eql(client_options.fetch(:max_connection_state_ttl))
647
+
648
+ connection.once(:connected) do
649
+ connection_id = connection.id
650
+ resumed_with_clean_connection = false
651
+
652
+ connection.once(:disconnected) do
653
+ disconnected_at = Time.now
654
+
655
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
656
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
657
+
658
+ # Make sure the next connect does not have the resume param
659
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
660
+ url = args[4]
661
+ uri = URI.parse(url)
662
+ expect(CGI::parse(uri.query)['resume']).to be_empty
663
+ resumed_with_clean_connection = true
664
+ original.call(*args, &block)
665
+ end
666
+
667
+ connection.once(:connecting) do
668
+ connection.once(:connected) do |state_change|
669
+ expect(connection.id).to_not eql(connection_id)
670
+ expect(resumed_with_clean_connection).to be_truthy
671
+ stop_reactor
672
+ end
673
+ end
674
+ end
675
+
676
+ connection.transport.unbind
677
+ end
678
+ end
679
+
680
+ it 'still reattaches the channels automatically following a new connection being established (#RTN15g2)' do
681
+ connection.once(:connected) do
682
+ connection_id = connection.id
683
+ resumed_with_clean_connection = false
684
+ channel_emitted_an_attached = false
685
+
686
+ channel.attach do
687
+ channel.once(:attached) do |channel_state_change|
688
+ expect(channel_state_change.resumed).to be_falsey
689
+ channel_emitted_an_attached = true
690
+ end
691
+
692
+ connection.once(:disconnected) do
693
+ disconnected_at = Time.now
694
+
695
+ pseudo_time_passed = connection.connection_state_ttl + connection.details.max_idle_interval + 1
696
+ allow(connection).to receive(:time_since_connection_confirmed_alive?).and_return(pseudo_time_passed)
697
+
698
+ # Make sure the next connect does not have the resume param
699
+ allow(EventMachine).to receive(:connect).and_wrap_original do |original, *args, &block|
700
+ url = args[4]
701
+ uri = URI.parse(url)
702
+ expect(CGI::parse(uri.query)['resume']).to be_empty
703
+ resumed_with_clean_connection = true
704
+ original.call(*args, &block)
705
+ end
706
+
707
+ connection.once(:connecting) do
708
+ connection.once(:connected) do |state_change|
709
+ expect(connection.id).to_not eql(connection_id)
710
+ expect(resumed_with_clean_connection).to be_truthy
711
+
712
+ wait_until(lambda { channel.attached? }) do
713
+ expect(channel_emitted_an_attached).to be_truthy
714
+ stop_reactor
715
+ end
716
+ end
717
+ end
718
+ end
719
+
720
+ connection.transport.unbind
721
+ end
722
+ end
723
+ end
724
+ end
725
+ end
726
+
517
727
  context 'and subsequently fails to reconnect' do
518
728
  let(:retry_every) { 1.5 }
519
729
 
@@ -935,16 +1145,20 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
935
1145
  previous_connection_id = connection.id
936
1146
  previous_connection_key = connection.key
937
1147
 
938
- five_minutes_time = Time.now + 5 * 60
939
- allow(Time).to receive(:now) { five_minutes_time }
940
-
941
1148
  connection.once(:connected) do
942
1149
  expect(connection.key).to_not eql(previous_connection_key)
943
1150
  expect(connection.id).to_not eql(previous_connection_id)
944
1151
  stop_reactor
945
1152
  end
946
1153
 
947
- kill_connection_transport_and_prevent_valid_resume
1154
+ # Wait until next tick before stubbing otherwise liveness test may
1155
+ # record the stubbed last contact time as the future time
1156
+ EventMachine.next_tick do
1157
+ five_minutes_time = Time.now + 5 * 60
1158
+ allow(Time).to receive(:now) { five_minutes_time }
1159
+
1160
+ kill_connection_transport_and_prevent_valid_resume
1161
+ end
948
1162
  end
949
1163
  end
950
1164
  end
@@ -1017,7 +1231,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1017
1231
  }
1018
1232
 
1019
1233
  let(:client_options) do
1020
- default_options.merge(auth_callback: Proc.new do
1234
+ default_options.merge(auth_callback: lambda do |token_params|
1021
1235
  @auth_requests ||= 0
1022
1236
  @auth_requests += 1
1023
1237