ably 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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