ably-rest 0.8.3 → 0.8.5

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/lib/submodules/ably-ruby/CHANGELOG.md +10 -0
  4. data/lib/submodules/ably-ruby/README.md +1 -1
  5. data/lib/submodules/ably-ruby/Rakefile +1 -1
  6. data/lib/submodules/ably-ruby/lib/ably/auth.rb +24 -20
  7. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +3 -0
  8. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +41 -0
  9. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +43 -0
  10. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +1 -1
  11. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +1 -1
  12. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +2 -1
  13. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +8 -6
  14. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +4 -0
  15. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +4 -1
  16. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +35 -4
  17. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +13 -13
  18. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +13 -5
  19. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +27 -7
  20. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +22 -12
  21. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +16 -10
  22. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -0
  23. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +5 -4
  24. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +42 -24
  25. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +25 -17
  26. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
  27. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -2
  28. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +2 -2
  29. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +15 -0
  30. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  31. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +9 -9
  32. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +2 -2
  33. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +168 -21
  34. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channels_spec.rb +6 -2
  35. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +6 -5
  36. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +29 -19
  37. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +150 -35
  38. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +146 -23
  39. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +2 -2
  40. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +44 -24
  41. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +1 -1
  42. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +1 -1
  43. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +77 -46
  44. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +31 -3
  45. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +15 -5
  46. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +9 -7
  47. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +30 -4
  48. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +9 -6
  49. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +1 -1
  50. data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +44 -0
  51. data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +54 -0
  52. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +8 -0
  53. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +1 -1
  54. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +18 -0
  55. metadata +6 -2
@@ -1,21 +1,23 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
3
 
4
- describe Ably::Realtime::Channels do
4
+ describe Ably::Realtime::Channels, :event_machine do
5
5
  shared_examples 'a channel' do
6
6
  it 'returns a channel object' do
7
7
  expect(channel).to be_a Ably::Realtime::Channel
8
8
  expect(channel.name).to eql(channel_name)
9
+ stop_reactor
9
10
  end
10
11
 
11
12
  it 'returns channel object and passes the provided options' do
12
13
  expect(channel_with_options.options).to eql(options)
14
+ stop_reactor
13
15
  end
14
16
  end
15
17
 
16
18
  vary_by_protocol do
17
19
  let(:client) do
18
- Ably::Realtime::Client.new(key: api_key, environment: environment, protocol: protocol)
20
+ auto_close Ably::Realtime::Client.new(key: api_key, environment: environment, protocol: protocol)
19
21
  end
20
22
  let(:channel_name) { random_str }
21
23
  let(:options) { { key: 'value' } }
@@ -41,6 +43,7 @@ describe Ably::Realtime::Channels do
41
43
  new_channel = client.channels.get(channel_name, new_channel_options)
42
44
  expect(new_channel).to be_a(Ably::Realtime::Channel)
43
45
  expect(new_channel.options[:encrypted]).to eql(true)
46
+ stop_reactor
44
47
  end
45
48
  end
46
49
 
@@ -52,6 +55,7 @@ describe Ably::Realtime::Channels do
52
55
  new_channel = client.channels.get(channel_name)
53
56
  expect(new_channel).to be_a(Ably::Realtime::Channel)
54
57
  expect(original_channel.options).to eql(options)
58
+ stop_reactor
55
59
  end
56
60
  end
57
61
 
@@ -11,7 +11,7 @@ describe Ably::Realtime::Client, :event_machine do
11
11
  let(:connection) { subject.connection }
12
12
  let(:auth_params) { subject.auth.auth_params_sync }
13
13
 
14
- subject { Ably::Realtime::Client.new(client_options) }
14
+ subject { auto_close Ably::Realtime::Client.new(client_options) }
15
15
 
16
16
  context 'initialization' do
17
17
  context 'basic auth' do
@@ -31,8 +31,8 @@ describe Ably::Realtime::Client, :event_machine do
31
31
  it 'fails to connect because a private key cannot be sent over a non-secure connection' do
32
32
  connection.on(:connected) { raise 'Should not have connected' }
