ably 0.8.8 → 0.8.9

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.
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