ably-rest 0.9.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/ably-rest.gemspec +2 -1
  3. data/lib/submodules/ably-ruby/.travis.yml +6 -4
  4. data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
  5. data/lib/submodules/ably-ruby/README.md +10 -0
  6. data/lib/submodules/ably-ruby/SPEC.md +1473 -852
  7. data/lib/submodules/ably-ruby/ably.gemspec +2 -1
  8. data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
  9. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
  10. data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
  11. data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
  12. data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
  13. data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
  14. data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
  15. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
  16. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
  17. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
  18. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
  19. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
  20. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
  21. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
  22. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
  23. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
  24. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
  25. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
  26. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
  27. data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
  28. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
  29. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
  30. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
  31. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
  32. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
  33. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
  34. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
  35. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
  36. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
  37. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
  38. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
  39. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
  40. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
  41. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
  42. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
  43. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
  44. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
  45. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
  46. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  47. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
  48. data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
  49. data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
  50. data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
  51. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
  52. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
  53. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
  54. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
  55. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
  56. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
  57. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
  58. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
  59. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
  60. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
  61. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
  62. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
  63. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
  64. data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
  65. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
  66. data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
  67. data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
  68. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
  69. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
  70. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
  71. data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
  72. data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
  73. data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
  74. data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
  75. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
  76. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
  77. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
  78. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
  79. data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
  80. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
  81. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
  82. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
  83. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
  84. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
  85. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
  86. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
  87. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
  88. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
  89. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
  90. metadata +7 -5
@@ -61,6 +61,7 @@ describe Ably::Realtime::Connection, :event_machine do
61
61
  context 'for renewable tokens' do
62
62
  context 'that are valid for the duration of the test' do
63
63
  context 'with valid pre authorized token expiring in the future' do
64
+ let(:client_options) { default_options.merge(use_token_auth: true) }
64
65
  it 'uses the existing token created by Auth' do
65
66
  client.auth.authorize(ttl: 300)
66
67
  expect(client.auth).to_not receive(:request_token)
@@ -137,7 +138,7 @@ describe Ably::Realtime::Connection, :event_machine do
137
138
  end
138
139
  let(:client_options) { default_options.merge(auth_callback: token_callback) }
139
140
 
140
- it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token' do
141
+ it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token (#RSA4b)' do
141
142
  started_at = Time.now.to_f
142
143
  connection.once(:disconnected) do
143
144
  connection.once(:disconnected) do |connection_state_change|
@@ -150,25 +151,33 @@ describe Ably::Realtime::Connection, :event_machine do
150
151
  end
151
152
 
152
153
  context 'when disconnected_retry_timeout is 0.5 seconds' do
153
- let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback, log_level: :error) }
154
+ let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback) }
154
155
 
155
156
  it 'renews the token on connect, and continues to attempt renew based on the retry schedule' do
156
- started_at = Time.now.to_f
157
157
  disconnect_count = 0
158
+ first_disconnected_at = nil
158
159
  connection.on(:disconnected) do |connection_state_change|
160
+ first_disconnected_at ||= begin
161
+ Time.now.to_f
162
+ end
159
163
  expect(connection_state_change.reason.code).to eql(40142) # token expired
160
- disconnect_count += 1
161
- if disconnect_count == 6
162
- expect(Time.now.to_f - started_at).to be > 4 * 0.5 # at least 4 0.5 second pauses should have happened
163
- expect(Time.now.to_f - started_at).to be < 9 # allow 1.5 seconds for each authentication cycle
164
+ if disconnect_count == 4 # 3 attempts to reconnect after initial
165
+ # First disconnect reattempts immediately as part of connect sequence
166
+ # Second disconnect reattempt immediately as part of disconnected retry sequence
167
+ # Following two take 0.5 second each
168
+ # Not convinced two immediate retries is necessary, but not worth engineering effort to fix given
169
+ # it's only one extra retry
170
+ expect(Time.now.to_f - first_disconnected_at).to be > 2 * 0.5
171
+ expect(Time.now.to_f - first_disconnected_at).to be < 9 # allow 1.5 seconds for each authentication cycle
164
172
  stop_reactor
165
173
  end
174
+ disconnect_count += 1
166
175
  end
167
176
  end
168
177
  end
169
178
 
170
179
  context 'using implicit token auth' do
171
- let(:client_options) { default_options.merge(use_token_auth: true, token_params: { ttl: ttl }) }
180
+ let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }) }
172
181
 
173
182
  before do
174
183
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', -10 # ensure client lib thinks token is still valid
@@ -178,14 +187,18 @@ describe Ably::Realtime::Connection, :event_machine do
178
187
  connection.once(:disconnected) do
179
188
  expect(client.rest_client.connection).to receive(:post).
180
189
  with(/requestToken$/, anything).
181
- exactly(:once).
190
+ exactly(:twice). # it retries an expired token request immediately
182
191
  and_call_original
183
192
 
184
193
  expect(client.rest_client).to_not receive(:fallback_connection)
185
194
  expect(client).to_not receive(:fallback_endpoint)
186
195
 
196
+ # Connection will go into :disconnected, then back to
197
+ # :connecting, then :disconnected again
187
198
  connection.once(:disconnected) do
188
- stop_reactor
199
+ connection.once(:disconnected) do
200
+ stop_reactor
201
+ end
189
202
  end
