ably 1.1.4.rc → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +41 -0
  3. data/CHANGELOG.md +85 -0
  4. data/COPYRIGHT +1 -0
  5. data/LICENSE +173 -10
  6. data/MAINTAINERS.md +1 -0
  7. data/README.md +24 -18
  8. data/SPEC.md +1020 -922
  9. data/ably.gemspec +13 -8
  10. data/lib/ably.rb +1 -0
  11. data/lib/ably/agent.rb +3 -0
  12. data/lib/ably/auth.rb +12 -2
  13. data/lib/ably/exceptions.rb +6 -0
  14. data/lib/ably/models/connection_details.rb +2 -0
  15. data/lib/ably/models/message.rb +14 -0
  16. data/lib/ably/models/presence_message.rb +14 -0
  17. data/lib/ably/models/protocol_message.rb +8 -0
  18. data/lib/ably/modules/ably.rb +11 -1
  19. data/lib/ably/realtime/channel.rb +7 -11
  20. data/lib/ably/realtime/channel/channel_manager.rb +3 -3
  21. data/lib/ably/realtime/channel/channel_properties.rb +24 -0
  22. data/lib/ably/realtime/channel/publisher.rb +5 -0
  23. data/lib/ably/realtime/client.rb +9 -0
  24. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  25. data/lib/ably/realtime/connection.rb +9 -5
  26. data/lib/ably/realtime/connection/websocket_transport.rb +67 -1
  27. data/lib/ably/realtime/presence.rb +0 -14
  28. data/lib/ably/rest/channel.rb +10 -3
  29. data/lib/ably/rest/client.rb +22 -21
  30. data/lib/ably/version.rb +1 -13
  31. data/spec/acceptance/realtime/auth_spec.rb +1 -1
  32. data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
  33. data/spec/acceptance/realtime/channel_spec.rb +24 -0
  34. data/spec/acceptance/realtime/client_spec.rb +72 -16
  35. data/spec/acceptance/realtime/connection_failures_spec.rb +29 -12
  36. data/spec/acceptance/realtime/connection_spec.rb +31 -33
  37. data/spec/acceptance/realtime/presence_history_spec.rb +3 -59
  38. data/spec/acceptance/realtime/presence_spec.rb +66 -157
  39. data/spec/acceptance/realtime/push_admin_spec.rb +3 -19
  40. data/spec/acceptance/rest/auth_spec.rb +6 -75
  41. data/spec/acceptance/rest/base_spec.rb +8 -4
  42. data/spec/acceptance/rest/channel_spec.rb +13 -0
  43. data/spec/acceptance/rest/client_spec.rb +144 -45
  44. data/spec/acceptance/rest/push_admin_spec.rb +3 -19
  45. data/spec/shared/client_initializer_behaviour.rb +131 -8
  46. data/spec/shared/model_behaviour.rb +1 -1
  47. data/spec/spec_helper.rb +12 -2
  48. data/spec/support/serialization_helper.rb +21 -0
  49. data/spec/unit/models/message_spec.rb +59 -0
  50. data/spec/unit/models/presence_message_spec.rb +49 -0
  51. data/spec/unit/models/protocol_message_spec.rb +48 -0
  52. data/spec/unit/realtime/channel_spec.rb +1 -1
  53. data/spec/unit/realtime/client_spec.rb +19 -6
  54. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  55. data/spec/unit/rest/channel_spec.rb +10 -0
  56. data/spec/unit/rest/client_spec.rb +20 -0
  57. metadata +52 -32
  58. data/.travis.yml +0 -18
@@ -89,31 +89,15 @@ describe Ably::Rest::Push::Admin do
89
89
  end
90
90
  end
91
91
 
92
- def request_body(request, protocol)
93
- if protocol == :msgpack
94
- MessagePack.unpack(request.body)
95
- else
96
- JSON.parse(request.body)
97
- end
98
- end
99
-
100
- def serialize(object, protocol)
101
- if protocol == :msgpack
102
- MessagePack.pack(object)
103
- else
104
- JSON.dump(object)
105
- end
106
- end
107
-
108
92
  let!(:publish_stub) do
109
93
  stub_request(:post, "#{client.endpoint}/push/publish").
110
94
  with do |request|
