ably 1.1.4 → 1.1.8

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check.yml +15 -1
  3. data/CHANGELOG.md +109 -0
  4. data/COPYRIGHT +1 -1
  5. data/README.md +23 -9
  6. data/SPEC.md +289 -228
  7. data/ably.gemspec +14 -9
  8. data/lib/ably/agent.rb +3 -0
  9. data/lib/ably/exceptions.rb +6 -0
  10. data/lib/ably/models/connection_details.rb +8 -0
  11. data/lib/ably/models/delta_extras.rb +29 -0
  12. data/lib/ably/models/error_info.rb +6 -2
  13. data/lib/ably/models/message.rb +25 -0
  14. data/lib/ably/models/presence_message.rb +14 -0
  15. data/lib/ably/models/protocol_message.rb +13 -8
  16. data/lib/ably/modules/ably.rb +11 -1
  17. data/lib/ably/realtime/channel/channel_manager.rb +2 -2
  18. data/lib/ably/realtime/channel/channel_state_machine.rb +5 -1
  19. data/lib/ably/realtime/channel/publisher.rb +6 -0
  20. data/lib/ably/realtime/channel.rb +2 -0
  21. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
  22. data/lib/ably/realtime/client.rb +1 -0
  23. data/lib/ably/realtime/connection/connection_manager.rb +13 -4
  24. data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
  25. data/lib/ably/realtime/connection.rb +2 -2
  26. data/lib/ably/rest/channel.rb +11 -3
  27. data/lib/ably/rest/client.rb +37 -18
  28. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
  29. data/lib/ably/version.rb +1 -13
  30. data/lib/ably.rb +1 -0
  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 +220 -1
  34. data/spec/acceptance/realtime/connection_failures_spec.rb +85 -13
  35. data/spec/acceptance/realtime/connection_spec.rb +263 -32
  36. data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
  37. data/spec/acceptance/realtime/presence_spec.rb +31 -159
  38. data/spec/acceptance/rest/base_spec.rb +8 -4
  39. data/spec/acceptance/rest/channel_spec.rb +84 -9
  40. data/spec/acceptance/rest/channels_spec.rb +1 -1
  41. data/spec/acceptance/rest/client_spec.rb +72 -33
  42. data/spec/shared/client_initializer_behaviour.rb +131 -0
  43. data/spec/shared/model_behaviour.rb +1 -1
  44. data/spec/spec_helper.rb +11 -2
  45. data/spec/support/test_app.rb +1 -1
  46. data/spec/unit/models/delta_extras_spec.rb +14 -0
  47. data/spec/unit/models/error_info_spec.rb +17 -1
  48. data/spec/unit/models/message_spec.rb +83 -0
  49. data/spec/unit/models/presence_message_spec.rb +49 -0
  50. data/spec/unit/models/protocol_message_spec.rb +72 -20
  51. data/spec/unit/realtime/channel_spec.rb +3 -2
  52. data/spec/unit/realtime/channels_spec.rb +3 -3
  53. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
  54. data/spec/unit/rest/channel_spec.rb +44 -1
  55. data/spec/unit/rest/client_spec.rb +47 -0
  56. metadata +48 -36
@@ -5,11 +5,13 @@ describe Ably::Rest::Channel do
5
5
  include Ably::Modules::Conversions
6
6
 
7
7
  vary_by_protocol do
8
- let(:default_options) { { key: api_key, environment: environment, protocol: protocol} }
8
+ let(:default_options) { { key: api_key, environment: environment, protocol: protocol, max_frame_size: max_frame_size, max_message_size: max_message_size } }
9
9
  let(:client_options) { default_options }
10
10
  let(:client) do
11
11
  Ably::Rest::Client.new(client_options)
12
12
  end
13
+ let(:max_message_size) { nil }
14
+ let(:max_frame_size) { nil }
13
15
 
14
16
  describe '#publish' do
15
17
  let(:channel_name) { random_str }
@@ -60,6 +62,9 @@ describe Ably::Rest::Channel do
60
62
  end
61
63
 
62
64
  it 'publishes an array of messages in one HTTP request' do