190
203
  end
191
204
  end
@@ -198,7 +211,7 @@ describe Ably::Realtime::Connection, :event_machine do
198
211
  let(:ttl) { 5 }
199
212
  let(:channel_name) { random_str }
200
213
  let(:channel) { client.channel(channel_name) }
201
- let(:client_options) { default_options.merge(use_token_auth: true, token_params: { ttl: ttl }) }
214
+ let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }) }
202
215
 
203
216
  context 'the server' do
204
217
  it 'disconnects the client, and the client automatically renews the token and then reconnects', em_timeout: 15 do
@@ -229,6 +242,7 @@ describe Ably::Realtime::Connection, :event_machine do
229
242
  end
230
243
 
231
244
  context 'connection state' do
245
+ let(:publish_count) { 10 }
232
246
  let(:ttl) { 4 }
233
247
  let(:auth_requests) { [] }
234
248
  let(:token_callback) do
@@ -243,13 +257,15 @@ describe Ably::Realtime::Connection, :event_machine do
243
257
  let(:publishing_channel) { publishing_client.channels.get(channel_name) }
244
258
  let(:messages_received) { [] }
245
259
 
246
- def publish_and_check_first_disconnect
247
- 10.times.each { |index| publishing_channel.publish('event', index.to_s) }
260
+ def publish_and_check_disconnect(options = {})
261
+ iteration = options.fetch(:iteration) { 1 }
262
+ total_expected = publish_count * iteration
263
+ publish_count.times.each { |index| publishing_channel.publish('event', (total_expected - publish_count + index).to_s) }
248
264
  channel.subscribe('event') do |message|
249
265
  messages_received << message.data.to_i
250
- if messages_received.count == 10
251
- expect(messages_received).to match(10.times)
252
- expect(auth_requests.count).to eql(2)
266
+ if messages_received.count == total_expected
267
+ expect(messages_received).to match(total_expected.times)
268
+ expect(auth_requests.count).to eql(iteration + 1)
253
269
  EventMachine.add_timer(1) do
254
270
  channel.unsubscribe 'event'
255
271
  yield
@@ -258,25 +274,19 @@ describe Ably::Realtime::Connection, :event_machine do
258
274
  end
259
275
  end
260
276
 
261
- def publish_and_check_second_disconnect
262
- 10.times.each { |index| publishing_channel.publish('event', (index + 10).to_s) }
263
- channel.subscribe('event') do |message|
264
- messages_received << message.data.to_i
265
- if messages_received.count == 20
266
- expect(messages_received).to match(20.times)
267
- expect(auth_requests.count).to eql(3)
268
- stop_reactor
269
- end
270
- end
271
- end
272
-
273
- it 'retains messages published when disconnected twice during authentication', em_timeout: 20 do
277
+ it 'retains messages published when disconnected three times during authentication', em_timeout: 30 do
274
278
  publishing_channel.attach do
275
279
  channel.attach do
276
280
  connection.once(:disconnected) do
277
- publish_and_check_first_disconnect do
281
+ publish_and_check_disconnect(iteration: 1) do
278
282
  connection.once(:disconnected) do
279
- publish_and_check_second_disconnect
283
+ publish_and_check_disconnect(iteration: 2) do
284
+ connection.once(:disconnected) do
285
+ publish_and_check_disconnect(iteration: 3) do
286
+ stop_reactor
287
+ end
288
+ end
289
+ end
280
290
  end
281
291
  end
282
292
  end
@@ -322,13 +332,14 @@ describe Ably::Realtime::Connection, :event_machine do
322
332
  let!(:expired_token_details) do
323
333
  # Request a token synchronously
324
334
  token_client = auto_close Ably::Realtime::Client.new(default_options)
325
- token_client.auth.request_token_sync(ttl: 0.01)
335
+ token_client.auth.request_token_sync(ttl: ttl)
326
336
  end
327
337
 
328
338
  context 'opening a new connection' do
329
339
  let(:client_options) { default_options.merge(key: nil, token: expired_token_details.token, log_level: :none) }
340
+ let(:ttl) { 0.01 }
330
341
 
331
- it 'transitions state to failed', em_timeout: 10 do
342
+ it 'transitions state to failed (#RSA4a)', em_timeout: 10 do
332
343
  EventMachine.add_timer(1) do # wait for token to expire
333
344
  expect(expired_token_details).to be_expired
334
345
  connection.once(:connected) { raise 'Connection should never connect as token has expired' }
@@ -341,7 +352,17 @@ describe Ably::Realtime::Connection, :event_machine do
341
352
  end
342
353
 
343
354
  context 'when connected' do
344
- skip 'transitions state to failed'
355
+ let(:client_options) { default_options.merge(key: nil, token: expired_token_details.token, log_level: :none) }
356
+ let(:ttl) { 4 }
357
+
358
+ it 'transitions state to failed (#RSA4a)' do
359
+ connection.once(:connected) do
360
+ connection.once(:failed) do
361
+ expect(client.connection.error_reason.code).to eql(40142)
362
+ stop_reactor
363
+ end
364
+ end
365
+ end
345
366
  end
346
367
  end
347
368
  end
@@ -579,11 +600,13 @@ describe Ably::Realtime::Connection, :event_machine do
579
600
  end
