ably-rest 0.7.1 → 0.7.3
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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Realtime::Presence, 'history', :event_machine do
|
|
5
|
+
vary_by_protocol do
|
|
6
|
+
let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
|
7
|
+
|
|
8
|
+
let(:channel_name) { "persisted:#{random_str(2)}" }
|
|
9
|
+
|
|
10
|
+
let(:client_one) { Ably::Realtime::Client.new(default_options.merge(client_id: random_str)) }
|
|
11
|
+
let(:channel_client_one) { client_one.channel(channel_name) }
|
|
12
|
+
let(:presence_client_one) { channel_client_one.presence }
|
|
13
|
+
|
|
14
|
+
let(:client_two) { Ably::Realtime::Client.new(default_options.merge(client_id: random_str)) }
|
|
15
|
+
let(:channel_client_two) { client_two.channel(channel_name) }
|
|
16
|
+
let(:presence_client_two) { channel_client_two.presence }
|
|
17
|
+
|
|
18
|
+
let(:data) { random_str }
|
|
19
|
+
let(:leave_data) { random_str }
|
|
20
|
+
|
|
21
|
+
it 'provides up to the moment presence history' do
|
|
22
|
+
presence_client_one.enter(data: data) do
|
|
23
|
+
presence_client_one.leave(data: leave_data) do
|
|
24
|
+
presence_client_one.history do |history|
|
|
25
|
+
expect(history.count).to eql(2)
|
|
26
|
+
|
|
27
|
+
expect(history[1].action).to eq(:enter)
|
|
28
|
+
expect(history[1].client_id).to eq(client_one.client_id)
|
|
29
|
+
expect(history[1].data).to eql(data)
|
|
30
|
+
|
|
31
|
+
expect(history[0].action).to eq(:leave)
|
|
32
|
+
expect(history[0].client_id).to eq(client_one.client_id)
|
|
33
|
+
expect(history[0].data).to eql(leave_data)
|
|
34
|
+
|
|
35
|
+
stop_reactor
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'ensures REST presence history message IDs match ProtocolMessage wrapped message and connection IDs via Realtime' do
|
|
42
|
+
presence_client_one.subscribe(:enter) do |message|
|
|
43
|
+
presence_client_one.history do |history|
|
|
44
|
+
expect(history.count).to eql(1)
|
|
45
|
+
|
|
46
|
+
expect(history[0].id).to eql(message.id)
|
|
47
|
+
expect(history[0].connection_id).to eql(message.connection_id)
|
|
48
|
+
stop_reactor
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
presence_client_one.enter(data: data)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Ably::Realtime::Presence, :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(:anonymous_client) { Ably::Realtime::Client.new(client_options) }
|
|
10
|
+
let(:client_one) { Ably::Realtime::Client.new(client_options.merge(client_id: random_str)) }
|
|
11
|
+
let(:client_two) { Ably::Realtime::Client.new(client_options.merge(client_id: random_str)) }
|
|
12
|
+
|
|
13
|
+
let(:channel_name) { "presence-#{random_str(4)}" }
|
|
14
|
+
let(:channel_anonymous_client) { anonymous_client.channel(channel_name) }
|
|
15
|
+
let(:presence_anonymous_client) { channel_anonymous_client.presence }
|
|
16
|
+
let(:channel_client_one) { client_one.channel(channel_name) }
|
|
17
|
+
let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name) }
|
|
18
|
+
let(:presence_client_one) { channel_client_one.presence }
|
|
19
|
+
let(:channel_client_two) { client_two.channel(channel_name) }
|
|
20
|
+
let(:presence_client_two) { channel_client_two.presence }
|
|
21
|
+
let(:data_payload) { random_str }
|
|
22
|
+
|
|
23
|
+
context 'when attached (but not present) on a presence channel with an anonymous client (no client ID)' do
|
|
24
|
+
it 'maintains state as other clients enter and leave the channel' do
|
|
25
|
+
channel_anonymous_client.attach do
|
|
26
|
+
presence_anonymous_client.subscribe(:enter) do |presence_message|
|
|
27
|
+
expect(presence_message.client_id).to eql(client_one.client_id)
|
|
28
|
+
|
|
29
|
+
presence_anonymous_client.get do |members|
|
|
30
|
+
expect(members.first.client_id).to eql(client_one.client_id)
|
|
31
|
+
expect(members.first.action).to eq(:enter)
|
|
32
|
+
|
|
33
|
+
presence_anonymous_client.subscribe(:leave) do |presence_message|
|
|
34
|
+
expect(presence_message.client_id).to eql(client_one.client_id)
|
|
35
|
+
|
|
36
|
+
presence_anonymous_client.get do |members|
|
|
37
|
+
expect(members.count).to eql(0)
|
|
38
|
+
stop_reactor
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
presence_client_one.enter do
|
|
46
|
+
presence_client_one.leave
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context '#sync_complete?' do
|
|
52
|
+
context 'when attaching to a channel without any members present' do
|
|
53
|
+
it 'is true and the presence channel is considered synced immediately' do
|
|
54
|
+
channel_anonymous_client.attach do
|
|
55
|
+
expect(channel_anonymous_client.presence).to be_sync_complete
|
|
56
|
+
stop_reactor
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'when attaching to a channel with members present' do
|
|
62
|
+
it 'is false and the presence channel will subsequently be synced' do
|
|
63
|
+
presence_client_one.enter do
|
|
64
|
+
channel_anonymous_client.attach do
|
|
65
|
+
expect(channel_anonymous_client.presence).to_not be_sync_complete
|
|
66
|
+
channel_anonymous_client.presence.get do
|
|
67
|
+
expect(channel_anonymous_client.presence).to be_sync_complete
|
|
68
|
+
stop_reactor
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'when the SYNC of a presence channel spans multiple ProtocolMessage messages' do
|
|
77
|
+
context 'with 250 existing (present) members' do
|
|
78
|
+
let(:enter_expected_count) { 250 }
|
|
79
|
+
let(:present) { [] }
|
|
80
|
+
let(:entered) { [] }
|
|
81
|
+
|
|
82
|
+
context 'when a new client attaches to the presence channel', em_timeout: 10 do
|
|
83
|
+
it 'emits :present for each member' do
|
|
84
|
+
enter_expected_count.times do |index|
|
|
85
|
+
presence_client_one.enter_client("client:#{index}") do |message|
|
|
86
|
+
entered << message
|
|
87
|
+
next unless entered.count == enter_expected_count
|
|
88
|
+
|
|
89
|
+
presence_anonymous_client.subscribe(:present) do |present_message|
|
|
90
|
+
expect(present_message.action).to eq(:present)
|
|
91
|
+
present << present_message
|
|
92
|
+
next unless present.count == enter_expected_count
|
|
93
|
+
|
|
94
|
+
expect(present.map(&:client_id).uniq.count).to eql(enter_expected_count)
|
|
95
|
+
stop_reactor
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context '#get' do
|
|
102
|
+
it '#waits until sync is complete', event_machine: 15 do
|
|
103
|
+
enter_expected_count.times do |index|
|
|
104
|
+
presence_client_one.enter_client("client:#{index}") do |message|
|
|
105
|
+
entered << message
|
|
106
|
+
next unless entered.count == enter_expected_count
|
|
107
|
+
|
|
108
|
+
presence_anonymous_client.get do |members|
|
|
109
|
+
expect(members.map(&:client_id).uniq.count).to eql(enter_expected_count)
|
|
110
|
+
expect(members.count).to eql(enter_expected_count)
|
|
111
|
+
stop_reactor
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
context 'automatic attachment of channel on access to presence object' do
|
|
122
|
+
it 'is implicit if presence state is initalized' do
|
|
123
|
+
channel_client_one.presence
|
|
124
|
+
channel_client_one.on(:attached) do
|
|
125
|
+
expect(channel_client_one.state).to eq(:attached)
|
|
126
|
+
stop_reactor
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'is disabled if presence state is not initialized' do
|
|
131
|
+
channel_client_one.attach do
|
|
132
|
+
channel_client_one.detach do
|
|
133
|
+
expect(channel_client_one.state).to eq(:detached)
|
|
134
|
+
|
|
135
|
+
channel_client_one.presence # access the presence object
|
|
136
|
+
EventMachine.add_timer(1) do
|
|
137
|
+
expect(channel_client_one.state).to eq(:detached)
|
|
138
|
+
stop_reactor
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'state' do
|
|
146
|
+
context 'once opened' do
|
|
147
|
+
it 'once opened, enters the :left state if the channel detaches' do
|
|
148
|
+
detached = false
|
|
149
|
+
|
|
150
|
+
channel_client_one.presence.on(:left) do
|
|
151
|
+
expect(channel_client_one.presence.state).to eq(:left)
|
|
152
|
+
EventMachine.next_tick do
|
|
153
|
+
expect(detached).to eq(true)
|
|
154
|
+
stop_reactor
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
channel_client_one.presence.enter do |presence|
|
|
159
|
+
expect(presence.state).to eq(:entered)
|
|
160
|
+
channel_client_one.detach do
|
|
161
|
+
expect(channel_client_one.state).to eq(:detached)
|
|
162
|
+
detached = true
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context '#enter' do
|
|
170
|
+
it 'allows client_id to be set on enter for anonymous clients' do
|
|
171
|
+
channel_anonymous_client.presence.enter client_id: "123"
|
|
172
|
+
|
|
173
|
+
channel_anonymous_client.presence.subscribe do |presence|
|
|
174
|
+
expect(presence.client_id).to eq("123")
|
|
175
|
+
stop_reactor
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
context 'data attribute' do
|
|
180
|
+
context 'when provided as argument option to #enter' do
|
|
181
|
+
it 'remains intact following #leave' do
|
|
182
|
+
leave_callback_called = false
|
|
183
|
+
|
|
184
|
+
presence_client_one.enter(data: 'stored') do
|
|
185
|
+
expect(presence_client_one.data).to eql('stored')
|
|
186
|
+
|
|
187
|
+
presence_client_one.leave do |presence|
|
|
188
|
+
leave_callback_called = true
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
presence_client_one.on(:left) do
|
|
192
|
+
expect(presence_client_one.data).to eql('stored')
|
|
193
|
+
|
|
194
|
+
EventMachine.next_tick do
|
|
195
|
+
expect(leave_callback_called).to eql(true)
|
|
196
|
+
stop_reactor
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'raises an exception if client_id is not set' do
|
|
205
|
+
expect { channel_anonymous_client.presence.enter }.to raise_error(Ably::Exceptions::Standard, /without a client_id/)
|
|
206
|
+
stop_reactor
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'returns a Deferrable' do
|
|
210
|
+
expect(presence_client_one.enter).to be_a(EventMachine::Deferrable)
|
|
211
|
+
stop_reactor
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'calls the Deferrable callback on success' do
|
|
215
|
+
presence_client_one.enter.callback do |presence|
|
|
216
|
+
expect(presence).to eql(presence_client_one)
|
|
217
|
+
expect(presence_client_one.state).to eq(:entered)
|
|
218
|
+
stop_reactor
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
context '#update' do
|
|
224
|
+
it 'without previous #enter automatically enters' do
|
|
225
|
+
presence_client_one.update(data: data_payload) do
|
|
226
|
+
EventMachine.add_timer(1) do
|
|
227
|
+
expect(presence_client_one.state).to eq(:entered)
|
|
228
|
+
stop_reactor
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
context 'when ENTERED' do
|
|
234
|
+
it 'has no effect on the state' do
|
|
235
|
+
presence_client_one.enter do
|
|
236
|
+
presence_client_one.once_state_changed { fail 'State should not have changed ' }
|
|
237
|
+
|
|
238
|
+
presence_client_one.update(data: data_payload) do
|
|
239
|
+
EventMachine.add_timer(1) do
|
|
240
|
+
expect(presence_client_one.state).to eq(:entered)
|
|
241
|
+
presence_client_one.off
|
|
242
|
+
stop_reactor
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'updates the data if :data argument provided' do
|
|
250
|
+
presence_client_one.enter(data: 'prior') do
|
|
251
|
+
presence_client_one.update(data: data_payload)
|
|
252
|
+
end
|
|
253
|
+
presence_client_one.subscribe(:update) do |message|
|
|
254
|
+
expect(message.data).to eql(data_payload)
|
|
255
|
+
stop_reactor
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'updates the data to nil if :data argument is not provided (assumes nil value)' do
|
|
260
|
+
presence_client_one.enter(data: 'prior') do
|
|
261
|
+
presence_client_one.update
|
|
262
|
+
end
|
|
263
|
+
presence_client_one.subscribe(:update) do |message|
|
|
264
|
+
expect(message.data).to be_nil
|
|
265
|
+
stop_reactor
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'returns a Deferrable' do
|
|
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
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context '#leave' do
|
|
288
|
+
context ':data option' do
|
|
289
|
+
let(:data) { random_str }
|
|
290
|
+
let(:enter_data) { random_str }
|
|
291
|
+
|
|
292
|
+
context 'when set to a string' do
|
|
293
|
+
it 'emits the new data for the leave event' do
|
|
294
|
+
presence_client_one.enter data: enter_data do
|
|
295
|
+
presence_client_one.leave data: data
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
299
|
+
expect(presence_message.data).to eql(data)
|
|
300
|
+
stop_reactor
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
context 'when set to nil' do
|
|
306
|
+
it 'emits the previously defined value as a convenience' do
|
|
307
|
+
presence_client_one.enter data: enter_data do
|
|
308
|
+
presence_client_one.leave data: nil
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
312
|
+
expect(presence_message.data).to eql(enter_data)
|
|
313
|
+
stop_reactor
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
context 'when not passed as an argument' do
|
|
319
|
+
it 'emits the previously defined value as a convenience' do
|
|
320
|
+
presence_client_one.enter data: enter_data do
|
|
321
|
+
presence_client_one.leave
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
325
|
+
expect(presence_message.data).to eql(enter_data)
|
|
326
|
+
stop_reactor
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
it 'raises an exception if not entered' do
|
|
333
|
+
expect { channel_anonymous_client.presence.leave }.to raise_error(Ably::Exceptions::Standard, /Unable to leave presence channel that is not entered/)
|
|
334
|
+
stop_reactor
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'returns a Deferrable' do
|
|
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
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
context ':left event' do
|
|
356
|
+
it 'emits the data defined in enter' do
|
|
357
|
+
channel_client_one.presence.enter(data: 'data') do
|
|
358
|
+
channel_client_one.presence.leave
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
channel_client_two.presence.subscribe(:leave) do |message|
|
|
362
|
+
expect(message.data).to eql('data')
|
|
363
|
+
stop_reactor
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it 'emits the data defined in update' do
|
|
368
|
+
channel_client_one.presence.enter(data: 'something else') do
|
|
369
|
+
channel_client_one.presence.update(data: 'data') do
|
|
370
|
+
channel_client_one.presence.leave
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
channel_client_two.presence.subscribe(:leave) do |message|
|
|
375
|
+
expect(message.data).to eql('data')
|
|
376
|
+
stop_reactor
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
context 'entering/updating/leaving presence state on behalf of another client_id' do
|
|
382
|
+
let(:client_count) { 5 }
|
|
383
|
+
let(:clients) { [] }
|
|
384
|
+
let(:data) { random_str }
|
|
385
|
+
|
|
386
|
+
context '#enter_client' do
|
|
387
|
+
context 'multiple times on the same channel with different client_ids' do
|
|
388
|
+
it "has no affect on the client's presence state and only enters on behalf of the provided client_id" do
|
|
389
|
+
client_count.times do |client_id|
|
|
390
|
+
presence_client_one.enter_client("client:#{client_id}") do
|
|
391
|
+
presence_client_one.on(:entered) { raise 'Should not have entered' }
|
|
392
|
+
next unless client_id == client_count - 1
|
|
393
|
+
|
|
394
|
+
EventMachine.add_timer(1) do
|
|
395
|
+
expect(presence_client_one.state).to eq(:initialized)
|
|
396
|
+
stop_reactor
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it 'enters a channel and sets the data based on the provided :data option' do
|
|
403
|
+
client_count.times do |client_id|
|
|
404
|
+
presence_client_one.enter_client("client:#{client_id}", data: data)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
presence_anonymous_client.subscribe(:enter) do |presence|
|
|
408
|
+
expect(presence.data).to eql(data)
|
|
409
|
+
clients << presence
|
|
410
|
+
next unless clients.count == 5
|
|
411
|
+
|
|
412
|
+
expect(clients.map(&:client_id).uniq.count).to eql(5)
|
|
413
|
+
stop_reactor
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it 'returns a Deferrable' do
|
|
419
|
+
expect(presence_client_one.enter_client('client_id')).to be_a(EventMachine::Deferrable)
|
|
420
|
+
stop_reactor
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'calls the Deferrable callback on success' do
|
|
424
|
+
presence_client_one.enter_client('client_id').callback do |presence|
|
|
425
|
+
expect(presence).to eql(presence_client_one)
|
|
426
|
+
stop_reactor
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
context '#update_client' do
|
|
432
|
+
context 'multiple times on the same channel with different client_ids' do
|
|
433
|
+
it 'updates the data attribute for the member when :data option provided' do
|
|
434
|
+
updated_callback_count = 0
|
|
435
|
+
|
|
436
|
+
client_count.times do |client_id|
|
|
437
|
+
presence_client_one.enter_client("client:#{client_id}") do
|
|
438
|
+
presence_client_one.update_client("client:#{client_id}", data: data) do
|
|
439
|
+
updated_callback_count += 1
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
presence_anonymous_client.subscribe(:update) do |presence|
|
|
445
|
+
expect(presence.data).to eql(data)
|
|
446
|
+
clients << presence
|
|
447
|
+
next unless clients.count == 5
|
|
448
|
+
|
|
449
|
+
wait_until(proc { updated_callback_count == 5 }) do
|
|
450
|
+
expect(clients.map(&:client_id).uniq.count).to eql(5)
|
|
451
|
+
expect(updated_callback_count).to eql(5)
|
|
452
|
+
stop_reactor
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it 'updates the data attribute to null for the member when :data option is not provided (assumed null)' do
|
|
458
|
+
presence_client_one.enter_client('client_1') do
|
|
459
|
+
presence_client_one.update_client('client_1')
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
presence_anonymous_client.subscribe(:update) do |presence|
|
|
463
|
+
expect(presence.client_id).to eql('client_1')
|
|
464
|
+
expect(presence.data).to be_nil
|
|
465
|
+
stop_reactor
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it 'enters if not already entered' do
|
|
470
|
+
updated_callback_count = 0
|
|
471
|
+
|
|
472
|
+
client_count.times do |client_id|
|
|
473
|
+
presence_client_one.update_client("client:#{client_id}", data: data) do
|
|
474
|
+
updated_callback_count += 1
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
presence_anonymous_client.subscribe(:enter) do |presence|
|
|
479
|
+
expect(presence.data).to eql(data)
|
|
480
|
+
clients << presence
|
|
481
|
+
next unless clients.count == 5
|
|
482
|
+
|
|
483
|
+
wait_until(proc { updated_callback_count == 5 }) do
|
|
484
|
+
expect(clients.map(&:client_id).uniq.count).to eql(5)
|
|
485
|
+
expect(updated_callback_count).to eql(5)
|
|
486
|
+
stop_reactor
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
it 'returns a Deferrable' do
|
|
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
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
context '#leave_client' do
|
|
506
|
+
context 'leaves a channel' do
|
|
507
|
+
context 'multiple times on the same channel with different client_ids' do
|
|
508
|
+
it 'emits the :leave event for each client_id' do
|
|
509
|
+
left_callback_count = 0
|
|
510
|
+
|
|
511
|
+
client_count.times do |client_id|
|
|
512
|
+
presence_client_one.enter_client("client:#{client_id}", data: random_str) do
|
|
513
|
+
presence_client_one.leave_client("client:#{client_id}", data: data) do
|
|
514
|
+
left_callback_count += 1
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
presence_anonymous_client.subscribe(:leave) do |presence|
|
|
520
|
+
expect(presence.data).to eql(data)
|
|
521
|
+
clients << presence
|
|
522
|
+
next unless clients.count == 5
|
|
523
|
+
|
|
524
|
+
wait_until(proc { left_callback_count == 5 }) do
|
|
525
|
+
expect(clients.map(&:client_id).uniq.count).to eql(5)
|
|
526
|
+
expect(left_callback_count).to eql(5)
|
|
527
|
+
stop_reactor
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
it 'succeeds if that client_id has not previously entered the channel' do
|
|
533
|
+
left_callback_count = 0
|
|
534
|
+
|
|
535
|
+
client_count.times do |client_id|
|
|
536
|
+
presence_client_one.leave_client("client:#{client_id}") do
|
|
537
|
+
left_callback_count += 1
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
presence_anonymous_client.subscribe(:leave) do |presence|
|
|
542
|
+
expect(presence.data).to be_nil
|
|
543
|
+
clients << presence
|
|
544
|
+
next unless clients.count == 5
|
|
545
|
+
|
|
546
|
+
wait_until(proc { left_callback_count == 5 }) do
|
|
547
|
+
expect(clients.map(&:client_id).uniq.count).to eql(5)
|
|
548
|
+
expect(left_callback_count).to eql(5)
|
|
549
|
+
stop_reactor
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
context 'with a new value in :data option' do
|
|
556
|
+
it 'emits the leave event with the new data value' do
|
|
557
|
+
presence_client_one.enter_client("client:unique", data: random_str) do
|
|
558
|
+
presence_client_one.leave_client("client:unique", data: data)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
562
|
+
expect(presence_message.data).to eql(data)
|
|
563
|
+
stop_reactor
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
context 'with a nil value in :data option' do
|
|
569
|
+
it 'emits the leave event with the previous value as a convenience' do
|
|
570
|
+
presence_client_one.enter_client("client:unique", data: data) do
|
|
571
|
+
presence_client_one.leave_client("client:unique", data: nil)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
575
|
+
expect(presence_message.data).to eql(data)
|
|
576
|
+
stop_reactor
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
context 'with no :data option' do
|
|
582
|
+
it 'emits the leave event with the previous value as a convenience' do
|
|
583
|
+
presence_client_one.enter_client("client:unique", data: data) do
|
|
584
|
+
presence_client_one.leave_client("client:unique")
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
presence_client_one.subscribe(:leave) do |presence_message|
|
|
588
|
+
expect(presence_message.data).to eql(data)
|
|
589
|
+
stop_reactor
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it 'returns a Deferrable' do
|
|
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
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
context '#get' do
|
|
610
|
+
it 'returns a Deferrable' do
|
|
611
|
+
expect(presence_client_one.get).to be_a(EventMachine::Deferrable)
|
|
612
|
+
stop_reactor
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
it 'calls the Deferrable callback on success' do
|
|
616
|
+
presence_client_one.get.callback do |presence|
|
|
617
|
+
expect(presence).to eq([])
|
|
618
|
+
stop_reactor
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it 'returns the current members on the channel' do
|
|
623
|
+
presence_client_one.enter do
|
|
624
|
+
presence_client_one.get do |members|
|
|
625
|
+
expect(members.count).to eq(1)
|
|
626
|
+
|
|
627
|
+
expect(client_one.client_id).to_not be_nil
|
|
628
|
+
|
|
629
|
+
this_member = members.first
|
|
630
|
+
expect(this_member.client_id).to eql(client_one.client_id)
|
|
631
|
+
|
|
632
|
+
stop_reactor
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
it 'filters by connection_id option if provided' do
|
|
638
|
+
presence_client_one.enter do
|
|
639
|
+
presence_client_two.enter
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
presence_client_one.subscribe(:enter) do |presence_message|
|
|
643
|
+
# wait until the client_two enter event has been sent to client_one
|
|
644
|
+
next unless presence_message.client_id == client_two.client_id
|
|
645
|
+
|
|
646
|
+
presence_client_one.get(connection_id: client_one.connection.id) do |members|
|
|
647
|
+
expect(members.count).to eq(1)
|
|
648
|
+
expect(members.first.connection_id).to eql(client_one.connection.id)
|
|
649
|
+
|
|
650
|
+
presence_client_one.get(connection_id: client_two.connection.id) do |members|
|
|
651
|
+
expect(members.count).to eq(1)
|
|
652
|
+
expect(members.first.connection_id).to eql(client_two.connection.id)
|
|
653
|
+
stop_reactor
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
it 'filters by client_id option if provided' do
|
|
660
|
+
presence_client_one.enter(client_id: 'one') do
|
|
661
|
+
presence_client_two.enter client_id: 'two'
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
presence_client_one.subscribe(:enter) do |presence_message|
|
|
665
|
+
# wait until the client_two enter event has been sent to client_one
|
|
666
|
+
next unless presence_message.client_id == 'two'
|
|
667
|
+
|
|
668
|
+
presence_client_one.get(client_id: 'one') do |members|
|
|
669
|
+
expect(members.count).to eq(1)
|
|
670
|
+
expect(members.first.client_id).to eql('one')
|
|
671
|
+
expect(members.first.connection_id).to eql(client_one.connection.id)
|
|
672
|
+
|
|
673
|
+
presence_client_one.get(client_id: 'two') do |members|
|
|
674
|
+
expect(members.count).to eq(1)
|
|
675
|
+
expect(members.first.client_id).to eql('two')
|
|
676
|
+
expect(members.first.connection_id).to eql(client_two.connection.id)
|
|
677
|
+
stop_reactor
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
it 'does not wait for SYNC to complete if :wait_for_sync option is false' do
|
|
684
|
+
presence_client_one.enter(client_id: 'one') do
|
|
685
|
+
presence_client_two.get(wait_for_sync: false) do |members|
|
|
686
|
+
expect(members.count).to eql(0)
|
|
687
|
+
stop_reactor
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
context 'when a member enters and then leaves' do
|
|
693
|
+
it 'has no members' do
|
|
694
|
+
presence_client_one.enter do
|
|
695
|
+
presence_client_one.leave do
|
|
696
|
+
presence_client_one.get do |members|
|
|
697
|
+
expect(members.count).to eq(0)
|
|
698
|
+
stop_reactor
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
context 'with lots of members on different clients' do
|
|
706
|
+
let(:members_per_client) { 10 }
|
|
707
|
+
let(:clients_entered) { Hash.new { |hash, key| hash[key] = 0 } }
|
|
708
|
+
let(:total_members) { members_per_client * 2 }
|
|
709
|
+
|
|
710
|
+
it 'returns a complete list of members on all clients' do
|
|
711
|
+
members_per_client.times do |index|
|
|
712
|
+
presence_client_one.enter_client("client_1:#{index}")
|
|
713
|
+
presence_client_two.enter_client("client_2:#{index}")
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
presence_client_one.subscribe(:enter) do
|
|
717
|
+
clients_entered[:client_one] += 1
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
presence_client_two.subscribe(:enter) do
|
|
721
|
+
clients_entered[:client_two] += 1
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
wait_until(proc { clients_entered[:client_one] + clients_entered[:client_two] == total_members * 2 }) do
|
|
725
|
+
presence_anonymous_client.get do |anonymous_members|
|
|
726
|
+
expect(anonymous_members.count).to eq(total_members)
|
|
727
|
+
expect(anonymous_members.map(&:client_id).uniq.count).to eq(total_members)
|
|
728
|
+
|
|
729
|
+
presence_client_one.get do |client_one_members|
|
|
730
|
+
presence_client_two.get do |client_two_members|
|
|
731
|
+
expect(client_one_members.count).to eq(total_members)
|
|
732
|
+
expect(client_one_members.count).to eq(client_two_members.count)
|
|
733
|
+
stop_reactor
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
context '#subscribe' do
|
|
743
|
+
let(:messages) { [] }
|
|
744
|
+
|
|
745
|
+
context 'with no arguments' do
|
|
746
|
+
it 'calls the callback for all presence events' do
|
|
747
|
+
when_all(channel_client_one.attach, channel_client_two.attach) do
|
|
748
|
+
presence_client_two.subscribe do |presence_message|
|
|
749
|
+
messages << presence_message
|
|
750
|
+
next unless messages.count == 3
|
|
751
|
+
|
|
752
|
+
expect(messages.map(&:action).map(&:to_sym)).to contain_exactly(:enter, :update, :leave)
|
|
753
|
+
stop_reactor
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
presence_client_one.enter
|
|
757
|
+
presence_client_one.update
|
|
758
|
+
presence_client_one.leave
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
context '#unsubscribe' do
|
|
765
|
+
context 'with no arguments' do
|
|
766
|
+
it 'removes the callback for all presence events' do
|
|
767
|
+
when_all(channel_client_one.attach, channel_client_two.attach) do
|
|
768
|
+
subscribe_callback = proc { raise 'Should not be called' }
|
|
769
|
+
presence_client_two.subscribe &subscribe_callback
|
|
770
|
+
presence_client_two.unsubscribe &subscribe_callback
|
|
771
|
+
|
|
772
|
+
presence_client_one.enter
|
|
773
|
+
presence_client_one.update
|
|
774
|
+
presence_client_one.leave do
|
|
775
|
+
EventMachine.add_timer(1) do
|
|
776
|
+
stop_reactor
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
context 'REST #get' do
|
|
785
|
+
it 'returns current members' do
|
|
786
|
+
presence_client_one.enter(data: data_payload) do
|
|
787
|
+
members = channel_rest_client_one.presence.get
|
|
788
|
+
this_member = members.first
|
|
789
|
+
|
|
790
|
+
expect(this_member).to be_a(Ably::Models::PresenceMessage)
|
|
791
|
+
expect(this_member.client_id).to eql(client_one.client_id)
|
|
792
|
+
expect(this_member.data).to eql(data_payload)
|
|
793
|
+
|
|
794
|
+
stop_reactor
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
it 'returns no members once left' do
|
|
799
|
+
presence_client_one.enter(data: data_payload) do
|
|
800
|
+
presence_client_one.leave do
|
|
801
|
+
members = channel_rest_client_one.presence.get
|
|
802
|
+
expect(members.count).to eql(0)
|
|
803
|
+
stop_reactor
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
context 'client_id with ASCII_8BIT' do
|
|
810
|
+
let(:client_id) { random_str.encode(Encoding::ASCII_8BIT) }
|
|
811
|
+
|
|
812
|
+
context 'in connection set up' do
|
|
813
|
+
let(:client_one) { Ably::Realtime::Client.new(default_options.merge(client_id: client_id)) }
|
|
814
|
+
|
|
815
|
+
it 'is converted into UTF_8' do
|
|
816
|
+
presence_client_one.enter
|
|
817
|
+
presence_client_one.on(:entered) do |presence|
|
|
818
|
+
expect(presence.client_id.encoding).to eql(Encoding::UTF_8)
|
|
819
|
+
expect(presence.client_id.encode(Encoding::ASCII_8BIT)).to eql(client_id)
|
|
820
|
+
stop_reactor
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
context 'in channel options' do
|
|
826
|
+
let(:client_one) { Ably::Realtime::Client.new(default_options) }
|
|
827
|
+
|
|
828
|
+
it 'is converted into UTF_8' do
|
|
829
|
+
presence_client_one.enter(client_id: client_id)
|
|
830
|
+
presence_client_one.on(:entered) do |presence|
|
|
831
|
+
expect(presence.client_id.encoding).to eql(Encoding::UTF_8)
|
|
832
|
+
expect(presence.client_id.encode(Encoding::ASCII_8BIT)).to eql(client_id)
|
|
833
|
+
stop_reactor
|
|
834
|
+
end
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
context 'encoding and decoding of presence message data' do
|
|
840
|
+
let(:secret_key) { random_str }
|
|
841
|
+
let(:cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
|
842
|
+
let(:channel_name) { random_str }
|
|
843
|
+
let(:encrypted_channel) { client_one.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
|
844
|
+
let(:channel_rest_client_one) { client_one.rest_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
|
845
|
+
|
|
846
|
+
let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
|
|
847
|
+
|
|
848
|
+
let(:data) { { 'key' => random_str } }
|
|
849
|
+
let(:data_as_json) { data.to_json }
|
|
850
|
+
let(:data_as_cipher) { crypto.encrypt(data.to_json) }
|
|
851
|
+
|
|
852
|
+
it 'encrypts presence message data' do
|
|
853
|
+
encrypted_channel.attach do
|
|
854
|
+
encrypted_channel.presence.enter data: data
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
encrypted_channel.presence.__incoming_msgbus__.unsubscribe(:presence) # remove all subscribe callbacks that could decrypt the message
|
|
858
|
+
encrypted_channel.presence.__incoming_msgbus__.subscribe(:presence) do |presence|
|
|
859
|
+
if protocol == :json
|
|
860
|
+
expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc/base64')
|
|
861
|
+
expect(crypto.decrypt(Base64.decode64(presence['data']))).to eql(data_as_json)
|
|
862
|
+
else
|
|
863
|
+
expect(presence['encoding']).to eql('json/utf-8/cipher+aes-256-cbc')
|
|
864
|
+
expect(crypto.decrypt(presence['data'])).to eql(data_as_json)
|
|
865
|
+
end
|
|
866
|
+
stop_reactor
|
|
867
|
+
end
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
context '#subscribe' do
|
|
871
|
+
it 'emits decrypted enter events' do
|
|
872
|
+
encrypted_channel.attach do
|
|
873
|
+
encrypted_channel.presence.enter data: data
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
encrypted_channel.presence.subscribe(:enter) do |presence_message|
|
|
877
|
+
expect(presence_message.encoding).to be_nil
|
|
878
|
+
expect(presence_message.data).to eql(data)
|
|
879
|
+
stop_reactor
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
it 'emits decrypted update events' do
|
|
884
|
+
encrypted_channel.attach do
|
|
885
|
+
encrypted_channel.presence.enter(data: 'to be updated') do
|
|
886
|
+
encrypted_channel.presence.update data: data
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
encrypted_channel.presence.subscribe(:update) do |presence_message|
|
|
891
|
+
expect(presence_message.encoding).to be_nil
|
|
892
|
+
expect(presence_message.data).to eql(data)
|
|
893
|
+
stop_reactor
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
it 'emits previously set data for leave events' do
|
|
898
|
+
encrypted_channel.attach do
|
|
899
|
+
encrypted_channel.presence.enter(data: data) do
|
|
900
|
+
encrypted_channel.presence.leave
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
encrypted_channel.presence.subscribe(:leave) do |presence_message|
|
|
905
|
+
expect(presence_message.encoding).to be_nil
|
|
906
|
+
expect(presence_message.data).to eql(data)
|
|
907
|
+
stop_reactor
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
context '#get' do
|
|
913
|
+
it 'returns a list of members with decrypted data' do
|
|
914
|
+
encrypted_channel.presence.enter(data: data) do
|
|
915
|
+
encrypted_channel.presence.get do |members|
|
|
916
|
+
member = members.first
|
|
917
|
+
expect(member.encoding).to be_nil
|
|
918
|
+
expect(member.data).to eql(data)
|
|
919
|
+
stop_reactor
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
context 'REST #get' do
|
|
926
|
+
it 'returns a list of members with decrypted data' do
|
|
927
|
+
encrypted_channel.presence.enter(data: data) do
|
|
928
|
+
member = channel_rest_client_one.presence.get.first
|
|
929
|
+
expect(member.encoding).to be_nil
|
|
930
|
+
expect(member.data).to eql(data)
|
|
931
|
+
stop_reactor
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
context 'when cipher settings do not match publisher' do
|
|
937
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
|
938
|
+
let(:incompatible_cipher_options) { { key: secret_key, algorithm: 'aes', mode: 'cbc', key_length: 128 } }
|
|
939
|
+
let(:incompatible_encrypted_channel) { client_two.channel(channel_name, encrypted: true, cipher_params: incompatible_cipher_options) }
|
|
940
|
+
|
|
941
|
+
it 'delivers an unencoded presence message left with encoding value' do
|
|
942
|
+
encrypted_channel.presence.enter data: data
|
|
943
|
+
|
|
944
|
+
incompatible_encrypted_channel.presence.subscribe(:enter) do
|
|
945
|
+
incompatible_encrypted_channel.presence.get do |members|
|
|
946
|
+
member = members.first
|
|
947
|
+
expect(member.encoding).to match(/cipher\+aes-256-cbc/)
|
|
948
|
+
expect(member.data).to_not eql(data)
|
|
949
|
+
stop_reactor
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
it 'emits an error when cipher does not match and presence data cannot be decoded' do
|
|
955
|
+
incompatible_encrypted_channel.attach do
|
|
956
|
+
incompatible_encrypted_channel.on(:error) do |error|
|
|
957
|
+
expect(error).to be_a(Ably::Exceptions::CipherError)
|
|
958
|
+
expect(error.message).to match(/Cipher algorithm AES-128-CBC does not match/)
|
|
959
|
+
stop_reactor
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
encrypted_channel.attach do
|
|
963
|
+
encrypted_channel.presence.enter data: data
|
|
964
|
+
end
|
|
965
|
+
end
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
context 'leaving' do
|
|
971
|
+
specify 'expect :left event once underlying connection is closed' do
|
|
972
|
+
presence_client_one.on(:left) do
|
|
973
|
+
expect(presence_client_one.state).to eq(:left)
|
|
974
|
+
stop_reactor
|
|
975
|
+
end
|
|
976
|
+
presence_client_one.enter do
|
|
977
|
+
client_one.close
|
|
978
|
+
end
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
specify 'expect :left event with client data from enter event' do
|
|
982
|
+
presence_client_one.subscribe(:leave) do |message|
|
|
983
|
+
presence_client_one.get do |members|
|
|
984
|
+
expect(members.count).to eq(0)
|
|
985
|
+
expect(message.data).to eql(data_payload)
|
|
986
|
+
stop_reactor
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
presence_client_one.enter(data: data_payload) do
|
|
990
|
+
presence_client_one.leave
|
|
991
|
+
end
|
|
992
|
+
end
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
skip 'ensure connection_id is unique and updated on ENTER'
|
|
996
|
+
skip 'ensure connection_id for presence member matches the messages they publish on the channel'
|
|
997
|
+
skip 'stop a call to get when the channel has not been entered'
|
|
998
|
+
skip 'stop a call to get when the channel has been entered but the list is not up to date'
|
|
999
|
+
skip 'presence will resume sync if connection is dropped mid-way'
|
|
1000
|
+
end
|
|
1001
|
+
end
|