diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9cfcd6cc..34b1e30b 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -32,8 +32,9 @@ jobs: run: | mkdir junit bundle exec parallel_rspec --prefix-output-with-test-env-number --first-is-1 -- spec/${{ matrix.type }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: ${{ matrix.ruby }}-${{ matrix.protocol }}-${{ matrix.type }} path: | junit/ coverage/ diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..55801c59 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,2 @@ +AllCops: + DisabledByDefault: true diff --git a/lib/ably/modules/ably.rb b/lib/ably/modules/ably.rb index 0cb5ef9a..1a746dbd 100644 --- a/lib/ably/modules/ably.rb +++ b/lib/ably/modules/ably.rb @@ -4,19 +4,26 @@ # # @see file:README.md README module Ably - # Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to + # Fallback hosts to use when a connection to main.realtime.ably.net 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 # see https://ably.com/docs/client-lib-development-guide/features/#RSC15a # - FALLBACK_DOMAIN = 'ably-realtime.com'.freeze + PROD_FALLBACK_DOMAIN = 'ably-realtime.com'.freeze + NONPROD_FALLBACK_DOMAIN = 'ably-realtime-nonprod.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 + # Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com + FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "main.#{host}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze }.freeze + + # Prod fallback suffixes a.fallback.ably-realtime.com ... e.fallback.ably-realtime.com + PROD_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host| + "#{host}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze + end.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 + # Nonprod fallback suffixes a.fallback.ably-realtime-nonprod.com ... e.fallback.ably-realtime-nonprod.com + NONPROD_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host| + "#{host}.fallback.#{NONPROD_FALLBACK_DOMAIN}".freeze end.freeze INTERNET_CHECK = { diff --git a/lib/ably/realtime/client.rb b/lib/ably/realtime/client.rb index b35aaa94..60e2333b 100644 --- a/lib/ably/realtime/client.rb +++ b/lib/ably/realtime/client.rb @@ -14,8 +14,6 @@ class Client extend Forwardable using Ably::Util::AblyExtensions - DOMAIN = 'realtime.ably.io' - # A {Aby::Realtime::Channels} object. # # @spec RTC3, RTS1 @@ -73,7 +71,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, :environment, :custom_host, :custom_port, :custom_tls_port def_delegators :@rest_client, :log_level def_delegators :@rest_client, :options @@ -289,10 +287,34 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block) end end - # @!attribute [r] endpoint + # @!attribute [r] hostname + # @return [String] The primary hostname to connect to Ably + def hostname + if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost' + return endpoint + end + + if endpoint.start_with?('nonprod:') + "#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}" + else + "#{endpoint}.realtime.#{root_domain}" + end + end + + # @!attribute [r] root_domain + # @return [String] The root domain used in the hostname + def root_domain + if endpoint.start_with?('nonprod:') + 'ably-nonprod.net' + else + 'ably.net' + end + end + + # @!attribute [r] uri # @return [URI::Generic] Default Ably Realtime endpoint used for all requests - def endpoint - endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-')) + def uri + uri_for_host(custom_realtime_host || hostname) end # (see Ably::Rest::Client#register_encoder) @@ -322,8 +344,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 @@ -341,7 +363,8 @@ 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/lib/ably/realtime/connection.rb b/lib/ably/realtime/connection.rb index 6295ef09..653f764b 100644 --- a/lib/ably/realtime/connection.rb +++ b/lib/ably/realtime/connection.rb @@ -173,7 +173,7 @@ def initialize(client, options) @state = STATE(state_machine.current_state) @manager = ConnectionManager.new(self) - @current_host = client.endpoint.host + @current_host = client.uri.host reset_client_msg_serial end @@ -396,12 +396,13 @@ def determine_host @current_host = if internet_is_up_result client.fallback_endpoint.host else - client.endpoint.host + client.uri.host end yield current_host end else - @current_host = client.endpoint.host + # Use hostname from client.uri (#REC1) + @current_host = client.uri.host yield current_host end end @@ -496,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| diff --git a/lib/ably/rest/client.rb b/lib/ably/rest/client.rb index 3dbda2df..c37aeef6 100644 --- a/lib/ably/rest/client.rb +++ b/lib/ably/rest/client.rb @@ -18,9 +18,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 @@ -43,7 +40,11 @@ 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 hostname used to connect to Ably + # @return [String] + attr_reader :endpoint + + # Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment (deprecated) # @return [String] attr_reader :environment @@ -135,7 +136,8 @@ class Client # @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 Specify a routing policy or fully-qualified domain name to connect to Ably + # @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment (deprecated) # @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 @@ -188,8 +190,6 @@ def initialize(options) @agent = options.delete(:agent) || Ably::AGENT @realtime_client = options.delete(:realtime_client) @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 @@ -203,9 +203,14 @@ 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) + @environment = options.delete(:environment) # nil is production + @environment = nil if [:production, 'production'].include?(@environment) + @endpoint = environment || options.delete_with_default(:endpoint, 'main') + 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 @@ -213,8 +218,10 @@ def initialize(options) options_fallback_hosts when custom_host || options[:realtime_host] || custom_port || custom_tls_port [] - when environment - CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" } + when endpoint.start_with?('nonprod:') + NONPROD_FALLBACKS_SUFFIXES.map { |host| "#{endpoint.gsub('nonprod:', '')}.#{host}" } + when endpoint != 'main' + PROD_FALLBACKS_SUFFIXES.map { |host| "#{endpoint}.#{host}" } else Ably::FALLBACK_HOSTS end @@ -426,10 +433,34 @@ def push @push ||= Push.new(self) end - # @!attribute [r] endpoint + # @!attribute [r] hostname + # @return [String] The primary hostname to connect to Ably + def hostname + if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost' + return endpoint + end + + if endpoint.start_with?('nonprod:') + "#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}" + else + "#{endpoint}.realtime.#{root_domain}" + end + end + + # @!attribute [r] root_domain + # @return [String] The root domain used in the hostname + def root_domain + if endpoint.start_with?('nonprod:') + 'ably-nonprod.net' + else + 'ably.net' + end + end + + # @!attribute [r] uri # @return [URI::Generic] Default Ably REST endpoint used for all requests - def endpoint - endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-')) + def uri + uri_for_host(custom_host || hostname) end # @!attribute [r] logger @@ -480,7 +511,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 @@ -493,7 +524,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 @@ -653,7 +684,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/spec/acceptance/realtime/client_spec.rb b/spec/acceptance/realtime/client_spec.rb index b392d118..6c67b855 100644 --- a/spec/acceptance/realtime/client_spec.rb +++ b/spec/acceptance/realtime/client_spec.rb @@ -34,7 +34,7 @@ client.connection.once(:failed) do expect(custom_logger_object.logs.find do |severity, message| next unless %w(fatal error).include?(severity.to_s) - message.match(%r{https://help.ably.io/error/40400}) + message.match(%r{https://help.ably.io/error/40101}) end).to_not be_nil stop_reactor end @@ -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(: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, "#{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, "#{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, "#{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, "#{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 6afb7616..7f4de0cb 100644 --- a/spec/acceptance/realtime/connection_failures_spec.rb +++ b/spec/acceptance/realtime/connection_failures_spec.rb @@ -31,8 +31,8 @@ error = connection_state_change.reason expect(connection.state).to eq(:failed) # TODO: Check error type is an InvalidToken exception - expect(error.status).to eq(404) - expect(error.code).to eq(40400) # not found + expect(error.status).to eq(401) + expect(error.code).to eq(40101) # not found stop_reactor end end @@ -46,7 +46,7 @@ error = connection_state_change.reason expect(connection.state).to eq(:failed) # TODO: Check error type is a TokenNotFound exception - expect(error.status).to eq(401) + expect(error.status).to eq(404) expect(error.code).to eq(40400) # not found stop_reactor end @@ -930,7 +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::Realtime::Client::DOMAIN}" + host = client.hostname expect(hosts.first).to eql(host) expect(hosts.length).to eql(1) stop_reactor @@ -1396,10 +1396,10 @@ def kill_connection_transport_and_prevent_valid_resume channel = client.channels.get("foo") channel.attach do connection.once(:failed) do |state_change| - expect(state_change.reason.code).to eql(40400) - expect(connection.error_reason.code).to eql(40400) + expect(state_change.reason.code).to eql(40101) + expect(connection.error_reason.code).to eql(40101) expect(channel).to be_failed - expect(channel.error_reason.code).to eql(40400) + expect(channel.error_reason.code).to eql(40101) stop_reactor end @@ -1469,13 +1469,13 @@ def kill_connection_transport_and_prevent_valid_resume end context 'with non-production environment' do - let(:environment) { 'sandbox' } - let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" } + let(:environment) { 'nonprod:sandbox' } + let(:expected_host) { "sandbox.realtime.ably-nonprod.net" } let(:client_options) { timeout_options.merge(environment: environment) } context ':fallback_hosts_use_default is unset' 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::NONPROD_FALLBACKS_SUFFIXES.map { |suffix| "#{environment.gsub("nonprod:", "")}.#{suffix}" } + [expected_host] } let(:fallback_hosts_used) { Array.new } it 'uses fallback hosts by default' do @@ -1568,7 +1568,7 @@ def kill_connection_transport_and_prevent_valid_resume stub_const 'Ably::FALLBACK_HOSTS', custom_hosts end - let(:expected_host) { Ably::Realtime::Client::DOMAIN } + let(:expected_host) { 'main.realtime.ably.net' } let(:client_options) { timeout_options.merge(environment: nil) } let(:fallback_hosts_used) { Array.new } diff --git a/spec/acceptance/realtime/connection_spec.rb b/spec/acceptance/realtime/connection_spec.rb index 36ca720f..c4099027 100644 --- a/spec/acceptance/realtime/connection_spec.rb +++ b/spec/acceptance/realtime/connection_spec.rb @@ -28,8 +28,8 @@ end context 'current_host' do - it 'is available immediately after the client is instanced' do - expect(connection.current_host.to_s).to match(/\.ably\.io$/) + it 'is available immediately after the client is instanced (#REC1)' do + expect(connection.current_host.to_s).to match(/\.realtime\.ably[-nonprod]*\.net$/) stop_reactor end end diff --git a/spec/acceptance/realtime/message_spec.rb b/spec/acceptance/realtime/message_spec.rb index b218a8e1..63c8aa35 100644 --- a/spec/acceptance/realtime/message_spec.rb +++ b/spec/acceptance/realtime/message_spec.rb @@ -775,7 +775,7 @@ def publish_and_check_extras(extras) 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(:uri).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com')) end end end diff --git a/spec/acceptance/realtime/push_admin_spec.rb b/spec/acceptance/realtime/push_admin_spec.rb index e9dfba92..1be57968 100644 --- a/spec/acceptance/realtime/push_admin_spec.rb +++ b/spec/acceptance/realtime/push_admin_spec.rb @@ -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/rest/auth_spec.rb b/spec/acceptance/rest/auth_spec.rb index ef07af9f..8e9a7866 100644 --- a/spec/acceptance/rest/auth_spec.rb +++ b/spec/acceptance/rest/auth_spec.rb @@ -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( @@ -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://#{client.hostname}/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://#{client.hostname}/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) diff --git a/spec/acceptance/rest/base_spec.rb b/spec/acceptance/rest/base_spec.rb index 0e046458..12cee653 100644 --- a/spec/acceptance/rest/base_spec.rb +++ b/spec/acceptance/rest/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 @@ -72,13 +72,13 @@ describe 'failed requests' do context 'due to invalid Auth' do - it 'should raise an InvalidRequest exception with a valid error message and code' do + it 'should raise an UnauthorizedRequest exception with a valid error message and code' do invalid_client = Ably::Rest::Client.new(key: 'appid.keyuid:keysecret', environment: environment) expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error| - expect(error).to be_a(Ably::Exceptions::ResourceMissing) - expect(error.message).to match(/No application found/) - expect(error.code).to eql(40400) - expect(error.status).to eql(404) + expect(error).to be_a(Ably::Exceptions::UnauthorizedRequest) + expect(error.message).to match(/no application id/) + expect(error.code).to eql(40101) + expect(error.status).to eql(401) end end end @@ -87,7 +87,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 @@ -100,7 +100,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 @@ -121,7 +121,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, @@ -129,7 +129,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' } } diff --git a/spec/acceptance/rest/channel_spec.rb b/spec/acceptance/rest/channel_spec.rb index 37a4e174..36d15eab 100644 --- a/spec/acceptance/rest/channel_spec.rb +++ b/spec/acceptance/rest/channel_spec.rb @@ -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(: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, "#{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(: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, "#{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/rest/client_spec.rb b/spec/acceptance/rest/client_spec.rb index e1619c6e..dcbdf7ca 100644 --- a/spec/acceptance/rest/client_spec.rb +++ b/spec/acceptance/rest/client_spec.rb @@ -33,9 +33,9 @@ def encode64(text) it 'logs an entry with a help href url matching the code #TI5' do begin client.channels.get('foo').publish('test') - raise 'Expected Ably::Exceptions::ResourceMissing' - rescue Ably::Exceptions::ResourceMissing => err - expect err.to_s.match(%r{https://help.ably.io/error/40400}) + raise 'Expected Ably::Exceptions::UnauthorizedRequest' + rescue Ably::Exceptions::UnauthorizedRequest => err + expect err.to_s.match(%r{https://help.ably.io/error/40101}) end end end @@ -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::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}") + stub_request(:get, "https://#{client.hostname}/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::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}"). + stub_request(:get, "#{http_protocol}://#{client.hostname}/channels/#{channel_name}/messages?#{history_querystring}"). with(headers: { 'Authorization' => "Bearer #{encode64(token_string)}" }). to_return(body: [], headers: { 'Content-Type' => 'application/json' }) end @@ -301,20 +301,20 @@ def encode64(text) context 'configured' do let(:client_options) { default_options.merge(key: api_key, environment: 'production') } - 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.b.fallback.ably-realtime.com, main.c.fallback.ably-realtime.com, main.d.fallback.ably-realtime.com, main.e.fallback.ably-realtime.com (#RSC15a, #REC1)' 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(main.a.fallback.ably-realtime.com main.b.fallback.ably-realtime.com main.c.fallback.ably-realtime.com main.d.fallback.ably-realtime.com main.e.fallback.ably-realtime.com)) end end context 'when environment is NOT production (#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(environment: 'nonprod:sandbox', key: api_key, fallback_hosts: []) } let!(:default_host_request_stub) do - stub_request(:post, "https://#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://#{client.hostname}#{path}").to_return do raise Faraday::TimeoutError.new('timeout error message') end end @@ -325,20 +325,20 @@ 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(environment: '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.b.fallback.ably-realtime-nonprod.com, sandbox.c.fallback.ably-realtime-nonprod.com, sandbox.d.fallback.ably-realtime-nonprod.com, sandbox.e.fallback.ably-realtime-nonprod.com (#RSC15a, #REC1b3)' 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) } + let(:custom_hosts) { %w(main.a.fallback.ably-realtime.com main.b.fallback.ably-realtime.com) } let(:max_retry_count) { 2 } let(:max_retry_duration) { 0.5 } let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } } @@ -366,7 +366,7 @@ def encode64(text) context 'and connection times out' do let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://#{client.hostname}#{path}").to_return do raise Faraday::TimeoutError.new('timeout error message') end end @@ -380,7 +380,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::Rest::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://#{client.hostname}#{path}").to_return do sleep max_retry_duration * 1.5 raise Faraday::TimeoutError.new('timeout error message') end @@ -397,7 +397,7 @@ def encode64(text) context 'and connection fails' do let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://#{client.hostname}#{path}").to_return do raise Faraday::ConnectionFailed.new('connection failure error message') end end @@ -422,7 +422,7 @@ def encode64(text) end let(:requests) { [] } let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return do + stub_request(:post, "https://#{client.hostname}#{path}").to_return do requests << true if requests.count == 1 raise Faraday::ConnectionFailed.new('connection failure error message') @@ -450,7 +450,7 @@ def encode64(text) context 'and basic authentication fails' do let(:status) { 401 } let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return( + stub_request(:post, "https://#{client.hostname}#{path}").to_return( headers: { 'Content-Type' => 'application/json' }, status: status, body: { @@ -482,7 +482,7 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://#{client.hostname}#{path}").to_return(&fallback_block) end it 'attempts the fallback hosts as this is an authentication failure (#RSC15d)' do @@ -518,7 +518,7 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://#{client.hostname}#{path}").to_return(&fallback_block) end context 'with custom fallback hosts provided' do @@ -554,8 +554,8 @@ def encode64(text) end context 'using a local web-server', webmock: false do - let(:primary_host) { 'local-rest.ably.io' } - let(:fallbacks) { ['local.ably.io', 'localhost'] } + let(:primary_host) { 'local.realtime.ably.net' } + let(:fallbacks) { ['localhost.ably.net', 'localhost'] } let(:port) { rand(10000) + 2000 } let(:channel_name) { 'foo' } let(:request_timeout) { 3 } @@ -838,7 +838,7 @@ def encode64(text) 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" } } + let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}.#{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 } @@ -863,7 +863,7 @@ def encode64(text) end end let!(:default_host_request_stub) do - stub_request(:post, "https://#{env}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block) + stub_request(:post, "https://#{client.hostname}#{path}").to_return(&fallback_block) end context 'with no fallback hosts provided (#TBC, see https://github.com/ably/wiki/issues/361)' do @@ -1086,7 +1086,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 +1113,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(:uri) { client.uri } context 'get' do it 'returns an HttpPaginatedResponse object' do @@ -1156,7 +1156,7 @@ def encode64(text) context 'post', :webmock do before do - stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). + stub_request(:delete, "#{uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1168,14 +1168,14 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:post, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:post, uri, {}, { content: 'x' * Ably::Rest::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, "#{uri}/push/channelSubscriptions?deviceId=#{device_id}"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1190,7 +1190,7 @@ def encode64(text) let(:body_params) { { 'metadata' => { 'key' => 'value' } } } before do - stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:patch, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1203,7 +1203,7 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:patch, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:patch, uri, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end @@ -1219,7 +1219,7 @@ def encode64(text) end before do - stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:put, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1232,7 +1232,7 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:put, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:put, uri, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end @@ -1246,7 +1246,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 +1268,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' }) @@ -1312,7 +1312,7 @@ def encode64(text) 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'] @@ -1374,7 +1374,7 @@ def encode64(text) let(:client_options) do default_options.merge( rest_host: 'non.existent.domain.local', - fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')], + fallback_hosts: ["sandbox.realtime.ably-nonprod.net"], key: api_key, logger: custom_logger, log_retries_as_info: false) diff --git a/spec/acceptance/rest/message_spec.rb b/spec/acceptance/rest/message_spec.rb index 49960226..5cb15307 100644 --- a/spec/acceptance/rest/message_spec.rb +++ b/spec/acceptance/rest/message_spec.rb @@ -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: ["#{environment.gsub("nonprod:", "")}-realtime.ably.io"]) } context 'when there is a network failure triggering an automatic retry (#RSL1k4)' do def mock_for_two_publish_failures diff --git a/spec/acceptance/rest/presence_spec.rb b/spec/acceptance/rest/presence_spec.rb index b0c61c8e..bd76f460 100644 --- a/spec/acceptance/rest/presence_spec.rb +++ b/spec/acceptance/rest/presence_spec.rb @@ -68,12 +68,12 @@ limit: 100 } end - let(:endpoint) do - client.endpoint + let(: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, "#{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 } @@ -115,12 +115,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(: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, "#{uri}/channels/#{channel_name_encoded}/presence?limit=100"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -197,8 +197,8 @@ let(:presence) { client.channel(channel_name).presence } let(:user) { 'appid.keyuid' } let(:secret) { random_str(8) } - let(:endpoint) do - client.endpoint + let(:uri) do + client.uri end let(:client) do Ably::Rest::Client.new(key: "#{user}:#{secret}") @@ -213,7 +213,7 @@ 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, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -253,7 +253,7 @@ } 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, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -322,8 +322,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(:uri) do + client.uri end let(:client) do Ably::Rest::Client.new(client_options.merge(key: "#{user}:#{secret}")) @@ -357,7 +357,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, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type }) } @@ -374,7 +374,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, "#{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 }) } @@ -404,7 +404,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, "#{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 } @@ -428,7 +428,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, "#{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/rest/push_admin_spec.rb b/spec/acceptance/rest/push_admin_spec.rb index a5e1433f..36824fa4 100644 --- a/spec/acceptance/rest/push_admin_spec.rb +++ b/spec/acceptance/rest/push_admin_spec.rb @@ -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/rspec_config.rb b/spec/rspec_config.rb index c299da4a..c498afc6 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' + environment: 'nonprod:sandbox' )) WebMock.enable! end diff --git a/spec/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index 84ff2df4..0bdec092 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,130 @@ def rest? end end - context 'endpoint' do + context 'uri' 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") + expect(subject.uri.to_s).to eql("#{protocol}s://main.realtime.ably.net") + end + + context 'with endpoint option' do + context 'specifying a routing policy id (REC1b4)' do + let(:client_options) { default_options.merge(endpoint: 'test', auto_connect: false) } + + it 'returns the routing policy uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://test.realtime.ably.net") + end + end + + context 'specifying a nonprod routing policy id (REC1b3)' do + let(:client_options) { default_options.merge(endpoint: 'nonprod:test', auto_connect: false) } + + it 'returns the nonprod routing policy uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://test.realtime.ably-nonprod.net") + end + end + + context 'specifying a uri (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: 'example.com', auto_connect: false) } + + it 'returns the uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://example.com") + end + end + + context 'specifying an IPv4 address (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: '127.0.0.1', auto_connect: false) } + + it 'returns the IP address' do + expect(subject.uri.to_s).to eql("#{protocol}s://127.0.0.1") + end + end + + context 'specifying an IPv6 address (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: '::1', auto_connect: false) } + + it 'returns the IP address' do + expect(subject.uri.to_s).to eql("#{protocol}s://[::1]") + end + end + + context 'specifying localhost (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: 'localhost', auto_connect: false) } + + it 'returns localhost' do + expect(subject.uri.to_s).to eql("#{protocol}s://localhost") + end + end end context 'with environment option' do - let(:client_options) { default_options.merge(environment: 'sandbox', auto_connect: false) } + context 'specifying a routing policy id (REC1b4)' do + let(:client_options) { default_options.merge(endpoint: 'test', auto_connect: false) } + + it 'returns the routing policy uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://test.realtime.ably.net") + end + end + + context 'specifying a nonprod routing policy id (REC1b3)' do + let(:client_options) { default_options.merge(endpoint: 'nonprod:test', auto_connect: false) } + + it 'returns the nonprod routing policy uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://test.realtime.ably-nonprod.net") + end + end + + context 'specifying a uri (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: 'example.com', auto_connect: false) } + + it 'returns the uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://example.com") + end + end - it 'uses an alternate endpoint' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") + context 'specifying an IPv4 address (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: '127.0.0.1', auto_connect: false) } + + it 'returns the IP address' do + expect(subject.uri.to_s).to eql("#{protocol}s://127.0.0.1") + end + end + + context 'specifying an IPv6 address (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: '::1', auto_connect: false) } + + it 'returns the IP address' do + expect(subject.uri.to_s).to eql("#{protocol}s://[::1]") + end + end + + context 'specifying localhost (REC1b2)' do + let(:client_options) { default_options.merge(endpoint: 'localhost', auto_connect: false) } + + it 'returns localhost' do + expect(subject.uri.to_s).to eql("#{protocol}s://localhost") + end end end context 'with rest_host option' do let(:client_options) { default_options.merge(rest_host: 'custom-rest.host.com', auto_connect: false) } - it 'uses an alternate endpoint for REST clients' do + it 'uses an alternate uri 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") + expect(subject.uri.to_s).to eql("#{protocol}s://custom-rest.host.com") end end context 'with realtime_host option' do let(:client_options) { default_options.merge(realtime_host: 'custom-realtime.host.com', auto_connect: false) } - it 'uses an alternate endpoint for Realtime clients' do + it 'uses an alternate uri 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") + expect(subject.uri.to_s).to eql("#{protocol}s://custom-realtime.host.com") end end @@ -193,7 +277,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 +285,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 +303,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,17 +350,62 @@ def rest? end end + context 'endpoint' do + context 'when set without custom fallback hosts configured' do + let(:endpoint) { 'foo' } + let(:client_options) { default_options.merge(endpoint: endpoint) } + let(:default_fallbacks) { %w(a b c d e).map { |id| "#{endpoint}.#{id}.fallback.ably-realtime.com" } } + + it 'sets the endpoint attribute' do + expect(subject.endpoint).to eql(endpoint) + end + + it 'uses the default fallback hosts (#REC2c1)' do + expect(subject.fallback_hosts.sort).to eql(default_fallbacks) + end + end + + context 'when set with custom fallback hosts configured' do + let(:endpoint) { 'foo' } + let(:custom_fallbacks) { %w(a b c).map { |id| "#{endpoint}-#{id}.foo.com" } } + let(:client_options) { default_options.merge(endpoint: endpoint, fallback_hosts: custom_fallbacks) } + + it 'sets the endpoint attribute' do + expect(subject.endpoint).to eql(endpoint) + end + + it 'uses the custom provided fallback hosts (#REC2a2)' do + expect(subject.fallback_hosts.sort).to eql(custom_fallbacks) + end + end + + context 'when set with fallback_hosts_use_default' do + let(:endpoint) { 'foo' } + let(:custom_fallbacks) { %w(a b c).map { |id| "#{endpoint}-#{id}.foo.com" } } + let(:default_production_fallbacks) { %w(a b c d e).map { |id| "main.#{id}.fallback.ably-realtime.com" } } + let(:client_options) { default_options.merge(endpoint: endpoint, fallback_hosts_use_default: true) } + + it 'sets the endpoint attribute' do + expect(subject.endpoint).to eql(endpoint) + end + + it 'uses the production default fallback hosts (#REC2b)' do + expect(subject.fallback_hosts.sort).to eql(default_production_fallbacks) + end + 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" } } + 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 + it 'uses the default fallback hosts (#REC2c1)' do expect(subject.fallback_hosts.sort).to eql(default_fallbacks) end end @@ -290,7 +419,7 @@ def rest? expect(subject.environment).to eql(environment) end - it 'uses the custom provided fallback hosts (#RSC15a)' do + it 'uses the custom provided fallback hosts (#REC2a2)' do expect(subject.fallback_hosts.sort).to eql(custom_fallbacks) end end @@ -298,14 +427,14 @@ def rest? 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(:default_production_fallbacks) { %w(a b c d e).map { |id| "main.#{id}.fallback.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 - it 'uses the production default fallback hosts (#RTN17b)' do + it 'uses the production default fallback hosts (#REC2b)' do expect(subject.fallback_hosts.sort).to eql(default_production_fallbacks) end end diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 2e7fc394..e1bf93ae 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}" } @@ -66,11 +66,11 @@ def delete end def environment - ENV['ABLY_ENV'] || 'sandbox' + ENV['ABLY_ENV'] || 'nonprod:sandbox' end def create_test_app - url = "#{sandbox_client.endpoint}/apps" + url = "#{sandbox_client.uri}/apps" headers = { 'Accept' => 'application/json', @@ -86,7 +86,7 @@ def create_test_app end def host - sandbox_client.endpoint.host + sandbox_client.uri.host end def realtime_host @@ -94,12 +94,13 @@ def realtime_host end def create_test_stats(stats) - client = Ably::Rest::Client.new(key: api_key, environment: environment) + client = Ably::Rest::Client.new(key: api_key, endpoint: environment) 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::Rest::Client.new(key: 'app.key:secret', tls: true, environment: environment) end diff --git a/spec/unit/models/http_paginated_result_spec.rb b/spec/unit/models/http_paginated_result_spec.rb index 1c4ac199..cb8ef1bc 100644 --- a/spec/unit/models/http_paginated_result_spec.rb +++ b/spec/unit/models/http_paginated_result_spec.rb @@ -23,7 +23,7 @@ status: status }) end - let(:base_url) { 'http://rest.ably.io/channels/channel_name' } + let(:base_url) { 'http://main.realtime.ably.net/channels/channel_name' } let(:full_url) { "#{base_url}/whatever?param=exists" } let(:paginated_result_options) { Hash.new } let(:first_paged_request) { paginated_result_class.new(http_response, full_url, client, paginated_result_options) } @@ -193,7 +193,7 @@ end context 'with paged http response' do - let(:base_url) { 'http://rest.ably.io/channels/channel_name' } + let(:base_url) { 'http://main.realtime.ably.net/channels/channel_name' } let(:full_url) { "#{base_url}/messages" } let(:headers) do { diff --git a/spec/unit/realtime/client_spec.rb b/spec/unit/realtime/client_spec.rb index 48eb81c7..afb70841 100644 --- a/spec/unit/realtime/client_spec.rb +++ b/spec/unit/realtime/client_spec.rb @@ -13,7 +13,7 @@ let(:client_options) { { key: 'appid.keyuid:keysecret', auto_connect: false } } it 'passes on the options to the initializer' do - rest_client = instance_double('Ably::Rest::Client', auth: instance_double('Ably::Auth'), options: client_options, environment: 'production', use_tls?: true, custom_tls_port: nil) + rest_client = instance_double('Ably::Rest::Client', auth: instance_double('Ably::Auth'), options: client_options, environment: 'production', use_tls?: true, custom_tls_port: nil, endpoint: 'main') expect(Ably::Rest::Client).to receive(:new).with(hash_including(client_options)).and_return(rest_client) realtime_client end @@ -25,6 +25,11 @@ realtime_client.public_send attribute end end + + specify "#endpoint (#REC1)" do + expect(realtime_client.rest_client).to receive(:endpoint) + realtime_client.endpoint + end end end diff --git a/spec/unit/realtime/connection_spec.rb b/spec/unit/realtime/connection_spec.rb index 9c227af0..60aa9599 100644 --- a/spec/unit/realtime/connection_spec.rb +++ b/spec/unit/realtime/connection_spec.rb @@ -2,7 +2,8 @@ require 'shared/protocol_msgbus_behaviour' describe Ably::Realtime::Connection do - let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, endpoint: double('endpoint', host: 'realtime.ably.io')) } + # Using main.realtime.ably.net as the default hostname (#REC1) + let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, uri: double('uri', host: 'main.realtime.ably.net')) } subject do Ably::Realtime::Connection.new(client, {}).tap do |connection|