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,439 +1,453 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
- require 'securerandom'
3
- require 'json'
4
3
  require 'base64'
4
+ require 'json'
5
+ require 'securerandom'
5
6
 
6
- describe 'Ably::Realtime::Channel Messages' do
7
- include RSpec::EventMachine
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) }
8
15
 
9
- [:msgpack, :json].each do |protocol|
10
- context "over #{protocol}" do
11
- let(:default_options) { options.merge(api_key: api_key, environment: environment, protocol: protocol) }
12
- let(:client) do
13
- Ably::Realtime::Client.new(default_options)
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
14
32
  end
15
- let(:channel) { client.channel(channel_name) }
33
+ end
16
34
 
17
- let(:other_client) do
18
- Ably::Realtime::Client.new(default_options)
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
19
46
  end
20
- let(:other_client_channel) { other_client.channel(channel_name) }
47
+ end
21
48
 
22
- let(:channel_name) { 'subscribe_send_text' }
23
- let(:options) { { :protocol => :json } }
24
- let(:payload) { 'Test message (subscribe_send_text)' }
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) }
25
52
 
26
- it 'sends a string message' do
27
- run_reactor do
28
- channel.attach
29
- channel.on(:attached) do
30
- channel.publish('test_event', payload) do |message|
31
- expect(message.data).to eql(payload)
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)
32
70
  stop_reactor
33
71
  end
72
+ channel.publish('event', payload)
34
73
  end
35
74
  end
36
75
  end
37
76
 
38
- it 'sends a single message with an echo on another connection' do
39
- run_reactor do
40
- other_client_channel.attach do
41
- channel.publish 'test_event', payload
42
- other_client_channel.subscribe('test_event') do |message|
43
- expect(message.data).to eql(payload)
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)
44
82
  stop_reactor
45
83
  end
46
84
  end
47
85
  end
48
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
49
99
 
50
- context 'with echo_messages => false' do
100
+ context 'with :echo_messages option set to false' do
51
101
  let(:no_echo_client) do
52
102
  Ably::Realtime::Client.new(default_options.merge(echo_messages: false))
53
103
  end
54
104
  let(:no_echo_channel) { no_echo_client.channel(channel_name) }
55
105
 
56
- it 'sends a single message without a reply yet the messages is echoed on another normal connection' do
57
- run_reactor(10) do
58
- channel.attach do |echo_channel|
59
- no_echo_channel.attach do
60
- no_echo_channel.publish 'test_event', payload
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
61
110
 
62
- no_echo_channel.subscribe('test_event') do |message|
63
- fail "Message should not have been echoed back"
64
- end
111
+ no_echo_channel.subscribe('test_event') do |message|
112
+ fail "Message should not have been echoed back"
113
+ end
65
114
 
66
- echo_channel.subscribe('test_event') do |message|
67
- expect(message.data).to eql(payload)
68
- EventMachine.add_timer(1) do
69
- stop_reactor
70
- end
115
+ echo_channel.subscribe('test_event') do |message|
116
+ expect(message.data).to eql(payload)
117
+ EventMachine.add_timer(1) do
118
+ stop_reactor
71
119
  end
72
120
  end
73
121
  end
74
122
  end
75
123
  end
76
124
  end
125
+ end
77
126
 
78
- context 'with multiple messages' do
79
- let(:send_count) { 15 }
80
- let(:expected_echos) { send_count * 2 }
81
- let(:channel_name) { SecureRandom.hex }
82
- let(:echos) do
83
- { client: 0, other: 0 }
84
- end
85
- let(:callbacks) do
86
- { client: 0, other: 0 }
87
- end
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
88
137
 
89
- it 'sends and receives the messages on both opened connections and calls the callbacks (expects twice number of messages due to local echos)' do
90
- run_reactor(8) do
91
- check_message_and_callback_counts = Proc.new do
92
- if echos[:client] == expected_echos && echos[:other] == expected_echos
93
- # Wait for message backlog to clear
94
- EventMachine.add_timer(0.5) do
95
- expect(echos[:client]).to eql(expected_echos)
96
- expect(echos[:other]).to eql(expected_echos)
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)
97
145
 
