ably 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +42 -48
  4. data/SPEC.md +1099 -640
  5. data/ably.gemspec +10 -4
  6. data/lib/ably/auth.rb +155 -47
  7. data/lib/ably/exceptions.rb +2 -0
  8. data/lib/ably/models/channel_state_change.rb +2 -3
  9. data/lib/ably/models/connection_details.rb +54 -0
  10. data/lib/ably/models/protocol_message.rb +14 -4
  11. data/lib/ably/models/token_details.rb +13 -7
  12. data/lib/ably/models/token_request.rb +1 -2
  13. data/lib/ably/modules/ably.rb +3 -2
  14. data/lib/ably/modules/message_emitter.rb +1 -3
  15. data/lib/ably/modules/state_emitter.rb +2 -2
  16. data/lib/ably/realtime/auth.rb +6 -0
  17. data/lib/ably/realtime/channel/channel_manager.rb +2 -0
  18. data/lib/ably/realtime/channel.rb +15 -4
  19. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  20. data/lib/ably/realtime/client.rb +10 -3
  21. data/lib/ably/realtime/connection/connection_manager.rb +58 -54
  22. data/lib/ably/realtime/connection.rb +62 -6
  23. data/lib/ably/realtime/presence.rb +18 -5
  24. data/lib/ably/rest/channel.rb +9 -1
  25. data/lib/ably/rest/client.rb +32 -14
  26. data/lib/ably/rest/presence.rb +1 -1
  27. data/lib/ably/version.rb +1 -1
  28. data/lib/ably.rb +2 -0
  29. data/spec/acceptance/realtime/auth_spec.rb +251 -11
  30. data/spec/acceptance/realtime/channel_history_spec.rb +12 -2
  31. data/spec/acceptance/realtime/channel_spec.rb +316 -24
  32. data/spec/acceptance/realtime/client_spec.rb +93 -1
  33. data/spec/acceptance/realtime/connection_failures_spec.rb +177 -86
  34. data/spec/acceptance/realtime/connection_spec.rb +284 -60
  35. data/spec/acceptance/realtime/message_spec.rb +45 -6
  36. data/spec/acceptance/realtime/presence_history_spec.rb +4 -0
  37. data/spec/acceptance/realtime/presence_spec.rb +181 -49
  38. data/spec/acceptance/realtime/time_spec.rb +13 -0
  39. data/spec/acceptance/rest/auth_spec.rb +222 -4
  40. data/spec/acceptance/rest/channel_spec.rb +132 -1
  41. data/spec/acceptance/rest/client_spec.rb +129 -28
  42. data/spec/acceptance/rest/presence_spec.rb +7 -7
  43. data/spec/acceptance/rest/time_spec.rb +10 -0
  44. data/spec/shared/client_initializer_behaviour.rb +41 -17
  45. data/spec/spec_helper.rb +1 -0
  46. data/spec/support/debug_failure_helper.rb +16 -0
  47. data/spec/unit/models/connection_details_spec.rb +60 -0
  48. data/spec/unit/models/protocol_message_spec.rb +45 -0
  49. data/spec/unit/modules/event_emitter_spec.rb +3 -1
  50. data/spec/unit/realtime/channel_spec.rb +6 -5
  51. data/spec/unit/realtime/client_spec.rb +5 -1
  52. data/spec/unit/realtime/connection_spec.rb +5 -1
  53. data/spec/unit/realtime/realtime_spec.rb +5 -1
  54. metadata +54 -7
@@ -84,7 +84,7 @@ describe Ably::Realtime::Connection, :event_machine do
84
84
  end
85
85
 
86
86
  context 'that expire' do
87
- let(:client_options) { default_options.merge(log_level: :none) }
87
+ let(:client_options) { default_options.merge(log_level: :error) }
88
88
 
89
89
  before do
90
90
  expect(client.rest_client.time.to_f).to be_within(2).of(Time.now.to_i), "Local clock is out of sync with Ably"
