ably-rest 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -117,7 +117,7 @@ describe Ably::Realtime::Client, :event_machine do
117
117
  context 'when the returned token has a client_id' do
118
118
  it "sets Auth#client_id to the new token's client_id immediately when connecting" do
119
119
  subject.auth.authorize do
120
- expect(subject.connection).to be_connecting
120
+ expect(subject.connection).to be_connected
121
121
  expect(subject.auth.client_id).to eql(client_id)
122
122
  stop_reactor
123
123
  end
@@ -125,7 +125,7 @@ describe Ably::Realtime::Client, :event_machine do
125
125
 
126
126
  it "sets Client#client_id to the new token's client_id immediately when connecting" do
127
127
  subject.auth.authorize do
128
- expect(subject.connection).to be_connecting
128
+ expect(subject.connection).to be_connected
129
129
  expect(subject.client_id).to eql(client_id)
130
130
  stop_reactor
131
131
  end
@@ -13,6 +13,9 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
13
13
  let(:client) do
14
14
  auto_close Ably::Realtime::Client.new(client_options)
15
15
  end
16
+ let(:rest_client) do
17
+ Ably::Rest::Client.new(default_options)
18
+ end
16
19
 
17
20
  context 'authentication failure' do
18
21
  let(:client_options) do
@@ -50,6 +53,152 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
50
53
  end
51
54
  end
52
55
  end
56
+
57
+ context 'with auth_url' do
58
+ context 'opening a new connection' do
59
+ context 'request fails due to network failure' do
60
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: "http://#{random_str}.domain.will.never.resolve.to/path", log_level: :fatal) }
61
+
62
+ specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
63
+ states = Hash.new { |hash, key| hash[key] = [] }
64
+
65
+ connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
66
+
67
+ connection.on do |connection_state|
68
+ states[connection_state.current.to_sym] << Time.now
69
+ if states[:disconnected].count == 2 && connection_state.current == :disconnected
70
+ expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
71
+ expect(connection.error_reason.message).to match(/auth_url/)
72
+ EventMachine.add_timer(2) do
73
+ expect(states.keys).to include(:connecting, :disconnected)
74
+ expect(states[:connecting].count).to eql(2)
75
+ expect(states[:connected].count).to eql(0)
76
+ stop_reactor
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'request fails due to invalid content', :webmock do
84
+ let(:auth_endpoint) { "http://#{random_str}.domain.will.never.resolve.to/authenticate" }
85
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_url: auth_endpoint, log_level: :fatal) }
86
+
87
+ before do
88
+ stub_request(:get, auth_endpoint).
89
+ to_return(:status => 200, :body => "", :headers => { "Content-type" => "text/html" })
90
+ end
91
+
92
+ specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
93
+ states = Hash.new { |hash, key| hash[key] = [] }
94
+
95
+ connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
96
+
97
+ connection.on do |connection_state|
98
+ states[connection_state.current.to_sym] << Time.now
99
+ if states[:disconnected].count == 2 && connection_state.current == :disconnected
100
+ expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
101
+ expect(connection.error_reason.message).to match(/auth_url/)
102
+ expect(connection.error_reason.message).to match(/Content Type.*not supported/)
103
+ EventMachine.add_timer(2) do
104
+ expect(states.keys).to include(:connecting, :disconnected)
105
+ expect(states[:connecting].count).to eql(2)
106
+ expect(states[:connected].count).to eql(0)
107
+ stop_reactor
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ context 'existing CONNECTED connection' do
116
+ context 'authorize request failure leaves connection in existing condition' do
117
+ let(:auth_options) { { auth_url: "http://#{random_str}.domain.will.never.resolve.to/path" } }
118
+ let(:client_options) { default_options.merge(use_token_auth: true, log_level: :fatal) }
119
+
120
+ specify 'the connection remains in the CONNECTED state and authorize fails (#RSA4c, #RSA4c1, #RSA4c3)' do
121
+ connection.once(:connected) do
122
+ connection.on { raise "State should not change and should stay connected" }
123
+
124
+ client.auth.authorize(nil, auth_options).tap do |deferrable|
125
+ deferrable.callback { raise "Authorize should not succeed" }
126
+ deferrable.errback do |err|
127
+ expect(err).to be_a(Ably::Exceptions::ConnectionError)
128
+ expect(err.message).to match(/auth_url/)
129
+
130
+ EventMachine.add_timer(1) do
131
+ expect(connection).to be_connected
132
+ connection.off
133
+ stop_reactor
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ context 'with auth_callback' do
144
+ context 'opening a new connection' do
145
+ context 'when callback fails due to an exception' do
146
+ let(:client_options) { default_options.reject { |k, v| k == :key }.merge(auth_callback: Proc.new { raise "Cannot issue token" }, log_level: :fatal) }
147
+
148
+ it 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
149
+ states = Hash.new { |hash, key| hash[key] = [] }
150
+
151
+ connection.once(:connected) { raise "Connection can never move to connected because of auth failures" }
152
+
153
+ connection.on do |connection_state|
154
+ states[connection_state.current.to_sym] << Time.now
155
+ if states[:disconnected].count == 2 && connection_state.current == :disconnected
156
+ expect(connection.error_reason).to be_a(Ably::Exceptions::ConnectionError)
157
+ expect(connection.error_reason.message).to match(/auth_callback/)
158
+ EventMachine.add_timer(2) do
159
+ expect(states.keys).to include(:connecting, :disconnected)
160
+ expect(states[:connecting].count).to eql(2)
161
+ expect(states[:connected].count).to eql(0)
162
+ stop_reactor
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'existing CONNECTED connection' do
170
+ context 'when callback fails due to the request taking longer than realtime_request_timeout' do
171
+ let(:request_timeout) { 3 }
172
+ let(:client_options) { default_options.merge(
173
+ realtime_request_timeout: request_timeout,
174
+ use_token_auth: true,
175
+ log_level: :fatal)
176
+ }
177
+ let(:auth_options) { { auth_callback: Proc.new { sleep 10 }, } }
178
+
179
+ it 'the authorization request fails as configured in the realtime_request_timeout (#RSA4c, #RSA4c1, #RSA4c3)' do
180
+ connection.once(:connected) do
181
+ connection.on { raise "State should not change and should stay connected" }
182
+
183
+ client.auth.authorize(nil, auth_options).tap do |deferrable|
184
+ deferrable.callback { raise "Authorize should not succeed" }
185
+ deferrable.errback do |err|
186
+ expect(err).to be_a(Ably::Exceptions::ConnectionError)
187
+ expect(err.message).to match(/auth_callback/)
188
+
189
+ EventMachine.add_timer(1) do
190
+ expect(connection).to be_connected
191
+ connection.off
192
+ stop_reactor
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
53
202
  end