98
- expect(callbacks[:client]).to eql(send_count)
99
- expect(callbacks[:other]).to eql(send_count)
146
+ expect(callbacks[:client]).to eql(send_count)
147
+ expect(callbacks[:other]).to eql(send_count)
100
148
 
101
- EventMachine.stop
102
- end
103
- end
149
+ stop_reactor
104
150
  end
151
+ end
152
+ end
105
153
 
106
- published = false
107
- attach_callback = Proc.new do
108
- next if published
109
-
110
- if channel.attached? && other_client_channel.attached?
111
- send_count.times do |index|
112
- channel.publish('test_event', "#{index}: #{payload}") do
113
- callbacks[:client] += 1
114
- end
115
- other_client_channel.publish('test_event', "#{index}: #{payload}") do
116
- callbacks[:other] += 1
117
- end
118
- end
119
-
120
- published = true
121
- end
122
- end
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
123
162
 
124
- channel.subscribe('test_event') do |message|
125
- echos[:client] += 1
126
- check_message_and_callback_counts.call
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
127
167
  end
128
- other_client_channel.subscribe('test_event') do |message|
129
- echos[:other] += 1
130
- check_message_and_callback_counts.call
168
+ other_client_channel.publish('test_event', "#{index}: #{payload}") do
169
+ callbacks[:other] += 1
131
170
  end
132
-
133
- channel.attach &attach_callback
134
- other_client_channel.attach &attach_callback
135
171
  end
136
172
  end
137
173
  end
174
+ end
138
175
 
139
- context 'without suitable publishing permissions' do
140
- let(:restricted_client) do
141
- Ably::Realtime::Client.new(options.merge(api_key: restricted_api_key, environment: environment, protocol: protocol))
142
- end
143
- let(:restricted_channel) { restricted_client.channel("cansubscribe:example") }
144
- let(:payload) { 'Test message without permission to publish' }
145
-
146
- it 'calls the error callback' do
147
- run_reactor do
148
- restricted_channel.attach
149
- restricted_channel.on(:attached) do
150
- deferrable = restricted_channel.publish('test_event', payload)
151
- deferrable.errback do |message, error|
152
- expect(message.data).to eql(payload)
153
- expect(error.status).to eql(401)
154
- stop_reactor
155
- end
156
- deferrable.callback do |message|
157
- fail 'Success callback should not have been called'
158
- stop_reactor
159
- end
160
- end
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'
161
193
  end
162
194
  end
163
195
  end
196
+ end
164
197
 
165
- context 'encoding and decoding encrypted messages' do
166
- shared_examples 'an Ably encrypter and decrypter' do |item, data|
167
- let(:algorithm) { data['algorithm'].upcase }
168
- let(:mode) { data['mode'].upcase }
169
- let(:key_length) { data['keylength'] }
170
- let(:secret_key) { Base64.decode64(data['key']) }
171
- let(:iv) { Base64.decode64(data['iv']) }
172
-
173
- let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
174
-
175
- context 'publish & subscribe' do
176
- let(:encoded) { item['encoded'] }
177
- let(:encoded_data) { encoded['data'] }
178
- let(:encoded_encoding) { encoded['encoding'] }
179
- let(:encoded_data_decoded) do
180
- if encoded_encoding == 'json'
181
- JSON.parse(encoded_data)
182
- elsif encoded_encoding == 'base64'
183
- Base64.decode64(encoded_data)
184
- else
185
- encoded_data
186
- end
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
187
219
  end
220
+ end
188
221
 
189
- let(:encrypted) { item['encrypted'] }
190
- let(:encrypted_data) { encrypted['data'] }
191
- let(:encrypted_encoding) { encrypted['encoding'] }
192
- let(:encrypted_data_decoded) do
193
- if encrypted_encoding.match(%r{/base64$})
194
- Base64.decode64(encrypted_data)
195
- else
196
- encrypted_data
197
- end
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
198
230
  end
231
+ end
199
232
 
