ably 0.1.5 → 0.1.6

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -2,54 +2,55 @@ require "spec_helper"
2
2
  require "securerandom"
3
3
 
4
4
  describe "REST" do
5
- let(:client) do
6
- Ably::Rest::Client.new(api_key: api_key, environment: environment)
7
- end
8
-
9
- describe "fetching application stats" do
10
- def number_of_channels() 3 end
11
- def number_of_messages_per_channel() 5 end
5
+ [:json, :msgpack].each do |protocol|
6
+ context "over #{protocol}" do
7
+ describe "fetching application stats" do
8
+ before(:context) do
9
+ reload_test_app
10
+ end
12
11
 
13
- before(:context) do
14
- @context_client = Ably::Rest::Client.new(api_key: api_key, environment: environment)
12
+ def number_of_channels() 3 end
13
+ def number_of_messages_per_channel() 5 end
15
14
 
16
- # Wait until the start of the next minute according to the service
17
- # time because stats are created in 1 minute intervals
18
- service_time = @context_client.time
19
- @interval_start = (service_time.to_i / 60 + 1) * 60
20
- sleep_time = @interval_start - Time.now.to_i
15
+ before(:context) do
16
+ @context_client = Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
21
17
 
22
- if sleep_time > 30
23
- @interval_start -= 60 # there is enough time to generate the stats in this minute interval
24
- elsif sleep_time > 0
25
- sleep sleep_time
26
- end
18
+ # Wait until the start of the next minute according to the service
19
+ # time because stats are created in 1 minute intervals
20
+ service_time = @context_client.time
21
+ @interval_start = (service_time.to_i / 60 + 1) * 60
22
+ sleep_time = @interval_start - Time.now.to_i
27
23
 
28
- number_of_channels.times do |i|
29
- channel = @context_client.channel("stats-#{i}")
24
+ if sleep_time > 30
25
+ @interval_start -= 60 # there is enough time to generate the stats in this minute interval
26
+ elsif sleep_time > 0
27
+ sleep sleep_time
28
+ end
30
29
 
31
- number_of_messages_per_channel.times do |j|
32
- channel.publish("event-#{j}", "data-#{j}") || raise("Unable to publish message")
33
- end
34
- end
30
+ number_of_channels.times do |i|
31
+ channel = @context_client.channel("stats-#{i}")
35
32
 
36
- sleep(10)
37
- end
33
+ number_of_messages_per_channel.times do |j|
34
+ channel.publish("event-#{j}", "data-#{j}") || raise("Unable to publish message")
35
+ end
36
+ end
38
37
 
39
- [:minute, :hour, :day, :month].each do |interval|
40
- context "by #{interval}" do
41
- it "should return all the stats for the application" do
42
- stats = @context_client.stats(start: @interval_start * 1000, by: interval.to_s, direction: 'forwards')
38
+ sleep(10)
39
+ end
43
40
 
44
- expect(stats.size).to eql(1)
41
+ [:minute, :hour, :day, :month].each do |interval|
42
+ context "by #{interval}" do
43
+ it "should return all the stats for the application" do
44
+ stats = @context_client.stats(start: @interval_start * 1000, by: interval.to_s, direction: 'forwards')
45
45
 
46
- stat = stats.first
46
+ expect(stats.size).to eql(1)
47
47
 
48
- expect(stat[:inbound][:all][:all][:count]).to eql(number_of_channels * number_of_messages_per_channel)
49
- expect(stat[:inbound][:rest][:all][:count]).to eql(number_of_channels * number_of_messages_per_channel)
48
+ stat = stats.first
50
49
 
51
- # TODO: Review number of Channels opened issue for intervals other than minute
52
- expect(stat[:channels][:opened]).to eql(number_of_channels) if interval == :minute
50
+ expect(stat[:inbound][:all][:messages][:count]).to eql(number_of_channels * number_of_messages_per_channel)
51
+ expect(stat[:inbound][:rest][:messages][:count]).to eql(number_of_channels * number_of_messages_per_channel)
52
+ end
53
+ end
53
54
  end