111
- expect(request_body(request, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
112
- expect(request_body(request, protocol)['recipient']).to_not have_key('camel_case')
95
+ expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
96
+ expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case')
113
97
  true
114
98
  end.to_return(
115
99
  :status => 201,
116
- :body => serialize({}, protocol),
100
+ :body => serialize_body({}, protocol),
117
101
  :headers => { 'Content-Type' => content_type }
118
102
  )
119
103
  end
@@ -69,14 +69,6 @@ shared_examples 'a client initializer' do
69
69
  expect { subject }.to raise_error(ArgumentError, /key and key_name or key_secret are mutually exclusive/)
70
70
  end
71
71
  end
72
-
73
- context 'client_id as only option' do
74
- let(:client_options) { { client_id: 'valid' } }
75
-
76
- it 'requires a valid key' do
77
- expect { subject }.to raise_error(ArgumentError, /client_id cannot be provided without a complete API key or means to authenticate/)
78
- end
79
- end
80
72
  end
81
73
 
82
74
  context 'with valid arguments' do
@@ -273,6 +265,137 @@ shared_examples 'a client initializer' do
273
265
  end
274
266
  end
275
267
  end
268
+
269
+ context 'environment' do
270
+ context 'when set without custom fallback hosts configured' do
271
+ let(:environment) { 'foo' }
272
+ let(:client_options) { default_options.merge(environment: environment) }
273
+ let(:default_fallbacks) { %w(a b c d e).map { |id| "#{environment}-#{id}-fallback.ably-realtime.com" } }
274
+
275
+ it 'sets the environment attribute' do
276
+ expect(subject.environment).to eql(environment)
277
+ end
278
+
279
+ it 'uses the default fallback hosts (#TBC, see https://github.com/ably/wiki/issues/361)' do
280
+ expect(subject.fallback_hosts.sort).to eql(default_fallbacks)
281
+ end
282
+ end
283
+
284
+ context 'when set with custom fallback hosts configured' do
285
+ let(:environment) { 'foo' }
286
+ let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
287
+ let(:client_options) { default_options.merge(environment: environment, fallback_hosts: custom_fallbacks) }
288
+
289
+ it 'sets the environment attribute' do
290
+ expect(subject.environment).to eql(environment)
291
+ end
292
+
293
+ it 'uses the custom provided fallback hosts (#RSC15a)' do
294
+ expect(subject.fallback_hosts.sort).to eql(custom_fallbacks)
295
+ end
296
+ end
297
+
298
+ context 'when set with fallback_hosts_use_default' do
299
+ let(:environment) { 'foo' }
300
+ let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
301
+ let(:default_production_fallbacks) { %w(a b c d e).map { |id| "#{id}.ably-realtime.com" } }
302
+ let(:client_options) { default_options.merge(environment: environment, fallback_hosts_use_default: true) }
303
+
304
+ it 'sets the environment attribute' do
305
+ expect(subject.environment).to eql(environment)
306
+ end
307
+
308
+ it 'uses the production default fallback hosts (#RTN17b)' do
309
+ expect(subject.fallback_hosts.sort).to eql(default_production_fallbacks)
310
+ end
311
+ end
312
+ end
313
+
314
+ context 'rest_host' do
315
+ context 'when set without custom fallback hosts configured' do
316
+ let(:custom_rest_host) { 'foo.com' }
317
+ let(:client_options) { default_options.merge(rest_host: custom_rest_host) }
318
+
319
+ it 'sets the custom_host attribute' do
320
+ expect(subject.custom_host).to eql(custom_rest_host)
321
+ end
322
+
323
+ it 'has no default fallback hosts' do
324
+ expect(subject.fallback_hosts).to be_empty
325
+ end
326
+ end
327
+
328
+ context 'when set with environment and without custom fallback hosts configured' do
329
+ let(:environment) { 'foobar' }
330
+ let(:custom_rest_host) { 'foo.com' }
331
+ let(:client_options) { default_options.merge(environment: environment, rest_host: custom_rest_host) }
332
+
333
+ it 'sets the environment attribute' do
334
+ expect(subject.environment).to eql(environment)
335
+ end
336
+
337
+ it 'sets the custom_host attribute' do
338
+ expect(subject.custom_host).to eql(custom_rest_host)
339
+ end
340
+
341
+ it 'has no default fallback hosts' do
342
+ expect(subject.fallback_hosts).to be_empty
343
+ end
344
+ end
345
+
346
+ context 'when set with custom fallback hosts configured' do
347
+ let(:custom_rest_host) { 'foo.com' }
348
+ let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } }
349
+ let(:client_options) { default_options.merge(rest_host: custom_rest_host, fallback_hosts: custom_fallbacks) }
350
+
351
+ it 'sets the custom_host attribute' do
352
+ expect(subject.custom_host).to eql(custom_rest_host)
353
+ end
354
+
355
+ it 'has no default fallback hosts' do
356
+ expect(subject.fallback_hosts.sort).to eql(custom_fallbacks)
357
+ end
358
+ end
359
+ end
360
+
361
+ context 'realtime_host' do
362
+ context 'when set without custom fallback hosts configured' do
363
+ let(:custom_realtime_host) { 'realtime.foo.com' }
364
+ let(:client_options) { default_options.merge(realtime_host: custom_realtime_host) }
365
+
366
+ # These tests are shared between realtime & rest clients
367
+ # So don't test for the attribute, instead test the options
368
+ it 'sets the realtime_host option' do
369
+ expect(subject.options[:realtime_host]).to eql(custom_realtime_host)
370
+ end
371
+
372
+ it 'has no default fallback hosts' do
373
+ expect(subject.fallback_hosts).to be_empty
374
+ end
375
+ end
376
+ end
377
+
378
+ context 'custom port' do
379
+ context 'when set without custom fallback hosts configured' do
380
+ let(:custom_port) { 555 }
381
+ let(:client_options) { default_options.merge(port: custom_port) }
382
+
383
+ it 'has no default fallback hosts' do
384
+ expect(subject.fallback_hosts).to be_empty
385
+ end
386
+ end
387
+ end
388
+
389
+ context 'custom TLS port' do
390
+ context 'when set without custom fallback hosts configured' do
391
+ let(:custom_port) { 555 }
392
+ let(:client_options) { default_options.merge(tls_port: custom_port) }
393
+
394
+ it 'has no default fallback hosts' do
395
+ expect(subject.fallback_hosts).to be_empty
396
+ end
397
+ end
398
+ end
276
399
  end