@@ -94,59 +94,97 @@ describe Ably::Realtime::Connection, :event_machine do
94
94
  # Ensure tokens issued expire immediately after issue
95
95
  @original_renew_token_buffer = Ably::Auth::TOKEN_DEFAULTS.fetch(:renew_token_buffer)
96
96
  stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0)
97
-
98
- # Authorise synchronously to ensure token has been issued
99
- client.auth.authorise_sync(ttl: ttl)
100
97
  end
101
98
 
102
99
  let(:original_renew_token_buffer) { @original_renew_token_buffer }
103
100
 
104
101
  context 'opening a new connection' do
105
- context 'with recently expired token' do
102
+ context 'with almost expired tokens' do
103
+ before do
104
+ # Authorise synchronously to ensure token has been issued
105
+ client.auth.authorise_sync(ttl: ttl)
106
+ end
107
+
106
108
  let(:ttl) { 2 }
107
109
 
108
- it 'renews the token on connect without changing connection state' do
109
- connection.once(:connecting) do
110
- sleep ttl + 0.1
111
- expect(client.auth.current_token_details).to be_expired
112
- expect(client.rest_client.auth).to receive(:authorise).at_least(:once).and_call_original
113
- connection.once(:connected) do
114
- expect(client.auth.current_token_details).to_not be_expired
110
+ it 'renews token every time after it expires' do
111
+ started_at = Time.now.to_f
112
+ connected_times = 0
113
+ disconnected_times = 0
114
+ connection.on(:connected) do
115
+ connected_times += 1
116
+ end
117
+ connection.on(:disconnected) do
118
+ disconnected_times += 1
119
+ if disconnected_times == 3
120
+ expect(connected_times).to eql(3)
121
+ expect(Time.now.to_f - started_at).to be > ttl * 3
122
+ expect(Time.now.to_f - started_at).to be < (ttl * 2) * 3
115
123
  stop_reactor
116
124
  end
117
- connection.once_state_changed do
118
- raise "Invalid state #{connection.state}" unless connection.state == :connected
119
- end
120
125
  end
121
126
  end
122
127
  end
123
128
 
124
- context 'with immediately expiring token' do
129
+ context 'with immediately expired token' do
125
130
  let(:ttl) { 0.001 }
131
+ let(:auth_requests) { [] }
132
+ let(:token_callback) do
133
+ Proc.new do
134
+ auth_requests << Time.now
135
+ Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
136
+ end
137
+ end
138
+ let(:client_options) { default_options.merge(auth_callback: token_callback) }
126
139
 
127
- it 'renews the token on connect, and only makes one subsequent attempt to obtain a new token' do
128
- expect(client.rest_client.auth).to receive(:authorise).at_least(:twice).and_call_original
140
+ it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token' do
141
+ started_at = Time.now.to_f
129
142
  connection.once(:disconnected) do
130
- connection.once(:failed) do |connection_state_change|
143
+ connection.once(:disconnected) do |connection_state_change|
131
144
  expect(connection_state_change.reason.code).to eql(40140) # token expired
145
+ expect(Time.now.to_f - started_at).to be < 1000
146
+ expect(auth_requests.count).to eql(2)
132
147
  stop_reactor
133
148
  end
134
149
  end
135
150
  end
136
151
 
137
- it 'uses the primary host for subsequent connection and auth requests' do
138
- EventMachine.add_timer(1) do # wait for token to expire
152
+ 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
+
155
+ 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
+ disconnect_count = 0
158
+ connection.on(:disconnected) do |connection_state_change|
159
+ expect(connection_state_change.reason.code).to eql(40140) # 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
+ stop_reactor
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ context 'using implicit token auth' do
171
+ let(:client_options) { default_options.merge(use_token_auth: true, token_params: { ttl: ttl }) }
172
+
173
+ before do
174
+ stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', -10 # ensure client lib thinks token is still valid
175
+ end
176
+
177
+ it 'uses the primary host for subsequent connection and auth requests' do
139
178
  connection.once(:disconnected) do
