ably-rest 0.7.3 → 0.7.5
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.
- checksums.yaml +8 -8
- data/.travis.yml +1 -0
- data/SPEC.md +480 -472
- data/lib/ably-rest.rb +1 -1
- data/lib/submodules/ably-ruby/LICENSE.txt +1 -1
- data/lib/submodules/ably-ruby/README.md +107 -24
- data/lib/submodules/ably-ruby/SPEC.md +531 -398
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +24 -16
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +9 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +17 -9
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +12 -8
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +18 -10
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +15 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +31 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/message_emitter.rb +77 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_deferrable.rb +71 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +28 -8
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +0 -5
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +54 -11
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +21 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +7 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +29 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +41 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +72 -24
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +26 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +74 -208
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +264 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +59 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +6 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +3 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +18 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +28 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +116 -46
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +55 -10
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +32 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +456 -96
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +96 -7
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +8 -0
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +71 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +13 -7
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +15 -14
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +17 -17
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +28 -9
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +50 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +76 -2
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +51 -20
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +3 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +52 -26
- data/lib/submodules/ably-ruby/spec/unit/realtime/safe_deferrable_spec.rb +12 -0
- data/spec/spec_helper.rb +5 -0
- metadata +12 -4
- data/lib/submodules/ably-ruby/.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
|