33
33
 
34
- connection.on(:failed) do |error|
35
- expect(error).to be_a(Ably::Exceptions::InsecureRequest)
34
+ connection.on(:failed) do |connection_state_change|
35
+ expect(connection_state_change.reason).to be_a(Ably::Exceptions::InsecureRequest)
36
36
  stop_reactor
37
37
  end
38
38
  end
@@ -44,7 +44,8 @@ describe Ably::Realtime::Client, :event_machine do
44
44
  [true, false].each do |tls_enabled|
45
45
  context "with TLS #{tls_enabled ? 'enabled' : 'disabled'}" do
46
46
  let(:capability) { { :foo => ["publish"] } }
47
- let(:token_details) { Ably::Realtime::Client.new(default_options).auth.request_token_sync(capability: capability) }
47
+ let(:token_client) { auto_close Ably::Realtime::Client.new(default_options) }
48
+ let(:token_details) { token_client.auth.request_token_sync(capability: capability) }
48
49
  let(:client_options) { default_options.merge(token: token_details.token) }
49
50
 
50
51
  context 'and a pre-generated Token provided with the :token option' do
@@ -93,7 +94,7 @@ describe Ably::Realtime::Client, :event_machine do
93
94
  let(:auth) { subject.auth }
94
95
 
95
96
  subject do
96
- Ably::Realtime::Client.new(client_options.merge(auth_callback: Proc.new do
97
+ auto_close Ably::Realtime::Client.new(client_options.merge(auth_callback: Proc.new do
97
98
  @block_called = true
98
99
  auth.create_token_request_sync(client_id: client_id)
99
100
  end))
@@ -11,7 +11,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
11
11
 
12
12
  let(:client_options) { default_options }
13
13
  let(:client) do
14
- Ably::Realtime::Client.new(client_options)
14
+ auto_close Ably::Realtime::Client.new(client_options)
15
15
  end
16
16
 
17
17
  context 'authentication failure' do
@@ -24,7 +24,8 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
24
24
  let(:invalid_key) { 'not_an_app.invalid_key_name:invalid_key_value' }
25
25
 
26
26
  it 'enters the failed state and returns a not found error' do
27
- connection.on(:failed) do |error|
27
+ connection.on(:failed) do |connection_state_change|
28
+ error = connection_state_change.reason
28
29
  expect(connection.state).to eq(:failed)
29
30
  # TODO: Check error type is an InvalidToken exception
30
31
  expect(error.status).to eq(404)
@@ -38,7 +39,8 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
38
39
  let(:invalid_key) { "#{app_id}.invalid_key_name:invalid_key_value" }
39
40
 
40
41
  it 'enters the failed state and returns an authorization error' do
41
- connection.on(:failed) do |error|
42
+ connection.on(:failed) do |connection_state_change|
43
+ error = connection_state_change.reason
42
44
  expect(connection.state).to eq(:failed)
43
45
  # TODO: Check error type is a TokenNotFOund exception
44
46
  expect(error.status).to eq(401)
@@ -182,7 +184,8 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
182
184
  context '#error_reason' do
183
185
  [:disconnected, :suspended, :failed].each do |state|
184
186
  it "contains the error when state is #{state}" do
185
- connection.on(state) do |error|
187
+ connection.on(state) do |connection_state_change|
188
+ error = connection_state_change.reason
186
189
  expect(connection.error_reason).to eq(error)
187
190
  expect(connection.error_reason.code).to eql(80000)
188
191
  stop_reactor
@@ -278,7 +281,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
278
281
  let(:channel_name) { random_str }
279
282
  let(:channel) { client.channel(channel_name) }
280
283
  let(:publishing_client) do
281
- Ably::Realtime::Client.new(client_options)
284
+ auto_close Ably::Realtime::Client.new(client_options)
282
285
  end
283
286
  let(:publishing_client_channel) { publishing_client.channel(channel_name) }
284
287
  let(:client_options) { default_options.merge(log_level: :none) }
@@ -389,7 +392,10 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
389
392
  connection.transport.close_connection_after_writing
390
393
 
391
394
  connection.once(:connected) do
392
- expect(connection.key).to eql(previous_connection_key)
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])
393
399
  expect(connection.id).to eql(previous_connection_id)