54
203
 
55
204
  context 'automatic connection retry' do
@@ -100,7 +249,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
100
249
  connection.once(:suspended) do
101
250
  expect(connection.state).to eq(:suspended)
102
251
 
103
- expect(state_changes[:connecting]).to eql(expected_retry_attempts)
252
+ expect(state_changes[:connecting]).to eql(expected_retry_attempts + 1) # allow for initial connecting attempt
104
253
  expect(state_changes[:disconnected]).to eql(expected_retry_attempts)
105
254
 
106
255
  expect(time_passed).to be > max_time_in_state_for_tests
@@ -213,7 +362,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
213
362
 
214
363
  context 'when connection state is :failed' do
215
364
  describe '#close' do
216
- it 'will not transition state to :close and raises a InvalidStateChange exception' do
365
+ it 'will not transition state to :close and fails with an InvalidStateChange exception' do
217
366
  connection.on(:connected) { raise 'Connection should not have reached :connected state' }
218
367
 
219
368
  connection.once(:suspended) do
@@ -222,8 +371,11 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
222
371
 
223
372
  connection.once(:failed) do
224
373
  expect(connection.state).to eq(:failed)
225
- expect { connection.close }.to raise_error Ably::Exceptions::InvalidStateChange, /Unable to transition from failed => closing/
226
- stop_reactor
374
+ connection.close.errback do |error|
375
+ expect(error).to be_a(Ably::Exceptions::InvalidStateChange)
376
+ expect(error.message).to match(/Unable to transition from failed => closing/)
377
+ stop_reactor
378
+ end
227
379
  end
228
380
  end
229
381
  end
@@ -457,7 +609,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
457
609
  end
458
610
 
459
611
  context 'after successfully reconnecting and resuming' do
460
- it 'retains connection_id and updates the connection_key' do
612
+ it 'retains connection_id and updates the connection_key (#RTN15e, #RTN16d)' do
461
613
  connection.once(:connected) do
462
614
  previous_connection_id = connection.id
463
615
  connection.transport.close_connection_after_writing
@@ -472,8 +624,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
472
624
  end
473
625
  end
474
626
 
