This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Adapter::Test do
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new do |stub|
|
||||
stub.get('http://domain.test/hello') do
|
||||
[200, { 'Content-Type' => 'text/html' }, 'domain: hello']
|
||||
end
|
||||
|
||||
stub.get('http://wrong.test/hello') do
|
||||
[200, { 'Content-Type' => 'text/html' }, 'wrong: hello']
|
||||
end
|
||||
|
||||
stub.get('http://wrong.test/bait') do
|
||||
[404, { 'Content-Type' => 'text/html' }]
|
||||
end
|
||||
|
||||
stub.get('/hello') do
|
||||
[200, { 'Content-Type' => 'text/html' }, 'hello']
|
||||
end
|
||||
|
||||
stub.get('/method-echo') do |env|
|
||||
[200, { 'Content-Type' => 'text/html' }, env[:method].to_s]
|
||||
end
|
||||
|
||||
stub.get(%r{\A/resources/\d+(?:\?|\z)}) do
|
||||
[200, { 'Content-Type' => 'text/html' }, 'show']
|
||||
end
|
||||
|
||||
stub.get(%r{\A/resources/(specified)\z}) do |_env, meta|
|
||||
[200, { 'Content-Type' => 'text/html' }, "show #{meta[:match_data][1]}"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:connection) do
|
||||
Faraday.new do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
end
|
||||
|
||||
let(:response) { connection.get('/hello') }
|
||||
|
||||
context 'with simple path sets status' do
|
||||
subject { response.status }
|
||||
|
||||
it { is_expected.to eq 200 }
|
||||
end
|
||||
|
||||
context 'with simple path sets headers' do
|
||||
subject { response.headers['Content-Type'] }
|
||||
|
||||
it { is_expected.to eq 'text/html' }
|
||||
end
|
||||
|
||||
context 'with simple path sets body' do
|
||||
subject { response.body }
|
||||
|
||||
it { is_expected.to eq 'hello' }
|
||||
end
|
||||
|
||||
context 'with host points to the right stub' do
|
||||
subject { connection.get('http://domain.test/hello').body }
|
||||
|
||||
it { is_expected.to eq 'domain: hello' }
|
||||
end
|
||||
|
||||
describe 'can be called several times' do
|
||||
subject { connection.get('/hello').body }
|
||||
|
||||
it { is_expected.to eq 'hello' }
|
||||
end
|
||||
|
||||
describe 'can handle regular expression path' do
|
||||
subject { connection.get('/resources/1').body }
|
||||
|
||||
it { is_expected.to eq 'show' }
|
||||
end
|
||||
|
||||
describe 'can handle single parameter block' do
|
||||
subject { connection.get('/method-echo').body }
|
||||
|
||||
it { is_expected.to eq 'get' }
|
||||
end
|
||||
|
||||
describe 'can handle regular expression path with captured result' do
|
||||
subject { connection.get('/resources/specified').body }
|
||||
|
||||
it { is_expected.to eq 'show specified' }
|
||||
end
|
||||
|
||||
context 'with get params' do
|
||||
subject { connection.get('/param?a=1').body }
|
||||
|
||||
before do
|
||||
stubs.get('/param?a=1') { [200, {}, 'a'] }
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
describe 'ignoring unspecified get params' do
|
||||
before do
|
||||
stubs.get('/optional?a=1') { [200, {}, 'a'] }
|
||||
end
|
||||
|
||||
context 'with multiple params' do
|
||||
subject { connection.get('/optional?a=1&b=1').body }
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
context 'with single param' do
|
||||
subject { connection.get('/optional?a=1').body }
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
context 'without params' do
|
||||
subject(:request) { connection.get('/optional') }
|
||||
|
||||
it do
|
||||
expect { request }.to raise_error(
|
||||
Faraday::Adapter::Test::Stubs::NotFound
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with http headers' do
|
||||
before do
|
||||
stubs.get('/yo', 'X-HELLO' => 'hello') { [200, {}, 'a'] }
|
||||
stubs.get('/yo') { [200, {}, 'b'] }
|
||||
end
|
||||
|
||||
context 'with header' do
|
||||
subject do
|
||||
connection.get('/yo') { |env| env.headers['X-HELLO'] = 'hello' }.body
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
context 'without header' do
|
||||
subject do
|
||||
connection.get('/yo').body
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'b' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'different outcomes for the same request' do
|
||||
def make_request
|
||||
connection.get('/foo')
|
||||
end
|
||||
|
||||
subject(:request) { make_request.body }
|
||||
|
||||
before do
|
||||
stubs.get('/foo') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
|
||||
stubs.get('/foo') { [200, { 'Content-Type' => 'text/html' }, 'world'] }
|
||||
end
|
||||
|
||||
context 'the first request' do
|
||||
it { is_expected.to eq 'hello' }
|
||||
end
|
||||
|
||||
context 'the second request' do
|
||||
before do
|
||||
make_request
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'world' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'yielding env to stubs' do
|
||||
subject { connection.get('http://foo.com/foo?a=1').body }
|
||||
|
||||
before do
|
||||
stubs.get '/foo' do |env|
|
||||
expect(env[:url].path).to eq '/foo'
|
||||
expect(env[:url].host).to eq 'foo.com'
|
||||
expect(env[:params]['a']).to eq '1'
|
||||
expect(env[:request_headers]['Accept']).to eq 'text/plain'
|
||||
[200, {}, 'a']
|
||||
end
|
||||
|
||||
connection.headers['Accept'] = 'text/plain'
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
describe 'params parsing' do
|
||||
subject { connection.get('http://foo.com/foo?a[b]=1').body }
|
||||
|
||||
context 'with default encoder' do
|
||||
before do
|
||||
stubs.get '/foo' do |env|
|
||||
expect(env[:params]['a']['b']).to eq '1'
|
||||
[200, {}, 'a']
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
context 'with nested encoder' do
|
||||
before do
|
||||
stubs.get '/foo' do |env|
|
||||
expect(env[:params]['a']['b']).to eq '1'
|
||||
[200, {}, 'a']
|
||||
end
|
||||
|
||||
connection.options.params_encoder = Faraday::NestedParamsEncoder
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
|
||||
context 'with flat encoder' do
|
||||
before do
|
||||
stubs.get '/foo' do |env|
|
||||
expect(env[:params]['a[b]']).to eq '1'
|
||||
[200, {}, 'a']
|
||||
end
|
||||
|
||||
connection.options.params_encoder = Faraday::FlatParamsEncoder
|
||||
end
|
||||
|
||||
it { is_expected.to eq 'a' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'raising an error if no stub was found' do
|
||||
describe 'for request' do
|
||||
subject(:request) { connection.get('/invalid') { [200, {}, []] } }
|
||||
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
|
||||
describe 'for specified host' do
|
||||
subject(:request) { connection.get('http://domain.test/bait') }
|
||||
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
|
||||
describe 'for request without specified header' do
|
||||
subject(:request) { connection.get('/yo') }
|
||||
|
||||
before do
|
||||
stubs.get('/yo', 'X-HELLO' => 'hello') { [200, {}, 'a'] }
|
||||
end
|
||||
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'for request with non default params encoder' do
|
||||
let(:connection) do
|
||||
Faraday.new(request: { params_encoder: Faraday::FlatParamsEncoder }) do |builder|
|
||||
builder.adapter :test, stubs
|
||||
end
|
||||
end
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new do |stubs|
|
||||
stubs.get('/path?a=x&a=y&a=z') { [200, {}, 'a'] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when all flat param values are correctly set' do
|
||||
subject(:request) { connection.get('/path?a=x&a=y&a=z') }
|
||||
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
|
||||
shared_examples 'raise NotFound when params do not satisfy the flat param values' do |params|
|
||||
subject(:request) { connection.get('/path', params) }
|
||||
|
||||
context "with #{params.inspect}" do
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x] }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x y] }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { a: %w[x z y] } # NOTE: The order of the value is also compared.
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the flat param values', { b: %w[x y z] }
|
||||
end
|
||||
|
||||
describe 'strict_mode' do
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new(strict_mode: true) do |stubs|
|
||||
stubs.get('/strict?a=12&b=xy', 'Authorization' => 'Bearer m_ck', 'X-C' => 'hello') { [200, {}, 'a'] }
|
||||
stubs.get('/with_user_agent?a=12&b=xy', authorization: 'Bearer m_ck', 'User-Agent' => 'My Agent') { [200, {}, 'a'] }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when params and headers are exactly set' do
|
||||
subject(:request) { connection.get('/strict', { a: '12', b: 'xy' }, { authorization: 'Bearer m_ck', x_c: 'hello' }) }
|
||||
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
|
||||
context 'when params and headers are exactly set with a custom user agent' do
|
||||
subject(:request) { connection.get('/with_user_agent', { a: '12', b: 'xy' }, { authorization: 'Bearer m_ck', 'User-Agent' => 'My Agent' }) }
|
||||
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
|
||||
shared_examples 'raise NotFound when params do not satisfy the strict check' do |params|
|
||||
subject(:request) { connection.get('/strict', params, { 'Authorization' => 'Bearer m_ck', 'X-C' => 'hello' }) }
|
||||
|
||||
context "with #{params.inspect}" do
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { a: '12' }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { b: 'xy' }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { a: '123', b: 'xy' }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { a: '12', b: 'xyz' }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { a: '12', b: 'xy', c: 'hello' }
|
||||
it_behaves_like 'raise NotFound when params do not satisfy the strict check', { additional: 'special', a: '12', b: 'xy', c: 'hello' }
|
||||
|
||||
shared_examples 'raise NotFound when headers do not satisfy the strict check' do |path, headers|
|
||||
subject(:request) { connection.get(path, { a: 12, b: 'xy' }, headers) }
|
||||
|
||||
context "with #{headers.inspect}" do
|
||||
it { expect { request }.to raise_error described_class::Stubs::NotFound }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/strict', { 'X-C' => 'hello' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck', 'x-c': 'Hi' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/strict', { authorization: 'Basic m_ck', 'x-c': 'hello' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck', 'x-c': 'hello', x_special: 'special' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'Unknown' }
|
||||
it_behaves_like 'raise NotFound when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent', x_special: 'special' }
|
||||
|
||||
context 'when strict_mode is disabled' do
|
||||
before do
|
||||
stubs.strict_mode = false
|
||||
end
|
||||
|
||||
shared_examples 'does not raise NotFound even when params do not satisfy the strict check' do |params|
|
||||
subject(:request) { connection.get('/strict', params, { 'Authorization' => 'Bearer m_ck', 'X-C' => 'hello' }) }
|
||||
|
||||
context "with #{params.inspect}" do
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'does not raise NotFound even when params do not satisfy the strict check', { a: '12', b: 'xy' }
|
||||
it_behaves_like 'does not raise NotFound even when params do not satisfy the strict check', { a: '12', b: 'xy', c: 'hello' }
|
||||
it_behaves_like 'does not raise NotFound even when params do not satisfy the strict check', { additional: 'special', a: '12', b: 'xy', c: 'hello' }
|
||||
|
||||
shared_examples 'does not raise NotFound even when headers do not satisfy the strict check' do |path, headers|
|
||||
subject(:request) { connection.get(path, { a: 12, b: 'xy' }, headers) }
|
||||
|
||||
context "with #{headers.inspect}" do
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck', 'x-c': 'hello' }
|
||||
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck', 'x-c': 'hello', x_special: 'special' }
|
||||
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/strict', { authorization: 'Bearer m_ck', 'x-c': 'hello', user_agent: 'Special Agent' }
|
||||
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent' }
|
||||
it_behaves_like 'does not raise NotFound even when headers do not satisfy the strict check', '/with_user_agent', { authorization: 'Bearer m_ck', user_agent: 'My Agent', x_special: 'special' }
|
||||
end
|
||||
|
||||
describe 'body_match?' do
|
||||
let(:stubs) do
|
||||
described_class::Stubs.new do |stubs|
|
||||
stubs.post('/no_check') { [200, {}, 'ok'] }
|
||||
stubs.post('/with_string', 'abc') { [200, {}, 'ok'] }
|
||||
stubs.post(
|
||||
'/with_proc',
|
||||
->(request_body) { JSON.parse(request_body, symbolize_names: true) == { x: '!', a: [{ m: [{ a: true }], n: 123 }] } },
|
||||
{ content_type: 'application/json' }
|
||||
) do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when trying without any args for body' do
|
||||
subject(:without_body) { connection.post('/no_check') }
|
||||
|
||||
it { expect(without_body.status).to eq 200 }
|
||||
end
|
||||
|
||||
context 'when trying with string body stubs' do
|
||||
subject(:with_string) { connection.post('/with_string', 'abc') }
|
||||
|
||||
it { expect(with_string.status).to eq 200 }
|
||||
end
|
||||
|
||||
context 'when trying with proc body stubs' do
|
||||
subject(:with_proc) do
|
||||
connection.post('/with_proc', JSON.dump(a: [{ n: 123, m: [{ a: true }] }], x: '!'), { 'Content-Type' => 'application/json' })
|
||||
end
|
||||
|
||||
it { expect(with_proc.status).to eq 200 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'request timeout' do
|
||||
subject(:request) do
|
||||
connection.get('/sleep') do |req|
|
||||
req.options.timeout = timeout
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stubs.get('/sleep') do
|
||||
sleep(0.01)
|
||||
[200, {}, '']
|
||||
end
|
||||
end
|
||||
|
||||
context 'when request is within timeout' do
|
||||
let(:timeout) { 1 }
|
||||
|
||||
it { expect(request.status).to eq 200 }
|
||||
end
|
||||
|
||||
context 'when request is too slow' do
|
||||
let(:timeout) { 0.001 }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { request }.to raise_error(Faraday::TimeoutError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::AdapterRegistry do
|
||||
describe '#initialize' do
|
||||
subject(:registry) { described_class.new }
|
||||
|
||||
it { expect { registry.get(:FinFangFoom) }.to raise_error(NameError) }
|
||||
it { expect { registry.get('FinFangFoom') }.to raise_error(NameError) }
|
||||
|
||||
it 'looks up class by string name' do
|
||||
expect(registry.get('Faraday::Connection')).to eq(Faraday::Connection)
|
||||
end
|
||||
|
||||
it 'looks up class by symbol name' do
|
||||
expect(registry.get(:Faraday)).to eq(Faraday)
|
||||
end
|
||||
|
||||
it 'caches lookups with implicit name' do
|
||||
registry.set :symbol
|
||||
expect(registry.get('symbol')).to eq(:symbol)
|
||||
end
|
||||
|
||||
it 'caches lookups with explicit name' do
|
||||
registry.set 'string', :name
|
||||
expect(registry.get(:name)).to eq('string')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Adapter do
|
||||
let(:adapter) { Faraday::Adapter.new }
|
||||
let(:request) { {} }
|
||||
|
||||
context '#request_timeout' do
|
||||
it 'gets :read timeout' do
|
||||
expect(timeout(:read)).to eq(nil)
|
||||
|
||||
request[:timeout] = 5
|
||||
request[:write_timeout] = 1
|
||||
|
||||
expect(timeout(:read)).to eq(5)
|
||||
|
||||
request[:read_timeout] = 2
|
||||
|
||||
expect(timeout(:read)).to eq(2)
|
||||
end
|
||||
|
||||
it 'gets :open timeout' do
|
||||
expect(timeout(:open)).to eq(nil)
|
||||
|
||||
request[:timeout] = 5
|
||||
request[:write_timeout] = 1
|
||||
|
||||
expect(timeout(:open)).to eq(5)
|
||||
|
||||
request[:open_timeout] = 2
|
||||
|
||||
expect(timeout(:open)).to eq(2)
|
||||
end
|
||||
|
||||
it 'gets :write timeout' do
|
||||
expect(timeout(:write)).to eq(nil)
|
||||
|
||||
request[:timeout] = 5
|
||||
request[:read_timeout] = 1
|
||||
|
||||
expect(timeout(:write)).to eq(5)
|
||||
|
||||
request[:write_timeout] = 2
|
||||
|
||||
expect(timeout(:write)).to eq(2)
|
||||
end
|
||||
|
||||
it 'attempts unknown timeout type' do
|
||||
expect { timeout(:unknown) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
def timeout(type)
|
||||
adapter.send(:request_timeout, type, request)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,808 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CustomEncoder
|
||||
def encode(params)
|
||||
params.map { |k, v| "#{k.upcase}-#{v.to_s.upcase}" }.join(',')
|
||||
end
|
||||
|
||||
def decode(params)
|
||||
params.split(',').to_h { |pair| pair.split('-') }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'initializer with url' do
|
||||
context 'with simple url' do
|
||||
let(:address) { 'http://httpbingo.org' }
|
||||
|
||||
it { expect(subject.host).to eq('httpbingo.org') }
|
||||
it { expect(subject.port).to eq(80) }
|
||||
it { expect(subject.scheme).to eq('http') }
|
||||
it { expect(subject.path_prefix).to eq('/') }
|
||||
it { expect(subject.params).to eq({}) }
|
||||
end
|
||||
|
||||
context 'with complex url' do
|
||||
let(:address) { 'http://httpbingo.org:815/fish?a=1' }
|
||||
|
||||
it { expect(subject.port).to eq(815) }
|
||||
it { expect(subject.path_prefix).to eq('/fish') }
|
||||
it { expect(subject.params).to eq('a' => '1') }
|
||||
end
|
||||
|
||||
context 'with IPv6 address' do
|
||||
let(:address) { 'http://[::1]:85/' }
|
||||
|
||||
it { expect(subject.host).to eq('[::1]') }
|
||||
it { expect(subject.port).to eq(85) }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'default connection options' do
|
||||
after { Faraday.default_connection_options = nil }
|
||||
|
||||
it 'works with implicit url' do
|
||||
conn = Faraday.new 'http://httpbingo.org/foo'
|
||||
expect(conn.options.timeout).to eq(10)
|
||||
end
|
||||
|
||||
it 'works with option url' do
|
||||
conn = Faraday.new url: 'http://httpbingo.org/foo'
|
||||
expect(conn.options.timeout).to eq(10)
|
||||
end
|
||||
|
||||
it 'works with instance connection options' do
|
||||
conn = Faraday.new 'http://httpbingo.org/foo', request: { open_timeout: 1 }
|
||||
expect(conn.options.timeout).to eq(10)
|
||||
expect(conn.options.open_timeout).to eq(1)
|
||||
end
|
||||
|
||||
it 'default connection options persist with an instance overriding' do
|
||||
conn = Faraday.new 'http://nigiri.com/bar'
|
||||
conn.options.timeout = 1
|
||||
expect(Faraday.default_connection_options.request.timeout).to eq(10)
|
||||
|
||||
other = Faraday.new url: 'https://httpbingo.org/foo'
|
||||
other.options.timeout = 1
|
||||
|
||||
expect(Faraday.default_connection_options.request.timeout).to eq(10)
|
||||
end
|
||||
|
||||
it 'default connection uses default connection options' do
|
||||
expect(Faraday.default_connection.options.timeout).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Faraday::Connection do
|
||||
let(:conn) { Faraday::Connection.new(url, options) }
|
||||
let(:url) { nil }
|
||||
let(:options) { nil }
|
||||
|
||||
describe '.new' do
|
||||
subject { conn }
|
||||
|
||||
context 'with implicit url param' do
|
||||
# Faraday::Connection.new('http://httpbingo.org')
|
||||
let(:url) { address }
|
||||
|
||||
it_behaves_like 'initializer with url'
|
||||
end
|
||||
|
||||
context 'with explicit url param' do
|
||||
# Faraday::Connection.new(url: 'http://httpbingo.org')
|
||||
let(:url) { { url: address } }
|
||||
|
||||
it_behaves_like 'initializer with url'
|
||||
end
|
||||
|
||||
context 'with custom builder' do
|
||||
let(:custom_builder) { Faraday::RackBuilder.new }
|
||||
let(:options) { { builder: custom_builder } }
|
||||
|
||||
it { expect(subject.builder).to eq(custom_builder) }
|
||||
end
|
||||
|
||||
context 'with custom params' do
|
||||
let(:options) { { params: { a: 1 } } }
|
||||
|
||||
it { expect(subject.params).to eq('a' => 1) }
|
||||
end
|
||||
|
||||
context 'with custom params and params in url' do
|
||||
let(:url) { 'http://httpbingo.org/fish?a=1&b=2' }
|
||||
let(:options) { { params: { a: 3 } } }
|
||||
it { expect(subject.params).to eq('a' => 3, 'b' => '2') }
|
||||
end
|
||||
|
||||
context 'with basic_auth in url' do
|
||||
let(:url) { 'http://Aladdin:open%20sesame@httpbingo.org/fish' }
|
||||
|
||||
it { expect(subject.headers['Authorization']).to eq('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==') }
|
||||
end
|
||||
|
||||
context 'with custom headers' do
|
||||
let(:options) { { headers: { user_agent: 'Faraday' } } }
|
||||
|
||||
it { expect(subject.headers['User-agent']).to eq('Faraday') }
|
||||
end
|
||||
|
||||
context 'with ssl false' do
|
||||
let(:options) { { ssl: { verify: false } } }
|
||||
|
||||
it { expect(subject.ssl.verify?).to be_falsey }
|
||||
end
|
||||
|
||||
context 'with verify_hostname false' do
|
||||
let(:options) { { ssl: { verify_hostname: false } } }
|
||||
|
||||
it { expect(subject.ssl.verify_hostname?).to be_falsey }
|
||||
end
|
||||
|
||||
context 'with empty block' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
it { expect(conn.builder.handlers.size).to eq(0) }
|
||||
end
|
||||
|
||||
context 'with block' do
|
||||
let(:conn) do
|
||||
Faraday::Connection.new(params: { 'a' => '1' }) do |faraday|
|
||||
faraday.adapter :test
|
||||
faraday.url_prefix = 'http://httpbingo.org/omnom'
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(conn.builder.handlers.size).to eq(0) }
|
||||
it { expect(conn.path_prefix).to eq('/omnom') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#close' do
|
||||
before { Faraday.default_adapter = :test }
|
||||
after { Faraday.default_adapter = nil }
|
||||
|
||||
it 'can close underlying app' do
|
||||
expect(conn.app).to receive(:close)
|
||||
conn.close
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_exclusive_url' do
|
||||
context 'with relative path' do
|
||||
subject { conn.build_exclusive_url('sake.html') }
|
||||
|
||||
it 'uses connection host as default host' do
|
||||
conn.host = 'httpbingo.org'
|
||||
expect(subject.host).to eq('httpbingo.org')
|
||||
expect(subject.scheme).to eq('http')
|
||||
end
|
||||
|
||||
it do
|
||||
conn.path_prefix = '/fish'
|
||||
expect(subject.path).to eq('/fish/sake.html')
|
||||
end
|
||||
|
||||
it do
|
||||
conn.path_prefix = '/'
|
||||
expect(subject.path).to eq('/sake.html')
|
||||
end
|
||||
|
||||
it do
|
||||
conn.path_prefix = 'fish'
|
||||
expect(subject.path).to eq('/fish/sake.html')
|
||||
end
|
||||
|
||||
it do
|
||||
conn.path_prefix = '/fish/'
|
||||
expect(subject.path).to eq('/fish/sake.html')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with absolute path' do
|
||||
subject { conn.build_exclusive_url('/sake.html') }
|
||||
|
||||
after { expect(subject.path).to eq('/sake.html') }
|
||||
|
||||
it { conn.path_prefix = '/fish' }
|
||||
it { conn.path_prefix = '/' }
|
||||
it { conn.path_prefix = 'fish' }
|
||||
it { conn.path_prefix = '/fish/' }
|
||||
end
|
||||
|
||||
context 'with complete url' do
|
||||
subject { conn.build_exclusive_url('http://httpbingo.org/sake.html?a=1') }
|
||||
|
||||
it { expect(subject.scheme).to eq('http') }
|
||||
it { expect(subject.host).to eq('httpbingo.org') }
|
||||
it { expect(subject.port).to eq(80) }
|
||||
it { expect(subject.path).to eq('/sake.html') }
|
||||
it { expect(subject.query).to eq('a=1') }
|
||||
end
|
||||
|
||||
it 'overrides connection port for absolute url' do
|
||||
conn.port = 23
|
||||
uri = conn.build_exclusive_url('http://httpbingo.org')
|
||||
expect(uri.port).to eq(80)
|
||||
end
|
||||
|
||||
it 'does not add ending slash given nil url' do
|
||||
conn.url_prefix = 'http://httpbingo.org/nigiri'
|
||||
uri = conn.build_exclusive_url
|
||||
expect(uri.path).to eq('/nigiri')
|
||||
end
|
||||
|
||||
it 'does not add ending slash given empty url' do
|
||||
conn.url_prefix = 'http://httpbingo.org/nigiri'
|
||||
uri = conn.build_exclusive_url('')
|
||||
expect(uri.path).to eq('/nigiri')
|
||||
end
|
||||
|
||||
it 'does not use connection params' do
|
||||
conn.url_prefix = 'http://httpbingo.org/nigiri'
|
||||
conn.params = { a: 1 }
|
||||
expect(conn.build_exclusive_url.to_s).to eq('http://httpbingo.org/nigiri')
|
||||
end
|
||||
|
||||
it 'allows to provide params argument' do
|
||||
conn.url_prefix = 'http://httpbingo.org/nigiri'
|
||||
conn.params = { a: 1 }
|
||||
params = Faraday::Utils::ParamsHash.new
|
||||
params[:a] = 2
|
||||
uri = conn.build_exclusive_url(nil, params)
|
||||
expect(uri.to_s).to eq('http://httpbingo.org/nigiri?a=2')
|
||||
end
|
||||
|
||||
it 'handles uri instances' do
|
||||
uri = conn.build_exclusive_url(URI('/sake.html'))
|
||||
expect(uri.path).to eq('/sake.html')
|
||||
end
|
||||
|
||||
it 'always returns new URI instance' do
|
||||
conn.url_prefix = 'http://httpbingo.org'
|
||||
uri1 = conn.build_exclusive_url(nil)
|
||||
uri2 = conn.build_exclusive_url(nil)
|
||||
expect(uri1).not_to equal(uri2)
|
||||
end
|
||||
|
||||
context 'with url_prefixed connection' do
|
||||
let(:url) { 'http://httpbingo.org/get/' }
|
||||
|
||||
it 'parses url and changes scheme' do
|
||||
conn.scheme = 'https'
|
||||
uri = conn.build_exclusive_url('sake.html')
|
||||
expect(uri.to_s).to eq('https://httpbingo.org/get/sake.html')
|
||||
end
|
||||
|
||||
it 'joins url to base with ending slash' do
|
||||
uri = conn.build_exclusive_url('sake.html')
|
||||
expect(uri.to_s).to eq('http://httpbingo.org/get/sake.html')
|
||||
end
|
||||
|
||||
it 'used default base with ending slash' do
|
||||
uri = conn.build_exclusive_url
|
||||
expect(uri.to_s).to eq('http://httpbingo.org/get/')
|
||||
end
|
||||
|
||||
it 'overrides base' do
|
||||
uri = conn.build_exclusive_url('/sake/')
|
||||
expect(uri.to_s).to eq('http://httpbingo.org/sake/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with colon in path' do
|
||||
let(:url) { 'http://service.com' }
|
||||
|
||||
it 'joins url to base when used absolute path' do
|
||||
conn = Faraday.new(url: url)
|
||||
uri = conn.build_exclusive_url('/service:search?limit=400')
|
||||
expect(uri.to_s).to eq('http://service.com/service:search?limit=400')
|
||||
end
|
||||
|
||||
it 'joins url to base when used relative path' do
|
||||
conn = Faraday.new(url: url)
|
||||
uri = conn.build_exclusive_url('service:search?limit=400')
|
||||
expect(uri.to_s).to eq('http://service.com/service%3Asearch?limit=400')
|
||||
end
|
||||
|
||||
it 'joins url to base when used with path prefix' do
|
||||
conn = Faraday.new(url: url)
|
||||
conn.path_prefix = '/api'
|
||||
uri = conn.build_exclusive_url('service:search?limit=400')
|
||||
expect(uri.to_s).to eq('http://service.com/api/service%3Asearch?limit=400')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a custom `default_uri_parser`' do
|
||||
let(:url) { 'http://httpbingo.org' }
|
||||
let(:parser) { Addressable::URI }
|
||||
|
||||
around do |example|
|
||||
with_default_uri_parser(parser) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not raise error' do
|
||||
expect { conn.build_exclusive_url('/nigiri') }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_url' do
|
||||
let(:url) { 'http://httpbingo.org/nigiri' }
|
||||
|
||||
it 'uses params' do
|
||||
conn.params = { a: 1, b: 1 }
|
||||
expect(conn.build_url.to_s).to eq('http://httpbingo.org/nigiri?a=1&b=1')
|
||||
end
|
||||
|
||||
it 'merges params' do
|
||||
conn.params = { a: 1, b: 1 }
|
||||
url = conn.build_url(nil, b: 2, c: 3)
|
||||
expect(url.to_s).to eq('http://httpbingo.org/nigiri?a=1&b=2&c=3')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_request' do
|
||||
let(:url) { 'https://ahttpbingo.org/sake.html' }
|
||||
let(:request) { conn.build_request(:get) }
|
||||
|
||||
before do
|
||||
conn.headers = { 'Authorization' => 'token abc123' }
|
||||
request.headers.delete('Authorization')
|
||||
end
|
||||
|
||||
it { expect(conn.headers.keys).to eq(['Authorization']) }
|
||||
it { expect(conn.headers.include?('Authorization')).to be_truthy }
|
||||
it { expect(request.headers.keys).to be_empty }
|
||||
it { expect(request.headers.include?('Authorization')).to be_falsey }
|
||||
end
|
||||
|
||||
describe '#to_env' do
|
||||
subject { conn.build_request(:get).to_env(conn).url }
|
||||
|
||||
let(:url) { 'http://httpbingo.org/sake.html' }
|
||||
let(:options) { { params: @params } }
|
||||
|
||||
it 'parses url params into query' do
|
||||
@params = { 'a[b]' => '1 + 2' }
|
||||
expect(subject.query).to eq('a%5Bb%5D=1+%2B+2')
|
||||
end
|
||||
|
||||
it 'escapes per spec' do
|
||||
@params = { 'a' => '1+2 foo~bar.-baz' }
|
||||
expect(subject.query).to eq('a=1%2B2+foo~bar.-baz')
|
||||
end
|
||||
|
||||
it 'bracketizes nested params in query' do
|
||||
@params = { 'a' => { 'b' => 'c' } }
|
||||
expect(subject.query).to eq('a%5Bb%5D=c')
|
||||
end
|
||||
|
||||
it 'bracketizes repeated params in query' do
|
||||
@params = { 'a' => [1, 2] }
|
||||
expect(subject.query).to eq('a%5B%5D=1&a%5B%5D=2')
|
||||
end
|
||||
|
||||
it 'without braketizing repeated params in query' do
|
||||
@params = { 'a' => [1, 2] }
|
||||
conn.options.params_encoder = Faraday::FlatParamsEncoder
|
||||
expect(subject.query).to eq('a=1&a=2')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'proxy support' do
|
||||
it 'accepts string' do
|
||||
with_env 'http_proxy' => 'http://env-proxy.com:80' do
|
||||
conn.proxy = 'http://proxy.com'
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts uri' do
|
||||
with_env 'http_proxy' => 'http://env-proxy.com:80' do
|
||||
conn.proxy = URI.parse('http://proxy.com')
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts hash with string uri' do
|
||||
with_env 'http_proxy' => 'http://env-proxy.com:80' do
|
||||
conn.proxy = { uri: 'http://proxy.com', user: 'rick' }
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
expect(conn.proxy.user).to eq('rick')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts hash' do
|
||||
with_env 'http_proxy' => 'http://env-proxy.com:80' do
|
||||
conn.proxy = { uri: URI.parse('http://proxy.com'), user: 'rick' }
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
expect(conn.proxy.user).to eq('rick')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts http env' do
|
||||
with_env 'http_proxy' => 'http://env-proxy.com:80' do
|
||||
expect(conn.proxy.host).to eq('env-proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts http env with auth' do
|
||||
with_env 'http_proxy' => 'http://a%40b:my%20pass@proxy.com:80' do
|
||||
expect(conn.proxy.user).to eq('a@b')
|
||||
expect(conn.proxy.password).to eq('my pass')
|
||||
end
|
||||
end
|
||||
|
||||
it 'accepts env without scheme' do
|
||||
with_env 'http_proxy' => 'localhost:8888' do
|
||||
uri = conn.proxy[:uri]
|
||||
expect(uri.host).to eq('localhost')
|
||||
expect(uri.port).to eq(8888)
|
||||
end
|
||||
end
|
||||
|
||||
it 'fetches no proxy from nil env' do
|
||||
with_env 'http_proxy' => nil do
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'fetches no proxy from blank env' do
|
||||
with_env 'http_proxy' => '' do
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not accept uppercase env' do
|
||||
with_env 'HTTP_PROXY' => 'http://localhost:8888/' do
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when url in no proxy list' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
|
||||
conn = Faraday::Connection.new('http://example.com')
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when url in no proxy list with url_prefix' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
|
||||
conn = Faraday::Connection.new
|
||||
conn.url_prefix = 'http://example.com'
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when prefixed url is not in no proxy list' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
|
||||
conn = Faraday::Connection.new('http://prefixedexample.com')
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when subdomain url is in no proxy list' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
|
||||
conn = Faraday::Connection.new('http://subdomain.example.com')
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when url not in no proxy list' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example2.com' do
|
||||
conn = Faraday::Connection.new('http://example.com')
|
||||
expect(conn.proxy.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when ip address is not in no proxy list but url is' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'localhost' do
|
||||
conn = Faraday::Connection.new('http://127.0.0.1')
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows when url is not in no proxy list but ip address is' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => '127.0.0.1' do
|
||||
conn = Faraday::Connection.new('http://localhost')
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows in multi element no proxy list' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example0.com,example.com,example1.com' do
|
||||
expect(Faraday::Connection.new('http://example0.com').proxy).to be_nil
|
||||
expect(Faraday::Connection.new('http://example.com').proxy).to be_nil
|
||||
expect(Faraday::Connection.new('http://example1.com').proxy).to be_nil
|
||||
expect(Faraday::Connection.new('http://example2.com').proxy.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'test proxy requires uri' do
|
||||
expect { conn.proxy = { uri: :bad_uri, user: 'rick' } }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'uses env http_proxy' do
|
||||
with_env 'http_proxy' => 'http://proxy.com' do
|
||||
conn = Faraday.new
|
||||
expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
|
||||
expect(conn.proxy_for_request('http://google.co.uk').host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses processes no_proxy before http_proxy' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'google.co.uk' do
|
||||
conn = Faraday.new
|
||||
expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
|
||||
expect(conn.proxy_for_request('http://google.co.uk')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses env https_proxy' do
|
||||
with_env 'https_proxy' => 'https://proxy.com' do
|
||||
conn = Faraday.new
|
||||
expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
|
||||
expect(conn.proxy_for_request('https://google.co.uk').host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses processes no_proxy before https_proxy' do
|
||||
with_env 'https_proxy' => 'https://proxy.com', 'no_proxy' => 'google.co.uk' do
|
||||
conn = Faraday.new
|
||||
expect(conn.instance_variable_get(:@manual_proxy)).to be_falsey
|
||||
expect(conn.proxy_for_request('https://google.co.uk')).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'gives priority to manually set proxy' do
|
||||
with_env 'https_proxy' => 'https://proxy.com', 'no_proxy' => 'google.co.uk' do
|
||||
conn = Faraday.new
|
||||
conn.proxy = 'http://proxy2.com'
|
||||
|
||||
expect(conn.instance_variable_get(:@manual_proxy)).to be_truthy
|
||||
expect(conn.proxy_for_request('https://google.co.uk').host).to eq('proxy2.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores env proxy if set that way' do
|
||||
with_env_proxy_disabled do
|
||||
with_env 'http_proxy' => 'http://duncan.proxy.com:80' do
|
||||
expect(conn.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'performing a request' do
|
||||
let(:url) { 'http://example.com' }
|
||||
let(:conn) do
|
||||
Faraday.new do |f|
|
||||
f.adapter :test do |stubs|
|
||||
stubs.get(url) do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'dynamically checks proxy' do
|
||||
with_env 'http_proxy' => 'http://proxy.com:80' do
|
||||
expect(conn.proxy.uri.host).to eq('proxy.com')
|
||||
|
||||
conn.get(url) do |req|
|
||||
expect(req.options.proxy.uri.host).to eq('proxy.com')
|
||||
end
|
||||
end
|
||||
|
||||
conn.get(url)
|
||||
expect(conn.instance_variable_get(:@temp_proxy)).to be_nil
|
||||
end
|
||||
|
||||
it 'dynamically check no proxy' do
|
||||
with_env 'http_proxy' => 'http://proxy.com', 'no_proxy' => 'example.com' do
|
||||
expect(conn.proxy.uri.host).to eq('proxy.com')
|
||||
|
||||
conn.get('http://example.com') do |req|
|
||||
expect(req.options.proxy).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#dup' do
|
||||
subject { conn.dup }
|
||||
|
||||
let(:url) { 'http://httpbingo.org/foo' }
|
||||
let(:options) do
|
||||
{
|
||||
ssl: { verify: :none },
|
||||
headers: { 'content-type' => 'text/plain' },
|
||||
params: { 'a' => '1' },
|
||||
request: { timeout: 5 }
|
||||
}
|
||||
end
|
||||
|
||||
it { expect(subject.build_exclusive_url).to eq(conn.build_exclusive_url) }
|
||||
it { expect(subject.headers['content-type']).to eq('text/plain') }
|
||||
it { expect(subject.params['a']).to eq('1') }
|
||||
|
||||
context 'after manual changes' do
|
||||
before do
|
||||
subject.headers['content-length'] = 12
|
||||
subject.params['b'] = '2'
|
||||
subject.options[:open_timeout] = 10
|
||||
end
|
||||
|
||||
it { expect(subject.builder.handlers.size).to eq(1) }
|
||||
it { expect(conn.builder.handlers.size).to eq(1) }
|
||||
it { expect(conn.headers.key?('content-length')).to be_falsey }
|
||||
it { expect(conn.params.key?('b')).to be_falsey }
|
||||
it { expect(subject.options[:timeout]).to eq(5) }
|
||||
it { expect(conn.options[:open_timeout]).to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#respond_to?' do
|
||||
it { expect(Faraday.respond_to?(:get)).to be_truthy }
|
||||
it { expect(Faraday.respond_to?(:post)).to be_truthy }
|
||||
end
|
||||
|
||||
describe 'default_connection_options' do
|
||||
context 'assigning a default value' do
|
||||
before do
|
||||
Faraday.default_connection_options = nil
|
||||
Faraday.default_connection_options.request.timeout = 10
|
||||
end
|
||||
|
||||
it_behaves_like 'default connection options'
|
||||
end
|
||||
|
||||
context 'assigning a hash' do
|
||||
before { Faraday.default_connection_options = { request: { timeout: 10 } } }
|
||||
|
||||
it_behaves_like 'default connection options'
|
||||
end
|
||||
|
||||
context 'preserving a user_agent assigned via default_conncetion_options' do
|
||||
around do |example|
|
||||
old = Faraday.default_connection_options
|
||||
Faraday.default_connection_options = { headers: { user_agent: 'My Agent 1.2' } }
|
||||
example.run
|
||||
Faraday.default_connection_options = old
|
||||
end
|
||||
|
||||
context 'when url is a Hash' do
|
||||
let(:conn) { Faraday.new(url: 'http://example.co', headers: { 'CustomHeader' => 'CustomValue' }) }
|
||||
|
||||
it { expect(conn.headers).to eq('CustomHeader' => 'CustomValue', 'User-Agent' => 'My Agent 1.2') }
|
||||
end
|
||||
|
||||
context 'when url is a String' do
|
||||
let(:conn) { Faraday.new('http://example.co', headers: { 'CustomHeader' => 'CustomValue' }) }
|
||||
|
||||
it { expect(conn.headers).to eq('CustomHeader' => 'CustomValue', 'User-Agent' => 'My Agent 1.2') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'request params' do
|
||||
context 'with simple url' do
|
||||
let(:url) { 'http://example.com' }
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
||||
|
||||
before do
|
||||
conn.adapter(:test, stubs)
|
||||
stubs.get('http://example.com?a=a&p=3') do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
|
||||
after { stubs.verify_stubbed_calls }
|
||||
|
||||
it 'test_overrides_request_params' do
|
||||
conn.get('?p=2&a=a', p: 3)
|
||||
end
|
||||
|
||||
it 'test_overrides_request_params_block' do
|
||||
conn.get('?p=1&a=a', p: 2) do |req|
|
||||
req.params[:p] = 3
|
||||
end
|
||||
end
|
||||
|
||||
it 'test_overrides_request_params_block_url' do
|
||||
conn.get(nil, p: 2) do |req|
|
||||
req.url('?p=1&a=a', 'p' => 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with url and extra params' do
|
||||
let(:url) { 'http://example.com?a=1&b=2' }
|
||||
let(:options) { { params: { c: 3 } } }
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
||||
|
||||
before do
|
||||
conn.adapter(:test, stubs)
|
||||
end
|
||||
|
||||
it 'merges connection and request params' do
|
||||
expected = 'http://example.com?a=1&b=2&c=3&limit=5&page=1'
|
||||
stubs.get(expected) { [200, {}, 'ok'] }
|
||||
conn.get('?page=1', limit: 5)
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
it 'allows to override all params' do
|
||||
expected = 'http://example.com?b=b'
|
||||
stubs.get(expected) { [200, {}, 'ok'] }
|
||||
conn.get('?p=1&a=a', p: 2) do |req|
|
||||
expect(req.params[:a]).to eq('a')
|
||||
expect(req.params['c']).to eq(3)
|
||||
expect(req.params['p']).to eq(2)
|
||||
req.params = { b: 'b' }
|
||||
expect(req.params['b']).to eq('b')
|
||||
end
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
|
||||
it 'allows to set params_encoder for single request' do
|
||||
encoder = CustomEncoder.new
|
||||
expected = 'http://example.com/?A-1,B-2,C-3,FEELING-BLUE'
|
||||
stubs.get(expected) { [200, {}, 'ok'] }
|
||||
|
||||
conn.get('/', a: 1, b: 2, c: 3, feeling: 'blue') do |req|
|
||||
req.options.params_encoder = encoder
|
||||
end
|
||||
stubs.verify_stubbed_calls
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default params encoder' do
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
||||
|
||||
before do
|
||||
conn.adapter(:test, stubs)
|
||||
stubs.get('http://example.com?color%5B%5D=blue&color%5B%5D=red') do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
|
||||
after { stubs.verify_stubbed_calls }
|
||||
|
||||
it 'supports array params in url' do
|
||||
conn.get('http://example.com?color[]=blue&color[]=red')
|
||||
end
|
||||
|
||||
it 'supports array params in params' do
|
||||
conn.get('http://example.com', color: %w[blue red])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with flat params encoder' do
|
||||
let(:options) { { request: { params_encoder: Faraday::FlatParamsEncoder } } }
|
||||
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
||||
|
||||
before do
|
||||
conn.adapter(:test, stubs)
|
||||
stubs.get('http://example.com?color=blue&color=red') do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
|
||||
after { stubs.verify_stubbed_calls }
|
||||
|
||||
it 'supports array params in params' do
|
||||
conn.get('http://example.com', color: %w[blue red])
|
||||
end
|
||||
|
||||
context 'with array param in url' do
|
||||
let(:url) { 'http://example.com?color[]=blue&color[]=red' }
|
||||
|
||||
it do
|
||||
conn.get('/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Error do
|
||||
describe '.initialize' do
|
||||
subject { described_class.new(exception, response) }
|
||||
let(:response) { nil }
|
||||
|
||||
context 'with exception only' do
|
||||
let(:exception) { RuntimeError.new('test') }
|
||||
|
||||
it { expect(subject.wrapped_exception).to eq(exception) }
|
||||
it { expect(subject.response).to be_nil }
|
||||
it { expect(subject.message).to eq(exception.message) }
|
||||
it { expect(subject.backtrace).to eq(exception.backtrace) }
|
||||
it { expect(subject.inspect).to eq('#<Faraday::Error wrapped=#<RuntimeError: test>>') }
|
||||
it { expect(subject.response_status).to be_nil }
|
||||
it { expect(subject.response_headers).to be_nil }
|
||||
it { expect(subject.response_body).to be_nil }
|
||||
end
|
||||
|
||||
context 'with response hash' do
|
||||
let(:exception) { { status: 400 } }
|
||||
|
||||
it { expect(subject.wrapped_exception).to be_nil }
|
||||
it { expect(subject.response).to eq(exception) }
|
||||
it { expect(subject.message).to eq('the server responded with status 400') }
|
||||
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
|
||||
it { expect(subject.response_status).to eq(400) }
|
||||
it { expect(subject.response_headers).to be_nil }
|
||||
it { expect(subject.response_body).to be_nil }
|
||||
end
|
||||
|
||||
context 'with string' do
|
||||
let(:exception) { 'custom message' }
|
||||
|
||||
it { expect(subject.wrapped_exception).to be_nil }
|
||||
it { expect(subject.response).to be_nil }
|
||||
it { expect(subject.message).to eq('custom message') }
|
||||
it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: custom message>>') }
|
||||
it { expect(subject.response_status).to be_nil }
|
||||
it { expect(subject.response_headers).to be_nil }
|
||||
it { expect(subject.response_body).to be_nil }
|
||||
end
|
||||
|
||||
context 'with anything else #to_s' do
|
||||
let(:exception) { %w[error1 error2] }
|
||||
|
||||
it { expect(subject.wrapped_exception).to be_nil }
|
||||
it { expect(subject.response).to be_nil }
|
||||
it { expect(subject.message).to eq('["error1", "error2"]') }
|
||||
it { expect(subject.inspect).to eq('#<Faraday::Error #<Faraday::Error: ["error1", "error2"]>>') }
|
||||
it { expect(subject.response_status).to be_nil }
|
||||
it { expect(subject.response_headers).to be_nil }
|
||||
it { expect(subject.response_body).to be_nil }
|
||||
end
|
||||
|
||||
context 'with exception string and response hash' do
|
||||
let(:exception) { 'custom message' }
|
||||
let(:response) { { status: 400 } }
|
||||
|
||||
it { expect(subject.wrapped_exception).to be_nil }
|
||||
it { expect(subject.response).to eq(response) }
|
||||
it { expect(subject.message).to eq('custom message') }
|
||||
it { expect(subject.inspect).to eq('#<Faraday::Error response={:status=>400}>') }
|
||||
it { expect(subject.response_status).to eq(400) }
|
||||
it { expect(subject.response_headers).to be_nil }
|
||||
it { expect(subject.response_body).to be_nil }
|
||||
end
|
||||
|
||||
context 'with exception and response object' do
|
||||
let(:exception) { RuntimeError.new('test') }
|
||||
let(:body) { { test: 'test' } }
|
||||
let(:headers) { { 'Content-Type' => 'application/json' } }
|
||||
let(:response) { Faraday::Response.new(status: 400, response_headers: headers, response_body: body) }
|
||||
|
||||
it { expect(subject.wrapped_exception).to eq(exception) }
|
||||
it { expect(subject.response).to eq(response) }
|
||||
it { expect(subject.message).to eq(exception.message) }
|
||||
it { expect(subject.backtrace).to eq(exception.backtrace) }
|
||||
it { expect(subject.response_status).to eq(400) }
|
||||
it { expect(subject.response_headers).to eq(headers) }
|
||||
it { expect(subject.response_body).to eq(body) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::MiddlewareRegistry do
|
||||
before do
|
||||
stub_const('CustomMiddleware', custom_middleware_klass)
|
||||
end
|
||||
let(:custom_middleware_klass) { Class.new(Faraday::Middleware) }
|
||||
let(:dummy) { Class.new { extend Faraday::MiddlewareRegistry } }
|
||||
|
||||
after { dummy.unregister_middleware(:custom) }
|
||||
|
||||
it 'allows to register with constant' do
|
||||
dummy.register_middleware(custom: custom_middleware_klass)
|
||||
expect(dummy.lookup_middleware(:custom)).to eq(custom_middleware_klass)
|
||||
end
|
||||
|
||||
it 'allows to register with symbol' do
|
||||
dummy.register_middleware(custom: :CustomMiddleware)
|
||||
expect(dummy.lookup_middleware(:custom)).to eq(custom_middleware_klass)
|
||||
end
|
||||
|
||||
it 'allows to register with string' do
|
||||
dummy.register_middleware(custom: 'CustomMiddleware')
|
||||
expect(dummy.lookup_middleware(:custom)).to eq(custom_middleware_klass)
|
||||
end
|
||||
|
||||
it 'allows to register with Proc' do
|
||||
dummy.register_middleware(custom: -> { custom_middleware_klass })
|
||||
expect(dummy.lookup_middleware(:custom)).to eq(custom_middleware_klass)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Middleware do
|
||||
subject { described_class.new(app) }
|
||||
let(:app) { double }
|
||||
|
||||
describe 'options' do
|
||||
context 'when options are passed to the middleware' do
|
||||
subject { described_class.new(app, options) }
|
||||
let(:options) { { field: 'value' } }
|
||||
|
||||
it 'accepts options when initialized' do
|
||||
expect(subject.options[:field]).to eq('value')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_request' do
|
||||
subject do
|
||||
Class.new(described_class) do
|
||||
def on_request(env)
|
||||
# do nothing
|
||||
end
|
||||
end.new(app)
|
||||
end
|
||||
|
||||
it 'is called by #call' do
|
||||
expect(app).to receive(:call).and_return(app)
|
||||
expect(app).to receive(:on_complete)
|
||||
is_expected.to receive(:call).and_call_original
|
||||
is_expected.to receive(:on_request)
|
||||
subject.call(double)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#on_error' do
|
||||
subject do
|
||||
Class.new(described_class) do
|
||||
def on_error(error)
|
||||
# do nothing
|
||||
end
|
||||
end.new(app)
|
||||
end
|
||||
|
||||
it 'is called by #call' do
|
||||
expect(app).to receive(:call).and_raise(Faraday::ConnectionFailed)
|
||||
is_expected.to receive(:call).and_call_original
|
||||
is_expected.to receive(:on_error)
|
||||
|
||||
expect { subject.call(double) }.to raise_error(Faraday::ConnectionFailed)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#close' do
|
||||
context "with app that doesn't support \#close" do
|
||||
it 'should issue warning' do
|
||||
is_expected.to receive(:warn)
|
||||
subject.close
|
||||
end
|
||||
end
|
||||
|
||||
context "with app that supports \#close" do
|
||||
it 'should issue warning' do
|
||||
expect(app).to receive(:close)
|
||||
is_expected.to_not receive(:warn)
|
||||
subject.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Env do
|
||||
subject(:env) { described_class.new }
|
||||
|
||||
it 'allows to access members' do
|
||||
expect(env.method).to be_nil
|
||||
env.method = :get
|
||||
expect(env.method).to eq(:get)
|
||||
end
|
||||
|
||||
it 'allows to access symbol non members' do
|
||||
expect(env[:custom]).to be_nil
|
||||
env[:custom] = :boom
|
||||
expect(env[:custom]).to eq(:boom)
|
||||
end
|
||||
|
||||
it 'allows to access string non members' do
|
||||
expect(env['custom']).to be_nil
|
||||
env['custom'] = :boom
|
||||
expect(env['custom']).to eq(:boom)
|
||||
end
|
||||
|
||||
it 'ignores false when fetching' do
|
||||
ssl = Faraday::SSLOptions.new
|
||||
ssl.verify = false
|
||||
expect(ssl.fetch(:verify, true)).to be_falsey
|
||||
end
|
||||
|
||||
it 'handle verify_hostname when fetching' do
|
||||
ssl = Faraday::SSLOptions.new
|
||||
ssl.verify_hostname = true
|
||||
expect(ssl.fetch(:verify_hostname, false)).to be_truthy
|
||||
end
|
||||
|
||||
it 'retains custom members' do
|
||||
env[:foo] = 'custom 1'
|
||||
env[:bar] = :custom2
|
||||
env2 = Faraday::Env.from(env)
|
||||
env2[:baz] = 'custom 3'
|
||||
|
||||
expect(env2[:foo]).to eq('custom 1')
|
||||
expect(env2[:bar]).to eq(:custom2)
|
||||
expect(env[:baz]).to be_nil
|
||||
end
|
||||
|
||||
describe '#body' do
|
||||
subject(:env) { described_class.from(body: { foo: 'bar' }) }
|
||||
|
||||
context 'when response is not finished yet' do
|
||||
it 'returns the request body' do
|
||||
expect(env.body).to eq(foo: 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response is finished' do
|
||||
before do
|
||||
env.status = 200
|
||||
env.body = { bar: 'foo' }
|
||||
env.response = Faraday::Response.new(env)
|
||||
end
|
||||
|
||||
it 'returns the response body' do
|
||||
expect(env.body).to eq(bar: 'foo')
|
||||
end
|
||||
|
||||
it 'allows to access request_body' do
|
||||
expect(env.request_body).to eq(foo: 'bar')
|
||||
end
|
||||
|
||||
it 'allows to access response_body' do
|
||||
expect(env.response_body).to eq(bar: 'foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,297 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Options do
|
||||
SubOptions = Class.new(Faraday::Options.new(:sub_a, :sub_b))
|
||||
ParentOptions = Faraday::Options.new(:a, :b, :c) do
|
||||
options c: SubOptions
|
||||
end
|
||||
|
||||
describe '#merge' do
|
||||
it 'merges options with hashes' do
|
||||
options = ParentOptions.new(1)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
|
||||
dup = options.merge a: 2, b: 3
|
||||
expect(dup.a).to eq(2)
|
||||
expect(dup.b).to eq(3)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
end
|
||||
|
||||
it 'deeply merges two options' do
|
||||
sub_opts1 = SubOptions.from(sub_a: 3)
|
||||
sub_opts2 = SubOptions.from(sub_b: 4)
|
||||
opt1 = ParentOptions.from(a: 1, c: sub_opts1)
|
||||
opt2 = ParentOptions.from(b: 2, c: sub_opts2)
|
||||
|
||||
merged = opt1.merge(opt2)
|
||||
|
||||
expected_sub_opts = SubOptions.from(sub_a: 3, sub_b: 4)
|
||||
expected = ParentOptions.from(a: 1, b: 2, c: expected_sub_opts)
|
||||
expect(merged).to eq(expected)
|
||||
end
|
||||
|
||||
it 'deeply merges options with hashes' do
|
||||
sub_opts1 = SubOptions.from(sub_a: 3)
|
||||
sub_opts2 = { sub_b: 4 }
|
||||
opt1 = ParentOptions.from(a: 1, c: sub_opts1)
|
||||
opt2 = { b: 2, c: sub_opts2 }
|
||||
|
||||
merged = opt1.merge(opt2)
|
||||
|
||||
expected_sub_opts = SubOptions.from(sub_a: 3, sub_b: 4)
|
||||
expected = ParentOptions.from(a: 1, b: 2, c: expected_sub_opts)
|
||||
expect(merged).to eq(expected)
|
||||
end
|
||||
|
||||
it 'deeply merges options with nil' do
|
||||
sub_opts = SubOptions.new(3, 4)
|
||||
options = ParentOptions.new(1, 2, sub_opts)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to eq(2)
|
||||
expect(options.c.sub_a).to eq(3)
|
||||
expect(options.c.sub_b).to eq(4)
|
||||
|
||||
options2 = ParentOptions.from(b: 5, c: nil)
|
||||
|
||||
merged = options.merge(options2)
|
||||
|
||||
expect(merged.b).to eq(5)
|
||||
expect(merged.c).to eq(sub_opts)
|
||||
end
|
||||
|
||||
it 'deeply merges options with options having nil sub-options' do
|
||||
options = ParentOptions.from(a: 1)
|
||||
|
||||
sub_opts = SubOptions.new(3, 4)
|
||||
options2 = ParentOptions.from(b: 2, c: sub_opts)
|
||||
|
||||
expect(options.a).to eq(1)
|
||||
expect(options2.b).to eq(2)
|
||||
expect(options2.c.sub_a).to eq(3)
|
||||
expect(options2.c.sub_b).to eq(4)
|
||||
|
||||
merged = options.merge(options2)
|
||||
|
||||
expect(merged.c).to eq(sub_opts)
|
||||
end
|
||||
|
||||
describe '#dup' do
|
||||
it 'duplicate options but not sub-options' do
|
||||
sub_opts = SubOptions.from(sub_a: 3)
|
||||
opts = ParentOptions.from(b: 1, c: sub_opts)
|
||||
|
||||
duped = opts.dup
|
||||
duped.b = 2
|
||||
duped.c.sub_a = 4
|
||||
|
||||
expect(opts.b).to eq(1)
|
||||
expect(opts.c.sub_a).to eq(4)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deep_dup' do
|
||||
it 'duplicate options and also suboptions' do
|
||||
sub_opts = SubOptions.from(sub_a: 3)
|
||||
opts = ParentOptions.from(b: 1, c: sub_opts)
|
||||
|
||||
duped = opts.deep_dup
|
||||
duped.b = 2
|
||||
duped.c.sub_a = 4
|
||||
|
||||
expect(opts.b).to eq(1)
|
||||
expect(opts.c.sub_a).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clear' do
|
||||
it 'clears the options' do
|
||||
options = SubOptions.new(1)
|
||||
expect(options.empty?).not_to be_truthy
|
||||
options.clear
|
||||
expect(options.empty?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#empty?' do
|
||||
it 'returns true only if all options are nil' do
|
||||
options = SubOptions.new
|
||||
expect(options.empty?).to be_truthy
|
||||
options.sub_a = 1
|
||||
expect(options.empty?).not_to be_truthy
|
||||
options.delete(:sub_a)
|
||||
expect(options.empty?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#each_key' do
|
||||
it 'allows to iterate through keys' do
|
||||
options = ParentOptions.new(1, 2, 3)
|
||||
enum = options.each_key
|
||||
expect(enum.next.to_sym).to eq(:a)
|
||||
expect(enum.next.to_sym).to eq(:b)
|
||||
expect(enum.next.to_sym).to eq(:c)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#key?' do
|
||||
it 'returns true if the key exists and is not nil' do
|
||||
options = SubOptions.new
|
||||
expect(options.key?(:sub_a)).not_to be_truthy
|
||||
options.sub_a = 1
|
||||
expect(options.key?(:sub_a)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#each_value' do
|
||||
it 'allows to iterate through values' do
|
||||
options = ParentOptions.new(1, 2, 3)
|
||||
enum = options.each_value
|
||||
expect(enum.next).to eq(1)
|
||||
expect(enum.next).to eq(2)
|
||||
expect(enum.next).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value?' do
|
||||
it 'returns true if any key has that value' do
|
||||
options = SubOptions.new
|
||||
expect(options.value?(1)).not_to be_truthy
|
||||
options.sub_a = 1
|
||||
expect(options.value?(1)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
it 'updates options from hashes' do
|
||||
options = ParentOptions.new(1)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
|
||||
updated = options.update a: 2, b: 3
|
||||
expect(options.a).to eq(2)
|
||||
expect(options.b).to eq(3)
|
||||
expect(updated).to eq(options)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete' do
|
||||
it 'allows to remove value for key' do
|
||||
options = ParentOptions.new(1)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.delete(:a)).to eq(1)
|
||||
expect(options.a).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#from' do
|
||||
it { expect { ParentOptions.from invalid: 1 }.to raise_error(NoMethodError) }
|
||||
|
||||
it 'works with options' do
|
||||
options = ParentOptions.new(1)
|
||||
|
||||
value = ParentOptions.from(options)
|
||||
expect(value.a).to eq(1)
|
||||
expect(value.b).to be_nil
|
||||
end
|
||||
|
||||
it 'works with options with sub object' do
|
||||
sub = SubOptions.new(1)
|
||||
options = ParentOptions.from a: 1, c: sub
|
||||
expect(options).to be_a_kind_of(ParentOptions)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
expect(options.c).to be_a_kind_of(SubOptions)
|
||||
expect(options.c.sub_a).to eq(1)
|
||||
end
|
||||
|
||||
it 'works with hash' do
|
||||
options = ParentOptions.from a: 1
|
||||
expect(options).to be_a_kind_of(ParentOptions)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
end
|
||||
|
||||
it 'works with hash with sub object' do
|
||||
options = ParentOptions.from a: 1, c: { sub_a: 1 }
|
||||
expect(options).to be_a_kind_of(ParentOptions)
|
||||
expect(options.a).to eq(1)
|
||||
expect(options.b).to be_nil
|
||||
expect(options.c).to be_a_kind_of(SubOptions)
|
||||
expect(options.c.sub_a).to eq(1)
|
||||
end
|
||||
|
||||
it 'works with deep hash' do
|
||||
hash = { b: 1 }
|
||||
options = ParentOptions.from a: hash
|
||||
expect(options.a[:b]).to eq(1)
|
||||
|
||||
hash[:b] = 2
|
||||
expect(options.a[:b]).to eq(1)
|
||||
|
||||
options.a[:b] = 3
|
||||
expect(hash[:b]).to eq(2)
|
||||
expect(options.a[:b]).to eq(3)
|
||||
end
|
||||
|
||||
it 'works with nil' do
|
||||
options = ParentOptions.from(nil)
|
||||
expect(options).to be_a_kind_of(ParentOptions)
|
||||
expect(options.a).to be_nil
|
||||
expect(options.b).to be_nil
|
||||
end
|
||||
|
||||
it 'respects inheritance' do
|
||||
subclass = Class.new(ParentOptions)
|
||||
options = subclass.from(c: { sub_a: 'hello' })
|
||||
expect(options.c).to be_a_kind_of(SubOptions)
|
||||
expect(options.c.sub_a).to eq('hello')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#memoized' do
|
||||
subject(:options_class) { Class.new(ParentOptions) }
|
||||
it 'requires block' do
|
||||
expect { options_class.memoized(:a) }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'accepts block' do
|
||||
options_class.memoized(:a) { :foo }
|
||||
expect(options_class.new.a).to eql(:foo)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#fetch' do
|
||||
subject { SubOptions.new }
|
||||
|
||||
context 'when the fetched key has no value' do
|
||||
it 'uses falsey default' do
|
||||
expect(subject.fetch(:sub_a, false) { |_| :blah }).to be_falsey
|
||||
end
|
||||
|
||||
it 'accepts block' do
|
||||
expect(subject.fetch(:sub_a) { |k| "yo #{k.inspect}" }).to eq('yo :sub_a')
|
||||
end
|
||||
|
||||
it 'needs a default if key is missing' do
|
||||
expect { subject.fetch(:sub_a) }.to raise_error(Faraday::Options.fetch_error_class)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the fetched key has a value' do
|
||||
before do
|
||||
subject.sub_a = 1
|
||||
end
|
||||
|
||||
it 'grabs value' do
|
||||
expect(subject.fetch(:sub_a, false) { |_| :blah }).to eq(1)
|
||||
end
|
||||
|
||||
it 'works with key' do
|
||||
expect(subject.fetch(:sub_a)).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::ProxyOptions do
|
||||
describe '#from' do
|
||||
it 'works with string' do
|
||||
options = Faraday::ProxyOptions.from 'http://user:pass@example.org'
|
||||
expect(options.user).to eq('user')
|
||||
expect(options.password).to eq('pass')
|
||||
expect(options.uri).to be_a_kind_of(URI)
|
||||
expect(options.path).to eq('')
|
||||
expect(options.port).to eq(80)
|
||||
expect(options.host).to eq('example.org')
|
||||
expect(options.scheme).to eq('http')
|
||||
expect(options.inspect).to match('#<Faraday::ProxyOptions uri=')
|
||||
end
|
||||
|
||||
it 'defaults to http' do
|
||||
options = Faraday::ProxyOptions.from 'example.org'
|
||||
expect(options.port).to eq(80)
|
||||
expect(options.host).to eq('example.org')
|
||||
expect(options.scheme).to eq('http')
|
||||
end
|
||||
|
||||
it 'works with nil' do
|
||||
options = Faraday::ProxyOptions.from nil
|
||||
expect(options).to be_a_kind_of(Faraday::ProxyOptions)
|
||||
expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
|
||||
end
|
||||
|
||||
it 'works with no auth' do
|
||||
proxy = Faraday::ProxyOptions.from 'http://example.org'
|
||||
expect(proxy.user).to be_nil
|
||||
expect(proxy.password).to be_nil
|
||||
end
|
||||
|
||||
it 'treats empty string as nil' do
|
||||
proxy = nil
|
||||
proxy_string = proxy.to_s # => empty string
|
||||
options = Faraday::ProxyOptions.from proxy_string
|
||||
expect(options).to be_a_kind_of(Faraday::ProxyOptions)
|
||||
expect(options.inspect).to eq('#<Faraday::ProxyOptions (empty)>')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows hash access' do
|
||||
proxy = Faraday::ProxyOptions.from 'http://a%40b:pw%20d@example.org'
|
||||
expect(proxy.user).to eq('a@b')
|
||||
expect(proxy[:user]).to eq('a@b')
|
||||
expect(proxy.password).to eq('pw d')
|
||||
expect(proxy[:password]).to eq('pw d')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::RequestOptions do
|
||||
subject(:options) { Faraday::RequestOptions.new }
|
||||
|
||||
it 'allows to set the request proxy' do
|
||||
expect(options.proxy).to be_nil
|
||||
|
||||
expect { options[:proxy] = { booya: 1 } }.to raise_error(NoMethodError)
|
||||
|
||||
options[:proxy] = { user: 'user' }
|
||||
expect(options.proxy).to be_a_kind_of(Faraday::ProxyOptions)
|
||||
expect(options.proxy.user).to eq('user')
|
||||
|
||||
options.proxy = nil
|
||||
expect(options.proxy).to be_nil
|
||||
expect(options.inspect).to eq('#<Faraday::RequestOptions (empty)>')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rack/utils'
|
||||
|
||||
RSpec.describe Faraday::FlatParamsEncoder do
|
||||
it_behaves_like 'a params encoder'
|
||||
|
||||
it 'decodes arrays' do
|
||||
query = 'a=one&a=two&a=three'
|
||||
expected = { 'a' => %w[one two three] }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes boolean values' do
|
||||
query = 'a=true&b=false'
|
||||
expected = { 'a' => 'true', 'b' => 'false' }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'encodes boolean values' do
|
||||
params = { a: true, b: false }
|
||||
expect(subject.encode(params)).to eq('a=true&b=false')
|
||||
end
|
||||
|
||||
it 'encodes boolean values in array' do
|
||||
params = { a: [true, false] }
|
||||
expect(subject.encode(params)).to eq('a=true&a=false')
|
||||
end
|
||||
|
||||
it 'encodes empty array in hash' do
|
||||
params = { a: [] }
|
||||
expect(subject.encode(params)).to eq('a=')
|
||||
end
|
||||
|
||||
it 'encodes unsorted when asked' do
|
||||
params = { b: false, a: true }
|
||||
expect(subject.encode(params)).to eq('a=true&b=false')
|
||||
Faraday::FlatParamsEncoder.sort_params = false
|
||||
expect(subject.encode(params)).to eq('b=false&a=true')
|
||||
Faraday::FlatParamsEncoder.sort_params = true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,150 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rack/utils'
|
||||
|
||||
RSpec.describe Faraday::NestedParamsEncoder do
|
||||
it_behaves_like 'a params encoder'
|
||||
|
||||
it 'decodes arrays' do
|
||||
query = 'a[1]=one&a[2]=two&a[3]=three'
|
||||
expected = { 'a' => %w[one two three] }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes hashes' do
|
||||
query = 'a[b1]=one&a[b2]=two&a[b][c]=foo'
|
||||
expected = { 'a' => { 'b1' => 'one', 'b2' => 'two', 'b' => { 'c' => 'foo' } } }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested arrays rack compat' do
|
||||
query = 'a[][one]=1&a[][two]=2&a[][one]=3&a[][two]=4'
|
||||
expected = Rack::Utils.parse_nested_query(query)
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested array mixed types' do
|
||||
query = 'a[][one]=1&a[]=2&a[]=&a[]'
|
||||
expected = Rack::Utils.parse_nested_query(query)
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested ignores invalid array' do
|
||||
query = '[][a]=1&b=2'
|
||||
expected = { 'a' => '1', 'b' => '2' }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested ignores repeated array notation' do
|
||||
query = 'a[][][]=1'
|
||||
expected = { 'a' => ['1'] }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested ignores malformed keys' do
|
||||
query = '=1&[]=2'
|
||||
expected = {}
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested subkeys dont have to be in brackets' do
|
||||
query = 'a[b]c[d]e=1'
|
||||
expected = { 'a' => { 'b' => { 'c' => { 'd' => { 'e' => '1' } } } } }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'decodes nested final value overrides any type' do
|
||||
query = 'a[b][c]=1&a[b]=2'
|
||||
expected = { 'a' => { 'b' => '2' } }
|
||||
expect(subject.decode(query)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'encodes rack compat' do
|
||||
params = { a: [{ one: '1', two: '2' }, '3', ''] }
|
||||
result = Faraday::Utils.unescape(Faraday::NestedParamsEncoder.encode(params)).split('&')
|
||||
expected = Rack::Utils.build_nested_query(params).split('&')
|
||||
expect(result).to match_array(expected)
|
||||
end
|
||||
|
||||
it 'encodes empty string array value' do
|
||||
expected = 'baz=&foo%5Bbar%5D='
|
||||
result = Faraday::NestedParamsEncoder.encode(foo: { bar: '' }, baz: '')
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
|
||||
it 'encodes nil array value' do
|
||||
expected = 'baz&foo%5Bbar%5D'
|
||||
result = Faraday::NestedParamsEncoder.encode(foo: { bar: nil }, baz: nil)
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
|
||||
it 'encodes empty array value' do
|
||||
expected = 'baz%5B%5D&foo%5Bbar%5D%5B%5D'
|
||||
result = Faraday::NestedParamsEncoder.encode(foo: { bar: [] }, baz: [])
|
||||
expect(result).to eq(expected)
|
||||
end
|
||||
|
||||
it 'encodes boolean values' do
|
||||
params = { a: true, b: false }
|
||||
expect(subject.encode(params)).to eq('a=true&b=false')
|
||||
end
|
||||
|
||||
it 'encodes boolean values in array' do
|
||||
params = { a: [true, false] }
|
||||
expect(subject.encode(params)).to eq('a%5B%5D=true&a%5B%5D=false')
|
||||
end
|
||||
|
||||
it 'encodes unsorted when asked' do
|
||||
params = { b: false, a: true }
|
||||
expect(subject.encode(params)).to eq('a=true&b=false')
|
||||
Faraday::NestedParamsEncoder.sort_params = false
|
||||
expect(subject.encode(params)).to eq('b=false&a=true')
|
||||
Faraday::NestedParamsEncoder.sort_params = true
|
||||
end
|
||||
|
||||
it 'encodes arrays indices when asked' do
|
||||
params = { a: [0, 1, 2] }
|
||||
expect(subject.encode(params)).to eq('a%5B%5D=0&a%5B%5D=1&a%5B%5D=2')
|
||||
Faraday::NestedParamsEncoder.array_indices = true
|
||||
expect(subject.encode(params)).to eq('a%5B0%5D=0&a%5B1%5D=1&a%5B2%5D=2')
|
||||
Faraday::NestedParamsEncoder.array_indices = false
|
||||
end
|
||||
|
||||
shared_examples 'a wrong decoding' do
|
||||
it do
|
||||
expect { subject.decode(query) }.to raise_error(TypeError) do |e|
|
||||
expect(e.message).to eq(error_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expecting hash but getting string' do
|
||||
let(:query) { 'a=1&a[b]=2' }
|
||||
let(:error_message) { "expected Hash (got String) for param `a'" }
|
||||
it_behaves_like 'a wrong decoding'
|
||||
end
|
||||
|
||||
context 'when expecting hash but getting array' do
|
||||
let(:query) { 'a[]=1&a[b]=2' }
|
||||
let(:error_message) { "expected Hash (got Array) for param `a'" }
|
||||
it_behaves_like 'a wrong decoding'
|
||||
end
|
||||
|
||||
context 'when expecting nested hash but getting non nested' do
|
||||
let(:query) { 'a[b]=1&a[b][c]=2' }
|
||||
let(:error_message) { "expected Hash (got String) for param `b'" }
|
||||
it_behaves_like 'a wrong decoding'
|
||||
end
|
||||
|
||||
context 'when expecting array but getting hash' do
|
||||
let(:query) { 'a[b]=1&a[]=2' }
|
||||
let(:error_message) { "expected Array (got Hash) for param `a'" }
|
||||
it_behaves_like 'a wrong decoding'
|
||||
end
|
||||
|
||||
context 'when expecting array but getting string' do
|
||||
let(:query) { 'a=1&a[]=2' }
|
||||
let(:error_message) { "expected Array (got String) for param `a'" }
|
||||
it_behaves_like 'a wrong decoding'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,317 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::RackBuilder do
|
||||
# mock handler classes
|
||||
(Handler = Struct.new(:app)).class_eval do
|
||||
def call(env)
|
||||
env[:request_headers]['X-Middleware'] ||= ''
|
||||
env[:request_headers]['X-Middleware'] += ":#{self.class.name.split('::').last}"
|
||||
app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
class Apple < Handler
|
||||
end
|
||||
|
||||
class Orange < Handler
|
||||
end
|
||||
|
||||
class Banana < Handler
|
||||
end
|
||||
|
||||
subject { conn.builder }
|
||||
before { Faraday.default_adapter = :test }
|
||||
after { Faraday.default_adapter = nil }
|
||||
|
||||
context 'with default stack' do
|
||||
let(:conn) { Faraday::Connection.new }
|
||||
|
||||
it { expect(subject[0]).to eq(Faraday::Request.lookup_middleware(:url_encoded)) }
|
||||
it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(Faraday.default_adapter)) }
|
||||
end
|
||||
|
||||
context 'with custom empty block' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
it { expect(subject[0]).to be_nil }
|
||||
it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(Faraday.default_adapter)) }
|
||||
end
|
||||
|
||||
context 'with custom adapter only' do
|
||||
let(:conn) do
|
||||
Faraday::Connection.new do |builder|
|
||||
builder.adapter :test do |stub|
|
||||
stub.get('/') { |_| [200, {}, ''] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(subject[0]).to be_nil }
|
||||
it { expect(subject.adapter).to eq(Faraday::Adapter.lookup_middleware(:test)) }
|
||||
end
|
||||
|
||||
context 'with custom handler and adapter' do
|
||||
let(:conn) do
|
||||
Faraday::Connection.new do |builder|
|
||||
builder.use Apple
|
||||
builder.adapter :test do |stub|
|
||||
stub.get('/') { |_| [200, {}, ''] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'locks the stack after making a request' do
|
||||
expect(subject.locked?).to be_falsey
|
||||
conn.get('/')
|
||||
expect(subject.locked?).to be_truthy
|
||||
expect { subject.use(Orange) }.to raise_error(Faraday::RackBuilder::StackLocked)
|
||||
end
|
||||
|
||||
it 'dup stack is unlocked' do
|
||||
expect(subject.locked?).to be_falsey
|
||||
subject.lock!
|
||||
expect(subject.locked?).to be_truthy
|
||||
dup = subject.dup
|
||||
expect(dup).to eq(subject)
|
||||
expect(dup.locked?).to be_falsey
|
||||
end
|
||||
|
||||
it 'allows to compare handlers' do
|
||||
expect(subject.handlers.first).to eq(Faraday::RackBuilder::Handler.new(Apple))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when having a single handler' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
before { subject.use(Apple) }
|
||||
|
||||
it { expect(subject.handlers).to eq([Apple]) }
|
||||
|
||||
it 'allows use' do
|
||||
subject.use(Orange)
|
||||
expect(subject.handlers).to eq([Apple, Orange])
|
||||
end
|
||||
|
||||
it 'allows insert_before' do
|
||||
subject.insert_before(Apple, Orange)
|
||||
expect(subject.handlers).to eq([Orange, Apple])
|
||||
end
|
||||
|
||||
it 'allows insert_after' do
|
||||
subject.insert_after(Apple, Orange)
|
||||
expect(subject.handlers).to eq([Apple, Orange])
|
||||
end
|
||||
|
||||
it 'raises an error trying to use an unregistered symbol' do
|
||||
expect { subject.use(:apple) }.to raise_error(Faraday::Error) do |err|
|
||||
expect(err.message).to eq(':apple is not registered on Faraday::Middleware')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when having two handlers' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
before do
|
||||
subject.use(Apple)
|
||||
subject.use(Orange)
|
||||
end
|
||||
|
||||
it 'allows insert_before' do
|
||||
subject.insert_before(Orange, Banana)
|
||||
expect(subject.handlers).to eq([Apple, Banana, Orange])
|
||||
end
|
||||
|
||||
it 'allows insert_after' do
|
||||
subject.insert_after(Apple, Banana)
|
||||
expect(subject.handlers).to eq([Apple, Banana, Orange])
|
||||
end
|
||||
|
||||
it 'allows to swap handlers' do
|
||||
subject.swap(Apple, Banana)
|
||||
expect(subject.handlers).to eq([Banana, Orange])
|
||||
end
|
||||
|
||||
it 'allows to delete a handler' do
|
||||
subject.delete(Apple)
|
||||
expect(subject.handlers).to eq([Orange])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when adapter is added with named options' do
|
||||
after { Faraday.default_adapter_options = {} }
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:cat_adapter) do
|
||||
Class.new(Faraday::Adapter) do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(app, name:)
|
||||
super(app)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:cat) { subject.adapter.build }
|
||||
|
||||
it 'adds a handler to construct adapter with named options' do
|
||||
Faraday.default_adapter = cat_adapter
|
||||
Faraday.default_adapter_options = { name: 'Chloe' }
|
||||
expect { cat }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(cat.name).to eq 'Chloe'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when middleware is added with named arguments' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:dog_middleware) do
|
||||
Class.new(Faraday::Middleware) do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(app, name:)
|
||||
super(app)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:dog) do
|
||||
subject.handlers.find { |handler| handler == dog_middleware }.build
|
||||
end
|
||||
|
||||
it 'adds a handler to construct middleware with options passed to use' do
|
||||
subject.use dog_middleware, name: 'Rex'
|
||||
expect { dog }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(dog.name).to eq('Rex')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a middleware is added with named arguments' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:cat_request) do
|
||||
Class.new(Faraday::Middleware) do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(app, name:)
|
||||
super(app)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:cat) do
|
||||
subject.handlers.find { |handler| handler == cat_request }.build
|
||||
end
|
||||
|
||||
it 'adds a handler to construct request adapter with options passed to request' do
|
||||
Faraday::Request.register_middleware cat_request: cat_request
|
||||
subject.request :cat_request, name: 'Felix'
|
||||
expect { cat }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(cat.name).to eq('Felix')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a middleware is added with named arguments' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:fish_response) do
|
||||
Class.new(Faraday::Middleware) do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(app, name:)
|
||||
super(app)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:fish) do
|
||||
subject.handlers.find { |handler| handler == fish_response }.build
|
||||
end
|
||||
|
||||
it 'adds a handler to construct response adapter with options passed to response' do
|
||||
Faraday::Response.register_middleware fish_response: fish_response
|
||||
subject.response :fish_response, name: 'Bubbles'
|
||||
expect { fish }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(fish.name).to eq('Bubbles')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a plain adapter is added with named arguments' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:rabbit_adapter) do
|
||||
Class.new(Faraday::Adapter) do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(app, name:)
|
||||
super(app)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:rabbit) do
|
||||
subject.adapter.build
|
||||
end
|
||||
|
||||
it 'adds a handler to construct adapter with options passed to adapter' do
|
||||
Faraday::Adapter.register_middleware rabbit_adapter: rabbit_adapter
|
||||
subject.adapter :rabbit_adapter, name: 'Thumper'
|
||||
expect { rabbit }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(rabbit.name).to eq('Thumper')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when handlers are directly added or updated' do
|
||||
let(:conn) { Faraday::Connection.new {} }
|
||||
|
||||
let(:rock_handler) do
|
||||
Class.new do
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(_app, name:)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:rock) do
|
||||
subject.handlers.find { |handler| handler == rock_handler }.build
|
||||
end
|
||||
|
||||
it 'adds a handler to construct adapter with options passed to insert' do
|
||||
subject.insert 0, rock_handler, name: 'Stony'
|
||||
expect { rock }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(rock.name).to eq('Stony')
|
||||
end
|
||||
|
||||
it 'adds a handler with options passed to insert_after' do
|
||||
subject.insert_after 0, rock_handler, name: 'Rocky'
|
||||
expect { rock }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(rock.name).to eq('Rocky')
|
||||
end
|
||||
|
||||
it 'adds a handler with options passed to swap' do
|
||||
subject.insert 0, rock_handler, name: 'Flint'
|
||||
subject.swap 0, rock_handler, name: 'Chert'
|
||||
expect { rock }.to_not output(
|
||||
/warning: Using the last argument as keyword parameters is deprecated/
|
||||
).to_stderr
|
||||
expect(rock.name).to eq('Chert')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Authorization do
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.request :authorization, auth_type, *auth_config
|
||||
b.adapter :test do |stub|
|
||||
stub.get('/auth-echo') do |env|
|
||||
[200, {}, env[:request_headers]['Authorization']]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not interfere with existing authentication' do
|
||||
context 'and request already has an authentication header' do
|
||||
let(:response) { conn.get('/auth-echo', nil, authorization: 'OAuth oauth_token') }
|
||||
|
||||
it 'does not interfere with existing authorization' do
|
||||
expect(response.body).to eq('OAuth oauth_token')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:response) { conn.get('/auth-echo') }
|
||||
|
||||
describe 'basic_auth' do
|
||||
let(:auth_type) { :basic }
|
||||
|
||||
context 'when passed correct params' do
|
||||
let(:auth_config) { %w[aladdin opensesame] }
|
||||
|
||||
it { expect(response.body).to eq('Basic YWxhZGRpbjpvcGVuc2VzYW1l') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed very long values' do
|
||||
let(:auth_config) { ['A' * 255, ''] }
|
||||
|
||||
it { expect(response.body).to eq("Basic #{'QUFB' * 85}Og==") }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'authorization' do
|
||||
let(:auth_type) { :Bearer }
|
||||
|
||||
context 'when passed a string' do
|
||||
let(:auth_config) { ['custom'] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer custom') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed a proc' do
|
||||
let(:auth_config) { [-> { 'custom_from_proc' }] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer custom_from_proc') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed a callable' do
|
||||
let(:callable) { double('Callable Authorizer', call: 'custom_from_callable') }
|
||||
let(:auth_config) { [callable] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer custom_from_callable') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'with an argument' do
|
||||
let(:response) { conn.get('/auth-echo', nil, 'middle' => 'crunchy surprise') }
|
||||
|
||||
context 'when passed a proc' do
|
||||
let(:auth_config) { [proc { |env| "proc #{env.request_headers['middle']}" }] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer proc crunchy surprise') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed a lambda' do
|
||||
let(:auth_config) { [->(env) { "lambda #{env.request_headers['middle']}" }] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer lambda crunchy surprise') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
|
||||
context 'when passed a callable with an argument' do
|
||||
let(:callable) do
|
||||
Class.new do
|
||||
def call(env)
|
||||
"callable #{env.request_headers['middle']}"
|
||||
end
|
||||
end.new
|
||||
end
|
||||
let(:auth_config) { [callable] }
|
||||
|
||||
it { expect(response.body).to eq('Bearer callable crunchy surprise') }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when passed too many arguments' do
|
||||
let(:auth_config) { %w[baz foo] }
|
||||
|
||||
it { expect { response }.to raise_error(ArgumentError) }
|
||||
|
||||
include_examples 'does not interfere with existing authentication'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Instrumentation do
|
||||
class FakeInstrumenter
|
||||
attr_reader :instrumentations
|
||||
|
||||
def initialize
|
||||
@instrumentations = []
|
||||
end
|
||||
|
||||
def instrument(name, env)
|
||||
@instrumentations << [name, env]
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
let(:config) { {} }
|
||||
let(:options) { Faraday::Request::Instrumentation::Options.from config }
|
||||
let(:instrumenter) { FakeInstrumenter.new }
|
||||
let(:conn) do
|
||||
Faraday.new do |f|
|
||||
f.request :instrumentation, config.merge(instrumenter: instrumenter)
|
||||
f.adapter :test do |stub|
|
||||
stub.get '/' do
|
||||
[200, {}, 'ok']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(options.name).to eq('request.faraday') }
|
||||
it 'defaults to ActiveSupport::Notifications' do
|
||||
res = options.instrumenter
|
||||
rescue NameError => e
|
||||
expect(e.to_s).to match('ActiveSupport')
|
||||
else
|
||||
expect(res).to eq(ActiveSupport::Notifications)
|
||||
end
|
||||
|
||||
it 'instruments with default name' do
|
||||
expect(instrumenter.instrumentations.size).to eq(0)
|
||||
|
||||
res = conn.get '/'
|
||||
expect(res.body).to eq('ok')
|
||||
expect(instrumenter.instrumentations.size).to eq(1)
|
||||
|
||||
name, env = instrumenter.instrumentations.first
|
||||
expect(name).to eq('request.faraday')
|
||||
expect(env[:url].path).to eq('/')
|
||||
end
|
||||
|
||||
context 'with custom name' do
|
||||
let(:config) { { name: 'custom' } }
|
||||
|
||||
it { expect(options.name).to eq('custom') }
|
||||
it 'instruments with custom name' do
|
||||
expect(instrumenter.instrumentations.size).to eq(0)
|
||||
|
||||
res = conn.get '/'
|
||||
expect(res.body).to eq('ok')
|
||||
expect(instrumenter.instrumentations.size).to eq(1)
|
||||
|
||||
name, env = instrumenter.instrumentations.first
|
||||
expect(name).to eq('custom')
|
||||
expect(env[:url].path).to eq('/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom instrumenter' do
|
||||
let(:config) { { instrumenter: :custom } }
|
||||
|
||||
it { expect(options.instrumenter).to eq(:custom) }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request::Json do
|
||||
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
|
||||
|
||||
def process(body, content_type = nil)
|
||||
env = { body: body, request_headers: Faraday::Utils::Headers.new }
|
||||
env[:request_headers]['content-type'] = content_type if content_type
|
||||
middleware.call(Faraday::Env.from(env)).env
|
||||
end
|
||||
|
||||
def result_body
|
||||
result[:body]
|
||||
end
|
||||
|
||||
def result_type
|
||||
result[:request_headers]['content-type']
|
||||
end
|
||||
|
||||
context 'no body' do
|
||||
let(:result) { process(nil) }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to be_nil
|
||||
end
|
||||
|
||||
it "doesn't add content type" do
|
||||
expect(result_type).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty body' do
|
||||
let(:result) { process('') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to be_empty
|
||||
end
|
||||
|
||||
it "doesn't add content type" do
|
||||
expect(result_type).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'string body' do
|
||||
let(:result) { process('{"a":1}') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body' do
|
||||
let(:result) { process(a: 1) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'empty object body' do
|
||||
let(:result) { process({}) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'true body' do
|
||||
let(:result) { process(true) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('true')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'false body' do
|
||||
let(:result) { process(false) }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('false')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with json type' do
|
||||
let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/json; charset=utf-8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with vendor json type' do
|
||||
let(:result) { process({ a: 1 }, 'application/vnd.myapp.v1+json; charset=utf-8') }
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/vnd.myapp.v1+json; charset=utf-8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'object body with incompatible type' do
|
||||
let(:result) { process({ a: 1 }, 'application/xml; charset=utf-8') }
|
||||
|
||||
it "doesn't change body" do
|
||||
expect(result_body).to eq(a: 1)
|
||||
end
|
||||
|
||||
it "doesn't change content type" do
|
||||
expect(result_type).to eq('application/xml; charset=utf-8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with encoder' do
|
||||
let(:encoder) do
|
||||
double('Encoder').tap do |e|
|
||||
allow(e).to receive(:dump) { |s, opts| JSON.generate(s, opts) }
|
||||
end
|
||||
end
|
||||
|
||||
let(:result) { process(a: 1) }
|
||||
|
||||
context 'when encoder is passed as object' do
|
||||
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: encoder }) }
|
||||
|
||||
it 'calls specified JSON encoder\'s dump method' do
|
||||
expect(encoder).to receive(:dump).with({ a: 1 })
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when encoder is passed as an object-method pair' do
|
||||
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: [encoder, :dump] }) }
|
||||
|
||||
it 'calls specified JSON encoder' do
|
||||
expect(encoder).to receive(:dump).with({ a: 1 })
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when encoder is not passed' do
|
||||
let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
|
||||
|
||||
it 'calls JSON.generate' do
|
||||
expect(JSON).to receive(:generate).with({ a: 1 })
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
it 'encodes body' do
|
||||
expect(result_body).to eq('{"a":1}')
|
||||
end
|
||||
|
||||
it 'adds content type' do
|
||||
expect(result_type).to eq('application/json')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
|
||||
RSpec.describe Faraday::Request::UrlEncoded do
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.request :url_encoded
|
||||
b.adapter :test do |stub|
|
||||
stub.post('/echo') do |env|
|
||||
posted_as = env[:request_headers]['Content-Type']
|
||||
body = env[:body]
|
||||
if body.respond_to?(:read)
|
||||
body = body.read
|
||||
end
|
||||
[200, { 'Content-Type' => posted_as }, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'does nothing without payload' do
|
||||
response = conn.post('/echo')
|
||||
expect(response.headers['Content-Type']).to be_nil
|
||||
expect(response.body.empty?).to be_truthy
|
||||
end
|
||||
|
||||
it 'ignores custom content type' do
|
||||
response = conn.post('/echo', { some: 'data' }, 'content-type' => 'application/x-foo')
|
||||
expect(response.headers['Content-Type']).to eq('application/x-foo')
|
||||
expect(response.body).to eq(some: 'data')
|
||||
end
|
||||
|
||||
it 'works with no headers' do
|
||||
response = conn.post('/echo', fruit: %w[apples oranges])
|
||||
expect(response.headers['Content-Type']).to eq('application/x-www-form-urlencoded')
|
||||
expect(response.body).to eq('fruit%5B%5D=apples&fruit%5B%5D=oranges')
|
||||
end
|
||||
|
||||
it 'works with with headers' do
|
||||
response = conn.post('/echo', { 'a' => 123 }, 'content-type' => 'application/x-www-form-urlencoded')
|
||||
expect(response.headers['Content-Type']).to eq('application/x-www-form-urlencoded')
|
||||
expect(response.body).to eq('a=123')
|
||||
end
|
||||
|
||||
it 'works with nested params' do
|
||||
response = conn.post('/echo', user: { name: 'Mislav', web: 'mislav.net' })
|
||||
expect(response.headers['Content-Type']).to eq('application/x-www-form-urlencoded')
|
||||
expected = { 'user' => { 'name' => 'Mislav', 'web' => 'mislav.net' } }
|
||||
expect(Faraday::Utils.parse_nested_query(response.body)).to eq(expected)
|
||||
end
|
||||
|
||||
it 'works with non nested params' do
|
||||
response = conn.post('/echo', dimensions: %w[date location]) do |req|
|
||||
req.options.params_encoder = Faraday::FlatParamsEncoder
|
||||
end
|
||||
expect(response.headers['Content-Type']).to eq('application/x-www-form-urlencoded')
|
||||
expected = { 'dimensions' => %w[date location] }
|
||||
expect(Faraday::Utils.parse_query(response.body)).to eq(expected)
|
||||
expect(response.body).to eq('dimensions=date&dimensions=location')
|
||||
end
|
||||
|
||||
it 'works with unicode' do
|
||||
err = capture_warnings do
|
||||
response = conn.post('/echo', str: 'eé cç aã aâ')
|
||||
expect(response.body).to eq('str=e%C3%A9+c%C3%A7+a%C3%A3+a%C3%A2')
|
||||
end
|
||||
expect(err.empty?).to be_truthy
|
||||
end
|
||||
|
||||
it 'works with nested keys' do
|
||||
response = conn.post('/echo', 'a' => { 'b' => { 'c' => ['d'] } })
|
||||
expect(response.body).to eq('a%5Bb%5D%5Bc%5D%5B%5D=d')
|
||||
end
|
||||
|
||||
it 'works with files' do
|
||||
response = conn.post('/echo', StringIO.new('str=apple'))
|
||||
expect(response.body).to eq('str=apple')
|
||||
end
|
||||
|
||||
context 'customising default_space_encoding' do
|
||||
around do |example|
|
||||
Faraday::Utils.default_space_encoding = '%20'
|
||||
example.run
|
||||
Faraday::Utils.default_space_encoding = nil
|
||||
end
|
||||
|
||||
it 'uses the custom character to encode spaces' do
|
||||
response = conn.post('/echo', str: 'apple banana')
|
||||
expect(response.body).to eq('str=apple%20banana')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Request do
|
||||
let(:conn) do
|
||||
Faraday.new(url: 'http://httpbingo.org/api',
|
||||
headers: { 'Mime-Version' => '1.0' },
|
||||
request: { oauth: { consumer_key: 'anonymous' } })
|
||||
end
|
||||
let(:http_method) { :get }
|
||||
let(:block) { nil }
|
||||
|
||||
subject { conn.build_request(http_method, &block) }
|
||||
|
||||
context 'when nothing particular is configured' do
|
||||
it { expect(subject.http_method).to eq(:get) }
|
||||
it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
|
||||
it { expect(subject.to_env(conn).ssl.verify_hostname).to be_falsey }
|
||||
end
|
||||
|
||||
context 'when HTTP method is post' do
|
||||
let(:http_method) { :post }
|
||||
|
||||
it { expect(subject.http_method).to eq(:post) }
|
||||
end
|
||||
|
||||
context 'when setting the url on setup with a URI' do
|
||||
let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
|
||||
|
||||
it { expect(subject.path).to eq(URI.parse('foo.json')) }
|
||||
it { expect(subject.params).to eq('a' => '1') }
|
||||
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
||||
end
|
||||
|
||||
context 'when setting the url on setup with a string path and params' do
|
||||
let(:block) { proc { |req| req.url 'foo.json', 'a' => 1 } }
|
||||
|
||||
it { expect(subject.path).to eq('foo.json') }
|
||||
it { expect(subject.params).to eq('a' => 1) }
|
||||
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
|
||||
end
|
||||
|
||||
context 'when setting the url on setup with a path including params' do
|
||||
let(:block) { proc { |req| req.url 'foo.json?b=2&a=1#qqq' } }
|
||||
|
||||
it { expect(subject.path).to eq('foo.json') }
|
||||
it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
|
||||
it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
|
||||
end
|
||||
|
||||
context 'when setting a header on setup with []= syntax' do
|
||||
let(:block) { proc { |req| req['Server'] = 'Faraday' } }
|
||||
let(:headers) { subject.to_env(conn).request_headers }
|
||||
|
||||
it { expect(subject.headers['Server']).to eq('Faraday') }
|
||||
it { expect(headers['mime-version']).to eq('1.0') }
|
||||
it { expect(headers['server']).to eq('Faraday') }
|
||||
end
|
||||
|
||||
context 'when setting the body on setup' do
|
||||
let(:block) { proc { |req| req.body = 'hi' } }
|
||||
|
||||
it { expect(subject.body).to eq('hi') }
|
||||
it { expect(subject.to_env(conn).body).to eq('hi') }
|
||||
end
|
||||
|
||||
context 'with global request options set' do
|
||||
let(:env_request) { subject.to_env(conn).request }
|
||||
|
||||
before do
|
||||
conn.options.timeout = 3
|
||||
conn.options.open_timeout = 5
|
||||
conn.ssl.verify = false
|
||||
conn.proxy = 'http://proxy.com'
|
||||
end
|
||||
|
||||
it { expect(subject.options.timeout).to eq(3) }
|
||||
it { expect(subject.options.open_timeout).to eq(5) }
|
||||
it { expect(env_request.timeout).to eq(3) }
|
||||
it { expect(env_request.open_timeout).to eq(5) }
|
||||
|
||||
context 'and per-request options set' do
|
||||
let(:block) do
|
||||
proc do |req|
|
||||
req.options.timeout = 10
|
||||
req.options.boundary = 'boo'
|
||||
req.options.oauth[:consumer_secret] = 'xyz'
|
||||
req.options.context = {
|
||||
foo: 'foo',
|
||||
bar: 'bar'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it { expect(subject.options.timeout).to eq(10) }
|
||||
it { expect(subject.options.open_timeout).to eq(5) }
|
||||
it { expect(env_request.timeout).to eq(10) }
|
||||
it { expect(env_request.open_timeout).to eq(5) }
|
||||
it { expect(env_request.boundary).to eq('boo') }
|
||||
it { expect(env_request.context).to eq(foo: 'foo', bar: 'bar') }
|
||||
it do
|
||||
oauth_expected = { consumer_secret: 'xyz', consumer_key: 'anonymous' }
|
||||
expect(env_request.oauth).to eq(oauth_expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'supports marshal serialization' do
|
||||
expect(Marshal.load(Marshal.dump(subject))).to eq(subject)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,189 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Response::Json, type: :response do
|
||||
let(:options) { {} }
|
||||
let(:headers) { {} }
|
||||
let(:middleware) do
|
||||
described_class.new(lambda { |env|
|
||||
Faraday::Response.new(env)
|
||||
}, **options)
|
||||
end
|
||||
|
||||
def process(body, content_type = 'application/json', options = {})
|
||||
env = {
|
||||
body: body, request: options,
|
||||
request_headers: Faraday::Utils::Headers.new,
|
||||
response_headers: Faraday::Utils::Headers.new(headers)
|
||||
}
|
||||
env[:response_headers]['content-type'] = content_type if content_type
|
||||
yield(env) if block_given?
|
||||
middleware.call(Faraday::Env.from(env))
|
||||
end
|
||||
|
||||
context 'no type matching' do
|
||||
it "doesn't change nil body" do
|
||||
expect(process(nil).body).to be_nil
|
||||
end
|
||||
|
||||
it 'nullifies empty body' do
|
||||
expect(process('').body).to be_nil
|
||||
end
|
||||
|
||||
it 'parses json body' do
|
||||
response = process('{"a":1}')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
expect(response.env[:raw_body]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with preserving raw' do
|
||||
let(:options) { { preserve_raw: true } }
|
||||
|
||||
it 'parses json body' do
|
||||
response = process('{"a":1}')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
expect(response.env[:raw_body]).to eq('{"a":1}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default regexp type matching' do
|
||||
it 'parses json body of correct type' do
|
||||
response = process('{"a":1}', 'application/x-json')
|
||||
expect(response.body).to eq('a' => 1)
|
||||
end
|
||||
|
||||
it 'ignores json body of incorrect type' do
|
||||
response = process('{"a":1}', 'text/json-xml')
|
||||
expect(response.body).to eq('{"a":1}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array type matching' do
|
||||
let(:options) { { content_type: %w[a/b c/d] } }
|
||||
|
||||
it 'parses json body of correct type' do
|
||||
expect(process('{"a":1}', 'a/b').body).to be_a(Hash)
|
||||
expect(process('{"a":1}', 'c/d').body).to be_a(Hash)
|
||||
end
|
||||
|
||||
it 'ignores json body of incorrect type' do
|
||||
expect(process('{"a":1}', 'a/d').body).not_to be_a(Hash)
|
||||
end
|
||||
end
|
||||
|
||||
it 'chokes on invalid json' do
|
||||
expect { process('{!') }.to raise_error(Faraday::ParsingError)
|
||||
end
|
||||
|
||||
it 'includes the response on the ParsingError instance' do
|
||||
process('{') { |env| env[:response] = Faraday::Response.new }
|
||||
raise 'Parsing should have failed.'
|
||||
rescue Faraday::ParsingError => e
|
||||
expect(e.response).to be_a(Faraday::Response)
|
||||
end
|
||||
|
||||
context 'HEAD responses' do
|
||||
it "nullifies the body if it's only one space" do
|
||||
response = process(' ')
|
||||
expect(response.body).to be_nil
|
||||
end
|
||||
|
||||
it "nullifies the body if it's two spaces" do
|
||||
response = process(' ')
|
||||
expect(response.body).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'JSON options' do
|
||||
let(:body) { '{"a": 1}' }
|
||||
let(:result) { { a: 1 } }
|
||||
let(:options) do
|
||||
{
|
||||
parser_options: {
|
||||
symbolize_names: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'passes relevant options to JSON parse' do
|
||||
expect(::JSON).to receive(:parse)
|
||||
.with(body, options[:parser_options])
|
||||
.and_return(result)
|
||||
|
||||
response = process(body)
|
||||
expect(response.body).to eq(result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with decoder' do
|
||||
let(:decoder) do
|
||||
double('Decoder').tap do |e|
|
||||
allow(e).to receive(:load) { |s, opts| JSON.parse(s, opts) }
|
||||
end
|
||||
end
|
||||
|
||||
let(:body) { '{"a": 1}' }
|
||||
let(:result) { { a: 1 } }
|
||||
|
||||
context 'when decoder is passed as object' do
|
||||
let(:options) do
|
||||
{
|
||||
parser_options: {
|
||||
decoder: decoder,
|
||||
option: :option_value,
|
||||
symbolize_names: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'passes relevant options to specified decoder\'s load method' do
|
||||
expect(decoder).to receive(:load)
|
||||
.with(body, { option: :option_value, symbolize_names: true })
|
||||
.and_return(result)
|
||||
|
||||
response = process(body)
|
||||
expect(response.body).to eq(result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when decoder is passed as an object-method pair' do
|
||||
let(:options) do
|
||||
{
|
||||
parser_options: {
|
||||
decoder: [decoder, :load],
|
||||
option: :option_value,
|
||||
symbolize_names: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'passes relevant options to specified decoder\'s method' do
|
||||
expect(decoder).to receive(:load)
|
||||
.with(body, { option: :option_value, symbolize_names: true })
|
||||
.and_return(result)
|
||||
|
||||
response = process(body)
|
||||
expect(response.body).to eq(result)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when decoder is not passed' do
|
||||
let(:options) do
|
||||
{
|
||||
parser_options: {
|
||||
symbolize_names: true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'passes relevant options to JSON parse' do
|
||||
expect(JSON).to receive(:parse)
|
||||
.with(body, { symbolize_names: true })
|
||||
.and_return(result)
|
||||
|
||||
response = process(body)
|
||||
expect(response.body).to eq(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,258 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'stringio'
|
||||
require 'logger'
|
||||
|
||||
RSpec.describe Faraday::Response::Logger do
|
||||
let(:string_io) { StringIO.new }
|
||||
let(:logger) { Logger.new(string_io) }
|
||||
let(:logger_options) { {} }
|
||||
let(:conn) do
|
||||
rubbles = ['Barney', 'Betty', 'Bam Bam']
|
||||
|
||||
Faraday.new do |b|
|
||||
b.response :logger, logger, logger_options do |logger|
|
||||
logger.filter(/(soylent green is) (.+)/, '\1 tasty')
|
||||
logger.filter(/(api_key:).*"(.+)."/, '\1[API_KEY]')
|
||||
logger.filter(/(password)=(.+)/, '\1=[HIDDEN]')
|
||||
end
|
||||
b.adapter :test do |stubs|
|
||||
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
|
||||
stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] }
|
||||
stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] }
|
||||
stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] }
|
||||
stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] }
|
||||
stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
|
||||
stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
|
||||
stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
|
||||
stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
logger.level = Logger::DEBUG
|
||||
end
|
||||
|
||||
it 'still returns output' do
|
||||
resp = conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(resp.body).to eq('hello')
|
||||
end
|
||||
|
||||
context 'without configuration' do
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.response :logger
|
||||
b.adapter :test do |stubs|
|
||||
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'defaults to stdout' do
|
||||
expect(Logger).to receive(:new).with($stdout).and_return(Logger.new(nil))
|
||||
conn.get('/hello')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with default formatter' do
|
||||
let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
|
||||
|
||||
before { allow(Faraday::Logging::Formatter).to receive(:new).and_return(formatter) }
|
||||
|
||||
it 'delegates logging to the formatter' do
|
||||
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
|
||||
expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
|
||||
conn.get '/hello'
|
||||
end
|
||||
|
||||
context 'when no route' do
|
||||
it 'delegates logging to the formatter' do
|
||||
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
|
||||
expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
|
||||
|
||||
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with custom formatter' do
|
||||
let(:formatter_class) do
|
||||
Class.new(Faraday::Logging::Formatter) do
|
||||
def request(_env)
|
||||
info 'Custom log formatter request'
|
||||
end
|
||||
|
||||
def response(_env)
|
||||
info 'Custom log formatter response'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:logger_options) { { formatter: formatter_class } }
|
||||
|
||||
it 'logs with custom formatter' do
|
||||
conn.get '/hello'
|
||||
|
||||
expect(string_io.string).to match('Custom log formatter request')
|
||||
expect(string_io.string).to match('Custom log formatter response')
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs method and url' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match('GET http:/hello')
|
||||
end
|
||||
|
||||
it 'logs status' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match('Status 200')
|
||||
end
|
||||
|
||||
it 'does not log error message by default' do
|
||||
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
||||
expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
|
||||
end
|
||||
|
||||
it 'logs request headers by default' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%(Accept: "text/html))
|
||||
end
|
||||
|
||||
it 'logs response headers by default' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%(Content-Type: "text/html))
|
||||
end
|
||||
|
||||
it 'does not log request body by default' do
|
||||
conn.post '/ohai', 'name=Unagi', accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(name=Unagi))
|
||||
end
|
||||
|
||||
it 'does not log response body by default' do
|
||||
conn.post '/ohai', 'name=Toro', accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(fred))
|
||||
end
|
||||
|
||||
it 'logs filter headers' do
|
||||
conn.headers = { 'api_key' => 'ABC123' }
|
||||
conn.get '/filtered_headers', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%(api_key:))
|
||||
expect(string_io.string).to match(%([API_KEY]))
|
||||
expect(string_io.string).not_to match(%(ABC123))
|
||||
end
|
||||
|
||||
it 'logs filter url' do
|
||||
conn.get '/filtered_url?password=hunter2', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%([HIDDEN]))
|
||||
expect(string_io.string).not_to match(%(hunter2))
|
||||
end
|
||||
|
||||
context 'when not logging request headers' do
|
||||
let(:logger_options) { { headers: { request: false } } }
|
||||
|
||||
it 'does not log request headers if option is false' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(Accept: "text/html))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not logging response headers' do
|
||||
let(:logger_options) { { headers: { response: false } } }
|
||||
|
||||
it 'does not log response headers if option is false' do
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(Content-Type: "text/html))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logging request body' do
|
||||
let(:logger_options) { { bodies: { request: true } } }
|
||||
|
||||
it 'log only request body' do
|
||||
conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
|
||||
expect(string_io.string).to match(%(name=Tamago))
|
||||
expect(string_io.string).not_to match(%(pebbles))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logging response body' do
|
||||
let(:logger_options) { { bodies: { response: true } } }
|
||||
|
||||
it 'log only response body' do
|
||||
conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
|
||||
expect(string_io.string).to match(%(pebbles))
|
||||
expect(string_io.string).not_to match(%(name=Hamachi))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logging request and response bodies' do
|
||||
let(:logger_options) { { bodies: true } }
|
||||
|
||||
it 'log request and response body' do
|
||||
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
||||
expect(string_io.string).to match(%(name=Ebi))
|
||||
expect(string_io.string).to match(%(pebbles))
|
||||
end
|
||||
|
||||
it 'log response body object' do
|
||||
conn.get '/rubbles', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
|
||||
end
|
||||
|
||||
it 'logs filter body' do
|
||||
conn.get '/filtered_body', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%(soylent green is))
|
||||
expect(string_io.string).to match(%(tasty))
|
||||
expect(string_io.string).not_to match(%(people))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logging errors' do
|
||||
let(:logger_options) { { errors: true } }
|
||||
|
||||
it 'logs error message' do
|
||||
expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
|
||||
expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logging headers and errors' do
|
||||
let(:logger_options) { { headers: true, errors: true } }
|
||||
|
||||
it 'logs error message' do
|
||||
expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
|
||||
expect(string_io.string).to match(%(Failed to open TCP connection))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using log_level' do
|
||||
let(:logger_options) { { bodies: true, log_level: :debug } }
|
||||
|
||||
it 'logs request/request body on the specified level (debug)' do
|
||||
logger.level = Logger::DEBUG
|
||||
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
||||
expect(string_io.string).to match(%(name=Ebi))
|
||||
expect(string_io.string).to match(%(pebbles))
|
||||
end
|
||||
|
||||
it 'logs headers on the debug level' do
|
||||
logger.level = Logger::DEBUG
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).to match(%(Content-Type: "text/html))
|
||||
end
|
||||
|
||||
it 'does not log request/response body on the info level' do
|
||||
logger.level = Logger::INFO
|
||||
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(name=Ebi))
|
||||
expect(string_io.string).not_to match(%(pebbles))
|
||||
end
|
||||
|
||||
it 'does not log headers on the info level' do
|
||||
logger.level = Logger::INFO
|
||||
conn.get '/hello', nil, accept: 'text/html'
|
||||
expect(string_io.string).not_to match(%(Content-Type: "text/html))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,211 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Response::RaiseError do
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.response :raise_error
|
||||
b.adapter :test do |stub|
|
||||
stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
|
||||
stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('unauthorized') { [401, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
|
||||
stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
|
||||
stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises no exception for 200 responses' do
|
||||
expect { conn.get('ok') }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'raises Faraday::BadRequestError for 400 responses' do
|
||||
expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 400')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(400)
|
||||
expect(ex.response_status).to eq(400)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::UnauthorizedError for 401 responses' do
|
||||
expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 401')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(401)
|
||||
expect(ex.response_status).to eq(401)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ForbiddenError for 403 responses' do
|
||||
expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 403')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(403)
|
||||
expect(ex.response_status).to eq(403)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ResourceNotFound for 404 responses' do
|
||||
expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 404')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(404)
|
||||
expect(ex.response_status).to eq(404)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ProxyAuthError for 407 responses' do
|
||||
expect { conn.get('proxy-error') }.to raise_error(Faraday::ProxyAuthError) do |ex|
|
||||
expect(ex.message).to eq('407 "Proxy Authentication Required"')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(407)
|
||||
expect(ex.response_status).to eq(407)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::RequestTimeoutError for 408 responses' do
|
||||
expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 408')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(408)
|
||||
expect(ex.response_status).to eq(408)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ConflictError for 409 responses' do
|
||||
expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 409')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(409)
|
||||
expect(ex.response_status).to eq(409)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::UnprocessableEntityError for 422 responses' do
|
||||
expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 422')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(422)
|
||||
expect(ex.response_status).to eq(422)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::TooManyRequestsError for 429 responses' do
|
||||
expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 429')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(429)
|
||||
expect(ex.response_status).to eq(429)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::NilStatusError for nil status in response' do
|
||||
expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
|
||||
expect(ex.message).to eq('http status could not be derived from the server response')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('nil')
|
||||
expect(ex.response[:status]).to be_nil
|
||||
expect(ex.response_status).to be_nil
|
||||
expect(ex.response_body).to eq('fail')
|
||||
expect(ex.response_headers['X-Reason']).to eq('nil')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ClientError for other 4xx responses' do
|
||||
expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 499')
|
||||
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
||||
expect(ex.response[:status]).to eq(499)
|
||||
expect(ex.response_status).to eq(499)
|
||||
expect(ex.response_body).to eq('keep looking')
|
||||
expect(ex.response_headers['X-Reason']).to eq('because')
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises Faraday::ServerError for 500 responses' do
|
||||
expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
|
||||
expect(ex.message).to eq('the server responded with status 500')
|
||||
expect(ex.response[:headers]['X-Error']).to eq('bailout')
|
||||
expect(ex.response[:status]).to eq(500)
|
||||
expect(ex.response_status).to eq(500)
|
||||
expect(ex.response_body).to eq('fail')
|
||||
expect(ex.response_headers['X-Error']).to eq('bailout')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'request info' do
|
||||
let(:conn) do
|
||||
Faraday.new do |b|
|
||||
b.response :raise_error, **middleware_options
|
||||
b.adapter :test do |stub|
|
||||
stub.post(url, request_body, request_headers) do
|
||||
[400, { 'X-Reason' => 'because' }, 'keep looking']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:middleware_options) { {} }
|
||||
let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
|
||||
let(:request_headers) { { 'Authorization' => 'Basic 123' } }
|
||||
let(:url_path) { 'request' }
|
||||
let(:query_params) { 'full=true' }
|
||||
let(:url) { "#{url_path}?#{query_params}" }
|
||||
|
||||
subject(:perform_request) do
|
||||
conn.post url do |req|
|
||||
req.headers['Authorization'] = 'Basic 123'
|
||||
req.body = request_body
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the request info in the exception' do
|
||||
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
||||
expect(ex.response[:request][:method]).to eq(:post)
|
||||
expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
|
||||
expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
|
||||
expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
|
||||
expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
|
||||
expect(ex.response[:request][:body]).to eq(request_body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the include_request option is set to false' do
|
||||
let(:middleware_options) { { include_request: false } }
|
||||
|
||||
it 'does not include request info in the exception' do
|
||||
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
||||
expect(ex.response.keys).to contain_exactly(
|
||||
:status,
|
||||
:headers,
|
||||
:body
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Response do
|
||||
subject { Faraday::Response.new(env) }
|
||||
|
||||
let(:env) do
|
||||
Faraday::Env.from(status: 404, body: 'yikes', url: Faraday::Utils.URI('https://lostisland.github.io/faraday'),
|
||||
response_headers: { 'Content-Type' => 'text/plain' })
|
||||
end
|
||||
|
||||
it { expect(subject.finished?).to be_truthy }
|
||||
it { expect { subject.finish({}) }.to raise_error(RuntimeError) }
|
||||
it { expect(subject.success?).to be_falsey }
|
||||
it { expect(subject.status).to eq(404) }
|
||||
it { expect(subject.body).to eq('yikes') }
|
||||
it { expect(subject.headers['Content-Type']).to eq('text/plain') }
|
||||
it { expect(subject['content-type']).to eq('text/plain') }
|
||||
|
||||
describe '#apply_request' do
|
||||
before { subject.apply_request(body: 'a=b', method: :post) }
|
||||
|
||||
it { expect(subject.body).to eq('yikes') }
|
||||
it { expect(subject.env[:method]).to eq(:post) }
|
||||
end
|
||||
|
||||
describe '#to_hash' do
|
||||
let(:hash) { subject.to_hash }
|
||||
|
||||
it { expect(hash).to be_a(Hash) }
|
||||
it { expect(hash[:status]).to eq(subject.status) }
|
||||
it { expect(hash[:response_headers]).to eq(subject.headers) }
|
||||
it { expect(hash[:body]).to eq(subject.body) }
|
||||
it { expect(hash[:url]).to eq(subject.env.url) }
|
||||
end
|
||||
|
||||
describe 'marshal serialization support' do
|
||||
subject { Faraday::Response.new }
|
||||
let(:loaded) { Marshal.load(Marshal.dump(subject)) }
|
||||
|
||||
before do
|
||||
subject.on_complete {}
|
||||
subject.finish(env.merge(params: 'moo'))
|
||||
end
|
||||
|
||||
it { expect(loaded.env[:params]).to be_nil }
|
||||
it { expect(loaded.env[:body]).to eq(env[:body]) }
|
||||
it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
|
||||
it { expect(loaded.env[:status]).to eq(env[:status]) }
|
||||
it { expect(loaded.env[:url]).to eq(env[:url]) }
|
||||
end
|
||||
|
||||
describe '#on_complete' do
|
||||
subject { Faraday::Response.new }
|
||||
|
||||
it 'parse body on finish' do
|
||||
subject.on_complete { |env| env[:body] = env[:body].upcase }
|
||||
subject.finish(env)
|
||||
|
||||
expect(subject.body).to eq('YIKES')
|
||||
end
|
||||
|
||||
it 'can access response body in on_complete callback' do
|
||||
subject.on_complete { |env| env[:body] = subject.body.upcase }
|
||||
subject.finish(env)
|
||||
|
||||
expect(subject.body).to eq('YIKES')
|
||||
end
|
||||
|
||||
it 'can access response body in on_complete callback' do
|
||||
callback_env = nil
|
||||
subject.on_complete { |env| callback_env = env }
|
||||
subject.finish({})
|
||||
|
||||
expect(subject.env).to eq(callback_env)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Utils::Headers do
|
||||
subject { Faraday::Utils::Headers.new }
|
||||
|
||||
context 'when Content-Type is set to application/json' do
|
||||
before { subject['Content-Type'] = 'application/json' }
|
||||
|
||||
it { expect(subject.keys).to eq(['Content-Type']) }
|
||||
it { expect(subject['Content-Type']).to eq('application/json') }
|
||||
it { expect(subject['CONTENT-TYPE']).to eq('application/json') }
|
||||
it { expect(subject['content-type']).to eq('application/json') }
|
||||
it { is_expected.to include('content-type') }
|
||||
end
|
||||
|
||||
context 'when Content-Type is set to application/xml' do
|
||||
before { subject['Content-Type'] = 'application/xml' }
|
||||
|
||||
it { expect(subject.keys).to eq(['Content-Type']) }
|
||||
it { expect(subject['Content-Type']).to eq('application/xml') }
|
||||
it { expect(subject['CONTENT-TYPE']).to eq('application/xml') }
|
||||
it { expect(subject['content-type']).to eq('application/xml') }
|
||||
it { is_expected.to include('content-type') }
|
||||
end
|
||||
|
||||
describe '#fetch' do
|
||||
before { subject['Content-Type'] = 'application/json' }
|
||||
|
||||
it { expect(subject.fetch('Content-Type')).to eq('application/json') }
|
||||
it { expect(subject.fetch('CONTENT-TYPE')).to eq('application/json') }
|
||||
it { expect(subject.fetch(:content_type)).to eq('application/json') }
|
||||
it { expect(subject.fetch('invalid', 'default')).to eq('default') }
|
||||
it { expect(subject.fetch('invalid', false)).to eq(false) }
|
||||
it { expect(subject.fetch('invalid', nil)).to be_nil }
|
||||
it { expect(subject.fetch('Invalid') { |key| "#{key} key" }).to eq('Invalid key') }
|
||||
it 'calls a block when provided' do
|
||||
block_called = false
|
||||
expect(subject.fetch('content-type') { block_called = true }).to eq('application/json')
|
||||
expect(block_called).to be_falsey
|
||||
end
|
||||
it 'raises an error if key not found' do
|
||||
expected_error = defined?(KeyError) ? KeyError : IndexError
|
||||
expect { subject.fetch('invalid') }.to raise_error(expected_error)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete' do
|
||||
before do
|
||||
subject['Content-Type'] = 'application/json'
|
||||
@deleted = subject.delete('content-type')
|
||||
end
|
||||
|
||||
it { expect(@deleted).to eq('application/json') }
|
||||
it { expect(subject.size).to eq(0) }
|
||||
it { is_expected.not_to include('content-type') }
|
||||
it { expect(subject.delete('content-type')).to be_nil }
|
||||
end
|
||||
|
||||
describe '#parse' do
|
||||
context 'when response headers leave http status line out' do
|
||||
let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
|
||||
|
||||
before { subject.parse(headers) }
|
||||
|
||||
it { expect(subject.keys).to eq(%w[Content-Type]) }
|
||||
it { expect(subject['Content-Type']).to eq('text/html') }
|
||||
it { expect(subject['content-type']).to eq('text/html') }
|
||||
end
|
||||
|
||||
context 'when response headers values include a colon' do
|
||||
let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://httpbingo.org/\r\n\r\n" }
|
||||
|
||||
before { subject.parse(headers) }
|
||||
|
||||
it { expect(subject['location']).to eq('http://httpbingo.org/') }
|
||||
end
|
||||
|
||||
context 'when response headers include a blank line' do
|
||||
let(:headers) { "HTTP/1.1 200 OK\r\n\r\nContent-Type: text/html\r\n\r\n" }
|
||||
|
||||
before { subject.parse(headers) }
|
||||
|
||||
it { expect(subject['content-type']).to eq('text/html') }
|
||||
end
|
||||
|
||||
context 'when response headers include already stored keys' do
|
||||
let(:headers) { "HTTP/1.1 200 OK\r\nX-Numbers: 123\r\n\r\n" }
|
||||
|
||||
before do
|
||||
h = subject
|
||||
h[:x_numbers] = 8
|
||||
h.parse(headers)
|
||||
end
|
||||
|
||||
it do
|
||||
expect(subject[:x_numbers]).to eq('8, 123')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Faraday::Utils do
|
||||
describe 'headers parsing' do
|
||||
let(:multi_response_headers) do
|
||||
"HTTP/1.x 500 OK\r\nContent-Type: text/html; charset=UTF-8\r\n" \
|
||||
"HTTP/1.x 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
|
||||
end
|
||||
|
||||
it 'parse headers for aggregated responses' do
|
||||
headers = Faraday::Utils::Headers.new
|
||||
headers.parse(multi_response_headers)
|
||||
|
||||
result = headers.to_hash
|
||||
|
||||
expect(result['Content-Type']).to eq('application/json; charset=UTF-8')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'URI parsing' do
|
||||
let(:url) { 'http://example.com/abc' }
|
||||
|
||||
it 'escapes safe buffer' do
|
||||
str = FakeSafeBuffer.new('$32,000.00')
|
||||
expect(Faraday::Utils.escape(str)).to eq('%2432%2C000.00')
|
||||
end
|
||||
|
||||
it 'parses with default parser' do
|
||||
with_default_uri_parser(nil) do
|
||||
uri = normalize(url)
|
||||
expect(uri.host).to eq('example.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'parses with URI' do
|
||||
with_default_uri_parser(::URI) do
|
||||
uri = normalize(url)
|
||||
expect(uri.host).to eq('example.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'parses with block' do
|
||||
with_default_uri_parser(->(u) { "booya#{'!' * u.size}" }) do
|
||||
expect(normalize(url)).to eq('booya!!!!!!!!!!!!!!!!!!!!!!')
|
||||
end
|
||||
end
|
||||
|
||||
it 'replaces headers hash' do
|
||||
headers = Faraday::Utils::Headers.new('authorization' => 't0ps3cr3t!')
|
||||
expect(headers).to have_key('authorization')
|
||||
|
||||
headers.replace('content-type' => 'text/plain')
|
||||
expect(headers).not_to have_key('authorization')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.deep_merge!' do
|
||||
let(:connection_options) { Faraday::ConnectionOptions.new }
|
||||
let(:url) do
|
||||
{
|
||||
url: 'http://example.com/abc',
|
||||
headers: { 'Mime-Version' => '1.0' },
|
||||
request: { oauth: { consumer_key: 'anonymous' } },
|
||||
ssl: { version: '2' }
|
||||
}
|
||||
end
|
||||
|
||||
it 'recursively merges the headers' do
|
||||
connection_options.headers = { user_agent: 'My Agent 1.0' }
|
||||
deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
|
||||
|
||||
expect(deep_merge.headers).to eq('Mime-Version' => '1.0', user_agent: 'My Agent 1.0')
|
||||
end
|
||||
|
||||
context 'when a target hash has an Options Struct value' do
|
||||
let(:request) do
|
||||
{
|
||||
params_encoder: nil,
|
||||
proxy: nil,
|
||||
bind: nil,
|
||||
timeout: nil,
|
||||
open_timeout: nil,
|
||||
read_timeout: nil,
|
||||
write_timeout: nil,
|
||||
boundary: nil,
|
||||
oauth: { consumer_key: 'anonymous' },
|
||||
context: nil,
|
||||
on_data: nil
|
||||
}
|
||||
end
|
||||
let(:ssl) do
|
||||
{
|
||||
verify: nil,
|
||||
ca_file: nil,
|
||||
ca_path: nil,
|
||||
verify_mode: nil,
|
||||
cert_store: nil,
|
||||
client_cert: nil,
|
||||
client_key: nil,
|
||||
certificate: nil,
|
||||
private_key: nil,
|
||||
verify_depth: nil,
|
||||
version: '2',
|
||||
min_version: nil,
|
||||
max_version: nil,
|
||||
verify_hostname: nil
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not overwrite an Options Struct value' do
|
||||
deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
|
||||
|
||||
expect(deep_merge.request.to_h).to eq(request)
|
||||
expect(deep_merge.ssl.to_h).to eq(ssl)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user