277
400
 
278
401
  context 'delegators' do
@@ -19,7 +19,7 @@ shared_examples 'a model' do |shared_options = {}|
19
19
  end
20
20
 
21
21
  context '#attributes', :api_private do
22
- let(:model_options) { { action: 5 } }
22
+ let(:model_options) { { action: 5, max_message_size: 65536, max_frame_size: 524288 } }
23
23
 
24
24
  it 'provides access to #attributes' do
25
25
  expect(model.attributes).to eq(model_options)
data/spec/spec_helper.rb CHANGED
@@ -5,8 +5,17 @@ def console(message)
5
5
  end
6
6
 
7
7
  unless RUBY_VERSION.match(/^1\./)
8
- require 'coveralls'
9
- Coveralls.wear!
8
+ require 'simplecov'
9
+
10
+ SimpleCov.start do
11
+ require 'simplecov-lcov'
12
+ SimpleCov::Formatter::LcovFormatter.config do |c|
13
+ c.report_with_single_file = true
14
+ c.single_report_path = 'coverage/lcov.info'
15
+ end
16
+ formatter SimpleCov::Formatter::LcovFormatter
17
+ add_filter %w[vendor spec]
18
+ end
10
19
  end
11
20
 
12
21
  require 'webmock/rspec'
@@ -19,6 +28,7 @@ require 'support/event_emitter_helper'
19
28
  require 'support/private_api_formatter'
20
29
  require 'support/protocol_helper'
21
30
  require 'support/random_helper'
31
+ require 'support/serialization_helper'
22
32
  require 'support/test_logger_helper'
23
33
 
24
34
  require 'rspec_config'
@@ -0,0 +1,21 @@
1
+ module SerializationHelper
2
+ def serialize_body(object, protocol)
3
+ if protocol == :msgpack
4
+ MessagePack.pack(object)
5
+ else
6
+ JSON.dump(object)
7
+ end
8
+ end
9
+
10
+ def deserialize_body(object, protocol)
11
+ if protocol == :msgpack
12
+ MessagePack.unpack(object)
13
+ else
14
+ JSON.parse(object)
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include self
20
+ end
21
+ end
@@ -211,6 +211,65 @@ describe Ably::Models::Message do
211
211
  end
212
212
  end
213
213
 
