ably 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +9 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +8 -1
  6. data/Rakefile +10 -0
  7. data/ably.gemspec +18 -18
  8. data/lib/ably.rb +6 -5
  9. data/lib/ably/auth.rb +11 -14
  10. data/lib/ably/exceptions.rb +18 -15
  11. data/lib/ably/logger.rb +102 -0
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/message.rb +19 -5
  14. data/lib/ably/models/message_encoders/base.rb +107 -0
  15. data/lib/ably/models/message_encoders/base64.rb +39 -0
  16. data/lib/ably/models/message_encoders/cipher.rb +80 -0
  17. data/lib/ably/models/message_encoders/json.rb +33 -0
  18. data/lib/ably/models/message_encoders/utf8.rb +33 -0
  19. data/lib/ably/models/paginated_resource.rb +23 -6
  20. data/lib/ably/models/presence_message.rb +19 -7
  21. data/lib/ably/models/protocol_message.rb +5 -4
  22. data/lib/ably/models/token.rb +2 -2
  23. data/lib/ably/modules/channels_collection.rb +0 -3
  24. data/lib/ably/modules/conversions.rb +3 -3
  25. data/lib/ably/modules/encodeable.rb +68 -0
  26. data/lib/ably/modules/event_emitter.rb +10 -4
  27. data/lib/ably/modules/event_machine_helpers.rb +6 -4
  28. data/lib/ably/modules/http_helpers.rb +7 -2
  29. data/lib/ably/modules/model_common.rb +2 -0
  30. data/lib/ably/modules/state_emitter.rb +10 -1
  31. data/lib/ably/realtime.rb +19 -12
  32. data/lib/ably/realtime/channel.rb +26 -13
  33. data/lib/ably/realtime/client.rb +31 -7
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -3
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +13 -4
  36. data/lib/ably/realtime/connection.rb +152 -46
  37. data/lib/ably/realtime/connection/connection_manager.rb +168 -0
  38. data/lib/ably/realtime/connection/connection_state_machine.rb +56 -33
  39. data/lib/ably/realtime/connection/websocket_transport.rb +56 -29
  40. data/lib/ably/{models → realtime/models}/nil_channel.rb +1 -1
  41. data/lib/ably/realtime/presence.rb +38 -13
  42. data/lib/ably/rest.rb +7 -5
  43. data/lib/ably/rest/channel.rb +24 -3
  44. data/lib/ably/rest/client.rb +56 -17
  45. data/lib/ably/rest/middleware/encoder.rb +49 -0
  46. data/lib/ably/rest/middleware/exceptions.rb +3 -2
  47. data/lib/ably/rest/middleware/logger.rb +37 -0
  48. data/lib/ably/rest/presence.rb +10 -2
  49. data/lib/ably/util/crypto.rb +57 -29
  50. data/lib/ably/util/pub_sub.rb +11 -0
  51. data/lib/ably/version.rb +1 -1
  52. data/spec/acceptance/realtime/channel_spec.rb +65 -7
  53. data/spec/acceptance/realtime/connection_spec.rb +123 -27
  54. data/spec/acceptance/realtime/message_spec.rb +319 -34
  55. data/spec/acceptance/realtime/presence_history_spec.rb +58 -0
  56. data/spec/acceptance/realtime/presence_spec.rb +160 -18
  57. data/spec/acceptance/rest/auth_spec.rb +93 -49
  58. data/spec/acceptance/rest/base_spec.rb +10 -10
  59. data/spec/acceptance/rest/channel_spec.rb +35 -19
  60. data/spec/acceptance/rest/channels_spec.rb +8 -8
  61. data/spec/acceptance/rest/message_spec.rb +224 -0
  62. data/spec/acceptance/rest/presence_spec.rb +159 -23
  63. data/spec/acceptance/rest/stats_spec.rb +5 -5
  64. data/spec/acceptance/rest/time_spec.rb +4 -4
  65. data/spec/integration/rest/auth.rb +1 -1
  66. data/spec/resources/crypto-data-128.json +56 -0
  67. data/spec/resources/crypto-data-256.json +56 -0
  68. data/spec/rspec_config.rb +39 -0
  69. data/spec/spec_helper.rb +4 -42
  70. data/spec/support/api_helper.rb +1 -1
  71. data/spec/support/event_machine_helper.rb +0 -5
  72. data/spec/support/protocol_msgbus_helper.rb +3 -3
  73. data/spec/support/test_app.rb +3 -3
  74. data/spec/unit/logger_spec.rb +135 -0
  75. data/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  76. data/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  77. data/spec/unit/models/message_encoders/json_spec.rb +135 -0
  78. data/spec/unit/models/message_encoders/utf8_spec.rb +100 -0
  79. data/spec/unit/models/message_spec.rb +16 -1
  80. data/spec/unit/models/paginated_resource_spec.rb +46 -0
  81. data/spec/unit/models/presence_message_spec.rb +18 -5
  82. data/spec/unit/models/token_spec.rb +1 -1
  83. data/spec/unit/modules/event_emitter_spec.rb +24 -10
  84. data/spec/unit/realtime/channel_spec.rb +3 -3
  85. data/spec/unit/realtime/channels_spec.rb +1 -1
  86. data/spec/unit/realtime/client_spec.rb +44 -2
  87. data/spec/unit/realtime/connection_spec.rb +2 -2
  88. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +4 -4
  89. data/spec/unit/realtime/presence_spec.rb +1 -1
  90. data/spec/unit/realtime/realtime_spec.rb +3 -3
  91. data/spec/unit/realtime/websocket_transport_spec.rb +24 -0
  92. data/spec/unit/rest/channels_spec.rb +1 -1
  93. data/spec/unit/rest/client_spec.rb +45 -10
  94. data/spec/unit/util/crypto_spec.rb +82 -0
  95. data/spec/unit/{modules → util}/pub_sub_spec.rb +13 -1
  96. metadata +43 -12
  97. data/spec/acceptance/crypto.rb +0 -63
