ably 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
- require 'support/model_helper'
2
+ require 'shared/model_behaviour'
3
3
 
4
4
  describe Ably::Models::ErrorInfo do
5
5
  subject { Ably::Models::ErrorInfo }
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
- require 'securerandom'
3
2
 
4
- describe Ably::Models::IdiomaticRubyWrapper do
3
+ describe Ably::Models::IdiomaticRubyWrapper, :api_private do
5
4
  include Ably::Modules::Conversions
6
5
 
7
6
  let(:mixed_case_data) do
@@ -107,7 +106,7 @@ describe Ably::Models::IdiomaticRubyWrapper do
107
106
  'lowercasestring' => 'lowercasestringValue'
108
107
  }
109
108
  end
110
- let(:unique_value) { SecureRandom.hex }
109
+ let(:unique_value) { random_str }
111
110
 
112
111
  subject { Ably::Models::IdiomaticRubyWrapper.new(data) }
113
112
 
@@ -5,7 +5,7 @@ require 'msgpack'
5
5
  require 'ably/models/message_encoders/base64'
6
6
 
7
7
  describe Ably::Models::MessageEncoders::Base64 do
8
- let(:decoded_data) { SecureRandom.hex(32) }
8
+ let(:decoded_data) { random_str(32) }
9
9
  let(:base64_data) { Base64.encode64(decoded_data) }
10
10
  let(:binary_data) { MessagePack.pack(decoded_data) }
11
11
  let(:base64_binary_data) { Base64.encode64(binary_data) }
@@ -111,7 +111,7 @@ describe Ably::Models::MessageEncoders::Base64 do
111
111
  end
112
112
 
113
113
  context 'message with empty binary string payload' do
114
- let(:message) { { data: ''.force_encoding(Encoding::ASCII_8BIT), encoding: nil } }
114
+ let(:message) { { data: ''.encode(Encoding::ASCII_8BIT), encoding: nil } }
115
115
 
116
116
  it 'leaves the message data intact' do
117
117
  expect(message[:data]).to eql('')
@@ -3,11 +3,11 @@ require 'ably/models/message_encoders/cipher'
3
3
  require 'msgpack'
4
4
 
5
5
  describe Ably::Models::MessageEncoders::Cipher do
6
- let(:secret_key) { SecureRandom.hex(64) }
6
+ let(:secret_key) { random_str(64) }
7
7
  let(:crypto_options) { { key: secret_key, algorithm: 'AES', mode: 'CBC', key_length: 128 } }
8
8
  let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
9
9
 
10
- let(:decoded_data) { SecureRandom.hex(32) }
10
+ let(:decoded_data) { random_str(32) }
11
11
  let(:cipher_data) { crypto.encrypt(decoded_data) }
12
12
 
13
13
  let(:binary_data) { MessagePack.pack(decoded_data) }
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  require 'ably/models/message_encoders/utf8'
4
4
 
5
5
  describe Ably::Models::MessageEncoders::Utf8 do
6
- let(:string_ascii) { 'string'.force_encoding(Encoding::ASCII_8BIT) }
7
- let(:string_utf8) { 'string'.force_encoding(Encoding::UTF_8) }
6
+ let(:string_ascii) { 'string'.encode(Encoding::ASCII_8BIT) }
7
+ let(:string_utf8) { 'string'.encode(Encoding::UTF_8) }
8
8
 
9
9
  let(:client) { instance_double('Ably::Realtime::Client') }
10
10
 
@@ -53,48 +53,4 @@ describe Ably::Models::MessageEncoders::Utf8 do
53
53
  end
54
54
  end
55
55
  end
56
-
57
- context '#encode' do
58
- before do
59
- subject.encode message, {}
60
- end
61
-
62
- context 'message with json payload' do
63
- let(:message) { { data: string_ascii, encoding: 'json' } }
64
-
65
- it 'sets the cencoding' do
66
- expect(message[:data]).to eql(string_utf8)
67
- expect(message[:data].encoding).to eql(Encoding::UTF_8)
68
- end
69
-
70
- it 'adds the encoding' do
71
- expect(message[:encoding]).to eql('json/utf-8')
72
- end
73
- end
74
-
75
-
76
- context 'message with string payload and no encoding' do
77
- let(:message) { { data: string_ascii, encoding: nil } }
78
-
79
- it 'leaves the message data intact' do
80
- expect(message[:data]).to eql(string_ascii)
81
- end
82
-
83
- it 'leaves the encoding intact' do
84
- expect(message[:encoding]).to eql(nil)
85
- end
86
- end
87
-
88
- context 'message with string payload and UTF-8 encoding' do
89
- let(:message) { { data: string_ascii, encoding: 'utf-8' } }
90
-
91
- it 'leaves the message data intact' do
92
- expect(message[:data]).to eql(string_ascii)
93
- end
94
-
95
- it 'leaves the encoding intact' do
96
- expect(message[:encoding]).to eql('utf-8')
97
- end
98
- end
99
- end
100
56
  end