475
- it 'emits any error received from Ably but leaves the channels attached' do
476
- emitted_error = nil
627
+ it 'includes the error received in the connection state change from Ably but leaves the channels attached' do
477
628
  channel.attach do
478
629
  connection.transport.close_connection_after_writing
479
630
 
@@ -487,19 +638,15 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
487
638
  Ably::Realtime::Client::IncomingMessageDispatcher.new(client, connection)
488
639
  end
489
640
 
490
- connection.once(:connected) do
641
+ connection.once(:connected) do |connection_state_change|
491
642
  EM.add_timer(0.5) do
492
- expect(emitted_error).to be_a(Ably::Exceptions::Standard)
493
- expect(emitted_error.message).to match(/Injected error/)
643
+ expect(connection_state_change.reason).to be_a(Ably::Exceptions::Standard)
644
+ expect(connection_state_change.reason.message).to match(/Injected error/)
494
645
  expect(connection.error_reason).to be_a(Ably::Exceptions::Standard)
495
646
  expect(channel).to be_attached
496
647
  stop_reactor
497
648
  end
498
649
  end
499
-
500
- connection.once(:error) do |error|
501
- emitted_error = error
502
- end
503
650
  end
504
651
  end
505
652
 
@@ -541,7 +688,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
541
688
 
542
689
  channel.attach do
543
690
  publishing_client_channel.attach do
544
- connection.transport.off # remove all event handlers that detect socket connection state has changed
691
+ connection.transport.unsafe_off # remove all event handlers that detect socket connection state has changed
545
692
  connection.transport.close_connection_after_writing
546
693
 
547
694
  publishing_client_channel.publish('event', 'message') do
@@ -563,6 +710,36 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
563
710
  end
564
711
  end
565
712
  end
713
+
714
+ it 'retains the client_serial (#RTN15c2, #RTN15c3)' do
715
+ last_message = nil
716
+ channel = client.channels.get("foo")
717
+
718
+ connection.once(:connected) do
719
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
720
+ if protocol_message.action == :message
721
+ last_message = protocol_message
722
+ end
723
+ end
724
+
725
+ channel.publish("first") do
726
+ expect(last_message.message_serial).to eql(0)
727
+ channel.publish("second") do
728
+ expect(last_message.message_serial).to eql(1)
729
+ connection.once(:connected) do
730
+ channel.publish("first on resumed connection") do
731
+ # Message serial reset after failed resume
732
+ expect(last_message.message_serial).to eql(2)
733
+ stop_reactor
734
+ end
735
+ end
736
+
737
+ # simulate connection dropped to re-establish web socket
738
+ connection.transition_state_machine :disconnected
739
+ end
740
+ end
741
+ end
742
+ end
566
743
  end
567
744
 
568
745
  context 'when failing to resume' do
@@ -589,36 +766,159 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
589
766
  end
590
767
  end
591
768
 
592
- it 'detaches all channels' do
769
+ it 'issue a reattach for all attached channels and fail all message awaiting an ACK (#RTN15c3)' do
593
770
  channel_count = 10
594
771
  channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
595
772
  when_all(*channels.map(&:attach)) do
596
- detached_channels = []
773
+ attached_channels = []
774
+ reattaching_channels = []
775
+ attach_protocol_messages = []
776
+ failed_messages = []
777
+
597
778
  channels.each do |channel|
598
- channel.on(:detached) do |channel_state_change|
779
+ channel.publish("foo").errback do
780
+ failed_messages << channel
781
+ end
782
+ channel.on(:attaching) do |channel_state_change|
599
783
  error = channel_state_change.reason
600
784
  expect(error.message).to match(/Unable to recover connection/i)
