ably 0.7.2 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.md +107 -24
- data/SPEC.md +531 -398
- data/lib/ably/auth.rb +23 -15
- data/lib/ably/exceptions.rb +9 -0
- data/lib/ably/models/message.rb +17 -9
- data/lib/ably/models/paginated_resource.rb +12 -8
- data/lib/ably/models/presence_message.rb +18 -10
- data/lib/ably/models/protocol_message.rb +15 -4
- data/lib/ably/modules/async_wrapper.rb +4 -3
- data/lib/ably/modules/event_emitter.rb +31 -2
- data/lib/ably/modules/message_emitter.rb +77 -0
- data/lib/ably/modules/safe_deferrable.rb +71 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +28 -8
- data/lib/ably/realtime.rb +0 -5
- data/lib/ably/realtime/channel.rb +24 -29
- data/lib/ably/realtime/channel/channel_manager.rb +54 -11
- data/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
- data/lib/ably/realtime/client.rb +7 -2
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
- data/lib/ably/realtime/connection.rb +41 -9
- data/lib/ably/realtime/connection/connection_manager.rb +72 -24
- data/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
- data/lib/ably/realtime/connection/websocket_transport.rb +19 -6
- data/lib/ably/realtime/presence.rb +74 -208
- data/lib/ably/realtime/presence/members_map.rb +264 -0
- data/lib/ably/realtime/presence/presence_manager.rb +59 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
- data/lib/ably/rest/channel.rb +1 -1
- data/lib/ably/rest/client.rb +6 -2
- data/lib/ably/rest/presence.rb +1 -1
- data/lib/ably/util/pub_sub.rb +3 -1
- data/lib/ably/util/safe_deferrable.rb +18 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +2 -2
- data/spec/acceptance/realtime/channel_spec.rb +28 -6
- data/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
- data/spec/acceptance/realtime/connection_spec.rb +55 -10
- data/spec/acceptance/realtime/message_spec.rb +32 -0
- data/spec/acceptance/realtime/presence_spec.rb +456 -96
- data/spec/acceptance/realtime/stats_spec.rb +2 -2
- data/spec/acceptance/realtime/time_spec.rb +2 -2
- data/spec/acceptance/rest/auth_spec.rb +75 -7
- data/spec/shared/client_initializer_behaviour.rb +8 -0
- data/spec/shared/safe_deferrable_behaviour.rb +71 -0
- data/spec/support/api_helper.rb +1 -1
- data/spec/support/event_machine_helper.rb +1 -1
- data/spec/support/test_app.rb +13 -7
- data/spec/unit/models/message_spec.rb +15 -14
- data/spec/unit/models/paginated_resource_spec.rb +4 -4
- data/spec/unit/models/presence_message_spec.rb +17 -17
- data/spec/unit/models/stat_spec.rb +4 -4
- data/spec/unit/modules/async_wrapper_spec.rb +28 -9
- data/spec/unit/modules/event_emitter_spec.rb +50 -0
- data/spec/unit/modules/state_emitter_spec.rb +76 -2
- data/spec/unit/realtime/channel_spec.rb +51 -20
- data/spec/unit/realtime/channels_spec.rb +3 -3
- data/spec/unit/realtime/connection_spec.rb +30 -0
- data/spec/unit/realtime/presence_spec.rb +52 -26
- data/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
- metadata +85 -39
- checksums.yaml +0 -7
- data/.ruby-version.old +0 -1
@@ -2,6 +2,8 @@
|
|
2
2
|
require 'spec_helper'
|
3
3
|
|
4
4
|
describe Ably::Realtime::Presence, :event_machine do
|
5
|
+
include Ably::Modules::Conversions
|
6
|
+
|
5
7
|
vary_by_protocol do
|
6
8
|
let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
7
9
|
let(:client_options) { default_options }
|
@@ -20,6 +22,98 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
20
22
|
let(:presence_client_two) { channel_client_two.presence }
|
21
23
|
let(:data_payload) { random_str }
|
22
24
|
|
25
|
+
def force_connection_failure(client)
|
26
|
+
# Prevent any further SYNC messages coming in on this connection
|
27
|
+
client.connection.transport.send(:driver).remove_all_listeners('message')
|
28
|
+
client.connection.transport.unbind
|
29
|
+
end
|
30
|
+
|
31
|
+
shared_examples_for 'a public presence method' do |method_name, expected_state, args, options = {}|
|
32
|
+
def setup_test(method_name, args, options)
|
33
|
+
if options[:enter_first]
|
34
|
+
presence_client_one.public_send(method_name.to_s.gsub(/leave|update/, 'enter'), args) do
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
else
|
38
|
+
yield
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
unless expected_state == :left
|
43
|
+
%w(detached failed).each do |state|
|
44
|
+
it "raise an exception if the channel is #{state}" do
|
45
|
+
setup_test(method_name, args, options) do
|
46
|
+
channel_client_one.attach do
|
47
|
+
channel_client_one.change_state state.to_sym
|
48
|
+
expect { presence_client_one.public_send(method_name, args) }.to raise_error Ably::Exceptions::IncompatibleStateForOperation, /Operation is not allowed when channel is in STATE.#{state}/i
|
49
|
+
stop_reactor
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
|
57
|
+
setup_test(method_name, args, options) do
|
58
|
+
expect(presence_client_one.public_send(method_name, args)).to be_a(Ably::Util::SafeDeferrable)
|
59
|
+
stop_reactor
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'calls the Deferrable callback on success' do
|
64
|
+
setup_test(method_name, args, options) do
|
65
|
+
presence_client_one.public_send(method_name, args).callback do |presence|
|
66
|
+
expect(presence).to eql(presence_client_one)
|
67
|
+
expect(presence_client_one.state).to eq(expected_state) if expected_state
|
68
|
+
stop_reactor
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'catches exceptions in the provided method block and logs them to the logger' do
|
74
|
+
setup_test(method_name, args, options) do
|
75
|
+
expect(presence_client_one.logger).to receive(:error).with(/Intentional exception/) do
|
76
|
+
stop_reactor
|
77
|
+
end
|
78
|
+
presence_client_one.public_send(method_name, args) { raise 'Intentional exception' }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'if connection fails before success' do
|
83
|
+
before do
|
84
|
+
# Reconfigure client library so that it makes no retry attempts and fails immediately
|
85
|
+
stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
|
86
|
+
Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
|
87
|
+
disconnected: { retry_every: 0.1, max_time_in_state: 0 },
|
88
|
+
suspended: { retry_every: 0.1, max_time_in_state: 0 }
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:client_options) { default_options.merge(log_level: :none) }
|
93
|
+
|
94
|
+
it 'calls the Deferrable errback if channel is detached' do
|
95
|
+
setup_test(method_name, args, options) do
|
96
|
+
channel_client_one.attach do
|
97
|
+
client_one.connection.__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
98
|
+
# Don't allow any messages to reach the server
|
99
|
+
client_one.connection.__outgoing_protocol_msgbus__.unsubscribe
|
100
|
+
force_connection_failure client_one
|
101
|
+
end
|
102
|
+
|
103
|
+
presence_client_one.public_send(method_name, args).tap do |deferrable|
|
104
|
+
deferrable.callback { raise 'Should not succeed' }
|
105
|
+
deferrable.errback do |presence, error|
|
106
|
+
expect(presence).to be_a(Ably::Realtime::Presence)
|
107
|
+
expect(error).to be_kind_of(Ably::Exceptions::BaseAblyException)
|
108
|
+
stop_reactor
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
23
117
|
context 'when attached (but not present) on a presence channel with an anonymous client (no client ID)' do
|
24
118
|
it 'maintains state as other clients enter and leave the channel' do
|
25
119
|
channel_anonymous_client.attach do
|
@@ -30,11 +124,11 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
30
124
|
expect(members.first.client_id).to eql(client_one.client_id)
|
31
125
|
expect(members.first.action).to eq(:enter)
|
32
126
|
|
33
|
-
presence_anonymous_client.subscribe(:leave) do |
|
34
|
-
expect(
|
127
|
+
presence_anonymous_client.subscribe(:leave) do |leave_presence_message|
|
128
|
+
expect(leave_presence_message.client_id).to eql(client_one.client_id)
|
35
129
|
|
36
|
-
presence_anonymous_client.get do |
|
37
|
-
expect(
|
130
|
+
presence_anonymous_client.get do |members_once_left|
|
131
|
+
expect(members_once_left.count).to eql(0)
|
38
132
|
stop_reactor
|
39
133
|
end
|
40
134
|
end
|
@@ -48,6 +142,49 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
48
142
|
end
|
49
143
|
end
|
50
144
|
|
145
|
+
context '#members map', api_private: true do
|
146
|
+
it 'is available once the channel is created' do
|
147
|
+
expect(presence_anonymous_client.members).to_not be_nil
|
148
|
+
stop_reactor
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'is not synchronised when initially created' do
|
152
|
+
expect(presence_anonymous_client.members).to_not be_sync_complete
|
153
|
+
stop_reactor
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'will trigger an :in_sync event when synchronisation is complete' do
|
157
|
+
presence_client_one.enter
|
158
|
+
presence_client_two.enter
|
159
|
+
|
160
|
+
presence_anonymous_client.members.once(:in_sync) do
|
161
|
+
stop_reactor
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'before server sync complete' do
|
166
|
+
it 'behaves like an Enumerable allowing direct access to current members' do
|
167
|
+
expect(presence_anonymous_client.members.count).to eql(0)
|
168
|
+
expect(presence_anonymous_client.members.map(&:member_key)).to eql([])
|
169
|
+
stop_reactor
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'once server sync is complete' do
|
174
|
+
it 'behaves like an Enumerable allowing direct access to current members' do
|
175
|
+
when_all(presence_client_one.enter, presence_client_two.enter) do
|
176
|
+
presence_anonymous_client.members.once(:in_sync) do
|
177
|
+
expect(presence_anonymous_client.members.count).to eql(2)
|
178
|
+
member_ids = presence_anonymous_client.members.map(&:member_key)
|
179
|
+
expect(member_ids.count).to eql(2)
|
180
|
+
expect(member_ids.uniq.count).to eql(2)
|
181
|
+
stop_reactor
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
51
188
|
context '#sync_complete?' do
|
52
189
|
context 'when attaching to a channel without any members present' do
|
53
190
|
it 'is true and the presence channel is considered synced immediately' do
|
@@ -73,33 +210,162 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
73
210
|
end
|
74
211
|
end
|
75
212
|
|
76
|
-
context '
|
77
|
-
context '
|
213
|
+
context '250 existing (present) members on a channel (3 SYNC pages)' do
|
214
|
+
context 'requires at least 3 SYNC ProtocolMessages' do
|
78
215
|
let(:enter_expected_count) { 250 }
|
79
216
|
let(:present) { [] }
|
80
217
|
let(:entered) { [] }
|
218
|
+
let(:sync_pages_received) { [] }
|
219
|
+
|
220
|
+
def setup_members_on(presence)
|
221
|
+
enter_expected_count.times do |index|
|
222
|
+
presence.enter_client("client:#{index}") do |message|
|
223
|
+
entered << message
|
224
|
+
next unless entered.count == enter_expected_count
|
225
|
+
yield
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
81
229
|
|
82
|
-
context 'when a
|
230
|
+
context 'when a client attaches to the presence channel', em_timeout: 10 do
|
83
231
|
it 'emits :present for each member' do
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
232
|
+
setup_members_on(presence_client_one) do
|
233
|
+
presence_anonymous_client.subscribe(:present) do |present_message|
|
234
|
+
expect(present_message.action).to eq(:present)
|
235
|
+
present << present_message
|
236
|
+
next unless present.count == enter_expected_count
|
237
|
+
|
238
|
+
expect(present.map(&:client_id).uniq.count).to eql(enter_expected_count)
|
239
|
+
stop_reactor
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context 'and a member leaves before the SYNC operation is complete' do
|
245
|
+
it 'emits :leave immediately as the member leaves' do
|
246
|
+
all_client_ids = enter_expected_count.times.map { |id| "client:#{id}" }
|
247
|
+
|
248
|
+
setup_members_on(presence_client_one) do
|
249
|
+
leave_member = nil
|
88
250
|
|
89
251
|
presence_anonymous_client.subscribe(:present) do |present_message|
|
90
|
-
expect(present_message.action).to eq(:present)
|
91
252
|
present << present_message
|
92
|
-
|
253
|
+
all_client_ids.delete(present_message.client_id)
|
254
|
+
end
|
93
255
|
|
94
|
-
|
256
|
+
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
257
|
+
expect(leave_message.client_id).to eql(leave_member.client_id)
|
258
|
+
expect(present.count).to be < enter_expected_count
|
95
259
|
stop_reactor
|
96
260
|
end
|
261
|
+
|
262
|
+
anonymous_client.connect do
|
263
|
+
anonymous_client.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
264
|
+
if protocol_message.action == :sync
|
265
|
+
sync_pages_received << protocol_message
|
266
|
+
if sync_pages_received.count == 1
|
267
|
+
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
268
|
+
leave_member = Ably::Models::PresenceMessage.new(
|
269
|
+
'id' => "#{client_one.connection.id}-#{all_client_ids.first}:0",
|
270
|
+
'clientId' => all_client_ids.first,
|
271
|
+
'connectionId' => client_one.connection.id,
|
272
|
+
'timestamp' => as_since_epoch(Time.now),
|
273
|
+
'action' => leave_action
|
274
|
+
)
|
275
|
+
presence_anonymous_client.__incoming_msgbus__.publish :presence, leave_member
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'ignores presence events with timestamps prior to the current :present event in the MembersMap' do
|
284
|
+
started_at = Time.now
|
285
|
+
|
286
|
+
setup_members_on(presence_client_one) do
|
287
|
+
leave_member = nil
|
288
|
+
|
289
|
+
presence_anonymous_client.subscribe(:present) do |present_message|
|
290
|
+
present << present_message
|
291
|
+
leave_member = present_message unless leave_member
|
292
|
+
|
293
|
+
if present.count == enter_expected_count
|
294
|
+
presence_anonymous_client.get do |members|
|
295
|
+
expect(members.find { |member| member.client_id == leave_member.client_id}.action).to eq(:present)
|
296
|
+
stop_reactor
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
302
|
+
raise 'Leave event should not have been fired because it is out of date'
|
303
|
+
end
|
304
|
+
|
305
|
+
anonymous_client.connect do
|
306
|
+
anonymous_client.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
307
|
+
if protocol_message.action == :sync
|
308
|
+
sync_pages_received << protocol_message
|
309
|
+
if sync_pages_received.count == 1
|
310
|
+
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
311
|
+
leave_member = Ably::Models::PresenceMessage.new(
|
312
|
+
leave_member.as_json.merge('action' => leave_action, 'timestamp' => as_since_epoch(started_at))
|
313
|
+
)
|
314
|
+
presence_anonymous_client.__incoming_msgbus__.publish :presence, leave_member
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'does not emit :present after the :leave event has been emitted, and that member is not included in the list of members via #get' do
|
323
|
+
left_client = 10
|
324
|
+
left_client_id = "client:#{left_client}"
|
325
|
+
|
326
|
+
setup_members_on(presence_client_one) do
|
327
|
+
member_left_emitted = false
|
328
|
+
|
329
|
+
presence_anonymous_client.subscribe(:present) do |present_message|
|
330
|
+
if present_message.client_id == left_client_id
|
331
|
+
raise "Member #{present_message.client_id} should not have been emitted as present"
|
332
|
+
end
|
333
|
+
present << present_message.client_id
|
334
|
+
end
|
335
|
+
|
336
|
+
presence_anonymous_client.subscribe(:leave) do |leave_message|
|
337
|
+
if present.include?(leave_message.client_id)
|
338
|
+
raise "Member #{leave_message.client_id} should not have been emitted as present previously"
|
339
|
+
end
|
340
|
+
expect(leave_message.client_id).to eql(left_client_id)
|
341
|
+
member_left_emitted = true
|
342
|
+
end
|
343
|
+
|
344
|
+
presence_anonymous_client.get do |members|
|
345
|
+
expect(members.count).to eql(enter_expected_count - 1)
|
346
|
+
expect(member_left_emitted).to eql(true)
|
347
|
+
expect(members.map(&:client_id)).to_not include(left_client_id)
|
348
|
+
stop_reactor
|
349
|
+
end
|
350
|
+
|
351
|
+
channel_anonymous_client.attach do
|
352
|
+
leave_action = Ably::Models::PresenceMessage::ACTION.Leave
|
353
|
+
fake_leave_presence_message = Ably::Models::PresenceMessage.new(
|
354
|
+
'id' => "#{client_one.connection.id}-#{left_client_id}:0",
|
355
|
+
'clientId' => left_client_id,
|
356
|
+
'connectionId' => client_one.connection.id,
|
357
|
+
'timestamp' => as_since_epoch(Time.now),
|
358
|
+
'action' => leave_action
|
359
|
+
)
|
360
|
+
# Push out a LEAVE event directly to the Presence object before it's received the :present action via the SYNC ProtocolMessage
|
361
|
+
presence_anonymous_client.__incoming_msgbus__.publish :presence, fake_leave_presence_message
|
362
|
+
end
|
97
363
|
end
|
98
364
|
end
|
99
365
|
end
|
100
366
|
|
101
367
|
context '#get' do
|
102
|
-
it '
|
368
|
+
it 'waits until sync is complete', event_machine: 15 do
|
103
369
|
enter_expected_count.times do |index|
|
104
370
|
presence_client_one.enter_client("client:#{index}") do |message|
|
105
371
|
entered << message
|
@@ -119,7 +385,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
119
385
|
end
|
120
386
|
|
121
387
|
context 'automatic attachment of channel on access to presence object' do
|
122
|
-
it 'is implicit if presence state is
|
388
|
+
it 'is implicit if presence state is initialized' do
|
123
389
|
channel_client_one.presence
|
124
390
|
channel_client_one.on(:attached) do
|
125
391
|
expect(channel_client_one.state).to eq(:attached)
|
@@ -201,23 +467,40 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
201
467
|
end
|
202
468
|
end
|
203
469
|
|
470
|
+
context 'message #connection_id' do
|
471
|
+
it 'matches the current client connection_id' do
|
472
|
+
channel_client_two.attach do
|
473
|
+
presence_client_one.enter
|
474
|
+
end
|
475
|
+
|
476
|
+
presence_client_two.subscribe do |presence|
|
477
|
+
expect(presence.connection_id).to eq(client_one.connection.id)
|
478
|
+
stop_reactor
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
204
483
|
it 'raises an exception if client_id is not set' do
|
205
484
|
expect { channel_anonymous_client.presence.enter }.to raise_error(Ably::Exceptions::Standard, /without a client_id/)
|
206
485
|
stop_reactor
|
207
486
|
end
|
208
487
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
488
|
+
context 'without necessary capabilities to join presence' do
|
489
|
+
let(:restricted_client) do
|
490
|
+
Ably::Realtime::Client.new(default_options.merge(api_key: restricted_api_key, log_level: :fatal))
|
491
|
+
end
|
492
|
+
let(:restricted_channel) { restricted_client.channel("cansubscribe:channel") }
|
493
|
+
let(:restricted_presence) { restricted_channel.presence }
|
213
494
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
495
|
+
it 'calls the Deferrable errback on capabilities failure' do
|
496
|
+
restricted_presence.enter(client_id: 'clientId').tap do |deferrable|
|
497
|
+
deferrable.callback { raise "Should not succeed" }
|
498
|
+
deferrable.errback { stop_reactor }
|
499
|
+
end
|
219
500
|
end
|
220
501
|
end
|
502
|
+
|
503
|
+
it_should_behave_like 'a public presence method', :enter, :entered, {}
|
221
504
|
end
|
222
505
|
|
223
506
|
context '#update' do
|
@@ -266,22 +549,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
266
549
|
end
|
267
550
|
end
|
268
551
|
|
269
|
-
|
270
|
-
presence_client_one.enter do
|
271
|
-
expect(presence_client_one.update).to be_a(EventMachine::Deferrable)
|
272
|
-
stop_reactor
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
it 'calls the Deferrable callback on success' do
|
277
|
-
presence_client_one.enter do
|
278
|
-
presence_client_one.update.callback do |presence|
|
279
|
-
expect(presence).to eql(presence_client_one)
|
280
|
-
expect(presence_client_one.state).to eq(:entered)
|
281
|
-
stop_reactor
|
282
|
-
end
|
283
|
-
end
|
284
|
-
end
|
552
|
+
it_should_behave_like 'a public presence method', :update, :entered, {}, enter_first: true
|
285
553
|
end
|
286
554
|
|
287
555
|
context '#leave' do
|
@@ -334,22 +602,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
334
602
|
stop_reactor
|
335
603
|
end
|
336
604
|
|
337
|
-
|
338
|
-
presence_client_one.enter do
|
339
|
-
expect(presence_client_one.leave).to be_a(EventMachine::Deferrable)
|
340
|
-
stop_reactor
|
341
|
-
end
|
342
|
-
end
|
343
|
-
|
344
|
-
it 'calls the Deferrable callback on success' do
|
345
|
-
presence_client_one.enter do
|
346
|
-
presence_client_one.leave.callback do |presence|
|
347
|
-
expect(presence).to eql(presence_client_one)
|
348
|
-
expect(presence_client_one.state).to eq(:left)
|
349
|
-
stop_reactor
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|
605
|
+
it_should_behave_like 'a public presence method', :leave, :left, {}, enter_first: true
|
353
606
|
end
|
354
607
|
|
355
608
|
context ':left event' do
|
@@ -415,15 +668,36 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
415
668
|
end
|
416
669
|
end
|
417
670
|
|
418
|
-
|
419
|
-
|
420
|
-
|
671
|
+
context 'message #connection_id' do
|
672
|
+
let(:client_id) { random_str }
|
673
|
+
|
674
|
+
it 'matches the current client connection_id' do
|
675
|
+
channel_client_two.attach do
|
676
|
+
presence_client_one.enter_client(client_id)
|
677
|
+
end
|
678
|
+
|
679
|
+
presence_client_two.subscribe do |presence|
|
680
|
+
expect(presence.client_id).to eq(client_id)
|
681
|
+
expect(presence.connection_id).to eq(client_one.connection.id)
|
682
|
+
stop_reactor
|
683
|
+
end
|
684
|
+
end
|
421
685
|
end
|
422
686
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
687
|
+
it_should_behave_like 'a public presence method', :enter_client, nil, 'client_id'
|
688
|
+
|
689
|
+
context 'without necessary capabilities to enter on behalf of another client' do
|
690
|
+
let(:restricted_client) do
|
691
|
+
Ably::Realtime::Client.new(default_options.merge(api_key: restricted_api_key, log_level: :fatal))
|
692
|
+
end
|
693
|
+
let(:restricted_channel) { restricted_client.channel("cansubscribe:channel") }
|
694
|
+
let(:restricted_presence) { restricted_channel.presence }
|
695
|
+
|
696
|
+
it 'calls the Deferrable errback on capabilities failure' do
|
697
|
+
restricted_presence.enter_client('clientId').tap do |deferrable|
|
698
|
+
deferrable.callback { raise "Should not succeed" }
|
699
|
+
deferrable.errback { stop_reactor }
|
700
|
+
end
|
427
701
|
end
|
428
702
|
end
|
429
703
|
end
|
@@ -489,17 +763,7 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
489
763
|
end
|
490
764
|
end
|
491
765
|
|
492
|
-
|
493
|
-
expect(presence_client_one.update_client('client_id')).to be_a(EventMachine::Deferrable)
|
494
|
-
stop_reactor
|
495
|
-
end
|
496
|
-
|
497
|
-
it 'calls the Deferrable callback on success' do
|
498
|
-
presence_client_one.update_client('client_id').callback do |presence|
|
499
|
-
expect(presence).to eql(presence_client_one)
|
500
|
-
stop_reactor
|
501
|
-
end
|
502
|
-
end
|
766
|
+
it_should_behave_like 'a public presence method', :update_client, nil, 'client_id'
|
503
767
|
end
|
504
768
|
|
505
769
|
context '#leave_client' do
|
@@ -592,23 +856,13 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
592
856
|
end
|
593
857
|
end
|
594
858
|
|
595
|
-
|
596
|
-
expect(presence_client_one.leave_client('client_id')).to be_a(EventMachine::Deferrable)
|
597
|
-
stop_reactor
|
598
|
-
end
|
599
|
-
|
600
|
-
it 'calls the Deferrable callback on success' do
|
601
|
-
presence_client_one.leave_client('client_id').callback do |presence|
|
602
|
-
expect(presence).to eql(presence_client_one)
|
603
|
-
stop_reactor
|
604
|
-
end
|
605
|
-
end
|
859
|
+
it_should_behave_like 'a public presence method', :leave_client, nil, 'client_id'
|
606
860
|
end
|
607
861
|
end
|
608
862
|
|
609
863
|
context '#get' do
|
610
|
-
it 'returns a
|
611
|
-
expect(presence_client_one.get).to be_a(
|
864
|
+
it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
|
865
|
+
expect(presence_client_one.get).to be_a(Ably::Util::SafeDeferrable)
|
612
866
|
stop_reactor
|
613
867
|
end
|
614
868
|
|
@@ -619,6 +873,89 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
619
873
|
end
|
620
874
|
end
|
621
875
|
|
876
|
+
it 'catches exceptions in the provided method block' do
|
877
|
+
expect(presence_client_one.logger).to receive(:error).with(/Intentional exception/) do
|
878
|
+
stop_reactor
|
879
|
+
end
|
880
|
+
presence_client_one.get { raise 'Intentional exception' }
|
881
|
+
end
|
882
|
+
|
883
|
+
%w(detached failed).each do |state|
|
884
|
+
it "raise an exception if the channel is #{state}" do
|
885
|
+
channel_client_one.attach do
|
886
|
+
channel_client_one.change_state state.to_sym
|
887
|
+
expect { presence_client_one.get }.to raise_error Ably::Exceptions::IncompatibleStateForOperation, /Operation is not allowed when channel is in STATE.#{state}/i
|
888
|
+
stop_reactor
|
889
|
+
end
|
890
|
+
end
|
891
|
+
end
|
892
|
+
|
893
|
+
context 'during a sync' do
|
894
|
+
let(:pages) { 2 }
|
895
|
+
let(:members_per_page) { 100 }
|
896
|
+
let(:sync_pages_received) { [] }
|
897
|
+
let(:client_options) { default_options.merge(log_level: :none) }
|
898
|
+
|
899
|
+
def connect_members_deferrables
|
900
|
+
(members_per_page * pages + 1).times.map do |index|
|
901
|
+
presence_client_one.enter_client("client:#{index}")
|
902
|
+
end
|
903
|
+
end
|
904
|
+
|
905
|
+
before do
|
906
|
+
# Reconfigure client library so that it makes no retry attempts and fails immediately
|
907
|
+
stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
|
908
|
+
Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
|
909
|
+
disconnected: { retry_every: 0.1, max_time_in_state: 0 },
|
910
|
+
suspended: { retry_every: 0.1, max_time_in_state: 0 }
|
911
|
+
)
|
912
|
+
end
|
913
|
+
|
914
|
+
it 'fails if the connection fails' do
|
915
|
+
when_all(*connect_members_deferrables) do
|
916
|
+
channel_client_two.attach do
|
917
|
+
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
918
|
+
if protocol_message.action == :sync
|
919
|
+
sync_pages_received << protocol_message
|
920
|
+
force_connection_failure client_two if sync_pages_received.count == 1
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
|
925
|
+
presence_client_two.get.tap do |deferrable|
|
926
|
+
deferrable.callback { raise 'Get should not succeed' }
|
927
|
+
deferrable.errback do |error|
|
928
|
+
stop_reactor
|
929
|
+
end
|
930
|
+
end
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
it 'fails if the channel is detached' do
|
935
|
+
when_all(*connect_members_deferrables) do
|
936
|
+
channel_client_two.attach do
|
937
|
+
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
938
|
+
if protocol_message.action == :sync
|
939
|
+
# prevent any more SYNC messages coming through
|
940
|
+
client_two.connection.transport.__incoming_protocol_msgbus__.unsubscribe
|
941
|
+
channel_client_two.change_state :detaching
|
942
|
+
channel_client_two.change_state :detached
|
943
|
+
end
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
presence_client_two.get.tap do |deferrable|
|
948
|
+
deferrable.callback { raise 'Get should not succeed' }
|
949
|
+
deferrable.errback do |error|
|
950
|
+
stop_reactor
|
951
|
+
end
|
952
|
+
end
|
953
|
+
end
|
954
|
+
end
|
955
|
+
end
|
956
|
+
|
957
|
+
# skip 'it fails if the connection changes to failed state'
|
958
|
+
|
622
959
|
it 'returns the current members on the channel' do
|
623
960
|
presence_client_one.enter do
|
624
961
|
presence_client_one.get do |members|
|
@@ -753,9 +1090,11 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
753
1090
|
stop_reactor
|
754
1091
|
end
|
755
1092
|
|
756
|
-
presence_client_one.enter
|
757
|
-
|
758
|
-
|
1093
|
+
presence_client_one.enter do
|
1094
|
+
presence_client_one.update do
|
1095
|
+
presence_client_one.leave
|
1096
|
+
end
|
1097
|
+
end
|
759
1098
|
end
|
760
1099
|
end
|
761
1100
|
end
|
@@ -992,10 +1331,31 @@ describe Ably::Realtime::Presence, :event_machine do
|
|
992
1331
|
end
|
993
1332
|
end
|
994
1333
|
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1334
|
+
context 'connection failure mid-way through a large member sync' do
|
1335
|
+
let(:members_count) { 400 }
|
1336
|
+
let(:sync_pages_received) { [] }
|
1337
|
+
|
1338
|
+
# Will re-enable once https://github.com/ably/realtime/issues/91 is resolved
|
1339
|
+
skip 'resumes the SYNC operation', em_timeout: 15 do
|
1340
|
+
when_all(*members_count.times.map do |index|
|
1341
|
+
presence_client_one.enter_client("client:#{index}")
|
1342
|
+
end) do
|
1343
|
+
channel_client_two.attach do
|
1344
|
+
client_two.connection.transport.__incoming_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
1345
|
+
if protocol_message.action == :sync
|
1346
|
+
sync_pages_received << protocol_message
|
1347
|
+
force_connection_failure client_two if sync_pages_received.count == 2
|
1348
|
+
end
|
1349
|
+
end
|
1350
|
+
end
|
1351
|
+
|
1352
|
+
presence_client_two.get do |members|
|
1353
|
+
expect(members.count).to eql(members_count)
|
1354
|
+
expect(members.map(&:member_key).uniq.count).to eql(members_count)
|
1355
|
+
stop_reactor
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
end
|
1000
1360
|
end
|
1001
1361
|
end
|