@@ -1,5 +1,6 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
- require 'support/model_helper'
3
+ require 'shared/model_behaviour'
3
4
  require 'base64'
4
5
  require 'msgpack'
5
6
 
@@ -16,13 +17,56 @@ describe Ably::Models::Message do
16
17
 
17
18
  context '#timestamp' do
18
19
  let(:model) { subject.new({}, protocol_message) }
19
- it 'retrieves attribute :timestamp from ProtocolMessage' do
20
+
21
+ it 'retrieves attribute :timestamp as Time object from ProtocolMessage' do
20
22
  expect(model.timestamp).to be_a(Time)
21
23
  expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
22
24
  end
23
25
  end
24
26
 
25
- context 'Java naming' do
27
+ context '#connection_id attribute' do
28
+ let(:protocol_connection_id) { random_str }
29
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new('connectionId' => protocol_connection_id, action: 1, timestamp: protocol_message_timestamp) }
30
+ let(:model_connection_id) { random_str }
31
+
32
+ context 'when this model has a connectionId attribute' do
33
+ context 'but no protocol message' do
34
+ let(:model) { subject.new('connectionId' => model_connection_id ) }
35
+
36
+ it 'uses the model value' do
37
+ expect(model.connection_id).to eql(model_connection_id)
38
+ end
39
+ end
40
+
41
+ context 'with a protocol message with a different connectionId' do
42
+ let(:model) { subject.new({ 'connectionId' => model_connection_id }, protocol_message) }
43
+
44
+ it 'uses the model value' do
45
+ expect(model.connection_id).to eql(model_connection_id)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when this model has no connectionId attribute' do
51
+ context 'and no protocol message' do
52
+ let(:model) { subject.new({ }) }
53
+
54
+ it 'uses the model value' do
55
+ expect(model.connection_id).to be_nil
56
+ end
57
+ end
58
+
59
+ context 'with a protocol message with a connectionId' do
60
+ let(:model) { subject.new({ }, protocol_message) }
61
+
62
+ it 'uses the model value' do
63
+ expect(model.connection_id).to eql(protocol_connection_id)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'Java naming', :api_private do
26
70
  let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
27
71
 
28
72
  it 'converts the attribute to ruby symbol naming convention' do
@@ -30,7 +74,71 @@ describe Ably::Models::Message do
30
74
  end
31
75
  end
32
76
 
33
- context '#to_json' do
77
+ context 'initialized with' do
78
+ %w(name client_id encoding).each do |attribute|
79
+ context ":#{attribute}" do
80
+ let(:encoded_value) { value.encode(encoding) }
81
+ let(:value) { random_str }
82
+ let(:options) { { attribute.to_sym => encoded_value } }
83
+ let(:model) { subject.new(options, protocol_message) }
84
+ let(:model_attribute) { model.public_send(attribute) }
85
+
86
+ context 'as UTF_8 string' do
87
+ let(:encoding) { Encoding::UTF_8 }
88
+
89
+ it 'is permitted' do
90
+ expect(model_attribute).to eql(encoded_value)
91
+ end
92
+
93
+ it 'remains as UTF-8' do
94
+ expect(model_attribute.encoding).to eql(encoding)
95
+ end
96
+ end
97
+
98
+ context 'as SHIFT_JIS string' do
99
+ let(:encoding) { Encoding::SHIFT_JIS }
100
+
101
+ it 'gets converted to UTF-8' do
102
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
103
+ end
104
+
105
+ it 'is compatible with original encoding' do
106
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
107
+ end
108
+ end
109
+
110
+ context 'as ASCII_8BIT string' do
111
+ let(:encoding) { Encoding::ASCII_8BIT }
112
+
113
+ it 'gets converted to UTF-8' do
114
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
115
+ end
116
+
117
+ it 'is compatible with original encoding' do
118
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
119
+ end
120
+ end
121
+
122
+ context 'as Integer' do
123
+ let(:encoded_value) { 1 }
124
+
125
+ it 'raises an argument error' do
126
+ expect { model_attribute }.to raise_error ArgumentError, /must be a String/
127
+ end
128
+ end
129
+
130
+ context 'as Nil' do
131
+ let(:encoded_value) { nil }
132
+
133
+ it 'is permitted' do
134
+ expect(model_attribute).to be_nil
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ context '#to_json', :api_private do
34
142
  let(:json_object) { JSON.parse(model.to_json) }
