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
@@ -0,0 +1,229 @@
1
+ require 'spec_helper'
2
+ require 'support/model_helper'
3
+
4
+ describe Ably::Models::Message do
5
+ include Ably::Modules::Conversions
6
+
7
+ subject { Ably::Models::Message }
8
+ let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
9
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
10
+
11
+ it_behaves_like 'a model', with_simple_attributes: %w(name client_id data) do
12
+ let(:model_args) { [protocol_message] }
13
+ end
14
+
15
+ context '#timestamp' do
16
+ let(:model) { subject.new({}, protocol_message) }
17
+ it 'retrieves attribute :timestamp from ProtocolMessage' do
18
+ expect(model.timestamp).to be_a(Time)
19
+ expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
20
+ end
21
+ end
22
+
23
+ context 'Java naming' do
24
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
25
+
26
+ it 'converts the attribute to ruby symbol naming convention' do
27
+ expect(model.client_id).to eql('joe')
28
+ end
29
+ end
30
+
31
+ context '#to_json' do
32
+ let(:json_object) { JSON.parse(model.to_json) }
33
+
34
+ context 'with valid data' do
35
+ let(:model) { subject.new({ name: 'test', clientId: 'joe' }, protocol_message) }
36
+
37
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
38
+ expect(json_object["clientId"]).to eql('joe')
39
+ end
40
+ end
41
+
42
+ context 'with invalid data' do
43
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
44
+
45
+ it 'raises an exception' do
46
+ expect { model.to_json }.to raise_error RuntimeError, /cannot generate a valid Hash/
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'from REST request with embedded fields' do
52
+ let(:id) { SecureRandom.hex }
53
+ let(:message_time) { Time.now + 60 }
54
+ let(:timestamp) { as_since_epoch(message_time) }
55
+ let(:model) { subject.new(id: id, timestamp: timestamp) }
56
+
57
+ context 'with protocol message' do
58
+ specify '#id prefers embedded ID' do
59
+ expect(model.id).to eql(id)
60
+ end
61
+
62
+ specify '#timestamp prefers embedded timestamp' do
63
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
64
+ end
65
+ end
66
+
67
+ context 'without protocol message' do
68
+ specify '#id uses embedded ID' do
69
+ expect(model.id).to eql(id)
70
+ end
71
+
72
+ specify '#timestamp uses embedded timestamp' do
73
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'part of ProtocolMessage' do
79
+ let(:ably_time) { Time.now + 5 }
80
+ let(:message_serial) { SecureRandom.random_number(1_000_000) }
81
+ let(:connection_id) { SecureRandom.hex }
82
+
83
+ let(:message_0_payload) do
84
+ {
85
+ 'string_key' => 'string_value',
86
+ 1 => 2,
87
+ true => false
88
+ }
89
+ end
90
+
91
+ let(:message_0_json) do
92
+ {
93
+ name: 'zero',
94
+ data: message_0_payload
95
+ }
96
+ end
97
+
98
+ let(:message_1_json) do
99
+ {
100
+ name: 'one',
101
+ data: 'simple string'
102
+ }
103
+ end
104
+
105
+ let(:protocol_message_id) { SecureRandom.hex }
106
+ let(:protocol_message) do
107
+ Ably::Models::ProtocolMessage.new({
108
+ action: :message,
109
+ timestamp: ably_time.to_i,
110
+ msg_serial: message_serial,
111
+ id: protocol_message_id,
112
+ messages: [
113
+ message_0_json, message_1_json
114
+ ]
115
+ })
116
+ end
117
+
118
+ let(:message_0) { protocol_message.messages.first }
119
+ let(:message_1) { protocol_message.messages.last }
120
+
121
+ it 'should generate a message ID from the index, serial and connection id' do
122
+ expect(message_0.id).to eql("#{protocol_message_id}:0")
123
+ expect(message_1.id).to eql("#{protocol_message_id}:1")
124
+ end
125
+
126
+ it 'should not modify the data payload' do
127
+ expect(message_0.data['string_key']).to eql('string_value')
128
+ expect(message_0.data[1]).to eql(2)
129
+ expect(message_0.data[true]).to eql(false)
130
+ expect(message_0.data).to eql(message_0_payload)
131
+
132
+ expect(message_1.data).to eql('simple string')
133
+ end
134
+
135
+ it 'should not allow changes to the payload' do
136
+ expect { message_0.data["test"] = true }.to raise_error RuntimeError, /can't modify frozen Hash/
137
+ end
138
+ end
139
+
140
+ context 'Message conversion method' do
141
+ let(:json) { { name: 'test', data: 'conversion' } }
142
+
143
+ context 'with JSON' do
144
+ context 'without ProtocolMessage' do
145
+ subject { Ably::Models.Message(json) }
146
+
147
+ it 'returns a Message object' do
148
+ expect(subject).to be_a(Ably::Models::Message)
149
+ end
150
+
151
+ it 'initializes with the JSON' do
152
+ expect(subject.name).to eql('test')
153
+ end
154
+
155
+ it 'raises an exception when accessing ProtocolMessage' do
156
+ expect { subject.protocol_message }.to raise_error RuntimeError
157
+ end
158
+
159
+ it 'has no ProtocolMessage' do
160
+ expect(subject.assigned_to_protocol_message?).to eql(false)
161
+ end
162
+ end
163
+
164
+ context 'with ProtocolMessage' do
165
+ subject { Ably::Models.Message(json, protocol_message) }
166
+
167
+ it 'returns a Message object' do
168
+ expect(subject).to be_a(Ably::Models::Message)
169
+ end
170
+
171
+ it 'initializes with the JSON' do
172
+ expect(subject.name).to eql('test')
173
+ end
174
+
175
+ it 'provides access to ProtocolMessage' do
176
+ expect(subject.protocol_message).to eql(protocol_message)
177
+ end
178
+
179
+ it 'has a ProtocolMessage' do
180
+ expect(subject.assigned_to_protocol_message?).to eql(true)
181
+ end
182
+ end
183
+ end
184
+
185
+ context 'with another Message' do
186
+ let(:message) { Ably::Models::Message.new(json) }
187
+
188
+ context 'without ProtocolMessage' do
189
+ subject { Ably::Models.Message(message) }
190
+
191
+ it 'returns a Message object' do
192
+ expect(subject).to be_a(Ably::Models::Message)
193
+ end
194
+
195
+ it 'initializes with the JSON' do
196
+ expect(subject.name).to eql('test')
197
+ end
198
+
199
+ it 'raises an exception when accessing ProtocolMessage' do
200
+ expect { subject.protocol_message }.to raise_error RuntimeError
201
+ end
202
+
203
+ it 'has no ProtocolMessage' do
204
+ expect(subject.assigned_to_protocol_message?).to eql(false)
205
+ end
206
+ end
207
+
208
+ context 'with ProtocolMessage' do
209
+ subject { Ably::Models.Message(message, protocol_message) }
210
+
211
+ it 'returns a Message object' do
212
+ expect(subject).to be_a(Ably::Models::Message)
213
+ end
214
+
215
+ it 'initializes with the JSON' do
216
+ expect(subject.name).to eql('test')
217
+ end
218
+
219
+ it 'provides access to ProtocolMessage' do
220
+ expect(subject.protocol_message).to eql(protocol_message)
221
+ end
222
+
223
+ it 'has a ProtocolMessage' do
224
+ expect(subject.assigned_to_protocol_message?).to eql(true)
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -1,11 +1,11 @@
1
1
  require 'spec_helper'
