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