580
601
 
581
602
  context 'when closing' do
582
- it 'raises an exception before the connection is closed' do
603
+ it 'fails the deferrable before the connection is closed' do
583
604
  connection.connect do
584
605
  connection.once(:closing) do
585
- expect { connection.connect }.to raise_error Ably::Exceptions::InvalidStateChange
586
- stop_reactor
606
+ connection.connect.errback do |error|
607
+ expect(error).to be_a(Ably::Exceptions::InvalidStateChange)
608
+ stop_reactor
609
+ end
587
610
  end
588
611
  connection.close
589
612
  end
@@ -605,26 +628,35 @@ describe Ably::Realtime::Connection, :event_machine do
605
628
  it 'the sent message msgSerial is 0 but the connection serial remains at -1' do
606
629
  channel.attach do
607
630
  connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
608
- connection.__outgoing_protocol_msgbus__.unsubscribe
609
- expect(protocol_message['msgSerial']).to eql(0)
610
- expect(connection.serial).to eql(-1)
611
- stop_reactor
631
+ if protocol_message.action == :message
632
+ connection.__outgoing_protocol_msgbus__.unsubscribe
633
+ expect(protocol_message['msgSerial']).to eql(0)
634
+ expect(connection.serial).to eql(-1)
635
+ stop_reactor
636
+ end
612
637
  end
613
638
  channel.publish('event', 'data')
614
639
  end
615
640
  end
616
641
  end
617
642
 
618
- it 'is set to 0 when a message sent ACK is received' do
619
- channel.publish('event', 'data') do
643
+ it 'is set to 0 when a message is received back' do
644
+ channel.publish('event', 'data')
645
+ channel.subscribe do
620
646
  expect(connection.serial).to eql(0)
621
647
  stop_reactor
622
648
  end
623
649
  end
624
650
 
625
- it 'is set to 1 when the second message sent ACK is received' do
651
+ it 'is set to 1 when the second message is received' do
626
652
  channel.publish('event', 'data') do
627
- channel.publish('event', 'data') do
653
+ channel.publish('event', 'data')
654
+ end
655
+
656
+ messages = []
657
+ channel.subscribe do |message|
658
+ messages << message
659
+ if messages.length == 2
628
660
  expect(connection.serial).to eql(1)
629
661
  stop_reactor
630
662
  end
@@ -671,7 +703,6 @@ describe Ably::Realtime::Connection, :event_machine do
671
703
 
672
704
  def log_connection_changes
673
705
  connection.on(:closing) { events[:closing_emitted] = true }
674
- connection.on(:error) { events[:error_emitted] = true }
675
706
 
676
707
  connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
677
708
  events[:closed_message_from_server_received] = true if protocol_message.action == :closed
@@ -684,7 +715,6 @@ describe Ably::Realtime::Connection, :event_machine do
684
715
  expect(connection.state).to eq(:closed)
685
716
 
686
717
  EventMachine.add_timer(1) do # allow for all subscribers on incoming message bes
687
- expect(events[:error_emitted]).to_not eql(true)
688
718
  expect(events[:closed_message_from_server_received]).to_not eql(true)
689
719
  expect(events[:closing_emitted]).to eql(true)
690
720
  stop_reactor
@@ -701,7 +731,6 @@ describe Ably::Realtime::Connection, :event_machine do
701
731
  connection.on(:connected) do
702
732
  connection.on(:closed) do
703
733
  EventMachine.add_timer(1) do # allow for all subscribers on incoming message bus
704
- expect(events[:error_emitted]).to_not eql(true)
705
734
  expect(events[:closed_message_from_server_received]).to eql(true)
706
735
  expect(events[:closing_emitted]).to eql(true)
707
736
  stop_reactor
@@ -732,14 +761,13 @@ describe Ably::Realtime::Connection, :event_machine do
732
761
  connection.on(:closed) do
733
762
  expect(Time.now - close_requested_at).to be >= custom_timeout
734
763
  expect(connection.state).to eq(:closed)
735
- expect(events[:error_emitted]).to_not eql(true)
736
764
  expect(events[:closed_message_from_server_received]).to_not eql(true)
737
765
  expect(events[:closing_emitted]).to eql(true)
738
766
  stop_reactor
739
767
  end
740
768
 
741
769
  log_connection_changes
742
- connection.close
770
+ EventMachine.next_tick { connection.close }
743
771
  end
744
772
  end
745
773
  end
@@ -748,26 +776,117 @@ describe Ably::Realtime::Connection, :event_machine do
748
776
  end
749
777
 
750
778
  context '#ping' do
751
- it 'echoes a heart beat' do
779
+ it 'echoes a heart beat (#RTN13a)' do
752
780
  connection.on(:connected) do
753
781
  connection.ping do |time_elapsed|
754
782
  expect(time_elapsed).to be > 0
783
+ expect(time_elapsed).to be < 3
755
784
  stop_reactor
756
785
  end
757
786
  end
758
787
  end
759
788
 