601
- detached_channels << channel
602
- next unless detached_channels.count == channel_count
603
- expect(detached_channels.count).to eql(channel_count)
785
+ reattaching_channels << channel
786
+ end
787
+ channel.on(:attached) do
788
+ attached_channels << channel
789
+ next unless attached_channels.count == channel_count
790
+ expect(reattaching_channels.count).to eql(channel_count)
791
+ expect(failed_messages.count).to eql(channel_count)
792
+ expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
793
+ stop_reactor
794
+ end
795
+ end
796
+
797
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
798
+ if protocol_message.action == :attach
799
+ attach_protocol_messages << protocol_message.channel
800
+ end
801
+ end
802
+
803
+ kill_connection_transport_and_prevent_valid_resume
804
+ end
805
+ end
806
+
807
+ it 'issue a reattach for all attaching channels and fail all queued messages (#RTN15c3)' do
808
+ channel_count = 10
809
+ channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
810
+
811
+ channels.map(&:attach)
812
+
813
+ attached_channels = []
814
+ attach_protocol_messages = []
815
+ failed_messages = []
816
+
817
+ channels.each do |channel|
818
+ channel.publish("foo").errback do
819
+ failed_messages << channel
820
+ end
821
+
822
+ channel.on(:attached) do |state_change|
823
+ attached_channels << channel
824
+ expect(state_change).to_not be_resumed
825
+ next unless attached_channels.count == channel_count
826
+ expect(failed_messages.count).to eql(channel_count)
827
+ expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
828
+ stop_reactor
829
+ end
830
+ end
831
+
832
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
833
+ if protocol_message.action == :attach
834
+ attach_protocol_messages << protocol_message.channel
835
+ end
836
+ end
837
+
838
+ client.connection.once(:connected) do
839
+ kill_connection_transport_and_prevent_valid_resume
840
+ end
841
+ end
842
+
843
+ it 'issue a attach for all suspended channels (#RTN15c3)' do
844
+ channel_count = 10
845
+ channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
846
+
847
+ when_all(*channels.map(&:attach)) do
848
+ # Force all channels into a suspended state
849
+ channels.map do |channel|
850
+ channel.transition_state_machine! :suspended
851
+ expect(channel).to be_suspended
852
+ end
853
+
854
+ attached_channels = []
855
+ reattaching_channels = []
856
+ attach_protocol_messages = []
857
+
858
+ channels.each do |channel|
859
+ channel.on(:attaching) do
860
+ reattaching_channels << channel
861
+ end
862
+ channel.on(:attached) do
863
+ attached_channels << channel
864
+ next unless attached_channels.count == channel_count
865
+ expect(reattaching_channels.count).to eql(channel_count)
866
+ expect(attach_protocol_messages.uniq).to match(channels.map(&:name))
604
867
  stop_reactor
605
868
  end
606
869
  end
607
870
 
871
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
872
+ if protocol_message.action == :attach
873
+ attach_protocol_messages << protocol_message.channel
874
+ end
875
+ end
876
+
608
877
  kill_connection_transport_and_prevent_valid_resume
609
878
  end
610
879
  end
611
880
 
612
- it 'emits an error on the channel and sets the error reason' do
881
+ it 'sets the error reason on each channel' do
613
882
  channel.attach do
883
+ channel.on(:attaching) do |state_change|
884
+ expect(state_change.reason.message).to match(/Unable to recover connection/i)
885
+ expect(state_change.reason.code).to eql(80008)
886
+ expect(channel.error_reason.code).to eql(80008)
887
+
888
+ channel.on(:attached) do |state_change|
889
+ stop_reactor
890
+ end
891
+ end
614
892
  kill_connection_transport_and_prevent_valid_resume
615
893
  end
894
+ end
616
895
 
617
- channel.on(:error) do |error|
618
- expect(error.message).to match(/Unable to recover connection/i)
619
- expect(error.code).to eql(80008)
620
- expect(channel.error_reason).to eql(error)
621
- stop_reactor
896
+ it 'resets the client_serial (#RTN15c3)' do
897
+ last_message = nil
898
+ channel = client.channels.get("foo")
899
+
900
+ connection.once(:connected) do
901
+ connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
902
+ if protocol_message.action == :message
903
+ last_message = protocol_message
904
+ end
905
+ end
906
+
907
+ channel.publish("first") do
908
+ expect(last_message.message_serial).to eql(0)
909
+ channel.publish("second") do
910
+ expect(last_message.message_serial).to eql(1)
911
+ connection.once(:connected) do
912
+ channel.publish("first on new connection") do
913
+ # Message serial reset after failed resume
914
+ expect(last_message.message_serial).to eql(0)
915
+ stop_reactor
916
+ end
917
+ end
918
+
919
+ kill_connection_transport_and_prevent_valid_resume
920
+ end
921
+ end
622
922
  end
623
923
  end
624
924
  end
@@ -649,6 +949,137 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
649
949
  end
650
950
  end
651
951
  end
