ably 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -1,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