require 'spec_helper' RSpec.describe HTTParty::ConnectionAdapter do describe "initialization" do let(:uri) { URI 'http://www.google.com' } it "takes a URI as input" do HTTParty::ConnectionAdapter.new(uri) end it "raises an ArgumentError if the uri is nil" do expect { HTTParty::ConnectionAdapter.new(nil) }.to raise_error ArgumentError end it "raises an ArgumentError if the uri is a String" do expect { HTTParty::ConnectionAdapter.new('http://www.google.com') }.to raise_error ArgumentError end it "sets the uri" do adapter = HTTParty::ConnectionAdapter.new(uri) expect(adapter.uri).to be uri end it "also accepts an optional options hash" do HTTParty::ConnectionAdapter.new(uri, {}) end it "sets the options" do options = {foo: :bar} adapter = HTTParty::ConnectionAdapter.new(uri, options) expect(adapter.options.keys).to include(:verify, :verify_peer, :foo) end end describe ".call" do let(:uri) { URI 'http://www.google.com' } let(:options) { { foo: :bar } } it "generates an HTTParty::ConnectionAdapter instance with the given uri and options" do expect(HTTParty::ConnectionAdapter).to receive(:new).with(uri, options).and_return(double(connection: nil)) HTTParty::ConnectionAdapter.call(uri, options) end it "calls #connection on the connection adapter" do adapter = double('Adapter') connection = double('Connection') expect(adapter).to receive(:connection).and_return(connection) allow(HTTParty::ConnectionAdapter).to receive_messages(new: adapter) expect(HTTParty::ConnectionAdapter.call(uri, options)).to be connection end end describe '#connection' do let(:uri) { URI 'http://www.google.com' } let(:options) { Hash.new } let(:adapter) { HTTParty::ConnectionAdapter.new(uri, options) } describe "the resulting connection" do subject { adapter.connection } it { is_expected.to be_an_instance_of Net::HTTP } context "using port 80" do let(:uri) { URI 'http://foobar.com' } it { is_expected.not_to use_ssl } end context "when dealing with ssl" do let(:uri) { URI 'https://foobar.com' } context "uses the system cert_store, by default" do let!(:system_cert_store) do system_cert_store = double('default_cert_store') expect(system_cert_store).to receive(:set_default_paths) expect(OpenSSL::X509::Store).to receive(:new).and_return(system_cert_store) system_cert_store end it { is_expected.to use_cert_store(system_cert_store) } end context "should use the specified cert store, when one is given" do let(:custom_cert_store) { double('custom_cert_store') } let(:options) { {cert_store: custom_cert_store} } it { is_expected.to use_cert_store(custom_cert_store) } end context "using port 443 for ssl" do let(:uri) { URI 'https://api.foo.com/v1:443' } it { is_expected.to use_ssl } end context "https scheme with default port" do it { is_expected.to use_ssl } end context "https scheme with non-standard port" do let(:uri) { URI 'https://foobar.com:123456' } it { is_expected.to use_ssl } end context "when ssl version is set" do let(:options) { {ssl_version: :TLSv1} } it "sets ssl version" do expect(subject.ssl_version).to eq(:TLSv1) end end if RUBY_VERSION > '1.9' end context "when dealing with IPv6" do let(:uri) { URI 'http://[fd00::1]' } it "strips brackets from the address" do expect(subject.address).to eq('fd00::1') end end context "specifying ciphers" do let(:options) { {ciphers: 'RC4-SHA' } } it "should set the ciphers on the connection" do expect(subject.ciphers).to eq('RC4-SHA') end end if RUBY_VERSION > '1.9' context "when timeout is not set" do it "doesn't set the timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false ) expect(http).not_to receive(:open_timeout=) expect(http).not_to receive(:read_timeout=) expect(http).not_to receive(:write_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end context "when setting timeout" do context "to 5 seconds" do let(:options) { {timeout: 5} } describe '#open_timeout' do subject { super().open_timeout } it { is_expected.to eq(5) } end describe '#read_timeout' do subject { super().read_timeout } it { is_expected.to eq(5) } end if RUBY_VERSION >= '2.6.0' describe '#write_timeout' do subject { super().write_timeout } it { is_expected.to eq(5) } end end end context "and timeout is a string" do let(:options) { {timeout: "five seconds"} } it "doesn't set the timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false ) expect(http).not_to receive(:open_timeout=) expect(http).not_to receive(:read_timeout=) expect(http).not_to receive(:write_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end end context "when timeout is not set and read_timeout is set to 6 seconds" do let(:options) { {read_timeout: 6} } describe '#read_timeout' do subject { super().read_timeout } it { is_expected.to eq(6) } end it "should not set the open_timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0 ) expect(http).not_to receive(:open_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end it "should not set the write_timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0 ) expect(http).not_to receive(:write_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end context "when timeout is set and read_timeout is set to 6 seconds" do let(:options) { {timeout: 5, read_timeout: 6} } describe '#open_timeout' do subject { super().open_timeout } it { is_expected.to eq(5) } end if RUBY_VERSION >= '2.6.0' describe '#write_timeout' do subject { super().write_timeout } it { is_expected.to eq(5) } end end describe '#read_timeout' do subject { super().read_timeout } it { is_expected.to eq(6) } end it "should override the timeout option" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0, :open_timeout= => 0, :write_timeout= => 0, ) expect(http).to receive(:open_timeout=) expect(http).to receive(:read_timeout=).twice if RUBY_VERSION >= '2.6.0' expect(http).to receive(:write_timeout=) end allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end context "when timeout is not set and open_timeout is set to 7 seconds" do let(:options) { {open_timeout: 7} } describe '#open_timeout' do subject { super().open_timeout } it { is_expected.to eq(7) } end it "should not set the read_timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :open_timeout= => 0 ) expect(http).not_to receive(:read_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end it "should not set the write_timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :open_timeout= => 0 ) expect(http).not_to receive(:write_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end context "when timeout is set and open_timeout is set to 7 seconds" do let(:options) { {timeout: 5, open_timeout: 7} } describe '#open_timeout' do subject { super().open_timeout } it { is_expected.to eq(7) } end if RUBY_VERSION >= '2.6.0' describe '#write_timeout' do subject { super().write_timeout } it { is_expected.to eq(5) } end end describe '#read_timeout' do subject { super().read_timeout } it { is_expected.to eq(5) } end it "should override the timeout option" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0, :open_timeout= => 0, :write_timeout= => 0, ) expect(http).to receive(:open_timeout=).twice expect(http).to receive(:read_timeout=) if RUBY_VERSION >= '2.6.0' expect(http).to receive(:write_timeout=) end allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end if RUBY_VERSION >= '2.6.0' context "when timeout is not set and write_timeout is set to 8 seconds" do let(:options) { {write_timeout: 8} } describe '#write_timeout' do subject { super().write_timeout } it { is_expected.to eq(8) } end it "should not set the open timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0, :open_timeout= => 0, :write_timeout= => 0, ) expect(http).not_to receive(:open_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end it "should not set the read timeout" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0, :open_timeout= => 0, :write_timeout= => 0, ) expect(http).not_to receive(:read_timeout=) allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end context "when timeout is set and write_timeout is set to 8 seconds" do let(:options) { {timeout: 2, write_timeout: 8} } describe '#write_timeout' do subject { super().write_timeout } it { is_expected.to eq(8) } end it "should override the timeout option" do http = double( "http", :null_object => true, :use_ssl= => false, :use_ssl? => false, :read_timeout= => 0, :open_timeout= => 0, :write_timeout= => 0, ) expect(http).to receive(:read_timeout=) expect(http).to receive(:open_timeout=) expect(http).to receive(:write_timeout=).twice allow(Net::HTTP).to receive_messages(new: http) adapter.connection end end end context "when debug_output" do let(:http) { Net::HTTP.new(uri) } before do allow(Net::HTTP).to receive_messages(new: http) end context "is set to $stderr" do let(:options) { {debug_output: $stderr} } it "has debug output set" do expect(http).to receive(:set_debug_output).with($stderr) adapter.connection end end context "is not provided" do it "does not set_debug_output" do expect(http).not_to receive(:set_debug_output) adapter.connection end end end context 'when providing proxy address and port' do let(:options) { {http_proxyaddr: '1.2.3.4', http_proxyport: 8080} } it { is_expected.to be_a_proxy } describe '#proxy_address' do subject { super().proxy_address } it { is_expected.to eq('1.2.3.4') } end describe '#proxy_port' do subject { super().proxy_port } it { is_expected.to eq(8080) } end context 'as well as proxy user and password' do let(:options) do { http_proxyaddr: '1.2.3.4', http_proxyport: 8080, http_proxyuser: 'user', http_proxypass: 'pass' } end describe '#proxy_user' do subject { super().proxy_user } it { is_expected.to eq('user') } end describe '#proxy_pass' do subject { super().proxy_pass } it { is_expected.to eq('pass') } end end end context 'when providing nil as proxy address' do let(:uri) { URI 'http://noproxytest.com' } let(:options) { {http_proxyaddr: nil} } it { is_expected.not_to be_a_proxy } it "does pass nil proxy parameters to the connection, this forces to not use a proxy" do http = Net::HTTP.new("noproxytest.com") expect(Net::HTTP).to receive(:new).once.with("noproxytest.com", 80, nil, nil, nil, nil).and_return(http) adapter.connection end end context 'when not providing a proxy address' do let(:uri) { URI 'http://proxytest.com' } it "does not pass any proxy parameters to the connection" do http = Net::HTTP.new("proxytest.com") expect(Net::HTTP).to receive(:new).once.with("proxytest.com", 80).and_return(http) adapter.connection end end context 'when providing a local bind address and port' do let(:options) { {local_host: "127.0.0.1", local_port: 12345 } } describe '#local_host' do subject { super().local_host } it { is_expected.to eq('127.0.0.1') } end describe '#local_port' do subject { super().local_port } it { is_expected.to eq(12345) } end end if RUBY_VERSION >= '2.0' context "when providing PEM certificates" do let(:pem) { :pem_contents } let(:options) { {pem: pem, pem_password: "password"} } context "when scheme is https" do let(:uri) { URI 'https://google.com' } let(:cert) { double("OpenSSL::X509::Certificate") } let(:key) { double("OpenSSL::PKey::RSA") } before do expect(OpenSSL::X509::Certificate).to receive(:new).with(pem).and_return(cert) expect(OpenSSL::PKey::RSA).to receive(:new).with(pem, "password").and_return(key) end it "uses the provided PEM certificate" do expect(subject.cert).to eq(cert) expect(subject.key).to eq(key) end it "will verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) end context "when options include verify=false" do let(:options) { {pem: pem, pem_password: "password", verify: false} } it "should not verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) end end context "when options include verify_peer=false" do let(:options) { {pem: pem, pem_password: "password", verify_peer: false} } it "should not verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) end end end context "when scheme is not https" do let(:uri) { URI 'http://google.com' } let(:http) { Net::HTTP.new(uri) } before do allow(Net::HTTP).to receive_messages(new: http) expect(OpenSSL::X509::Certificate).not_to receive(:new).with(pem) expect(OpenSSL::PKey::RSA).not_to receive(:new).with(pem, "password") expect(http).not_to receive(:cert=) expect(http).not_to receive(:key=) end it "has no PEM certificate " do expect(subject.cert).to be_nil expect(subject.key).to be_nil end end end context "when providing PKCS12 certificates" do let(:p12) { :p12_contents } let(:options) { {p12: p12, p12_password: "password"} } context "when scheme is https" do let(:uri) { URI 'https://google.com' } let(:pkcs12) { double("OpenSSL::PKCS12", certificate: cert, key: key) } let(:cert) { double("OpenSSL::X509::Certificate") } let(:key) { double("OpenSSL::PKey::RSA") } before do expect(OpenSSL::PKCS12).to receive(:new).with(p12, "password").and_return(pkcs12) end it "uses the provided P12 certificate " do expect(subject.cert).to eq(cert) expect(subject.key).to eq(key) end it "will verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) end context "when options include verify=false" do let(:options) { {p12: p12, p12_password: "password", verify: false} } it "should not verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) end end context "when options include verify_peer=false" do let(:options) { {p12: p12, p12_password: "password", verify_peer: false} } it "should not verify the certificate" do expect(subject.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) end end end context "when scheme is not https" do let(:uri) { URI 'http://google.com' } let(:http) { Net::HTTP.new(uri) } before do allow(Net::HTTP).to receive_messages(new: http) expect(OpenSSL::PKCS12).not_to receive(:new).with(p12, "password") expect(http).not_to receive(:cert=) expect(http).not_to receive(:key=) end it "has no PKCS12 certificate " do expect(subject.cert).to be_nil expect(subject.key).to be_nil end end end context "when uri port is not defined" do context "falls back to 80 port on http" do let(:uri) { URI 'http://foobar.com' } before { allow(uri).to receive(:port).and_return(nil) } it { expect(subject.port).to be 80 } end context "falls back to 443 port on https" do let(:uri) { URI 'https://foobar.com' } before { allow(uri).to receive(:port).and_return(nil) } it { expect(subject.port).to be 443 } end end end end end