ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,457 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'base64'
4
+ require 'json'
5
+ require 'securerandom'
6
+
7
+ describe 'Ably::Realtime::Channel Message', :event_machine do
8
+ vary_by_protocol do
9
+ let(:default_options) { options.merge(api_key: api_key, environment: environment, protocol: protocol) }
10
+ let(:client_options) { default_options }
11
+ let(:client) do
12
+ Ably::Realtime::Client.new(client_options)
13
+ end
14
+ let(:channel) { client.channel(channel_name) }
15
+
16
+ let(:other_client) do
17
+ Ably::Realtime::Client.new(client_options)
18
+ end
19
+ let(:other_client_channel) { other_client.channel(channel_name) }
20
+
21
+ let(:channel_name) { "subscribe_send_text-#{random_str}" }
22
+ let(:options) { { :protocol => :json } }
23
+ let(:payload) { 'Test message (subscribe_send_text)' }
24
+
25
+ it 'sends a String data payload' do
26
+ channel.attach
27
+ channel.on(:attached) do
28
+ channel.publish('test_event', payload) do |message|
29
+ expect(message.data).to eql(payload)
30
+ stop_reactor
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'with ASCII_8BIT message name' do
36
+ let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) }
37
+ it 'is converted into UTF_8' do
38
+ channel.attach do
39
+ channel.publish message_name, payload
40
+ end
41
+ channel.subscribe do |message|
42
+ expect(message.name.encoding).to eql(Encoding::UTF_8)
43
+ expect(message.name.encode(Encoding::ASCII_8BIT)).to eql(message_name)
44
+ stop_reactor
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'when the message publisher has a client_id' do
50
+ let(:client_id) { random_str }
51
+ let(:client_options) { default_options.merge(client_id: client_id) }
52
+
53
+ it 'contains a #client_id attribute' do
54
+ when_all(channel.attach, other_client_channel.attach) do
55
+ other_client_channel.subscribe('event') do |message|
56
+ expect(message.client_id).to eql(client_id)
57
+ stop_reactor
58
+ end
59
+ channel.publish('event', payload)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#connection_id attribute' do
65
+ context 'over realtime' do
66
+ it 'matches the sender connection#id' do
67
+ when_all(channel.attach, other_client_channel.attach) do
68
+ other_client_channel.subscribe('event') do |message|
69
+ expect(message.connection_id).to eql(client.connection.id)
70
+ stop_reactor
71
+ end
72
+ channel.publish('event', payload)
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'when retrieved over REST' do
78
+ it 'matches the sender connection#id' do
79
+ channel.publish('event', payload) do
80
+ channel.history do |messages|
81
+ expect(messages.first.connection_id).to eql(client.connection.id)
82
+ stop_reactor
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe 'local echo when published' do
90
+ it 'is enabled by default' do
91
+ channel.attach do
92
+ channel.publish 'test_event', payload
93
+ channel.subscribe('test_event') do |message|
94
+ expect(message.data).to eql(payload)
95
+ stop_reactor
96
+ end
97
+ end
98
+ end
99
+
100
+ context 'with :echo_messages option set to false' do
101
+ let(:no_echo_client) do
102
+ Ably::Realtime::Client.new(default_options.merge(echo_messages: false))
103
+ end
104
+ let(:no_echo_channel) { no_echo_client.channel(channel_name) }
105
+
106
+ it 'will not echo messages to the client but will still broadcast messages to other connected clients', em_timeout: 10 do
107
+ channel.attach do |echo_channel|
108
+ no_echo_channel.attach do
109
+ no_echo_channel.publish 'test_event', payload
110
+
111
+ no_echo_channel.subscribe('test_event') do |message|
112
+ fail "Message should not have been echoed back"
113
+ end
114
+
115
+ echo_channel.subscribe('test_event') do |message|
116
+ expect(message.data).to eql(payload)
117
+ EventMachine.add_timer(1) do
118
+ stop_reactor
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'publishing lots of messages across two connections' do
128
+ let(:send_count) { 30 }
129
+ let(:expected_echos) { send_count * 2 }
130
+ let(:channel_name) { random_str }
131
+ let(:echos) do
132
+ { client: 0, other: 0 }
133
+ end
134
+ let(:callbacks) do
135
+ { client: 0, other: 0 }
136
+ end
137
+
138
+ it 'sends and receives the messages on both opened connections and calls the success callbacks for each message published', em_timeout: 10 do
139
+ check_message_and_callback_counts = Proc.new do
140
+ if echos[:client] == expected_echos && echos[:other] == expected_echos
141
+ # Wait for message backlog to clear
142
+ EventMachine.add_timer(0.5) do
143
+ expect(echos[:client]).to eql(expected_echos)
144
+ expect(echos[:other]).to eql(expected_echos)
145
+
146
+ expect(callbacks[:client]).to eql(send_count)
147
+ expect(callbacks[:other]).to eql(send_count)
148
+
149
+ stop_reactor
150
+ end
151
+ end
152
+ end
153
+
154
+ channel.subscribe('test_event') do |message|
155
+ echos[:client] += 1
156
+ check_message_and_callback_counts.call
157
+ end
158
+ other_client_channel.subscribe('test_event') do |message|
159
+ echos[:other] += 1
160
+ check_message_and_callback_counts.call
161
+ end
162
+
163
+ when_all(channel.attach, other_client_channel.attach) do
164
+ send_count.times do |index|
165
+ channel.publish('test_event', "#{index}: #{payload}") do
166
+ callbacks[:client] += 1
167
+ end
168
+ other_client_channel.publish('test_event', "#{index}: #{payload}") do
169
+ callbacks[:other] += 1
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ context 'without suitable publishing permissions' do
177
+ let(:restricted_client) do
178
+ Ably::Realtime::Client.new(options.merge(api_key: restricted_api_key, environment: environment, protocol: protocol))
179
+ end
180
+ let(:restricted_channel) { restricted_client.channel("cansubscribe:example") }
181
+ let(:payload) { 'Test message without permission to publish' }
182
+
183
+ it 'calls the error callback' do
184
+ restricted_channel.attach do
185
+ deferrable = restricted_channel.publish('test_event', payload)
186
+ deferrable.errback do |message, error|
187
+ expect(message.data).to eql(payload)
188
+ expect(error.status).to eql(401)
189
+ stop_reactor
190
+ end
191
+ deferrable.callback do |message|
192
+ fail 'Success callback should not have been called'
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ context 'encoding and decoding encrypted messages' do
199
+ shared_examples 'an Ably encrypter and decrypter' do |item, data|
200
+ let(:algorithm) { data['algorithm'].upcase }
201
+ let(:mode) { data['mode'].upcase }
202
+ let(:key_length) { data['keylength'] }
203
+ let(:secret_key) { Base64.decode64(data['key']) }
204
+ let(:iv) { Base64.decode64(data['iv']) }
205
+
206
+ let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
207
+
208
+ context 'with #publish and #subscribe' do
209
+ let(:encoded) { item['encoded'] }
210
+ let(:encoded_data) { encoded['data'] }
211
+ let(:encoded_encoding) { encoded['encoding'] }
212
+ let(:encoded_data_decoded) do
213
+ if encoded_encoding == 'json'
214
+ JSON.parse(encoded_data)
215
+ elsif encoded_encoding == 'base64'
216
+ Base64.decode64(encoded_data)
217
+ else
218
+ encoded_data
219
+ end
220
+ end
221
+
222
+ let(:encrypted) { item['encrypted'] }
223
+ let(:encrypted_data) { encrypted['data'] }
224
+ let(:encrypted_encoding) { encrypted['encoding'] }
225
+ let(:encrypted_data_decoded) do
226
+ if encrypted_encoding.match(%r{/base64$})
227
+ Base64.decode64(encrypted_data)
228
+ else
229
+ encrypted_data
230
+ end
231
+ end
232
+
233
+ let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
234
+
235
+ it 'encrypts message automatically before they are pushed to the server' do
236
+ encrypted_channel.__incoming_msgbus__.unsubscribe # remove all subscribe callbacks that could decrypt the message
237
+
238
+ encrypted_channel.__incoming_msgbus__.subscribe(:message) do |message|
239
+ if protocol == :json
240
+ expect(message['encoding']).to eql(encrypted_encoding)
241
+ expect(message['data']).to eql(encrypted_data)
242
+ else
243
+ # Messages received over binary protocol will not have Base64 encoded data
244
+ expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
245
+ expect(message['data']).to eql(encrypted_data_decoded)
246
+ end
247
+ stop_reactor
248
+ end
249
+
250
+ encrypted_channel.publish 'example', encoded_data_decoded
251
+ end
252
+
253
+ it 'sends and receives messages that are encrypted & decrypted by the Ably library' do
254
+ encrypted_channel.publish 'example', encoded_data_decoded
255
+ encrypted_channel.subscribe do |message|
256
+ expect(message.data).to eql(encoded_data_decoded)
257
+ expect(message.encoding).to be_nil
258
+ stop_reactor
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ resources_root = File.expand_path('../../../resources', __FILE__)
265
+
266
+ def self.add_tests_for_data(data)
267
+ data['items'].each_with_index do |item, index|
268
+ context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
269
+ it_behaves_like 'an Ably encrypter and decrypter', item, data
270
+ end
271
+ end
272
+ end
273
+
274
+ context 'with AES-128-CBC using crypto-data-128.json fixtures' do
275
+ data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
276
+ add_tests_for_data data
277
+ end
278
+
279
+ context 'with AES-256-CBC using crypto-data-256.json fixtures' do
280
+ data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
281
+ add_tests_for_data data
282
+ end
283
+
284
+ context 'with multiple sends from one client to another' do
285
+ let(:cipher_options) { { key: random_str(32) } }
286
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
287
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
288
+
289
+ let(:data) { MessagePack.pack({ 'key' => random_str }) }
290
+ let(:message_count) { 50 }
291
+
292
+ it 'encrypts and decrypts all messages' do
293
+ messages_received = {
294
+ decrypted: 0,
295
+ encrypted: 0
296
+ }
297
+
298
+ encrypted_channel_client2.attach do
299
+ encrypted_channel_client2.subscribe do |message|
300
+ expect(message.data).to eql("#{message.name}-#{data}")
301
+ expect(message.encoding).to be_nil
302
+ messages_received[:decrypted] += 1
303
+ stop_reactor if messages_received[:decrypted] == message_count
304
+ end
305
+
306
+ encrypted_channel_client1.__incoming_msgbus__.subscribe(:message) do |message|
307
+ expect(message['encoding']).to match(/cipher\+/)
308
+ messages_received[:encrypted] += 1
309
+ end
310
+ end
311
+
312
+ message_count.times do |index|
313
+ encrypted_channel_client2.publish index.to_s, "#{index}-#{data}"
314
+ end
315
+ end
316
+ end
317
+
318
+ context 'subscribing with a different transport protocol' do
319
+ let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
320
+ let(:other_client) do
321
+ Ably::Realtime::Client.new(default_options.merge(protocol: other_protocol))
322
+ end
323
+
324
+ let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
325
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
326
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
327
+
328
+ before do
329
+ expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
330
+ end
331
+
332
+ [MessagePack.pack({ 'key' => SecureRandom.hex }), 'ã unicode', { 'key' => SecureRandom.hex }].each do |payload|
333
+ payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
334
+
335
+ it "delivers a #{payload_description} payload to the receiver" do
336
+ encrypted_channel_client1.publish 'example', payload
337
+ encrypted_channel_client2.subscribe do |message|
338
+ expect(message.data).to eql(payload)
339
+ expect(message.encoding).to be_nil
340
+ stop_reactor
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ context 'publishing on an unencrypted channel and subscribing on an encrypted channel with another client' do
347
+ let(:client_options) { default_options.merge(log_level: :fatal) }
348
+ let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
349
+ let(:unencrypted_channel_client1) { client.channel(channel_name) }
350
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
351
+
352
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
353
+
354
+ it 'does not attempt to decrypt the message' do
355
+ unencrypted_channel_client1.publish 'example', payload
356
+ encrypted_channel_client2.subscribe do |message|
357
+ expect(message.data).to eql(payload)
358
+ expect(message.encoding).to be_nil
359
+ stop_reactor
360
+ end
361
+ end
362
+ end
363
+
364
+ context 'publishing on an encrypted channel and subscribing on an unencrypted channel with another client' do
365
+ let(:client_options) { default_options.merge(log_level: :fatal) }
366
+ let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
367
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
368
+ let(:unencrypted_channel_client2) { other_client.channel(channel_name) }
369
+
370
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
371
+
372
+ it 'delivers the message but still encrypted with a value in the #encoding attribute' do
373
+ encrypted_channel_client1.publish 'example', payload
374
+ unencrypted_channel_client2.subscribe do |message|
375
+ expect(message.data).to_not eql(payload)
376
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
377
+ stop_reactor
378
+ end
379
+ end
380
+
381
+ it 'triggers a Cipher error on the channel' do
382
+ unencrypted_channel_client2.attach do
383
+ encrypted_channel_client1.publish 'example', payload
384
+ unencrypted_channel_client2.on(:error) do |error|
385
+ expect(error).to be_a(Ably::Exceptions::CipherError)
386
+ expect(error.code).to eql(92001)
387
+ expect(error.message).to match(/Message cannot be decrypted/)
388
+ stop_reactor
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ context 'publishing on an encrypted channel and subscribing with a different algorithm on another client' do
395
+ let(:client_options) { default_options.merge(log_level: :fatal) }
396
+ let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
397
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
398
+ let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
399
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
400
+
401
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
402
+
403
+ it 'delivers the message but still encrypted with the cipher detials in the #encoding attribute' do
404
+ encrypted_channel_client1.publish 'example', payload
405
+ encrypted_channel_client2.subscribe do |message|
406
+ expect(message.data).to_not eql(payload)
407
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
408
+ stop_reactor
409
+ end
410
+ end
411
+
412
+ it 'triggers a Cipher error on the channel' do
413
+ encrypted_channel_client2.attach do
414
+ encrypted_channel_client1.publish 'example', payload
415
+ encrypted_channel_client2.on(:error) do |error|
416
+ expect(error).to be_a(Ably::Exceptions::CipherError)
417
+ expect(error.code).to eql(92002)
418
+ expect(error.message).to match(/Cipher algorithm [\w-]+ does not match/)
419
+ stop_reactor
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ context 'publishing on an encrypted channel and subscribing with a different key on another client' do
426
+ let(:client_options) { default_options.merge(log_level: :fatal) }
427
+ let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
428
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
429
+ let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
430
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
431
+
432
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
433
+
434
+ it 'delivers the message but still encrypted with the cipher details in the #encoding attribute' do
435
+ encrypted_channel_client1.publish 'example', payload
436
+ encrypted_channel_client2.subscribe do |message|
437
+ expect(message.data).to_not eql(payload)
438
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
439
+ stop_reactor
440
+ end
441
+ end
442
+
443
+ it 'triggers a Cipher error on the channel' do
444
+ encrypted_channel_client2.attach do
445
+ encrypted_channel_client1.publish 'example', payload
446
+ encrypted_channel_client2.on(:error) do |error|
447
+ expect(error).to be_a(Ably::Exceptions::CipherError)
448
+ expect(error.code).to eql(92003)
449
+ expect(error.message).to match(/CipherError decrypting data/)
450
+ stop_reactor
451
+ end
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end