214
+ describe '#size' do
215
+ let(:model) { subject.new({ name: name, data: data, client_id: client_id, extras: extras }, protocol_message: protocol_message) }
216
+
217
+ context 'String (#TO3l8a)' do
218
+ let(:data) { 'example string data' }
219
+ let(:client_id) { '1' }
220
+ let(:name) { 'My Name' }
221
+ let(:extras) { 'extras' }
222
+
223
+ it 'should return 33 bytes' do
224
+ expect(model.size).to eq(33)
225
+ end
226
+ end
227
+
228
+ context 'Object (#TO3l8b)' do
229
+ let(:data) { Object.new }
230
+ let(:client_id) { String('10') }
231
+ let(:name) { 'John' }
232
+ let(:extras) { Hash.new }
233
+
234
+ it 'should return 38 bytes' do
235
+ expect(model.size).to eq(38)
236
+ end
237
+ end
238
+
239
+ context 'Array (#TO3l8b)' do
240
+ let(:data) { [1, 'two', :three] }
241
+ let(:client_id) { '2' }
242
+ let(:name) { 'Kate' }
243
+ let(:extras) { [] }
244
+
245
+ it 'should return 24 bytes' do
246
+ expect(model.size).to eq(24)
247
+ end
248
+ end
249
+
250
+ context 'extras (#TO3l8d)' do
251
+ let(:data) { { example: 'value', score: 1, hash: { test: true } } }
252
+ let(:client_id) { '3' }
253
+ let(:name) { 'John' }
254
+ let(:extras) { {} }
255
+
256
+ it 'should return 57 bytes' do
257
+ expect(model.size).to eq(57)
258
+ end
259
+ end
260
+
261
+ context 'nil (#TO3l8e)' do
262
+ let(:data) { nil }
263
+ let(:client_id) { '' }
264
+ let(:name) { '' }
265
+ let(:extras) { nil}
266
+
267
+ it 'should return 19 bytes' do
268
+ expect(model.size).to eq(0)
269
+ end
270
+ end
271
+ end
272
+
214
273
  context 'from REST request with embedded fields', :api_private do
215
274
  let(:id) { random_str }
216
275
  let(:protocol_message_id) { random_str }
@@ -220,6 +220,55 @@ describe Ably::Models::PresenceMessage do
220
220
  end
221
221
  end
222
222
 
223
+ describe '#size' do
224
+ let(:model) { subject.new({ action: 'enter', data: data, client_id: client_id }, protocol_message: protocol_message) }
225
+
226
+ context 'String (#TO3l8a)' do
227
+ let(:data) { 'example string data' }
228
+ let(:client_id) { '1' }
229
+
230
+ it 'should return 20 bytes' do
231
+ expect(model.size).to eq(20)
232
+ end
233
+ end
234
+
235
+ context 'Object (#TO3l8b)' do
236
+ let(:data) { Object.new }
237
+ let(:client_id) { '10' }
238
+
239
+ it 'should return 32 bytes' do
240
+ expect(model.size).to eq(32)
241
+ end
242
+ end
243
+
244
+ context 'Array (#TO3l8b)' do
245
+ let(:data) { [1, 'two', :three] }
246
+ let(:client_id) { '2' }
247
+
248
+ it 'should return 18 bytes' do
249
+ expect(model.size).to eq(18)
250
+ end
251
+ end
252
+
253
+ context 'extras (#TO3l8d)' do
254
+ let(:data) { { example: 'value', score: 1, hash: { test: true } } }
255
+ let(:client_id) { '3' }
256
+
257
+ it 'should return 51 bytes' do
258
+ expect(model.size).to eq(51)
259
+ end
260
+ end
261
+
262
+ context 'nil (#TO3l8e)' do
263
+ let(:data) { nil }
264
+ let(:client_id) { '4' }
265
+
266
+ it 'should return 1 bytes' do
267
+ expect(model.size).to eq(1)
268
+ end
269
+ end
270
+ end
271
+
223
272
  context 'from REST request with embedded fields', :api_private do
224
273
  let(:id) { random_str }
225
274
  let(:message_time) { Time.now + 60 }
@@ -318,6 +318,54 @@ describe Ably::Models::ProtocolMessage do
318
318
  end
319
319
  end
320
320
 