394
400
  stop_reactor
395
401
  end
@@ -462,9 +468,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
462
468
 
463
469
  context 'when failing to resume' do
464
470
  context 'because the connection_key is not or no longer valid' do
471
+ let(:channel) { client.channel(random_str) }
472
+
465
473
  def kill_connection_transport_and_prevent_valid_resume
466
474
  connection.transport.close_connection_after_writing
467
- connection.configure_new '0123456789abcdef', '0123456789abcdef', -1 # force the resume connection key to be invalid
475
+ connection.configure_new '0123456789abcdef', 'wVIsgTHAB1UvXh7z-1991d8586', -1 # force the resume connection key to be invalid
468
476
  end
469
477
 
470
478
  it 'updates the connection_id and connection_key' do
@@ -488,8 +496,9 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
488
496
  when_all(*channels.map(&:attach)) do
489
497
  detached_channels = []
490
498
  channels.each do |channel|
491
- channel.on(:detached) do |error|
492
- expect(error.message).to match(/Invalid connection key/i)
499
+ channel.on(:detached) do |channel_state_change|
500
+ error = channel_state_change.reason
501
+ expect(error.message).to match(/Unable to recover connection/i)
493
502
  detached_channels << channel
494
503
  next unless detached_channels.count == channel_count
495
504
  expect(detached_channels.count).to eql(channel_count)
@@ -502,16 +511,16 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
502
511
  end
503
512
 
504
513
  it 'emits an error on the channel and sets the error reason' do
505
- client.channel(random_str).attach do |channel|
506
- channel.on(:error) do |error|
507
- expect(error.message).to match(/Invalid connection key/i)
508
- expect(error.code).to eql(80008)
509
- expect(channel.error_reason).to eql(error)
510
- stop_reactor
511
- end
512
-
514
+ channel.attach do
513
515
  kill_connection_transport_and_prevent_valid_resume
514
516
  end
517
+
518
+ channel.on(:error) do |error|
519
+ expect(error.message).to match(/Unable to recover connection/i)
520
+ expect(error.code).to eql(80008)
521
+ expect(channel.error_reason).to eql(error)
522
+ stop_reactor
523
+ end
515
524
  end
516
525
  end
517
526
  end
@@ -620,11 +629,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
620
629
 
621
630
  it 'uses a fallback host on every subsequent disconnected attempt until suspended' do
622
631
  request = 0
623
- expect(EventMachine).to receive(:connect).exactly(retry_count_for_one_state).times do |host|
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|
624
634
  if request == 0
625
635
  expect(host).to eql(expected_host)
626
636
  else
627
- expect(custom_hosts).to include(host)
628
637
  fallback_hosts_used << host
629
638
  end
630
639
  request += 1
@@ -632,6 +641,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
632
641
  end
633
642
 
634
643
  connection.on(:suspended) do
644
+ fallback_hosts_used.pop # remove suspended attempt host
635
645
  expect(fallback_hosts_used.uniq).to match_array(custom_hosts)
636
646
  stop_reactor
637
647
  end
@@ -11,7 +11,7 @@ describe Ably::Realtime::Connection, :event_machine do
11
11
  end
12
12
 
13
13
  let(:client_options) { default_options }
14
- let(:client) { Ably::Realtime::Client.new(client_options) }
14
+ let(:client) { auto_close Ably::Realtime::Client.new(client_options) }
15
15
 
16
16
  before(:example) do
17
17
  EventMachine.add_shutdown_hook do
@@ -29,7 +29,7 @@ describe Ably::Realtime::Connection, :event_machine do
29
29
 
30
30
  context 'with :auto_connect option set to false' do
31
31
  let(:client) do
32
- Ably::Realtime::Client.new(default_options.merge(auto_connect: false))
32
+ auto_close Ably::Realtime::Client.new(default_options.merge(auto_connect: false))
33
33
  end
