ably 1.1.4 → 1.1.5

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.
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