ably 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -2
  3. data/LICENSE +2 -2
  4. data/README.md +81 -20
  5. data/SPEC.md +235 -178
  6. data/lib/ably/auth.rb +1 -1
  7. data/lib/ably/exceptions.rb +10 -1
  8. data/lib/ably/models/cipher_params.rb +114 -0
  9. data/lib/ably/models/connection_details.rb +8 -6
  10. data/lib/ably/models/error_info.rb +3 -3
  11. data/lib/ably/models/idiomatic_ruby_wrapper.rb +27 -20
  12. data/lib/ably/models/message.rb +15 -15
  13. data/lib/ably/models/message_encoders/cipher.rb +8 -7
  14. data/lib/ably/models/presence_message.rb +17 -17
  15. data/lib/ably/models/protocol_message.rb +26 -19
  16. data/lib/ably/models/stats.rb +15 -15
  17. data/lib/ably/models/token_details.rb +14 -12
  18. data/lib/ably/models/token_request.rb +16 -14
  19. data/lib/ably/modules/async_wrapper.rb +1 -1
  20. data/lib/ably/modules/encodeable.rb +10 -10
  21. data/lib/ably/modules/model_common.rb +13 -5
  22. data/lib/ably/realtime/channel.rb +1 -2
  23. data/lib/ably/realtime/presence.rb +29 -58
  24. data/lib/ably/realtime/presence/members_map.rb +2 -2
  25. data/lib/ably/rest/channel.rb +1 -2
  26. data/lib/ably/rest/middleware/exceptions.rb +14 -4
  27. data/lib/ably/rest/presence.rb +3 -1
  28. data/lib/ably/util/crypto.rb +50 -40
  29. data/lib/ably/version.rb +1 -1
  30. data/spec/acceptance/realtime/message_spec.rb +20 -20
  31. data/spec/acceptance/realtime/presence_history_spec.rb +7 -7
  32. data/spec/acceptance/realtime/presence_spec.rb +65 -77
  33. data/spec/acceptance/rest/auth_spec.rb +8 -8
  34. data/spec/acceptance/rest/base_spec.rb +4 -4
  35. data/spec/acceptance/rest/channel_spec.rb +1 -1
  36. data/spec/acceptance/rest/client_spec.rb +1 -1
  37. data/spec/acceptance/rest/encoders_spec.rb +4 -4
  38. data/spec/acceptance/rest/message_spec.rb +15 -15
  39. data/spec/acceptance/rest/presence_spec.rb +4 -4
  40. data/spec/shared/model_behaviour.rb +7 -7
  41. data/spec/unit/models/cipher_params_spec.rb +140 -0
  42. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +15 -8
  43. data/spec/unit/models/message_encoders/cipher_spec.rb +28 -22
  44. data/spec/unit/models/message_encoders/json_spec.rb +24 -0
  45. data/spec/unit/models/protocol_message_spec.rb +3 -3
  46. data/spec/unit/util/crypto_spec.rb +50 -17
  47. metadata +5 -2
@@ -18,7 +18,7 @@ describe Ably::Auth do
18
18
  :client_id,
19
19
  :timestamp,
20
20
  :nonce
21
- ].map { |key| "#{token_request.hash[key]}\n" }.join("")
21
+ ].map { |key| "#{token_request.attributes[key]}\n" }.join("")
22
22
 