34
34
 
35
35
  it 'does not connect automatically' do
@@ -96,7 +96,7 @@ describe Ably::Realtime::Connection, :event_machine do
96
96
  stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0)
97
97
 
98
98
  # Authorise synchronously to ensure token has been issued
99
- client.auth.authorise_sync(token_params: { ttl: ttl })
99
+ client.auth.authorise_sync(ttl: ttl)
100
100
  end
101
101
 
102
102
  let(:original_renew_token_buffer) { @original_renew_token_buffer }
@@ -127,8 +127,8 @@ describe Ably::Realtime::Connection, :event_machine do
127
127
  it 'renews the token on connect, and only makes one subsequent attempt to obtain a new token' do
128
128
  expect(client.rest_client.auth).to receive(:authorise).at_least(:twice).and_call_original
129
129
  connection.once(:disconnected) do
130
- connection.once(:failed) do |error|
131
- expect(error.code).to eql(40140) # token expired
130
+ connection.once(:failed) do |connection_state_change|
131
+ expect(connection_state_change.reason.code).to eql(40140) # token expired
132
132
  stop_reactor
133
133
  end
134
134
  end
@@ -158,7 +158,7 @@ describe Ably::Realtime::Connection, :event_machine do
158
158
  context 'when connected with a valid non-expired token' do
159
159
  context 'that then expires following the connection being opened' do
160
160
  let(:ttl) { 5 }
161
- let(:channel) { client.channel('test') }
161
+ let(:channel) { client.channel(random_str) }
162
162
 
163
163
  context 'the server' do
164
164
  it 'disconnects the client, and the client automatically renews the token and then reconnects', em_timeout: 15 do
@@ -167,8 +167,8 @@ describe Ably::Realtime::Connection, :event_machine do
167
167
 
168
168
  connection.once(:connected) do
169
169
  started_at = Time.now
170
- connection.once(:disconnected) do |error|
171
- expect(error.code).to eq(40140) # Token expired
170
+ connection.once(:disconnected) do |connection_state_change|
171
+ expect(connection_state_change.reason.code).to eq(40140) # Token expired
172
172
 
173
173
  # Token has expired, so now ensure it is not used again
174
174
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', original_token_expiry_buffer
@@ -178,7 +178,6 @@ describe Ably::Realtime::Connection, :event_machine do
178
178
  expect(client.auth.current_token_details).to_not be_expired
179
179
  expect(Time.now - started_at >= ttl)
180
180
  expect(original_token).to be_expired
181
- expect(error.code).to eql(40140) # token expired
182
181
  stop_reactor
183
182
  end
184
183
  end
@@ -205,7 +204,8 @@ describe Ably::Realtime::Connection, :event_machine do
205
204
 
206
205
  let!(:expired_token_details) do
207
206
  # Request a token synchronously
208
- Ably::Realtime::Client.new(default_options).auth.request_token_sync(token_params: { ttl: 0.01 })
207
+ token_client = auto_close Ably::Realtime::Client.new(default_options)
208
+ token_client.auth.request_token_sync(ttl: 0.01)
209
209
  end
210
210
 
211
211
  context 'opening a new connection' do
@@ -271,8 +271,7 @@ describe Ably::Realtime::Connection, :event_machine do
271
271
  end
272
272
 
273
273
  it 'calls the Deferrable callback on success' do
274
- connection.connect.callback do |connection|
275
- expect(connection).to be_a(Ably::Realtime::Connection)
274
+ connection.connect.callback do
276
275
  expect(connection.state).to eq(:connected)
277
276
  stop_reactor
278
277
  end
@@ -307,7 +306,8 @@ describe Ably::Realtime::Connection, :event_machine do
307
306
  end
308
307
 
309
308
  describe 'once connected' do
310
- let(:connection2) { Ably::Realtime::Client.new(client_options).connection }
309
+ let(:client2) { auto_close Ably::Realtime::Client.new(client_options) }
310
+ let(:connection2) { client2.connection }
311
311
 