952
+
953
+ context 'when an ERROR protocol message is received' do
954
+ %w(connecting connected).each do |state|
955
+ state = state.to_sym
956
+ context "whilst #{state}" do
957
+ context 'with a token error code in the range 40140 <= code < 40150 (#RTN14b)' do
958
+ let(:client_options) { default_options.merge(use_token_auth: true) }
959
+
960
+ it 'triggers a re-authentication' do
961
+ connection.once(state) do
962
+ current_token = client.auth.current_token_details
963
+
964
+ error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i, error: { code: 40140 })
965
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
966
+
967
+ connection.once(:connected) do
968
+ expect(client.auth.current_token_details).to_not eql(current_token)
969
+ stop_reactor
970
+ end
971
+ end
972
+ end
973
+ end
974
+
975
+ context 'with an error code indicating an error other than a token failure (#RTN14g, #RTN15i)' do
976
+ it 'causes the connection to fail' do
977
+ connection.once(state) do
978
+ connection.once(:failed) do
979
+ stop_reactor
980
+ end
981
+
982
+ error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i, error: { code: 50000 })
983
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
984
+ end
985
+ end
986
+ end
987
+
988
+ context 'with no error code indicating an error other than a token failure (#RTN14g, #RTN15i)' do
989
+ it 'causes the connection to fail' do
990
+ connection.once(state) do
991
+ connection.once(:failed) do
992
+ stop_reactor
993
+ end
994
+
995
+ error_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Error.to_i)
996
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, error_message
997
+ end
998
+ end
999
+ end
1000
+ end
1001
+ end
1002
+ end
1003
+
1004
+ context "whilst resuming" do
1005
+ context "with a token error code in the region 40140 <= code < 40150 (#{}RTN15c5)" do
1006
+ before do
1007
+ stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', 0 # allow token to be used even if about to expire
1008
+ stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0) # Ensure tokens issued expire immediately after issue
1009
+ end
1010
+
1011
+ let!(:four_second_token) {
1012
+ rest_client.auth.request_token(ttl: 4).token
1013
+ }
1014
+
1015
+ let!(:normal_token) {
1016
+ rest_client.auth.request_token.token
1017
+ }
1018
+
1019
+ let(:client_options) do
1020
+ default_options.merge(auth_callback: Proc.new do
1021
+ @auth_requests ||= 0
1022
+ @auth_requests += 1
1023
+
1024
+ case @auth_requests
1025
+ when 1
1026
+ four_second_token
1027
+ when 2
1028
+ normal_token
1029
+ end
1030
+ end)
1031
+ end
1032
+
1033
+ it 'triggers a re-authentication and then resumes the connection' do
1034
+ connection.once(:connected) do
1035
+ connection_id = connection.id
1036
+
1037
+ connecting_attempts = 0
1038
+ connection.on(:connecting) { connecting_attempts += 1 }
1039
+
1040
+ connection.once(:connected) do
1041
+ expect(@auth_requests).to eql(2) # initial + reconnect fails due to expiry & then obtains new token
1042
+ expect(connecting_attempts).to eql(2) # reconnect with failed token, then reconnect with successful token
1043
+ expect(connection.id).to eql(connection_id)
1044
+ stop_reactor
1045
+ end
1046
+
1047
+ # Prevent token expired DISCONNECTED arriving on the transport
1048
+ # Instead we want to let the client lib catch a transport closed event
1049
+ # Then attempt to reconnect with an expired token
1050
+ connection.transport.__incoming_protocol_msgbus__.unsubscribe
1051
+
1052
+ EventMachine.next_tick do
1053
+ # Lock the EventMachine for 4 seconds until the token has expired
1054
+ sleep 5
1055
+
1056
+ # Simulate an abrupt disconnection which will in turn resume but with an expired token
1057
+ connection.transport.close_connection_after_writing
1058
+ end
1059
+ end
1060
+ end
1061
+ end
1062
+ end
1063
+
1064
+ context 'with any other error (#RTN15c4)' do
1065
+ it 'moves the connection to the failed state' do
1066
+ channel = client.channels.get("foo")
1067
+ channel.attach do
1068
+ connection.once(:failed) do |state_change|
1069
+ expect(state_change.reason.code).to eql(40400)
1070
+ expect(connection.error_reason.code).to eql(40400)
1071
+ expect(channel).to be_failed
1072
+ expect(channel.error_reason.code).to eql(40400)
1073
+ stop_reactor
1074
+ end
1075
+
1076
+ allow(client.rest_client.auth).to receive(:key).and_return("invalid.key:secret")
1077
+
1078
+ # Simulate an abrupt disconnection which will in turn resume with an invalid key
1079
+ connection.transport.close_connection_after_writing
1080
+ end
1081
+ end
1082
+ end
652
1083
  end
653
1084
 
654
1085
  describe 'fallback host feature' do