ably 1.1.4 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/ably.gemspec CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency 'eventmachine', '~> 1.2.6'
22
22
  spec.add_runtime_dependency 'em-http-request', '~> 1.1'
23
23
  spec.add_runtime_dependency 'statesman', '~> 7.4'
24
- spec.add_runtime_dependency 'faraday', '>= 0.12', '< 2.0.0'
25
- spec.add_runtime_dependency 'excon', '~> 0.55'
24
+ spec.add_runtime_dependency 'faraday', '~> 1.0'
25
+ spec.add_runtime_dependency 'typhoeus', '~> 1.4'
26
26
 
27
27
  if RUBY_VERSION.match(/^1\./)
28
28
  spec.add_runtime_dependency 'json', '< 2.0'
@@ -6,8 +6,18 @@
6
6
  module Ably
7
7
  # Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
8
8
  # network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
9
+ # see https://docs.ably.io/client-lib-development-guide/features/#RSC15a
9
10
  #
10
- FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com).freeze
11
+ FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
12
+ FALLBACK_IDS = %w(a b c d e).freeze
13
+
14
+ # Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com
15
+ FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze
16
+
17
+ # Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com
18
+ CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
19
+ "-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze
20
+ end.freeze
11
21
 
12
22
  INTERNET_CHECK = {
13
23
  url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
@@ -74,6 +74,7 @@ module Ably
74
74
  def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
75
75
  def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
76
76
  def_delegators :@rest_client, :log_level
77
+ def_delegators :@rest_client, :options
77
78
 
78
79
  # Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
79
80
  #
@@ -3,6 +3,9 @@ require 'json'
3
3
  require 'logger'
4
4
  require 'uri'
5
5
 
6
+ require 'typhoeus'
7
+ require 'typhoeus/adapters/faraday'
8
+
6
9
  require 'ably/rest/middleware/exceptions'
7
10
 
8
11
  module Ably
@@ -181,16 +184,18 @@ module Ably
181
184
  @idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
182
185
 
183
186
 
184
- if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
185
- raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
187
+ if options[:fallback_hosts_use_default] && options[:fallback_hosts]
188
+ raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
186
189
  end
187
190
  @fallback_hosts = case
188
191
  when options.delete(:fallback_hosts_use_default)
189
192
  Ably::FALLBACK_HOSTS
190
193
  when options_fallback_hosts = options.delete(:fallback_hosts)
191
194
  options_fallback_hosts
192
- when environment || custom_host || options[:realtime_host] || custom_port || custom_tls_port
195
+ when custom_host || options[:realtime_host] || custom_port || custom_tls_port
193
196
  []
197
+ when environment
198
+ CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
194
199
  else
195
200
  Ably::FALLBACK_HOSTS
196
201
  end
@@ -202,6 +207,8 @@ module Ably
202
207
  @http_defaults = HTTP_DEFAULTS.dup
203
208
  options.each do |key, val|
204
209
  if http_key = key[/^http_(.+)/, 1]
210
+ # Typhoeus converts decimal durations to milliseconds, so 0.0001 timeout is treated as 0 (no timeout)
211
+ val = 0.001 if val.kind_of?(Numeric) && (val > 0) && (val < 0.001)
205
212
  @http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
206
213
  end
207
214
  end
@@ -665,7 +672,7 @@ module Ably
665
672
  }
666
673
  end
667
674
 
668
- # Return a Faraday middleware stack to initiate the Faraday::Connection with
675
+ # Return a Faraday middleware stack to initiate the Faraday::RackBuilder with
669
676
  #
670
677
  # @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
671
678
  def middleware
@@ -677,8 +684,8 @@ module Ably
677
684
 
678
685
  setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
679
686
 
680
- # Set Faraday's HTTP adapter
681
- builder.adapter :excon
687
+ # Set Faraday's HTTP adapter with support for HTTP/2
688
+ builder.adapter :typhoeus, http_version: :httpv2_0
682
689
  end
683
690
  end
684
691
 
@@ -7,7 +7,10 @@ module Ably
7
7
  class FailIfUnsupportedMimeType < Faraday::Response::Middleware