140
179
  expect(client.rest_client.connection).to receive(:post).
141
180
  with(/requestToken$/, anything).
142
- at_least(:once).
181
+ exactly(:once).
143
182
  and_call_original
144
183
 
145
184
  expect(client.rest_client).to_not receive(:fallback_connection)
146
185
  expect(client).to_not receive(:fallback_endpoint)
147
186
 
148
- connection.once(:failed) do
149
- connection.off
187
+ connection.once(:disconnected) do
150
188
  stop_reactor
151
189
  end
152
190
  end
@@ -157,15 +195,16 @@ describe Ably::Realtime::Connection, :event_machine do
157
195
 
158
196
  context 'when connected with a valid non-expired token' do
159
197
  context 'that then expires following the connection being opened' do
160
- let(:ttl) { 5 }
161
- let(:channel) { client.channel(random_str) }
198
+ let(:ttl) { 5 }
199
+ let(:channel_name) { random_str }
200
+ let(:channel) { client.channel(channel_name) }
201
+ let(:client_options) { default_options.merge(use_token_auth: true, token_params: { ttl: ttl }) }
162
202
 
163
203
  context 'the server' do
164
204
  it 'disconnects the client, and the client automatically renews the token and then reconnects', em_timeout: 15 do
165
- original_token = client.auth.current_token_details
166
- expect(original_token).to_not be_expired
167
-
168
205
  connection.once(:connected) do
206
+ original_token = client.auth.current_token_details
207
+ expect(original_token).to_not be_expired
169
208
  started_at = Time.now
170
209
  connection.once(:disconnected) do |connection_state_change|
171
210
  expect(connection_state_change.reason.code).to eq(40140) # Token expired
@@ -189,8 +228,86 @@ describe Ably::Realtime::Connection, :event_machine do
189
228
  end
190
229
  end
191
230
 
192
- skip 'retains connection state'
193
- skip 'changes state to failed if a new token cannot be issued'
231
+ context 'connection state' do
232
+ let(:ttl) { 4 }
233
+ let(:auth_requests) { [] }
234
+ let(:token_callback) do
235
+ Proc.new do
236
+ sleep 2
237
+ auth_requests << Time.now
238
+ Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
239
+ end
240
+ end
241
+ let(:client_options) { default_options.merge(auth_callback: token_callback) }
242
+ let(:publishing_client) { auto_close Ably::Realtime::Client.new(default_options) }
243
+ let(:publishing_channel) { publishing_client.channels.get(channel_name) }
244
+ let(:messages_received) { [] }
245
+
246
+ def publish_and_check_first_disconnect
247
+ 10.times.each { |index| publishing_channel.publish('event', index.to_s) }
248
+ channel.subscribe('event') do |message|
249
+ 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)
253
+ EventMachine.add_timer(1) do
254
+ channel.unsubscribe 'event'
255
+ yield
256
+ end
257
+ end
258
+ end
259
+ end
260
+
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
274
+ publishing_channel.attach do
275
+ channel.attach do
276
+ connection.once(:disconnected) do
277
+ publish_and_check_first_disconnect do
278
+ connection.once(:disconnected) do
279
+ publish_and_check_second_disconnect
280
+ end
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ context 'and subsequent token is invalid' do
289
+ let(:ttl) { 2 }
290
+ let(:token_callback) do
291
+ Proc.new do
292
+ if @token_issued
293
+ "#{app_id}.invalid-token-invalid-token-invalid-token"
294
+ else
295
+ @token_issued = true
296
+ Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
297
+ end
298
+ end
299
+ end
300
+ let(:client_options) { default_options.merge(auth_callback: token_callback, log_level: :none) }
301
+
302
+ it 'transitions the connection to the failed state' do
303
+ connection.once(:disconnected) do
304
+ connection.once(:failed) do
305
+ expect(connection.error_reason.code).to eql(40101)
306
+ stop_reactor
307
+ end
308
+ end
309
+ end
310
+ end
194
311
  end
