lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-0.8.2 vs lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb in ably-rest-0.8.3
- old
+ new
@@ -8,36 +8,134 @@
let(:client) { Ably::Rest::Client.new(client_options) }
connection_retry = Ably::Rest::Client::CONNECTION_RETRY
+ def encode64(text)
+ Base64.encode64(text).gsub("\n", '')
+ end
+
context '#initialize' do
let(:client_id) { random_str }
let(:token_request) { client.auth.create_token_request(key_name: key_name, key_secret: key_secret, client_id: client_id) }
- context 'with a :auth_callback Proc' do
+ context 'with only an API key' do
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key)) }
+
+ it 'uses basic authentication' do
+ expect(client.auth).to be_using_basic_auth
+ 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
+ end
+ end
+
+ context 'with :use_token_auth set to true' do
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, use_token_auth: true)) }
+
+ it 'uses token authentication' do
+ expect(client.auth).to be_using_token_auth
+ end
+ end
+
+ context 'with a :client_id configured' do
+ let(:client) { Ably::Rest::Client.new(client_options.merge(key: api_key, client_id: random_str)) }
+
+ it 'uses token authentication' do
+ expect(client.auth).to be_using_token_auth
+ end
+ end
+
+ context 'with an :auth_callback Proc' do
let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
it 'calls the auth Proc 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
+ expect(client.auth).to be_using_token_auth
+ end
end
context 'with an auth URL' do
- let(:client_options) { default_options.merge(auth_url: token_request_url, auth_method: :get) }
+ let(:client_options) { default_options.merge(key: api_key, auth_url: token_request_url, auth_method: :get) }
let(:token_request_url) { 'http://get.token.request.com/' }
- before do
- allow(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, :auth_method => :get).and_return(token_request)
+ it 'uses token authentication' do
+ expect(client.auth).to be_using_token_auth
end
- it 'sends an HTTP request to the provided URL 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)
+ context 'before any REST request' do
+ before do
+ expect(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, hash_including(:auth_method => :get)).once do
+ client.auth.create_token_request(token_params: { client_id: client_id })
+ end
+ end
+
+ it 'sends an HTTP request to the provided auth URL 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
end
end
+
+ context 'auth headers', webmock: true do
+ let(:channel_name) { random_str }
+ let(:history_params) { { 'direction' => 'backwards', 'limit' => 100 } }
+ let(:history_querystring) { history_params.map { |k, v| "#{k}=#{v}" }.join("&") }
+
+ context 'with basic auth', webmock: true do
+ let(:client_options) { default_options.merge(key: api_key) }
+
+ let!(:get_message_history_stub) do
+ stub_request(:get, "https://#{api_key}@#{environment}-#{Ably::Rest::Client::DOMAIN}/channels/#{channel_name}/messages?#{history_querystring}").
+ to_return(body: [], headers: { 'Content-Type' => 'application/json' })
+ end
+
+ it 'sends the API key in authentication part of the secure URL (the Authorization: Basic header is not used with the Faraday HTTP library by default)' do
+ client.channel(channel_name).history history_params
+ expect(get_message_history_stub).to have_been_requested
+ end
+ end
+
+ context 'with token auth', webmock: true do
+ let(:token_string) { random_str }
+ 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}").
+ with(headers: { 'Authorization' => "Bearer #{encode64(token_string)}" }).
+ to_return(body: [], headers: { 'Content-Type' => 'application/json' })
+ end
+
+ context 'without specifying protocol' do
+ let(:http_protocol) { 'https' }
+
+ it 'sends the token string over HTTPS in the Authorization Bearer header with Base64 encoding' do
+ client.channel(channel_name).history history_params
+ expect(get_message_history_stub).to have_been_requested
+ end
+ end
+
+ context 'when setting constructor ClientOption :tls to false' do
+ let(:client_options) { default_options.merge(token: token_string, tls: false) }
+ let(:http_protocol) { 'http' }
+
+ it 'sends the token string over HTTP in the Authorization Bearer header with Base64 encoding' do
+ client.channel(channel_name).history history_params
+ expect(get_message_history_stub).to have_been_requested
+ end
+ end
+ end
+ end
end
context 'using tokens' do
let(:client) do
Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new do
@@ -51,12 +149,17 @@
# If token expires against whilst runnig tests in a slower CI environment then use this token
let(:token_request_next) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
context 'when expired' do
- let(:token_request_options) { { key_name: key_name, key_secret: key_secret, ttl: Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER } }
+ before do
+ # Ensure tokens issued expire immediately after issue
+ stub_const 'Ably::Auth::TOKEN_DEFAULTS', Ably::Auth::TOKEN_DEFAULTS.merge(renew_token_buffer: 0)
+ end
+ let(:token_request_options) { { key_name: key_name, key_secret: key_secret, token_params: { ttl: Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER } } }
+
it 'creates a new token automatically when the old token expires' 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(token_request_1.client_id)
sleep 1
@@ -128,19 +231,20 @@
raise Faraday::TimeoutError.new('timeout error message')
end
end
it 'does not retry failed requests with fallback hosts when there is a connection error' do
- expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
end
end
context 'when environment is production' do
let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
let(:max_attempts) { 2 }
let(:cumulative_timeout) { 0.5 }
let(:client_options) { default_options.merge(environment: nil, key: api_key) }
+ let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
before do
stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
stub_const 'Ably::Rest::Client::CONNECTION_RETRY', {
single_request_open_timeout: 4,
@@ -149,19 +253,15 @@
max_retry_attempts: max_attempts
}
end
let!(:first_fallback_request_stub) do
- stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return do
- raise Faraday::SSLError.new('ssl error message')
- end
+ stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return(&fallback_block)
end
let!(:second_fallback_request_stub) do
- stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return do
- raise Faraday::SSLError.new('ssl error message')
- end
+ stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return(&fallback_block)
end
context 'and connection times out' do
let!(:default_host_request_stub) do
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
@@ -183,11 +283,11 @@
raise Faraday::TimeoutError.new('timeout error message')
end
end
it 'makes no further attempts to any fallback hosts' do
- expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeout
expect(default_host_request_stub).to have_been_requested
expect(first_fallback_request_stub).to_not have_been_requested
expect(second_fallback_request_stub).to_not have_been_requested
end
end
@@ -205,10 +305,56 @@
expect(default_host_request_stub).to have_been_requested
expect(first_fallback_request_stub).to have_been_requested
expect(second_fallback_request_stub).to have_been_requested
end
end
+
+ context 'and basic authentication fails' do
+ let(:status) { 401 }
+ let!(:default_host_request_stub) do
+ stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return(
+ headers: { 'Content-Type' => 'application/json' },
+ status: status,
+ body: {
+ "error" => {
+ "statusCode" => 401,
+ "code" => 40101,
+ "message" => "Invalid credentials"
+ }
+ }.to_json
+ )
+ end
+
+ it 'does not attempt the fallback hosts as this is an authentication failure' do
+ expect { publish_block.call }.to raise_error(Ably::Exceptions::InvalidRequest)
+ expect(default_host_request_stub).to have_been_requested
+ expect(first_fallback_request_stub).to_not have_been_requested
+ expect(second_fallback_request_stub).to_not have_been_requested
+ end
+ end
+
+ context 'and server returns a 50x error' do
+ let(:status) { 502 }
+ let(:fallback_block) do
+ Proc.new do
+ {
+ headers: { 'Content-Type' => 'text/html' },
+ status: status
+ }
+ end
+ end
+ let!(:default_host_request_stub) do
+ stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
+ end
+
+ it 'attempts 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
+ expect(first_fallback_request_stub).to have_been_requested
+ expect(second_fallback_request_stub).to have_been_requested
+ end
+ end
end
end
context 'with a custom host' do
let(:custom_host) { 'host.does.not.exist' }
@@ -251,11 +397,11 @@
raise Faraday::TimeoutError.new('timeout error message')
end
end
it 'fails immediately and raises a Faraday Error' do
- expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
+ expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeout
end
context 'fallback hosts' do
before do
Ably::FALLBACK_HOSTS.each do |host|
@@ -264,13 +410,29 @@
end
end
end
specify 'are never used' do
- expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
+ expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeout
expect(custom_host_request_stub).to have_been_requested
end
end
+ end
+ end
+
+ context '#auth' do
+ let(:dummy_auth_url) { 'http://dummy.url' }
+ let(:unique_ttl) { 1234 }
+ let(:client_options) { default_options.merge(auth_url: dummy_auth_url, ttl: unique_ttl) }
+
+
+ it 'is provides access to the Auth object' do
+ expect(client.auth).to be_kind_of(Ably::Auth)
+ end
+
+ it 'configures the Auth object with all ClientOptions passed to client in the initializer' do
+ expect(client.auth.options[:ttl]).to eql(unique_ttl)
+ expect(client.auth.options[:auth_url]).to eql(dummy_auth_url)
end
end
end
end