760
- context 'when not connected' do
761
- it 'raises an exception' do
762
- expect { connection.ping }.to raise_error RuntimeError, /Cannot send a ping when connection/
763
- stop_reactor
789
+ it 'sends a unique ID in each protocol message (#RTN13e)' do
790
+ connection.on(:connected) do
791
+ heartbeat_ids = []
792
+ pings_complete = []
793
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
794
+ if protocol_message.action == :heartbeat
795
+ heartbeat_ids << protocol_message.id
796
+ end
797
+ end
798
+
799
+ ping_block = Proc.new do
800
+ pings_complete << true
801
+ if pings_complete.length == 3
802
+ expect(heartbeat_ids.uniq.length).to eql(3)
803
+ stop_reactor
804
+ end
805
+ end
806
+
807
+ connection.ping(&ping_block)
808
+ connection.ping(&ping_block)
809
+ connection.ping(&ping_block)
810
+ end
811
+ end
812
+
813
+ it 'waits until the connection becomes CONNECTED when in the CONNETING state' do
814
+ connection.once(:connecting) do
815
+ connection.ping do |time_elapsed|
816
+ expect(connection.state).to eq(:connected)
817
+ stop_reactor
818
+ end
819
+ end
820
+ end
821
+
822
+ context 'with incompatible states' do
823
+ let(:client_options) { default_options.merge(log_level: :none) }
824
+
825
+ context 'when not connected' do
826
+ it 'fails the deferrable (#RTN13b)' do
827
+ connection.ping.errback do |error|
828
+ expect(error.message).to match(/Cannot send a ping when.*initialized/i)
829
+ stop_reactor
830
+ end
831
+ end
832
+ end
833
+
834
+ context 'when suspended' do
835
+ it 'fails the deferrable (#RTN13b)' do
836
+ connection.once(:connected) do
837
+ connection.transition_state_machine! :suspended
838
+ connection.ping.errback do |error|
839
+ expect(error.message).to match(/Cannot send a ping when.*suspended/i)
840
+ stop_reactor
841
+ end
842
+ end
843
+ end
844
+ end
845
+
846
+ context 'when failed' do
847
+ it 'fails the deferrable (#RTN13b)' do
848
+ connection.once(:connected) do
849
+ connection.transition_state_machine! :failed
850
+ connection.ping.errback do |error|
851
+ expect(error.message).to match(/Cannot send a ping when.*failed/i)
852
+ stop_reactor
853
+ end
854
+ end
855
+ end
856
+ end
857
+
858
+ context 'when closed' do
859
+ it 'fails the deferrable (#RTN13b)' do
860
+ connection.once(:connected) do
861
+ connection.close
862
+ connection.once(:closed) do
863
+ connection.ping.errback do |error|
864
+ expect(error.message).to match(/Cannot send a ping when.*closed/i)
865
+ stop_reactor
866
+ end
867
+ end
868
+ end
869
+ end
870
+ end
871
+
872
+ context 'when it becomes closed' do
873
+ it 'fails the deferrable (#RTN13b)' do
874
+ connection.once(:connected) do
875
+ connection.ping.errback do |error|
876
+ expect(error.message).to match(/Ping failed as connection has changed state to.*closing/i)
877
+ stop_reactor
878
+ end
879
+ connection.close
880
+ end
881
+ end
764
882
  end
765
883
  end
766
884
 
767
885
  context 'with a success block that raises an exception' do
768
886
  it 'catches the exception and logs the error' do
769
887
  connection.on(:connected) do
770
- expect(connection.logger).to receive(:error).with(/Forced exception/) do
888
+ expect(connection.logger).to receive(:error) do |*args, &block|
889
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Forced exception/)
771
890
  stop_reactor
772
891
  end
773
892
  connection.ping { raise 'Forced exception' }
@@ -778,13 +897,22 @@ describe Ably::Realtime::Connection, :event_machine do
778
897
  context 'when ping times out' do
779
898
  let(:client_options) { default_options.merge(log_level: :error) }
780
899
 
781
- it 'logs a warning' do
900
+ it 'fails the deferrable logs a warning (#RTN13a, #RTN13c)' do
901
+ message_logged = false
782
902
  connection.once(:connected) do
783
903
  allow(connection).to receive(:defaults).and_return(connection.defaults.merge(realtime_request_timeout: 0.0001))
784
- expect(connection.logger).to receive(:warn).with(/Ping timed out/) do
785
- stop_reactor
904
+ expect(connection.logger).to receive(:warn) do |*args, &block|
905
+ expect(block.call).to match(/Ping timed out/)
906
+ message_logged = true
907
+ end
908
+ connection.ping.errback do |error|
909
+ EventMachine.add_timer(0.1) do
910
+ expect(error.message).to match(/Ping timed out/)
911
+ expect(error.code).to eql(50003)
912
+ expect(message_logged).to be_truthy
913
+ stop_reactor
914
+ end
786
915
  end
787
- connection.ping
788
916
  end
789
917
  end
790
918
 
@@ -800,6 +928,107 @@ describe Ably::Realtime::Connection, :event_machine do
800
928
  end
801
929
  end
802
930
 
