ably 0.6.2 → 0.7.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -1,211 +1,554 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
- require 'securerandom'
3
3
 
4
- describe Ably::Realtime::Channel do
5
- include RSpec::EventMachine
4
+ describe Ably::Realtime::Channel, :event_machine do
5
+ vary_by_protocol do
6
+ let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
7
+ let(:client_options) { default_options }
8
+
9
+ let(:client) { Ably::Realtime::Client.new(client_options) }
10
+ let(:channel_name) { random_str }
11
+ let(:payload) { random_str }
12
+ let(:channel) { client.channel(channel_name) }
13
+ let(:messages) { [] }
14
+
15
+ describe 'initialization' do
16
+ context 'with :connect_automatically option set to false on connection' do
17
+ let(:client) do
18
+ Ably::Realtime::Client.new(default_options.merge(connect_automatically: false))
19
+ end
6
20
 
7
- [:msgpack, :json].each do |protocol|
8
- context "over #{protocol}" do
9
- let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
21
+ it 'remains initialized when accessing a channel' do
22
+ client.channel('test')
23
+ EventMachine.add_timer(2) do
24
+ expect(client.connection).to be_initialized
25
+ stop_reactor
26
+ end
27
+ end
10
28
 
11
- let(:client) { Ably::Realtime::Client.new(default_options) }
12
- let(:channel_name) { SecureRandom.hex(2) }
13
- let(:payload) { SecureRandom.hex(4) }
14
- let(:channel) { client.channel(channel_name) }
15
- let(:messages) { [] }
29
+ it 'opens a connection implicitly on #attach' do
30
+ client.channel('test').attach do
31
+ expect(client.connection).to be_connected
32
+ stop_reactor
33
+ end
34
+ end
16
35
 
17
- it 'attaches to a channel' do
18
- run_reactor do
36
+ it 'opens a connection implicitly when accessing #presence' do
37
+ client.channel('test').tap do |channel|
38
+ channel.on(:attached) do
39
+ expect(client.connection).to be_connected
40
+ stop_reactor
41
+ end
42
+ channel.presence
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#attach' do
49
+ it 'emits attaching then attached events' do
50
+ channel.once(:attaching) do
51
+ channel.once(:attached) do
52
+ stop_reactor
53
+ end
54
+ end
55
+
56
+ channel.attach
57
+ end
58
+
59
+ it 'ignores subsequent #attach calls but calls the success callback if provided' do
60
+ channel.once(:attaching) do
19
61
  channel.attach
