ably 0.1.6 → 0.2.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/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/LICENSE.txt +1 -1
- data/README.md +8 -1
- data/Rakefile +10 -0
- data/ably.gemspec +18 -18
- data/lib/ably.rb +6 -5
- data/lib/ably/auth.rb +11 -14
- data/lib/ably/exceptions.rb +18 -15
- data/lib/ably/logger.rb +102 -0
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/message.rb +19 -5
- data/lib/ably/models/message_encoders/base.rb +107 -0
- data/lib/ably/models/message_encoders/base64.rb +39 -0
- data/lib/ably/models/message_encoders/cipher.rb +80 -0
- data/lib/ably/models/message_encoders/json.rb +33 -0
- data/lib/ably/models/message_encoders/utf8.rb +33 -0
- data/lib/ably/models/paginated_resource.rb +23 -6
- data/lib/ably/models/presence_message.rb +19 -7
- data/lib/ably/models/protocol_message.rb +5 -4
- data/lib/ably/models/token.rb +2 -2
- data/lib/ably/modules/channels_collection.rb +0 -3
- data/lib/ably/modules/conversions.rb +3 -3
- data/lib/ably/modules/encodeable.rb +68 -0
- data/lib/ably/modules/event_emitter.rb +10 -4
- data/lib/ably/modules/event_machine_helpers.rb +6 -4
- data/lib/ably/modules/http_helpers.rb +7 -2
- data/lib/ably/modules/model_common.rb +2 -0
- data/lib/ably/modules/state_emitter.rb +10 -1
- data/lib/ably/realtime.rb +19 -12
- data/lib/ably/realtime/channel.rb +26 -13
- data/lib/ably/realtime/client.rb +31 -7
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
- data/lib/ably/realtime/connection.rb +152 -46
- data/lib/ably/realtime/connection/connection_manager.rb +168 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
- data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
- data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
- data/lib/ably/realtime/presence.rb +38 -13
- data/lib/ably/rest.rb +7 -5
- data/lib/ably/rest/channel.rb +24 -3
- data/lib/ably/rest/client.rb +56 -17
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +3 -2
- data/lib/ably/rest/middleware/logger.rb +37 -0
- data/lib/ably/rest/presence.rb +10 -2
- data/lib/ably/util/crypto.rb +57 -29
- data/lib/ably/util/pub_sub.rb +11 -0
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_spec.rb +65 -7
- data/spec/acceptance/realtime/connection_spec.rb +123 -27
- data/spec/acceptance/realtime/message_spec.rb +319 -34
- data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
- data/spec/acceptance/realtime/presence_spec.rb +160 -18
- data/spec/acceptance/rest/auth_spec.rb +93 -49
- data/spec/acceptance/rest/base_spec.rb +10 -10
- data/spec/acceptance/rest/channel_spec.rb +35 -19
- data/spec/acceptance/rest/channels_spec.rb +8 -8
- data/spec/acceptance/rest/message_spec.rb +224 -0
- data/spec/acceptance/rest/presence_spec.rb +159 -23
- data/spec/acceptance/rest/stats_spec.rb +5 -5
- data/spec/acceptance/rest/time_spec.rb +4 -4
- data/spec/integration/rest/auth.rb +1 -1
- data/spec/resources/crypto-data-128.json +56 -0
- data/spec/resources/crypto-data-256.json +56 -0
- data/spec/rspec_config.rb +39 -0
- data/spec/spec_helper.rb +4 -42
- data/spec/support/api_helper.rb +1 -1
- data/spec/support/event_machine_helper.rb +0 -5
- data/spec/support/protocol_msgbus_helper.rb +3 -3
- data/spec/support/test_app.rb +3 -3
- data/spec/unit/logger_spec.rb +135 -0
- data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
- data/spec/unit/models/message_spec.rb +16 -1
- data/spec/unit/models/paginated_resource_spec.rb +46 -0
- data/spec/unit/models/presence_message_spec.rb +18 -5
- data/spec/unit/models/token_spec.rb +1 -1
- data/spec/unit/modules/event_emitter_spec.rb +24 -10
- data/spec/unit/realtime/channel_spec.rb +3 -3
- data/spec/unit/realtime/channels_spec.rb +1 -1
- data/spec/unit/realtime/client_spec.rb +44 -2
- data/spec/unit/realtime/connection_spec.rb +2 -2
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
- data/spec/unit/realtime/presence_spec.rb +1 -1
- data/spec/unit/realtime/realtime_spec.rb +3 -3
- data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
- data/spec/unit/rest/channels_spec.rb +1 -1
- data/spec/unit/rest/client_spec.rb +45 -10
- data/spec/unit/util/crypto_spec.rb +82 -0
- data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
- metadata +43 -12
- data/spec/acceptance/crypto.rb +0 -63
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'REST' do
|
5
5
|
let(:client) do
|
6
6
|
Ably::Rest::Client.new(api_key: api_key, environment: environment)
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
9
|
+
describe 'protocol' do
|
10
10
|
include Ably::Modules::Conversions
|
11
11
|
|
12
12
|
let(:client_options) { {} }
|
@@ -71,8 +71,8 @@ describe "REST" do
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
describe
|
75
|
-
it
|
74
|
+
describe 'invalid requests in middleware' do
|
75
|
+
it 'should raise an InvalidRequest exception with a valid message' do
|
76
76
|
invalid_client = Ably::Rest::Client.new(api_key: 'appid.keyuid:keysecret', environment: environment)
|
77
77
|
expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error|
|
78
78
|
expect(error).to be_a(Ably::Exceptions::InvalidToken)
|
@@ -82,7 +82,7 @@ describe "REST" do
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
describe
|
85
|
+
describe 'server error with JSON response', webmock: true do
|
86
86
|
let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
|
87
87
|
|
88
88
|
before do
|
@@ -90,18 +90,18 @@ describe "REST" do
|
|
90
90
|
to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
|
91
91
|
end
|
92
92
|
|
93
|
-
it
|
93
|
+
it 'should raise a ServerError exception' do
|
94
94
|
expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Internal error/)
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
describe
|
98
|
+
describe 'server error', webmock: true do
|
99
99
|
before do
|
100
100
|
stub_request(:get, "#{client.endpoint}/time").
|
101
101
|
to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
|
102
102
|
end
|
103
103
|
|
104
|
-
it
|
104
|
+
it 'should raise a ServerError exception' do
|
105
105
|
expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Unknown/)
|
106
106
|
end
|
107
107
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Ably::Rest::Channel do
|
5
5
|
include Ably::Modules::Conversions
|
6
6
|
|
7
7
|
[:msgpack, :json].each do |protocol|
|
@@ -10,44 +10,60 @@ describe "REST" do
|
|
10
10
|
Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
|
11
11
|
end
|
12
12
|
|
13
|
-
describe
|
14
|
-
let(:channel) { client.channel(
|
15
|
-
let(:event) {
|
16
|
-
let(:message) {
|
13
|
+
describe 'publishing messages' do
|
14
|
+
let(:channel) { client.channel('test') }
|
15
|
+
let(:event) { 'foo' }
|
16
|
+
let(:message) { 'woop!' }
|
17
17
|
|
18
|
-
it
|
18
|
+
it 'should publish the message ok' do
|
19
19
|
expect(channel.publish(event, message)).to eql(true)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
describe
|
23
|
+
describe 'fetching channel history' do
|
24
24
|
let(:channel) { client.channel("persisted:#{SecureRandom.hex(4)}") }
|
25
25
|
let(:expected_history) do
|
26
26
|
[
|
27
|
-
{ :name =>
|
28
|
-
{ :name =>
|
29
|
-
{ :name =>
|
27
|
+
{ :name => 'test1', :data => 'foo' },
|
28
|
+
{ :name => 'test2', :data => 'bar' },
|
29
|
+
{ :name => 'test3', :data => 'baz' }
|
30
30
|
]
|
31
31
|
end
|
32
|
+
let!(:before_published) { client.time }
|
32
33
|
|
33
34
|
before(:each) do
|
34
35
|
expected_history.each do |message|
|
35
|
-
channel.publish(message[:name], message[:data]) || raise(
|
36
|
+
channel.publish(message[:name], message[:data]) || raise('Unable to publish message')
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
it
|
40
|
+
it 'should return all the history for the channel' do
|
40
41
|
actual_history = channel.history
|
41
42
|
|
42
43
|
expect(actual_history.size).to eql(3)
|
43
44
|
|
44
45
|
expected_history.each do |message|
|
45
|
-
|
46
|
-
|
46
|
+
message_name, message_data = message[:name], message[:data]
|
47
|
+
matching_message = actual_history.find { |message| message.name == message_name && message.data == message_data }
|
48
|
+
expect(matching_message).to be_a(Ably::Models::Message)
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
|
52
|
+
context 'timestamps' do
|
53
|
+
it 'should be greater than the time before the messages were published' do
|
54
|
+
channel.history.each do |message|
|
55
|
+
expect(before_published.to_f).to be < message.timestamp.to_f
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should return messages with unique IDs' do
|
61
|
+
message_ids = channel.history.map(&:id).compact
|
62
|
+
expect(message_ids.count).to eql(3)
|
63
|
+
expect(message_ids.uniq.count).to eql(3)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return paged history' do
|
51
67
|
page_1 = channel.history(limit: 1)
|
52
68
|
page_2 = page_1.next_page
|
53
69
|
page_3 = page_2.next_page
|
@@ -71,7 +87,7 @@ describe "REST" do
|
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
74
|
-
describe
|
90
|
+
describe 'history options' do
|
75
91
|
let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" }
|
76
92
|
let(:channel) { client.channel(channel_name) }
|
77
93
|
let(:endpoint) do
|
@@ -82,7 +98,7 @@ describe "REST" do
|
|
82
98
|
end
|
83
99
|
|
84
100
|
[:start, :end].each do |option|
|
85
|
-
describe "
|
101
|
+
describe ":#{option}", webmock: true do
|
86
102
|
let!(:history_stub) {
|
87
103
|
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?live=true&#{option}=#{milliseconds}").
|
88
104
|
to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
3
|
|
4
4
|
describe Ably::Rest::Channels do
|
5
5
|
[:msgpack, :json].each do |protocol|
|
@@ -10,30 +10,30 @@ describe Ably::Rest::Channels do
|
|
10
10
|
let(:channel_name) { SecureRandom.hex }
|
11
11
|
let(:options) { { key: 'value' } }
|
12
12
|
|
13
|
-
shared_examples
|
14
|
-
it
|
13
|
+
shared_examples 'a channel' do
|
14
|
+
it 'should access a channel' do
|
15
15
|
expect(channel).to be_a Ably::Rest::Channel
|
16
16
|
expect(channel.name).to eql(channel_name)
|
17
17
|
end
|
18
18
|
|
19
|
-
it
|
19
|
+
it 'should allow options to be set on a channel' do
|
20
20
|
expect(channel_with_options.options).to eql(options)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
describe
|
24
|
+
describe 'using shortcut method on client' do
|
25
25
|
let(:channel) { client.channel(channel_name) }
|
26
26
|
let(:channel_with_options) { client.channel(channel_name, options) }
|
27
27
|
it_behaves_like 'a channel'
|
28
28
|
end
|
29
29
|
|
30
|
-
describe
|
30
|
+
describe 'using documented .get method on client.channels' do
|
31
31
|
let(:channel) { client.channels.get(channel_name) }
|
32
32
|
let(:channel_with_options) { client.channels.get(channel_name, options) }
|
33
33
|
it_behaves_like 'a channel'
|
34
34
|
end
|
35
35
|
|
36
|
-
describe
|
36
|
+
describe 'using undocumented [] method on client.channels' do
|
37
37
|
let(:channel) { client.channels[channel_name] }
|
38
38
|
let(:channel_with_options) { client.channels[channel_name, options] }
|
39
39
|
it_behaves_like 'a channel'
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe 'Ably::Rest Message' do
|
5
|
+
include Ably::Modules::Conversions
|
6
|
+
|
7
|
+
[:msgpack, :json].each do |protocol|
|
8
|
+
context "over #{protocol}" do
|
9
|
+
let(:default_client_options) { { api_key: api_key, environment: environment, protocol: protocol } }
|
10
|
+
let(:client) { Ably::Rest::Client.new(default_client_options) }
|
11
|
+
let(:other_client) { Ably::Rest::Client.new(default_client_options) }
|
12
|
+
|
13
|
+
describe 'encryption and encoding' do
|
14
|
+
let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" }
|
15
|
+
let(:cipher_options) { { key: SecureRandom.hex(32) } }
|
16
|
+
let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
17
|
+
|
18
|
+
context 'encoding and decoding encrypted messages' do
|
19
|
+
shared_examples 'an Ably encrypter and decrypter' do |item, data|
|
20
|
+
let(:algorithm) { data['algorithm'].upcase }
|
21
|
+
let(:mode) { data['mode'].upcase }
|
22
|
+
let(:key_length) { data['keylength'] }
|
23
|
+
let(:secret_key) { Base64.decode64(data['key']) }
|
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
|
70
|
+
|
71
|
+
message = encrypted_channel.history.first
|
72
|
+
expect(message.data).to eql(encoded_data_decoded)
|
73
|
+
expect(message.encoding).to be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
resources_root = File.expand_path('../../../resources', __FILE__)
|
79
|
+
|
80
|
+
def self.add_tests_for_data(data)
|
81
|
+
data['items'].each_with_index do |item, index|
|
82
|
+
context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
|
83
|
+
it_behaves_like 'an Ably encrypter and decrypter', item, data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with AES-128-CBC' do
|
89
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
|
90
|
+
add_tests_for_data data
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with AES-256-CBC' do
|
94
|
+
data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
|
95
|
+
add_tests_for_data data
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'multiple messages' do
|
99
|
+
let(:data) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
100
|
+
let(:message_count) { 20 }
|
101
|
+
|
102
|
+
it 'encrypt and decrypt messages' do
|
103
|
+
message_count.times do |index|
|
104
|
+
encrypted_channel.publish index.to_s, "#{index}-#{data}"
|
105
|
+
end
|
106
|
+
|
107
|
+
messages = encrypted_channel.history
|
108
|
+
|
109
|
+
expect(messages.count).to eql(message_count)
|
110
|
+
messages.each do |message|
|
111
|
+
expect(message.data).to eql("#{message.name}-#{data}")
|
112
|
+
expect(message.encoding).to be_nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "sending using protocol #{protocol} and retrieving with a different protocol" do
|
118
|
+
let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
|
119
|
+
let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) }
|
120
|
+
let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
121
|
+
|
122
|
+
before do
|
123
|
+
expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
|
124
|
+
end
|
125
|
+
|
126
|
+
[MessagePack.pack({ 'key' => SecureRandom.hex }), '€ unicode', { 'key' => SecureRandom.hex }].each do |payload|
|
127
|
+
payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
|
128
|
+
|
129
|
+
specify "delivers a #{payload_description} payload to the receiver" do
|
130
|
+
encrypted_channel.publish 'example', payload
|
131
|
+
|
132
|
+
message = other_client_channel.history.first
|
133
|
+
expect(message.data).to eql(payload)
|
134
|
+
expect(message.encoding).to be_nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'publishing on an unencrypted channel and retrieving on an encrypted channel' do
|
140
|
+
let(:unencrypted_channel) { client.channel(channel_name) }
|
141
|
+
let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
142
|
+
|
143
|
+
let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
144
|
+
|
145
|
+
it 'does not attempt to decrypt the message' do
|
146
|
+
unencrypted_channel.publish 'example', payload
|
147
|
+
|
148
|
+
message = other_client_encrypted_channel.history.first
|
149
|
+
expect(message.data).to eql(payload)
|
150
|
+
expect(message.encoding).to be_nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'publishing on an encrypted channel and retrieving on an unencrypted channel' do
|
155
|
+
let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
|
156
|
+
let(:other_client_unencrypted_channel) { other_client.channel(channel_name) }
|
157
|
+
|
158
|
+
let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
159
|
+
|
160
|
+
skip 'delivers the message but still encrypted' do
|
161
|
+
# TODO: Decide if we should raise an exception or allow the message through
|
162
|
+
encrypted_channel.publish 'example', payload
|
163
|
+
|
164
|
+
message = other_client_unencrypted_channel.history.first
|
165
|
+
expect(message.data).to_not eql(payload)
|
166
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'triggers a Cipher exception' do
|
170
|
+
encrypted_channel.publish 'example', payload
|
171
|
+
expect { other_client_unencrypted_channel.history }.to raise_error Ably::Exceptions::CipherError, /Message cannot be decrypted/
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'publishing on an encrypted channel and subscribing with a different algorithm on another client' do
|
176
|
+
let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
177
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
178
|
+
let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
|
179
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
180
|
+
|
181
|
+
let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
182
|
+
|
183
|
+
skip 'delivers the message but still encrypted' do
|
184
|
+
# TODO: Decide if we should raise an exception or allow the message through
|
185
|
+
encrypted_channel.publish 'example', payload
|
186
|
+
|
187
|
+
message = other_client_unencrypted_channel.history.first
|
188
|
+
expect(message.data).to_not eql(payload)
|
189
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'triggers a Cipher exception' do
|
193
|
+
encrypted_channel_client1.publish 'example', payload
|
194
|
+
expect { encrypted_channel_client2.history }.to raise_error Ably::Exceptions::CipherError, /Cipher algorithm [\w\d-]+ does not match/
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'publishing on an encrypted channel and subscribing with a different key on another client' do
|
199
|
+
let(:cipher_options_client1) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
200
|
+
let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
|
201
|
+
let(:cipher_options_client2) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
202
|
+
let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
|
203
|
+
|
204
|
+
let(:payload) { MessagePack.pack({ 'key' => SecureRandom.hex }) }
|
205
|
+
|
206
|
+
skip 'delivers the message but still encrypted' do
|
207
|
+
# TODO: Decide if we should raise an exception or allow the message through
|
208
|
+
encrypted_channel.publish 'example', payload
|
209
|
+
|
210
|
+
message = other_client_unencrypted_channel.history.first
|
211
|
+
expect(message.data).to_not eql(payload)
|
212
|
+
expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'triggers a Cipher exception' do
|
216
|
+
encrypted_channel_client1.publish 'example', payload
|
217
|
+
expect { encrypted_channel_client2.history }.to raise_error Ably::Exceptions::CipherError, /CipherError decrypting data/
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe Ably::Rest::Presence do
|
5
5
|
include Ably::Modules::Conversions
|
6
6
|
|
7
7
|
[:msgpack, :json].each do |protocol|
|
@@ -12,54 +12,73 @@ describe "REST" do
|
|
12
12
|
|
13
13
|
let(:fixtures) do
|
14
14
|
TestApp::APP_SPEC['channels'].first['presence'].map do |fixture|
|
15
|
-
IdiomaticRubyWrapper(fixture, stop_at: [:
|
15
|
+
IdiomaticRubyWrapper(fixture, stop_at: [:data])
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe
|
20
|
-
let(:channel) { client.channel(
|
19
|
+
describe '#get presence' do
|
20
|
+
let(:channel) { client.channel('persisted:presence_fixtures') }
|
21
21
|
let(:presence) { channel.presence.get }
|
22
22
|
|
23
|
-
it
|
23
|
+
it 'returns current members on the channel' do
|
24
24
|
expect(presence.size).to eql(4)
|
25
25
|
|
26
26
|
fixtures.each do |fixture|
|
27
27
|
presence_message = presence.find { |client| client.client_id == fixture[:client_id] }
|
28
|
-
expect(presence_message.
|
28
|
+
expect(presence_message.data).to eq(fixture[:data])
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
describe
|
34
|
-
let(:channel) { client.channel(
|
35
|
-
let(:
|
33
|
+
describe 'presence #history' do
|
34
|
+
let(:channel) { client.channel('persisted:presence_fixtures') }
|
35
|
+
let(:presence_history) { channel.presence.history }
|
36
36
|
|
37
|
-
it
|
38
|
-
expect(
|
37
|
+
it 'returns recent presence activity' do
|
38
|
+
expect(presence_history.size).to eql(4)
|
39
39
|
|
40
40
|
fixtures.each do |fixture|
|
41
|
-
presence_message =
|
42
|
-
expect(presence_message.
|
41
|
+
presence_message = presence_history.find { |client| client.client_id == fixture['clientId'] }
|
42
|
+
expect(presence_message.data).to eq(fixture[:data])
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
context 'with options' do
|
47
47
|
let(:page_size) { 2 }
|
48
|
-
let(:paged_history_forward) { channel.presence.history(limit: page_size, direction: :forwards) }
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
context 'forwards' do
|
50
|
+
let(:presence_history) { channel.presence.history(direction: :forwards) }
|
51
|
+
let(:paged_history_forward) { channel.presence.history(limit: page_size, direction: :forwards) }
|
53
52
|
|
54
|
-
|
53
|
+
it 'returns recent presence activity with options passed to Ably' do
|
54
|
+
expect(paged_history_forward).to be_a(Ably::Models::PaginatedResource)
|
55
|
+
expect(paged_history_forward.size).to eql(2)
|
55
56
|
|
56
|
-
|
57
|
-
|
57
|
+
next_page = paged_history_forward.next_page
|
58
|
+
|
59
|
+
expect(paged_history_forward.first.id).to eql(presence_history.first.id)
|
60
|
+
expect(next_page.first.id).to eql(presence_history[page_size].id)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'backwards' do
|
65
|
+
let(:presence_history) { channel.presence.history(direction: :backwards) }
|
66
|
+
let(:paged_history_backward) { channel.presence.history(limit: page_size, direction: :backwards) }
|
67
|
+
|
68
|
+
it 'returns recent presence activity with options passed to Ably' do
|
69
|
+
expect(paged_history_backward).to be_a(Ably::Models::PaginatedResource)
|
70
|
+
expect(paged_history_backward.size).to eql(2)
|
71
|
+
|
72
|
+
next_page = paged_history_backward.next_page
|
73
|
+
|
74
|
+
expect(paged_history_backward.first.id).to eql(presence_history.first.id)
|
75
|
+
expect(next_page.first.id).to eql(presence_history[page_size].id)
|
76
|
+
end
|
58
77
|
end
|
59
78
|
end
|
60
79
|
end
|
61
80
|
|
62
|
-
describe
|
81
|
+
describe 'options' do
|
63
82
|
let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" }
|
64
83
|
let(:presence) { client.channel(channel_name).presence }
|
65
84
|
let(:user) { 'appid.keyuid' }
|
@@ -106,6 +125,123 @@ describe "REST" do
|
|
106
125
|
end
|
107
126
|
end
|
108
127
|
end
|
128
|
+
|
129
|
+
describe 'decoding', webmock: true do
|
130
|
+
let(:user) { 'appid.keyuid' }
|
131
|
+
let(:secret) { SecureRandom.hex(8) }
|
132
|
+
let(:endpoint) do
|
133
|
+
client.endpoint.tap do |client_end_point|
|
134
|
+
client_end_point.user = user
|
135
|
+
client_end_point.password = secret
|
136
|
+
end
|
137
|
+
end
|
138
|
+
let(:client) do
|
139
|
+
Ably::Rest::Client.new(api_key: "#{user}:#{secret}", environment: environment, protocol: protocol)
|
140
|
+
end
|
141
|
+
|
142
|
+
let(:data) { SecureRandom.hex(32) }
|
143
|
+
let(:channel_name) { "persisted:#{SecureRandom.hex(4)}" }
|
144
|
+
let(:cipher_options) { { key: SecureRandom.hex(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
|
145
|
+
let(:presence) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options).presence }
|
146
|
+
|
147
|
+
let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
|
148
|
+
|
149
|
+
let(:content_type) do
|
150
|
+
if protocol == :msgpack
|
151
|
+
'application/x-msgpack'
|
152
|
+
else
|
153
|
+
'application/json'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'valid decodeable content' do
|
158
|
+
let(:serialized_encoded_message) do
|
159
|
+
if protocol == :msgpack
|
160
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-256-cbc' })
|
161
|
+
MessagePack.pack([msg.as_json])
|
162
|
+
else
|
163
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-256-cbc/base64' })
|
164
|
+
[msg].to_json
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context '#get' do
|
169
|
+
let!(:get_stub) {
|
170
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
|
171
|
+
to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
|
172
|
+
}
|
173
|
+
|
174
|
+
after do
|
175
|
+
expect(get_stub).to have_been_requested
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'automaticaly decodes presence messages' do
|
179
|
+
present = presence.get
|
180
|
+
expect(present.first.encoding).to be_nil
|
181
|
+
expect(present.first.data).to eql(data)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context '#history' do
|
186
|
+
let!(:history_stub) {
|
187
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?live=true").
|
188
|
+
to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
|
189
|
+
}
|
190
|
+
|
191
|
+
after do
|
192
|
+
expect(history_stub).to have_been_requested
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'automaticaly decodes presence messages' do
|
196
|
+
history = presence.history
|
197
|
+
expect(history.first.encoding).to be_nil
|
198
|
+
expect(history.first.data).to eql(data)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'invalid data' do
|
204
|
+
let(:serialized_encoded_message_with_invalid_encoding) do
|
205
|
+
if protocol == :msgpack
|
206
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-128-cbc' })
|
207
|
+
MessagePack.pack([msg.as_json])
|
208
|
+
else
|
209
|
+
msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-128-cbc/base64' })
|
210
|
+
[msg].to_json
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context '#get' do
|
215
|
+
let!(:get_stub) {
|
216
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
|
217
|
+
to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
|
218
|
+
}
|
219
|
+
|
220
|
+
after do
|
221
|
+
expect(get_stub).to have_been_requested
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'raises a cipher error' do
|
225
|
+
expect { presence.get }.to raise_error Ably::Exceptions::CipherError
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context '#history' do
|
230
|
+
let!(:history_stub) {
|
231
|
+
stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?live=true").
|
232
|
+
to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
|
233
|
+
}
|
234
|
+
|
235
|
+
after do
|
236
|
+
expect(history_stub).to have_been_requested
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'raises a cipher error' do
|
240
|
+
expect { presence.history }.to raise_error Ably::Exceptions::CipherError
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
109
245
|
end
|
110
246
|
end
|
111
247
|
end
|