931
+ context 'Heartbeats (#RTN23)' do
932
+ context 'heartbeat interval' do
933
+ context 'when reduced artificially' do
934
+ let(:protocol_message_attributes) do
935
+ {
936
+ action: Ably::Models::ProtocolMessage::ACTION.Connected.to_i,
937
+ connection_serial: 55,
938
+ connection_details: {
939
+ max_idle_interval: 2 * 1000
940
+ }
941
+ }
942
+ end
943
+ let(:client_options) { default_options.merge(realtime_request_timeout: 3) }
944
+ let(:expected_heartbeat_interval) { 5 }
945
+
946
+ it 'is the sum of the max_idle_interval and realtime_request_timeout (#RTN23a)' do
947
+ connection.once(:connected) do
948
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
949
+ EventMachine.next_tick do
950
+ expect(connection.heartbeat_interval).to eql(expected_heartbeat_interval)
951
+ stop_reactor
952
+ end
953
+ end
954
+ end
955
+
956
+ it 'disconnects the transport if no heartbeat received since connected (#RTN23a)' do
957
+ connection.once(:connected) do
958
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
959
+ last_received_at = Time.now
960
+ connection.once(:disconnected) do
961
+ expect(Time.now.to_i - last_received_at.to_i).to be_within(2).of(expected_heartbeat_interval)
962
+ stop_reactor
963
+ end
964
+ end
965
+ end
966
+
967
+ it 'disconnects the transport if no heartbeat received since last event received (#RTN23a)' do
968
+ connection.once(:connected) do
969
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
970
+ last_received_at = Time.now
971
+ EventMachine.add_timer(3) { client.channels.get('foo').attach }
972
+ connection.once(:disconnected) do
973
+ expect(Time.now.to_i - last_received_at.to_i).to be_within(2).of(expected_heartbeat_interval + 3)
974
+ stop_reactor
975
+ end
976
+ end
977
+ end
978
+ end
979
+ end
980
+
981
+ context 'transport-level heartbeats are supported in the websocket transport' do
982
+ it 'provides the heartbeats argument in the websocket connection params (#RTN23b)' do
983
+ expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
984
+ uri = URI.parse(url)
985
+ expect(CGI::parse(uri.query)['heartbeats'][0]).to eql('false')
986
+ stop_reactor
987
+ end
988
+ client
989
+ end
990
+
991
+ it 'receives websocket heartbeat messages (#RTN23b) [slow test as need to wait for heartbeat]', em_timeout: 45 do
992
+ skip "Heartbeats param is missing from realtime implementation, see https://github.com/ably/realtime/issues/656"
993
+
994
+ connection.once(:connected) do
995
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
996
+ if protocol_message.action == :heartbeat
997
+ expect(protocol_message.attributes[:source]).to eql('websocket')
998
+ expect(connection.time_since_connection_confirmed_alive?).to be_within(1).of(0)
999
+ stop_reactor
1000
+ end
1001
+ end
1002
+ end
1003
+ end
1004
+ end
1005
+
1006
+ context 'with websocket heartbeats disabled (undocumented)' do
1007
+ let(:client_options) { default_options.merge(websocket_heartbeats_disabled: true) }
1008
+
1009
+ it 'does not provide the heartbeats argument in the websocket connection params (#RTN23b)' do
1010
+ expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1011
+ uri = URI.parse(url)
1012
+ expect(CGI::parse(uri.query)['heartbeats'][0]).to be_nil
1013
+ stop_reactor
1014
+ end
1015
+ client
1016
+ end
1017
+
1018
+ it 'receives websocket protocol messages (#RTN23b) [slow test as need to wait for heartbeat]', em_timeout: 45 do
1019
+ connection.once(:connected) do
1020
+ connection.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
1021
+ if protocol_message.action == :heartbeat
1022
+ expect(protocol_message.attributes[:source]).to_not eql('websocket')
1023
+ expect(connection.time_since_connection_confirmed_alive?).to be_within(1).of(0)
1024
+ stop_reactor
1025
+ end
1026
+ end
1027
+ end
1028
+ end
1029
+ end
1030
+ end
1031
+
803
1032
  context '#details' do
804
1033
  let(:connection) { client.connection }
805
1034
 
@@ -810,7 +1039,7 @@ describe Ably::Realtime::Connection, :event_machine do
810
1039
  end
811
1040
  end
812
1041
 
813
- it 'contains the ConnectionDetails object once connected' do
1042
+ it 'contains the ConnectionDetails object once connected (#RTN21)' do
814
1043
  connection.on(:connected) do
815
1044
  expect(connection.details).to be_a(Ably::Models::ConnectionDetails)
816
1045
  expect(connection.details.connection_key).to_not be_nil
@@ -819,7 +1048,7 @@ describe Ably::Realtime::Connection, :event_machine do
819
1048
  end
820
1049
  end
821
1050
 
822
- it 'contains the new ConnectionDetails object once a subsequent connection is created' do
1051
+ it 'contains the new ConnectionDetails object once a subsequent connection is created (#RTN21)' do
823
1052
  connection.once(:connected) do
824
1053
  expect(connection.details.connection_key).to_not be_nil
825
1054
  old_key = connection.details.connection_key
@@ -834,13 +1063,13 @@ describe Ably::Realtime::Connection, :event_machine do
834
1063
  end
835
1064
  end
836
1065
 
837
- context 'with a different connection_state_ttl' do
1066
+ context 'with a different default connection_state_ttl' do
838
1067
  before do
839
1068
  old_defaults = Ably::Realtime::Connection::DEFAULTS
840
1069
  stub_const 'Ably::Realtime::Connection::DEFAULTS', old_defaults.merge(connection_state_ttl: 15)