20
- channel.on(:attached) do
21
- expect(channel.state).to eq(:attached)
62
+ channel.once(:attached) do
63
+ channel.attach do
64
+ stop_reactor
65
+ end
66
+ end
67
+ end
68
+
69
+ channel.attach
70
+ end
71
+
72
+ it 'attaches to a channel' do
73
+ channel.attach
74
+ channel.on(:attached) do
75
+ expect(channel.state).to eq(:attached)
76
+ stop_reactor
77
+ end
78
+ end
79
+
80
+ it 'attaches to a channel and calls the provided block' do
81
+ channel.attach do
82
+ expect(channel.state).to eq(:attached)
83
+ stop_reactor
84
+ end
85
+ end
86
+
87
+ it 'returns a Deferrable' do
88
+ expect(channel.attach).to be_a(EventMachine::Deferrable)
89
+ stop_reactor
90
+ end
91
+
92
+ it 'calls the Deferrable callback on success' do
93
+ channel.attach.callback do |channel|
94
+ expect(channel).to be_a(Ably::Realtime::Channel)
95
+ expect(channel.state).to eq(:attached)
96
+ stop_reactor
97
+ end
98
+ end
99
+
100
+ context 'when state is :failed' do
101
+ let(:client_options) { default_options.merge(log_level: :fatal) }
102
+
103
+ it 'reattaches' do
104
+ channel.attach do
105
+ channel.transition_state_machine :failed, RuntimeError.new
106
+ expect(channel).to be_failed
107
+ channel.attach do
108
+ expect(channel).to be_attached
109
+ stop_reactor
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ context 'when state is :detaching' do
116
+ it 'moves straight to attaching and skips detached' do
117
+ channel.once(:detaching) do
118
+ channel.once(:detached) { raise 'Detach should not have been reached' }
119
+
120
+ channel.once(:attaching) do
121
+ channel.once(:attached) do
122
+ channel.off
123
+ stop_reactor
124
+ end
125
+ end
126
+
127
+ channel.attach
128
+ end
129
+
130
+ channel.attach do
131
+ channel.detach
132
+ end
133
+ end
134
+ end
135
+
136
+ context 'with many connections and many channels on each simultaneously' do
137
+ let(:connection_count) { 30 }
138
+ let(:channel_count) { 10 }
139
+ let(:permutation_count) { connection_count * channel_count }
140
+ let(:channel_connection_ids) { [] }
141
+
142
+ it 'attaches all channels', em_timeout: 15 do
143
+ connection_count.times.map do
144
+ Ably::Realtime::Client.new(default_options)
145
+ end.each do |client|
146
+ channel_count.times.map do |index|
147
+ client.channel("channel-#{index}").attach do
148
+ channel_connection_ids << "#{client.connection.id}:#{index}"
149
+ next unless channel_connection_ids.count == permutation_count
150
+
151
+ expect(channel_connection_ids.uniq.count).to eql(permutation_count)
152
+ stop_reactor
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ context 'failure as a result of insufficient key permissions' do
160
+ let(:restricted_client) do
161
+ Ably::Realtime::Client.new(default_options.merge(api_key: restricted_api_key, log_level: :fatal))
162
+ end
163
+ let(:restricted_channel) { restricted_client.channel("cannot_subscribe") }
164
+
165
+ it 'triggers failed event' do
166
+ restricted_channel.attach
167
+ restricted_channel.on(:failed) do |error|
168
+ expect(restricted_channel.state).to eq(:failed)
169
+ expect(error.status).to eq(401)
170
+ stop_reactor
171
+ end
172
+ end
173
+
174
+ it 'calls the errback of the returned Deferrable' do
175
+ restricted_channel.attach.errback do |channel, error|
176
+ expect(restricted_channel.state).to eq(:failed)
177
+ expect(error.status).to eq(401)
178
+ stop_reactor
179
+ end
180
+ end
181
+
182
+ it 'triggers an error event' do
183
+ restricted_channel.attach
184
+ restricted_channel.on(:error) do |error|
185
+ expect(restricted_channel.state).to eq(:failed)
186
+ expect(error.status).to eq(401)
187
+ stop_reactor
188
+ end
189
+ end
190
+
191
+ it 'updates the error_reason' do
192
+ restricted_channel.attach
193
+ restricted_channel.on(:failed) do
194
+ expect(restricted_channel.error_reason.status).to eq(401)
195
+ stop_reactor
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#detach' do
202
+ it 'detaches from a channel' do
203
+ channel.attach do
204
+ channel.detach
205
+ channel.on(:detached) do
206
+ expect(channel.state).to eq(:detached)
207
+ stop_reactor
208
+ end
209
+ end
210
+ end
211
+
212
+ it 'detaches from a channel and calls the provided block' do
213
+ channel.attach do |chan|
214
+ chan.detach do |detached_chan|
215
+ expect(detached_chan.state).to eq(:detached)
216
+ stop_reactor
217
+ end
218
+ end
219
+ end
220
+
221
+ it 'emits :detaching then :detached events' do
222
+ channel.once(:detaching) do
223
+ channel.once(:detached) do
224
+ stop_reactor
225
+ end
226
+ end
227
+
228
+ channel.attach do
229
+ channel.detach
230
+ end
231
+ end
232
+
233
+ it 'returns a Deferrable' do
234
+ expect(channel.attach).to be_a(EventMachine::Deferrable)
235
+ stop_reactor
236
+ end
237
+
238
+ it 'calls the Deferrable callback on success' do
239
+ channel.attach do
240
+ channel.detach.callback do |channel|
241
+ expect(channel).to be_a(Ably::Realtime::Channel)
242
+ expect(channel.state).to eq(:detached)
22
243
  stop_reactor
23
244
  end
24
245
  end
25
246
  end
26
247
 
27
- it 'attaches to a channel with a block' do
28
- run_reactor do
248
+ context 'when state is :failed' do
249
+ let(:client_options) { default_options.merge(log_level: :fatal) }
250
+
251
+ it 'raises an exception' do
29
252
  channel.attach do
30
- expect(channel.state).to eq(:attached)
253
+ channel.transition_state_machine :failed, RuntimeError.new
254
+ expect(channel).to be_failed
255
+ expect { channel.detach }.to raise_error Ably::Exceptions::StateChangeError
31
256
  stop_reactor
32
257
  end
33
258
  end
34
259
  end
35
260
 
36
- it 'detaches from a channel with a block' do
37
- run_reactor do
38
- channel.attach do |chan|
39
- chan.detach do |detached_chan|
40
- expect(detached_chan.state).to eq(:detached)
41
- stop_reactor
261
+ context 'when state is :attaching' do
262
+ it 'moves straight to :detaching state and skips :attached' do
263
+ channel.once(:attaching) do
264
+ channel.once(:attached) { raise 'Attached should never be reached' }
265
+
266
+ channel.once(:detaching) do
267
+ channel.once(:detached) do
268
+ stop_reactor
269
+ end
42
270
  end