@@ -1,12 +1,12 @@
1
- require "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
3
 
4
- describe "REST" do
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 "protocol" do
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 "invalid requests in middleware" do
75
- it "should raise an InvalidRequest exception with a valid message" do
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 "server error with JSON response", webmock: true do
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 "should raise a ServerError exception" do
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 "server error", webmock: true do
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 "should raise a ServerError exception" do
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 "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
3
 
4
- describe "REST" do
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 "publishing messages" do
14
- let(:channel) { client.channel("test") }
15
- let(:event) { "foo" }
16
- let(:message) { "woop!" }
13
+ describe 'publishing messages' do
14
+ let(:channel) { client.channel('test') }
15
+ let(:event) { 'foo' }
16
+ let(:message) { 'woop!' }
17
17
 
18
- it "should publish the message ok" do
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 "fetching channel history" do
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 => "test1", :data => "foo" },
28
- { :name => "test2", :data => "bar" },
29
- { :name => "test3", :data => "baz" }
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("Unable to publish message")
36
+ channel.publish(message[:name], message[:data]) || raise('Unable to publish message')
36
37
  end
37
38
  end
38
39
 
39
- it "should return all the history for the channel" do
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
- expect(actual_history).to include(Ably::Models::Message.new(message))
46
- expect(actual_history.map(&:hash)).to include(message)
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
- it "should return paged history" do
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 "options" do
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 ":{option}", webmock: true do
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 "spec_helper"
2
- require "securerandom"
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 "a channel" do
14
- it "should access a channel" do
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 "should allow options to be set on a channel" do
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 "using shortcut method on client" do
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 "using documented .get method on client.channels" do
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 "using undocumented [] method on client.channels" do
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 "spec_helper"
2
- require "securerandom"
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
3
 
4
- describe "REST" do
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: [:client_data])
15
+ IdiomaticRubyWrapper(fixture, stop_at: [:data])
16
16
  end
17
17
  end
18
18
 
19
- describe "fetching presence" do
20
- let(:channel) { client.channel("persisted:presence_fixtures") }
19
+ describe '#get presence' do
20
+ let(:channel) { client.channel('persisted:presence_fixtures') }
21
21
  let(:presence) { channel.presence.get }
22
22
 
23
- it "returns current members on the channel" do
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.client_data).to eq(fixture[:client_data])
28
+ expect(presence_message.data).to eq(fixture[:data])
29
29
  end
30
30
  end
31
31
  end
32
32
 
33
- describe "presence history" do
34
- let(:channel) { client.channel("persisted:presence_fixtures") }
35
- let(:history) { channel.presence.history }
33
+ describe 'presence #history' do
34
+ let(:channel) { client.channel('persisted:presence_fixtures') }
35
+ let(:presence_history) { channel.presence.history }
36
36
 
37
- it "returns recent presence activity" do
38
- expect(history.size).to eql(4)
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 = history.find { |client| client.client_id == fixture['clientId'] }
42
- expect(presence_message.client_data).to eq(fixture[:client_data])
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
- it "returns recent presence activity with options passsed to Ably" do
51
- expect(paged_history_forward).to be_a(Ably::Models::PaginatedResource)
52
- expect(paged_history_forward.size).to eql(2)
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
- next_page = paged_history_forward.next_page
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
- expect(paged_history_forward.first.id).to eql(history.last.id)
57
- expect(next_page.first.id).to eql(history[page_size].id)
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 "options" do
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