841
1070
  end
842
1071
 
843
- it 'updates the private Connection#connection_state_ttl' do
1072
+ it 'updates the private Connection#connection_state_ttl when received from Ably in ConnectionDetails' do
844
1073
  expect(connection.connection_state_ttl).to eql(15)
845
1074
 
846
1075
  connection.once(:connected) do
@@ -884,11 +1113,16 @@ describe Ably::Realtime::Connection, :event_machine do
884
1113
  expect(connection.serial).to eql(expected_serial)
885
1114
 
886
1115
  channel.attach do
887
- channel.publish('event', 'data') do
1116
+ channel.publish('event', 'data')
1117
+ channel.subscribe do
1118
+ channel.unsubscribe
1119
+
888
1120
  expected_serial += 1 # attach message received
889
1121
  expect(connection.serial).to eql(expected_serial)
890
1122
 
891
- channel.publish('event', 'data') do
1123
+ channel.publish('event', 'data')
1124
+ channel.subscribe do
1125
+ channel.unsubscribe
892
1126
  expected_serial += 1 # attach message received
893
1127
  expect(connection.serial).to eql(expected_serial)
894
1128
 
@@ -942,7 +1176,7 @@ describe Ably::Realtime::Connection, :event_machine do
942
1176
  context "opening a new connection using a recently disconnected connection's #recovery_key" do
943
1177
  context 'connection#id and connection#key after recovery' do
944
1178
  it 'remains the same for id and party for key' do
945
- connection_key_consistent_part_regex = /.*?!(\w{5,})-/
1179
+ connection_key_consistent_part_regex = /.*?!([\w-]{5,})-\w+/
946
1180
  previous_connection_id = nil
947
1181
  previous_connection_key = nil
948
1182
 
@@ -1024,13 +1258,13 @@ describe Ably::Realtime::Connection, :event_machine do
1024
1258
  context 'with invalid formatted value sent to server' do
1025
1259
  let(:client_options) { default_options.merge(recover: 'not-a-valid-connection-key:1', log_level: :none) }
1026
1260
 
1027
- it 'emits a fatal error on the connection object, sets the #error_reason and disconnects' do
1028
- connection.once(:error) do |error|
1261
+ it 'sets the #error_reason and moves the connection to FAILED' do
1262
+ connection.once(:failed) do |state_change|
1029
1263
  expect(connection.state).to eq(:failed)
1030
- expect(error.message).to match(/Invalid connectionKey/i)
1264
+ expect(state_change.reason.message).to match(/Invalid connectionKey/i)
1031
1265
  expect(connection.error_reason.message).to match(/Invalid connectionKey/i)
1032
1266
  expect(connection.error_reason.code).to eql(80018)
1033
- expect(connection.error_reason).to eql(error)
1267
+ expect(connection.error_reason).to eql(state_change.reason)
1034
1268
  stop_reactor
1035
1269
  end
1036
1270
  end
@@ -1039,13 +1273,13 @@ describe Ably::Realtime::Connection, :event_machine do
1039
1273
  context 'with expired (missing) value sent to server' do
1040
1274
  let(:client_options) { default_options.merge(recover: 'wVIsgTHAB1UvXh7z-1991d8586:0', log_level: :fatal) }
1041
1275
 
1042
- it 'emits an error on the connection object, sets the #error_reason, yet will connect anyway' do
1043
- connection.once(:error) do |error|
1276
+ it 'connects but sets the error reason and includes the reason in the state change' do
1277
+ connection.once(:connected) do |state_change|
1044
1278
  expect(connection.state).to eq(:connected)
1045
- expect(error.message).to match(/Unable to recover connection/i)
1279
+ expect(state_change.reason.message).to match(/Unable to recover connection/i)
1046
1280
  expect(connection.error_reason.message).to match(/Unable to recover connection/i)
1047
1281
  expect(connection.error_reason.code).to eql(80008)
1048
- expect(connection.error_reason).to eql(error)
1282
+ expect(connection.error_reason).to eql(state_change.reason)
1049
1283
  stop_reactor
1050
1284
  end
1051
1285
  end
@@ -1076,22 +1310,22 @@ describe Ably::Realtime::Connection, :event_machine do
1076
1310
  end
1077
1311
 
1078
1312
  context 'when a state transition is unsupported' do
1079
- let(:client_options) { default_options.merge(log_level: :none) } # silence FATAL errors
1313
+ let(:client_options) { default_options.merge(log_level: :fatal) } # silence FATAL errors
1080
1314
 
1081
- it 'emits a InvalidStateChange' do
1315
+ it 'logs the invalid state change as fatal' do
1082
1316
  connection.connect do
1083
1317
  connection.transition_state_machine :initialized
1318
+ EventMachine.add_timer(1) { stop_reactor }
1084
1319
  end
1085
1320
 
1086
- connection.on(:error) do |error|
1087
- expect(error).to be_a(Ably::Exceptions::InvalidStateChange)
1088
- stop_reactor
1321
+ expect(client.logger).to receive(:fatal).at_least(:once) do |*args, &block|
1322
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Unable to transition/)
1089
1323
  end
1090
1324
  end
1091
1325
  end
1092
1326
 
1093
1327
  context 'protocol failure' do