8
8
  def on_complete(env)
9
9
  unless env.response_headers['Ably-Middleware-Parsed'] == true
10
- unless (500..599).include?(env.status)
10
+ # Ignore empty body with success status code for no body response
11
+ return if env.body.to_s.empty? && env.status == 204
12
+
13
+ unless (500..599).include?(env.status)
11
14
  raise Ably::Exceptions::InvalidResponseBody,
12
15
  "Content Type #{env.response_headers['Content-Type']} is not supported by this client library"
13
16
  end
data/lib/ably/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '1.1.4'
2
+ VERSION = '1.1.5'
3
3
  PROTOCOL_VERSION = '1.1'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -156,10 +156,20 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
156
156
 
157
157
  stub_request(:get, auth_url).
158
158
  to_return do |request|
159
- sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout)
160
- { status: [500, "Internal Server Error"] }
161
- end.then.
162
- to_return(:status => 201, :body => token_response.to_json, :headers => { 'Content-Type' => 'application/json' })
159
+ sleep Ably::Rest::Client::HTTP_DEFAULTS.fetch(:request_timeout)
160
+ { status: [500, "Internal Server Error"] }
161
+ end.then.
162
+ to_return(:status => 201, :body => token_response.to_json, :headers => { 'Content-Type' => 'application/json' })
163
+
164
+ stub_request(:get, 'https://internet-up.ably-realtime.com/is-the-internet-up.txt')
165
+ .with(
166
+ headers: {
167
+ 'Accept-Encoding' => 'gzip, compressed',
168
+ 'Connection' => 'close',
169
+ 'Host' => 'internet-up.ably-realtime.com',
170
+ 'User-Agent' => 'EventMachine HttpClient'
171
+ }
172
+ ).to_return(status: 200, body: 'yes\n', headers: { 'Content-Type' => 'text/plain' })
163
173
  end
164
174
 
165
175
  specify 'the connection moves to the disconnected state and tries again, returning again to the disconnected state (#RSA4c, #RSA4c1, #RSA4c2)' do
@@ -1423,14 +1433,19 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1423
1433
  let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
1424
1434
  let(:client_options) { timeout_options.merge(environment: environment) }
1425
1435
 
1426
- it 'does not use a fallback host by default' do
1427
- expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host|
1428
- expect(host).to eql(expected_host)
1429
- raise EventMachine::ConnectionError
1430
- end
1436
+ context ':fallback_hosts_use_default is unset' do
1437
+ let(:max_time_in_state_for_tests) { 8 }
1438
+ let(:expected_hosts) { Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |suffix| "#{environment}#{suffix}" } + [expected_host] }
1439
+ let(:fallback_hosts_used) { Array.new }
1440
+
1441
+ it 'uses fallback hosts by default' do
1442
+ allow(connection).to receive(:create_transport) do |host|
1443
+ fallback_hosts_used << host
1444
+ raise EventMachine::ConnectionError
1445
+ end
1431
1446
 
1432
- connection.once(:suspended) do
1433
1447
  connection.once(:suspended) do
1448
+ expect(fallback_hosts_used.uniq).to match_array(expected_hosts)
1434
1449
  stop_reactor
1435
1450
  end
1436
1451
  end
@@ -1508,7 +1523,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
1508
1523
  end
1509
1524
 
1510
1525
  context 'with production environment' do
1511
- let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
1526
+ let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) }
1512
1527
  before do
1513
1528
  stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
1514
1529
  end
@@ -122,7 +122,7 @@ describe Ably::Realtime::Connection, :event_machine do
122
122
  end
123
123
  end
124
124
 
125
- context 'with immediately expired token' do
125
+ context 'with immediately expired token and no fallback hosts' do
126
126
  let(:ttl) { 0.001 }
127
127
  let(:auth_requests) { [] }
128
128
  let(:token_callback) do
@@ -131,7 +131,7 @@ describe Ably::Realtime::Connection, :event_machine do
131
131
  Ably::Rest::Client.new(default_options).auth.request_token(ttl: ttl).token
