ably 0.6.2 → 0.7.0

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