321
+ context '#message_size (#TO3l8)' do
322
+ context 'on presence' do
323
+ let(:protocol_message) do
324
+ new_protocol_message(presence: [{ action: 1, data: 'test342343', client_id: 'sdf' }])
325
+ end
326
+
327
+ it 'should return 13 bytes (sum in bytes: data and client_id)' do
328
+ expect(protocol_message.message_size).to eq(13)
329
+ end
330
+ end
331
+
332
+ context 'on message' do
333
+ let(:protocol_message) do
334
+ new_protocol_message(messages: [{ action: 1, unknown: 'test', data: 'test342343', client_id: 'sdf', name: 'sf23ewrew', extras: { time: Time.now, time_zone: 'UTC' } }])
335
+ end
336
+
337
+ it 'should return 76 bytes (sum in bytes: data, client_id, name, extras)' do
338
+ expect(protocol_message.message_size).to eq(76)
339
+ end
340
+ end
341
+ end
342
+
343
+ context '#has_correct_message_size? (#TO3l8)' do
344
+ context 'on presence' do
345
+ it 'should return true when a message has correct size' do
346
+ protocol_message = new_protocol_message(presence: [{ action: 1, data: 'x' * 100 }])
347
+ expect(protocol_message.has_correct_message_size?).to eq(true)
348
+ end
349
+
350
+ it 'should return false when a message has not correct size' do
351
+ protocol_message = new_protocol_message(presence: [{ action: 1, data: 'x' * 65537 }])
352
+ expect(protocol_message.has_correct_message_size?).to eq(false)
353
+ end
354
+ end
355
+
356
+ context 'on message' do
357
+ it 'should return true when a message has correct size' do
358
+ protocol_message = new_protocol_message(messages: [{ name: 'x' * 100 }])
359
+ expect(protocol_message.has_correct_message_size?).to eq(true)
360
+ end
361
+
362
+ it 'should return false when a message has not correct size' do
363
+ protocol_message = new_protocol_message(messages: [{ name: 'x' * 65537 }])
364
+ expect(protocol_message.has_correct_message_size?).to eq(false)
365
+ end
366
+ end
367
+ end
368
+
321
369
  context '#connection_details (#TR4o)' do
322
370
  let(:connection_details) { protocol_message.connection_details }
323
371
 
@@ -68,7 +68,7 @@ describe Ably::Realtime::Channel do
68
68
 
69
69
  describe '#publish name argument' do
70
70
  let(:encoded_value) { random_str.encode(encoding) }
71
- let(:message) { instance_double('Ably::Models::Message', client_id: nil) }
71
+ let(:message) { instance_double('Ably::Models::Message', client_id: nil, size: 0) }
72
72
 
73
73
  before do
74
74
  allow(subject).to receive(:create_message).and_return(message)
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  require 'shared/client_initializer_behaviour'
4
4
 
5
5
  describe Ably::Realtime::Client do
6
- subject do
6
+ subject(:realtime_client) do
7
7
  Ably::Realtime::Client.new(client_options)
8
8
  end
9
9
 
@@ -15,28 +15,41 @@ describe Ably::Realtime::Client do
15
15
  it 'passes on the options to the initializer' do
16
16
  rest_client = instance_double('Ably::Rest::Client', auth: instance_double('Ably::Auth'), options: client_options, environment: 'production', use_tls?: true, custom_tls_port: nil)
17
17
  expect(Ably::Rest::Client).to receive(:new).with(hash_including(client_options)).and_return(rest_client)
18
- subject
18
+ realtime_client
19
19
  end
20
20
 
21
21
  context 'for attribute' do
22
22
  [:environment, :use_tls?, :log_level, :custom_host].each do |attribute|
23
23
  specify "##{attribute}" do
24
- expect(subject.rest_client).to receive(attribute)
25
- subject.public_send attribute
24
+ expect(realtime_client.rest_client).to receive(attribute)
25
+ realtime_client.public_send attribute
26
26
  end
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
+ context 'when :transport_params option is passed' do
32
+ let(:expected_transport_params) do
33
+ { 'heartbeats' => 'true', 'v' => '1.0', 'extra_param' => 'extra_param' }
34
+ end
35
+ let(:client_options) do
36
+ { key: 'appid.keyuid:keysecret', transport_params: { heartbeats: true, v: 1.0, extra_param: 'extra_param'} }
37
+ end
38
+
39
+ it 'converts options to strings' do
40
+ expect(realtime_client.transport_params).to eq(expected_transport_params)
41
+ end
42
+ end
43
+
31
44
  context 'push' do
32
45
  let(:client_options) { { key: 'appid.keyuid:keysecret' } }
33
46
 
34
47
  specify '#device is not supported and raises an exception' do
35
- expect { subject.device }.to raise_error Ably::Exceptions::PushNotificationsNotSupported
48
+ expect { realtime_client.device }.to raise_error Ably::Exceptions::PushNotificationsNotSupported
36
49
  end
37
50
 
38
51
  specify '#push returns a Push object' do
39
- expect(subject.push).to be_a(Ably::Realtime::Push)
52
+ expect(realtime_client.push).to be_a(Ably::Realtime::Push)
40
53
  end
41
54
  end
42
55