312
312
  describe 'connection#id' do
313
313
  it 'is a string' do
@@ -437,7 +437,7 @@ describe Ably::Realtime::Connection, :event_machine do
437
437
 
438
438
  it 'calls the Deferrable callback on success' do
439
439
  connection.connect do
440
- connection.close.callback do |connection|
440
+ connection.close.callback do
441
441
  expect(connection).to be_a(Ably::Realtime::Connection)
442
442
  expect(connection.state).to eq(:closed)
443
443
  stop_reactor
@@ -577,7 +577,7 @@ describe Ably::Realtime::Connection, :event_machine do
577
577
  let(:channel_name) { random_str }
578
578
  let(:channel) { client.channel(channel_name) }
579
579
  let(:publishing_client) do
580
- Ably::Realtime::Client.new(client_options)
580
+ auto_close Ably::Realtime::Client.new(client_options)
581
581
  end
582
582
  let(:publishing_client_channel) { publishing_client.channel(channel_name) }
583
583
  let(:client_options) { default_options.merge(log_level: :fatal) }
@@ -595,9 +595,10 @@ describe Ably::Realtime::Connection, :event_machine do
595
595
  def self.available_states
596
596
  [:connecting, :connected, :disconnected, :suspended, :failed]
597
597
  end
598
- let(:available_states) { self.class.available_states}
598
+ let(:available_states) { self.class.available_states }
599
599
  let(:states) { Hash.new }
600
600
  let(:client_options) { default_options.merge(log_level: :none) }
601
+ let(:channel) { client.channel(random_str) }
601
602
 
602
603
  it 'is composed of connection key and serial that is kept up to date with each message ACK received' do
603
604
  connection.on(:connected) do
@@ -605,7 +606,7 @@ describe Ably::Realtime::Connection, :event_machine do
605
606
  expect(connection.key).to_not be_nil
606
607
  expect(connection.serial).to eql(expected_serial)
607
608
 
608
- client.channel('test').attach do |channel|
609
+ channel.attach do
609
610
  channel.publish('event', 'data') do
610
611
  expected_serial += 1 # attach message received
611
612
  expect(connection.serial).to eql(expected_serial)
@@ -671,9 +672,10 @@ describe Ably::Realtime::Connection, :event_machine do
671
672
  end
672
673
 
673
674
  connection.once(:failed) do
674
- recover_client = Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
675
+ recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
675
676
  recover_client.connection.on(:connected) do
676
- expect(recover_client.connection.key).to eql(previous_connection_key)
677
+ expect(recover_client.connection.key[/^\w{5,}-/, 0]).to_not be_nil
678
+ expect(recover_client.connection.key[/^\w{5,}-/, 0]).to eql(previous_connection_key[/^\w{5,}-/, 0])
677
679
  expect(recover_client.connection.id).to eql(previous_connection_id)
678
680
  stop_reactor
679
681
  end
@@ -686,7 +688,7 @@ describe Ably::Realtime::Connection, :event_machine do
686
688
  end
687
689
 
688
690
  connection.once(:failed) do
689
- recover_client = Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
691
+ recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
690
692
  recover_client.connection.on_resume do
691
693
  raise 'Should not call the resume callback'
692
694
  end
@@ -702,14 +704,15 @@ describe Ably::Realtime::Connection, :event_machine do
702
704
  let(:client_options) { default_options.merge(log_level: :none) }
703
705
 
704
706
  it 'recovers server-side queued messages' do
705
- channel.attach do |message|
707
+ channel.attach do
706
708
  connection.transition_state_machine! :failed
707
709
  end
708
710
 
709
711
  connection.on(:failed) do
710
712
  publishing_client_channel.publish('event', 'message') do
711
- recover_client = Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
712
- recover_client.channel(channel_name).attach do |recover_client_channel|
713
+ recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: client.connection.recovery_key))
714
+ recover_client_channel = recover_client.channel(channel_name)
715
+ recover_client_channel.attach do
713
716
  recover_client_channel.subscribe('event') do |message|
714
717
  expect(message.data).to eql('message')
