lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-1.0.6 vs lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-1.1.0

- old
+ new

@@ -2,11 +2,11 @@ require 'spec_helper' require 'webrick' describe Ably::Rest::Client do vary_by_protocol do - let(:default_options) { { environment: environment, protocol: protocol } } + let(:default_options) { { environment: environment, protocol: protocol, log_retries_as_info: true } } let(:client_options) { default_options } let(:client) { Ably::Rest::Client.new(client_options) } http_defaults = Ably::Rest::Client::HTTP_DEFAULTS @@ -25,10 +25,23 @@ it 'uses basic authentication' do expect(client.auth).to be_using_basic_auth end end + context 'with an invalid API key' do + let(:client) { Ably::Rest::Client.new(client_options.merge(key: 'app.key:secret', log_level: :fatal)) } + + 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}) + end + end + end + context 'with an explicit string :token' do let(:client) { Ably::Rest::Client.new(client_options.merge(token: random_str)) } it 'uses token authentication' do expect(client.auth).to be_using_token_auth @@ -708,10 +721,113 @@ client.channel(channel_name).publish('event', 'data') expect(@primary_host_requested).to be_truthy expect(@fallback_request_count).to eql(2) end end + + context 'to fail the primary host, allow a fallback to succeed, then later trigger a fallback to the primary host (#RSC15f)' do + before do + @request_count = 0 + @primary_host_request_count = 0 + @web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null")) + @web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res| + @request_count += 1 + if req.header["host"].first.include?(primary_host) + @primary_host_request_count += 1 + # Fail all requests to the primary host so that a fallback is used + # Except request 6 which should suceed and clear the fallback host preference + if @request_count == 6 + res.status = 200 + res['Content-Type'] = 'application/json' + res.body = '{}' + else + res.status = 500 + end + else + # Fail the second request (first failed fallback of first request) + # Fail the third request on the previously succeeded fallback host to trigger an attempt on the primary host + if [2, 5].include?(@request_count) + res.status = 500 + else + res.status = 200 + res['Content-Type'] = 'application/json' + res.body = '{}' + end + end + end + + Thread.new do + @web_server.start + end + end + + let(:client_options) do + default_options.merge( + rest_host: primary_host, + fallback_hosts: fallbacks, + token: 'fake.token', + port: port, + tls: false, + log_level: :error + ).merge(additional_client_options) + end + + let (:additional_client_options) { {} } + + it 'succeeds and remembers fallback host preferences across requests' do + # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(3) + expect(fallbacks).to include(client.using_preferred_fallback_host?) + successfull_fallback = client.using_preferred_fallback_host? + expect(@primary_host_request_count).to eql(1) + + # Send another request, which should go straight to the fallback as it succeeded previously + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(4) + expect(successfull_fallback).to eql(client.using_preferred_fallback_host?) + expect(@primary_host_request_count).to eql(1) + + # A subsequent request should fail to the fallback, go the primary host and succeed + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(6) + expect(client.using_preferred_fallback_host?).to be_falsey + expect(@primary_host_request_count).to eql(2) + + # A subsequent request will fail on the primary endpoint, and we expect the fallback to be used again + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(8) + expect(fallbacks).to include(client.using_preferred_fallback_host?) + successfull_fallback = client.using_preferred_fallback_host? + expect(@primary_host_request_count).to eql(3) + + # Send another request, which should go straight to the fallback as it succeeded previously + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(9) + expect(successfull_fallback).to eql(client.using_preferred_fallback_host?) + expect(@primary_host_request_count).to eql(3) + end + + context 'with custom :fallback_retry_timeout' do + let (:additional_client_options) { { fallback_retry_timeout: 5 } } + + it 'stops using the preferred fallback after this time' do + # Send a request, expect primary endpoint to fail, one fallback to fail, second fallback to succeed + client.channel(channel_name).publish('event', 'data') + expect(@request_count).to eql(3) + expect(fallbacks).to include(client.using_preferred_fallback_host?) + expect(@primary_host_request_count).to eql(1) + + # Wait for the preferred fallback cache to expire + sleep 5 + + # Send another request, which should go straight to the primary host again as fallback host is expired + client.channel(channel_name).publish('event', 'data') + expect(@primary_host_request_count).to eql(2) + end + end + end end end context 'when environment is not production and server returns a 50x error' do let(:custom_hosts) { %w(A.foo.com B.foo.com) } @@ -722,11 +838,12 @@ let(:production_options) do default_options.merge( environment: env, key: api_key, http_max_retry_duration: max_retry_duration, - http_max_retry_count: max_retry_count + http_max_retry_count: max_retry_count, + log_level: :fatal, ) end let(:status) { 502 } let(:fallback_block) do @@ -749,11 +866,11 @@ let!(:second_fallback_request_stub) do stub_request(:post, "https://#{custom_hosts[1]}#{path}").to_return(&fallback_block) end let(:client_options) { - production_options.merge(fallback_hosts: custom_hosts, log_level: :error) + production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal) } it 'attempts the fallback hosts as this is not an authentication failure' do expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError) expect(default_host_request_stub).to have_been_requested @@ -762,11 +879,11 @@ end end context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do let(:client_options) { - production_options.merge(fallback_hosts: []) + production_options.merge(fallback_hosts: [], log_level: :fatal) } it 'does not attempt the fallback hosts as this is an authentication failure' do expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError) expect(default_host_request_stub).to have_been_requested @@ -787,11 +904,11 @@ let!(:second_fallback_request_stub) do stub_request(:post, "https://#{Ably::FALLBACK_HOSTS[1]}#{path}").to_return(&fallback_block) end let(:client_options) { - production_options.merge(fallback_hosts: custom_hosts, log_level: :error) + production_options.merge(fallback_hosts: custom_hosts, log_level: :fatal) } it 'attempts the default fallback hosts as this is an authentication failure' do expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError) expect(default_host_request_stub).to have_been_requested @@ -964,11 +1081,11 @@ end it 'sends a protocol version and lib version header (#G4, #RSC7a, #RSC7b)' do client.channels.get('foo').publish("event") expect(publish_message_stub).to have_been_requested - expect(Ably::PROTOCOL_VERSION).to eql('1.0') + expect(Ably::PROTOCOL_VERSION).to eql('1.1') end end end end @@ -1082,11 +1199,11 @@ end end end context 'option add_request_ids: true and specified fallback hosts', :webmock do - let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error } } + let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true, log_level: :error, log_retries_as_info: true } } let(:requests) { [] } before do @request_id = nil hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io'] @@ -1138,11 +1255,11 @@ end end context 'failed request logging', :prevent_log_stubbing do let(:custom_logger) { TestLogger.new } - let(:client_options) { default_options.merge(key: api_key, logger: custom_logger) } + let(:client_options) { default_options.merge(key: api_key, logger: custom_logger, log_retries_as_info: false) } it 'is absent when requests do not fail' do client.time expect(custom_logger.logs(min_severity: :warn)).to be_empty end @@ -1151,11 +1268,12 @@ let(:client_options) do default_options.merge( rest_host: 'non.existent.domain.local', fallback_hosts: [[environment, Ably::Rest::Client::DOMAIN].join('-')], key: api_key, - logger: custom_logger) + logger: custom_logger, + log_retries_as_info: false) end it 'is present with success message when requests do not actually fail' do client.time expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to eql(1) @@ -1167,10 +1285,11 @@ let(:client_options) do default_options.merge( rest_host: 'non.existent.domain.local', fallback_hosts: ['non2.existent.domain.local'], key: api_key, - logger: custom_logger) + logger: custom_logger, + log_retries_as_info: false) end it 'is present when all requests fail' do expect { client.time }.to raise_error(Ably::Exceptions::ConnectionError) expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/Retry/) }.length).to be >= 2