1094
- let(:client_options) { default_options.merge(protocol: :json) }
1328
+ let(:client_options) { default_options.merge(protocol: :json, log_level: :none) }
1095
1329
 
1096
1330
  context 'receiving an invalid ProtocolMessage' do
1097
1331
  it 'emits an error on the connection and logs a fatal error message' do
@@ -1099,9 +1333,11 @@ describe Ably::Realtime::Connection, :event_machine do
1099
1333
  connection.transport.send(:driver).emit 'message', OpenStruct.new(data: { action: 500 }.to_json)
1100
1334
  end
1101
1335
 
1102
- expect(client.logger).to receive(:fatal).with(/Invalid Protocol Message/)
1103
- connection.on(:error) do |error|
1104
- expect(error.message).to match(/Invalid Protocol Message/)
1336
+ expect(client.logger).to receive(:fatal).at_least(:once) do |*args, &block|
1337
+ expect(args.concat([block ? block.call : nil]).join(',')).to match(/Invalid Protocol Message/)
1338
+ end
1339
+ connection.on(:failed) do |state_change|
1340
+ expect(state_change.reason.message).to match(/Invalid Protocol Message/)
1105
1341
  stop_reactor
1106
1342
  end
1107
1343
  end
@@ -1238,11 +1474,13 @@ describe Ably::Realtime::Connection, :event_machine do
1238
1474
  )
1239
1475
  end
1240
1476
 
1241
- it 'detaches the channels and prevents publishing of messages on those channels' do
1477
+ it 'moves the channels into the suspended state and prevents publishing of messages on those channels' do
1242
1478
  channel.attach do
1243
- channel.once(:detached) do
1244
- expect { channel.publish 'test' }.to raise_error(Ably::Exceptions::ChannelInactive)
1245
- stop_reactor
1479
+ channel.once(:suspended) do
1480
+ channel.publish('test').errback do |error|
1481
+ expect(error).to be_a(Ably::Exceptions::MessageQueueingDisabled)
1482
+ stop_reactor
1483
+ end
1246
1484
  end
1247
1485
 
1248
1486
  close_connection_proc = Proc.new do
@@ -1270,8 +1508,10 @@ describe Ably::Realtime::Connection, :event_machine do
1270
1508
  it 'sets all channels to failed and prevents publishing of messages on those channels' do
1271
1509
  channel.attach
1272
1510
  channel.once(:failed) do
1273
- expect { channel.publish 'test' }.to raise_error(Ably::Exceptions::ChannelInactive)
1274
- stop_reactor
1511
+ channel.publish('test').errback do |error|
1512
+ expect(error).to be_a(Ably::Exceptions::ChannelInactive)
1513
+ stop_reactor
1514
+ end
1275
1515
  end
1276
1516
  end
1277
1517
  end
@@ -1304,6 +1544,7 @@ describe Ably::Realtime::Connection, :event_machine do
1304
1544
  context 'ConnectionStateChange object' do
1305
1545
  it 'has current state' do
1306
1546
  connection.on(:connected) do |connection_state_change|
1547
+ expect(connection_state_change.current).to be_a(Ably::Realtime::Connection::STATE)
1307
1548
  expect(connection_state_change.current).to eq(:connected)
1308
1549
  stop_reactor
1309
1550
  end
@@ -1311,11 +1552,20 @@ describe Ably::Realtime::Connection, :event_machine do
1311
1552
 
1312
1553
  it 'has a previous state' do
1313
1554
  connection.on(:connected) do |connection_state_change|
1555
+ expect(connection_state_change.previous).to be_a(Ably::Realtime::Connection::STATE)
1314
1556
  expect(connection_state_change.previous).to eq(:connecting)
1315
1557
  stop_reactor
1316
1558
  end
1317
1559
  end
1318
1560
 
1561
+ it 'has the event that generated the state change (#TH5)' do
1562
+ connection.on(:connected) do |connection_state_change|
1563
+ expect(connection_state_change.event).to be_a(Ably::Realtime::Connection::EVENT)
1564
+ expect(connection_state_change.event).to eq(:connected)
1565
+ stop_reactor
1566
+ end
1567
+ end
1568
+
1319
1569
  it 'contains a private API protocol_message attribute that is used for special state change events', :api_private do
1320
1570
  connection.on(:connected) do |connection_state_change|
1321
1571
  expect(connection_state_change.protocol_message).to be_a(Ably::Models::ProtocolMessage)
@@ -1393,29 +1643,118 @@ describe Ably::Realtime::Connection, :event_machine do
1393
1643
  end
1394
1644
  end
1395
1645
  end
