ably 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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