132
132
  end
133
133
  end
134
- let(:client_options) { default_options.merge(auth_callback: token_callback) }
134
+ let(:client_options) { default_options.merge(auth_callback: token_callback, fallback_hosts: []) }
135
135
 
136
136
  it 'renews the token on connect, and makes one immediate subsequent attempt to obtain a new token (#RSA4b)' do
137
137
  started_at = Time.now.to_f
@@ -146,7 +146,7 @@ describe Ably::Realtime::Connection, :event_machine do
146
146
  end
147
147
 
148
148
  context 'when disconnected_retry_timeout is 0.5 seconds' do
149
- let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback) }
149
+ let(:client_options) { default_options.merge(disconnected_retry_timeout: 0.5, auth_callback: token_callback, fallback_hosts: []) }
150
150
 
151
151
  it 'renews the token on connect, and continues to attempt renew based on the retry schedule' do
152
152
  disconnect_count = 0
@@ -172,7 +172,7 @@ describe Ably::Realtime::Connection, :event_machine do
172
172
  end
173
173
 
174
174
  context 'using implicit token auth' do
175
- let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }) }
175
+ let(:client_options) { default_options.merge(use_token_auth: true, default_token_params: { ttl: ttl }, fallback_hosts: []) }
176
176
 
177
177
  before do
178
178
  stub_const 'Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER', -10 # ensure client lib thinks token is still valid
@@ -441,7 +441,9 @@ describe Ably::Realtime::Connection, :event_machine do
441
441
  end
442
442
  end
443
443
 
444
- context '#connect' do
444
+ context '#connect with no fallbacks' do
445
+ let(:client_options) { default_options.merge(fallback_hosts: []) }
446
+
445
447
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
446
448
  expect(connection.connect).to be_a(Ably::Util::SafeDeferrable)
447
449
  stop_reactor
@@ -1167,6 +1169,7 @@ describe Ably::Realtime::Connection, :event_machine do
1167
1169
  host: 'this.host.does.not.exist.com'
1168
1170
  )
1169
1171
  )
1172
+ allow(client).to receive(:fallback_hosts).and_return([])
1170
1173
 
1171
1174
  connection.transition_state_machine! :disconnected
1172
1175
  end
@@ -87,8 +87,10 @@ describe Ably::Rest do
87
87
  let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
88
88
 
89
89
  before do
90
- stub_request(:get, "#{client.endpoint}/time").
91
- to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
90
+ (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
91
+ stub_request(:get, "#{host}/time")
92
+ .to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
93
+ end
92
94
  end
93
95
 
94
96
  it 'should raise a ServerError exception' do
@@ -98,8 +100,10 @@ describe Ably::Rest do
98
100
 
99
101
  describe '500 server error without a valid JSON response body', :webmock do
100
102
  before do
101
- stub_request(:get, "#{client.endpoint}/time").
102
- to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
103
+ (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
104
+ stub_request(:get, "#{host}/time").
105
+ to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
106
+ end
103
107
  end
104
108
 
105
109
  it 'should raise a ServerError exception' do
@@ -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)
@@ -1194,9 +1229,9 @@ describe Ably::Rest::Client do
1194
1229
 
1195
1230
  context 'request_id generation' do
1196
1231
  context 'Timeout error' do
1197
- context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
1232
+ context 'with option add_request_ids: true and no fallback hosts', :webmock, :prevent_log_stubbing do
1198
1233
  let(:custom_logger_object) { TestLogger.new }
1199
- let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
1234
+ let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true, fallback_hosts: []) }
1200
1235
 
1201
1236
  before do
1202
1237
  @request_id = nil
@@ -1286,8 +1321,8 @@ describe Ably::Rest::Client do
1286
1321
  end
1287
1322
  end
1288
1323
 
1289
- context 'without request_id' do
1290
- let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
1324
+ context 'without request_id and no fallback hosts' do
1325
+ let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0, fallback_hosts: []) }
1291
1326
 
1292
1327
  it 'does not include request_id in ConnectionTimeout error' do
1293
1328
  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