35
143
 
36
144
  context 'with valid data' do
@@ -50,7 +158,7 @@ describe Ably::Models::Message do
50
158
  end
51
159
 
52
160
  context 'with binary data' do
53
- let(:data) { MessagePack.pack(SecureRandom.hex(32)) }
161
+ let(:data) { MessagePack.pack(random_str(32)) }
54
162
  let(:model) { subject.new({ name: 'test', data: data }, protocol_message) }
55
163
 
56
164
  it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
@@ -63,13 +171,25 @@ describe Ably::Models::Message do
63
171
  end
64
172
  end
65
173
 
66
- context 'from REST request with embedded fields' do
67
- let(:id) { SecureRandom.hex }
68
- let(:message_time) { Time.now + 60 }
69
- let(:timestamp) { as_since_epoch(message_time) }
70
- let(:model) { subject.new(id: id, timestamp: timestamp) }
174
+ context 'from REST request with embedded fields', :api_private do
175
+ let(:id) { random_str }
176
+ let(:protocol_message_id) { random_str }
177
+ let(:message_time) { Time.now + 60 }
178
+ let(:message_timestamp) { as_since_epoch(message_time) }
179
+ let(:protocol_time) { Time.now }
180
+ let(:protocol_timestamp) { as_since_epoch(protocol_time) }
181
+
182
+ let(:protocol_message) do
183
+ Ably::Models::ProtocolMessage.new({
184
+ action: :message,
185
+ timestamp: protocol_timestamp,
186
+ id: protocol_message_id
187
+ })
188
+ end
71
189
 
72
190
  context 'with protocol message' do
191
+ let(:model) { subject.new({ id: id, timestamp: message_timestamp }, protocol_message) }
192
+
73
193
  specify '#id prefers embedded ID' do
74
194
  expect(model.id).to eql(id)
75
195
  end
@@ -80,6 +200,8 @@ describe Ably::Models::Message do
80
200
  end
81
201
 
82
202
  context 'without protocol message' do
203
+ let(:model) { subject.new(id: id, timestamp: message_timestamp) }
204
+
83
205
  specify '#id uses embedded ID' do
84
206
  expect(model.id).to eql(id)
85
207
  end
@@ -90,10 +212,10 @@ describe Ably::Models::Message do
90
212
  end
91
213
  end
92
214
 
93
- context 'part of ProtocolMessage' do
215
+ context 'part of ProtocolMessage', :api_private do
94
216
  let(:ably_time) { Time.now + 5 }
95
- let(:message_serial) { SecureRandom.random_number(1_000_000) }
96
- let(:connection_id) { SecureRandom.hex }
217
+ let(:message_serial) { random_int_str(1_000_000) }
218
+ let(:connection_id) { random_str }
97
219
 
98
220
  let(:message_0_payload) do
99
221
  {
@@ -117,7 +239,7 @@ describe Ably::Models::Message do
117
239
  }
118
240
  end
119
241
 
120
- let(:protocol_message_id) { SecureRandom.hex }
242
+ let(:protocol_message_id) { random_str }
121
243
  let(:protocol_message) do