715
718
  stop_reactor
@@ -738,9 +741,9 @@ describe Ably::Realtime::Connection, :event_machine do
738
741
  it 'emits a fatal error on the connection object, sets the #error_reason and disconnects' do
739
742
  connection.once(:error) do |error|
740
743
  expect(connection.state).to eq(:failed)
741
- expect(error.message).to match(/Invalid connection key/)
742
- expect(connection.error_reason.message).to match(/Invalid connection key/)
743
- expect(connection.error_reason.code).to eql(40006)
744
+ expect(error.message).to match(/Invalid connectionKey/i)
745
+ expect(connection.error_reason.message).to match(/Invalid connectionKey/i)
746
+ expect(connection.error_reason.code).to eql(80018)
744
747
  expect(connection.error_reason).to eql(error)
745
748
  stop_reactor
746
749
  end
@@ -748,13 +751,13 @@ describe Ably::Realtime::Connection, :event_machine do
748
751
  end
749
752
 
750
753
  context 'with expired (missing) value sent to server' do
751
- let(:client_options) { default_options.merge(recover: '0123456789abcdef:0', log_level: :fatal) }
754
+ let(:client_options) { default_options.merge(recover: 'wVIsgTHAB1UvXh7z-1991d8586:0', log_level: :fatal) }
752
755
 
753
756
  it 'emits an error on the connection object, sets the #error_reason, yet will connect anyway' do
754
757
  connection.once(:error) do |error|
755
758
  expect(connection.state).to eq(:connected)
756
- expect(error.message).to match(/Invalid connection key/i)
757
- expect(connection.error_reason.message).to match(/Invalid connection key/i)
759
+ expect(error.message).to match(/Unable to recover connection/i)
760
+ expect(connection.error_reason.message).to match(/Unable to recover connection/i)
758
761
  expect(connection.error_reason.code).to eql(80008)
759
762
  expect(connection.error_reason).to eql(error)
760
763
  stop_reactor
@@ -922,12 +925,12 @@ describe Ably::Realtime::Connection, :event_machine do
922
925
  expect(connection.__outgoing_message_queue__).to be_empty
923
926
  channel.publish 'test'
924
927
 
925
- EventMachine.add_timer(0.02) do
928
+ EventMachine.next_tick do
926
929
  expect(connection.__outgoing_message_queue__).to_not be_empty
927
930
  end
928
931
 
929
932
  connection.once(:connected) do
930
- EventMachine.add_timer(0.02) do
933
+ EventMachine.add_timer(0.1) do
931
934
  expect(connection.__outgoing_message_queue__).to be_empty
932
935
  stop_reactor
933
936
  end
@@ -940,6 +943,8 @@ describe Ably::Realtime::Connection, :event_machine do
940
943
  end
941
944
 
942
945
  context 'when connection enters the :suspended state' do
946
+ let(:client_options) { default_options.merge(:log_level => :fatal) }
947
+
943
948
  before do
944
949
  # Reconfigure client library retry periods so that client stays in suspended state
945
950
  stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
@@ -956,13 +961,21 @@ describe Ably::Realtime::Connection, :event_machine do
956
961
  stop_reactor
957
962
  end
958
963
 
964
+ close_connection_proc = Proc.new do
965
+ EventMachine.add_timer(0.001) do
966
+ if connection.transport.nil?
967
+ close_connection_proc.call
968
+ else
969
+ connection.transport.close_connection_after_writing
970
+ end
971
+ end
972
+ end
973
+
959
974
  # Keep disconnecting the websocket transport after it attempts reconnection
960
- connection.transport.close_connection_after_writing
961
975
  connection.on(:connecting) do
962
- EventMachine.add_timer(0.03) do
963
- connection.transport.close_connection_after_writing
964
- end
976
+ close_connection_proc.call
965
977
  end
978
+ close_connection_proc.call
966
979
  end
967
980
  end
968
981
  end
@@ -979,5 +992,107 @@ describe Ably::Realtime::Connection, :event_machine do
979
992
  end
980
993
  end
981
994
  end
