diff --git a/UPDATING.md b/UPDATING.md index 2ebad821..85860259 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -78,6 +78,58 @@ Ably::PubSub::Realtime::Connection::STATE.Connected `Ably::Rest` and `Ably::Realtime` raise `NameError` in 2.0. +### Connection options: `endpoint` + +The `:endpoint` option is now the only way to choose where a client connects. The +`:environment`, `:rest_host`, `:realtime_host` (and its alias `:ws_host`) and +`:fallback_hosts_use_default` options have been removed and are ignored, so a client that +still passes them connects to production (`main.realtime.ably.net`). + +| 1.x | 2.0 | +| --- | --- | +| no host options | no change; traffic moves to `main.realtime.ably.net` | +| `environment: 'sandbox'` | `endpoint: 'nonprod:sandbox'` | +| `environment: 'acme'` (dedicated cluster) | `endpoint: 'acme'` | +| `rest_host:` / `realtime_host: 'localhost'` | `endpoint: 'localhost'` | +| `rest_host` and `realtime_host` set to the same custom host | `endpoint: ''` | +| `rest_host` and `realtime_host` set to different hosts | not supported; use one host that serves both, or contact Ably | +| `fallback_hosts_use_default: true` | delete it | +| custom `fallback_hosts` | unchanged | + +```ruby +# 1.x +client = Ably::Rest::Client.new(key: ENV['ABLY_API_KEY'], environment: 'sandbox') + +# 2.0 +client = Ably::PubSub::Server.create_http_client(key: ENV['ABLY_API_KEY'], endpoint: 'nonprod:sandbox') +``` + +REST requests and the realtime connection now use the same primary domain: + +| `endpoint` | Primary domain | Default fallback hosts | +| --- | --- | --- | +| unset | `main.realtime.ably.net` | `main.[a-e].fallback.ably-realtime.com` | +| `'acme'` | `acme.realtime.ably.net` | `acme.[a-e].fallback.ably-realtime.com` | +| `'nonprod:sandbox'` | `sandbox.realtime.ably-nonprod.net` | `sandbox.[a-e].fallback.ably-realtime-nonprod.com` | +| a hostname, such as `'localhost'`, `'127.0.0.1'` or `'ably.example.com'` | the hostname | none | + +A custom `port` or `tls_port` no longer disables the default fallback hosts; pass +`fallback_hosts: []` to disable them. + +Firewall allowlists and proxies must allow `*.realtime.ably.net` and +`*.fallback.ably-realtime.com` in place of `rest.ably.io`, `realtime.ably.io` and +`*.ably-realtime.com`. + +Related client attributes: + +| 1.x | 2.0 | +| --- | --- | +| `client.endpoint` (a `URI`) | `client.uri`; `client.endpoint` now returns the `:endpoint` option | +| `client.environment` | `client.endpoint` | +| `client.custom_host`, `realtime_client.custom_realtime_host` | `client.primary_domain` | +| `Ably::PubSub::Http::Client::DOMAIN`, `Ably::PubSub::Realtime::Client::DOMAIN` | `client.primary_domain` | +| `Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES` | removed | + ### Releasing realtime channels In 1.x, `Channels#release` detached a realtime channel before removing it. In 2.0, a realtime channel can only be released diff --git a/core/lib/ably/internal.rb b/core/lib/ably/internal.rb index b82dca7d..d2b4d0b2 100644 --- a/core/lib/ably/internal.rb +++ b/core/lib/ably/internal.rb @@ -1,6 +1,6 @@ module Ably # Construction entry points for Ably's own per-side packages: +ably-pubsub-server+, and - # any future device-side package. + # any future device-side package, plus endpoint resolution shared by the clients. # # Applications must not use this module. It carries no compatibility guarantee and may # change in any release, including a patch. Use the factory functions of the package you @@ -12,6 +12,12 @@ module Ably # platform and a directly constructed client declares none. These entry points are how a # per-side package constructs the client it has stamped its side onto. module Internal + # Prefix of an +:endpoint+ value that selects a non-production routing policy, such as +nonprod:sandbox+ + NONPROD_ENDPOINT_PREFIX = 'nonprod:'.freeze + NONPROD_ROOT_DOMAIN = 'ably-nonprod.net'.freeze + NONPROD_FALLBACK_DOMAIN = 'ably-realtime-nonprod.com'.freeze + private_constant :NONPROD_ENDPOINT_PREFIX, :NONPROD_ROOT_DOMAIN, :NONPROD_FALLBACK_DOMAIN + class << self # Constructs an {Ably::PubSub::Http::Client}, bypassing the direct-construction refusal. # @@ -52,6 +58,38 @@ def direct_construction_error(subject, factory) MSG end + # Returns the primary domain for the given +:endpoint+ client option value + # + # @spec REC1b2, REC1b3, REC1b4 + # @param endpoint [String] a routing policy name such as +main+, a +nonprod:[id]+ routing policy, or a hostname + # @return [String] + # @api private + def primary_domain(endpoint) + return endpoint if hostname_endpoint?(endpoint) + + if nonprod_endpoint?(endpoint) + "#{nonprod_endpoint_id(endpoint)}.realtime.#{NONPROD_ROOT_DOMAIN}" + else + "#{endpoint}.realtime.#{Ably::PROD_ROOT_DOMAIN}" + end + end + + # Returns the default fallback domains for the given +:endpoint+ client option value + # + # @spec REC2c1, REC2c2, REC2c3, REC2c4 + # @param endpoint [String] a routing policy name such as +main+, a +nonprod:[id]+ routing policy, or a hostname + # @return [Array] empty when +endpoint+ is a hostname + # @api private + def fallback_domains(endpoint) + return [] if hostname_endpoint?(endpoint) + + if nonprod_endpoint?(endpoint) + Ably::FALLBACK_IDS.map { |id| "#{nonprod_endpoint_id(endpoint)}.#{id}.fallback.#{NONPROD_FALLBACK_DOMAIN}" } + else + Ably::FALLBACK_IDS.map { |id| "#{endpoint}.#{id}.fallback.#{Ably::PROD_FALLBACK_DOMAIN}" } + end + end + private # +allocate+ skips the class's overridden +new+, so +initialize+ has to be invoked @@ -59,6 +97,19 @@ def direct_construction_error(subject, factory) def construct(klass, options) klass.allocate.tap { |client| client.send(:initialize, options) } end + + # @spec REC1b2 + def hostname_endpoint?(endpoint) + endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost' + end + + def nonprod_endpoint?(endpoint) + endpoint.start_with?(NONPROD_ENDPOINT_PREFIX) + end + + def nonprod_endpoint_id(endpoint) + endpoint.delete_prefix(NONPROD_ENDPOINT_PREFIX) + end end end end diff --git a/core/lib/ably/modules/ably.rb b/core/lib/ably/modules/ably.rb index b566588e..eb6dd1fb 100644 --- a/core/lib/ably/modules/ably.rb +++ b/core/lib/ably/modules/ably.rb @@ -4,20 +4,21 @@ # # @see file:README.md README module Ably - # Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to - # network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar + # Default routing policy used when the +:endpoint+ client option is not provided + # @spec REC1a + ENDPOINT = 'main'.freeze + + PROD_ROOT_DOMAIN = 'ably.net'.freeze + PROD_FALLBACK_DOMAIN = 'ably-realtime.com'.freeze + + # Fallback hosts are used when a connection to the primary domain is not possible due to + # network failures either at the client, between the client and Ably, within an Ably data center, or at the domain registrar # see https://ably.com/docs/client-lib-development-guide/features/#RSC15a - # - FALLBACK_DOMAIN = 'ably-realtime.com'.freeze FALLBACK_IDS = %w(a b c d e).freeze - # Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com - FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze - - # Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com - CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host| - "-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze - end.freeze + # Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com + # @spec REC2c1 + FALLBACK_HOSTS = FALLBACK_IDS.map { |id| "#{ENDPOINT}.#{id}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze }.freeze INTERNET_CHECK = { url: '//internet-up.ably-realtime.com/is-the-internet-up.txt', diff --git a/core/lib/ably/pubsub/http/client.rb b/core/lib/ably/pubsub/http/client.rb index 7f49dbd8..bbc8ee0b 100644 --- a/core/lib/ably/pubsub/http/client.rb +++ b/core/lib/ably/pubsub/http/client.rb @@ -19,9 +19,6 @@ class Client extend Forwardable using Ably::Util::AblyExtensions - # Default Ably domain for REST - DOMAIN = 'rest.ably.io' - MAX_MESSAGE_SIZE = 65536 # See spec TO3l8 MAX_FRAME_SIZE = 524288 # See spec TO3l8 @@ -44,9 +41,15 @@ class Client def_delegators :auth, :client_id, :auth_options - # Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment + # The +:endpoint+ client option: a routing policy name such as +main+, a +nonprod:[id]+ routing policy, or a hostname + # @spec REC1 + # @return [String] + attr_reader :endpoint + + # The domain that REST requests and realtime connections are sent to, derived from {#endpoint} + # @spec REC1b # @return [String] - attr_reader :environment + attr_reader :primary_domain # The protocol configured for this client, either binary `:msgpack` or text based `:json` # @return [Symbol] @@ -70,10 +73,6 @@ class Client # @return [Logger::Severity] attr_reader :log_level - # The custom host that is being used if it was provided with the option +:rest_host+ when the {Client} was created - # @return [String,Nil] - attr_reader :custom_host - # The custom port for non-TLS requests if it was provided with the option +:port+ when the {Client} was created # @return [Integer,Nil] attr_reader :custom_port @@ -147,7 +146,7 @@ def self.new(*args, **kwargs, &block) # @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests # @option options [String] :token_details {Models::TokenDetails} used to authenticate requests # @option options [Boolean] :use_token_auth Will force Basic Auth if set to false, and Token auth if set to true - # @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment + # @option options [String] :endpoint ('main') A routing policy name such as 'main', a non-production routing policy such as 'nonprod:sandbox', or a hostname such as 'localhost' # @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported # @option options [Boolean] :use_binary_protocol (true) When true will use the MessagePack binary protocol, when false it will use JSON encoding. This option will overide :protocol option # @option options [Logger::Severity,Symbol] :log_level (Logger::WARN) Log level for the standard Logger that outputs to STDOUT. Can be set to :fatal (Logger::FATAL), :error (Logger::ERROR), :warn (Logger::WARN), :info (Logger::INFO), :debug (Logger::DEBUG) or :none @@ -167,8 +166,8 @@ def self.new(*args, **kwargs, &block) # @option options [Integer] :http_max_retry_count (3) maximum number of fallback host retries for HTTP requests that fail due to network issues or server problems # @option options [Integer] :http_max_retry_duration (15 seconds) maximum elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 5s, and then the subsequent fallback retry attempt takes 7s, no further fallback host attempts will be made as the total elapsed time of 12s exceeds the default 10s limit # - # @option options [Boolean] :fallback_hosts_use_default (false) When true, forces the user of fallback hosts even if a non-default production endpoint is being used - # @option options [Array] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary endpoint. If an empty array is provided, the fallback host functionality is disabled + # @option options [Array] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary domain. If an empty array is provided, the fallback host functionality is disabled. + # When not provided, fallback hosts are derived from +:endpoint+, and none are used if +:endpoint+ is a hostname # @option options [Integer] :fallback_retry_timeout (600 seconds) amount of time in seconds a REST client will continue to use a working fallback host when the primary fallback host has previously failed # # @option options [Boolean] :add_request_ids (false) When true, adds a unique request_id to each request sent to Ably servers. This is handy when reporting issues, because you can refer to a specific request. @@ -209,14 +208,14 @@ def initialize(options) @agent = "#{@agent} #{version ? "#{identifier}/#{version}" : identifier}" end @realtime_client = options.delete(:realtime_client) + + @endpoint = (options.delete(:endpoint) || Ably::ENDPOINT).to_s + @primary_domain = Ably::Internal.primary_domain(endpoint) @tls = options.delete_with_default(:tls, true) - @environment = options.delete(:environment) # nil is production - @environment = nil if [:production, 'production'].include?(@environment) @protocol = options.delete(:protocol) || :msgpack @debug_http = options.delete(:debug_http) @log_level = options.delete(:log_level) || ::Logger::WARN @custom_logger = options.delete(:logger) - @custom_host = options.delete(:rest_host) @custom_port = options.delete(:port) @custom_tls_port = options.delete(:tls_port) @add_request_ids = options.delete(:add_request_ids) @@ -225,21 +224,8 @@ def initialize(options) @max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE @idempotent_rest_publishing = options.delete_with_default(:idempotent_rest_publishing, true) - if options[:fallback_hosts_use_default] && options[:fallback_hosts] - raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided" - end - @fallback_hosts = case - when options.delete(:fallback_hosts_use_default) - Ably::FALLBACK_HOSTS - when options_fallback_hosts = options.delete(:fallback_hosts) - options_fallback_hosts - when custom_host || options[:realtime_host] || custom_port || custom_tls_port - [] - when environment - CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" } - else - Ably::FALLBACK_HOSTS - end + # REC2a2, REC2c + @fallback_hosts = options.delete(:fallback_hosts) || Ably::Internal.fallback_domains(endpoint) options[:fallback_retry_timeout] ||= FALLBACK_RETRY_TIMEOUT @@ -448,10 +434,10 @@ def push @push ||= Push.new(self) end - # @!attribute [r] endpoint - # @return [URI::Generic] Default Ably REST endpoint used for all requests - def endpoint - endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-')) + # @!attribute [r] uri + # @return [URI::Generic] Ably REST URI for the {#primary_domain}, used for all requests unless a fallback host is in use + def uri + uri_for_host(primary_domain) end # @!attribute [r] logger @@ -502,7 +488,7 @@ def connection(options = {}) if options[:use_fallback] fallback_connection else - @connection ||= Faraday.new(endpoint.to_s, connection_options) + @connection ||= Faraday.new(uri.to_s, connection_options) end end @@ -515,7 +501,7 @@ def connection(options = {}) # @api private def fallback_connection unless defined?(@fallback_connections) && @fallback_connections - @fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) } + @fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(uri_for_host(host).to_s, connection_options) } end @fallback_index ||= 0 @@ -675,7 +661,7 @@ def reauthorize_on_authorization_failure end end - def endpoint_for_host(host) + def uri_for_host(host) port = if use_tls? custom_tls_port else diff --git a/core/lib/ably/pubsub/realtime/client.rb b/core/lib/ably/pubsub/realtime/client.rb index 377a9299..63be90cb 100644 --- a/core/lib/ably/pubsub/realtime/client.rb +++ b/core/lib/ably/pubsub/realtime/client.rb @@ -15,8 +15,6 @@ class Client extend Forwardable using Ably::Util::AblyExtensions - DOMAIN = 'realtime.ably.io' - # A {Aby::Realtime::Channels} object. # # @spec RTC3, RTS1 @@ -55,10 +53,6 @@ class Client # @return [Boolean] attr_reader :queue_messages - # The custom realtime websocket host that is being used if it was provided with the option `:ws_host` when the {Client} was created - # @return [String,Nil] - attr_reader :custom_realtime_host - # When true, as soon as the client library is instantiated it will connect to Ably. If this attribute is false, a connection must be opened explicitly # @return [Boolean] attr_reader :auto_connect @@ -74,7 +68,7 @@ class Client def_delegators :auth, :client_id, :auth_options def_delegators :@rest_client, :encoders def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary? - def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port + def_delegators :@rest_client, :endpoint, :primary_domain, :custom_port, :custom_tls_port def_delegators :@rest_client, :log_level def_delegators :@rest_client, :options @@ -139,7 +133,6 @@ def initialize(options) @rest_client = Ably::Internal.create_http_client(options.merge(realtime_client: self)) @echo_messages = rest_client.options.fetch_with_default(:echo_messages, true) @queue_messages = rest_client.options.fetch_with_default(:queue_messages, true) - @custom_realtime_host = rest_client.options[:realtime_host] || rest_client.options[:ws_host] @auto_connect = rest_client.options.fetch_with_default(:auto_connect, true) @recover = rest_client.options.fetch_with_default(:recover, '') @@ -304,10 +297,10 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block) end end - # @!attribute [r] endpoint - # @return [URI::Generic] Default Ably Realtime endpoint used for all requests - def endpoint - endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-')) + # @!attribute [r] uri + # @return [URI::Generic] Ably WebSocket URI for the {#primary_domain}, shared with REST requests (RTN2) + def uri + uri_for_host(primary_domain) end # (see Ably::PubSub::Http::Client#register_encoder) @@ -337,8 +330,8 @@ def disable_automatic_connection_recovery # @api private def fallback_endpoint unless defined?(@fallback_endpoints) && @fallback_endpoints - @fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) } - @fallback_endpoints << endpoint # Try the original host last if all fallbacks have been used + @fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| uri_for_host(fallback_host) } + @fallback_endpoints << uri # Try the original host last if all fallbacks have been used end fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended) - 1 @@ -356,7 +349,7 @@ def device end private - def endpoint_for_host(host) + def uri_for_host(host) port = if use_tls? custom_tls_port else diff --git a/core/lib/ably/pubsub/realtime/connection.rb b/core/lib/ably/pubsub/realtime/connection.rb index 752a195e..d56ae613 100644 --- a/core/lib/ably/pubsub/realtime/connection.rb +++ b/core/lib/ably/pubsub/realtime/connection.rb @@ -174,7 +174,7 @@ def initialize(client, options) @state = STATE(state_machine.current_state) @manager = ConnectionManager.new(self) - @current_host = client.endpoint.host + @current_host = client.uri.hostname reset_client_msg_serial end @@ -395,14 +395,14 @@ def determine_host if should_use_fallback_hosts? internet_up? do |internet_is_up_result| @current_host = if internet_is_up_result - client.fallback_endpoint.host + client.fallback_endpoint.hostname else - client.endpoint.host + client.uri.hostname end yield current_host end else - @current_host = client.endpoint.host + @current_host = client.uri.hostname yield current_host end end @@ -497,8 +497,8 @@ def create_websocket_transport end end - url = URI(client.endpoint).tap do |endpoint| - endpoint.query = URI.encode_www_form(url_params) + url = URI(client.uri).tap do |uri| + uri.query = URI.encode_www_form(url_params) end determine_host do |host| @@ -658,22 +658,6 @@ def connection_state_available? end end - def production? - client.environment.nil? || client.environment == :production - end - - def custom_port? - if client.use_tls? - !!client.custom_tls_port - else - !!client.custom_port - end - end - - def custom_host? - !!client.custom_realtime_host - end - def should_use_fallback_hosts? if client.fallback_hosts && !client.fallback_hosts.empty? if connecting? && previous_state && !disconnected_from_connected_state? diff --git a/spec/acceptance/http/auth_spec.rb b/spec/acceptance/http/auth_spec.rb index 9beb218d..95b874cf 100644 --- a/spec/acceptance/http/auth_spec.rb +++ b/spec/acceptance/http/auth_spec.rb @@ -26,7 +26,7 @@ def hmac_for(token_request_attributes, secret) end vary_by_protocol do - let(:default_options) { { environment: environment, protocol: protocol } } + let(:default_options) { { endpoint: endpoint, protocol: protocol } } let(:client_options) { default_options.merge(key: api_key) } let(:client) do Ably::Internal.create_http_client(client_options) @@ -61,7 +61,7 @@ def request_body_includes(request, protocol, key, val) end it 'creates a TokenRequest automatically and sends it to Ably to obtain a token', webmock: true do - token_request_stub = stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + token_request_stub = stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). to_return(status: 201, body: serialize_body({}, protocol), headers: { 'Content-Type' => content_type }) expect(auth).to receive(:create_token_request).and_call_original auth.request_token @@ -90,7 +90,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, token_param, coerce_if_time_value(token_param, random, multiply: 1000)) end.to_return( @@ -121,7 +121,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'mac', mac) end.to_return( @@ -151,7 +151,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'mac', mac) end.to_return( @@ -293,7 +293,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:auth_url_content_type) { 'application/json' } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'key_name', key_name) end.to_return( @@ -517,7 +517,7 @@ def coerce_if_time_value(field_name, value, params = {}) end context 'that returns a Token string' do - let(:second_client) { Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol) } + let(:second_client) { Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol) } let(:token) { second_client.auth.request_token.token } let!(:token_details) do @@ -1033,7 +1033,7 @@ def coerce_if_time_value(field_name, value, params = {}) auth_callback = lambda do |callback| auth.create_token_request(token_attributes) end - client = Ably::Internal.create_http_client(auth_callback: auth_callback, environment: environment, protocol: protocol) + client = Ably::Internal.create_http_client(auth_callback: auth_callback, endpoint: endpoint, protocol: protocol) client.auth.authorize end end @@ -1053,7 +1053,7 @@ def coerce_if_time_value(field_name, value, params = {}) end let(:token) { token_details.token } let(:token_auth_client) do - Ably::Internal.create_http_client(token: token, environment: environment, protocol: protocol) + Ably::Internal.create_http_client(token: token, endpoint: endpoint, protocol: protocol) end it 'authenticates successfully using the provided :token' do @@ -1099,7 +1099,7 @@ def coerce_if_time_value(field_name, value, params = {}) } } - stub_request(:post, "https://#{environment}-rest.ably.io/channels/foo/publish"). + stub_request(:post, "https://#{TestApp.instance.host}/channels/foo/publish"). to_return(status: 401, body: token_expired.to_json, headers: { 'Content-Type' => 'application/json' }) end @@ -1158,7 +1158,7 @@ def coerce_if_time_value(field_name, value, params = {}) sleep 2.5 WebMock.enable! WebMock.disable_net_connect! - stub_request(:post, "https://#{environment}-rest.ably.io/keys/#{TestApp.instance.key_name}/requestToken"). + stub_request(:post, "https://#{TestApp.instance.host}/keys/#{TestApp.instance.key_name}/requestToken"). to_return(status: 401, body: token_expired_response.to_json, headers: { 'Content-Type' => 'application/json' }) expect { channel.publish 'event' }.to raise_error Ably::Exceptions::TokenExpired expect(auth.current_token_details).to eql(token) @@ -1186,10 +1186,10 @@ def coerce_if_time_value(field_name, value, params = {}) context 'when :client_id is provided in a token' do let(:client_id) { '123' } let(:token) do - Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol).auth.request_token(client_id: client_id) + Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol).auth.request_token(client_id: client_id) end let(:client) do - Ably::Internal.create_http_client(token: token, environment: environment, protocol: protocol) + Ably::Internal.create_http_client(token: token, endpoint: endpoint, protocol: protocol) end specify '#client_id contains the client_id' do @@ -1293,7 +1293,7 @@ def coerce_if_time_value(field_name, value, params = {}) context 'when using JWT' do let(:auth_url) { 'https://echo.ably.io/createJWT' } let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body } - let(:client) { Ably::Internal.create_http_client(token: token, environment: environment, protocol: protocol) } + let(:client) { Ably::Internal.create_http_client(token: token, endpoint: endpoint, protocol: protocol) } it 'authenticates correctly using the JWT token generated by the echo server' do expect(client.stats).to_not be_nil() diff --git a/spec/acceptance/http/base_spec.rb b/spec/acceptance/http/base_spec.rb index e696d4e6..bf1814f2 100644 --- a/spec/acceptance/http/base_spec.rb +++ b/spec/acceptance/http/base_spec.rb @@ -14,7 +14,7 @@ let(:body_value) { [as_since_epoch(now)] } before do - stub_request(:get, "#{client.endpoint}/time"). + stub_request(:get, "#{client.uri}/time"). with(:headers => { 'Accept' => mime }). to_return(:status => 200, :body => request_body, :headers => { 'Content-Type' => mime }) end @@ -67,13 +67,13 @@ vary_by_protocol do let(:client) do - Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true) + Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol, log_retries_as_info: true) end describe 'failed requests' do context 'due to invalid Auth' do it 'should raise an UnauthorizedRequest exception with a valid error message and code' do - invalid_client = Ably::Internal.create_http_client(key: 'appid.keyuid:keysecret', environment: environment) + invalid_client = Ably::Internal.create_http_client(key: 'appid.keyuid:keysecret', endpoint: endpoint) expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error| expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest) expect(error.code).to eql(40101) @@ -86,7 +86,7 @@ let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' } before do - (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host| + (client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host| stub_request(:get, "#{host}/time") .to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' }) end @@ -99,7 +99,7 @@ describe '500 server error without a valid JSON response body', :webmock do before do - (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host| + (client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host| stub_request(:get, "#{host}/time"). to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' }) end @@ -120,7 +120,7 @@ @token_requests = 0 @publish_attempts = 0 - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").to_return do + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").to_return do @token_requests += 1 { :body => public_send("token_#{@token_requests}").merge(expires: (Time.now.to_i + 60) * 1000).to_json, @@ -128,7 +128,7 @@ } end - stub_request(:post, "#{client.endpoint}/channels/#{channel}/publish").to_return do + stub_request(:post, "#{client.uri}/channels/#{channel}/publish").to_return do @publish_attempts += 1 if [1, 3].include?(@publish_attempts) { status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } } @@ -156,7 +156,7 @@ end context 'when NOT auth#token_renewable?' do - let(:client) { Ably::Internal.create_http_client(token: 'token ID cannot be used to create a new token', environment: environment, protocol: protocol) } + let(:client) { Ably::Internal.create_http_client(token: 'token ID cannot be used to create a new token', endpoint: endpoint, protocol: protocol) } it 'should raise an TokenExpired exception' do client.channel(channel).publish('evt', 'msg') diff --git a/spec/acceptance/http/channel_spec.rb b/spec/acceptance/http/channel_spec.rb index cf02207b..31bbb927 100644 --- a/spec/acceptance/http/channel_spec.rb +++ b/spec/acceptance/http/channel_spec.rb @@ -5,7 +5,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol, max_frame_size: max_frame_size, max_message_size: max_message_size, idempotent_rest_publishing: false } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol, max_frame_size: max_frame_size, max_message_size: max_message_size, idempotent_rest_publishing: false } } let(:client_options) { default_options } let(:client) do Ably::Internal.create_http_client(client_options) @@ -364,12 +364,12 @@ context 'with a non ASCII channel name' do let(:channel_name) { 'foo:¡€≤`☃' } let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' } - let(:endpoint) { client.endpoint } + let(:base_uri) { client.uri } let(:channel) { client.channels.get(channel_name) } context 'stubbed', :webmock do let!(:get_stub) { - stub_request(:post, "#{endpoint}/channels/#{channel_name_encoded}/publish"). + stub_request(:post, "#{base_uri}/channels/#{channel_name_encoded}/publish"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -534,8 +534,8 @@ describe '#history option' do let(:channel_name) { "persisted:#{random_str(4)}" } let(:channel) { client.channel(channel_name) } - let(:endpoint) do - client.endpoint + let(:base_uri) do + client.uri end let(:default_history_options) do { @@ -549,7 +549,7 @@ let!(:history_stub) { query_params = default_history_options .merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } diff --git a/spec/acceptance/http/channels_spec.rb b/spec/acceptance/http/channels_spec.rb index bca13dae..c3b47008 100644 --- a/spec/acceptance/http/channels_spec.rb +++ b/spec/acceptance/http/channels_spec.rb @@ -15,7 +15,7 @@ vary_by_protocol do let(:client) do - Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol) + Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol) end let(:channel_name) { random_str } let(:options) { { key: 'value' } } diff --git a/spec/acceptance/http/client_spec.rb b/spec/acceptance/http/client_spec.rb index 98a69c61..2aae4b95 100644 --- a/spec/acceptance/http/client_spec.rb +++ b/spec/acceptance/http/client_spec.rb @@ -4,7 +4,7 @@ describe Ably::PubSub::Http::Client do vary_by_protocol do - let(:default_options) { { environment: environment, protocol: protocol, log_retries_as_info: true } } + let(:default_options) { { endpoint: endpoint, protocol: protocol, log_retries_as_info: true } } let(:client_options) { default_options } let(:client) { Ably::Internal.create_http_client(client_options) } @@ -139,7 +139,7 @@ def encode64(text) let(:client_options) { default_options.merge(key: api_key, client_id: client_id) } let!(:get_message_history_stub) do - stub_request(:get, "https://#{environment}-#{Ably::PubSub::Http::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}") + stub_request(:get, "https://#{client.primary_domain}/channels/#{channel_name}/messages?#{history_querystring}") .with(headers: { 'X-Ably-ClientId' => encode64(client_id) }) .to_return(body: [], headers: { 'Content-Type' => 'application/json' }) end @@ -155,7 +155,7 @@ def encode64(text) let(:client_options) { default_options.merge(token: token_string) } let!(:get_message_history_stub) do - stub_request(:get, "#{http_protocol}://#{environment}-#{Ably::PubSub::Http::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}"). + stub_request(:get, "#{http_protocol}://#{client.primary_domain}/channels/#{channel_name}/messages?#{history_querystring}"). with(headers: { 'Authorization' => "Bearer #{encode64(token_string)}" }). to_return(body: [], headers: { 'Content-Type' => 'application/json' }) end @@ -244,7 +244,7 @@ def encode64(text) context 'connection transport' do context 'defaults' do - let(:client_options) { default_options.merge(key: api_key, environment: 'production') } + let(:client_options) { default_options.merge(key: api_key) } context 'for default host' do it "is configured to timeout connection opening in #{http_defaults.fetch(:open_timeout)} seconds" do @@ -270,7 +270,7 @@ def encode64(text) context 'with custom http_open_timeout and http_request_timeout options' do let(:http_open_timeout) { 999 } let(:http_request_timeout) { 666 } - let(:client_options) { default_options.merge(key: api_key, http_open_timeout: http_open_timeout, http_request_timeout: http_request_timeout, environment: 'production') } + let(:client_options) { default_options.merge(key: api_key, http_open_timeout: http_open_timeout, http_request_timeout: http_request_timeout) } context 'for default host' do it 'is configured to use custom open timeout' do @@ -299,22 +299,22 @@ def encode64(text) let(:publish_block) { lambda { client.channel('test').publish('event', 'data') } } context 'configured' do - let(:client_options) { default_options.merge(key: api_key, environment: 'production') } + let(:client_options) { default_options.merge(key: api_key, endpoint: Ably::ENDPOINT) } - 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 + it 'should make connection attempts to main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com (#RSC15a, #REC2c1)' do hosts = [] 5.times do hosts << client.fallback_connection.host end - 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)) + expect(hosts).to match_array(%w(a b c d e).map { |id| "main.#{id}.fallback.ably-realtime.com" }) end end - context 'when environment is NOT production (#RSC15b)' do + context 'when endpoint is a nonprod routing policy (#RSC15b)' do context 'and custom fallback hosts are empty' do - let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key, fallback_hosts: []) } + let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox', key: api_key, fallback_hosts: []) } let!(:default_host_request_stub) do - stub_request(:post, "https://#{environment}-#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://sandbox.realtime.ably-nonprod.net#{path}").to_return do raise Faraday::TimeoutError.new('timeout error message') end end @@ -325,26 +325,27 @@ def encode64(text) end context 'and no custom fallback hosts are provided' do - let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) } + let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox', key: api_key) } - 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 + it 'should make connection attempts to sandbox.a.fallback.ably-realtime-nonprod.com ... sandbox.e.fallback.ably-realtime-nonprod.com (#RSC15a, #REC2c3)' do hosts = [] 5.times do hosts << client.fallback_connection.host end - expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox-#{id}-fallback.ably-realtime.com" }) + expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox.#{id}.fallback.ably-realtime-nonprod.com" }) end end end - context 'when environment is production' do - let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) } + context 'when endpoint is the default production routing policy' do + let(:custom_hosts) { Ably::FALLBACK_HOSTS[0...2] } let(:max_retry_count) { 2 } let(:max_retry_duration) { 0.5 } let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } } let(:client_options) do default_options.merge( - environment: nil, + endpoint: Ably::ENDPOINT, + fallback_hosts: custom_hosts, key: api_key, http_max_retry_duration: max_retry_duration, http_max_retry_count: max_retry_count, @@ -352,10 +353,6 @@ def encode64(text) ) end - before do - stub_const 'Ably::FALLBACK_HOSTS', custom_hosts - end - let!(:first_fallback_request_stub) do stub_request(:post, "https://#{custom_hosts[0]}#{path}").to_return(&fallback_block) end @@ -366,7 +363,7 @@ def encode64(text) context 'and connection times out' do let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return do raise Faraday::TimeoutError.new('timeout error message') end end @@ -380,7 +377,7 @@ def encode64(text) context "and the total request time exeeds #{http_defaults.fetch(:max_retry_duration)} seconds" do let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return do sleep max_retry_duration * 1.5 raise Faraday::TimeoutError.new('timeout error message') end @@ -397,7 +394,7 @@ def encode64(text) context 'and connection fails' do let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return do raise Faraday::ConnectionFailed.new('connection failure error message') end end @@ -413,7 +410,8 @@ def encode64(text) context 'and first request to primary endpoint fails' do let(:client_options) do default_options.merge( - environment: nil, + endpoint: Ably::ENDPOINT, + fallback_hosts: custom_hosts, key: api_key, http_max_retry_duration: max_retry_duration, http_max_retry_count: max_retry_count, @@ -422,7 +420,7 @@ def encode64(text) end let(:requests) { [] } let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return do requests << true if requests.count == 1 raise Faraday::ConnectionFailed.new('connection failure error message') @@ -450,7 +448,7 @@ def encode64(text) context 'and basic authentication fails' do let(:status) { 401 } let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return( + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return( headers: { 'Content-Type' => 'application/json' }, status: status, body: { @@ -482,7 +480,7 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return(&fallback_block) end it 'attempts the fallback hosts as this is an authentication failure (#RSC15d)' do @@ -494,14 +492,14 @@ def encode64(text) end end - context 'when environment is production and server returns a 50x error' do + context 'when endpoint is the default production routing policy and server returns a 50x error' do let(:custom_hosts) { %w(A.foo.com B.foo.com) } let(:max_retry_count) { 2 } let(:max_retry_duration) { 0.5 } let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } } let(:production_options) do default_options.merge( - environment: nil, + endpoint: Ably::ENDPOINT, key: api_key, http_max_retry_duration: max_retry_duration, http_max_retry_count: max_retry_count @@ -518,7 +516,7 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://main.realtime.ably.net#{path}").to_return(&fallback_block) end context 'with custom fallback hosts provided' do @@ -599,7 +597,7 @@ def encode64(text) context 'POST with request timeout less than max_retry_duration' do let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -622,7 +620,7 @@ def encode64(text) context 'GET with request timeout less than max_retry_duration' do let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -645,7 +643,7 @@ def encode64(text) context 'POST with request timeout more than max_retry_duration' do let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -667,7 +665,7 @@ def encode64(text) context 'GET with request timeout more than max_retry_duration' do let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -714,7 +712,7 @@ def encode64(text) let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -769,7 +767,7 @@ def encode64(text) let(:client_options) do default_options.merge( - rest_host: primary_host, + endpoint: primary_host, fallback_hosts: fallbacks, token: 'fake.token', port: port, @@ -836,16 +834,16 @@ def encode64(text) end end - context 'when environment is not production and server returns a 50x error' do - let(:env) { 'custom-env' } - let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}-#{id}-fallback.ably-realtime.com" } } + context 'when endpoint is a custom routing policy and server returns a 50x error' do + let(:routing_policy) { 'custom-env' } + let(:default_fallbacks) { %w(a b c d e).map { |id| "#{routing_policy}.#{id}.fallback.ably-realtime.com" } } let(:custom_hosts) { %w(A.foo.com B.foo.com) } let(:max_retry_count) { 2 } let(:max_retry_duration) { 0.5 } let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } } let(:production_options) do default_options.merge( - environment: env, + endpoint: routing_policy, key: api_key, http_max_retry_duration: max_retry_duration, http_max_retry_count: max_retry_count, @@ -863,15 +861,15 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{env}-#{Ably::PubSub::Http::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://#{routing_policy}.realtime.ably.net#{path}").to_return(&fallback_block) end - context 'with no fallback hosts provided (#TBC, see https://github.com/ably/wiki/issues/361)' do + context 'with no fallback hosts provided (#REC2c4)' do let(:client_options) { production_options.merge(log_level: :fatal) } - it 'uses the default fallback hosts for that environment as this is not an authentication failure' do + it 'uses the default fallback hosts for that routing policy as this is not an authentication failure' do fallbacks_called_count = 0 default_fallbacks.each do |host| counting_fallback_proc = proc do @@ -917,39 +915,12 @@ def encode64(text) expect(default_host_request_stub).to have_been_requested end end - - context 'with fallback_hosts_use_default: true (#RSC15b, #TO3k7)' do - let(:custom_hosts) { Ably::FALLBACK_HOSTS[0...2] } - - before do - stub_const 'Ably::FALLBACK_HOSTS', custom_hosts - end - - let!(:first_fallback_request_stub) do - stub_request(:post, "https://#{Ably::FALLBACK_HOSTS[0]}#{path}").to_return(&fallback_block) - end - - let!(:second_fallback_request_stub) do - stub_request(:post, "https://#{Ably::FALLBACK_HOSTS[1]}#{path}").to_return(&fallback_block) - end - - let(:client_options) { - production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal) - } - - it 'attempts the default fallback hosts as this is an authentication failure' do - expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError) - expect(default_host_request_stub).to have_been_requested - expect(first_fallback_request_stub).to have_been_requested - expect(second_fallback_request_stub).to have_been_requested - end - end end end - context 'with a custom host' do + context 'with a hostname endpoint' do let(:custom_host) { 'host.does.not.exist' } - let(:client_options) { default_options.merge(key: api_key, rest_host: custom_host) } + let(:client_options) { default_options.merge(key: api_key, endpoint: custom_host) } let(:capability) { { :foo => ["publish"] } } context 'that does not exist' do @@ -969,12 +940,12 @@ def encode64(text) before do Ably::FALLBACK_HOSTS.each do |host| stub_request(:post, "https://#{host}#{path}").to_return do - raise 'Fallbacks should not be used with custom hosts' + raise 'Fallbacks should not be used with a hostname endpoint' end end end - specify 'are never used' do + specify 'are never used (#REC2c2, #RSC15m)' do expect { client.channel('test').publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionError expect(custom_host_request_stub).to have_been_requested end @@ -997,12 +968,12 @@ def encode64(text) before do Ably::FALLBACK_HOSTS.each do |host| stub_request(:post, "https://#{host}#{path}").to_return do - raise 'Fallbacks should not be used with custom hosts' + raise 'Fallbacks should not be used with a hostname endpoint' end end end - specify 'are never used' do + specify 'are never used (#REC2c2, #RSC15m)' do expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeout expect(custom_host_request_stub).to have_been_requested end @@ -1086,7 +1057,7 @@ def encode64(text) let(:client_options) { default_options.merge(key: api_key, agent: agent) } let!(:publish_message_stub) do - stub_request(:post, "#{client.endpoint}/channels/foo/publish"). + stub_request(:post, "#{client.uri}/channels/foo/publish"). with(headers: { 'X-Ably-Version' => Ably::PROTOCOL_VERSION, 'Ably-Agent' => agent || Ably::AGENT @@ -1113,7 +1084,7 @@ def encode64(text) context '#request (#RSC19*, #TO3l9)' do let(:client_options) { default_options.merge(key: api_key) } let(:device_id) { random_str } - let(:endpoint) { client.endpoint } + let(:base_uri) { client.uri } context 'get' do it 'returns an HttpPaginatedResponse object' do @@ -1156,7 +1127,7 @@ def encode64(text) context 'post', :webmock do before do - stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). + stub_request(:delete, "#{base_uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1168,14 +1139,14 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:post, endpoint, {}, { content: 'x' * Ably::PubSub::Http::Client::MAX_FRAME_SIZE }) + client.request(:post, base_uri, {}, { content: 'x' * Ably::PubSub::Http::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end context 'delete', :webmock do before do - stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}"). + stub_request(:delete, "#{base_uri}/push/channelSubscriptions?deviceId=#{device_id}"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1190,7 +1161,7 @@ def encode64(text) let(:body_params) { { 'metadata' => { 'key' => 'value' } } } before do - stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:patch, "#{base_uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1219,7 +1190,7 @@ def encode64(text) end before do - stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:put, "#{base_uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1246,7 +1217,7 @@ def encode64(text) before do @request_id = nil - stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request| + stub_request(:get, Addressable::Template.new("#{client.uri}/time{?request_id}")).with do |request| @request_id = request.uri.query_values['request_id'] end.to_return do raise Faraday::TimeoutError.new('timeout error message') @@ -1268,7 +1239,7 @@ def encode64(text) context 'with mocks to inspect the params', :webmock do before do - stub_request(:post, Addressable::Template.new("#{client.endpoint}/channels/#{channel_name}/publish{?request_id}")). + stub_request(:post, Addressable::Template.new("#{client.uri}/channels/#{channel_name}/publish{?request_id}")). with do |request| @request_id = request.uri.query_values['request_id'] end.to_return(:status => 200, :body => [], :headers => { 'Content-Type' => 'application/json' }) @@ -1306,13 +1277,13 @@ def encode64(text) end end - context 'option add_request_ids: true and specified fallback hosts', :webmock do - let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error, log_retries_as_info: true } } + context 'option add_request_ids: true and default fallback hosts', :webmock do + let(:client_options) { { key: api_key, add_request_ids: true, log_level: :error, log_retries_as_info: true } } let(:requests) { [] } before do @request_id = nil - hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io'] + hosts = Ably::FALLBACK_HOSTS + ['main.realtime.ably.net'] hosts.each do |host| stub_request(:get, Addressable::Template.new("https://#{host.downcase}/time{?request_id}")).with do |request| @request_id = request.uri.query_values['request_id'] @@ -1373,8 +1344,8 @@ def encode64(text) context 'with the first request failing' do let(:client_options) do default_options.merge( - rest_host: 'non.existent.domain.local', - fallback_hosts: [[environment, Ably::PubSub::Http::Client::DOMAIN].join('-')], + endpoint: 'non.existent.domain.local', + fallback_hosts: [TestApp.instance.host], key: api_key, logger: custom_logger, log_retries_as_info: false) @@ -1390,7 +1361,7 @@ def encode64(text) context 'with all requests failing' do let(:client_options) do default_options.merge( - rest_host: 'non.existent.domain.local', + endpoint: 'non.existent.domain.local', fallback_hosts: ['non2.existent.domain.local'], key: api_key, logger: custom_logger, diff --git a/spec/acceptance/http/encoders_spec.rb b/spec/acceptance/http/encoders_spec.rb index 830cf776..8b86b96f 100644 --- a/spec/acceptance/http/encoders_spec.rb +++ b/spec/acceptance/http/encoders_spec.rb @@ -3,7 +3,7 @@ require 'base64' describe Ably::Models::MessageEncoders do - let(:default_client_options) { { key: api_key, environment: environment } } + let(:default_client_options) { { key: api_key, endpoint: endpoint } } let(:client) { Ably::Internal.create_http_client(default_client_options.merge(protocol: protocol)) } let(:channel_options) { {} } let(:channel) { client.channel('test', channel_options) } diff --git a/spec/acceptance/http/message_spec.rb b/spec/acceptance/http/message_spec.rb index 1d9c8749..b1192c98 100644 --- a/spec/acceptance/http/message_spec.rb +++ b/spec/acceptance/http/message_spec.rb @@ -7,7 +7,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_client_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_client_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:client_options) { default_client_options } let(:client) { Ably::Internal.create_http_client(client_options) } let(:other_client) { Ably::Internal.create_http_client(client_options) } @@ -220,7 +220,7 @@ end context 'when idempotent publishing is enabled in the client library ClientOptions (#TO3n)' do - let(:client_options) { default_client_options.merge(idempotent_rest_publishing: true, log_level: :error, fallback_hosts: ["#{environment}-realtime.ably.io"]) } + let(:client_options) { default_client_options.merge(idempotent_rest_publishing: true, log_level: :error, fallback_hosts: [TestApp.instance.host]) } context 'when there is a network failure triggering an automatic retry (#RSL1k4)' do def mock_for_two_publish_failures diff --git a/spec/acceptance/http/presence_spec.rb b/spec/acceptance/http/presence_spec.rb index b4465be7..77207cc3 100644 --- a/spec/acceptance/http/presence_spec.rb +++ b/spec/acceptance/http/presence_spec.rb @@ -5,7 +5,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:client_options) { default_options } let(:client) do Ably::Internal.create_http_client(client_options) @@ -68,12 +68,12 @@ limit: 100 } end - let(:endpoint) do - client.endpoint + let(:base_uri) do + client.uri end let!(:get_stub) { query_params = query_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } let(:channel_name) { random_str } @@ -94,10 +94,10 @@ context 'filter query params', webmock: true do let(:channel_name) { random_str } let(:channel) { client.channels.get(channel_name) } - let(:endpoint) { client.endpoint } + let(:base_uri) { client.uri } def stub_presence_get(query) - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence"). with(query: query). to_return(body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -142,12 +142,12 @@ def stub_presence_get(query) context 'with a non ASCII channel name' do let(:channel_name) { 'foo:¡€≤`☃' } let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' } - let(:endpoint) { client.endpoint } + let(:base_uri) { client.uri } let(:channel) { client.channels.get(channel_name) } context 'stubbed', :webmock do let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{channel_name_encoded}/presence?limit=100"). + stub_request(:get, "#{base_uri}/channels/#{channel_name_encoded}/presence?limit=100"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -224,8 +224,8 @@ def stub_presence_get(query) let(:presence) { client.channel(channel_name).presence } let(:user) { 'appid.keyuid' } let(:secret) { random_str(8) } - let(:endpoint) do - client.endpoint + let(:base_uri) do + client.uri end let(:client) do Ably::Internal.create_http_client(key: "#{user}:#{secret}") @@ -240,7 +240,7 @@ def stub_presence_get(query) context 'limit options', :webmock do let!(:history_stub) { query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -280,7 +280,7 @@ def stub_presence_get(query) } let!(:history_stub) { query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -349,8 +349,8 @@ def message(client_id, messages) describe 'decoding permutations using mocked #history', :webmock do let(:user) { 'appid.keyuid' } let(:secret) { random_str(8) } - let(:endpoint) do - client.endpoint + let(:base_uri) do + client.uri end let(:client) do Ably::Internal.create_http_client(client_options.merge(key: "#{user}:#{secret}")) @@ -384,7 +384,7 @@ def message(client_id, messages) context '#get' do let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type }) } @@ -401,7 +401,7 @@ def message(client_id, messages) context '#history' do let!(:history_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type }) } @@ -431,7 +431,7 @@ def message(client_id, messages) context '#get' do let(:client_options) { default_options.merge(log_level: :fatal) } let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type }) } let(:presence_message) { presence.get.items.first } @@ -455,7 +455,7 @@ def message(client_id, messages) context '#history' do let(:client_options) { default_options.merge(log_level: :fatal) } let!(:history_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). + stub_request(:get, "#{base_uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type }) } let(:presence_message) { presence.history.items.first } diff --git a/spec/acceptance/http/push_admin_spec.rb b/spec/acceptance/http/push_admin_spec.rb index a5f36fbd..880f7052 100644 --- a/spec/acceptance/http/push_admin_spec.rb +++ b/spec/acceptance/http/push_admin_spec.rb @@ -5,7 +5,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol} } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol} } let(:client_options) { default_options } let(:client) do Ably::Internal.create_http_client(client_options) @@ -90,7 +90,7 @@ end let!(:publish_stub) do - stub_request(:post, "#{client.endpoint}/push/publish"). + stub_request(:post, "#{client.uri}/push/publish"). with do |request| expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val') expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case') @@ -119,7 +119,7 @@ 'transportType' => 'ablyChannel', 'channel' => channel, 'ablyKey' => api_key, - 'ablyUrl' => client.endpoint.to_s + 'ablyUrl' => client.uri.to_s } end let(:notification_payload) do diff --git a/spec/acceptance/http/stats_spec.rb b/spec/acceptance/http/stats_spec.rb index 9d545353..dc89a8db 100644 --- a/spec/acceptance/http/stats_spec.rb +++ b/spec/acceptance/http/stats_spec.rb @@ -48,7 +48,7 @@ end vary_by_protocol do - let(:client) { Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol) } + let(:client) { Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol) } describe 'fetching application stats' do it 'returns a PaginatedResult object' do diff --git a/spec/acceptance/http/time_spec.rb b/spec/acceptance/http/time_spec.rb index f64f6b32..ee36b928 100644 --- a/spec/acceptance/http/time_spec.rb +++ b/spec/acceptance/http/time_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Http::Client, '#time' do vary_by_protocol do let(:client) do - Ably::Internal.create_http_client(key: api_key, environment: environment, protocol: protocol) + Ably::Internal.create_http_client(key: api_key, endpoint: endpoint, protocol: protocol) end describe 'fetching the service time' do @@ -13,7 +13,7 @@ context 'with reconfigured HTTP timeout' do let(:client) do - Ably::Internal.create_http_client(http_request_timeout: 0.0001, key: api_key, environment: environment, protocol: protocol, log_retries_as_info: true) + Ably::Internal.create_http_client(http_request_timeout: 0.0001, key: api_key, endpoint: endpoint, protocol: protocol, log_retries_as_info: true) end it 'should raise a timeout exception' do diff --git a/spec/acceptance/realtime/auth_spec.rb b/spec/acceptance/realtime/auth_spec.rb index 0b4d3917..7890fddc 100644 --- a/spec/acceptance/realtime/auth_spec.rb +++ b/spec/acceptance/realtime/auth_spec.rb @@ -14,7 +14,7 @@ def disconnect_transport(connection) end vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:client_options) { default_options } let(:client) { auto_close Ably::Internal.create_realtime_client(client_options) } let(:auth) { client.auth } @@ -1116,7 +1116,7 @@ def disconnect_transport(connection) end context 'when the client is initialized with ClientOptions and the token is a JWT token' do - let(:client_options) { { token: token, environment: environment, protocol: protocol } } + let(:client_options) { { token: token, endpoint: endpoint, protocol: protocol } } context 'when credentials are valid' do let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body } @@ -1134,7 +1134,7 @@ def disconnect_transport(connection) context 'when credentials are invalid' do let(:key_secret) { 'invalid' } let(:token) { Faraday.get("#{auth_url}?keyName=#{key_name}&keySecret=#{key_secret}").body } - let(:client_options) { { token: token, environment: environment, protocol: protocol, log_level: :none } } + let(:client_options) { { token: token, endpoint: endpoint, protocol: protocol, log_level: :none } } it 'fails with an invalid signature error' do client.connection.once(:disconnected) do |state_change| diff --git a/spec/acceptance/realtime/channel_history_spec.rb b/spec/acceptance/realtime/channel_history_spec.rb index f88e040e..6fc348d6 100644 --- a/spec/acceptance/realtime/channel_history_spec.rb +++ b/spec/acceptance/realtime/channel_history_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Realtime::Channel, '#history', :event_machine do vary_by_protocol do - let(:default_options) { options.merge(key: api_key, environment: environment, protocol: protocol) } + let(:default_options) { options.merge(key: api_key, endpoint: endpoint, protocol: protocol) } let(:client) { auto_close Ably::Internal.create_realtime_client(default_options) } let(:channel) { client.channel(channel_name) } diff --git a/spec/acceptance/realtime/channel_spec.rb b/spec/acceptance/realtime/channel_spec.rb index da3e80f8..a8251ac1 100644 --- a/spec/acceptance/realtime/channel_spec.rb +++ b/spec/acceptance/realtime/channel_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Realtime::Channel, :event_machine do vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:client_options) { default_options } let(:client) { auto_close Ably::Internal.create_realtime_client(client_options) } diff --git a/spec/acceptance/realtime/channels_spec.rb b/spec/acceptance/realtime/channels_spec.rb index 582f6f29..f5f9069c 100644 --- a/spec/acceptance/realtime/channels_spec.rb +++ b/spec/acceptance/realtime/channels_spec.rb @@ -18,7 +18,7 @@ vary_by_protocol do let(:client_options) do - { key: api_key, environment: environment, protocol: protocol } + { key: api_key, endpoint: endpoint, protocol: protocol } end let(:client) do auto_close Ably::Internal.create_realtime_client(client_options) diff --git a/spec/acceptance/realtime/client_spec.rb b/spec/acceptance/realtime/client_spec.rb index a9ddd3ed..97a28b59 100644 --- a/spec/acceptance/realtime/client_spec.rb +++ b/spec/acceptance/realtime/client_spec.rb @@ -4,7 +4,7 @@ describe Ably::PubSub::Realtime::Client, :event_machine do vary_by_protocol do let(:default_options) do - { key: api_key, environment: environment, protocol: protocol } + { key: api_key, endpoint: endpoint, protocol: protocol } end let(:client_options) { default_options } @@ -234,7 +234,7 @@ context '#request (#RSC19*)' do let(:client_options) { default_options.merge(key: api_key) } let(:device_id) { random_str } - let(:endpoint) { subject.rest_client.endpoint } + let(:base_uri) { subject.rest_client.uri } context 'get' do it 'returns an HttpPaginatedResponse object' do @@ -287,7 +287,7 @@ context 'post', :webmock do before do - stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). + stub_request(:delete, "#{base_uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -301,7 +301,7 @@ context 'delete', :webmock do before do - stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}"). + stub_request(:delete, "#{base_uri}/push/channelSubscriptions?deviceId=#{device_id}"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -317,7 +317,7 @@ let(:body_params) { { 'metadata' => { 'key' => 'value' } } } before do - stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:patch, "#{base_uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -341,7 +341,7 @@ end before do - stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:put, "#{base_uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end diff --git a/spec/acceptance/realtime/connection_failures_spec.rb b/spec/acceptance/realtime/connection_failures_spec.rb index 989090e9..1e88c371 100644 --- a/spec/acceptance/realtime/connection_failures_spec.rb +++ b/spec/acceptance/realtime/connection_failures_spec.rb @@ -6,7 +6,7 @@ vary_by_protocol do let(:default_options) do - { key: api_key, environment: environment, protocol: protocol } + { key: api_key, endpoint: endpoint, protocol: protocol } end let(:client_options) { default_options } @@ -297,7 +297,7 @@ let(:timer) { Hash.new } let(:client_options) do - client_failure_options.merge(realtime_host: 'non.existent.host') + client_failure_options.merge(endpoint: 'non.existent.host') end def count_state_changes @@ -335,7 +335,7 @@ def time_passed context 'for the first time' do let(:client_options) do - default_options.merge(realtime_host: 'non.existent.host', disconnected_retry_timeout: 2, log_level: :error) + default_options.merge(endpoint: 'non.existent.host', disconnected_retry_timeout: 2, log_level: :error) end it 'reattempts connection immediately and then waits disconnected_retry_timeout for a subsequent attempt' do @@ -476,7 +476,7 @@ def time_passed it 'is reset to nil when :connected' do connection.once(:disconnected) do |error| # stub the host so that the connection connects - allow(connection).to receive(:determine_host).and_yield(TestApp.instance.realtime_host) + allow(connection).to receive(:determine_host).and_yield(TestApp.instance.host) connection.once(:connected) do expect(connection.error_reason).to be_nil stop_reactor @@ -535,7 +535,7 @@ def time_passed disconnected_retry_timeout: 0.1, suspended_retry_timeout: 0.1, max_connection_state_ttl: 0.2, - realtime_host: 'non.existent.host' + endpoint: 'non.existent.host' ) end @@ -930,8 +930,7 @@ def send_disconnect_message original_method.call(*args, &block) end connection.once(:connected) do - host = "#{"#{environment}-" if environment && environment.to_s != 'production'}#{Ably::PubSub::Realtime::Client::DOMAIN}" - expect(hosts.first).to eql(host) + expect(hosts.first).to eql(TestApp.instance.host) expect(hosts.length).to eql(1) stop_reactor end @@ -1418,7 +1417,7 @@ def kill_connection_transport_and_prevent_valid_resume let(:timeout_options) do default_options.merge( - environment: :production, + endpoint: Ably::ENDPOINT, log_level: :none, disconnected_retry_timeout: retry_every_for_tests, suspended_retry_timeout: retry_every_for_tests, @@ -1432,11 +1431,11 @@ def kill_connection_transport_and_prevent_valid_resume let(:retry_count_for_one_state) { 1 + expected_retry_attempts } # initial connect then disconnected let(:retry_count_for_all_states) { 1 + expected_retry_attempts + 1 } # initial connection, disconnected & then one suspended attempt - context 'with custom realtime websocket host option' do + context 'with a hostname endpoint' do let(:expected_host) { 'this.host.does.not.exist' } - let(:client_options) { timeout_options.merge(realtime_host: expected_host) } + let(:client_options) { timeout_options.merge(endpoint: expected_host) } - it 'never uses a fallback host' do + it 'never uses a fallback host (#REC2c2)' do expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host| expect(host).to eql(expected_host) raise EventMachine::ConnectionError @@ -1454,31 +1453,33 @@ def kill_connection_transport_and_prevent_valid_resume let(:custom_port) { 666} let(:client_options) { timeout_options.merge(tls_port: custom_port) } - it 'never uses a fallback host' do - expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host, port| - expect(port).to eql(custom_port) + it 'uses the custom port for every connection attempt, including to fallback hosts' do + ports_used = [] + allow(connection).to receive(:create_transport) do |host, port| + ports_used << port raise EventMachine::ConnectionError end connection.once(:suspended) do connection.once(:suspended) do + expect(ports_used.length).to be >= retry_count_for_all_states + expect(ports_used.uniq).to eql([custom_port]) stop_reactor end end end end - context 'with non-production environment' do - let(:environment) { 'sandbox' } - let(:expected_host) { "#{environment}-#{Ably::PubSub::Realtime::Client::DOMAIN}" } - let(:client_options) { timeout_options.merge(environment: environment) } + context 'with the test app routing policy endpoint' do + let(:expected_host) { TestApp.instance.host } + let(:client_options) { timeout_options.merge(endpoint: endpoint) } - context ':fallback_hosts_use_default is unset' do + context 'and no fallback hosts provided' do let(:max_time_in_state_for_tests) { 8 } - let(:expected_hosts) { Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |suffix| "#{environment}#{suffix}" } + [expected_host] } + let(:expected_hosts) { Ably::Internal.fallback_domains(endpoint) + [expected_host] } let(:fallback_hosts_used) { Array.new } - it 'uses fallback hosts by default' do + it 'uses the default fallback hosts for that routing policy (#REC2c3, #REC2c4)' do allow(connection).to receive(:create_transport) do |host| fallback_hosts_used << host raise EventMachine::ConnectionError @@ -1491,28 +1492,8 @@ def kill_connection_transport_and_prevent_valid_resume end end - context ':fallback_hosts_use_default is true' do + context 'when the connection succeeds' do let(:max_time_in_state_for_tests) { 4 } - let(:fallback_hosts_used) { Array.new } - let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts_use_default: true) } - - it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k7)' do - request = 0 - allow(connection).to receive(:create_transport) do |host| - if request == 0 - expect(host).to eql(expected_host) - else - fallback_hosts_used << host - end - request += 1 - raise EventMachine::ConnectionError - end - - connection.once(:suspended) do - expect(fallback_hosts_used.uniq).to match_array(Ably::FALLBACK_HOSTS + [expected_host]) - stop_reactor - end - end it 'does not use a fallback host if the connection connects on the default host and then later becomes disconnected', em_timeout: 25 do request = 0 @@ -1540,7 +1521,7 @@ def kill_connection_transport_and_prevent_valid_resume let(:max_time_in_state_for_tests) { 4 } let(:fallback_hosts) { %w(a.foo.com b.foo.com) } let(:fallback_hosts_used) { Array.new } - let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts: fallback_hosts) } + let(:client_options) { timeout_options.merge(endpoint: endpoint, fallback_hosts: fallback_hosts) } it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do request = 0 @@ -1562,14 +1543,10 @@ def kill_connection_transport_and_prevent_valid_resume end end - context 'with production environment' do - let(:custom_hosts) { %w(a.ably-realtime.com b.ably-realtime.com) } - before do - stub_const 'Ably::FALLBACK_HOSTS', custom_hosts - end - - let(:expected_host) { Ably::PubSub::Realtime::Client::DOMAIN } - let(:client_options) { timeout_options.merge(environment: nil) } + context 'with the default production routing policy endpoint' do + let(:custom_hosts) { Ably::FALLBACK_HOSTS[0...2] } + let(:expected_host) { 'main.realtime.ably.net' } + let(:client_options) { timeout_options.merge(fallback_hosts: custom_hosts) } let(:fallback_hosts_used) { Array.new } @@ -1598,7 +1575,7 @@ def kill_connection_transport_and_prevent_valid_resume @suspended = 0 end - context 'and default options' do + context 'and two fallback hosts' do let(:max_time_in_state_for_tests) { 2 } # allow time for 3 attempts, 2 configured fallbacks + primary host it 'uses a fallback host + the original host once on every subsequent disconnected attempt until suspended' do @@ -1671,7 +1648,7 @@ def kill_connection_transport_and_prevent_valid_resume let(:max_time_in_state_for_tests) { 3 } let(:fallback_hosts) { [] } let(:hosts_used) { Array.new } - let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) } + let(:client_options) { timeout_options.merge(fallback_hosts: fallback_hosts) } it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do allow(connection).to receive(:create_transport) do |host| @@ -1691,7 +1668,7 @@ def kill_connection_transport_and_prevent_valid_resume let(:max_time_in_state_for_tests) { 3 } let(:fallback_hosts) { %w(a.foo.com b.foo.com) } let(:fallback_hosts_used) { Array.new } - let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) } + let(:client_options) { timeout_options.merge(fallback_hosts: fallback_hosts) } it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do request = 0 diff --git a/spec/acceptance/realtime/connection_spec.rb b/spec/acceptance/realtime/connection_spec.rb index ad128862..245966de 100644 --- a/spec/acceptance/realtime/connection_spec.rb +++ b/spec/acceptance/realtime/connection_spec.rb @@ -7,7 +7,7 @@ vary_by_protocol do let(:default_options) do - { key: api_key, environment: environment, protocol: protocol } + { key: api_key, endpoint: endpoint, protocol: protocol } end let(:client_options) { default_options } @@ -29,7 +29,7 @@ context 'current_host' do it 'is available immediately after the client is instanced' do - expect(connection.current_host.to_s).to match(/\.ably\.io$/) + expect(connection.current_host).to eql(TestApp.instance.host) stop_reactor end end @@ -484,7 +484,7 @@ def expect_ordered_phases end context "when can't connect to host" do - let(:client_options) { super().merge(realtime_host: 'non-existent.ably.io') } + let(:client_options) { super().merge(endpoint: 'non-existent.ably.io') } it 'logs error on failed connection attempt' do logger_expectation = lambda do |*args, &block| @@ -1351,7 +1351,7 @@ def self.available_states it "is available when connection is in one of the states: #{available_states.join(', ')}" do connection.once(:connected) do - allow(client).to receive(:endpoint).and_return( + allow(client).to receive(:uri).and_return( URI::Generic.build( scheme: 'wss', host: 'this.host.does.not.exist.com' diff --git a/spec/acceptance/realtime/message_spec.rb b/spec/acceptance/realtime/message_spec.rb index 51d1206e..ee0da559 100644 --- a/spec/acceptance/realtime/message_spec.rb +++ b/spec/acceptance/realtime/message_spec.rb @@ -6,7 +6,7 @@ describe 'Ably::PubSub::Realtime::Channel Message', :event_machine do vary_by_protocol do - let(:default_options) { options.merge(key: api_key, environment: environment, protocol: protocol) } + let(:default_options) { options.merge(key: api_key, endpoint: endpoint, protocol: protocol) } let(:client_options) { default_options } let(:client) do auto_close Ably::Internal.create_realtime_client(client_options) @@ -398,7 +398,7 @@ def publish_and_check_extras(extras) context 'without suitable publishing permissions' do let(:restricted_client) do - auto_close Ably::Internal.create_realtime_client(options.merge(key: restricted_api_key, environment: environment, protocol: protocol, :log_level => :error)) + auto_close Ably::Internal.create_realtime_client(options.merge(key: restricted_api_key, endpoint: endpoint, protocol: protocol, :log_level => :error)) end let(:restricted_channel) { restricted_client.channel("cansubscribe:example") } let(:payload) { 'Test message without permission to publish' } @@ -774,8 +774,8 @@ def publish_and_check_extras(extras) if protocol_message.messages.find { |message| message.name == event_name } EventMachine.add_timer(0.0001) do connection.transition_state_machine :suspended - stub_const 'Ably::FALLBACK_HOSTS', [] - allow(client).to receive(:endpoint).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com')) + allow(client).to receive(:fallback_hosts).and_return([]) + allow(client).to receive(:uri).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com')) end end end @@ -820,7 +820,7 @@ def publish_and_check_extras(extras) end context 'message encoding interoperability' do - let(:client_options) { { key: api_key, environment: environment, protocol: :json } } + let(:client_options) { { key: api_key, endpoint: endpoint, protocol: :json } } let(:channel_name) { "subscribe_send_text-#{random_str}" } fixtures_path = File.expand_path('../../../../core/lib/submodules/ably-common/test-resources/messages-encoding.json', __FILE__) diff --git a/spec/acceptance/realtime/presence_history_spec.rb b/spec/acceptance/realtime/presence_history_spec.rb index a6d27c6b..e472d9ad 100644 --- a/spec/acceptance/realtime/presence_history_spec.rb +++ b/spec/acceptance/realtime/presence_history_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Realtime::Presence, 'history', :event_machine do vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:channel_name) { "persisted:#{random_str(2)}" } diff --git a/spec/acceptance/realtime/presence_spec.rb b/spec/acceptance/realtime/presence_spec.rb index e4e89986..3c2e4308 100644 --- a/spec/acceptance/realtime/presence_spec.rb +++ b/spec/acceptance/realtime/presence_spec.rb @@ -5,7 +5,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol } } let(:client_options) { default_options } let(:anonymous_client) { auto_close Ably::Internal.create_realtime_client(client_options) } diff --git a/spec/acceptance/realtime/push_admin_spec.rb b/spec/acceptance/realtime/push_admin_spec.rb index 1933d281..1bcb74c7 100644 --- a/spec/acceptance/realtime/push_admin_spec.rb +++ b/spec/acceptance/realtime/push_admin_spec.rb @@ -7,7 +7,7 @@ include Ably::Modules::Conversions vary_by_protocol do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol} } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol} } let(:client_options) { default_options } let(:client) do Ably::Internal.create_realtime_client(client_options) @@ -63,7 +63,7 @@ end context 'invalid recipient' do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol, log_level: :fatal } } it 'raises an error after receiving a 40x realtime response' do subject.publish({ invalid_recipient_details: 'foo.bar' }, basic_notification_payload).errback do |error| @@ -74,7 +74,7 @@ end context 'invalid push data' do - let(:default_options) { { key: api_key, environment: environment, protocol: protocol, log_level: :fatal } } + let(:default_options) { { key: api_key, endpoint: endpoint, protocol: protocol, log_level: :fatal } } it 'raises an error after receiving a 40x realtime response' do subject.publish(basic_recipient, { invalid_property_only: true }).errback do |error| @@ -102,7 +102,7 @@ end let!(:publish_stub) do - stub_request(:post, "#{client.rest_client.endpoint}/push/publish"). + stub_request(:post, "#{client.rest_client.uri}/push/publish"). with do |request| expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val') expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case') @@ -135,7 +135,7 @@ 'transportType' => 'ablyChannel', 'channel' => channel, 'ablyKey' => api_key, - 'ablyUrl' => client.rest_client.endpoint.to_s + 'ablyUrl' => client.rest_client.uri.to_s } end let(:notification_payload) do diff --git a/spec/acceptance/realtime/stats_spec.rb b/spec/acceptance/realtime/stats_spec.rb index 7e8bb7fe..d399d6af 100644 --- a/spec/acceptance/realtime/stats_spec.rb +++ b/spec/acceptance/realtime/stats_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Realtime::Client, '#stats', :event_machine do vary_by_protocol do let(:client) do - auto_close Ably::Internal.create_realtime_client(key: api_key, environment: environment, protocol: protocol) + auto_close Ably::Internal.create_realtime_client(key: api_key, endpoint: endpoint, protocol: protocol) end describe 'fetching stats' do diff --git a/spec/acceptance/realtime/time_spec.rb b/spec/acceptance/realtime/time_spec.rb index 106190bd..8b00c7a2 100644 --- a/spec/acceptance/realtime/time_spec.rb +++ b/spec/acceptance/realtime/time_spec.rb @@ -3,7 +3,7 @@ describe Ably::PubSub::Realtime::Client, '#time', :event_machine do vary_by_protocol do let(:client) do - auto_close Ably::Internal.create_realtime_client(key: api_key, environment: environment, protocol: protocol) + auto_close Ably::Internal.create_realtime_client(key: api_key, endpoint: endpoint, protocol: protocol) end describe 'fetching the service time' do @@ -25,7 +25,7 @@ context 'with reconfigured HTTP timeout' do let(:client) do - auto_close Ably::Internal.create_realtime_client(http_request_timeout: 0.0001, key: api_key, environment: environment, protocol: protocol, log_level: :fatal) + auto_close Ably::Internal.create_realtime_client(http_request_timeout: 0.0001, key: api_key, endpoint: endpoint, protocol: protocol, log_level: :fatal) end it 'should raise a timeout exception' do diff --git a/spec/rspec_config.rb b/spec/rspec_config.rb index c299da4a..8cd28b71 100644 --- a/spec/rspec_config.rb +++ b/spec/rspec_config.rb @@ -35,7 +35,7 @@ key_name: 'app_id.key_name', key_secret: 'secret', api_key: 'app_id.key_name:secret', - environment: 'sandbox' + endpoint: 'nonprod:sandbox' )) WebMock.enable! end diff --git a/spec/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index 0db17476..53670049 100644 --- a/spec/shared/client_initializer_behaviour.rb +++ b/spec/shared/client_initializer_behaviour.rb @@ -1,14 +1,6 @@ # encoding: utf-8 shared_examples 'a client initializer' do - def subdomain - if rest? - 'rest' - else - 'realtime' - end - end - def protocol if rest? 'http' @@ -154,38 +146,45 @@ def rest? end end - context 'endpoint' do + context 'endpoint (#REC1)' do before do allow_any_instance_of(subject.class).to receive(:auto_connect).and_return(false) end - it 'defaults to production' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://#{subdomain}.ably.io") + it 'defaults to main' do + expect(subject.endpoint).to eql('main') end - context 'with environment option' do - let(:client_options) { default_options.merge(environment: 'sandbox', auto_connect: false) } + it 'uses the main production primary domain (#REC1a)' do + expect(subject.primary_domain).to eql('main.realtime.ably.net') + expect(subject.uri.to_s).to eql("#{protocol}s://main.realtime.ably.net") + end + + context 'with a routing policy name' do + let(:client_options) { default_options.merge(endpoint: 'acme') } - it 'uses an alternate endpoint' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") + it 'uses the production primary domain for that routing policy (#REC1b4)' do + expect(subject.primary_domain).to eql('acme.realtime.ably.net') + expect(subject.uri.to_s).to eql("#{protocol}s://acme.realtime.ably.net") end end - context 'with rest_host option' do - let(:client_options) { default_options.merge(rest_host: 'custom-rest.host.com', auto_connect: false) } + context 'with a nonprod routing policy name' do + let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox') } - it 'uses an alternate endpoint for REST clients' do - skip 'does not apply as testing a Realtime client' unless rest? - expect(subject.endpoint.to_s).to eql("#{protocol}s://custom-rest.host.com") + it 'uses the non-production primary domain for that routing policy (#REC1b3)' do + expect(subject.primary_domain).to eql('sandbox.realtime.ably-nonprod.net') + expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably-nonprod.net") end end - context 'with realtime_host option' do - let(:client_options) { default_options.merge(realtime_host: 'custom-realtime.host.com', auto_connect: false) } + %w(foo.example.com localhost 127.0.0.1 ::1).each do |hostname| + context "with hostname #{hostname}" do + let(:client_options) { default_options.merge(endpoint: hostname) } - it 'uses an alternate endpoint for Realtime clients' do - skip 'does not apply as testing a REST client' if rest? - expect(subject.endpoint.to_s).to eql("#{protocol}s://custom-realtime.host.com") + it 'uses the hostname as the primary domain (#REC1b2)' do + expect(subject.primary_domain).to eql(hostname) + end end end @@ -193,7 +192,7 @@ def rest? let(:client_options) { default_options.merge(port: 999, tls: false, auto_connect: false) } it 'uses the custom port for non-TLS requests' do - expect(subject.endpoint.to_s).to include(":999") + expect(subject.uri.to_s).to include(":999") end end @@ -201,7 +200,7 @@ def rest? let(:client_options) { default_options.merge(tls_port: 666, tls: true, auto_connect: false) } it 'uses the custom port for TLS requests' do - expect(subject.endpoint.to_s).to include(":666") + expect(subject.uri.to_s).to include(":666") end end end @@ -219,7 +218,7 @@ def rest? end it 'uses HTTP' do - expect(subject.endpoint.to_s).to eql("#{protocol}://#{subdomain}.ably.io") + expect(subject.uri.to_s).to eql("#{protocol}://main.realtime.ably.net") end end @@ -266,133 +265,77 @@ def rest? end end - context 'environment' do - context 'when set without custom fallback hosts configured' do - let(:environment) { 'foo' } - let(:client_options) { default_options.merge(environment: environment) } - let(:default_fallbacks) { %w(a b c d e).map { |id| "#{environment}-#{id}-fallback.ably-realtime.com" } } - - it 'sets the environment attribute' do - expect(subject.environment).to eql(environment) - end - - it 'uses the default fallback hosts (#TBC, see https://github.com/ably/wiki/issues/361)' do - expect(subject.fallback_hosts.sort).to eql(default_fallbacks) - end + context 'fallback hosts (#REC2)' do + before do + allow_any_instance_of(subject.class).to receive(:auto_connect).and_return(false) end - context 'when set with custom fallback hosts configured' do - let(:environment) { 'foo' } - let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } } - let(:client_options) { default_options.merge(environment: environment, fallback_hosts: custom_fallbacks) } - - it 'sets the environment attribute' do - expect(subject.environment).to eql(environment) - end - - it 'uses the custom provided fallback hosts (#RSC15a)' do - expect(subject.fallback_hosts.sort).to eql(custom_fallbacks) - end + it 'defaults to the main production fallback hosts (#REC2c1)' do + expect(subject.fallback_hosts.sort).to eql(%w(a b c d e).map { |id| "main.#{id}.fallback.ably-realtime.com" }) + expect(subject.fallback_hosts.sort).to eql(Ably::FALLBACK_HOSTS) end - context 'when set with fallback_hosts_use_default' do - let(:environment) { 'foo' } - let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } } - let(:default_production_fallbacks) { %w(a b c d e).map { |id| "#{id}.ably-realtime.com" } } - let(:client_options) { default_options.merge(environment: environment, fallback_hosts_use_default: true) } - - it 'sets the environment attribute' do - expect(subject.environment).to eql(environment) - end + context 'with a routing policy name' do + let(:client_options) { default_options.merge(endpoint: 'acme') } - it 'uses the production default fallback hosts (#RTN17b)' do - expect(subject.fallback_hosts.sort).to eql(default_production_fallbacks) + it 'uses the production fallback hosts for that routing policy (#REC2c4)' do + expect(subject.fallback_hosts.sort).to eql(%w(a b c d e).map { |id| "acme.#{id}.fallback.ably-realtime.com" }) end end - end - context 'rest_host' do - context 'when set without custom fallback hosts configured' do - let(:custom_rest_host) { 'foo.com' } - let(:client_options) { default_options.merge(rest_host: custom_rest_host) } + context 'with a nonprod routing policy name' do + let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox') } - it 'sets the custom_host attribute' do - expect(subject.custom_host).to eql(custom_rest_host) - end - - it 'has no default fallback hosts' do - expect(subject.fallback_hosts).to be_empty + it 'uses the non-production fallback hosts for that routing policy (#REC2c3)' do + expect(subject.fallback_hosts.sort).to eql(%w(a b c d e).map { |id| "sandbox.#{id}.fallback.ably-realtime-nonprod.com" }) end end - context 'when set with environment and without custom fallback hosts configured' do - let(:environment) { 'foobar' } - let(:custom_rest_host) { 'foo.com' } - let(:client_options) { default_options.merge(environment: environment, rest_host: custom_rest_host) } - - it 'sets the environment attribute' do - expect(subject.environment).to eql(environment) - end - - it 'sets the custom_host attribute' do - expect(subject.custom_host).to eql(custom_rest_host) - end + %w(foo.example.com localhost 127.0.0.1 ::1).each do |hostname| + context "with hostname #{hostname}" do + let(:client_options) { default_options.merge(endpoint: hostname) } - it 'has no default fallback hosts' do - expect(subject.fallback_hosts).to be_empty + it 'has no default fallback hosts (#REC2c2)' do + expect(subject.fallback_hosts).to be_empty + end end end - context 'when set with custom fallback hosts configured' do - let(:custom_rest_host) { 'foo.com' } - let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } } - let(:client_options) { default_options.merge(rest_host: custom_rest_host, fallback_hosts: custom_fallbacks) } + context 'with custom fallback hosts configured' do + let(:custom_fallbacks) { %w(a b c).map { |id| "#{id}.foo.com" } } - it 'sets the custom_host attribute' do - expect(subject.custom_host).to eql(custom_rest_host) - end + %w(main nonprod:sandbox foo.example.com).each do |endpoint_value| + context "and endpoint #{endpoint_value}" do + let(:client_options) { default_options.merge(endpoint: endpoint_value, fallback_hosts: custom_fallbacks) } - it 'has no default fallback hosts' do - expect(subject.fallback_hosts.sort).to eql(custom_fallbacks) + it 'uses the custom provided fallback hosts (#REC2a2)' do + expect(subject.fallback_hosts.sort).to eql(custom_fallbacks) + end + end end end - end - context 'realtime_host' do - context 'when set without custom fallback hosts configured' do - let(:custom_realtime_host) { 'realtime.foo.com' } - let(:client_options) { default_options.merge(realtime_host: custom_realtime_host) } - - # These tests are shared between realtime & rest clients - # So don't test for the attribute, instead test the options - it 'sets the realtime_host option' do - expect(subject.options[:realtime_host]).to eql(custom_realtime_host) - end + context 'with an empty fallback hosts list' do + let(:client_options) { default_options.merge(fallback_hosts: []) } - it 'has no default fallback hosts' do + it 'has no fallback hosts' do expect(subject.fallback_hosts).to be_empty end end - end - context 'custom port' do - context 'when set without custom fallback hosts configured' do - let(:custom_port) { 555 } - let(:client_options) { default_options.merge(port: custom_port) } + context 'with a custom port' do + let(:client_options) { default_options.merge(port: 555) } - it 'has no default fallback hosts' do - expect(subject.fallback_hosts).to be_empty + it 'uses the default fallback hosts' do + expect(subject.fallback_hosts.sort).to eql(Ably::FALLBACK_HOSTS) end end - end - context 'custom TLS port' do - context 'when set without custom fallback hosts configured' do - let(:custom_port) { 555 } - let(:client_options) { default_options.merge(tls_port: custom_port) } + context 'with a custom TLS port' do + let(:client_options) { default_options.merge(tls_port: 555) } - it 'has no default fallback hosts' do - expect(subject.fallback_hosts).to be_empty + it 'uses the default fallback hosts' do + expect(subject.fallback_hosts.sort).to eql(Ably::FALLBACK_HOSTS) end end end diff --git a/spec/support/api_helper.rb b/spec/support/api_helper.rb index e4455118..5885b93e 100644 --- a/spec/support/api_helper.rb +++ b/spec/support/api_helper.rb @@ -21,8 +21,8 @@ def restricted_api_key TestApp.instance.restricted_api_key end - def environment - TestApp.instance.environment + def endpoint + TestApp.instance.endpoint end def reload_test_app diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 6d79fc47..5679f764 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -57,7 +57,7 @@ def restricted_api_key def delete return unless TestApp.instance_variable_get('@singleton__instance__') - url = "#{sandbox_client.endpoint}/apps/#{app_id}" + url = "#{sandbox_client.uri}/apps/#{app_id}" basic_auth = Base64.urlsafe_encode64(api_key).chomp headers = { "Authorization" => "Basic #{basic_auth}" } @@ -65,12 +65,12 @@ def delete Faraday.delete(url, nil, headers) end - def environment - ENV['ABLY_ENV'] || 'sandbox' + def endpoint + ENV['ABLY_ENDPOINT'] || 'nonprod:sandbox' end def create_test_app - url = "#{sandbox_client.endpoint}/apps" + url = "#{sandbox_client.uri}/apps" headers = { 'Accept' => 'application/json', @@ -82,25 +82,21 @@ def create_test_app @attributes = JSON.parse(response.body) - puts "Test app '#{app_id}' created in #{environment} environment" + puts "Test app '#{app_id}' created using endpoint #{endpoint}" end def host - sandbox_client.endpoint.host - end - - def realtime_host - host.gsub(/rest/, 'realtime') + sandbox_client.primary_domain end def create_test_stats(stats) - client = Ably::Internal.create_http_client(key: api_key, environment: environment) + client = Ably::Internal.create_http_client(key: api_key, endpoint: endpoint) response = client.post('/stats', stats) raise "Could not create stats fixtures. Ably responded with status #{response.status}\n#{response.body}" unless (200..299).include?(response.status) end private def sandbox_client - @sandbox_client ||= Ably::Internal.create_http_client(key: 'app.key:secret', tls: true, environment: environment) + @sandbox_client ||= Ably::Internal.create_http_client(key: 'app.key:secret', tls: true, endpoint: endpoint) end end diff --git a/spec/unit/pubsub/server_spec.rb b/spec/unit/pubsub/server_spec.rb index c114a64f..53f9fbe2 100644 --- a/spec/unit/pubsub/server_spec.rb +++ b/spec/unit/pubsub/server_spec.rb @@ -36,10 +36,10 @@ end context 'with an options hash' do - subject(:client) { Ably::PubSub::Server.create_http_client(key: api_key, environment: 'sandbox') } + subject(:client) { Ably::PubSub::Server.create_http_client(key: api_key, endpoint: 'nonprod:sandbox') } it 'passes the options through and stamps the side' do - expect(client.environment).to eql('sandbox') + expect(client.endpoint).to eql('nonprod:sandbox') expect(client.agent).to end_with(side_entry) end end diff --git a/spec/unit/realtime/client_spec.rb b/spec/unit/realtime/client_spec.rb index 9ccddb83..0a6db726 100644 --- a/spec/unit/realtime/client_spec.rb +++ b/spec/unit/realtime/client_spec.rb @@ -13,13 +13,13 @@ let(:client_options) { { key: 'appid.keyuid:keysecret', auto_connect: false } } it 'passes on the options to the initializer' do - rest_client = instance_double('Ably::PubSub::Http::Client', auth: instance_double('Ably::Auth'), options: client_options, environment: 'production', use_tls?: true, custom_tls_port: nil) + rest_client = instance_double('Ably::PubSub::Http::Client', auth: instance_double('Ably::Auth'), options: client_options, primary_domain: 'main.realtime.ably.net', use_tls?: true, custom_tls_port: nil) expect(Ably::Internal).to receive(:create_http_client).with(hash_including(client_options)).and_return(rest_client) realtime_client end context 'for attribute' do - [:environment, :use_tls?, :log_level, :custom_host].each do |attribute| + [:endpoint, :primary_domain, :use_tls?, :log_level, :custom_port].each do |attribute| specify "##{attribute}" do expect(realtime_client.rest_client).to receive(attribute) realtime_client.public_send attribute diff --git a/spec/unit/realtime/connection_spec.rb b/spec/unit/realtime/connection_spec.rb index 8bf32a92..f24e5bc7 100644 --- a/spec/unit/realtime/connection_spec.rb +++ b/spec/unit/realtime/connection_spec.rb @@ -2,7 +2,7 @@ require 'shared/protocol_msgbus_behaviour' describe Ably::PubSub::Realtime::Connection do - let(:client) { instance_double('Ably::PubSub::Realtime::Client', logger: double('logger').as_null_object, recover: nil, endpoint: double('endpoint', host: 'realtime.ably.io')) } + let(:client) { instance_double('Ably::PubSub::Realtime::Client', logger: double('logger').as_null_object, recover: nil, uri: double('uri', hostname: 'main.realtime.ably.net')) } subject do Ably::PubSub::Realtime::Connection.new(client, {}).tap do |connection|