2
2
  require 'ostruct'
3
3
 
4
- describe Ably::Rest::Models::PagedResource do
5
- let(:paged_resource_class) { Ably::Rest::Models::PagedResource }
4
+ describe Ably::Models::PaginatedResource do
5
+ let(:paginated_resource_class) { Ably::Models::PaginatedResource }
6
6
  let(:headers) { Hash.new }
7
7
  let(:client) do
8
- double('client').tap do |client|
8
+ instance_double('Ably::Rest::Client').tap do |client|
9
9
  allow(client).to receive(:get).and_return(http_response)
10
10
  end
11
11
  end
@@ -16,15 +16,15 @@ describe Ably::Rest::Models::PagedResource do
16
16
  ]
17
17
  end
18
18
  let(:http_response) do
19
- double('http_response', {
19
+ instance_double('Faraday::Response', {
20
20
  body: body,
21
21
  headers: headers
22
22
  })
23
23
  end
24
24
  let(:base_url) { 'http://rest.ably.io/channels/channel_name' }
25
25
  let(:full_url) { "#{base_url}/whatever?param=exists" }
26
- let(:paged_resource_options) { Hash.new }
27
- let(:first_paged_request) { paged_resource_class.new(http_response, full_url, client, paged_resource_options) }
26
+ let(:paginated_resource_options) { Hash.new }
27
+ let(:first_paged_request) { paginated_resource_class.new(http_response, full_url, client, paginated_resource_options) }
28
28
  subject { first_paged_request }
29
29
 
30
30
  it 'returns correct length from body' do
@@ -50,8 +50,16 @@ describe Ably::Rest::Models::PagedResource do
50
50
  expect(subject[2]).to be_nil
51
51
  end
52
52
 
53
+ specify '#first gets the first item in page' do
54
+ expect(subject.first[:id]).to eql(body[0][:id])
55
+ end
56
+
57
+ specify '#last gets the last item in page' do
58
+ expect(subject.last[:id]).to eql(body[1][:id])
59
+ end
60
+
53
61
  context 'with coercion' do
54
- let(:paged_resource_options) { { coerce_into: 'OpenStruct' } }
62
+ let(:paginated_resource_options) { { coerce_into: 'OpenStruct' } }
55
63
 
56
64
  it 'returns coerced objects' do
57
65
  expect(subject.first).to be_a(OpenStruct)
@@ -130,8 +138,8 @@ describe Ably::Rest::Models::PagedResource do
130
138
  expect(client).to receive(:get).with("#{base_url}/history?index=1").and_return(next_http_response).once
131
139
  end
132
140
 
133
- it 'returns another PagedResource' do
134
- expect(subject).to be_a(paged_resource_class)
141
+ it 'returns another PaginatedResource' do
142
+ expect(subject).to be_a(paginated_resource_class)
135
143
  end
136
144
 
137
145
  it 'retrieves the next page of results' do
@@ -158,8 +166,8 @@ describe Ably::Rest::Models::PagedResource do
158
166
  end
159
167
  subject { first_paged_request.next_page.first_page }
160
168
 
161
- it 'returns a PagedResource' do
162
- expect(subject).to be_a(paged_resource_class)
169
+ it 'returns a PaginatedResource' do
170
+ expect(subject).to be_a(paginated_resource_class)
163
171
  end
164
172
 
165
173
  it 'retrieves the first page of results' do
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+ require 'support/model_helper'
3
+
4
+ describe Ably::Models::PresenceMessage do
5
+ include Ably::Modules::Conversions
6
+
7
+ subject { Ably::Models::PresenceMessage }
8
+ let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
9
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
10
+
11
+ it_behaves_like 'a model', with_simple_attributes: %w(client_id member_id client_data) do
12
+ let(:model_args) { [protocol_message] }
13
+ end
14
+
15
+ context '#timestamp' do
16
+ let(:model) { subject.new({}, protocol_message) }
17
+ it 'retrieves attribute :timestamp from ProtocolMessage' do
18
+ expect(model.timestamp).to be_a(Time)
19
+ expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
20
+ end
21
+ end
22
+
23
+ context 'Java naming' do
24
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
25
+
26
+ it 'converts the attribute to ruby symbol naming convention' do
27
+ expect(model.client_id).to eql('joe')
28
+ end
29
+ end
30
+
31
+ context 'with action' do
32
+ let(:model) { subject.new({ action: 0 }, protocol_message) }
33
+
34
+ it 'provides action as an Enum' do
35
+ expect(model.action).to eq(:enter)
36
+ end
37
+ end
38
+
39
+ context 'without action' do
40
+ let(:model) { subject.new({}, protocol_message) }
41
+
42
+ it 'raises an exception when accessed' do
43
+ expect { model.action }.to raise_error KeyError
44
+ end
45
+ end
46
+
47
+ context '#to_json' do
48
+ let(:json_object) { JSON.parse(model.to_json) }
49
+
50
+ context 'with valid data' do
51
+ let(:model) { subject.new({ action: 'enter', clientId: 'joe' }, protocol_message) }
52
+
53
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
54
+ expect(json_object["clientId"]).to eql('joe')
55
+ end
56
+ end
57
+
58
+ context 'with invalid data' do
59
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
60
+
61
+ it 'raises an exception' do
62
+ expect { model.to_json }.to raise_error KeyError, /cannot generate a valid Hash/
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'from REST request with embedded fields' do
68
+ let(:id) { SecureRandom.hex }
69
+ let(:message_time) { Time.now + 60 }
70
+ let(:timestamp) { as_since_epoch(message_time) }
71
+ let(:model) { subject.new(id: id, timestamp: timestamp) }
72
+
73
+ context 'with protocol message' do
74
+ specify '#id prefers embedded ID' do
75
+ expect(model.id).to eql(id)
76
+ end
77
+
78
+ specify '#timestamp prefers embedded timestamp' do
79
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
80
+ end
81
+ end
82
+
83
+ context 'without protocol message' do
84
+ specify '#id uses embedded ID' do
85
+ expect(model.id).to eql(id)
86
+ end
87
+
88
+ specify '#timestamp uses embedded timestamp' do
89
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'part of ProtocolMessage' do
95
+ let(:ably_time) { Time.now + 5 }
96
+ let(:message_serial) { SecureRandom.random_number(1_000_000) }
97
+ let(:connection_id) { SecureRandom.hex }
98
+
99
+ let(:presence_0_payload) { SecureRandom.hex(8) }
100
+ let(:presence_0_json) do
101
+ {
102
+ client_id: 'zero',
103
+ client_data: presence_0_payload
104
+ }
105
+ end
106
+ let(:presence_1_payload) { SecureRandom.hex(8) }
107
+ let(:presence_1_json) do
108
+ {
109
+ client_id: 'one',
110
+ client_data: presence_1_payload
111
+ }
112
+ end
113
+
114
+ let(:protocol_message_id) { SecureRandom.hex }
115
+ let(:protocol_message) do
116
+ Ably::Models::ProtocolMessage.new({
117
+ action: :message,
118
+ timestamp: ably_time.to_i,
119
+ msg_serial: message_serial,
120
+ id: protocol_message_id,
121
+ presence: [
122
+ presence_0_json, presence_1_json
123
+ ]
124
+ })
125
+ end
126
+
127
+ let(:presence_0) { protocol_message.presence.first }
128
+ let(:presence_1) { protocol_message.presence.last }
129
+
130
+ it 'should generate a message ID from the index, serial and connection id' do
131
+ expect(presence_0.id).to eql("#{protocol_message_id}:0")
132
+ expect(presence_1.id).to eql("#{protocol_message_id}:1")
133
+ end
134
+
135
+ it 'should not modify the data payload' do
136
+ expect(presence_0.client_data).to eql(presence_0_payload)
137
+ expect(presence_1.client_data).to eql(presence_1_payload)
138
+ end
139
+ end
140
+
141
+ context 'PresenceMessage conversion method' do
142
+ let(:json) { { client_id: 'test' } }
143
+
144
+ context 'with JSON' do
145
+ context 'without ProtocolMessage' do
146
+ subject { Ably::Models.PresenceMessage(json) }
147
+
148
+ it 'returns a PresenceMessage object' do
149
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
150
+ end
151
+
152
+ it 'initializes with the JSON' do
153
+ expect(subject.client_id).to eql('test')
154
+ end
155
+
156
+ it 'raises an exception when accessing ProtocolMessage' do
157
+ expect { subject.protocol_message }.to raise_error RuntimeError
158
+ end
159
+
160
+ it 'has no ProtocolMessage' do
161
+ expect(subject.assigned_to_protocol_message?).to eql(false)
162
+ end
163
+ end
164
+
165
+ context 'with ProtocolMessage' do
166
+ subject { Ably::Models.PresenceMessage(json, protocol_message) }
167
+
168
+ it 'returns a PresenceMessage object' do
169
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
170
+ end
171
+
172
+ it 'initializes with the JSON' do
173
+ expect(subject.client_id).to eql('test')
174
+ end
175
+
176
+ it 'provides access to ProtocolMessage' do
177
+ expect(subject.protocol_message).to eql(protocol_message)
178
+ end
179
+
180
+ it 'has a ProtocolMessage' do
181
+ expect(subject.assigned_to_protocol_message?).to eql(true)
182
+ end
183
+ end
184
+ end
185
+
186
+ context 'with another PresenceMessage' do
187
+ let(:message) { Ably::Models::PresenceMessage.new(json) }
188
+
189
+ context 'without ProtocolMessage' do
190
+ subject { Ably::Models.PresenceMessage(message) }
191
+
192
+ it 'returns a PresenceMessage object' do
193
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
194
+ end
195
+
196
+ it 'initializes with the JSON' do
197
+ expect(subject.client_id).to eql('test')
198
+ end
199
+
200
+ it 'raises an exception when accessing ProtocolMessage' do
201
+ expect { subject.protocol_message }.to raise_error RuntimeError
202
+ end
203
+
204
+ it 'has no ProtocolMessage' do
205
+ expect(subject.assigned_to_protocol_message?).to eql(false)
206
+ end
207
+ end
208
+
209
+ context 'with ProtocolMessage' do
210
+ subject { Ably::Models.PresenceMessage(message, protocol_message) }
211
+
212
+ it 'returns a PresenceMessage object' do
213
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
214
+ end
215
+
216
+ it 'initializes with the JSON' do
217
+ expect(subject.client_id).to eql('test')
218
+ end
219
+
220
+ it 'provides access to ProtocolMessage' do
221
+ expect(subject.protocol_message).to eql(protocol_message)
222
+ end
223
+
224
+ it 'has a ProtocolMessage' do
225
+ expect(subject.assigned_to_protocol_message?).to eql(true)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end