195
312
  end
196
313
  end
@@ -228,6 +345,50 @@ describe Ably::Realtime::Connection, :event_machine do
228
345
  end
229
346
  end
230
347
  end
348
+
349
+ context 'with opaque token string that contain an implicit client_id' do
350
+ let(:client_options) { default_options.merge(token: token_string, key: nil) }
351
+ let(:rest_auth_client) { Ably::Rest::Client.new(default_options.merge(key: api_key)) }
352
+ let(:token_string) { rest_auth_client.auth.request_token(client_id: client_id).token }
353
+
354
+ context 'string' do
355
+ let(:client_id) { random_str }
356
+
357
+ it 'sets the Client#client_id and Auth#client_id once CONNECTED' do
358
+ expect(client.client_id).to be_nil
359
+ client.connection.once(:connected) do
360
+ expect(client.client_id).to eql(client_id)
361
+ stop_reactor
362
+ end
363
+ end
364
+
365
+ context 'that is incompatible with the current client client_id' do
366
+ let(:client_id) { random_str }
367
+ let(:client_options) { default_options.merge(client_id: 'incompatible', token: token_string, key: nil, log_level: :none) }
368
+
369
+ it 'fails the connection' do
370
+ expect(client.client_id).to eql('incompatible')
371
+ client.connection.once(:failed) do
372
+ expect(client.client_id).to eql('incompatible')
373
+ expect(client.connection.error_reason.code).to eql(40101) # Invalid clientId for credentials
374
+ stop_reactor
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ context 'wildcard' do
381
+ let(:client_id) { '*' }
382
+
383
+ it 'configures the Client#client_id and Auth#client_id with a wildcard once CONNECTED' do
384
+ expect(client.client_id).to be_nil
385
+ client.connection.once(:connected) do
386
+ expect(client.client_id).to eql('*')
387
+ stop_reactor
388
+ end
389
+ end
390
+ end
391
+ end
231
392
  end
232
393
  end
233
394
 
@@ -277,6 +438,50 @@ describe Ably::Realtime::Connection, :event_machine do
277
438
  end
278
439
  end
279
440
 
441
+ it 'calls the provided block on success even if state changes to disconnected first' do
442
+ been_disconnected = false
443
+
444
+ connection.once(:disconnected) do
445
+ been_disconnected = true
446
+ end
447
+ connection.once(:connecting) do
448
+ close_if_transport_available = proc do
449
+ EventMachine.add_timer(0.001) do
450
+ if connection.transport
451
+ connection.transport.close_connection_after_writing
452
+ else
453
+ close_if_transport_available.call
454
+ end
455
+ end
456
+ end
457
+ close_if_transport_available.call
458
+ end
459
+
460
+ connection.connect do
461
+ expect(connection.state).to eq(:connected)
462
+ expect(been_disconnected).to be_truthy
463
+ stop_reactor
464
+ end
465
+ end
466
+
467
+ context 'with invalid auth details' do
468
+ let(:client_options) { default_options.merge(key: 'this.is:invalid', log_level: :none) }
469
+
470
+ it 'calls the Deferrable errback only once on connection failure' do
471
+ errback_called = false
472
+ connection.connect.errback do
473
+ expect(connection.state).to eq(:failed)
474
+
475
+ raise 'Errback already called' if errback_called
476
+ errback_called = true
477
+
478
+ connection.connect.errback do
479
+ EventMachine.add_timer(0.5) { stop_reactor }
480
+ end
481
+ end
482
+ end
483
+ end
484
+
280
485
  context 'when already connected' do
281
486
  it 'does nothing and no further state changes are emitted' do
282
487
  connection.once(:connected) do