65
+ expect(client.max_message_size).to eq(Ably::Rest::Client::MAX_MESSAGE_SIZE)
66
+ expect(messages.sum(&:size) < Ably::Rest::Client::MAX_MESSAGE_SIZE).to eq(true)
67
+
63
68
  expect(client).to receive(:post).once.and_call_original
64
69
  expect(channel.publish(messages)).to eql(true)
65
70
  expect(channel.history.items.map(&:name)).to match_array(messages.map { |message| message[:name] })
@@ -68,17 +73,78 @@ describe Ably::Rest::Channel do
68
73
  end
69
74
 
70
75
  context 'with an array of Message objects' do
71
- let(:messages) do
72
- 10.times.map do |index|
73
- Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
76
+ context 'when max_message_size and max_frame_size is not set' do
77
+ before do
78
+ expect(client.max_message_size).to eq(Ably::Rest::Client::MAX_MESSAGE_SIZE)
79
+ expect(client.max_frame_size).to eq(Ably::Rest::Client::MAX_FRAME_SIZE)
80
+ end
81
+
82
+ context 'and messages size (130 bytes) is smaller than the max_message_size' do
83
+ let(:messages) do
84
+ 10.times.map do |index|
85
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
86
+ end
87
+ end
88
+
89
+ it 'publishes an array of messages in one HTTP request' do
90
+ expect(messages.sum &:size).to eq(130)
91
+ expect(client).to receive(:post).once.and_call_original
92
+ expect(channel.publish(messages)).to eql(true)
93
+ expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
94
+ expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
95
+ end
96
+ end
97
+
98
+ context 'and messages size (177784 bytes) is bigger than the max_message_size' do
99
+ let(:messages) do
100
+ 10000.times.map do |index|
101
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 1 })
102
+ end
103
+ end
104
+
105
+ it 'should not publish and raise Ably::Exceptions::MaxMessageSizeExceeded' do
106
+ expect(messages.sum &:size).to eq(177784)
107
+ expect { channel.publish(messages) }.to raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
108
+ end
74
109
  end
75
110
  end
76
111
 
77
- it 'publishes an array of messages in one HTTP request' do
78
- expect(client).to receive(:post).once.and_call_original
79
- expect(channel.publish(messages)).to eql(true)
80
- expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
81
- expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
112
+ context 'when max_message_size is 655 bytes' do
113
+ let(:max_message_size) { 655 }
114
+
115
+ before do
116
+ expect(client.max_message_size).to eq(max_message_size)
117
+ expect(client.max_frame_size).to eq(Ably::Rest::Client::MAX_FRAME_SIZE)
118
+ end
119
+
120
+ context 'and messages size (130 bytes) is smaller than the max_message_size' do
121
+ let(:messages) do
122
+ 10.times.map do |index|
123
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 10 })
124
+ end
125
+ end
126
+
127
+ it 'publishes an array of messages in one HTTP request' do
128
+ expect(messages.sum &:size).to eq(130)
129
+ expect(client).to receive(:post).once.and_call_original
130
+ expect(channel.publish(messages)).to eql(true)
131
+ expect(channel.history.items.map(&:name)).to match_array(messages.map(&:name))
132
+ expect(channel.history.items.map(&:data)).to match_array(messages.map(&:data))
133
+ end
134
+ end
135
+
136
+ context 'and messages size (177784 bytes) is bigger than the max_message_size' do
137
+ let(:messages) do
138
+ 10000.times.map do |index|
139
+ Ably::Models::Message(name: index.to_s, data: { "index" => index + 1 })
140
+ end
141
+ end
142
+
143
+ it 'should not publish and raise Ably::Exceptions::MaxMessageSizeExceeded' do
144
+ expect(messages.sum &:size).to eq(177784)
145
+ expect { channel.publish(messages) }.to raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
146
+ end
147
+ end
82
148
  end
83
149
  end
84
150
 
@@ -350,6 +416,15 @@ describe Ably::Rest::Channel do
350
416
  end
351
417
  end
352
418
  end
419
+
420
+ context 'message size is exceeded (#TO3l8)' do
421
+ let(:data) { 101.times.map { { data: 'x' * 655 } } }
422
+
423
+ it 'should raise Ably::Exceptions::MaxMessageSizeExceeded exception' do
424
+ expect { channel.publish([ data: data ]) }.to \
425
+ raise_error(Ably::Exceptions::MaxMessageSizeExceeded)
426
+ end
427
+ end
353
428
  end