54
55
  end
55
56
  end
@@ -2,13 +2,17 @@ require "spec_helper"
2
2
  require "securerandom"
3
3
 
4
4
  describe "REST" do
5
- let(:client) do
6
- Ably::Rest::Client.new(api_key: api_key, environment: environment)
7
- end
5
+ [:msgpack, :json].each do |protocol|
6
+ context "over #{protocol}" do
7
+ let(:client) do
8
+ Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
9
+ end
8
10
 
9
- describe "fetching the service time" do
10
- it "should return the service time as a Time object" do
11
- expect(client.time).to be_within(2).of(Time.now)
11
+ describe "fetching the service time" do
12
+ it "should return the service time as a Time object" do
13
+ expect(client.time).to be_within(2).of(Time.now)
14
+ end
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Ably::Modules::State do
3
+ describe Ably::Modules::StateEmitter do
4
4
  class ExampleStateWithEventEmitter
5
5
  include Ably::Modules::EventEmitter
6
6
  extend Ably::Modules::Enum
@@ -11,7 +11,7 @@ describe Ably::Modules::State do
11
11
  :connected,
12
12
  :disconnected
13
13
  )
14
- include Ably::Modules::State
14
+ include Ably::Modules::StateEmitter
15
15
 
16
16
  def initialize
17
17
  @state = :initializing
@@ -34,6 +34,20 @@ describe Ably::Modules::State do
34
34
  expect { subject.change_state :connecting }.to change { subject.state }.to(:connecting)
35
35
  end
36
36
 
37
+ context '#change_state with arguments' do
38
+ let(:args) { [5,3,1] }
39
+ let(:callback_status) { { called: false } }
40
+
41
+ it 'passes the arguments through to the triggered callback' do
42
+ subject.on(:connecting) do |*callback_args|
43
+ expect(callback_args).to eql(args)
44
+ callback_status[:called] = true
45
+ end
46
+ expect { subject.change_state :connecting, *args }.to change { subject.state }.to(:connecting)
47
+ expect(callback_status).to eql(called: true)
48
+ end
49
+ end
50
+
37
51
  context '#state?' do
38
52
  it 'returns true if state matches' do
39
53
  expect(subject.state?(initial_state)).to eql(true)
data/spec/spec_helper.rb CHANGED
@@ -16,6 +16,14 @@ RSpec.configure do |config|
16
16
  config.run_all_when_everything_filtered = true
17
17
  config.filter_run :focus
18
18
 
19
+ config.mock_with :rspec do |mocks|
20
+ # This option should be set when all dependencies are being loaded
21
+ # before a spec run, as is the case in a typical spec helper. It will
22
+ # cause any verifying double instantiation for a class that does not
23
+ # exist to raise, protecting against incorrectly spelt names.
24
+ mocks.verify_doubled_constant_names = true
25
+ end
26
+
19
27
  # Run specs in random order to surface order dependencies. If you find an
20
28
  # order dependency and want to debug it, you can fix the order by providing
21
29
  # the seed, which is printed after each run.
@@ -27,6 +35,12 @@ RSpec.configure do |config|
27
35
  end
28
36
 
29
37
  config.before(:example, :webmock => true) do
38
+ allow(TestApp).to receive(:instance).and_return(instance_double('TestApp',
39
+ app_id: 'app_id',
40
+ key_id: 'app_id.key_id',
41
+ api_key: 'app_id.key_id:secret',
42
+ environment: 'sandbox'
43
+ ))
30
44
  WebMock.enable!
31
45
  end
32
46
  end
@@ -25,6 +25,10 @@ module ApiHelper
25
25
  TestApp.instance.environment
26
26
  end
27
27
 
28
+ def reload_test_app
29
+ TestApp.reload
30
+ end
31
+
28
32
  def encode64(text)
29
33
  Base64.encode64(text).gsub("\n", '')
30
34
  end
@@ -1,7 +1,8 @@
1
1
  require 'securerandom'