@@ -509,12 +714,10 @@ describe Ably::Realtime::Connection, :event_machine do
509
714
  end
510
715
 
511
716
  context 'with an unresponsive connection' do
512
- let(:stubbed_timeout) { 2 }
717
+ let(:custom_timeout) { 2 }
718
+ let(:client_options) { default_options.merge(realtime_request_timeout: custom_timeout) }
513
719
 
514
720
  before do
515
- stub_const 'Ably::Realtime::Connection::ConnectionManager::TIMEOUTS',
516
- Ably::Realtime::Connection::ConnectionManager::TIMEOUTS.merge(close: stubbed_timeout)
517
-
518
721
  connection.on(:connected) do
519
722
  # Prevent all incoming & outgoing ProtocolMessages from being processed by the client library
520
723
  connection.__outgoing_protocol_msgbus__.unsubscribe
@@ -527,7 +730,7 @@ describe Ably::Realtime::Connection, :event_machine do
527
730
  close_requested_at = Time.now
528
731
 
529
732
  connection.on(:closed) do
530
- expect(Time.now - close_requested_at).to be >= stubbed_timeout
733
+ expect(Time.now - close_requested_at).to be >= custom_timeout
531
734
  expect(connection.state).to eq(:closed)
532
735
  expect(events[:error_emitted]).to_not eql(true)
533
736
  expect(events[:closed_message_from_server_received]).to_not eql(true)
@@ -571,6 +774,30 @@ describe Ably::Realtime::Connection, :event_machine do
571
774
  end
572
775
  end
573
776
  end
777
+
778
+ context 'when ping times out' do
779
+ let(:client_options) { default_options.merge(log_level: :error) }
780
+
781
+ it 'logs a warning' do
782
+ connection.once(:connected) do
783
+ 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
786
+ end
787
+ connection.ping
788
+ end
789
+ end
790
+
791
+ it 'yields to the block with a nil value' do
792
+ connection.once(:connected) do
793
+ allow(connection).to receive(:defaults).and_return(connection.defaults.merge(realtime_request_timeout: 0.0001))
794
+ connection.ping do |time_elapsed|
795
+ expect(time_elapsed).to be_nil
796
+ stop_reactor
797
+ end
798
+ end
799
+ end
800
+ end
574
801
  end
575
802
 
576
803
  context 'recovery' do
@@ -580,15 +807,14 @@ describe Ably::Realtime::Connection, :event_machine do
580
807
  auto_close Ably::Realtime::Client.new(client_options)
581
808
  end
582
809
  let(:publishing_client_channel) { publishing_client.channel(channel_name) }
583
- let(:client_options) { default_options.merge(log_level: :fatal) }
584
-
585
- before do
586
- # Reconfigure client library retry periods and timeouts so that tests run quickly
587
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
588
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
589
- disconnected: { retry_every: 0.1, max_time_in_state: 0.2 },
590
- suspended: { retry_every: 0.1, max_time_in_state: 0.2 },
591
- )
810
+
811
+ let(:client_options) do
812
+ default_options.merge(
813
+ log_level: :none,
814
+ disconnected_retry_timeout: 0.1,
815
+ suspended_retry_timeout: 0.1,
816
+ connection_state_ttl: 0.2
817
+ )
592
818
  end
593
819
 
594
820
  describe '#recovery_key' do
@@ -597,7 +823,6 @@ describe Ably::Realtime::Connection, :event_machine do
597
823
  end
598
824
  let(:available_states) { self.class.available_states }
599
825
  let(:states) { Hash.new }
600
- let(:client_options) { default_options.merge(log_level: :none) }
601
826
  let(:channel) { client.channel(random_str) }
602
827
 
603
828
  it 'is composed of connection key and serial that is kept up to date with each message ACK received' do
@@ -641,6 +866,11 @@ describe Ably::Realtime::Connection, :event_machine do
641
866
  end