200
- let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
201
-
202
- it 'encrypts message automatically when published' do
203
- run_reactor do
204
- encrypted_channel.__incoming_msgbus__.unsubscribe # remove all subscribe callbacks that could decrypt the message
205
-
206
- encrypted_channel.__incoming_msgbus__.subscribe(:message) do |message|
207
- if protocol == :json
208
- expect(message['encoding']).to eql(encrypted_encoding)
209
- expect(message['data']).to eql(encrypted_data)
210
- else
211
- # Messages received over binary protocol will not have Base64 encoded data
212
- expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
213
- expect(message['data']).to eql(encrypted_data_decoded)
214
- end
215
- stop_reactor
216
- end
233
+ let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
217
234
 
218
- encrypted_channel.publish 'example', encoded_data_decoded
219
- end
220
- end
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
221
237
 
222
- it 'sends and receives messages that are encrypted & decrypted by the Ably library' do
223
- run_reactor do
224
- encrypted_channel.publish 'example', encoded_data_decoded
225
- encrypted_channel.subscribe do |message|
226
- expect(message.data).to eql(encoded_data_decoded)
227
- expect(message.encoding).to be_nil
228
- stop_reactor
229
- end
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)
230
246
  end
247
+ stop_reactor
231
248
  end
232
- end
233
- end
234
249
 
235
- resources_root = File.expand_path('../../../resources', __FILE__)
250
+ encrypted_channel.publish 'example', encoded_data_decoded
251
+ end
236
252
 
237
- def self.add_tests_for_data(data)
238
- data['items'].each_with_index do |item, index|
239
- context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
240
- it_behaves_like 'an Ably encrypter and decrypter', item, data
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
241
259
  end
242
260
  end
243
261
  end
262
+ end
244
263
 
245
- context 'with AES-128-CBC' do
246
- data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
247
- add_tests_for_data data
248
- end
264
+ resources_root = File.expand_path('../../../resources', __FILE__)
249
265
 
250
- context 'with AES-256-CBC' do
251
- data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
252
- add_tests_for_data data
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
253
271
  end
272
+ end
254
273
 
255
- context 'multiple sends from one client to another' do
256
- let(:cipher_options) { { key: SecureRandom.hex(32) } }
257
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
258
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
259
-
260
- let(:data) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
261
- let(:message_count) { 50 }
262
-
263
- it 'encrypt and decrypt messages' do
264
- messages_received = {
265
- decrypted: 0,
266
- encrypted: 0
267
- }
268
-
269
- run_reactor do
270
- encrypted_channel_client2.attach do
271
- encrypted_channel_client2.subscribe do |message|
272
- expect(message.data).to eql("#{message.name}-#{data}")
273
- expect(message.encoding).to be_nil
274
- messages_received[:decrypted] += 1
275
- stop_reactor if messages_received[:decrypted] == message_count
276
- end
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
277
278
 
278
- encrypted_channel_client1.__incoming_msgbus__.subscribe(:message) do |message|
279
- expect(message['encoding']).to match(/cipher\+/)
280
- messages_received[:encrypted] += 1
281
- end
282
- end
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
283
 
284
- message_count.times do |index|
285
- encrypted_channel_client2.publish index.to_s, "#{index}-#{data}"
286
- end
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
287
309
  end
288
310
  end
289
- end
290
311
 
291
- context "sending using protocol #{protocol} and subscribing with a different protocol" do
292
- let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
293
- let(:other_client) do
294
- Ably::Realtime::Client.new(default_options.merge(protocol: other_protocol))
312
+ message_count.times do |index|
313
+ encrypted_channel_client2.publish index.to_s, "#{index}-#{data}"
295
314
  end
315
+ end
316
+ end
296
317
 
297
- let(:cipher_options) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
298
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
299
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
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
300
323
 
301
- before do
302
- expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
303
- end
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) }
304
327
 
305
- [MessagePack.pack({ 'key' => SecureRandom.hex }), '€ unicode', { 'key' => SecureRandom.hex }].each do |payload|
306
- payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
328
+ before do
329
+ expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
330
+ end
307
331
 
308
- specify "delivers a #{payload_description} payload to the receiver" do
309
- run_reactor do
310
- encrypted_channel_client1.publish 'example', payload
311
- encrypted_channel_client2.subscribe do |message|
312
- expect(message.data).to eql(payload)
313
- expect(message.encoding).to be_nil
314
- stop_reactor
315
- end
316
- end
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
317
341
  end