1646
+
1647
+ context 'whilst CONNECTED' do
1648
+ context 'when a CONNECTED message is received (#RTN24)' do
1649
+ let(:connection_key) { random_str(32) }
1650
+ let(:protocol_message_attributes) do
1651
+ {
1652
+ action: Ably::Models::ProtocolMessage::ACTION.Connected.to_i,
1653
+ connection_serial: 55,
1654
+ connection_details: {
1655
+ client_id: 'bob',
1656
+ connection_key: connection_key,
1657
+ connection_state_ttl: 33 * 1000,
1658
+ max_frame_size: 555,
1659
+ max_inbound_rate: 999,
1660
+ max_message_size: 1310,
1661
+ server_id: 'us-east-1-a.foo.com',
1662
+ max_idle_interval: 4 * 1000
1663
+ }
1664
+ }
1665
+ end
1666
+
1667
+ it 'emits an UPDATE event' do
1668
+ connection.once(:connected) do
1669
+ connection.once(:update) do |connection_state_change|
1670
+ expect(connection_state_change.current).to eq(:connected)
1671
+ expect(connection_state_change.previous).to eq(:connected)
1672
+ expect(connection_state_change.retry_in).to be_nil
1673
+ expect(connection_state_change.reason).to be_nil
1674
+ expect(connection.state).to eq(:connected)
1675
+ stop_reactor
1676
+ end
1677
+
1678
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
1679
+ end
1680
+ end
1681
+
1682
+ it 'updates the ConnectionDetail and Connection attributes (#RTC8a1)' do
1683
+ connection.once(:connected) do
1684
+ expect(client.auth.client_id).to eql('*')
1685
+
1686
+ connection.once(:update) do |connection_state_change|
1687
+ expect(client.auth.client_id).to eql('bob')
1688
+ expect(connection.key).to eql(connection_key)
1689
+ expect(connection.serial).to eql(55)
1690
+ expect(connection.connection_state_ttl).to eql(33)
1691
+
1692
+ expect(connection.details.client_id).to eql('bob')
1693
+ expect(connection.details.connection_key).to eql(connection_key)
1694
+ expect(connection.details.connection_state_ttl).to eql(33)
1695
+ expect(connection.details.max_frame_size).to eql(555)
1696
+ expect(connection.details.max_inbound_rate).to eql(999)
1697
+ expect(connection.details.max_message_size).to eql(1310)
1698
+ expect(connection.details.server_id).to eql('us-east-1-a.foo.com')
1699
+ expect(connection.details.max_idle_interval).to eql(4)
1700
+ stop_reactor
1701
+ end
1702
+
1703
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
1704
+ end
1705
+ end
1706
+ end
1707
+
1708
+ context 'when a CONNECTED message with an error is received' do
1709
+ let(:protocol_message_attributes) do
1710
+ {
1711
+ action: Ably::Models::ProtocolMessage::ACTION.Connected.to_i,
1712
+ connection_serial: 22,
1713
+ error: { code: 50000, message: 'Internal failure' },
1714
+ }
1715
+ end
1716
+
1717
+ it 'emits an UPDATE event' do
1718
+ connection.once(:connected) do
1719
+ connection.on(:update) do |connection_state_change|
1720
+ expect(connection_state_change.current).to eq(:connected)
1721
+ expect(connection_state_change.previous).to eq(:connected)
1722
+ expect(connection_state_change.retry_in).to be_nil
1723
+ expect(connection_state_change.reason).to be_a(Ably::Models::ErrorInfo)
1724
+ expect(connection_state_change.reason.code).to eql(50000)
1725
+ expect(connection_state_change.reason.message).to match(/Internal failure/)
1726
+ expect(connection.state).to eq(:connected)
1727
+ stop_reactor
1728
+ end
1729
+
1730
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(protocol_message_attributes)
1731
+ end
1732
+ end
1733
+ end
1734
+ end
1396
1735
  end
1397
1736
 
1398
1737
  context 'version params' do
1399
- it 'sends the protocol version param v' do
1738
+ it 'sends the protocol version param v (#G4, #RTN2f)' do
1400
1739
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1401
1740
  uri = URI.parse(url)
1402
- expect(CGI::parse(uri.query)['v'][0]).to eql(Ably::PROTOCOL_VERSION)
1741
+ expect(CGI::parse(uri.query)['v'][0]).to eql('1.0')
1403
1742
  stop_reactor
1404
1743
  end
1405
1744
  client
1406
1745
  end
1407
1746
 
1408
- it 'sends the lib version param lib' do
1747
+ it 'sends the lib version param lib (#RTN2g)' do
1409
1748
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1410
1749
  uri = URI.parse(url)
1411
- expect(CGI::parse(uri.query)['lib'][0]).to eql("ruby-#{Ably::VERSION}")
1750
+ expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-1\.0\.\d+(-[\w\.]+)?+$/)
1412
1751
  stop_reactor
1413
1752
  end
1414
1753
  client
1415
1754
  end
1416
1755
 
1417
1756
  context 'with variant' do
1418
- let(:variant) { 'foo ' }
1757
+ let(:variant) { 'foo' }
1419
1758
 
1420
1759
  before do
1421
1760
  Ably.lib_variant = variant
@@ -1425,10 +1764,10 @@ describe Ably::Realtime::Connection, :event_machine do
1425
1764
  Ably.lib_variant = nil
1426
1765
  end
1427
1766
 
1428
- it 'sends the lib version param lib with the variant' do
1767
+ it 'sends the lib version param lib with the variant (#RTN2g + #RSC7b)' do
1429
1768
  expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
1430
1769
  uri = URI.parse(url)
1431
- expect(CGI::parse(uri.query)['lib'][0]).to eql("ruby-#{variant}-#{Ably::VERSION}")
1770
+ expect(CGI::parse(uri.query)['lib'][0]).to match(/^ruby-#{variant}-1\.0\.\d+(-[\w\.]+)?$/)
1432
1771
  stop_reactor
1433
1772
  end
1434
1773
  client