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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version.old +1 -0
- data/.travis.yml +0 -2
- data/Rakefile +22 -4
- data/SPEC.md +1676 -0
- data/ably.gemspec +1 -1
- data/lib/ably.rb +0 -8
- data/lib/ably/auth.rb +54 -46
- data/lib/ably/exceptions.rb +19 -5
- data/lib/ably/logger.rb +1 -1
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
- data/lib/ably/models/message.rb +15 -12
- data/lib/ably/models/message_encoders/base.rb +6 -5
- data/lib/ably/models/message_encoders/base64.rb +1 -0
- data/lib/ably/models/message_encoders/cipher.rb +6 -3
- data/lib/ably/models/message_encoders/json.rb +1 -0
- data/lib/ably/models/message_encoders/utf8.rb +2 -9
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_resource.rb +5 -2
- data/lib/ably/models/presence_message.rb +21 -12
- data/lib/ably/models/protocol_message.rb +22 -6
- data/lib/ably/modules/ably.rb +11 -0
- data/lib/ably/modules/async_wrapper.rb +2 -0
- data/lib/ably/modules/conversions.rb +23 -3
- data/lib/ably/modules/encodeable.rb +2 -1
- data/lib/ably/modules/enum.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +7 -1
- data/lib/ably/modules/event_machine_helpers.rb +2 -0
- data/lib/ably/modules/http_helpers.rb +2 -0
- data/lib/ably/modules/model_common.rb +12 -2
- data/lib/ably/modules/state_emitter.rb +76 -0
- data/lib/ably/modules/state_machine.rb +53 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/ably/realtime.rb +4 -2
- data/lib/ably/realtime/channel.rb +51 -58
- data/lib/ably/realtime/channel/channel_manager.rb +91 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
- data/lib/ably/realtime/client.rb +70 -26
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +135 -92
- data/lib/ably/realtime/connection/connection_manager.rb +216 -33
- data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
- data/lib/ably/realtime/models/nil_channel.rb +10 -1
- data/lib/ably/realtime/presence.rb +336 -92
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +13 -4
- data/lib/ably/rest/client.rb +138 -38
- data/lib/ably/rest/middleware/logger.rb +24 -3
- data/lib/ably/rest/presence.rb +12 -7
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
- data/spec/acceptance/realtime/channel_spec.rb +461 -120
- data/spec/acceptance/realtime/client_spec.rb +119 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
- data/spec/acceptance/realtime/connection_spec.rb +571 -97
- data/spec/acceptance/realtime/message_spec.rb +347 -333
- data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
- data/spec/acceptance/realtime/presence_spec.rb +769 -239
- data/spec/acceptance/realtime/stats_spec.rb +14 -22
- data/spec/acceptance/realtime/time_spec.rb +16 -20
- data/spec/acceptance/rest/auth_spec.rb +425 -364
- data/spec/acceptance/rest/base_spec.rb +108 -176
- data/spec/acceptance/rest/channel_spec.rb +89 -89
- data/spec/acceptance/rest/channels_spec.rb +30 -32
- data/spec/acceptance/rest/client_spec.rb +273 -0
- data/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/spec/acceptance/rest/message_spec.rb +186 -163
- data/spec/acceptance/rest/presence_spec.rb +150 -111
- data/spec/acceptance/rest/stats_spec.rb +45 -40
- data/spec/acceptance/rest/time_spec.rb +8 -10
- data/spec/rspec_config.rb +10 -1
- data/spec/shared/client_initializer_behaviour.rb +212 -0
- data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
- data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/support/api_helper.rb +11 -0
- data/spec/support/event_machine_helper.rb +101 -3
- data/spec/support/markdown_spec_formatter.rb +90 -0
- data/spec/support/private_api_formatter.rb +36 -0
- data/spec/support/protocol_helper.rb +32 -0
- data/spec/support/random_helper.rb +15 -0
- data/spec/support/test_app.rb +4 -0
- data/spec/unit/auth_spec.rb +68 -0
- data/spec/unit/logger_spec.rb +77 -66
- data/spec/unit/models/error_info_spec.rb +1 -1
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
- data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
- data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
- data/spec/unit/models/message_spec.rb +160 -15
- data/spec/unit/models/paginated_resource_spec.rb +29 -27
- data/spec/unit/models/presence_message_spec.rb +163 -20
- data/spec/unit/models/protocol_message_spec.rb +43 -8
- data/spec/unit/modules/async_wrapper_spec.rb +2 -3
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/enum_spec.rb +2 -3
- data/spec/unit/modules/event_emitter_spec.rb +62 -5
- data/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/spec/unit/realtime/channel_spec.rb +107 -2
- data/spec/unit/realtime/channels_spec.rb +1 -0
- data/spec/unit/realtime/client_spec.rb +8 -48
- data/spec/unit/realtime/connection_spec.rb +3 -3
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
- data/spec/unit/realtime/presence_spec.rb +13 -4
- data/spec/unit/realtime/realtime_spec.rb +0 -11
- data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
- data/spec/unit/rest/channel_spec.rb +109 -0
- data/spec/unit/rest/channels_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +30 -125
- data/spec/unit/rest/rest_spec.rb +10 -0
- data/spec/unit/util/crypto_spec.rb +10 -5
- data/spec/unit/util/pub_sub_spec.rb +5 -5
- metadata +44 -12
- data/spec/integration/modules/state_emitter_spec.rb +0 -80
- data/spec/integration/rest/auth.rb +0 -9
@@ -1,221 +1,244 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'spec_helper'
|
2
3
|
require 'securerandom'
|
3
4
|
|
4
|
-
describe
|
5
|
+
describe Ably::Rest::Channel, 'messages' do
|
5
6
|
include Ably::Modules::Conversions
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
let(:iv) { Base64.decode64(data['iv']) }
|
25
|
-
|
26
|
-
let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
|
27
|
-
|
28
|
-
context 'publish & subscribe' do
|
29
|
-
let(:encoded) { item['encoded'] }
|
30
|
-
let(:encoded_data) { encoded['data'] }
|
31
|
-
let(:encoded_encoding) { encoded['encoding'] }
|
32
|
-
let(:encoded_data_decoded) do
|
33
|
-
if encoded_encoding == 'json'
|
34
|
-
JSON.parse(encoded_data)
|
35
|
-
elsif encoded_encoding == 'base64'
|
36
|
-
Base64.decode64(encoded_data)
|
37
|
-
else
|
38
|
-
encoded_data
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
let(:encrypted) { item['encrypted'] }
|
43
|
-
let(:encrypted_data) { encrypted['data'] }
|
44
|
-
let(:encrypted_encoding) { encrypted['encoding'] }
|
45
|
-
let(:encrypted_data_decoded) do
|
46
|
-
if encrypted_encoding.match(%r{/base64$})
|
47
|
-
Base64.decode64(encrypted_data)
|
48
|
-
else
|
49
|
-
encrypted_data
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'encrypts message automatically when published' do
|
54
|
-
expect(client).to receive(:post) do |path, message|
|
55
|
-
if protocol == :json
|
56
|
-
expect(message['encoding']).to eql(encrypted_encoding)
|
57
|
-
expect(Base64.decode64(message['data'])).to eql(encrypted_data_decoded)
|
58
|
-
else
|
59
|
-
# Messages sent over binary protocol will not have Base64 encoded data
|
60
|
-
expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
|
61
|
-
expect(message['data']).to eql(encrypted_data_decoded)
|
62
|
-
end
|
63
|
-
end.and_return(double('Response', status: 201))
|
64
|
-
|
65
|
-
encrypted_channel.publish 'example', encoded_data_decoded
|
66
|
-
end
|
67
|
-
|
68
|
-
it 'sends and receives messages that are encrypted & decrypted by the Ably library' do
|
69
|
-
encrypted_channel.publish 'example', encoded_data_decoded
|
8
|
+
vary_by_protocol do
|
9
|
+
let(:default_client_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
10
|
+
let(:client_options) { default_client_options }
|
11
|
+
let(:client) { Ably::Rest::Client.new(client_options) }
|
12
|
+
let(:other_client) { Ably::Rest::Client.new(client_options) }
|
13
|
+
let(:channel) { client.channel('test') }
|
14
|
+
|
15
|
+
context 'publishing with an ASCII_8BIT message name' do
|
16
|
+
let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) }
|
17
|
+
|
18
|
+
it 'is converted into UTF_8' do
|
19
|
+
channel.publish message_name, 'example'
|
20
|
+
message = channel.history.first
|
21
|
+
expect(message.name.encoding).to eql(Encoding::UTF_8)
|
22
|
+
expect(message.name.encode(Encoding::ASCII_8BIT)).to eql(message_name)
|
23
|
+
end
|
24
|
+
end
|
70
25
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
26
|
+
describe 'encryption and encoding' do
|
27
|
+
let(:channel_name) { "persisted:#{random_str}" }
|
28
|
+
let(:cipher_options) { { key: random_str(32) } }
|
29
|
+
let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
30
|
+
|
31
|
+
context 'with #publish and #history' do
|
32
|
+
shared_examples 'an Ably encrypter and decrypter' do |item, data|
|
33
|
+
let(:algorithm) { data['algorithm'].upcase }
|
34
|
+
let(:mode) { data['mode'].upcase }
|
35
|
+
let(:key_length) { data['keylength'] }
|
36
|
+
let(:secret_key) { Base64.decode64(data['key']) }
|
37
|
+
let(:iv) { Base64.decode64(data['iv']) }
|
38
|
+
|
39
|
+
let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
|
40
|
+
|
41
|
+
let(:encoded) { item['encoded'] }
|
42
|
+
let(:encoded_data) { encoded['data'] }
|
43
|
+
let(:encoded_encoding) { encoded['encoding'] }
|
44
|
+
let(:encoded_data_decoded) do
|
45
|
+
if encoded_encoding == 'json'
|
46
|
+
JSON.parse(encoded_data)
|
47
|
+
elsif encoded_encoding == 'base64'
|
48
|
+
Base64.decode64(encoded_data)
|
49
|
+
else
|
50
|
+
encoded_data
|
75
51
|
end
|
76
52
|
end
|
77
53
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
54
|
+
let(:encrypted) { item['encrypted'] }
|
55
|
+
let(:encrypted_data) { encrypted['data'] }
|
56
|
+
let(:encrypted_encoding) { encrypted['encoding'] }
|
57
|
+
let(:encrypted_data_decoded) do
|
58
|
+
if encrypted_encoding.match(%r{/base64$})
|
59
|
+
Base64.decode64(encrypted_data)
|
60
|
+
else
|
61
|
+
encrypted_data
|
85
62
|
end
|
86
63
|
end
|
87
64
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
65
|
+
it 'encrypts message automatically when published' do
|
66
|
+
expect(client).to receive(:post) do |path, message|
|
67
|
+
if protocol == :json
|
68
|
+
expect(message['encoding']).to eql(encrypted_encoding)
|
69
|
+
expect(Base64.decode64(message['data'])).to eql(encrypted_data_decoded)
|
70
|
+
else
|
71
|
+
# Messages sent over binary protocol will not have Base64 encoded data
|
72
|
+
expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
|
73
|
+
expect(message['data']).to eql(encrypted_data_decoded)
|
74
|
+
end
|
75
|
+
end.and_return(double('Response', status: 201))
|
92
76
|
|
93
|
-
|
94
|
-
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
|
95
|
-
add_tests_for_data data
|
77
|
+
encrypted_channel.publish 'example', encoded_data_decoded
|
96
78
|
end
|
97
79
|
|
98
|
-
|
99
|
-
|
100
|
-
let(:message_count) { 20 }
|
80
|
+
it 'sends and retrieves messages that are encrypted & decrypted by the Ably library' do
|
81
|
+
encrypted_channel.publish 'example', encoded_data_decoded
|
101
82
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
83
|
+
message = encrypted_channel.history.first
|
84
|
+
expect(message.data).to eql(encoded_data_decoded)
|
85
|
+
expect(message.encoding).to be_nil
|
86
|
+
end
|
87
|
+
end
|
106
88
|
|
107
|
-
|
89
|
+
resources_root = File.expand_path('../../../resources', __FILE__)
|
108
90
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
91
|
+
def self.add_tests_for_data(data)
|
92
|
+
data['items'].each_with_index do |item, index|
|
93
|
+
context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
|
94
|
+
it_behaves_like 'an Ably encrypter and decrypter', item, data
|
114
95
|
end
|
115
96
|
end
|
97
|
+
end
|
116
98
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
99
|
+
context 'with AES-128-CBC using crypto-data-128.json fixtures' do
|
100
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
|
101
|
+
add_tests_for_data data
|
102
|
+
end
|
121
103
|
|
122
|
-
|
123
|
-
|
124
|
-
|
104
|
+
context 'with AES-256-CBC using crypto-data-256.json fixtures' do
|
105
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
|
106
|
+
add_tests_for_data data
|
107
|
+
end
|
125
108
|
|
126
|
-
|
127
|
-
|
109
|
+
context 'when publishing lots of messages' do
|
110
|
+
let(:data) { MessagePack.pack({ 'key' => random_str }) }
|
111
|
+
let(:message_count) { 20 }
|
128
112
|
|
129
|
-
|
130
|
-
|
113
|
+
it 'encrypts on #publish and decrypts on #history' do
|
114
|
+
message_count.times do |index|
|
115
|
+
encrypted_channel.publish index.to_s, "#{index}-#{data}"
|
116
|
+
end
|
131
117
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
118
|
+
messages = encrypted_channel.history
|
119
|
+
|
120
|
+
expect(messages.count).to eql(message_count)
|
121
|
+
messages.each do |message|
|
122
|
+
expect(message.data).to eql("#{message.name}-#{data}")
|
123
|
+
expect(message.encoding).to be_nil
|
136
124
|
end
|
137
125
|
end
|
126
|
+
end
|
138
127
|
|
139
|
-
|
140
|
-
|
141
|
-
|
128
|
+
context 'when retrieving #history with a different protocol' do
|
129
|
+
let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
|
130
|
+
let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) }
|
131
|
+
let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
142
132
|
|
143
|
-
|
133
|
+
before do
|
134
|
+
expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
|
135
|
+
end
|
136
|
+
|
137
|
+
[MessagePack.pack({ 'key' => SecureRandom.hex }), 'ã unicode', { 'key' => SecureRandom.hex }].each do |payload|
|
138
|
+
payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
|
144
139
|
|
145
|
-
|
146
|
-
|
140
|
+
specify "delivers a #{payload_description} payload to the receiver" do
|
141
|
+
encrypted_channel.publish 'example', payload
|
147
142
|
|
148
|
-
message =
|
143
|
+
message = other_client_channel.history.first
|
149
144
|
expect(message.data).to eql(payload)
|
150
145
|
expect(message.encoding).to be_nil
|
151
146
|
end
|
152
147
|
end
|
148
|
+
end
|
153
149
|
|
154
|
-
|
155
|
-
|
156
|
-
|
150
|
+
context 'when publishing on an unencrypted channel and retrieving with #history on an encrypted channel' do
|
151
|
+
let(:unencrypted_channel) { client.channel(channel_name) }
|
152
|
+
let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
157
153
|
|
158
|
-
|
154
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
159
155
|
|
160
|
-
|
161
|
-
|
162
|
-
encrypted_channel.publish 'example', payload
|
156
|
+
it 'does not attempt to decrypt the message' do
|
157
|
+
unencrypted_channel.publish 'example', payload
|
163
158
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
159
|
+
message = other_client_encrypted_channel.history.first
|
160
|
+
expect(message.data).to eql(payload)
|
161
|
+
expect(message.encoding).to be_nil
|
162
|
+
end
|
163
|
+
end
|
168
164
|
|
169
|
-
|
170
|
-
|
171
|
-
|
165
|
+
context 'when publishing on an encrypted channel and retrieving with #history on an unencrypted channel' do
|
166
|
+
let(:client_options) { default_client_options.merge(log_level: :fatal) }
|
167
|
+
let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
168
|
+
let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
169
|
+
let(:other_client_unencrypted_channel) { other_client.channel(channel_name) }
|
170
|
+
|
171
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
172
|
+
|
173
|
+
before do
|
174
|
+
encrypted_channel.publish 'example', payload
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
|
178
|
+
message = other_client_unencrypted_channel.history.first
|
179
|
+
expect(message.data).to_not eql(payload)
|
180
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'logs a Cipher exception' do
|
184
|
+
expect(other_client.logger).to receive(:error) do |message|
|
185
|
+
expect(message).to match(/Message cannot be decrypted/)
|
172
186
|
end
|
187
|
+
other_client_unencrypted_channel.history
|
173
188
|
end
|
189
|
+
end
|
174
190
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
191
|
+
context 'publishing on an encrypted channel and retrieving #history with a different algorithm on another client' do
|
192
|
+
let(:client_options) { default_client_options.merge(log_level: :fatal) }
|
193
|
+
let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
194
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
195
|
+
let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
|
196
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
180
197
|
|
181
|
-
|
198
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
182
199
|
|
183
|
-
|
184
|
-
|
185
|
-
|
200
|
+
before do
|
201
|
+
encrypted_channel_client1.publish 'example', payload
|
202
|
+
end
|
186
203
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
204
|
+
it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
|
205
|
+
message = encrypted_channel_client2.history.first
|
206
|
+
expect(message.data).to_not eql(payload)
|
207
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
208
|
+
end
|
191
209
|
|
192
|
-
|
193
|
-
|
194
|
-
expect
|
210
|
+
it 'logs a Cipher exception' do
|
211
|
+
expect(other_client.logger).to receive(:error) do |message|
|
212
|
+
expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
|
195
213
|
end
|
214
|
+
encrypted_channel_client2.history
|
196
215
|
end
|
216
|
+
end
|
197
217
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
218
|
+
context 'publishing on an encrypted channel and subscribing with a different key on another client' do
|
219
|
+
let(:client_options) { default_client_options.merge(log_level: :fatal) }
|
220
|
+
let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
221
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
222
|
+
let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
223
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
203
224
|
|
204
|
-
|
225
|
+
let(:payload) { MessagePack.pack({ 'key' => random_str }) }
|
205
226
|
|
206
|
-
|
207
|
-
|
208
|
-
|
227
|
+
before do
|
228
|
+
encrypted_channel_client1.publish 'example', payload
|
229
|
+
end
|
209
230
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
231
|
+
it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
|
232
|
+
message = encrypted_channel_client2.history.first
|
233
|
+
expect(message.data).to_not eql(payload)
|
234
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
235
|
+
end
|
214
236
|
|
215
|
-
|
216
|
-
|
217
|
-
expect
|
237
|
+
it 'logs a Cipher exception' do
|
238
|
+
expect(other_client.logger).to receive(:error) do |message|
|
239
|
+
expect(message).to match(/CipherError decrypting data/)
|
218
240
|
end
|
241
|
+
encrypted_channel_client2.history
|
219
242
|
end
|
220
243
|
end
|
221
244
|
end
|
@@ -1,38 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
require 'spec_helper'
|
2
|
-
require 'securerandom'
|
3
3
|
|
4
4
|
describe Ably::Rest::Presence do
|
5
5
|
include Ably::Modules::Conversions
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
vary_by_protocol do
|
8
|
+
let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
9
|
+
let(:client_options) { default_options }
|
10
|
+
let(:client) do
|
11
|
+
Ably::Rest::Client.new(client_options)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:fixtures) do
|
15
|
+
TestApp::APP_SPEC['channels'].first['presence'].map do |fixture|
|
16
|
+
IdiomaticRubyWrapper(fixture, stop_at: [:data])
|
11
17
|
end
|
18
|
+
end
|
12
19
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
context 'tested against presence fixture data set up in test app' do
|
21
|
+
before(:context) do
|
22
|
+
# When this test is run as a part of a test suite, the presence data injected in the test app may have expired
|
23
|
+
WebMock.disable!
|
24
|
+
TestApp.reload
|
17
25
|
end
|
18
26
|
|
19
|
-
describe '#get
|
27
|
+
describe '#get' do
|
20
28
|
let(:channel) { client.channel('persisted:presence_fixtures') }
|
21
29
|
let(:presence) { channel.presence.get }
|
22
30
|
|
23
|
-
it 'returns current members on the channel' do
|
31
|
+
it 'returns current members on the channel with their action set to :present' do
|
24
32
|
expect(presence.size).to eql(4)
|
25
33
|
|
26
34
|
fixtures.each do |fixture|
|
27
35
|
presence_message = presence.find { |client| client.client_id == fixture[:client_id] }
|
28
36
|
expect(presence_message.data).to eq(fixture[:data])
|
37
|
+
expect(presence_message.action).to eq(:present)
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
32
|
-
|
41
|
+
context 'with :limit option' do
|
42
|
+
let(:page_size) { 2 }
|
43
|
+
let(:presence) { channel.presence.get(limit: page_size) }
|
44
|
+
|
45
|
+
it 'returns a paged response limiting number of members per page' do
|
46
|
+
expect(presence.size).to eql(2)
|
47
|
+
next_page = presence.next_page
|
48
|
+
expect(next_page.size).to eql(2)
|
49
|
+
expect(next_page).to be_last_page
|
50
|
+
end
|
51
|
+
end
|
33
52
|
end
|
34
53
|
|
35
|
-
describe '
|
54
|
+
describe '#history' do
|
36
55
|
let(:channel) { client.channel('persisted:presence_fixtures') }
|
37
56
|
let(:presence_history) { channel.presence.history }
|
38
57
|
|
@@ -48,11 +67,11 @@ describe Ably::Rest::Presence do
|
|
48
67
|
context 'with options' do
|
49
68
|
let(:page_size) { 2 }
|
50
69
|
|
51
|
-
context 'forwards' do
|
70
|
+
context 'direction: :forwards' do
|
52
71
|
let(:presence_history) { channel.presence.history(direction: :forwards) }
|
53
72
|
let(:paged_history_forward) { channel.presence.history(limit: page_size, direction: :forwards) }
|
54
73
|
|
55
|
-
it 'returns recent presence activity with
|
74
|
+
it 'returns recent presence activity forwards with most recent history last' do
|
56
75
|
expect(paged_history_forward).to be_a(Ably::Models::PaginatedResource)
|
57
76
|
expect(paged_history_forward.size).to eql(2)
|
58
77
|
|
@@ -63,11 +82,11 @@ describe Ably::Rest::Presence do
|
|
63
82
|
end
|
64
83
|
end
|
65
84
|
|
66
|
-
context 'backwards' do
|
85
|
+
context 'direction: :backwards' do
|
67
86
|
let(:presence_history) { channel.presence.history(direction: :backwards) }
|
68
87
|
let(:paged_history_backward) { channel.presence.history(limit: page_size, direction: :backwards) }
|
69
88
|
|
70
|
-
it 'returns recent presence activity with
|
89
|
+
it 'returns recent presence activity backwards with most recent history first' do
|
71
90
|
expect(paged_history_backward).to be_a(Ably::Models::PaginatedResource)
|
72
91
|
expect(paged_history_backward.size).to eql(2)
|
73
92
|
|
@@ -79,12 +98,14 @@ describe Ably::Rest::Presence do
|
|
79
98
|
end
|
80
99
|
end
|
81
100
|
end
|
101
|
+
end
|
82
102
|
|
83
|
-
|
84
|
-
|
103
|
+
describe '#history' do
|
104
|
+
context 'with time range options' do
|
105
|
+
let(:channel_name) { "persisted:#{random_str(4)}" }
|
85
106
|
let(:presence) { client.channel(channel_name).presence }
|
86
107
|
let(:user) { 'appid.keyuid' }
|
87
|
-
let(:secret) {
|
108
|
+
let(:secret) { random_str(8) }
|
88
109
|
let(:endpoint) do
|
89
110
|
client.endpoint.tap do |client_end_point|
|
90
111
|
client_end_point.user = user
|
@@ -96,9 +117,9 @@ describe Ably::Rest::Presence do
|
|
96
117
|
end
|
97
118
|
|
98
119
|
[:start, :end].each do |option|
|
99
|
-
describe "
|
120
|
+
describe ":#{option}", :webmock do
|
100
121
|
let!(:history_stub) {
|
101
|
-
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history
|
122
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?#{option}=#{milliseconds}").
|
102
123
|
to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
|
103
124
|
}
|
104
125
|
|
@@ -106,141 +127,159 @@ describe Ably::Rest::Presence do
|
|
106
127
|
presence.history(options)
|
107
128
|
end
|
108
129
|
|
109
|
-
context 'with milliseconds since epoch' do
|
130
|
+
context 'with milliseconds since epoch value' do
|
110
131
|
let(:milliseconds) { as_since_epoch(Time.now) }
|
111
132
|
let(:options) { { option => milliseconds } }
|
112
133
|
|
113
|
-
|
134
|
+
it 'uses this value in the history request' do
|
114
135
|
expect(history_stub).to have_been_requested
|
115
136
|
end
|
116
137
|
end
|
117
138
|
|
118
|
-
context 'with Time' do
|
139
|
+
context 'with Time object value' do
|
119
140
|
let(:time) { Time.now }
|
120
141
|
let(:milliseconds) { as_since_epoch(time) }
|
121
142
|
let(:options) { { option => time } }
|
122
143
|
|
123
|
-
|
144
|
+
it 'converts the value to milliseconds since epoch in the hisotry request' do
|
124
145
|
expect(history_stub).to have_been_requested
|
125
146
|
end
|
126
147
|
end
|
127
148
|
end
|
128
149
|
end
|
129
150
|
end
|
151
|
+
end
|
130
152
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
139
|
-
end
|
140
|
-
let(:client) do
|
141
|
-
Ably::Rest::Client.new(api_key: "#{user}:#{secret}", environment: environment, protocol: protocol)
|
153
|
+
describe 'decoding', :webmock do
|
154
|
+
let(:user) { 'appid.keyuid' }
|
155
|
+
let(:secret) { random_str(8) }
|
156
|
+
let(:endpoint) do
|
157
|
+
client.endpoint.tap do |client_end_point|
|
158
|
+
client_end_point.user = user
|
159
|
+
client_end_point.password = secret
|
142
160
|
end
|
161
|
+
end
|
162
|
+
let(:client) do
|
163
|
+
Ably::Rest::Client.new(client_options.merge(api_key: "#{user}:#{secret}"))
|
164
|
+
end
|
165
|
+
|
166
|
+
let(:data) { random_str(32) }
|
167
|
+
let(:channel_name) { "persisted:#{random_str(4)}" }
|
168
|
+
let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
169
|
+
let(:presence) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options).presence }
|
143
170
|
|
144
|
-
|
145
|
-
let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" }
|
146
|
-
let(:cipher_options) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
147
|
-
let(:presence) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options).presence }
|
171
|
+
let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
|
148
172
|
|
149
|
-
|
173
|
+
let(:content_type) do
|
174
|
+
if protocol == :msgpack
|
175
|
+
'application/x-msgpack'
|
176
|
+
else
|
177
|
+
'application/json'
|
178
|
+
end
|
179
|
+
end
|
150
180
|
|
151
|
-
|
181
|
+
context 'valid decodeable content' do
|
182
|
+
let(:serialized_encoded_message) do
|
152
183
|
if protocol == :msgpack
|
153
|
-
'
|
184
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-256-cbc' })
|
185
|
+
MessagePack.pack([msg.as_json])
|
154
186
|
else
|
155
|
-
'
|
187
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-256-cbc/base64' })
|
188
|
+
[msg].to_json
|
156
189
|
end
|
157
190
|
end
|
158
191
|
|
159
|
-
context '
|
160
|
-
let(:
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
else
|
165
|
-
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-256-cbc/base64' })
|
166
|
-
[msg].to_json
|
167
|
-
end
|
168
|
-
end
|
192
|
+
context '#get' do
|
193
|
+
let!(:get_stub) {
|
194
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
|
195
|
+
to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
|
196
|
+
}
|
169
197
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
|
174
|
-
}
|
175
|
-
|
176
|
-
after do
|
177
|
-
expect(get_stub).to have_been_requested
|
178
|
-
end
|
198
|
+
after do
|
199
|
+
expect(get_stub).to have_been_requested
|
200
|
+
end
|
179
201
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
end
|
202
|
+
it 'automaticaly decodes presence messages' do
|
203
|
+
present = presence.get
|
204
|
+
expect(present.first.encoding).to be_nil
|
205
|
+
expect(present.first.data).to eql(data)
|
185
206
|
end
|
207
|
+
end
|
186
208
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
209
|
+
context '#history' do
|
210
|
+
let!(:history_stub) {
|
211
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
|
212
|
+
to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
|
213
|
+
}
|
192
214
|
|
193
|
-
|
194
|
-
|
195
|
-
|
215
|
+
after do
|
216
|
+
expect(history_stub).to have_been_requested
|
217
|
+
end
|
196
218
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
end
|
219
|
+
it 'automaticaly decodes presence messages' do
|
220
|
+
history = presence.history
|
221
|
+
expect(history.first.encoding).to be_nil
|
222
|
+
expect(history.first.data).to eql(data)
|
202
223
|
end
|
203
224
|
end
|
225
|
+
end
|
204
226
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
end
|
227
|
+
context 'invalid data' do
|
228
|
+
let(:serialized_encoded_message_with_invalid_encoding) do
|
229
|
+
if protocol == :msgpack
|
230
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-128-cbc' })
|
231
|
+
MessagePack.pack([msg.as_json])
|
232
|
+
else
|
233
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-128-cbc/base64' })
|
234
|
+
[msg].to_json
|
214
235
|
end
|
236
|
+
end
|
215
237
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
238
|
+
context '#get' do
|
239
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
240
|
+
let!(:get_stub) {
|
241
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
|
242
|
+
to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
|
243
|
+
}
|
244
|
+
let(:presence_message) { presence.get.first }
|
221
245
|
|
222
|
-
|
223
|
-
|
224
|
-
|
246
|
+
after do
|
247
|
+
expect(get_stub).to have_been_requested
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'returns the messages still encoded' do
|
251
|
+
expect(presence_message.encoding).to match(/cipher\+aes-128-cbc/)
|
252
|
+
end
|
225
253
|
|
226
|
-
|
227
|
-
|
254
|
+
it 'logs a cipher error' do
|
255
|
+
expect(client.logger).to receive(:error) do |message|
|
256
|
+
expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
|
228
257
|
end
|
258
|
+
presence.get
|
229
259
|
end
|
260
|
+
end
|
230
261
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
262
|
+
context '#history' do
|
263
|
+
let(:client_options) { default_options.merge(log_level: :fatal) }
|
264
|
+
let!(:history_stub) {
|
265
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
|
266
|
+
to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
|
267
|
+
}
|
268
|
+
let(:presence_message) { presence.history.first }
|
236
269
|
|
237
|
-
|
238
|
-
|
239
|
-
|
270
|
+
after do
|
271
|
+
expect(history_stub).to have_been_requested
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'returns the messages still encoded' do
|
275
|
+
expect(presence_message.encoding).to match(/cipher\+aes-128-cbc/)
|
276
|
+
end
|
240
277
|
|
241
|
-
|
242
|
-
|
278
|
+
it 'logs a cipher error' do
|
279
|
+
expect(client.logger).to receive(:error) do |message|
|
280
|
+
expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
|
243
281
|
end
|
282
|
+
presence.history
|
244
283
|
end
|
245
284
|
end
|
246
285
|
end
|