354
429
 
355
430
  describe '#history' do
@@ -36,7 +36,7 @@ describe Ably::Rest::Channels do
36
36
  let(:new_channel_options) { { encrypted: true } }
37
37
  let(:original_channel) { client.channels.get(channel_name, options) }
38
38
 
39
- it 'overrides the existing channel options and returns the channel object' do
39
+ it 'overrides the existing channel options and returns the channel object (RSN3c)' do
40
40
  expect(original_channel.options).to_not include(:encrypted)
41
41
  new_channel = client.channels.get(channel_name, new_channel_options)
42
42
  expect(new_channel).to be_a(Ably::Rest::Channel)
@@ -301,30 +301,44 @@ describe Ably::Rest::Client do
301
301
  context 'configured' do
302
302
  let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
303
303
 
304
- it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com (#RSC15a)' do
304
+ it 'should make connection attempts to a.ably-realtime.com, b.ably-realtime.com, c.ably-realtime.com, d.ably-realtime.com, e.ably-realtime.com (#RSC15a)' do
305
305
  hosts = []
306
306
  5.times do
307
307
  hosts << client.fallback_connection.host
308
308
  end
309
- expect(hosts).to match_array(%w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com))
309
+ expect(hosts).to match_array(%w(a.ably-realtime.com b.ably-realtime.com c.ably-realtime.com d.ably-realtime.com e.ably-realtime.com))
310
310
  end
311
311
  end
312
312
 
313
313
  context 'when environment is NOT production (#RSC15b)' do
314
- let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) }
315
- let!(:default_host_request_stub) do
316
- stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
317
- raise Faraday::TimeoutError.new('timeout error message')
314
+ context 'and custom fallback hosts are empty' do
315
+ let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key, fallback_hosts: []) }
316
+ let!(:default_host_request_stub) do
317
+ stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
318
+ raise Faraday::TimeoutError.new('timeout error message')
319
+ end
320
+ end
321
+
322
+ it 'does not retry failed requests with fallback hosts when there is a connection error' do
323
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
318
324
  end
319
325
  end
320
326
 
321
- it 'does not retry failed requests with fallback hosts when there is a connection error' do
322
- expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
327
+ context 'and no custom fallback hosts are provided' do
328
+ let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) }
329
+
330
+ it 'should make connection attempts to sandbox-a-fallback.ably-realtime.com, sandbox-b-fallback.ably-realtime.com, sandbox-c-fallback.ably-realtime.com, sandbox-d-fallback.ably-realtime.com, sandbox-e-fallback.ably-realtime.com (#RSC15a)' do
331
+ hosts = []
332
+ 5.times do
333
+ hosts << client.fallback_connection.host
334
+ end
335
+ expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox-#{id}-fallback.ably-realtime.com" })
336
+ end
323
337
  end
324
338
  end
325
339
 
326
340
  context 'when environment is production' do
327
- let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
341
+ let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) }
328
342
  let(:max_retry_count) { 2 }
329
343
  let(:max_retry_duration) { 0.5 }
330
344
  let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
@@ -823,11 +837,12 @@ describe Ably::Rest::Client do
823
837
  end
824
838
 
825
839
  context 'when environment is not production and server returns a 50x error' do
840
+ let(:env) { 'custom-env' }
841
+ let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}-#{id}-fallback.ably-realtime.com" } }
826
842
  let(:custom_hosts) { %w(A.foo.com B.foo.com) }
827
843
  let(:max_retry_count) { 2 }
828
844
  let(:max_retry_duration) { 0.5 }
829
845
  let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
830
- let(:env) { 'custom-env' }
831
846
  let(:production_options) do
