# 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(',').map { |pair| pair.split('-') }.to_h 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 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