271
+
272
+ channel.detach
43
273
  end
274
+
275
+ channel.attach
44
276
  end
45
277
  end
46
278
 
47
- it 'publishes 3 messages once attached' do
48
- run_reactor do
49
- channel.attach do
50
- 3.times { channel.publish('event', payload) }
279
+ context 'when state is :detaching' do
280
+ it 'ignores subsequent #detach calls but calls the callback if provided' do
281
+ channel.once(:detaching) do
282
+ channel.detach
283
+ channel.once(:detached) do
284
+ channel.detach do
285
+ stop_reactor
286
+ end
287
+ end
51
288
  end
52
- channel.subscribe do |message|
53
- messages << message if message.data == payload
54
- stop_reactor if messages.length == 3
289
+
290
+ channel.attach do
291
+ channel.detach
55
292
  end
56
293
  end
294
+ end
295
+ end
57
296
 
58
- expect(messages.count).to eql(3)
297
+ describe 'channel recovery in :attaching state' do
298
+ context 'the transport is disconnected before the ATTACHED protocol message is received' do
299
+ skip 'attach times out and fails if not ATTACHED protocol message received'
300
+ skip 'channel is ATTACHED if ATTACHED protocol message is later received'
301
+ skip 'sends an ATTACH protocol message in response to a channel message being received on the attaching channel'
59
302
  end
303
+ end
60
304
 
61
- it 'publishes 3 messages from queue before attached' do
62
- run_reactor do
63
- 3.times { channel.publish('event', SecureRandom.hex) }
305
+ context '#publish' do
306
+ context 'when attached' do
307
+ it 'publishes messages' do
308
+ channel.attach do
309
+ 3.times { channel.publish('event', payload) }
310
+ end
64
311
  channel.subscribe do |message|
65
- messages << message if message.name == 'event'
66
- stop_reactor if messages.length == 3
312
+ messages << message if message.data == payload
313
+ stop_reactor if messages.count == 3
67
314
  end
68
315
  end
69
-
70
- expect(messages.count).to eql(3)
71
316
  end
72
317
 
73
- it 'publishes 3 messages from queue before attached in a single protocol message' do
74
- run_reactor do
75
- 3.times { channel.publish('event', SecureRandom.hex) }
318
+ context 'when not yet attached' do
319
+ it 'publishes queued messages once attached' do
320
+ 3.times { channel.publish('event', random_str) }
76
321
  channel.subscribe do |message|
77
322
  messages << message if message.name == 'event'
78
- stop_reactor if messages.length == 3
323
+ stop_reactor if messages.count == 3
79
324
  end
80
325
  end
81
326
 
82
- # All 3 messages should be batched into a single Protocol Message by the client library
83
- # message.id = "{protocol_message.id}:{protocol_message_index}"
327
+ it 'publishes queued messages within a single protocol message' do
328
+ 3.times { channel.publish('event', random_str) }
329
+ channel.subscribe do |message|
330
+ messages << message if message.name == 'event'
331
+ next unless messages.length == 3
84
332
 
85
- # Check that all messages share the same message_serial
86
- message_id = messages.map { |msg| msg.id.split(':')[0] }
87
- expect(message_id.uniq.count).to eql(1)
333
+ # All 3 messages should be batched into a single Protocol Message by the client library
334
+ # message.id = "{protocol_message.id}:{protocol_message_index}"
335
+ # Check that all messages share the same protocol_message.id
336
+ message_id = messages.map { |msg| msg.id.split(':')[0] }
337
+ expect(message_id.uniq.count).to eql(1)
88
338
 
89
- # Check that all messages use message index 0,1,2
90
- message_indexes = messages.map { |msg| msg.id.split(':')[1] }
91
- expect(message_indexes).to include("0", "1", "2")
339
+ # Check that messages use index 0,1,2 in the ID
340
+ message_indexes = messages.map { |msg| msg.id.split(':')[1] }
341
+ expect(message_indexes).to include("0", "1", "2")
342
+ stop_reactor
343
+ end
344
+ end
92
345
  end
346
+ end
93
347
 
94
- it 'subscribes and unsubscribes' do
95
- run_reactor do
348
+ describe '#subscribe' do
349
+ context 'with an event argument' do
350
+ it 'subscribes for a single event' do
96
351
  channel.subscribe('click') do |message|