642
867
  end
643
868
 
869
+ connection.once(:suspended) do
870
+ error_message = Ably::Models::ProtocolMessage.new(action: 9, error: { message: 'force failure' })
871
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
872
+ end
873
+
644
874
  connection.once(:failed) do
645
875
  expect(states.keys).to match_array(available_states)
646
876
  stop_reactor
@@ -659,8 +889,6 @@ describe Ably::Realtime::Connection, :event_machine do
659
889
 
660
890
  context "opening a new connection using a recently disconnected connection's #recovery_key" do
661
891
  context 'connection#id and connection#key after recovery' do
662
- let(:client_options) { default_options.merge(log_level: :none) }
663
-
664
892
  it 'remains the same' do
665
893
  previous_connection_id = nil
666
894
  previous_connection_key = nil
@@ -701,8 +929,6 @@ describe Ably::Realtime::Connection, :event_machine do
701
929
 
702
930
  context 'when messages have been sent whilst the old connection is disconnected' do
703
931
  describe 'the new connection' do
704
- let(:client_options) { default_options.merge(log_level: :none) }
705
-
706
932
  it 'recovers server-side queued messages' do
707
933
  channel.attach do
708
934
  connection.transition_state_machine! :failed
@@ -774,7 +1000,7 @@ describe Ably::Realtime::Connection, :event_machine do
774
1000
 
775
1001
  it 'opens each with a unique connection#id and connection#key' do
776
1002
  connection_count.times.map do
777
- Ably::Realtime::Client.new(client_options)
1003
+ auto_close Ably::Realtime::Client.new(client_options)
778
1004
  end.each do |client|
779
1005
  client.connection.on(:connected) do
780
1006
  connection_ids << client.connection.id
@@ -943,15 +1169,13 @@ describe Ably::Realtime::Connection, :event_machine do
943
1169
  end
944
1170
 
945
1171
  context 'when connection enters the :suspended state' do
946
- let(:client_options) { default_options.merge(:log_level => :fatal) }
947
-
948
- before do
949
- # Reconfigure client library retry periods so that client stays in suspended state
950
- stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
951
- Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
952
- disconnected: { retry_every: 0.01, max_time_in_state: 0.05 },
953
- suspended: { retry_every: 60, max_time_in_state: 60 }
954
- )
1172
+ let(:client_options) do
1173
+ default_options.merge(
1174
+ log_level: :fatal,
1175
+ disconnected_retry_timeout: 0.02,
1176
+ suspended_retry_timeout: 60,
1177
+ connection_state_ttl: 0.05
1178
+ )
955
1179
  end
956
1180
 
957
1181
  it 'detaches the channels and prevents publishing of messages on those channels' do
@@ -132,7 +132,6 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
132
132
  let(:client_options) { default_options.merge(client_id: client_id) }
133
133
 
134
134
  it 'contains a #client_id attribute' do
135
- skip 'Waiting for issue #256 to be resolved'
136
135
  when_all(channel.attach, other_client_channel.attach) do
137
136
  other_client_channel.subscribe('event') do |message|
138
137
  expect(message.client_id).to eql(client_id)
@@ -208,14 +207,26 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
208
207
  end
209
208
  end
210
209
 
211
- it 'will not echo messages to the client from other REST clients publishing using that connection_ID', em_timeout: 10 do
212
- skip 'Waiting on realtime#285 to be resolved'
210
+ it 'will not echo messages to the client from other REST clients publishing using that connection_key', em_timeout: 10 do
213
211
  no_echo_channel.attach do
214
212
  no_echo_channel.subscribe('test_event') do |message|
215
213
  fail "Message should not have been echoed back"
216
214
  end
217
215
 