995
+
996
+ context 'connection state change' do
997
+ it 'emits a ConnectionStateChange object' do
998
+ connection.on(:connected) do |connection_state_change|
999
+ expect(connection_state_change).to be_a(Ably::Models::ConnectionStateChange)
1000
+ stop_reactor
1001
+ end
1002
+ end
1003
+
1004
+ context 'ConnectionStateChange object' do
1005
+ it 'has current state' do
1006
+ connection.on(:connected) do |connection_state_change|
1007
+ expect(connection_state_change.current).to eq(:connected)
1008
+ stop_reactor
1009
+ end
1010
+ end
1011
+
1012
+ it 'has a previous state' do
1013
+ connection.on(:connected) do |connection_state_change|
1014
+ expect(connection_state_change.previous).to eq(:connecting)
1015
+ stop_reactor
1016
+ end
1017
+ end
1018
+
1019
+ it 'contains a private API protocol_message attribute that is used for special state change events', :api_private do
1020
+ connection.on(:connected) do |connection_state_change|
1021
+ expect(connection_state_change.protocol_message).to be_a(Ably::Models::ProtocolMessage)
1022
+ expect(connection_state_change.reason).to be_nil
1023
+ stop_reactor
1024
+ end
1025
+ end
1026
+
1027
+ it 'has an empty reason when there is no error' do
1028
+ connection.on(:closed) do |connection_state_change|
1029
+ expect(connection_state_change.reason).to be_nil
1030
+ stop_reactor
1031
+ end
1032
+ connection.connect do
1033
+ connection.close
1034
+ end
1035
+ end
1036
+
1037
+ context 'on failure' do
1038
+ let(:client_options) { default_options.merge(log_level: :none) }
1039
+
1040
+ it 'has a reason Error object when there is an error on the connection' do
1041
+ connection.on(:failed) do |connection_state_change|
1042
+ expect(connection_state_change.reason).to be_a(Ably::Exceptions::BaseAblyException)
1043
+ stop_reactor
1044
+ end
1045
+ connection.connect do
1046
+ error = Ably::Exceptions::ConnectionFailed.new('forced failure', 500, 50000)
1047
+ client.connection.manager.error_received_from_server error
1048
+ end
1049
+ end
1050
+ end
1051
+
1052
+ context 'retry_in' do
1053
+ let(:client_options) { default_options.merge(log_level: :error) }
1054
+
1055
+ it 'is nil when a retry is not required' do
1056
+ connection.on(:connected) do |connection_state_change|
1057
+ expect(connection_state_change.retry_in).to be_nil
1058
+ stop_reactor
1059
+ end
1060
+ end
1061
+
1062
+ it 'is 0 when first attempt to connect fails' do
1063
+ connection.once(:connecting) do
1064
+ connection.once(:disconnected) do |connection_state_change|
1065
+ expect(connection_state_change.retry_in).to eql(0)
1066
+ stop_reactor
1067
+ end
1068
+ EventMachine.add_timer(0.005) { connection.transport.unbind }
1069
+ end
1070
+ end
1071
+
1072
+ it 'is 0 when an immediate reconnect will occur' do
1073
+ connection.once(:connected) do
1074
+ connection.once(:disconnected) do |connection_state_change|
1075
+ expect(connection_state_change.retry_in).to eql(0)
1076
+ stop_reactor
1077
+ end
1078
+ connection.transport.unbind
1079
+ end
1080
+ end
1081
+
1082
+ it 'contains the next retry period when an immediate reconnect will not occur' do
1083
+ connection.once(:connected) do
1084
+ connection.once(:connecting) do
1085
+ connection.once(:disconnected) do |connection_state_change|
1086
+ expect(connection_state_change.retry_in).to be > 0
1087
+ stop_reactor
1088
+ end
1089
+ EventMachine.add_timer(0.005) { connection.transport.unbind }
1090
+ end
1091
+ connection.transport.unbind
1092
+ end
1093
+ end
1094
+ end
1095
+ end
1096
+ end
982
1097
  end
983
1098
  end