318
342
  end
319
343
  end
344
+ end
320
345
 
321
- context 'publishing on an unencrypted channel and subscribing on an encrypted channel with another client' do
322
- let(:cipher_options) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
323
- let(:unencrypted_channel_client1) { client.channel(channel_name) }
324
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
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) }
325
351
 
326
- let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
352
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
327
353
 
328
- it 'does not attempt to decrypt the message' do
329
- run_reactor do
330
- unencrypted_channel_client1.publish 'example', payload
331
- encrypted_channel_client2.subscribe do |message|
332
- expect(message.data).to eql(payload)
333
- expect(message.encoding).to be_nil
334
- stop_reactor
335
- end
336
- end
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
337
360
  end
338
361
  end
362
+ end
339
363
 
340
- context 'publishing on an encrypted channel and subscribing on an unencrypted channel with another client' do
341
- let(:cipher_options) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
342
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
343
- let(:unencrypted_channel_client2) { other_client.channel(channel_name) }
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) }
344
369
 
345
- let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
370
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
346
371
 
347
- it 'delivers the message but still encrypted' do
348
- run_reactor do
349
- encrypted_channel_client1.publish 'example', payload
350
- unencrypted_channel_client2.subscribe do |message|
351
- expect(message.data).to_not eql(payload)
352
- expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
353
- stop_reactor
354
- end
355
- end
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
356
378
  end
379
+ end
357
380
 
358
- it 'triggers a Cipher error on the channel' do
359
- run_reactor do
360
- unencrypted_channel_client2.attach do
361
- encrypted_channel_client1.publish 'example', payload
362
- unencrypted_channel_client2.on(:error) do |error|
363
- expect(error).to be_a(Ably::Exceptions::CipherError)
364
- expect(error.code).to eql(92001)
365
- expect(error.message).to match(/Message cannot be decrypted/)
366
- stop_reactor
367
- end
368
- end
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
369
389
  end
370
390
  end
371
391
  end
392
+ end
372
393
 
373
- context 'publishing on an encrypted channel and subscribing with a different algorithm on another client' do
374
- let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
375
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
376
- let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
377
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
378
-
379
- let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
380
-
381
- it 'delivers the message but still encrypted' do
382
- run_reactor do
383
- encrypted_channel_client1.publish 'example', payload
384
- encrypted_channel_client2.subscribe do |message|
385
- expect(message.data).to_not eql(payload)
386
- expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
387
- stop_reactor
388
- end
389
- end
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
390
409
  end
410
+ end
391
411
 
392
- it 'triggers a Cipher error on the channel' do
393
- run_reactor do
394
- encrypted_channel_client2.attach do
395
- encrypted_channel_client1.publish 'example', payload
396
- encrypted_channel_client2.on(:error) do |error|
397
- expect(error).to be_a(Ably::Exceptions::CipherError)
398
- expect(error.code).to eql(92002)
399
- expect(error.message).to match(/Cipher algorithm [\w\d-]+ does not match/)
400
- stop_reactor
401
- end
402
- end
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
403
420
  end
404
421
  end
405
422
  end
423
+ end
406
424
 
407
- context 'publishing on an encrypted channel and subscribing with a different key on another client' do
408
- let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
409
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
410
- let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
411
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
412
-
413
- let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
414
-
415
- it 'delivers the message but still encrypted' do
416
- run_reactor do
417
- encrypted_channel_client1.publish 'example', payload
418
- encrypted_channel_client2.subscribe do |message|
419
- expect(message.data).to_not eql(payload)
420
- expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
421
- stop_reactor
422
- end
423
- end
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
424
440
  end
441
+ end
425
442
 
426
- it 'triggers a Cipher error on the channel' do
427
- run_reactor do
428
- encrypted_channel_client2.attach do
429
- encrypted_channel_client1.publish 'example', payload
430
- encrypted_channel_client2.on(:error) do |error|
431
- expect(error).to be_a(Ably::Exceptions::CipherError)
432
- expect(error.code).to eql(92003)
433
- expect(error.message).to match(/CipherError decrypting data/)
434
- stop_reactor
435
- end
436
- end
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
437
451
  end
438
452
  end
439
453
  end