From 6010b478df0ab5edd97686f2e315bc791f790358 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 17:38:58 +0000 Subject: [PATCH 1/7] Disable Rubocop Rubocop needs to be configured for this repository, but is currently quite a bit of work to get it up to standards[1]. While we get to actually enabling Rubocop, it would be nice to be explicit that it's not something that my editor should bother me with when working in this repository. [1] https://github.com/ably/ably-ruby/issues/139 --- .rubocop.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .rubocop.yml 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 From 181e0b193389ea682df1642b1d5bb52824d39ce0 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Fri, 31 Jan 2025 13:28:14 +0000 Subject: [PATCH 2/7] Update upload-artifact action v3 is deprecated. --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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/ From d797bfe4151746ea97a8a990d75f77b16f72e91d Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Thu, 20 Mar 2025 16:18:44 +0000 Subject: [PATCH 3/7] Rename `endpoint` to `uri` The `endpoint` naming is going to be used as a client option, so we need to rename everywhere this is currently used to avoid conflation. --- lib/ably/realtime/client.rb | 11 ++++---- lib/ably/realtime/connection.rb | 10 +++---- lib/ably/rest/client.rb | 10 +++---- spec/acceptance/realtime/client_spec.rb | 10 +++---- spec/acceptance/realtime/message_spec.rb | 2 +- spec/acceptance/realtime/push_admin_spec.rb | 4 +-- spec/acceptance/rest/auth_spec.rb | 10 +++---- spec/acceptance/rest/base_spec.rb | 10 +++---- spec/acceptance/rest/channel_spec.rb | 10 +++---- spec/acceptance/rest/client_spec.rb | 22 +++++++-------- spec/acceptance/rest/presence_spec.rb | 30 ++++++++++----------- spec/acceptance/rest/push_admin_spec.rb | 4 +-- spec/shared/client_initializer_behaviour.rb | 22 +++++++-------- spec/support/test_app.rb | 6 ++--- spec/unit/realtime/connection_spec.rb | 2 +- 15 files changed, 82 insertions(+), 81 deletions(-) diff --git a/lib/ably/realtime/client.rb b/lib/ably/realtime/client.rb index b35aaa94..11815e91 100644 --- a/lib/ably/realtime/client.rb +++ b/lib/ably/realtime/client.rb @@ -291,8 +291,8 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block) # @!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('-')) + def uri + uri_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-')) end # (see Ably::Rest::Client#register_encoder) @@ -322,8 +322,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 +341,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..82ddb599 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,12 @@ 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 + @current_host = client.uri.host yield current_host end end @@ -496,8 +496,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..faa23fc3 100644 --- a/lib/ably/rest/client.rb +++ b/lib/ably/rest/client.rb @@ -428,8 +428,8 @@ def push # @!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('-')) + def uri + uri_for_host(custom_host || [@environment, DOMAIN].compact.join('-')) end # @!attribute [r] logger @@ -480,7 +480,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 +493,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 +653,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..ce26dd30 100644 --- a/spec/acceptance/realtime/client_spec.rb +++ b/spec/acceptance/realtime/client_spec.rb @@ -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/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..6b34e8f1 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( diff --git a/spec/acceptance/rest/base_spec.rb b/spec/acceptance/rest/base_spec.rb index 0e046458..4a8ff749 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 @@ -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..22872ac6 100644 --- a/spec/acceptance/rest/client_spec.rb +++ b/spec/acceptance/rest/client_spec.rb @@ -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' }) 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/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index 84ff2df4..c495a05f 100644 --- a/spec/shared/client_initializer_behaviour.rb +++ b/spec/shared/client_initializer_behaviour.rb @@ -154,38 +154,38 @@ 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://#{subdomain}.ably.io") end context 'with environment option' do let(:client_options) { default_options.merge(environment: 'sandbox', auto_connect: false) } - it 'uses an alternate endpoint' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") + it 'uses an alternate uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") 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 +193,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 +201,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 +219,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}://#{subdomain}.ably.io") end end diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 2e7fc394..9e5cf0dc 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}" } @@ -70,7 +70,7 @@ def environment 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 diff --git a/spec/unit/realtime/connection_spec.rb b/spec/unit/realtime/connection_spec.rb index 9c227af0..96cf5edd 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::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')) } + let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, uri: double('uri', host: 'realtime.ably.io'))) } subject do Ably::Realtime::Connection.new(client, {}).tap do |connection| From b11b31db2633efb8376866fd9246b9c0a9f4e46e Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Thu, 20 Mar 2025 16:25:04 +0000 Subject: [PATCH 4/7] Use endpoint as default connection option (ADR-119) This implements ADR-119[1], which specifies the client connection options to update requests to the endpoints implemented as part of ADR-042[2]. The endpoint may be one of the following: * a routing policy name (such as `main`) * a nonprod routing policy name (such as `nonprod:sandbox`) * a FQDN such as `foo.example.com` The endpoint option is not valid with any of environment, restHost or realtimeHost, but we still intend to support the legacy options. If the client has been configured to use any of these legacy options, then they should continue to work in the same way, using the same primary and fallback hostnames. If the client has not been explicitly configured, then the hostnames will change to the new `ably.net` domain when the package is upgraded. [1] https://ably.atlassian.net/wiki/spaces/ENG/pages/3428810778/ADR-119+ClientOptions+for+new+DNS+structure [2] https://ably.atlassian.net/wiki/spaces/ENG/pages/1791754276/ADR-042+DNS+Restructure --- lib/ably/modules/ably.rb | 21 ++++-- lib/ably/realtime/client.rb | 32 ++++++-- lib/ably/rest/client.rb | 53 +++++++++++--- .../realtime/connection_failures_spec.rb | 10 +-- spec/acceptance/realtime/connection_spec.rb | 2 +- spec/acceptance/rest/auth_spec.rb | 4 +- spec/acceptance/rest/client_spec.rb | 46 ++++++------ spec/acceptance/rest/message_spec.rb | 2 +- spec/rspec_config.rb | 2 +- spec/shared/client_initializer_behaviour.rb | 73 +++++++++++++++---- spec/support/test_app.rb | 5 +- .../unit/models/http_paginated_result_spec.rb | 4 +- spec/unit/realtime/client_spec.rb | 4 +- spec/unit/realtime/connection_spec.rb | 2 +- 14 files changed, 183 insertions(+), 77 deletions(-) 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 11815e91..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 uri - uri_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-')) + uri_for_host(custom_realtime_host || hostname) end # (see Ably::Rest::Client#register_encoder) diff --git a/lib/ably/rest/client.rb b/lib/ably/rest/client.rb index faa23fc3..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 uri - uri_for_host(custom_host || [@environment, DOMAIN].compact.join('-')) + uri_for_host(custom_host || hostname) end # @!attribute [r] logger diff --git a/spec/acceptance/realtime/connection_failures_spec.rb b/spec/acceptance/realtime/connection_failures_spec.rb index 6afb7616..699e5b87 100644 --- a/spec/acceptance/realtime/connection_failures_spec.rb +++ b/spec/acceptance/realtime/connection_failures_spec.rb @@ -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 @@ -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..ef8199d2 100644 --- a/spec/acceptance/realtime/connection_spec.rb +++ b/spec/acceptance/realtime/connection_spec.rb @@ -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_s).to match(/\.realtime\.ably[-nonprod]*\.net$/) stop_reactor end end diff --git a/spec/acceptance/rest/auth_spec.rb b/spec/acceptance/rest/auth_spec.rb index 6b34e8f1..8e9a7866 100644 --- a/spec/acceptance/rest/auth_spec.rb +++ b/spec/acceptance/rest/auth_spec.rb @@ -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/client_spec.rb b/spec/acceptance/rest/client_spec.rb index 22872ac6..5573cb8b 100644 --- a/spec/acceptance/rest/client_spec.rb +++ b/spec/acceptance/rest/client_spec.rb @@ -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)' 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.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 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 @@ -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/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 c495a05f..ba8d2fd5 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' @@ -160,14 +152,22 @@ def rest? end it 'defaults to production' do - expect(subject.uri.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 + let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox', auto_connect: false) } + + it 'uses an alternate uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably-nonprod.net") + end end context 'with environment option' do - let(:client_options) { default_options.merge(environment: 'sandbox', auto_connect: false) } + let(:client_options) { default_options.merge(environment: 'nonprod:sandbox', auto_connect: false) } it 'uses an alternate uri' do - expect(subject.uri.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") + expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably-nonprod.net") end end @@ -219,7 +219,7 @@ def rest? end it 'uses HTTP' do - expect(subject.uri.to_s).to eql("#{protocol}://#{subdomain}.ably.io") + expect(subject.uri.to_s).to eql("#{protocol}://main.realtime.ably.net") end end @@ -266,11 +266,56 @@ 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 (#TBC, see https://github.com/ably/wiki/issues/361)' 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 (#RSC15a)' 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 (#RTN17b)' 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) @@ -298,7 +343,7 @@ 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 diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 9e5cf0dc..e1bf93ae 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -66,7 +66,7 @@ def delete end def environment - ENV['ABLY_ENV'] || 'sandbox' + ENV['ABLY_ENV'] || 'nonprod:sandbox' end def create_test_app @@ -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..5b613a6d 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::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 context 'for attribute' do - [:environment, :use_tls?, :log_level, :custom_host].each do |attribute| + [:endpoint, :environment, :use_tls?, :log_level, :custom_host].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 96cf5edd..08e66733 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::Realtime::Connection do - let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, uri: double('uri', host: 'realtime.ably.io'))) } + 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| From f8eba189fbf72fa196614fd7af034d43e71d689e Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Tue, 11 Feb 2025 18:21:01 +0000 Subject: [PATCH 5/7] Update expected return The expected result has changed in this test, as per this internal Slack thread[1]. [1] https://ably-real-time.slack.com/archives/CURL4U2FP/p1733479167314059?thread_ts=1733420816.469159&cid=CURL4U2FP --- spec/acceptance/realtime/client_spec.rb | 2 +- spec/acceptance/realtime/connection_failures_spec.rb | 12 ++++++------ spec/acceptance/rest/base_spec.rb | 10 +++++----- spec/acceptance/rest/client_spec.rb | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/acceptance/realtime/client_spec.rb b/spec/acceptance/realtime/client_spec.rb index ce26dd30..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 diff --git a/spec/acceptance/realtime/connection_failures_spec.rb b/spec/acceptance/realtime/connection_failures_spec.rb index 699e5b87..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 @@ -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 diff --git a/spec/acceptance/rest/base_spec.rb b/spec/acceptance/rest/base_spec.rb index 4a8ff749..12cee653 100644 --- a/spec/acceptance/rest/base_spec.rb +++ b/spec/acceptance/rest/base_spec.rb @@ -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 diff --git a/spec/acceptance/rest/client_spec.rb b/spec/acceptance/rest/client_spec.rb index 5573cb8b..6a31b943 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 From 5b136ddd6e17a76f624a0a370aa8a576258397bb Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Thu, 3 Apr 2025 11:24:39 +0100 Subject: [PATCH 6/7] Update spec references --- lib/ably/realtime/connection.rb | 1 + spec/acceptance/realtime/connection_spec.rb | 2 +- spec/acceptance/rest/client_spec.rb | 4 ++-- spec/shared/client_initializer_behaviour.rb | 12 ++++++------ spec/unit/realtime/client_spec.rb | 7 ++++++- spec/unit/realtime/connection_spec.rb | 1 + 6 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/ably/realtime/connection.rb b/lib/ably/realtime/connection.rb index 82ddb599..653f764b 100644 --- a/lib/ably/realtime/connection.rb +++ b/lib/ably/realtime/connection.rb @@ -401,6 +401,7 @@ def determine_host yield current_host end else + # Use hostname from client.uri (#REC1) @current_host = client.uri.host yield current_host end diff --git a/spec/acceptance/realtime/connection_spec.rb b/spec/acceptance/realtime/connection_spec.rb index ef8199d2..c4099027 100644 --- a/spec/acceptance/realtime/connection_spec.rb +++ b/spec/acceptance/realtime/connection_spec.rb @@ -28,7 +28,7 @@ end context 'current_host' do - it 'is available immediately after the client is instanced' do + 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 diff --git a/spec/acceptance/rest/client_spec.rb b/spec/acceptance/rest/client_spec.rb index 6a31b943..dcbdf7ca 100644 --- a/spec/acceptance/rest/client_spec.rb +++ b/spec/acceptance/rest/client_spec.rb @@ -301,7 +301,7 @@ def encode64(text) context 'configured' do let(:client_options) { default_options.merge(key: api_key, environment: 'production') } - 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)' 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 @@ -327,7 +327,7 @@ def encode64(text) context 'and no custom fallback hosts are provided' do 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 diff --git a/spec/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index ba8d2fd5..aef00812 100644 --- a/spec/shared/client_initializer_behaviour.rb +++ b/spec/shared/client_initializer_behaviour.rb @@ -276,7 +276,7 @@ def rest? expect(subject.endpoint).to eql(endpoint) 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 +290,7 @@ def rest? expect(subject.endpoint).to eql(endpoint) 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 @@ -305,7 +305,7 @@ def rest? expect(subject.endpoint).to eql(endpoint) 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 @@ -321,7 +321,7 @@ def rest? 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 @@ -335,7 +335,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 @@ -350,7 +350,7 @@ def rest? 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/unit/realtime/client_spec.rb b/spec/unit/realtime/client_spec.rb index 5b613a6d..afb70841 100644 --- a/spec/unit/realtime/client_spec.rb +++ b/spec/unit/realtime/client_spec.rb @@ -19,12 +19,17 @@ end context 'for attribute' do - [:endpoint, :environment, :use_tls?, :log_level, :custom_host].each do |attribute| + [:environment, :use_tls?, :log_level, :custom_host].each do |attribute| specify "##{attribute}" do expect(realtime_client.rest_client).to receive(attribute) 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 08e66733..60aa9599 100644 --- a/spec/unit/realtime/connection_spec.rb +++ b/spec/unit/realtime/connection_spec.rb @@ -2,6 +2,7 @@ require 'shared/protocol_msgbus_behaviour' describe Ably::Realtime::Connection do + # 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 From c5a93254437612c1ce9773f52827ca6ef0f5e8e3 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Thu, 22 May 2025 10:31:18 +0100 Subject: [PATCH 7/7] Update tests for REC1b2 --- spec/shared/client_initializer_behaviour.rb | 96 +++++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/spec/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index aef00812..0bdec092 100644 --- a/spec/shared/client_initializer_behaviour.rb +++ b/spec/shared/client_initializer_behaviour.rb @@ -156,18 +156,102 @@ def rest? end context 'with endpoint option' do - let(:client_options) { default_options.merge(endpoint: 'nonprod:sandbox', auto_connect: false) } + context 'specifying a routing policy id (REC1b4)' do + let(:client_options) { default_options.merge(endpoint: 'test', auto_connect: false) } - it 'uses an alternate uri' do - expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably-nonprod.net") + 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: 'nonprod: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 + + 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 'uses an alternate uri' do - expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably-nonprod.net") + it 'returns localhost' do + expect(subject.uri.to_s).to eql("#{protocol}s://localhost") + end end end