ably-rest 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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