122
244
  Ably::Models::ProtocolMessage.new({
123
245
  action: :message,
@@ -150,9 +272,32 @@ describe Ably::Models::Message do
150
272
  it 'should not allow changes to the payload' do
151
273
  expect { message_0.data["test"] = true }.to raise_error RuntimeError, /can't modify frozen Hash/
152
274
  end
275
+
276
+ context 'with identical message objects' do
277
+ let(:protocol_message) do
278
+ Ably::Models::ProtocolMessage.new({
279
+ action: :message,
280
+ timestamp: ably_time.to_i,
281
+ msg_serial: message_serial,
282
+ id: protocol_message_id,
283
+ messages: [
284
+ message_0_json, message_0_json, message_0_json
285
+ ]
286
+ })
287
+ end
288
+
289
+ it 'provide a unique ID:index' do
290
+ expect(protocol_message.messages.map(&:id).uniq.count).to eql(3)
291
+ end
292
+
293
+ it 'recognises the index based on the object ID as opposed to message payload' do
294
+ expect(protocol_message.messages.first.id).to match(/0$/)
295
+ expect(protocol_message.messages.last.id).to match(/2$/)
296
+ end
297
+ end
153
298
  end
154
299
 
155
- context 'Message conversion method' do
300
+ context 'Message conversion method', :api_private do
156
301
  let(:json) { { name: 'test', data: 'conversion' } }
157
302
 
158
303
  context 'with JSON' do
@@ -58,7 +58,7 @@ describe Ably::Models::PaginatedResource do
58
58
  expect(subject.last[:id]).to eql(body[1][:id])
59
59
  end
60
60
 
61
- context 'with coercion' do
61
+ context 'with coercion', :api_private do
62
62
  let(:paginated_resource_options) { { coerce_into: 'OpenStruct' } }
63
63
 
64
64
  it 'returns coerced objects' do
@@ -67,7 +67,7 @@ describe Ably::Models::PaginatedResource do
67
67
  end
68
68
  end
69
69
 
70
- context 'paged transformations' do
70
+ context 'paged transformations', :api_private do
71
71
  let(:headers) do
72
72
  {
73
73
  'link' => [
@@ -114,43 +114,45 @@ describe Ably::Models::PaginatedResource do
114
114
  end
115
115
  end
116
116
 
117
- context 'with option async_blocking_operations: true' do
118
- include RSpec::EventMachine
117
+ if defined?(EventMachine)
118
+ context 'with option async_blocking_operations: true' do
119
+ include RSpec::EventMachine
119
120
 
120
- subject do
121
- paginated_resource_class.new(http_response, full_url, paged_client, async_blocking_operations: true)
122
- end
123
-
124
- context '#next_page' do
125
- it 'returns a deferrable object' do
126
- run_reactor do
127
- expect(subject.next_page).to be_a(EventMachine::Deferrable)
128
- stop_reactor
129
- end
121
+ subject do
122
+ paginated_resource_class.new(http_response, full_url, paged_client, async_blocking_operations: true)
130
123
  end
131
124
 
132
- it 'allows a success callback block to be added' do
133
- run_reactor do
134
- subject.next_page do |paginated_resource|
135
- expect(paginated_resource).to be_a(Ably::Models::PaginatedResource)
125
+ context '#next_page' do
126
+ it 'returns a deferrable object' do
127
+ run_reactor do
128
+ expect(subject.next_page).to be_a(EventMachine::Deferrable)
136
129
  stop_reactor
137
130
  end
138
131
  end
139
- end
140
- end
141
132
 
142
- context '#first_page' do
143
- it 'calls the errback callback when first page headers are missing' do
144
- run_reactor do
145
- subject.next_page do |paginated_resource|
146
- deferrable = subject.first_page
147
- deferrable.errback do |error|
148
- expect(error).to be_a(Ably::Exceptions::InvalidPageError)
133
+ it 'allows a success callback block to be added' do
134
+ run_reactor do
135
+ subject.next_page do |paginated_resource|
136
+ expect(paginated_resource).to be_a(Ably::Models::PaginatedResource)
149
137
  stop_reactor
150
138
  end
151
139
  end
152
140
  end
153
141
  end
142
+
143
+ context '#first_page' do
144
+ it 'calls the errback callback when first page headers are missing' do
145
+ run_reactor do
146
+ subject.next_page do |paginated_resource|
147
+ deferrable = subject.first_page
148
+ deferrable.errback do |error|
149
+ expect(error).to be_a(Ably::Exceptions::InvalidPageError)
150
+ stop_reactor
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
154
156
  end
155
157
  end
156
158
  end
@@ -1,5 +1,6 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
- require 'support/model_helper'
3
+ require 'shared/model_behaviour'
3
4
 
4
5
  describe Ably::Models::PresenceMessage do
5
6
  include Ably::Modules::Conversions
@@ -8,19 +9,87 @@ describe Ably::Models::PresenceMessage do
8
9
  let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
9
10
  let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
10
11
 
11
- it_behaves_like 'a model', with_simple_attributes: %w(client_id member_id data encoding) do
12
+ it_behaves_like 'a model', with_simple_attributes: %w(client_id data encoding) do
12
13
  let(:model_args) { [protocol_message] }
13
14
  end
14
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
+
15
84
  context '#timestamp' do
16
85
  let(:model) { subject.new({}, protocol_message) }
17
- it 'retrieves attribute :timestamp from ProtocolMessage' do
86
+ it 'retrieves attribute :timestamp as a Time object from ProtocolMessage' do
18
87
  expect(model.timestamp).to be_a(Time)
19
88
  expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
20
89
  end
21
90
  end
22
91
 
23
- context 'Java naming' do
92
+ context 'Java naming', :api_private do
24
93
  let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
25
94
 
26
95
  it 'converts the attribute to ruby symbol naming convention' do
@@ -28,15 +97,25 @@ describe Ably::Models::PresenceMessage do
28
97
  end
29
98
  end
30
99
 
31
- context 'with action' do
32
- let(:model) { subject.new({ action: 0 }, protocol_message) }
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) }
33
111
 
34
- it 'provides action as an Enum' do
35
- expect(model.action).to eq(:enter)
112
+ it 'provides action as an Enum' do
113
+ expect(model.action).to eq(:enter)
114
+ end
36
115
  end
37
116
  end
38
117
 
39
- context 'without action' do
118
+ context 'without action', :api_private do
40
119
  let(:model) { subject.new({}, protocol_message) }
41
120
 
42
121
  it 'raises an exception when accessed' do
@@ -44,7 +123,71 @@ describe Ably::Models::PresenceMessage do
44
123
  end
45
124
  end
46
125
 
47
- context '#to_json' do
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
48
191
  let(:json_object) { JSON.parse(model.to_json) }
49
192
 
50
193
  context 'with valid data' do
@@ -64,7 +207,7 @@ describe Ably::Models::PresenceMessage do
64
207
  end
65
208
 
66
209
  context 'with binary data' do
67
- let(:data) { MessagePack.pack(SecureRandom.hex(32)) }
210
+ let(:data) { MessagePack.pack(random_str(32)) }
68
211
  let(:model) { subject.new({ action: 'enter', data: data }, protocol_message) }
69
212
 
70
213
  it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
@@ -77,8 +220,8 @@ describe Ably::Models::PresenceMessage do
77
220
  end
78
221
  end
79
222
 
80
- context 'from REST request with embedded fields' do
81
- let(:id) { SecureRandom.hex }
223
+ context 'from REST request with embedded fields', :api_private do
224
+ let(:id) { random_str }
82
225
  let(:message_time) { Time.now + 60 }
83
226
  let(:timestamp) { as_since_epoch(message_time) }
84
227
  let(:model) { subject.new(id: id, timestamp: timestamp) }
@@ -104,19 +247,19 @@ describe Ably::Models::PresenceMessage do
104
247
  end
105
248
  end
106
249
 
107
- context 'part of ProtocolMessage' do
250
+ context 'part of ProtocolMessage', :api_private do
108
251
  let(:ably_time) { Time.now + 5 }
109
- let(:message_serial) { SecureRandom.random_number(1_000_000) }
110
- let(:connection_id) { SecureRandom.hex }
252
+ let(:message_serial) { random_int_str(1_000_000) }
253
+ let(:connection_id) { random_str }
111
254
 
112
- let(:presence_0_payload) { SecureRandom.hex(8) }
255
+ let(:presence_0_payload) { random_str(8) }
113
256
  let(:presence_0_json) do
114
257
  {
115
258
  client_id: 'zero',
116
259
  data: presence_0_payload
117
260
  }
118
261
  end
119
- let(:presence_1_payload) { SecureRandom.hex(8) }
262
+ let(:presence_1_payload) { random_str(8) }
120
263
  let(:presence_1_json) do
121
264
  {
122
265
  client_id: 'one',
@@ -124,7 +267,7 @@ describe Ably::Models::PresenceMessage do
124
267
  }
125
268
  end
126
269
 
127
- let(:protocol_message_id) { SecureRandom.hex }
270
+ let(:protocol_message_id) { random_str }
128
271
  let(:protocol_message) do
129
272
  Ably::Models::ProtocolMessage.new({
130
273
  action: :message,
@@ -151,7 +294,7 @@ describe Ably::Models::PresenceMessage do
151
294
  end
152
295
  end
153
296
 
154
- context 'PresenceMessage conversion method' do
297
+ context 'PresenceMessage conversion method', :api_private do
155
298
  let(:json) { { client_id: 'test' } }
156
299
 
157
300
  context 'with JSON' do