2
2
 
3
- shared_examples 'a realtime model' do |shared_options = {}|
4
- let(:args) { ([model_options] + model_args) }
3
+ shared_examples 'a model' do |shared_options = {}|
4
+ let(:base_model_options) { shared_options.fetch(:base_model_options, {}) }
5
+ let(:args) { ([base_model_options.merge(model_options)] + model_args) }
5
6
  let(:model) { subject.new(*args) }
6
7
 
7
8
  context 'attributes' do
@@ -17,18 +18,18 @@ shared_examples 'a realtime model' do |shared_options = {}|
17
18
  end
18
19
  end
19
20
 
20
- context '#json' do
21
+ context '#hash' do
21
22
  let(:model_options) { { action: 5 } }
22
23
 
23
- it 'provides access to #json' do
24
- expect(model.json).to eq(model_options)
24
+ it 'provides access to #hash' do
25
+ expect(model.hash).to eq(model_options)
25
26
  end
26
27
  end
27
28
 
28
29
  context '#[]' do
29
30
  let(:model_options) { { unusual: 'attribute' } }
30
31
 
31
- it 'provides accessor method to #json' do
32
+ it 'provides accessor method to #hash' do
32
33
  expect(model[:unusual]).to eql('attribute')
33
34
  end
34
35
  end
@@ -51,17 +52,35 @@ shared_examples 'a realtime model' do |shared_options = {}|
51
52
  end
52
53
  end
53
54
 
55
+ context '#to_msgpack' do
56
+ let(:model_options) { { name: 'test', action: 0, channel_snake_case: 'unique' } }
57
+ let(:serialized) { model.to_msgpack }
58
+
59
+ it 'returns a msgpack object with Ably payload naming' do
60
+ expect(MessagePack.unpack(serialized)).to include('channelSnakeCase' => 'unique')
61
+ end
62
+ end
63
+
64
+ context '#to_json' do
65
+ let(:model_options) { { name: 'test', action: 0, channel_snake_case: 'unique' } }
66
+ let(:serialized) { model.to_json }
67
+
68
+ it 'returns a JSON string with Ably payload naming' do
69
+ expect(JSON.parse(serialized)).to include('channelSnakeCase' => 'unique')
70
+ end
71
+ end
72
+
54
73
  context 'is immutable' do
55
74
  let(:model_options) { { channel: 'name' } }
56
75
 
57
76
  it 'prevents changes' do
