lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-1.0.5 vs lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-1.0.6
- old
+ new
@@ -63,14 +63,14 @@
it 'raises an exception' do
expect { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: '*')) }.to raise_error ArgumentError
end
end
- context 'with an :auth_callback Proc' do
- let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
+ context 'with an :auth_callback lambda' do
+ let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: lambda { |token_params| token_request })) }
- it 'calls the auth Proc to get a new token' do
+ it 'calls the auth lambda to get a new token' do
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
expect(client.auth.current_token_details.client_id).to eql(client_id)
end
it 'uses token authentication' do
@@ -91,12 +91,12 @@
client.auth.authorize
expect(client.auth.client_id).to eql('bob')
end
end
- context 'with an :auth_callback Proc (clientId provided in library options instead of as a token_request param)' do
- let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback: Proc.new { token_request })) }
+ context 'with an :auth_callback lambda (clientId provided in library options instead of as a token_request param)' do
+ let(:client) { Ably::Rest::Client.new(client_options.merge(client_id: client_id, auth_callback: lambda { |token_params| token_request })) }
let(:token_request) { client.auth.create_token_request({}, key_name: key_name, key_secret: key_secret) }
it 'correctly sets the clientId on the token' do
expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
expect(client.auth.current_token_details.client_id).to eql(client_id)
@@ -176,11 +176,11 @@
end
end
context 'using tokens' do
let(:client) do
- Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new do
+ Ably::Rest::Client.new(client_options.merge(auth_callback: lambda do |token_params|
@request_index ||= 0
@request_index += 1
send("token_request_#{@request_index > 2 ? 'next' : @request_index}")
end))
end
@@ -288,11 +288,11 @@
end
end
context 'fallback hosts', :webmock do
let(:path) { '/channels/test/publish' }
- let(:publish_block) { proc { client.channel('test').publish('event', 'data') } }
+ let(:publish_block) { lambda { client.channel('test').publish('event', 'data') } }
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
@@ -319,11 +319,11 @@
context 'when environment is production' do
let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
let(:max_retry_count) { 2 }
let(:max_retry_duration) { 0.5 }
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
let(:client_options) do
default_options.merge(
environment: nil,
key: api_key,
http_max_retry_duration: max_retry_duration,
@@ -452,11 +452,11 @@
end
context 'and server returns a 50x error' do
let(:status) { 502 }
let(:fallback_block) do
- Proc.new do
+ proc do
{
headers: { 'Content-Type' => 'text/html' },
status: status
}
end
@@ -476,11 +476,11 @@
context 'when environment is production and server returns a 50x error' do
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
let(:max_retry_count) { 2 }
let(:max_retry_duration) { 0.5 }
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
let(:production_options) do
default_options.merge(
environment: nil,
key: api_key,
http_max_retry_duration: max_retry_duration,
@@ -488,11 +488,11 @@
)
end
let(:status) { 502 }
let(:fallback_block) do
- Proc.new do
+ proc do
{
headers: { 'Content-Type' => 'text/html' },
status: status
}
end
@@ -545,74 +545,129 @@
end
context 'and timing out the primary host' do
before do
@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|
- if req.header["host"].first.include?(primary_host)
- @primary_host_requested = true
- sleep request_timeout + 0.5
- else
- @fallback_request_count ||= 0
- @fallback_request_count += 1
- if @fallback_request_count <= fail_fallback_request_count
+ request_handler = lambda do |result_body|
+ lambda do |req, res|
+ host = req.header["host"].first
+ if host.include?(primary_host)
+ @primary_host_request_count ||= 0
+ @primary_host_request_count += 1
sleep request_timeout + 0.5
else
- res.status = 200
- res['Content-Type'] = 'application/json'
- res.body = '{}'
+ @fallback_request_count ||= 0
+ @fallback_request_count += 1
+ @fallback_hosts_tried ||= []
+ @fallback_hosts_tried.push(host)
+ if @fallback_request_count <= fail_fallback_request_count
+ sleep request_timeout + 0.5
+ else
+ res.status = 200
+ res['Content-Type'] = 'application/json'
+ res.body = result_body
+ end
end
end
end
+ @web_server.mount_proc "/time", &request_handler.call('[1000000000000]')
+ @web_server.mount_proc "/channels/#{channel_name}/publish", &request_handler.call('{}')
Thread.new do
@web_server.start
end
end
- context 'with request timeout less than max_retry_duration' do
+ context 'POST with request timeout less than max_retry_duration' do
let(:client_options) do
default_options.merge(
rest_host: primary_host,
fallback_hosts: fallbacks,
token: 'fake.token',
port: port,
tls: false,
http_request_timeout: request_timeout,
- max_retry_duration: request_timeout * 3,
+ http_max_retry_duration: request_timeout * 2.5,
log_level: :error
)
end
let(:fail_fallback_request_count) { 1 }
- it 'tries one of the fallback hosts (#RSC15d)' do
+ it 'tries the primary host, then both fallback hosts (#RSC15d)' do
client.channel(channel_name).publish('event', 'data')
- expect(@primary_host_requested).to be_truthy
+ expect(@primary_host_request_count).to eql(1)
expect(@fallback_request_count).to eql(2)
+ expect(@fallback_hosts_tried.uniq.length).to eql(2)
end
end
- context 'with request timeout less than max_retry_duration' do
+ context 'GET with request timeout less than max_retry_duration' do
let(:client_options) do
default_options.merge(
rest_host: primary_host,
fallback_hosts: fallbacks,
token: 'fake.token',
port: port,
tls: false,
http_request_timeout: request_timeout,
- max_retry_duration: request_timeout / 2,
+ http_max_retry_duration: request_timeout * 2.5,
log_level: :error
)
end
+ let(:fail_fallback_request_count) { 1 }
+
+ it 'tries the primary host, then both fallback hosts (#RSC15d)' do
+ client.time
+ expect(@primary_host_request_count).to eql(1)
+ expect(@fallback_request_count).to eql(2)
+ expect(@fallback_hosts_tried.uniq.length).to eql(2)
+ end
+ end
+
+ context 'POST with request timeout more than max_retry_duration' do
+ let(:client_options) do
+ default_options.merge(
+ rest_host: primary_host,
+ fallback_hosts: fallbacks,
+ token: 'fake.token',
+ port: port,
+ tls: false,
+ http_request_timeout: request_timeout,
+ http_max_retry_duration: request_timeout / 2,
+ log_level: :error
+ )
+ end
let(:fail_fallback_request_count) { 0 }
- it 'tries one of the fallback hosts (#RSC15d)' do
- client.channel(channel_name).publish('event', 'data')
- expect(@primary_host_requested).to be_truthy
- expect(@fallback_request_count).to eql(1)
+ it 'does not try any fallback hosts (#RSC15d)' do
+ expect { client.channel(channel_name).publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionTimeout
+ expect(@primary_host_request_count).to eql(1)
+ expect(@fallback_request_count).to be_nil
end
end
+
+ context 'GET with request timeout more than max_retry_duration' do
+ let(:client_options) do
+ default_options.merge(
+ rest_host: primary_host,
+ fallback_hosts: fallbacks,
+ token: 'fake.token',
+ port: port,
+ tls: false,
+ http_request_timeout: request_timeout,
+ http_max_retry_duration: request_timeout / 2,
+ log_level: :error
+ )
+ end
+ let(:fail_fallback_request_count) { 0 }
+
+ it 'does not try any fallback hosts (#RSC15d)' do
+ expect { client.time }.to raise_error Ably::Exceptions::ConnectionTimeout
+ expect(@primary_host_request_count).to eql(1)
+ expect(@fallback_request_count).to be_nil
+ end
+ end
+
end
context 'and failing the primary host' do
before do
@web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false, :AccessLog => [], Logger: WEBrick::Log.new("/dev/null"))
@@ -660,11 +715,11 @@
context 'when environment is not production and server returns a 50x error' do
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
let(:max_retry_count) { 2 }
let(:max_retry_duration) { 0.5 }
- let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
+ let(:fallback_block) { proc { raise Faraday::SSLError.new('ssl error message') } }
let(:env) { 'custom-env' }
let(:production_options) do
default_options.merge(
environment: env,
key: api_key,
@@ -673,11 +728,11 @@
)
end
let(:status) { 502 }
let(:fallback_block) do
- Proc.new do
+ proc do
{
headers: { 'Content-Type' => 'text/html' },
status: status
}
end
@@ -960,57 +1015,80 @@
end
end
context 'request_id generation' do
context 'Timeout error' do
- context 'with request_id', :webmock do
- let(:custom_logger) do
- Class.new do
- def initialize
- @messages = []
- end
-
- [:fatal, :error, :warn, :info, :debug].each do |severity|
- define_method severity do |message, &block|
- message_val = [message]
- message_val << block.call if block
-
- @messages << [severity, message_val.compact.join(' ')]
- end
- end
-
- def logs
- @messages
- end
-
- def level
- 1
- end
-
- def level=(new_level)
- end
- end
- end
- let(:custom_logger_object) { custom_logger.new }
+ context 'with option add_request_ids: true', :webmock, :prevent_log_stubbing do
+ let(:custom_logger_object) { TestLogger.new }
let(:client_options) { default_options.merge(key: api_key, logger: custom_logger_object, add_request_ids: true) }
+
before do
@request_id = nil
stub_request(:get, Addressable::Template.new("#{client.endpoint}/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')
end
end
+
it 'has an error with the same request_id of the request' do
- expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
+ expect { client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
+ expect(@request_id).to be_a(String)
+ expect(@request_id).to_not be_empty
expect(custom_logger_object.logs.find { |severity, message| message.match(/#{@request_id}/i)} ).to_not be_nil
end
end
- context 'when specifying fallback hosts', :webmock do
- let(:client_options) { { key: api_key, fallback_hosts_use_default: true, add_request_ids: true } }
+ context 'with option add_request_ids: true and REST operations with a message body' do
+ let(:client_options) { default_options.merge({ key: api_key, add_request_ids: true }) }
+ let(:channel_name) { random_str }
+ let(:channel) { client.channels.get(channel_name) }
+
+ 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}")).
+ with do |request|
+ @request_id = request.uri.query_values['request_id']
+ end.to_return(:status => 200, :body => [], :headers => { 'Content-Type' => 'application/json' })
+ end
+
+ context 'with a single publish' do
+ it 'succeeds and sends the request_id as a param' do
+ channel.publish('name', { body: random_str })
+ expect(@request_id.to_s).to_not be_empty
+ end
+ end
+
+ context 'with an array publish' do
+ it 'succeeds and sends the request_id as a param' do
+ channel.publish([{ body: random_str }, { body: random_str }])
+ expect(@request_id.to_s).to_not be_empty
+ end
+ end
+ end
+
+ context 'without mocks to ensure the requests are accepted' do
+ context 'with a single publish' do
+ it 'succeeds and sends the request_id as a param' do
+ channel.publish('name', { body: random_str })
+ expect(channel.history.items.length).to eql(1)
+ end
+ end
+
+ context 'with an array publish' do
+ it 'succeeds and sends the request_id as a param' do
+ channel.publish([{ body: random_str }, { body: random_str }])
+ expect(channel.history.items.length).to eql(2)
+ end
+ 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(:requests) { [] }
+
before do
@request_id = nil
hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io']
hosts.each do |host|
stub_request(:get, Addressable::Template.new("https://#{host.downcase}/time{?request_id}")).with do |request|
@@ -1019,39 +1097,86 @@
end.to_return do
raise Faraday::TimeoutError.new('timeout error message')
end
end
end
- it 'request_id is the same across retries' do
+
+ specify 'request_id is the same across retries' do
expect{ client.time }.to raise_error(Ably::Exceptions::ConnectionTimeout, /#{@request_id}/)
+ expect(@request_id).to be_a(String)
+ expect(@request_id).to_not be_empty
expect(requests.uniq.count).to eql(1)
expect(requests.uniq.first).to eql(@request_id)
end
end
context 'without request_id' do
let(:client_options) { default_options.merge(key: api_key, http_request_timeout: 0) }
+
it 'does not include request_id in ConnectionTimeout error' do
begin
client.stats
rescue Ably::Exceptions::ConnectionTimeout => err
expect(err.request_id).to eql(nil)
end
end
end
- end
+ end
context 'UnauthorizedRequest nonce error' do
let(:token_params) { { nonce: "samenonce_#{protocol}", timestamp: Time.now.to_i } }
+
it 'includes request_id in UnauthorizedRequest error due to replayed nonce' do
client1 = Ably::Rest::Client.new(default_options.merge(key: api_key))
client2 = Ably::Rest::Client.new(default_options.merge(key: api_key, add_request_ids: true))
expect { client1.auth.request_token(token_params) }.not_to raise_error
begin
client2.auth.request_token(token_params)
rescue Ably::Exceptions::UnauthorizedRequest => err
expect(err.request_id).to_not eql(nil)
end
+ end
+ 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) }
+
+ it 'is absent when requests do not fail' do
+ client.time
+ expect(custom_logger.logs(min_severity: :warn)).to be_empty
+ end
+
+ context 'with the first request failing' do
+ 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)
+ 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)
+ expect(custom_logger.logs(min_severity: :warn).select { |severity, msg| msg.match(/SUCCEEDED/) }.length).to eql(1)
+ end
+ end
+
+ context 'with all requests failing' do
+ 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)
+ 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
+ expect(custom_logger.logs(min_severity: :error).select { |severity, msg| msg.match(/FAILED/) }.length).to eql(1)
end
end
end
end
end