97
- messages << message
352
+ expect(message.data).to eql('data')
353
+ stop_reactor
98
354
  end
99
- channel.attach do
100
- channel.unsubscribe('click')
101
- channel.publish('click', 'data')
102
- EventMachine.add_timer(2) do
103
- stop_reactor
104
- expect(messages.length).to eql(0)
105
- end
355
+ channel.publish('click', 'data')
356
+ end
357
+ end
358
+
359
+ context 'with no event argument' do
360
+ it 'subscribes for all events' do
361
+ channel.subscribe do |message|
362
+ expect(message.data).to eql('data')
363
+ stop_reactor
106
364
  end
365
+ channel.publish('click', 'data')
107
366
  end
108
367
  end
109
368
 
110
- it 'subscribes and unsubscribes from multiple channels' do
111
- run_reactor do
112
- click_callback = -> (message) { messages << message }
369
+ context 'many times with different event names' do
370
+ it 'filters events accordingly to each callback' do
371
+ click_callback = proc { |message| messages << message }
113
372
 
114
373
  channel.subscribe('click', &click_callback)
115
374
  channel.subscribe('move', &click_callback)
116
375
  channel.subscribe('press', &click_callback)
117
376
 
118
377
  channel.attach do
119
- channel.unsubscribe('click')
120
- channel.unsubscribe('move', &click_callback)
121
- channel.unsubscribe('press') { this_callback_is_not_subscribed_so_ignored }
122
-
123
378
  channel.publish('click', 'data')
124
379
  channel.publish('move', 'data')
125
- channel.publish('press', 'data')
380
+ channel.publish('press', 'data') do
381
+ EventMachine.add_timer(2) do
382
+ expect(messages.count).to eql(3)
383
+ stop_reactor
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ describe '#unsubscribe' do
392
+ context 'with an event argument' do
393
+ it 'unsubscribes for a single event' do
394
+ channel.subscribe('click') { raise 'Should not have been called' }
395
+ channel.unsubscribe('click')
126
396
 
127
- EventMachine.add_timer(2) do
397
+ channel.publish('click', 'data') do
398
+ EventMachine.add_timer(1) do
128
399
  stop_reactor
129
- # Only the press subscribe callback should still be subscribed
130
- expect(messages.length).to eql(1)
131
400
  end
132
401
  end
133
402
  end
134
403
  end
135
404
 
136
- it 'opens many connections and then many channels simultaneously' do
137
- run_reactor(15) do
138
- count, connected_ids = 25, []
405
+ context 'with no event argument' do
406
+ it 'unsubscribes for a single event' do
407
+ channel.subscribe { raise 'Should not have been called' }
408
+ channel.unsubscribe
139
409
 
140
- clients = count.times.map do
141
- Ably::Realtime::Client.new(default_options)
410
+ channel.publish('click', 'data') do
411
+ EventMachine.add_timer(1) do
412
+ stop_reactor
413
+ end
142
414
  end
415
+ end
416
+ end
417
+ end
143
418
 
144
- channels_opened = 0
145
- open_channels_on_clients = Proc.new do
146
- 5.times.each do |channel|
147
- clients.each do |client|
148
- client.channel("channel-#{channel}").attach do
149
- channels_opened += 1
150
- if channels_opened == clients.count * 5
151
- expect(channels_opened).to eql(clients.count * 5)
152
- stop_reactor
153
- end
154
- end
419
+ context 'when connection state changes to' do
420
+ context ':failed' do
421
+ let(:connection_error) { Ably::Exceptions::ConnectionError.new('forced failure', 500, 50000) }
422
+ let(:client_options) { default_options.merge(log_level: :none) }
423
+
424
+ def fake_error(error)
425
+ client.connection.manager.error_received_from_server error
426
+ end
427
+
428
+ context 'an :attached channel' do
429
+ it 'transitions state to :failed' do
430
+ channel.attach do
431
+ channel.on(:failed) do |error|
432
+ expect(error).to eql(connection_error)
433
+ stop_reactor
155
434
  end
435
+ fake_error connection_error
156
436
  end
157
437
  end
158
438
 
159
- clients.each do |client|
160
- client.connection.on(:connected) do
161
- connected_ids << client.connection.id
439
+ it 'triggers an error event on the channel' do
440
+ channel.attach do
441
+ channel.on(:error) do |error|
442
+ expect(error).to eql(connection_error)
443
+ stop_reactor
444
+ end
445
+ fake_error connection_error
446
+ end
447
+ end
162
448
 