58
- expect { model.json[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen Hash/
77
+ expect { model.hash[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen Hash/
59
78
  end
60
79
 
61
80
  it 'dups options' do
62
- expect(model.json[:channel]).to eql('name')
81
+ expect(model.hash[:channel]).to eql('name')
63
82
  model_options[:channel] = 'new'
64
- expect(model.json[:channel]).to eql('name')
83
+ expect(model.hash[:channel]).to eql('name')
65
84
  end
66
85
  end
67
86
  end
@@ -1,6 +1,13 @@
1
1
  shared_examples 'a protocol message bus' do
2
2
  describe '__protocol_msgbus__ PubSub' do
3
- let(:message) { double(:message, name: 'name', channel: 'channel', messages: []) }
3
+ let(:message) do
4
+ Ably::Models::ProtocolMessage.new(
5
+ action: 15,
6
+ channel: 'channel',
7
+ msg_serial: 0,
8
+ messages: []
9
+ )
10
+ end
4
11
 
5
12
  specify 'supports valid ProtocolMessage messages' do
6
13
  received = 0
@@ -5,7 +5,7 @@ class TestApp
5
5
  'keys' => [
6
6
  {},
7
7
  {
8
- 'capability' => '{ "*":["subscribe"], "canpublish:*":["publish"], "canpublish:andpresence":["presence","publish"] }'
8
+ 'capability' => '{ "cansubscribe:*":["subscribe"], "canpublish:*":["publish"], "canpublish:andpresence":["presence","publish"] }'
9
9
  }
10
10
  ],
11
11
  'namespaces' => [
@@ -15,28 +15,25 @@ class TestApp
15
15
  {
16
16
  'name' => 'persisted:presence_fixtures',
17
17
  'presence' => [
18
- { 'clientId' => 'client_bool', 'clientData' => true },
19
- { 'clientId' => 'client_int', 'clientData' => 24 },
18
+ { 'clientId' => 'client_bool', 'clientData' => 'true' },
19
+ { 'clientId' => 'client_int', 'clientData' => '24' },
20
20
  { 'clientId' => 'client_string', 'clientData' => 'This is a string clientData payload' },
21
- { 'clientId' => 'client_json', 'clientData' => { "test" => 'This is a JSONObject clientData payload'} }
21
+ { 'clientId' => 'client_json', 'clientData' => '{ "test" => \'This is a JSONObject clientData payload\'}' }
22
22
  ]
23
23
  }
24
24
  ]
25
25
  }
26
26
 
27
+ # If an app has already been created and we need a new app, create a new test app
28
+ # This is sometimes needed when a test needs to be isolated from any other tests
29
+ def self.reload
30
+ instance.create_test_app if instance_variable_get('@singleton__instance__')
31
+ end
32
+
27
33
  include Singleton
28
34
 
29
35
  def initialize
30
- url = "#{sandbox_client.endpoint}/apps"
31
-
32
- headers = {
33
- "Accept" => "application/json",
34
- "Content-Type" => "application/json"
35
- }
36
-
37
- response = Faraday.post(url, APP_SPEC.to_json, headers)
38
-
39
- @attributes = JSON.parse(response.body)
36
+ create_test_app
40
37
  end
41
38
 
42
39
  def app_id
@@ -80,6 +77,19 @@ class TestApp
80
77
  'sandbox'
81
78
  end
82
79
 
80
+ def create_test_app
81
+ url = "#{sandbox_client.endpoint}/apps"
82
+
83
+ headers = {
84
+ "Accept" => "application/json",
85
+ "Content-Type" => "application/json"
86
+ }
87
+
88
+ response = Faraday.post(url, APP_SPEC.to_json, headers)
89
+
90
+ @attributes = JSON.parse(response.body)
91
+ end
92
+
83
93
  private
84
94
  def sandbox_client
85
95
  @sandbox_client ||= Ably::Rest::Client.new(api_key: 'app.key:secret', tls: true, environment: environment)
@@ -1,15 +1,15 @@
1
1
  require 'spec_helper'
2
2
  require 'support/model_helper'
3
3
 
4
- describe Ably::Realtime::Models::ErrorInfo do
5
- subject { Ably::Realtime::Models::ErrorInfo }
4
+ describe Ably::Models::ErrorInfo do
5
+ subject { Ably::Models::ErrorInfo }
6
6
 
7
- it_behaves_like 'a realtime model', with_simple_attributes: %w(code status_code message) do
7
+ it_behaves_like 'a model', with_simple_attributes: %w(code status_code message) do
8
8
  let(:model_args) { [] }
9
9
  end
10
10
 
11
11
  context '#status' do
12
- subject { Ably::Realtime::Models::ErrorInfo.new('statusCode' => 401) }
12
+ subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
13
13
  it 'is an alias for #status_code' do
14
14
  expect(subject.status).to eql(subject.status_code)
15
15
  expect(subject.status).to eql(401)
@@ -15,7 +15,7 @@ describe Ably::Models::IdiomaticRubyWrapper do
15
15
  {
16
16
  'mixedCaseChild' => 'exists'
17
17
  }
18
- ],
18
+ ]
19
19
  }
20
20
  end
21
21
  subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data) }
@@ -58,8 +58,8 @@ describe Ably::Models::IdiomaticRubyWrapper do
58
58
  expect { subject.no_key_exists_for_this }.to raise_error NoMethodError
59
59
  end
60
60
 