218
- rest_client.channel(channel_name).publish('test_event', nil, connection_id: no_echo_client.connection.id)
216
+ rest_client.channel(channel_name).publish('test_event', nil, connection_key: no_echo_client.connection.key)
217
+ EventMachine.add_timer(1.5) do
218
+ stop_reactor
219
+ end
220
+ end
221
+ end
222
+
223
+ it 'will echo messages with a valid connection_id to the client from other REST clients publishing using that connection_key', em_timeout: 10 do
224
+ channel.attach do
225
+ channel.subscribe('test_event') do |message|
226
+ expect(message.connection_id).to eql(client.connection.id)
227
+ end
228
+
229
+ rest_client.channel(channel_name).publish('test_event', nil, connection_key: client.connection.key)
219
230
  EventMachine.add_timer(1.5) do
220
231
  stop_reactor
221
232
  end
@@ -599,7 +610,7 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
599
610
  let(:event_name) { random_str }
600
611
  let(:message_state) { [] }
601
612
  let(:connection) { client.connection }
602
- let(:client_options) { default_options.merge(:log_level => :none) }
613
+ let(:client_options) { default_options.merge(:log_level => :fatal) }
603
614
  let(:msgs_received) { [] }
604
615
 
605
616
  it 'publishes the message again, later receives the ACK and only one message is ever received from Ably' do
@@ -642,6 +653,32 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
642
653
  let(:connection) { client.connection }
643
654
  let(:event_name) { random_str }
644
655
 
656
+ describe 'the connection is not resumed' do
657
+ let(:client_options) { default_options.merge(:log_level => :fatal) }
658
+
659
+ it 'calls the errback for all messages' do
660
+ connection.once(:connected) do
661
+ connection.transport.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
662
+ if protocol_message.messages.find { |message| message.name == event_name }
663
+ EventMachine.add_timer(0.0001) do
664
+ connection.transport.unbind # trigger failure
665
+ connection.configure_new '0123456789abcdef', 'wVIsgTHAB1UvXh7z-1991d8586', -1 # force the resume connection key to be invalid
666
+ end
667
+ end
668
+ end
669
+ end
670
+
671
+ channel.publish(event_name).tap do |deferrable|
672
+ deferrable.callback do
673
+ raise 'Message delivery should not happen'
674
+ end
675
+ deferrable.errback do
676
+ stop_reactor
677
+ end
678
+ end
679
+ end
680
+ end
681
+
645
682
  describe 'the connection becomes suspended' do
646
683
  let(:client_options) { default_options.merge(:log_level => :fatal) }
647
684
 
@@ -649,8 +686,10 @@ describe 'Ably::Realtime::Channel Message', :event_machine do
649
686
  connection.once(:connected) do
650
687
  connection.transport.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
651
688
  if protocol_message.messages.find { |message| message.name == event_name }
652
- EventMachine.add_timer(0.001) do
689
+ EventMachine.add_timer(0.0001) do
653
690
  connection.transition_state_machine :suspended
691
+ stub_const 'Ably::FALLBACK_HOSTS', []
692
+ allow(client).to receive(:endpoint).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com'))
654
693
  end
655
694
  end
656
695
  end
@@ -71,6 +71,10 @@ describe Ably::Realtime::Presence, 'history', :event_machine do
71
71
  end
72
72
 
73
73
  context 'and two pages of messages' do
74
+ let(:wildcard_token) { Proc.new { Ably::Rest::Client.new(default_options).auth.request_token(client_id: '*') } }
75
+ let(:client_one) { auto_close Ably::Realtime::Client.new(default_options.merge(auth_callback: wildcard_token)) }
76
+ let(:client_two) { auto_close Ably::Realtime::Client.new(default_options.merge(auth_callback: wildcard_token)) }
77
+
74
78
  it 'retrieves two pages of messages before channel was attached' do
75
79
  when_all(*10.times.map { |i| presence_client_two.enter_client("client:#{i}", data: presence_data_before_attach) }) do
76
80
  when_all(*10.times.map { |i| presence_client_one.enter_client("client:#{i}", data: presence_data_after_attach) }) do