23
23
  encode64(
24
24
  OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
@@ -702,7 +702,7 @@ describe Ably::Auth do
702
702
 
703
703
  it 'calls the Proc once the token has expired and the new token is used' do
704
704
  client.stats
705
- expect(@block_called).to be_nil
705
+ expect(@block_called).to eql(0)
706
706
  sleep 3.5
707
707
  expect { client.stats }.to change { client.auth.current_token_details }
708
708
  expect(@block_called).to eql(1)
@@ -824,9 +824,9 @@ describe Ably::Auth do
824
824
  context 'with additional invalid attributes' do
825
825
  let(:token_params) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
826
826
  specify 'are ignored' do
827
- expect(subject.hash.keys).to_not include(:is_not_used_by_token_request)
828
- expect(subject.hash.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
829
- expect(subject.hash.keys).to include(:nonce)
827
+ expect(subject.attributes.keys).to_not include(:is_not_used_by_token_request)
828
+ expect(subject.attributes.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
829
+ expect(subject.attributes.keys).to include(:nonce)
830
830
  expect(subject.nonce).to eql('valid')
831
831
  end
832
832
  end
@@ -890,7 +890,7 @@ describe Ably::Auth do
890
890
  end
891
891
 
892
892
  it 'generates a valid HMAC' do
893
- hmac = hmac_for(Ably::Models::TokenRequest(token_request_attributes).hash, key_secret)
893
+ hmac = hmac_for(Ably::Models::TokenRequest(token_request_attributes).attributes, key_secret)
894
894
  expect(subject['mac']).to eql(hmac)
895
895
  end
896
896
  end
@@ -918,7 +918,7 @@ describe Ably::Auth do
918
918
 
919
919
  it 'disallows publishing on unspecified capability channels' do
920
920
  expect { token_auth_client.channel('bar').publish('event', 'data') }.to raise_error do |error|
921
- expect(error).to be_a(Ably::Exceptions::InvalidRequest)
921
+ expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest)
922
922
  expect(error.status).to eql(401)
923
923
  expect(error.code).to eql(40160)
924
924
  end
@@ -926,7 +926,7 @@ describe Ably::Auth do
926
926
 
927
927
  it 'fails if timestamp is invalid' do
928
928
  expect { auth.request_token(timestamp: Time.now - 180) }.to raise_error do |error|
929
- expect(error).to be_a(Ably::Exceptions::InvalidRequest)
929
+ expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest)
930
930
  expect(error.status).to eql(401)
931
931
  expect(error.code).to eql(40101)
932
932
  end
@@ -75,10 +75,10 @@ describe Ably::Rest do
75
75
  it 'should raise an InvalidRequest exception with a valid error message and code' do
76
76
  invalid_client = Ably::Rest::Client.new(key: 'appid.keyuid:keysecret', environment: environment)
77
77
  expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error|
78
- expect(error).to be_a(Ably::Exceptions::InvalidRequest)
79
- expect(error.message).to match(/invalid credentials/)
80
- expect(error.code).to eql(40100)
81
- expect(error.status).to eql(401)
78
+ expect(error).to be_a(Ably::Exceptions::ResourceMissing)
79
+ expect(error.message).to match(/No application found/)
80
+ expect(error.code).to eql(40400)
81
+ expect(error.status).to eql(404)
82
82
  end
83
83
  end
84
84
  end
@@ -87,7 +87,7 @@ describe Ably::Rest::Channel do
87
87
  let(:client_options) { default_options.merge(use_token_auth: true, token_params: { capability: capability }) }
88
88
 
89
89
  it 'raises a permission error when publishing' do
90
- expect { channel.publish(name, data) }.to raise_error(Ably::Exceptions::InvalidRequest, /not permitted/)
90
+ expect { channel.publish(name, data) }.to raise_error(Ably::Exceptions::UnauthorizedRequest, /not permitted/)
91
91
  end
92
92
  end
93
93
 
@@ -382,7 +382,7 @@ describe Ably::Rest::Client do
382
382
  end
383
383
 
384
384
  it 'does not attempt the fallback hosts as this is an authentication failure' do
385
- expect { publish_block.call }.to raise_error(Ably::Exceptions::InvalidRequest)
385
+ expect { publish_block.call }.to raise_error(Ably::Exceptions::UnauthorizedRequest)
386
386
  expect(default_host_request_stub).to have_been_requested
387
387
  expect(first_fallback_request_stub).to_not have_been_requested
388
388
  expect(second_fallback_request_stub).to_not have_been_requested
@@ -9,12 +9,12 @@ describe Ably::Models::MessageEncoders do
9
9
  let(:channel) { client.channel('test', channel_options) }
10
10
  let(:response) { instance_double('Faraday::Response', status: 201) }
11
11
 
12
- let(:cipher_params) { { key: random_str, algorithm: 'aes', mode: 'cbc', key_length: 128 } }
12
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
13
13
  let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
14
14
 
15
15
  let(:utf_8_data) { random_str.encode(Encoding::UTF_8) }
16
16
  let(:binary_data) { MessagePack.pack(random_str).encode(Encoding::ASCII_8BIT) }
17
- let(:json_data) { { 'key' => random_str } }
17
+ let(:json_data) { { 'some_id' => random_str } }
18
18
 
19
19
  after do
20
20
  channel.publish 'event', published_data
@@ -70,7 +70,7 @@ describe Ably::Models::MessageEncoders do
70
70
  end
71
71
 
72
72
  context 'with encryption' do
73
- let(:channel_options) { { encrypted: true, cipher_params: cipher_params } }
73
+ let(:channel_options) { { cipher: cipher_params } }
74
74
 
75
75
  context 'with UTF-8 data' do
76
76
  let(:published_data) { utf_8_data }
@@ -146,7 +146,7 @@ describe Ably::Models::MessageEncoders do
146
146
  end
147
147
 
148
148
  context 'with encryption' do
149
- let(:channel_options) { { encrypted: true, cipher_params: cipher_params } }
149
+ let(:channel_options) { { cipher: cipher_params } }
150
150
 
151
151
  context 'with UTF-8 data' do
152
152
  let(:published_data) { utf_8_data }
@@ -97,8 +97,8 @@ describe Ably::Rest::Channel, 'messages' do
97
97
 
98
98
  describe 'encryption and encoding' do
99
99
  let(:channel_name) { "persisted:#{random_str}" }
100
- let(:cipher_options) { { key: random_str(32) } }
101
- let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
100
+ let(:encrypted_channel) { client.channel(channel_name, cipher: cipher_options) }
101
+ let(:cipher_options) { { key: Ably::Util::Crypto.generate_random_key } }
102
102
 
103
103
  context 'with #publish and #history' do
104
104
  shared_examples 'an Ably encrypter and decrypter' do |item, data|
@@ -108,7 +108,7 @@ describe Ably::Rest::Channel, 'messages' do
108
108
  let(:secret_key) { Base64.decode64(data['key']) }
109
109
  let(:iv) { Base64.decode64(data['iv']) }
110
110
 
111
- let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
111
+ let(:cipher_options) { { key: secret_key, fixed_iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
112
112
 
113
113
  let(:encoded) { item['encoded'] }
114
114
  let(:encoded_data) { encoded['data'] }
@@ -200,7 +200,7 @@ describe Ably::Rest::Channel, 'messages' do
200
200
  context 'when retrieving #history with a different protocol' do
201
201
  let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
202
202
  let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) }
203
- let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
203
+ let(:other_client_channel) { other_client.channel(channel_name, cipher: cipher_options) }
204
204
 
205
205
  before do
206
206
  expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
@@ -221,7 +221,7 @@ describe Ably::Rest::Channel, 'messages' do
221
221
 
222
222
  context 'when publishing on an unencrypted channel and retrieving with #history on an encrypted channel' do
223
223
  let(:unencrypted_channel) { client.channel(channel_name) }
224
- let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
224
+ let(:other_client_encrypted_channel) { other_client.channel(channel_name, cipher: cipher_options) }
225
225
 
226
226
  let(:payload) { MessagePack.pack({ 'key' => random_str }) }
227
227
 
@@ -236,8 +236,8 @@ describe Ably::Rest::Channel, 'messages' do
236
236
 
237
237
  context 'when publishing on an encrypted channel and retrieving with #history on an unencrypted channel' do
238
238
  let(:client_options) { default_client_options.merge(log_level: :fatal) }
239
- let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
240
- let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
239
+ let(:cipher_options) { { key: Ably::Util::Crypto.generate_random_key(256), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
240
+ let(:encrypted_channel) { client.channel(channel_name, cipher: cipher_options) }
241
241
  let(:other_client_unencrypted_channel) { other_client.channel(channel_name) }
242
242
 
243
243
  let(:payload) { MessagePack.pack({ 'key' => random_str }) }
@@ -262,10 +262,10 @@ describe Ably::Rest::Channel, 'messages' do
262
262
 
263
263
  context 'publishing on an encrypted channel and retrieving #history with a different algorithm on another client' do
264
264
  let(:client_options) { default_client_options.merge(log_level: :fatal) }
265
- let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
266
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
267
- let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
268
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
265
+ let(:cipher_options_client1) { { key: Ably::Util::Crypto.generate_random_key(256), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
266
+ let(:encrypted_channel_client1) { client.channel(channel_name, cipher: cipher_options_client1) }
267
+ let(:cipher_options_client2) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
268
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, cipher: cipher_options_client2) }
269
269
 
270
270
  let(:payload) { MessagePack.pack({ 'key' => random_str }) }
271
271
 
@@ -289,10 +289,10 @@ describe Ably::Rest::Channel, 'messages' do
289
289
 
290
290
  context 'publishing on an encrypted channel and subscribing with a different key on another client' do
291
291
  let(:client_options) { default_client_options.merge(log_level: :fatal) }
292
- let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
293
- let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
294
- let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
295
- let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
292
+ let(:cipher_options_client1) { { key: Ably::Util::Crypto.generate_random_key(256), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
293
+ let(:encrypted_channel_client1) { client.channel(channel_name, cipher: cipher_options_client1) }
294
+ let(:cipher_options_client2) { { key: Ably::Util::Crypto.generate_random_key(256), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
295
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, cipher: cipher_options_client2) }
296
296
 
297
297
  let(:payload) { MessagePack.pack({ 'key' => random_str }) }
298
298
 
@@ -26,8 +26,8 @@ describe Ably::Rest::Presence do
26
26
  let(:secret_key) { Base64.decode64(cipher_details.fetch('key')) }
27
27
  let(:iv) { Base64.decode64(cipher_details.fetch('iv')) }
28
28
 
29
- let(:cipher_options) { { key: secret_key, algorithm: algorithm, mode: mode, key_length: key_length, iv: iv } }
30
- let(:fixtures_channel) { client.channel('persisted:presence_fixtures', encrypted: true, cipher_params: cipher_options, iv: iv) }
29
+ let(:cipher_options) { { key: secret_key, algorithm: algorithm, mode: mode, key_length: key_length, fixed_iv: iv } }
30
+ let(:fixtures_channel) { client.channel('persisted:presence_fixtures', cipher: cipher_options, fixed_iv: iv) }
31
31
 
32
32
  context 'tested against presence fixture data set up in test app' do
33
33
  before(:context) do
@@ -321,8 +321,8 @@ describe Ably::Rest::Presence do
321
321
 
322
322
  let(:data) { random_str(32) }
323
323
  let(:channel_name) { "persisted:#{random_str(4)}" }
324
- let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
325
- let(:presence) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options).presence }
324
+ let(:cipher_options) { { key: Ably::Util::Crypto.generate_random_key(256), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
325
+ let(:presence) { client.channel(channel_name, cipher: cipher_options).presence }
326
326
 
327
327
  let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
328
328
 
@@ -18,18 +18,18 @@ shared_examples 'a model' do |shared_options = {}|
18
18
  end
19
19
  end
20
20
 
21
- context '#hash', :api_private do
21
+ context '#attributes', :api_private do
22
22
  let(:model_options) { { action: 5 } }
23
23
 
24
- it 'provides access to #hash' do
25
- expect(model.hash).to eq(model_options)
24
+ it 'provides access to #attributes' do
25
+ expect(model.attributes).to eq(model_options)
26
26
  end
27
27
  end
28
28
 
29
29
  context '#[]', :api_private do
30
30
  let(:model_options) { { unusual: 'attribute' } }
31
31
 
32
- it 'provides accessor method to #hash' do
32
+ it 'provides accessor method to #attributes' do
33
33
  expect(model[:unusual]).to eql('attribute')
34
34
  end
35
35
  end
@@ -74,13 +74,13 @@ shared_examples 'a model' do |shared_options = {}|
74
74
  let(:model_options) { { channel: 'name' } }
75
75
 
76
76
  it 'prevents changes' do
77
- expect { model.hash[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
77
+ expect { model.attributes[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
78
78
  end
79
79
 
80
80
  it 'dups options' do
81
- expect(model.hash[:channel]).to eql('name')
81
+ expect(model.attributes[:channel]).to eql('name')
82
82
  model_options[:channel] = 'new'
83
- expect(model.hash[:channel]).to eql('name')
83
+ expect(model.attributes[:channel]).to eql('name')
84
84
  end
85
85
  end
86
86
  end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ describe Ably::Models::CipherParams do
5
+ context ':key missing from constructor' do
6
+ subject { Ably::Models::CipherParams.new }
7
+
8
+ it 'raises an exception' do
9
+ expect { subject }.to raise_error(/key.*required/)
10
+ end
11
+ end
12
+
13
+ describe '#key' do
14
+ context 'with :key in constructor' do
15
+ subject { Ably::Models::CipherParams.new(key: key) }
16
+
17
+ context 'as nil' do
18
+ let(:key) { nil }
19
+
20
+ it 'raises an exception' do
21
+ expect { subject }.to raise_error(/key.*required/)
22
+ end
23
+ end
24
+
25
+ context 'as a base64 encoded string' do
26
+ let(:binary_key) { Ably::Util::Crypto.generate_random_key }
27
+ let(:key) { Base64.encode64(binary_key) }
28
+
29
+ it 'is a binary representation of the base64 encoded string' do
30
+ expect(subject.key).to eql(binary_key)
31
+ expect(subject.key.encoding).to eql(Encoding::ASCII_8BIT)
32
+ end
33
+ end
34
+
35
+ context 'as a URL safe base64 encoded string' do
36
+ let(:base64_key) { "t+8lK21q7/44/YTpKTpHa6Icc/a08wIATyhxbVBb4RE=\n" }
37
+ let(:binary_key) { Base64.decode64(base64_key) }
38
+ let(:key) { base64_key.gsub('/', '_').gsub('+', '-') }
39
+
40
+ it 'is a binary representation of the URL safe base64 encoded string' do
41
+ expect(subject.key).to eql(binary_key)
42
+ end
43
+ end
44
+
45
+ context 'as a binary encoded string' do
46
+ let(:key) { Ably::Util::Crypto.generate_random_key }
47
+
48
+ it 'contains the binary string' do
49
+ expect(subject.key).to eql(key)
50
+ expect(subject.key.encoding).to eql(Encoding::ASCII_8BIT)
51
+ end
52
+ end
53
+
54
+ context 'with an incompatible :key_length constructor param' do
55
+ let(:key) { Ably::Util::Crypto.generate_random_key(256) }
56
+ subject { Ably::Models::CipherParams.new(key: key, key_length: 128) }
57
+
58
+ it 'raises an exception' do
59
+ expect { subject }.to raise_error(/Incompatible.*key.*length/)
60
+ end
61
+ end
62
+
63
+ context 'with an unsupported :key_length for aes-cbc encryption' do
64
+ let(:key) { "A" * 48 }
65
+ subject { Ably::Models::CipherParams.new(key: key, algorithm: 'aes', mode: 'cbc') }
66
+
67
+ it 'raises an exception' do
68
+ expect { subject }.to raise_error(/Unsupported key length/)
69
+ end
70
+ end
71
+
72
+ context 'with an invalid type' do
73
+ let(:key) { 111 }
74
+ subject { Ably::Models::CipherParams.new(key: key) }
75
+
76
+ it 'raises an exception' do
77
+ expect { subject }.to raise_error(/key param must/)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'with specified params in the constructor' do
84
+ let(:key) { Ably::Util::Crypto.generate_random_key(128) }
85
+ subject { Ably::Models::CipherParams.new(key: key, algorithm: 'aes', key_length: 128, mode: 'cbc') }
86
+
87
+ describe '#cipher_type' do
88
+ it 'contains the complete algorithm string as an upper case string' do
89
+ expect(subject.cipher_type).to eql ('AES-128-CBC')
90
+ end
91
+ end
92
+
93
+ describe '#mode' do
94
+ it 'contains the mode' do
95
+ expect(subject.mode).to eql ('cbc')
96
+ end
97
+ end
98
+
99
+ describe '#algorithm' do
100
+ it 'contains the algorithm' do
101
+ expect(subject.algorithm).to eql ('aes')
102
+ end
103
+ end
104
+
105
+ describe '#key_length' do
106
+ it 'contains the key_length' do
107
+ expect(subject.key_length).to eql(128)
108
+ end
109
+ end
110
+ end
111
+
112
+ context 'with combined param in the constructor' do
113
+ let(:key) { Ably::Util::Crypto.generate_random_key(128) }
114
+ subject { Ably::Models::CipherParams.new(key: key, combined: "FOO-128-BAR") }
115
+
116
+ describe '#cipher_type' do
117
+ it 'contains the complete algorithm string as an upper case string' do
118
+ expect(subject.cipher_type).to eql ('FOO-128-BAR')
119
+ end
120
+ end
121
+
122
+ describe '#mode' do
123
+ it 'contains the mode' do
124
+ expect(subject.mode).to eql ('bar')
125
+ end
126
+ end
127
+
128
+ describe '#algorithm' do
129
+ it 'contains the algorithm' do
130
+ expect(subject.algorithm).to eql ('foo')
131
+ end
132
+ end
133
+
134
+ describe '#key_length' do
135
+ it 'contains the key_length' do
136
+ expect(subject.key_length).to eql(128)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -57,8 +57,8 @@ describe Ably::Models::IdiomaticRubyWrapper, :api_private do
57
57
  expect { subject.no_key_exists_for_this }.to raise_error NoMethodError
58
58
  end
59
59
 
60
- specify '#hash returns raw Hash object' do
61
- expect(subject.hash).to eql(mixed_case_data)
60
+ specify '#attributes returns raw Hash object' do
61
+ expect(subject.attributes).to eql(mixed_case_data)
62
62
  end
63
63
 
64
64
  context 'recursively wrapping child objects' do
@@ -153,7 +153,7 @@ describe Ably::Models::IdiomaticRubyWrapper, :api_private do
153
153
  end
154
154
 
155
155
  it 'uses mixedCase' do
156
- expect(subject.hash['newKey']).to eql('new_value')
156
+ expect(subject.attributes['newKey']).to eql('new_value')
157
157
  expect(subject.new_key).to eql('new_value')
158
158
  end
159
159
  end
@@ -329,20 +329,27 @@ describe Ably::Models::IdiomaticRubyWrapper, :api_private do
329
329
  context '#dup' do
330
330
  let(:mixed_case_data) do
331
331
  {
332
- 'key' => 'value'
332
+ 'key_id' => 'value',
333
+ 'stop' => { client_id: "case won't change" }
333
334
  }.freeze
334
335
  end
335
336
  let(:dupe) { subject.dup }
337
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data, stop_at: [:stop]) }
336
338
 
337
339
  it 'returns a new object with the underlying JSON duped' do
338
- expect(subject.hash).to be_frozen
339
- expect(dupe.hash).to_not be_frozen
340
+ expect(subject.attributes).to be_frozen
341
+ expect(dupe.attributes).to_not be_frozen
340
342
  end
341
343
 
342
344
  it 'returns a new IdiomaticRubyWrapper with the same underlying Hash object' do
343
345
  expect(dupe).to be_a(Ably::Models::IdiomaticRubyWrapper)
344
- expect(dupe.hash).to be_a(Hash)
345
- expect(dupe.hash).to eql(mixed_case_data)
346
+ expect(dupe.attributes).to be_a(Hash)
347
+ expect(dupe.attributes).to eql(mixed_case_data)
348
+ end
349
+
350
+ it 'keeps the stop_at list intact' do
351
+ expect(dupe.stop_at.keys).to eql([:stop])
352
+ expect(dupe.attributes['stop']).to eql({ client_id: "case won't change" })
346
353
  end
347
354
  end
348
355
  end