163
- if connected_ids.count == 25
164
- expect(connected_ids.uniq.count).to eql(25)
165
- open_channels_on_clients.call
449
+ it 'updates the channel error_reason' do
450
+ channel.attach do
451
+ channel.on(:failed) do |error|
452
+ expect(channel.error_reason).to eql(connection_error)
453
+ stop_reactor
166
454
  end
455
+ fake_error connection_error
167
456
  end
168
457
  end
169
458
  end
170
- end
171
459
 
172
- it 'opens many connections and attaches to channels before connected' do
173
- run_reactor(15) do
174
- count, connected_ids = 25, []
460
+ context 'a :detached channel' do
461
+ it 'remains in the :detached state' do
462
+ channel.attach do
463
+ channel.on(:failed) { raise 'Failed state should not have been reached' }
464
+ channel.on(:error) { raise 'Error should not have been emitted' }
175
465
 
176
- clients = count.times.map do
177
- Ably::Realtime::Client.new(default_options)
466
+ channel.detach do
467
+ EventMachine.add_timer(1) do
468
+ expect(channel).to be_detached
469
+ stop_reactor
470
+ end
471
+
472
+ fake_error connection_error
473
+ end
474
+ end
178
475
  end
476
+ end
179
477
 
180
- channels_opened = 0
478
+ context 'a :failed channel' do
479
+ let(:original_error) { RuntimeError.new }
181
480
 
182
- clients.each do |client|
183
- 5.times.each do |channel|
184
- client.channel("channel-#{channel}").attach do
185
- channels_opened += 1
186
- if channels_opened == clients.count * 5
187
- expect(channels_opened).to eql(clients.count * 5)
481
+ it 'remains in the :failed state and ignores the failure error' do
482
+ channel.attach do
483
+ channel.on(:error) do
484
+ channel.on(:failed) { raise 'Failed state should not have been reached' }
485
+ channel.on(:error) { raise 'Error should not have been emitted' }
486
+
487
+ EventMachine.add_timer(1) do
488
+ expect(channel).to be_failed
489
+ expect(channel.error_reason).to eql(original_error)
188
490
  stop_reactor
189
491
  end
492
+
493
+ fake_error connection_error
190
494
  end
495
+
496
+ channel.transition_state_machine :failed, original_error
191
497
  end
192
498
  end
193
499
  end
194
500
  end
195
501
 
196
- context 'attach failure' do
197
- let(:restricted_client) do
198
- Ably::Realtime::Client.new(default_options.merge(api_key: restricted_api_key))
502
+ context ':closed' do
503
+ context 'an :attached channel' do
504
+ it 'transitions state to :detached' do
505
+ channel.attach do
506
+ channel.on(:detached) do
507
+ stop_reactor
508
+ end
509
+ client.connection.close
510
+ end
511
+ end
199
512
  end
200
- let(:restricted_channel) { restricted_client.channel("cannot_subscribe") }
201
513
 
202
- it 'triggers failed event' do
203
- run_reactor do
204
- restricted_channel.attach
205
- restricted_channel.on(:failed) do |error|
206
- expect(restricted_channel.state).to eq(:failed)
207
- expect(error.status).to eq(401)
208
- stop_reactor
514
+ context 'a :detached channel' do
515
+ it 'remains in the :detached state' do
516
+ channel.attach do
517
+ channel.detach do
518
+ channel.on(:detached) { raise 'Detached state should not have been reached' }
519
+ channel.on(:error) { raise 'Error should not have been emitted' }
520
+
521
+ EventMachine.add_timer(1) do
522
+ expect(channel).to be_detached
523
+ stop_reactor
524
+ end
525
+
526
+ client.connection.close
527
+ end
528
+ end
529
+ end
530
+ end
531
+
532
+ context 'a :failed channel' do
533
+ let(:original_error) { RuntimeError.new }
534
+ let(:client_options) { default_options.merge(log_level: :fatal) }
535
+
536
+ it 'remains in the :failed state and retains the error_reason' do
537
+ channel.attach do
538
+ channel.once(:error) do
539
+ channel.on(:detached) { raise 'Detached state should not have been reached' }
540
+ channel.on(:error) { raise 'Error should not have been emitted' }
541
+
542
+ EventMachine.add_timer(1) do
543
+ expect(channel).to be_failed
544
+ expect(channel.error_reason).to eql(original_error)
545
+ stop_reactor
546
+ end
547
+
548
+ client.connection.close
549
+ end
550
+
551
+ channel.transition_state_machine :failed, original_error
209
552
  end
210
553
  end
211
554
  end
@@ -213,5 +556,3 @@ describe Ably::Realtime::Channel do
213
556
  end
214
557
  end
215
558
  end
216
-
217
-