832
847
  default_options.merge(
833
848
  environment: env,
@@ -851,6 +866,26 @@ describe Ably::Rest::Client do
851
866
  stub_request(:post, "https://#{env}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
852
867
  end
853
868
 
869
+ context 'with no fallback hosts provided (#TBC, see https://github.com/ably/wiki/issues/361)' do
870
+ let(:client_options) {
871
+ production_options.merge(log_level: :fatal)
872
+ }
873
+
874
+ it 'uses the default fallback hosts for that environment as this is not an authentication failure' do
875
+ fallbacks_called_count = 0
876
+ default_fallbacks.each do |host|
877
+ counting_fallback_proc = proc do
878
+ fallbacks_called_count += 1
879
+ fallback_block.call
880
+ end
881
+ stub_request(:post, "https://#{host}#{path}").to_return(&counting_fallback_proc)
882
+ end
883
+ expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
884
+ expect(default_host_request_stub).to have_been_requested
885
+ expect(fallbacks_called_count).to be >= 2
886
+ end
887
+ end
888
+
854
889
  context 'with custom fallback hosts provided (#RSC15b, #TO3k6)' do
855
890
  let!(:first_fallback_request_stub) do
856
891
  stub_request(:post, "https://#{custom_hosts[0]}#{path}").to_return(&fallback_block)
@@ -1046,29 +1081,15 @@ describe Ably::Rest::Client do
1046
1081
  end
1047
1082
 
1048
1083
  context 'version headers', :webmock do
1049
- [nil, 'foo'].each do |variant|
1050
- context "with variant #{variant ? variant : 'none'}" do
1051
- if variant
1052
- before do
1053
- Ably.lib_variant = variant
1054
- end
1055
-
1056
- after do
1057
- Ably.lib_variant = nil
1058
- end
1059
- end
1084
+ [nil, 'ably-ruby/1.1.1 ruby/1.9.3'].each do |agent|
1085
+ context "with #{agent ? "custom #{agent}" : 'default'} agent" do
1086
+ let(:client_options) { default_options.merge(key: api_key, agent: agent) }
1060
1087
 
1061
- let(:client_options) { default_options.merge(key: api_key) }
1062
1088
  let!(:publish_message_stub) do
1063
- lib = ['ruby']
1064
- lib << variant if variant
1065
- lib << Ably::VERSION
1066
-
1067
-
1068
1089
  stub_request(:post, "#{client.endpoint}/channels/foo/publish").
1069
1090
  with(headers: {
1070
1091
  'X-Ably-Version' => Ably::PROTOCOL_VERSION,
1071
- 'X-Ably-Lib' => lib.join('-')
1092
+ 'Ably-Agent' => agent || Ably::AGENT
1072
1093
  }).
1073
1094
  to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
1074
1095
  end
@@ -1082,7 +1103,7 @@ describe Ably::Rest::Client do
1082
1103
  end
1083
1104
  end
1084
1105
 
1085
- context '#request (#RSC19*)' do
1106
+ context '#request (#RSC19*, #TO3l9)' do
1086
1107
  let(:client_options) { default_options.merge(key: api_key) }
1087
1108
  let(:device_id) { random_str }
1088
1109
  let(:endpoint) { client.endpoint }
@@ -1137,6 +1158,12 @@ describe Ably::Rest::Client do
1137
1158
 
1138
1159
  expect(response).to be_success
1139
1160
  end
1161
+
1162
+ it 'raises an exception once body size in bytes exceeded' do
1163
+ expect {
1164
+ client.request(:post, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1165
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1166
+ end
1140
1167
  end
1141
1168
 
1142
1169
  context 'delete', :webmock do
@@ -1166,6 +1193,12 @@ describe Ably::Rest::Client do
1166
1193
 
1167
1194
  expect(response).to be_success
1168
1195
  end
1196
+
1197
+ it 'raises an exception once body size in bytes exceeded' do
1198
+ expect {
1199
+ client.request(:patch, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1200
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1201
+ end
1169
1202
  end
1170
1203
 
1171
1204
  context 'put', :webmock do
@@ -1189,14 +1222,20 @@ describe Ably::Rest::Client do
1189
1222
 
1190
1223
  expect(response).to be_success
1191
1224
  end
1225
+
1226
+ it 'raises an exception once body size in bytes exceeded' do
1227
+ expect {
1228
+ client.request(:put, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE })
1229
+ }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded)
1230
+ end
1192
1231
  end
1193
1232
  end
1194
1233
 
1195
1234
  context 'request_id generation' do
1196
1235
  context 'Timeout error' do
1197
- context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
1236
+ context 'with option add_request_ids: true and no fallback hosts', :webmock, :prevent_log_stubbing do
1198
1237
  let(:custom_logger_object) { TestLogger.new }
1199
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
1238
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true, fallback_hosts: []) }
1200
1239
 
1201
1240
  before do
1202
1241
  @request_id = nil
@@ -1286,8 +1325,8 @@ describe Ably::Rest::Client do
1286
1325
  end
1287
1326
  end
1288
1327
 
1289
- context 'without request_id' do
1290
- let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1328
+ context 'without request_id and no fallback hosts' do
1329
+ let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0, fallback_hosts: []) }
1291
1330
 
