ably 0.6.2 → 0.7.0

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