ably-rest 0.7.1 → 0.7.3

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 (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,386 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+
5
+ describe Ably::Models::PresenceMessage do
6
+ include Ably::Modules::Conversions
7
+
8
+ subject { Ably::Models::PresenceMessage }
9
+ let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
10
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
11
+
12
+ it_behaves_like 'a model', with_simple_attributes: %w(client_id data encoding) do
13
+ let(:model_args) { [protocol_message] }
14
+ end
15
+
16
+ context '#connection_id attribute' do
17
+ let(:protocol_connection_id) { random_str }
18
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new('connectionId' => protocol_connection_id, action: 1, timestamp: protocol_message_timestamp) }
19
+ let(:model_connection_id) { random_str }
20
+
21
+ context 'when this model has a connectionId attribute' do
22
+ context 'but no protocol message' do
23
+ let(:model) { subject.new('connectionId' => model_connection_id ) }
24
+
25
+ it 'uses the model value' do
26
+ expect(model.connection_id).to eql(model_connection_id)
27
+ end
28
+ end
29
+
30
+ context 'with a protocol message with a different connectionId' do
31
+ let(:model) { subject.new({ 'connectionId' => model_connection_id }, protocol_message) }
32
+
33
+ it 'uses the model value' do
34
+ expect(model.connection_id).to eql(model_connection_id)
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when this model has no connectionId attribute' do
40
+ context 'and no protocol message' do
41
+ let(:model) { subject.new({ }) }
42
+
43
+ it 'uses the model value' do
44
+ expect(model.connection_id).to be_nil
45
+ end
46
+ end
47
+
48
+ context 'with a protocol message with a connectionId' do
49
+ let(:model) { subject.new({ }, protocol_message) }
50
+
51
+ it 'uses the model value' do
52
+ expect(model.connection_id).to eql(protocol_connection_id)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ context '#member_key attribute' do
59
+ let(:model) { subject.new(client_id: 'client_id', connection_id: 'connection_id') }
60
+
61
+ it 'is string in format connection_id:client_id' do
62
+ expect(model.member_key).to eql('connection_id:client_id')
63
+ end
64
+
65
+ context 'with the same client id across multiple connections' do
66
+ let(:connection_1) { subject.new({ client_id: 'same', connection_id: 'unique' }, protocol_message) }
67
+ let(:connection_2) { subject.new({ client_id: 'same', connection_id: 'different' }, protocol_message) }
68
+
69
+ it 'is unique' do
70
+ expect(connection_1.member_key).to_not eql(connection_2.member_key)
71
+ end
72
+ end
73
+
74
+ context 'with a single connection and different client_ids' do
75
+ let(:client_1) { subject.new({ client_id: 'unique', connection_id: 'same' }, protocol_message) }
76
+ let(:client_2) { subject.new({ client_id: 'different', connection_id: 'same' }, protocol_message) }
77
+
78
+ it 'is unique' do
79
+ expect(client_1.member_key).to_not eql(client_2.member_key)
80
+ end
81
+ end
82
+ end
83
+
84
+ context '#timestamp' do
85
+ let(:model) { subject.new({}, protocol_message) }
86
+ it 'retrieves attribute :timestamp as a Time object from ProtocolMessage' do
87
+ expect(model.timestamp).to be_a(Time)
88
+ expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
89
+ end
90
+ end
91
+
92
+ context 'Java naming', :api_private do
93
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
94
+
95
+ it 'converts the attribute to ruby symbol naming convention' do
96
+ expect(model.client_id).to eql('joe')
97
+ end
98
+ end
99
+
100
+ context 'with action', :api_private do
101
+ context 'absent' do
102
+ let(:model) { subject.new({ action: 0 }, protocol_message) }
103
+
104
+ it 'provides action as an Enum' do
105
+ expect(model.action).to eq(:absent)
106
+ end
107
+ end
108
+
109
+ context 'enter' do
110
+ let(:model) { subject.new({ action: 2 }, protocol_message) }
111
+
112
+ it 'provides action as an Enum' do
113
+ expect(model.action).to eq(:enter)
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'without action', :api_private do
119
+ let(:model) { subject.new({}, protocol_message) }
120
+
121
+ it 'raises an exception when accessed' do
122
+ expect { model.action }.to raise_error KeyError
123
+ end
124
+ end
125
+
126
+ context 'initialized with' do
127
+ %w(client_id connection_id encoding).each do |attribute|
128
+ context ":#{attribute}" do
129
+ let(:encoded_value) { value.encode(encoding) }
130
+ let(:value) { random_str }
131
+ let(:options) { { attribute.to_sym => encoded_value } }
132
+ let(:model) { subject.new(options, protocol_message) }
133
+ let(:model_attribute) { model.public_send(attribute) }
134
+
135
+ context 'as UTF_8 string' do
136
+ let(:encoding) { Encoding::UTF_8 }
137
+
138
+ it 'is permitted' do
139
+ expect(model_attribute).to eql(encoded_value)
140
+ end
141
+
142
+ it 'remains as UTF-8' do
143
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
144
+ end
145
+ end
146
+
147
+ context 'as SHIFT_JIS string' do
148
+ let(:encoding) { Encoding::SHIFT_JIS }
149
+
150
+ it 'gets converted to UTF-8' do
151
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
152
+ end
153
+
154
+ it 'is compatible with original encoding' do
155
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
156
+ end
157
+ end
158
+
159
+ context 'as ASCII_8BIT string' do
160
+ let(:encoding) { Encoding::ASCII_8BIT }
161
+
162
+ it 'gets converted to UTF-8' do
163
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
164
+ end
165
+
166
+ it 'is compatible with original encoding' do
167
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
168
+ end
169
+ end
170
+
171
+ context 'as Integer' do
172
+ let(:encoded_value) { 1 }
173
+
174
+ it 'raises an argument error' do
175
+ expect { model_attribute }.to raise_error ArgumentError, /must be a String/
176
+ end
177
+ end
178
+
179
+ context 'as Nil' do
180
+ let(:encoded_value) { nil }
181
+
182
+ it 'is permitted' do
183
+ expect(model_attribute).to be_nil
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ context '#to_json', :api_private do
191
+ let(:json_object) { JSON.parse(model.to_json) }
192
+
193
+ context 'with valid data' do
194
+ let(:model) { subject.new({ action: 'enter', clientId: 'joe' }, protocol_message) }
195
+
196
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
197
+ expect(json_object["clientId"]).to eql('joe')
198
+ end
199
+ end
200
+
201
+ context 'with invalid data' do
202
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
203
+
204
+ it 'raises an exception' do
205
+ expect { model.to_json }.to raise_error KeyError, /cannot generate a valid Hash/
206
+ end
207
+ end
208
+
209
+ context 'with binary data' do
210
+ let(:data) { MessagePack.pack(random_str(32)) }
211
+ let(:model) { subject.new({ action: 'enter', data: data }, protocol_message) }
212
+
213
+ it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
214
+ expect(json_object["data"]).to eql(::Base64.encode64(data))
215
+ end
216
+
217
+ it 'adds Base64 encoding' do
218
+ expect(json_object["encoding"]).to eql('base64')
219
+ end
220
+ end
221
+ end
222
+
223
+ context 'from REST request with embedded fields', :api_private do
224
+ let(:id) { random_str }
225
+ let(:message_time) { Time.now + 60 }
226
+ let(:timestamp) { as_since_epoch(message_time) }
227
+ let(:model) { subject.new(id: id, timestamp: timestamp) }
228
+
229
+ context 'with protocol message' do
230
+ specify '#id prefers embedded ID' do
231
+ expect(model.id).to eql(id)
232
+ end
233
+
234
+ specify '#timestamp prefers embedded timestamp' do
235
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
236
+ end
237
+ end
238
+
239
+ context 'without protocol message' do
240
+ specify '#id uses embedded ID' do
241
+ expect(model.id).to eql(id)
242
+ end
243
+
244
+ specify '#timestamp uses embedded timestamp' do
245
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'part of ProtocolMessage', :api_private do
251
+ let(:ably_time) { Time.now + 5 }
252
+ let(:message_serial) { random_int_str(1_000_000) }
253
+ let(:connection_id) { random_str }
254
+
255
+ let(:presence_0_payload) { random_str(8) }
256
+ let(:presence_0_json) do
257
+ {
258
+ client_id: 'zero',
259
+ data: presence_0_payload
260
+ }
261
+ end
262
+ let(:presence_1_payload) { random_str(8) }
263
+ let(:presence_1_json) do
264
+ {
265
+ client_id: 'one',
266
+ data: presence_1_payload
267
+ }
268
+ end
269
+
270
+ let(:protocol_message_id) { random_str }
271
+ let(:protocol_message) do
272
+ Ably::Models::ProtocolMessage.new({
273
+ action: :message,
274
+ timestamp: ably_time.to_i,
275
+ msg_serial: message_serial,
276
+ id: protocol_message_id,
277
+ presence: [
278
+ presence_0_json, presence_1_json
279
+ ]
280
+ })
281
+ end
282
+
283
+ let(:presence_0) { protocol_message.presence.first }
284
+ let(:presence_1) { protocol_message.presence.last }
285
+
286
+ it 'should generate a message ID from the index, serial and connection id' do
287
+ expect(presence_0.id).to eql("#{protocol_message_id}:0")
288
+ expect(presence_1.id).to eql("#{protocol_message_id}:1")
289
+ end
290
+
291
+ it 'should not modify the data payload' do
292
+ expect(presence_0.data).to eql(presence_0_payload)
293
+ expect(presence_1.data).to eql(presence_1_payload)
294
+ end
295
+ end
296
+
297
+ context 'PresenceMessage conversion method', :api_private do
298
+ let(:json) { { client_id: 'test' } }
299
+
300
+ context 'with JSON' do
301
+ context 'without ProtocolMessage' do
302
+ subject { Ably::Models.PresenceMessage(json) }
303
+
304
+ it 'returns a PresenceMessage object' do
305
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
306
+ end
307
+
308
+ it 'initializes with the JSON' do
309
+ expect(subject.client_id).to eql('test')
310
+ end
311
+
312
+ it 'raises an exception when accessing ProtocolMessage' do
313
+ expect { subject.protocol_message }.to raise_error RuntimeError
314
+ end
315
+
316
+ it 'has no ProtocolMessage' do
317
+ expect(subject.assigned_to_protocol_message?).to eql(false)
318
+ end
319
+ end
320
+
321
+ context 'with ProtocolMessage' do
322
+ subject { Ably::Models.PresenceMessage(json, protocol_message) }
323
+
324
+ it 'returns a PresenceMessage object' do
325
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
326
+ end
327
+
328
+ it 'initializes with the JSON' do
329
+ expect(subject.client_id).to eql('test')
330
+ end
331
+
332
+ it 'provides access to ProtocolMessage' do
333
+ expect(subject.protocol_message).to eql(protocol_message)
334
+ end
335
+
336
+ it 'has a ProtocolMessage' do
337
+ expect(subject.assigned_to_protocol_message?).to eql(true)
338
+ end
339
+ end
340
+ end
341
+
342
+ context 'with another PresenceMessage' do
343
+ let(:message) { Ably::Models::PresenceMessage.new(json) }
344
+
345
+ context 'without ProtocolMessage' do
346
+ subject { Ably::Models.PresenceMessage(message) }
347
+
348
+ it 'returns a PresenceMessage object' do
349
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
350
+ end
351
+
352
+ it 'initializes with the JSON' do
353
+ expect(subject.client_id).to eql('test')
354
+ end
355
+
356
+ it 'raises an exception when accessing ProtocolMessage' do
357
+ expect { subject.protocol_message }.to raise_error RuntimeError
358
+ end
359
+
360
+ it 'has no ProtocolMessage' do
361
+ expect(subject.assigned_to_protocol_message?).to eql(false)
362
+ end
363
+ end
364
+
365
+ context 'with ProtocolMessage' do
366
+ subject { Ably::Models.PresenceMessage(message, protocol_message) }
367
+
368
+ it 'returns a PresenceMessage object' do
369
+ expect(subject).to be_a(Ably::Models::PresenceMessage)
370
+ end
371
+
372
+ it 'initializes with the JSON' do
373
+ expect(subject.client_id).to eql('test')
374
+ end
375
+
376
+ it 'provides access to ProtocolMessage' do
377
+ expect(subject.protocol_message).to eql(protocol_message)
378
+ end
379
+
380
+ it 'has a ProtocolMessage' do
381
+ expect(subject.assigned_to_protocol_message?).to eql(true)
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,315 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+
5
+ describe Ably::Models::ProtocolMessage do
6
+ include Ably::Modules::Conversions
7
+ subject { Ably::Models::ProtocolMessage }
8
+
9
+ def new_protocol_message(options)
10
+ subject.new({ action: 1 }.merge(options))
11
+ end
12
+
13
+ it_behaves_like 'a model',
14
+ with_simple_attributes: %w(id channel channel_serial connection_id),
15
+ base_model_options: { action: 1 } do
16
+
17
+ let(:model_args) { [] }
18
+ end
19
+
20
+ context 'initializer action coercion', :api_private do
21
+ it 'ignores actions that are Integers' do
22
+ protocol_message = subject.new(action: 14)
23
+ expect(protocol_message.hash[:action]).to eql(14)
24
+ end
25
+
26
+ it 'converts actions to Integers if a symbol' do
27
+ protocol_message = subject.new(action: :message)
28
+ expect(protocol_message.hash[:action]).to eql(15)
29
+ end
30
+
31
+ it 'converts actions to Integers if a ACTION' do
32
+ protocol_message = subject.new(action: Ably::Models::ProtocolMessage::ACTION.Message)
33
+ expect(protocol_message.hash[:action]).to eql(15)
34
+ end
35
+
36
+ it 'raises an argument error if nil' do
37
+ expect { subject.new({}) }.to raise_error(ArgumentError)
38
+ end
39
+ end
40
+
41
+ context 'attributes' do
42
+ let(:unique_value) { random_str }
43
+
44
+ context 'Java naming', :api_private do
45
+ let(:protocol_message) { new_protocol_message(channelSerial: unique_value) }
46
+
47
+ it 'converts the attribute to ruby symbol naming convention' do
48
+ expect(protocol_message.channel_serial).to eql(unique_value)
49
+ end
50
+ end
51
+
52
+ context '#action', :api_private do
53
+ let(:protocol_message) { new_protocol_message(action: 14) }
54
+
55
+ it 'returns an Enum that behaves like a symbol' do
56
+ expect(protocol_message.action).to eq(:presence)
57
+ end
58
+
59
+ it 'returns an Enum that behaves like a Numeric' do
60
+ expect(protocol_message.action).to eq(14)
61
+ end
62
+
63
+ it 'returns an Enum that behaves like a String' do
64
+ expect(protocol_message.action).to eq('Presence')
65
+ end
66
+
67
+ it 'returns an Enum that matchdes the ACTION constant' do
68
+ expect(protocol_message.action).to eql(Ably::Models::ProtocolMessage::ACTION.Presence)
69
+ end
70
+ end
71
+
72
+ context '#timestamp' do
73
+ let(:protocol_message) { new_protocol_message(timestamp: as_since_epoch(Time.now)) }
74
+ it 'retrieves attribute :timestamp as Time object' do
75
+ expect(protocol_message.timestamp).to be_a(Time)
76
+ expect(protocol_message.timestamp.to_i).to be_within(1).of(Time.now.to_i)
77
+ end
78
+ end
79
+
80
+ context '#count' do
81
+ context 'when missing' do
82
+ let(:protocol_message) { new_protocol_message({}) }
83
+ it 'is 1' do
84
+ expect(protocol_message.count).to eql(1)
85
+ end
86
+ end
87
+
88
+ context 'when non numeric' do
89
+ let(:protocol_message) { new_protocol_message(count: 'A') }
90
+ it 'is 1' do
91
+ expect(protocol_message.count).to eql(1)
92
+ end
93
+ end
94
+
95
+ context 'when greater than 1' do
96
+ let(:protocol_message) { new_protocol_message(count: '666') }
97
+ it 'is the value of count' do
98
+ expect(protocol_message.count).to eql(666)
99
+ end
100
+ end
101
+ end
102
+
103
+ context '#message_serial' do
104
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
105
+ it 'converts :msg_serial to an Integer' do
106
+ expect(protocol_message.message_serial).to be_a(Integer)
107
+ expect(protocol_message.message_serial).to eql(55)
108
+ end
109
+ end
110
+
111
+ context '#has_message_serial?' do
112
+ context 'without msg_serial' do
113
+ let(:protocol_message) { new_protocol_message({}) }
114
+
115
+ it 'returns false' do
116
+ expect(protocol_message.has_message_serial?).to eql(false)
117
+ end
118
+ end
119
+
120
+ context 'with msg_serial' do
121
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
122
+
123
+ it 'returns true' do
124
+ expect(protocol_message.has_message_serial?).to eql(true)
125
+ end
126
+ end
127
+ end
128
+
129
+ context '#connection_serial' do
130
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
131
+ it 'converts :connection_serial to an Integer' do
132
+ expect(protocol_message.connection_serial).to be_a(Integer)
133
+ expect(protocol_message.connection_serial).to eql(55)
134
+ end
135
+ end
136
+
137
+ context '#flags' do
138
+ context 'when nil' do
139
+ let(:protocol_message) { new_protocol_message({}) }
140
+
141
+ it 'is zero' do
142
+ expect(protocol_message.flags).to eql(0)
143
+ end
144
+ end
145
+
146
+ context 'when numeric' do
147
+ let(:protocol_message) { new_protocol_message(flags: '25') }
148
+
149
+ it 'is an Integer' do
150
+ expect(protocol_message.flags).to eql(25)
151
+ end
152
+ end
153
+
154
+ context 'when has_presence' do
155
+ let(:protocol_message) { new_protocol_message(flags: 1) }
156
+
157
+ it '#has_presence_flag? is true' do
158
+ expect(protocol_message.has_presence_flag?).to be_truthy
159
+ end
160
+ end
161
+
162
+ context 'when has another future flag' do
163
+ let(:protocol_message) { new_protocol_message(flags: 2) }
164
+
165
+ it '#has_presence_flag? is false' do
166
+ expect(protocol_message.has_presence_flag?).to be_falsey
167
+ end
168
+ end
169
+ end
170
+
171
+ context '#has_connection_serial?' do
172
+ context 'without connection_serial' do
173
+ let(:protocol_message) { new_protocol_message({}) }
174
+
175
+ it 'returns false' do
176
+ expect(protocol_message.has_connection_serial?).to eql(false)
177
+ end
178
+ end
179
+
180
+ context 'with connection_serial' do
181
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
182
+
183
+ it 'returns true' do
184
+ expect(protocol_message.has_connection_serial?).to eql(true)
185
+ end
186
+ end
187
+ end
188
+
189
+ context '#serial' do
190
+ context 'with underlying msg_serial' do
191
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
192
+ it 'converts :msg_serial to an Integer' do
193
+ expect(protocol_message.serial).to be_a(Integer)
194
+ expect(protocol_message.serial).to eql(55)
195
+ end
196
+ end
197
+
198
+ context 'with underlying connection_serial' do
199
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
200
+ it 'converts :connection_serial to an Integer' do
201
+ expect(protocol_message.serial).to be_a(Integer)
202
+ expect(protocol_message.serial).to eql(55)
203
+ end
204
+ end
205
+
206
+ context 'with underlying connection_serial and msg_serial' do
207
+ let(:protocol_message) { new_protocol_message(connection_serial: "99", msg_serial: "11") }
208
+ it 'prefers connection_serial and converts :connection_serial to an Integer' do
209
+ expect(protocol_message.serial).to be_a(Integer)
210
+ expect(protocol_message.serial).to eql(99)
211
+ end
212
+ end
213
+ end
214
+
215
+ context '#has_serial?' do
216
+ context 'without msg_serial or connection_serial' do
217
+ let(:protocol_message) { new_protocol_message({}) }
218
+
219
+ it 'returns false' do
220
+ expect(protocol_message.has_serial?).to eql(false)
221
+ end
222
+ end
223
+
224
+ context 'with msg_serial' do
225
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
226
+
227
+ it 'returns true' do
228
+ expect(protocol_message.has_serial?).to eql(true)
229
+ end
230
+ end
231
+
232
+ context 'with connection_serial' do
233
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
234
+
235
+ it 'returns true' do
236
+ expect(protocol_message.has_serial?).to eql(true)
237
+ end
238
+ end
239
+ end
240
+
241
+ context '#error' do
242
+ context 'with no error attribute' do
243
+ let(:protocol_message) { new_protocol_message(action: 1) }
244
+
245
+ it 'returns nil' do
246
+ expect(protocol_message.error).to be_nil
247
+ end
248
+ end
249
+
250
+ context 'with nil error' do
251
+ let(:protocol_message) { new_protocol_message(error: nil) }
252
+
253
+ it 'returns nil' do
254
+ expect(protocol_message.error).to be_nil
255
+ end
256
+ end
257
+
258
+ context 'with error' do
259
+ let(:protocol_message) { new_protocol_message(error: { message: 'test_error' }) }
260
+
261
+ it 'returns a valid ErrorInfo object' do
262
+ expect(protocol_message.error).to be_a(Ably::Models::ErrorInfo)
263
+ expect(protocol_message.error.message).to eql('test_error')
264
+ end
265
+ end
266
+ end
267
+ end
268
+
269
+ context '#to_json', :api_private do
270
+ let(:json_object) { JSON.parse(model.to_json) }
271
+ let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
272
+ let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached }
273
+ let(:message_action) { Ably::Models::ProtocolMessage::ACTION.Message }
274
+
275
+ context 'with valid data' do
276
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message] }) }
277
+
278
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
279
+ expect(json_object["channelSerial"]).to eql('unique')
280
+ end
281
+
282
+ it 'populates the messages' do
283
+ expect(json_object["messages"].first).to include(message)
284
+ end
285
+ end
286
+
287
+ context 'with missing msg_serial for ack message' do
288
+ let(:model) { new_protocol_message({ :action => message_action }) }
289
+
290
+ it 'it raises an exception' do
291
+ expect { model.to_json }.to raise_error TypeError, /msg_serial.*missing/
292
+ end
293
+ end
294
+
295
+ context 'is aliased by #to_s' do
296
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message], :timestamp => as_since_epoch(Time.now) }) }
297
+
298
+ specify do
299
+ expect(json_object).to eql(JSON.parse("#{model}"))
300
+ end
301
+ end
302
+ end
303
+
304
+ context '#to_msgpack', :api_private do
305
+ let(:model) { new_protocol_message({ :connectionSerial => 'unique', messages: [message] }) }
306
+ let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
307
+ let(:packed) { model.to_msgpack }
308
+ let(:unpacked) { MessagePack.unpack(packed) }
309
+
310
+ it 'returns a unpackable msgpack object' do
311
+ expect(unpacked['connectionSerial']).to eq('unique')
312
+ expect(unpacked['messages'][0]['name']).to eq('event')
313
+ end
314
+ end
315
+ end