1292
1331
  it 'does not include request_id in ConnectionTimeout error' do
1293
1332
  begin
@@ -265,6 +265,137 @@ shared_examples 'a client initializer' do
265
265
  end
266
266
  end
267
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
268
399
  end
269
400
 
270
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'
@@ -59,7 +59,7 @@ class TestApp
59
59
 
60
60
  url = "#{sandbox_client.endpoint}/apps/#{app_id}"
61
61
 
62
- basic_auth = Base64.encode64(api_key).chomp
62
+ basic_auth = Base64.urlsafe_encode64(api_key).chomp
63
63
  headers = { "Authorization" => "Basic #{basic_auth}" }
64
64
 
65
65
  Faraday.delete(url, nil, headers)
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Models::DeltaExtras do
5
+ subject { described_class.new({ format: 'vcdiff', from: '1234-4567-8910-1001-1111'}) }
6
+
7
+ it 'should have `from` attribute' do
8
+ expect(subject.from).to eq('1234-4567-8910-1001-1111')
9
+ end
10
+
11
+ it 'should have `format` attribute' do
12
+ expect(subject.format).to eq('vcdiff')
13
+ end
14
+ end
@@ -5,7 +5,7 @@ describe Ably::Models::ErrorInfo do
5
5
  subject { Ably::Models::ErrorInfo }
6
6
 
7
7
  context '#TI1, #TI4' do
8
- it_behaves_like 'a model', with_simple_attributes: %w(code status_code href message) do
8
+ it_behaves_like 'a model', with_simple_attributes: %w(code status_code href message request_id cause) do
9
9
  let(:model_args) { [] }
10
10
  end
11
11
  end
@@ -18,6 +18,22 @@ describe Ably::Models::ErrorInfo do
18
18
  end
19
19
  end
20
20
 
21
+ context '#request_id #RSC7c' do
22
+ subject { Ably::Models::ErrorInfo.new('request_id' => '123-456-789-001') }
23
+
24
+ it 'should return request ID' do
25
+ expect(subject.request_id).to eql('123-456-789-001')
26
+ end
27
+ end
28
+
29
+ context '#cause #TI1' do
30
+ subject { Ably::Models::ErrorInfo.new('cause' => Ably::Models::ErrorInfo.new({})) }
31
+
32
+ it 'should return cause attribute' do
33
+ expect(subject.cause).to be_kind_of(Ably::Models::ErrorInfo)
34
+ end
35
+ end
36
+
21
37
  context 'log entries container help link #TI5' do
22
38
  context 'without an error code' do
23
39
  subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
@@ -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 }
@@ -547,4 +606,28 @@ describe Ably::Models::Message do
547
606
  end
548
607
  end
549
608
  end
609
+
610
+ context '#delta_extras (TM2i)' do
611
+ let(:delta_extras) { message.delta_extras }
612
+
613
+ context 'when delta' do
614
+ let(:message) { subject.new({ extras: { delta: { from: '1234-1234-5678-9009', format: 'vcdiff' } } }) }
615
+
616
+ it 'should return vcdiff format' do
617
+ expect(delta_extras.format).to eq('vcdiff')
618
+ end
619
+
620
+ it 'should return 1234-1234-5678-9009 message id' do
621
+ expect(delta_extras.from).to eq('1234-1234-5678-9009')
622
+ end
623
+ end
624
+
625
+ context 'when no delta' do
626
+ let(:message) { subject.new({ extras: {} }) }
627
+
628
+ it 'should return nil' do
629
+ expect(delta_extras).to eq(nil)
630
+ end
631
+ end
632
+ end
550
633
  end