61
- specify '#json returns raw JSON object' do
62
- expect(subject.json).to eql(mixed_case_data)
61
+ specify '#hash returns raw Hash object' do
62
+ expect(subject.hash).to eql(mixed_case_data)
63
63
  end
64
64
 
65
65
  context 'recursively wrapping child objects' do
@@ -154,17 +154,29 @@ describe Ably::Models::IdiomaticRubyWrapper do
154
154
  end
155
155
 
156
156
  it 'uses mixedCase' do
157
- expect(subject.json['newKey']).to eql('new_value')
157
+ expect(subject.hash['newKey']).to eql('new_value')
158
158
  expect(subject.new_key).to eql('new_value')
159
159
  end
160
160
  end
161
161
  end
162
162
 
163
163
  context 'acts like a duck' do
164
+ let(:parsed_json) { JSON.parse(subject.to_json) }
164
165
  specify '#to_json returns JSON stringified' do
165
166
  expect(subject.to_json).to eql(mixed_case_data.to_json)
166
167
  end
167
168
 
169
+ specify '#to_json returns child JSON objects in the JSON stringified' do
170
+ expect(parsed_json['arrayObject']).to eql(mixed_case_data['arrayObject'])
171
+ end
172
+
173
+ context 'with snake case JSON' do
174
+ let(:subject) { Ably::Models::IdiomaticRubyWrapper.new('wrong_case' => 'will_be_corrected')}
175
+ specify '#to_json uses mixedCase for any non mixedCase keys' do
176
+ expect(parsed_json['wrongCase']).to eql('will_be_corrected')
177
+ end
178
+ end
179
+
168
180
  context '#to_json with changes' do
169
181
  before do
170
182
  @original_mixed_case_data = mixed_case_data.to_json
@@ -263,13 +275,38 @@ describe Ably::Models::IdiomaticRubyWrapper do
263
275
  context '#to_hash' do
264
276
  let(:mixed_case_data) do
265
277
  {
266
- 'key' => 'value'
278
+ 'key' => 'value',
279
+ 'childObject' => {
280
+ 'child' => true
281
+ }
267
282
  }
268
283
  end
269
284
 
270
285
  it 'returns a hash' do
271
286
  expect(subject.to_hash).to include(key: 'value')
272
287
  end
288
+
289
+ it 'converts hashes within hashes' do
290
+ expect(subject.to_hash[:child_object]).to include(child: true)
291
+ end
292
+ end
293
+
294
+ context '#to_msgpack' do
295
+ let(:mixed_case_data) do
296
+ {
297
+ 'key' => 'value',
298
+ 'child' => {
299
+ 'with_attributes' => true
300
+ }
301
+ }
302
+ end
303
+ let(:msg_packed) { subject.to_msgpack }
304
+ let(:unpacked) { MessagePack.unpack(msg_packed) }
305
+
306
+ it 'returns a msgpack object' do
307
+ expect(unpacked).to include('key' => 'value')
308
+ expect(unpacked).to include('child' => { 'withAttributes' => true })
309
+ end
273
310
  end
274
311
 
275
312
  context '#dup' do
@@ -281,14 +318,14 @@ describe Ably::Models::IdiomaticRubyWrapper do
281
318
  let(:dupe) { subject.dup }
282
319
 
283
320
  it 'returns a new object with the underlying JSON duped' do
284
- expect(subject.json).to be_frozen
285
- expect(dupe.json).to_not be_frozen
321
+ expect(subject.hash).to be_frozen
322
+ expect(dupe.hash).to_not be_frozen
286
323
  end
287
324
 
288
325
  it 'returns a new IdiomaticRubyWrapper with the same underlying Hash object' do
289
326
  expect(dupe).to be_a(Ably::Models::IdiomaticRubyWrapper)
290
- expect(dupe.json).to be_a(Hash)
291
- expect(dupe.json).to eql(mixed_case_data)
327
+ expect(dupe.hash).to be_a(Hash)
328
+ expect(dupe.hash).to eql(mixed_case_data)
292
329
  end
293
330
  end
294
331
  end