From 9ba2f5fced6d956ff10b89457d8d43e6c689782c Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:42:13 -0500 Subject: [PATCH 01/36] replace httpi with faraday, pull in rubyntlm since it'll be needed for ntlm auth handshakes --- savon.gemspec | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/savon.gemspec b/savon.gemspec index 308e80d7..e57ef62e 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -17,7 +17,12 @@ Gem::Specification.new do |s| s.license = 'MIT' s.add_dependency "nori", "~> 2.4" - s.add_dependency "httpi", ">= 2.4.5" + s.add_dependency "faraday", ">= 2.8" + s.add_dependency "faraday-gzip", ">= 2.0" + s.add_dependency "faraday-digestauth", ">= 0.2" + s.add_dependency "faraday-net_http_persistent", ">= 2.1" + s.add_dependency "faraday-follow_redirects", ">= 0.3" + s.add_dependency "rubyntlm", ">= 0.6" s.add_dependency "wasabi", ">= 3.7" s.add_dependency "akami", "~> 1.2" s.add_dependency "gyoku", "~> 1.2" From 1473978f1effc514d2d9561df1d4b145bcc0d1d3 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:42:37 -0500 Subject: [PATCH 02/36] remove httpi mock adapter implementations, since faraday has its own test adapter --- spec/support/adapters.rb | 49 ---------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 spec/support/adapters.rb diff --git a/spec/support/adapters.rb b/spec/support/adapters.rb deleted file mode 100644 index d791e275..00000000 --- a/spec/support/adapters.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true -require 'httpi/adapter/httpclient' - -# Proxy adapter. Records all requests and passes them to HTTPClient -class AdapterForTest < HTTPI::Adapter::Base - - register :adapter_for_test - - def initialize(request) - @@requests ||= [] - @@requests.push request - @request = request - @worker = HTTPI::Adapter::HTTPClient.new(request) - end - - def client - @worker.client - end - - def request(method) - @@methods ||= [] - @@methods.push method - @worker.request(method) - end - -end - -# Fake adapter with request recording. -# Takes path from url and returns fixture WSDL with that name. -class FakeAdapterForTest < HTTPI::Adapter::Base - - register :fake_adapter_for_test - - def initialize(request) - @@requests ||= [] - @@requests.push request - @request = request - end - - attr_reader :client - - def request(method) - @@methods ||= [] - @@methods.push method - target = @request.url.path.to_sym - HTTPI::Response.new(200, {}, Fixture.wsdl(target)) - end - -end From 7fd858e54b6bf5e112991e5d264f9f333a9d3da7 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:43:09 -0500 Subject: [PATCH 03/36] create a helper for mocking out faraday responses --- spec/support/responses.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 spec/support/responses.rb diff --git a/spec/support/responses.rb b/spec/support/responses.rb new file mode 100644 index 00000000..f0c3ea2a --- /dev/null +++ b/spec/support/responses.rb @@ -0,0 +1,8 @@ +class Responses + class << self + def mock_faraday(code, headers, body) + env = Faraday::Env.new(status: code, response_headers: headers, response_body: body) + Faraday::Response.new(env) + end + end +end \ No newline at end of file From e6f0823b3adfdf9c6c2e06f18514c7b441a6ef8d Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:46:00 -0500 Subject: [PATCH 04/36] remove hashes from in front of descriptions because it breaks rubymine's test runner; use a mocked faraday response --- spec/savon/client_spec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/savon/client_spec.rb b/spec/savon/client_spec.rb index 4ba9d834..869f5b14 100644 --- a/spec/savon/client_spec.rb +++ b/spec/savon/client_spec.rb @@ -103,7 +103,7 @@ end end - describe "#call" do + describe "call" do it "calls a new SOAP operation" do locals = { :message => { :symbol => "AAPL" } } soap_response = new_soap_response @@ -171,7 +171,7 @@ end end - describe "#build_request" do + describe "build_request" do it "returns the request without making an actual call" do expected_request = mock('request') wsdl = Wasabi::Document.new('http://example.com') @@ -247,8 +247,7 @@ def new_http_response(options = {}) defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - - HTTPI::Response.new response[:code], response[:headers], response[:body] + Responses.mock_faraday(response[:code], response[:headers], response[:body]) end def new_soap_response(options = {}) From 43090085618a41bfd600bf4ca3ba7d5f47eec994 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:46:29 -0500 Subject: [PATCH 05/36] mock expectations use faraday responses now --- lib/savon/mock/expectation.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/savon/mock/expectation.rb b/lib/savon/mock/expectation.rb index f9623161..10c2af2e 100644 --- a/lib/savon/mock/expectation.rb +++ b/lib/savon/mock/expectation.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require "httpi" +require "faraday" module Savon class MockExpectation @@ -41,8 +41,8 @@ def response! unless @response raise ExpectationError, "This expectation was not set up with a response." end - - HTTPI::Response.new(@response[:code], @response[:headers], @response[:body]) + env = Faraday::Env.new(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body]) + Faraday::Response.new(env) end private From da1c4296735e331cf9f81ec054460ee118d4d9a0 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:47:01 -0500 Subject: [PATCH 06/36] use faraday approaches to determining error stats --- lib/savon/http_error.rb | 6 +++--- spec/savon/http_error_spec.rb | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/savon/http_error.rb b/lib/savon/http_error.rb index 484d4bd0..b742c061 100644 --- a/lib/savon/http_error.rb +++ b/lib/savon/http_error.rb @@ -4,7 +4,7 @@ module Savon class HTTPError < Error def self.present?(http) - http.error? + !http.success? end def initialize(http) @@ -14,13 +14,13 @@ def initialize(http) attr_reader :http def to_s - String.new("HTTP error (#{@http.code})").tap do |str_error| + String.new("HTTP error (#{@http.status})").tap do |str_error| str_error << ": #{@http.body}" unless @http.body.empty? end end def to_hash - { :code => @http.code, :headers => @http.headers, :body => @http.body } + { :code => @http.status, :headers => @http.headers, :body => @http.body } end end diff --git a/spec/savon/http_error_spec.rb b/spec/savon/http_error_spec.rb index fc107b81..f21bf283 100644 --- a/spec/savon/http_error_spec.rb +++ b/spec/savon/http_error_spec.rb @@ -23,7 +23,7 @@ describe "#http" do it "returns the HTTPI::Response" do - expect(http_error.http).to be_a(HTTPI::Response) + expect(http_error.http).to be_a(Faraday::Response) end end @@ -51,7 +51,8 @@ def new_response(options = {}) defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - HTTPI::Response.new response[:code], response[:headers], response[:body] + env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) + Faraday::Response.new(env) end end From 7fc03ffc1940184f7e268a1882891da96c84ef48 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:47:20 -0500 Subject: [PATCH 07/36] more faraday field renames --- spec/savon/mock_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/savon/mock_spec.rb b/spec/savon/mock_spec.rb index d9becd63..e2330978 100644 --- a/spec/savon/mock_spec.rb +++ b/spec/savon/mock_spec.rb @@ -45,7 +45,7 @@ expect(response).to_not be_successful expect(response).to be_a_soap_fault - expect(response.http.code).to eq(500) + expect(response.http.status).to eq(500) expect(response.http.headers).to eq("x-result" => "invalid") expect(response.http.body).to eq(soap_fault) end From 017711d56e1ab8f2e99bfcb4e2c68e096733afca Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 14:48:54 -0500 Subject: [PATCH 08/36] use faraday for observer specs --- spec/savon/observers_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/savon/observers_spec.rb b/spec/savon/observers_spec.rb index 3fbb9a0d..4aeec787 100644 --- a/spec/savon/observers_spec.rb +++ b/spec/savon/observers_spec.rb @@ -51,7 +51,7 @@ def notify(operation_name, builder, globals, locals) def notify(*) # return a response to mock the request - HTTPI::Response.new(201, { "x-result" => "valid" }, "valid!") + Responses.mock_faraday(201, { "x-result" => "valid" }, "valid!") end }.new @@ -60,7 +60,7 @@ def notify(*) response = new_client.call(:authenticate) - expect(response.http.code).to eq(201) + expect(response.http.status).to eq(201) expect(response.http.headers).to eq("x-result" => "valid") expect(response.http.body).to eq("valid!") end @@ -77,7 +77,7 @@ def notify(*) Savon.observers << observer expect { new_client.call(:authenticate) }. - to raise_error(Savon::Error, "Observers need to return an HTTPI::Response " \ + to raise_error(Savon::Error, "Observers need to return an Faraday::Response " \ "to mock the request or nil to execute the request.") end end From e024205325841ca5252a82459f9c639cb1a0262b Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 15:31:37 -0500 Subject: [PATCH 09/36] remove direct reliance on an http client in options; add an error for deprecated options; save body to locals --- lib/savon.rb | 8 ++ lib/savon/options.rb | 20 +++- spec/savon/options_spec.rb | 191 ++++++++++++++++++------------------- 3 files changed, 119 insertions(+), 100 deletions(-) diff --git a/lib/savon.rb b/lib/savon.rb index b8ece228..00508a03 100644 --- a/lib/savon.rb +++ b/lib/savon.rb @@ -7,6 +7,14 @@ module Savon UnknownOperationError = Class.new(Error) InvalidResponseError = Class.new(Error) + class DeprecatedOptionError < Error + attr_accessor :option + def initialize(option) + @option = option + super("#{option} is deprecated as it is not supported in Faraday") + end + end + def self.client(globals = {}, &block) Client.new(globals, &block) end diff --git a/lib/savon/options.rb b/lib/savon/options.rb index 71d78666..ab2cab59 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true require "logger" -require "httpi" module Savon class Options @@ -10,6 +9,11 @@ def initialize(options = {}) assign options end + def deprecate + option = caller_locations[0].label + raise DeprecatedOptionError.new(option) + end + attr_reader :option_type def [](option) @@ -198,13 +202,11 @@ def raise_errors(raise_errors) # Whether or not to log. def log(log) - HTTPI.log = log @options[:log] = log end # The logger to use. Defaults to a Savon::Logger instance. def logger(logger) - HTTPI.logger = logger @options[:logger] = logger end @@ -257,6 +259,7 @@ def ssl_verify_mode(verify_mode) # Sets the cert key file to use. def ssl_cert_key_file(file) + deprecate @options[:ssl_cert_key_file] = file end @@ -267,11 +270,13 @@ def ssl_cert_key(key) # Sets the cert key password to use. def ssl_cert_key_password(password) + deprecate @options[:ssl_cert_key_password] = password end # Sets the cert file to use. def ssl_cert_file(file) + deprecate @options[:ssl_cert_file] = file end @@ -287,10 +292,12 @@ def ssl_ca_cert_file(file) # Sets the ca cert to use. def ssl_ca_cert(cert) + deprecate @options[:ssl_ca_cert] = cert end def ssl_ciphers(ciphers) + deprecate @options[:ssl_ciphers] = ciphers end @@ -389,7 +396,8 @@ def initialize(options = {}) defaults = { :advanced_typecasting => true, :response_parser => :nokogiri, - :multipart => false + :multipart => false, + :body => false } super defaults.merge(options) @@ -485,5 +493,9 @@ def multipart(multipart) def headers(headers) @options[:headers] = headers end + + def body(body) + @options[:body] = body + end end end diff --git a/spec/savon/options_spec.rb b/spec/savon/options_spec.rb index 914d3be4..7a262720 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -7,6 +7,16 @@ RSpec.describe "Options" do + shared_examples(:deprecation) do + option = self.superclass.description[/:\w+/][1..-1].to_sym + it "Raises a deprecation error" do + expect { new_client(:endpoint => @server.url, option => :none) }.to( + raise_error(Savon::DeprecatedOptionError) {|e| + expect(e.option).to eql(option.to_s) + }) + end + end + before :all do @server = IntegrationServer.run end @@ -86,20 +96,22 @@ end context 'global :follow_redirects' do + # From the documentation, this might have compatability issues with ntlm due to its reliance on net-http-persistent + # TODO integration test this somehow.... it 'sets whether or not request should follow redirects' do client = new_client(:endpoint => @server.url, :follow_redirects => true) - HTTPI::Request.any_instance.expects(:follow_redirect=).with(true) + Faraday::Connection.any_instance.expects(:response).with(:follow_redirects) - response = client.call(:authenticate) + client.call(:authenticate) end it 'defaults to false' do client = new_client(:endpoint => @server.url) - HTTPI::Request.any_instance.expects(:follow_redirect=).with(false) + Faraday::Connection.any_instance.expects(:response).with(:follow_redirects).never - response = client.call(:authenticate) + client.call(:authenticate) end end @@ -109,18 +121,25 @@ client = new_client(:endpoint => @server.url, :proxy => proxy_url) # TODO: find a way to integration test this [dh, 2012-12-08] - HTTPI::Request.any_instance.expects(:proxy=).with(proxy_url) + Faraday::Connection.any_instance.expects(:proxy=).with(proxy_url) response = client.call(:authenticate) end end context "global :host" do + let(:host) { "https://example.com:8080" } + let(:path) { "#{host}/webserviceexternal/contracts.asmx"} it "overrides the WSDL endpoint host" do - client = new_client(:wsdl => Fixture.wsdl(:no_message_tag), host: "https://example.com:8080") + stubs = Faraday::Adapter::Test::Stubs.new + stubs.post(path) do + [200, {'Content-Type': 'application/xml'}, ''] + end + + client = new_client(:wsdl => Fixture.wsdl(:no_message_tag), host: host, adapter: [:test, stubs] ) - request = client.build_request(:update_orders) - expect(request.url.to_s).to eq "https://example.com:8080/webserviceexternal/contracts.asmx" + client.call(:update_orders) + expect{stubs.verify_stubbed_calls}.not_to raise_error end end @@ -135,11 +154,14 @@ end end + # This feature is non-functional in 3.2 and 3.0 due to https://github.com/ruby/ruby/pull/9374 (works in 3.1... unknown why.) context "global :open_timeout" do + let(:open_timeout) { 0.1 } it "makes the client timeout after n seconds" do + skip 'https://github.com/ruby/ruby/pull/9374' non_routable_ip = "http://192.0.2.0" - client = new_client(:endpoint => non_routable_ip, :open_timeout => 0.1) - + client = new_client(:endpoint => non_routable_ip, :open_timeout => open_timeout) + start_time = Time.now expect { client.call(:authenticate) }.to raise_error { |error| host_unreachable = error.kind_of? Errno::EHOSTUNREACH net_unreachable = error.kind_of? Errno::ENETUNREACH @@ -150,7 +172,9 @@ else # TODO: make HTTPI tag timeout errors, then depend on HTTPI::TimeoutError # instead of a specific client error [dh, 2012-12-08] - expect(error).to be_an(HTTPClient::ConnectTimeoutError) + expect(Time.now - start_time).to be_within(0.5).of(open_timeout) + expect(error).to be_an(Faraday::ConnectionFailed) + end } end @@ -161,7 +185,7 @@ client = new_client(:endpoint => @server.url(:timeout), :open_timeout => 0.1, :read_timeout => 0.1) expect { client.call(:authenticate) }. - to raise_error(HTTPClient::ReceiveTimeoutError) + to raise_error(Faraday::TimeoutError) end end @@ -313,7 +337,8 @@ def to_s end it "silences HTTPI as well" do - HTTPI.expects(:log=).with(false) + Faraday::Connection.any_instance.expects(:response).with(:logger, nil, {:headers => true, :level => 0}).never + new_client(:log => false) end @@ -327,7 +352,7 @@ def to_s end it "turns HTTPI logging back on as well" do - HTTPI.expects(:log=).with(true) + Faraday::Connection.any_instance.expects(:response).with(:logger, nil, {:headers => true, :level => 0}).at_least_once new_client(:log => true) end end @@ -347,12 +372,14 @@ def to_s expect(logger).to eq(custom_logger) end - it "sets the logger of HTTPI as well" do - custom_logger = Logger.new($stdout) - - client = new_client(:logger => custom_logger, :log => true) + it "sets the logger of faraday connection as well" do + Faraday::Connection.any_instance.expects(:response).with(:logger, nil, {:headers => true, :level => 0}).at_least_once + mock_stdout { + custom_logger = Logger.new($stdout) - expect(HTTPI.logger).to be custom_logger + client = new_client(:endpoint => @server.url, :logger => custom_logger, :log => true) + client.call(:authenticate) + } end end @@ -419,18 +446,18 @@ def to_s end end - context "global :ssl_version" do - it "sets the SSL version to use" do - HTTPI::Auth::SSL.any_instance.expects(:ssl_version=).with(:TLSv1).twice - - client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1) - client.call(:authenticate) - end - end + # context "global :ssl_version" do + # it "sets the SSL version to use" do + # Faraday::SSLOptions.any_instance.expects(:ssl_version=).with(:TLSv1).twice + # + # client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1) + # client.call(:authenticate) + # end + # end context "global :ssl_min_version" do it "sets the SSL min_version to use" do - HTTPI::Auth::SSL.any_instance.expects(:min_version=).with(:TLS1_2).twice + Faraday::SSLOptions.any_instance.expects(:min_version=).with(:TLS1_2).twice client = new_client(:endpoint => @server.url, :ssl_min_version => :TLS1_2) client.call(:authenticate) @@ -439,7 +466,7 @@ def to_s context "global :ssl_max_version" do it "sets the SSL max_version to use" do - HTTPI::Auth::SSL.any_instance.expects(:max_version=).with(:TLS1_2).twice + Faraday::SSLOptions.any_instance.expects(:max_version=).with(:TLS1_2).twice client = new_client(:endpoint => @server.url, :ssl_max_version => :TLS1_2) client.call(:authenticate) @@ -448,7 +475,7 @@ def to_s context "global :ssl_verify_mode" do it "sets the verify mode to use" do - HTTPI::Auth::SSL.any_instance.expects(:verify_mode=).with(:peer).twice + Faraday::SSLOptions.any_instance.expects(:verify_mode=).with(:peer).twice client = new_client(:endpoint => @server.url, :ssl_verify_mode => :peer) client.call(:authenticate) @@ -456,28 +483,18 @@ def to_s end context "global :ssl_ciphers" do - it "sets the ciphers to use" do - HTTPI::Auth::SSL.any_instance.expects(:ciphers=).with(:none).twice - - client = new_client(:endpoint => @server.url, :ssl_ciphers => :none) - client.call(:authenticate) - end + it_behaves_like(:deprecation) end context "global :ssl_cert_key_file" do - it "sets the cert key file to use" do - cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - HTTPI::Auth::SSL.any_instance.expects(:cert_key_file=).with(cert_key).twice + it_behaves_like(:deprecation) - client = new_client(:endpoint => @server.url, :ssl_cert_key_file => cert_key) - client.call(:authenticate) - end end context "global :ssl_cert_key" do it "sets the cert key to use" do cert_key = File.open(File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__)).read - HTTPI::Auth::SSL.any_instance.expects(:cert_key=).with(cert_key).twice + Faraday::SSLOptions.any_instance.expects(:client_key=).with(cert_key).twice client = new_client(:endpoint => @server.url, :ssl_cert_key => cert_key) client.call(:authenticate) @@ -486,32 +503,17 @@ def to_s context "global :ssl_cert_key_password" do - it "sets the encrypted cert key file password to use" do - cert_key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - cert_key_pass = "secure-password!42" - HTTPI::Auth::SSL.any_instance.expects(:cert_key_file=).with(cert_key).twice - HTTPI::Auth::SSL.any_instance.expects(:cert_key_password=).with(cert_key_pass).twice - - client = new_client(:endpoint => @server.url, :ssl_cert_key_file => cert_key, :ssl_cert_key_password => cert_key_pass) - client.call(:authenticate) - end - + it_behaves_like(:deprecation) end context "global :ssl_cert_file" do - it "sets the cert file to use" do - cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - HTTPI::Auth::SSL.any_instance.expects(:cert_file=).with(cert).twice - - client = new_client(:endpoint => @server.url, :ssl_cert_file => cert) - client.call(:authenticate) - end + it_behaves_like(:deprecation) end context "global :ssl_cert" do it "sets the cert to use" do cert = File.open(File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__)).read - HTTPI::Auth::SSL.any_instance.expects(:cert=).with(cert).twice + Faraday::SSLOptions.any_instance.expects(:client_cert=).with(cert).twice client = new_client(:endpoint => @server.url, :ssl_cert => cert) client.call(:authenticate) @@ -521,7 +523,7 @@ def to_s context "global :ssl_ca_cert_file" do it "sets the ca cert file to use" do ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - HTTPI::Auth::SSL.any_instance.expects(:ca_cert_file=).with(ca_cert).twice + Faraday::SSLOptions.any_instance.expects(:ca_file=).with(ca_cert).twice client = new_client(:endpoint => @server.url, :ssl_ca_cert_file => ca_cert) client.call(:authenticate) @@ -531,7 +533,7 @@ def to_s context "global :ssl_ca_cert_path" do it "sets the ca cert path to use" do ca_cert_path = "../../fixtures/ssl" - HTTPI::Auth::SSL.any_instance.expects(:ca_cert_path=).with(ca_cert_path).twice + Faraday::SSLOptions.any_instance.expects(:ca_path=).with(ca_cert_path).twice client = new_client(:endpoint => @server.url, :ssl_ca_cert_path => ca_cert_path) client.call(:authenticate) @@ -541,7 +543,7 @@ def to_s context "global :ssl_ca_cert_store" do it "sets the cert store to use" do cert_store = OpenSSL::X509::Store.new - HTTPI::Auth::SSL.any_instance.expects(:cert_store=).with(cert_store).twice + Faraday::SSLOptions.any_instance.expects(:cert_store=).with(cert_store).twice client = new_client(:endpoint => @server.url, :ssl_cert_store => cert_store) client.call(:authenticate) @@ -549,13 +551,7 @@ def to_s end context "global :ssl_ca_cert" do - it "sets the ca cert file to use" do - ca_cert = File.open(File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__)).read - HTTPI::Auth::SSL.any_instance.expects(:ca_cert=).with(ca_cert).twice - - client = new_client(:endpoint => @server.url, :ssl_ca_cert => ca_cert) - client.call(:authenticate) - end + it_behaves_like(:deprecation) end @@ -584,7 +580,7 @@ def to_s # TODO: find a way to integration test this. including an entire ntlm # server implementation seems a bit over the top though. - HTTPI::Auth::Config.any_instance.expects(:ntlm).with(*credentials) + Savon::Operation.any_instance.expects(:handle_ntlm) response = client.call(:authenticate) end @@ -930,34 +926,35 @@ def to_s context 'global: :adapter' do it 'passes option to Wasabi initializer for WSDL fetching' do - ## I want to use there something similar to the next mock expectation, but I can't - ## as due to how Savon sets up Wasabi::Document and Wasabi::Document initialize itself - ## adapter= method is called first time with nil and second time with adapter. [Envek, 2014-05-03] - # Wasabi::Document.any_instance.expects(:adapter=).with(:fake_adapter_for_test) + stubs = Faraday::Adapter::Test::Stubs.new + stubs.get(@server.url('authentication')) do + [200, {'Content-Type': 'application/xml'}, Fixture.wsdl('authentication')] + end + Wasabi::Document.any_instance.expects(:adapter=).with(nil) + Wasabi::Document.any_instance.expects(:adapter=).with([:test, stubs]) client = Savon.client( :log => false, :wsdl => @server.url(:authentication), - :adapter => :fake_adapter_for_test, + :adapter => [:test, stubs], ) - operations = client.operations - expect(operations).to eq([:authenticate]) - expect(FakeAdapterForTest.class_variable_get(:@@requests).size).to eq(1) - expect(FakeAdapterForTest.class_variable_get(:@@requests).first.url).to eq(URI.parse(@server.url(:authentication))) - expect(FakeAdapterForTest.class_variable_get(:@@methods)).to eq([:get]) + client.operations + expect{stubs.verify_stubbed_calls}.not_to raise_error end - it 'instructs HTTPI to use provided adapter for performing SOAP requests' do + it 'instructs Faraday to use a provided adapter for performing SOAP requests' do + stubs = Faraday::Adapter::Test::Stubs.new + stubs.post(@server.url('repeat')) do + [200, {'Content-Type': 'application/xml'}, Fixture.response('authentication')] + end client = new_client_without_wsdl( :endpoint => @server.url(:repeat), - :namespace => "http://v1.example.com", - :adapter => :adapter_for_test, + :namespace => "http://v1_0.ws.user.example.com", + :adapter => [:test, stubs], ) response = client.call(:authenticate) - expect(response.http.body).to include('xmlns:wsdl="http://v1.example.com"') - expect(response.http.body).to include('') - expect(AdapterForTest.class_variable_get(:@@requests).size).to eq(1) - expect(AdapterForTest.class_variable_get(:@@requests).first.url).to eq(URI.parse(@server.url(:repeat))) - expect(AdapterForTest.class_variable_get(:@@methods)).to eq([:post]) + expect(response.http.body).to include('') + expect(response.http.body).to include('') + expect{stubs.verify_stubbed_calls}.not_to raise_error end end @@ -1087,17 +1084,17 @@ def to_s end context "request :cookies" do - it "accepts an Array of HTTPI::Cookie objects for the next request" do - cookies = [ - HTTPI::Cookie.new("some-cookie=choc-chip"), - HTTPI::Cookie.new("another-cookie=ny-cheesecake") - ] + it "accepts a hash for the next request" do + cookies = { + 'some-cookie': 'choc-chip', + 'another-cookie': 'ny-cheesecake' + } client = new_client(:endpoint => @server.url(:inspect_request)) response = client.call(:authenticate, :cookies => cookies) cookie = inspect_request(response).cookie - expect(cookie.split(";")).to include( + expect(cookie.split("; ")).to include( "some-cookie=choc-chip", "another-cookie=ny-cheesecake" ) @@ -1151,4 +1148,6 @@ def inspect_request(response) OpenStruct.new(hash) end + + end From 69e68bf01caa316e88482a179c427dfd44963195 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 15:31:48 -0500 Subject: [PATCH 10/36] update response_spec to use faraday logic --- spec/savon/response_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/savon/response_spec.rb b/spec/savon/response_spec.rb index 5cb874dd..9e8c0a85 100644 --- a/spec/savon/response_spec.rb +++ b/spec/savon/response_spec.rb @@ -244,15 +244,16 @@ end describe "#http" do - it "should return the HTTPI::Response" do - expect(soap_response.http).to be_an(HTTPI::Response) + it "should return the Faraday::Response" do + expect(soap_response.http).to be_an(Faraday::Response) end end def soap_response(options = {}) defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - http_response = HTTPI::Response.new(response[:code], response[:headers], response[:body]) + env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) + http_response = Faraday::Response.new(env) Savon::Response.new(http_response, globals, locals) end @@ -268,7 +269,8 @@ def http_error_response def invalid_soap_response(options = {}) defaults = { :code => 200, :headers => {}, :body => "I'm not SOAP" } response = defaults.merge options - http_response = HTTPI::Response.new(response[:code], response[:headers], response[:body]) + env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) + http_response = Faraday::Response.new(env) Savon::Response.new(http_response, globals, locals) end From 92ee244107e610e528929a67c9aa5daf76f6c83d Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 15:32:15 -0500 Subject: [PATCH 11/36] update the integration test to use an existent service and faraday logic --- spec/integration/zipcode_example_spec.rb | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/integration/zipcode_example_spec.rb b/spec/integration/zipcode_example_spec.rb index cedeca04..b434fa45 100644 --- a/spec/integration/zipcode_example_spec.rb +++ b/spec/integration/zipcode_example_spec.rb @@ -2,26 +2,26 @@ require "spec_helper" RSpec.describe "ZIP code example" do - it "supports threads making requests simultaneously" do - client = Savon.client( - :wsdl => "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl", + let(:expected) { ["seventy million seventy thousand ten ", "twenty four million fifty thousand one hundred and ten ", "twenty million fifty thousand five hundred and fifty "] } + let(:request_data) { [70070010, 24050110, 20050550] } + let(:client) { + Savon.client( + wsdl: "https://www.dataaccess.com/webservicesserver/NumberConversion.wso?wsdl", + ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, # Lower timeouts so these specs don't take forever when the service is not available. - :open_timeout => 10, - :read_timeout => 10, - - :log => false # Disable logging for cleaner spec output. + open_timeout: 10, + read_timeout: 10, + log: false # Disable logging for cleaner spec output. ) - - mutex = Mutex.new - - request_data = [70070010, 24050110, 20050550] + } + let(:mutex) { Mutex.new } + it "supports threads making requests simultaneously" do threads_waiting = request_data.size - threads = request_data.map do |blz| thread = Thread.new do - response = call_and_fail_gracefully(client, :get_bank, :message => { :blz => blz }) - Thread.current[:value] = response.body[:get_bank_response][:details] + response = call_and_fail_gracefully(client, :number_to_words, :message => { :ubi_num => blz }) + Thread.current[:value] = response.body[:number_to_words_response][:number_to_words_result] mutex.synchronize { threads_waiting -= 1 } end @@ -34,6 +34,6 @@ threads.each(&:kill) values = threads.map { |thr| thr[:value] }.compact - expect(values.uniq.size).to eq(values.size) + expect(values).to match_array(expected) end end From 118125c31b3f407d471274dc059f9045f32ffd66 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 15:32:28 -0500 Subject: [PATCH 12/36] more httpi removal --- spec/spec_helper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d780f663..50fc7097 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,4 +28,3 @@ config.disable_monkey_patching! end -HTTPI.log = false From 6f2d6aa081f5fdc43a647ce7f3bfd96e82a9b400 Mon Sep 17 00:00:00 2001 From: lridge Date: Thu, 4 Jan 2024 15:35:46 -0500 Subject: [PATCH 13/36] rip out httpi; pass the connection around and use faraday post since building a faraday request requires accessing internal apis and is very nasty; solve ntlm handshake; convert various httpi fields to their faraday equivalents and mark any that don't have one --- lib/savon/operation.rb | 70 +++-- lib/savon/request.rb | 161 ++++++----- lib/savon/request_logger.rb | 13 +- spec/savon/operation_spec.rb | 35 ++- spec/savon/request_spec.rb | 517 +++++++++++++++++----------------- spec/savon/soap_fault_spec.rb | 14 +- 6 files changed, 435 insertions(+), 375 deletions(-) diff --git a/lib/savon/operation.rb b/lib/savon/operation.rb index 00e40d87..9a6d3c7f 100644 --- a/lib/savon/operation.rb +++ b/lib/savon/operation.rb @@ -7,6 +7,8 @@ require "savon/request_logger" require "savon/http_error" require "mail" +require 'faraday/gzip' + module Savon class Operation @@ -58,16 +60,20 @@ def call(locals = {}, &block) builder = build(locals, &block) response = Savon.notify_observers(@name, builder, @globals, @locals) - response ||= call_with_logging build_request(builder) + response ||= call_with_logging build_connection(builder) - raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response) + raise_expected_faraday_response! unless response.kind_of?(Faraday::Response) create_response(response) end def request(locals = {}, &block) builder = build(locals, &block) - build_request(builder) + connection = build_connection(builder) + connection.build_request(:post) do |req| + req.url(@globals[:endpoint]) + req.body = @locals[:body] + end end private @@ -83,37 +89,47 @@ def set_locals(locals, block) @locals = locals end - def call_with_logging(request) - @logger.log(request) { HTTPI.post(request, @globals[:adapter]) } + def call_with_logging(connection) + ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm) + @logger.log_response(connection.post(@globals[:endpoint]) { |request| + request.body = @locals[:body] + request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth + @logger.log_request(request) + }) end - def build_request(builder) - @locals[:soap_action] ||= soap_action - @globals[:endpoint] ||= endpoint + def handle_ntlm(connection) + ntlm_message = Net::NTLM::Message + response = connection.get(@globals[:endpoint]) do |request| + request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64 + end + challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1] + message = ntlm_message::Type2.decode64(challenge) + message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h) + end - request = SOAPRequest.new(@globals).build( + def build_connection(builder) + @globals[:endpoint] ||= endpoint + @locals[:soap_action] ||= soap_action + @locals[:body] = builder.to_s + @connection = SOAPRequest.new(@globals).build( :soap_action => soap_action, :cookies => @locals[:cookies], :headers => @locals[:headers] - ) - - request.url = endpoint - request.body = builder.to_s - - if builder.multipart - request.gzip - request.headers["Content-Type"] = ["multipart/related", - "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"", - "start=\"#{builder.multipart[:start]}\"", - "boundary=\"#{builder.multipart[:multipart_boundary]}\""].join("; ") - request.headers["MIME-Version"] = "1.0" + ) do |connection| + if builder.multipart + connection.request :gzip + connection.headers["Content-Type"] = %W[multipart/related + type="#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}", + start="#{builder.multipart[:start]}", + boundary="#{builder.multipart[:multipart_boundary]}"].join("; ") + connection.headers["MIME-Version"] = "1.0" + end + + connection.headers["Content-Length"] = @locals[:body].bytesize.to_s end - # TODO: could HTTPI do this automatically in case the header - # was not specified manually? [dh, 2013-01-04] - request.headers["Content-Length"] = request.body.bytesize.to_s - request end def soap_action @@ -138,8 +154,8 @@ def endpoint end end - def raise_expected_httpi_response! - raise Error, "Observers need to return an HTTPI::Response to mock " \ + def raise_expected_faraday_response! + raise Error, "Observers need to return an Faraday::Response to mock " \ "the request or nil to execute the request." end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 48c916a0..c40f530d 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -1,59 +1,78 @@ # frozen_string_literal: true -require "httpi" +require "faraday" module Savon class HTTPRequest - def initialize(globals, http_request = nil) + def initialize(globals, connection = nil) @globals = globals - @http_request = http_request || HTTPI::Request.new - end - - def build - @http_request + @connection = connection || Faraday::Connection.new end private - def configure_proxy - @http_request.proxy = @globals[:proxy] if @globals.include? :proxy + def configure_proxy(connection) + connection.proxy = @globals[:proxy] if @globals.include? :proxy end - def configure_timeouts - @http_request.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout - @http_request.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout - @http_request.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout + def configure_timeouts(connection) + connection.options.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout + connection.options.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout + connection.options.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout end - def configure_ssl - @http_request.auth.ssl.ssl_version = @globals[:ssl_version] if @globals.include? :ssl_version - @http_request.auth.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version - @http_request.auth.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version - - @http_request.auth.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode - @http_request.auth.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers - - @http_request.auth.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file - @http_request.auth.ssl.cert_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key - @http_request.auth.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file - @http_request.auth.ssl.cert = @globals[:ssl_cert] if @globals.include? :ssl_cert - @http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file - @http_request.auth.ssl.ca_cert_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path - @http_request.auth.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert - @http_request.auth.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store - - @http_request.auth.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password + def configure_ssl(connection) + connection.ssl.verify = @globals[:ssl_verify] if @globals.include? :ssl_verify + connection.ssl.ca_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file + connection.ssl.verify_hostname = @globals[:verify_hostname] if @globals.include? :verify_hostname + connection.ssl.ca_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path + connection.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode + connection.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store + connection.ssl.client_cert = @globals[:ssl_cert] if @globals.include? :ssl_cert + connection.ssl.client_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key + connection.ssl.certificate = @globals[:ssl_certificate] if @globals.include? :ssl_certificate + connection.ssl.private_key = @globals[:ssl_private_key] if @globals.include? :ssl_private_key + connection.ssl.verify_depth = @globals[:verify_depth] if @globals.include? :verify_depth + connection.ssl.version = @globals[:ssl_version] if @globals.include? :ssl_version + connection.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version + connection.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version + + # No Faraday Equivalent out of box, see: https://lostisland.github.io/faraday/#/customization/ssl-options + # connection.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file + # connection.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file + # connection.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert + # connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers + # connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password + + # deprecated and seems to break faraday via infinite stack recursion... Expected to use max_version and min_version + # see: https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D end - def configure_auth - @http_request.auth.basic(*@globals[:basic_auth]) if @globals.include? :basic_auth - @http_request.auth.digest(*@globals[:digest_auth]) if @globals.include? :digest_auth - @http_request.auth.ntlm(*@globals[:ntlm]) if @globals.include? :ntlm + def configure_auth(connection) + connection.request :authorization, :basic, *@globals[:basic_auth] if @globals.include? :basic_auth + if @globals.include? :digest_auth + begin + require 'faraday/digestauth' + connection.request :digest, *@globals[:digest_auth] + rescue LoadError => e + raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' + end + end + if @globals.include?(:ntlm) + begin + require 'rubyntlm' + require 'faraday/net_http_persistent' + connection.adapter :net_http_persistent, pool_size: 5 + rescue LoadError => e + raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' + end + end end - def configure_redirect_handling - if @globals.include? :follow_redirects - @http_request.follow_redirect = @globals[:follow_redirects] + def configure_redirect_handling(connection) + if @globals[:follow_redirects] + require 'faraday/follow_redirects' + connection.response :follow_redirects end end end @@ -61,20 +80,22 @@ def configure_redirect_handling class WSDLRequest < HTTPRequest def build - configure_proxy - configure_timeouts - configure_headers - configure_ssl - configure_auth - configure_redirect_handling - - @http_request + @connection.yield_self do |connection| + configure_proxy(connection) + configure_timeouts(connection) + configure_ssl(connection) + configure_auth(connection) + connection.adapter *@globals[:adapter] if !@globals[:adapter].nil? + connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + configure_headers(connection) + end + @connection end private - def configure_headers - @http_request.headers = @globals[:headers] if @globals.include? :headers + def configure_headers(connection) + connection.headers = @globals[:headers] if @globals.include? :headers end end @@ -85,29 +106,41 @@ class SOAPRequest < HTTPRequest 2 => "application/soap+xml;charset=%s" } + + def build(options = {}) - configure_proxy - configure_timeouts - configure_headers options[:soap_action], options[:headers] - configure_cookies options[:cookies] - configure_ssl - configure_auth - configure_redirect_handling - - @http_request + @connection.yield_self do |connection| + configure_proxy(connection) + configure_timeouts(connection) + configure_ssl(connection) + configure_auth(connection) + configure_headers(connection, options[:soap_action], options[:headers]) + configure_cookies(connection, options[:cookies]) + connection.adapter *@globals[:adapter] unless @globals[:adapter].nil? + connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + configure_redirect_handling(connection) + yield(connection) if block_given? + end + @connection end private - def configure_cookies(cookies) - @http_request.set_cookies(cookies) if cookies + def configure_cookies(connection, cookies) + connection.headers['Cookie'] = cookies.map do |key, value| + if key == :_ + value.join('; ') + else + "#{key}=#{value}" + end + end.join('; ') if cookies end - def configure_headers(soap_action, headers) - @http_request.headers = @globals[:headers] if @globals.include? :headers - @http_request.headers.merge!(headers) if headers - @http_request.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action - @http_request.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding] + def configure_headers(connection, soap_action, headers) + connection.headers = @globals[:headers] if @globals.include? :headers + connection.headers.merge!(headers) if headers + connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action + connection.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding] end end end diff --git a/lib/savon/request_logger.rb b/lib/savon/request_logger.rb index 079449ff..e143d871 100644 --- a/lib/savon/request_logger.rb +++ b/lib/savon/request_logger.rb @@ -27,21 +27,24 @@ def log? def log_headers? @globals[:log_headers] end - - private - def log_request(request) - logger.info { "SOAP request: #{request.url}" } + return unless log? + logger.info { "SOAP request: #{request.path}" } logger.info { headers_to_log(request.headers) } if log_headers? logger.debug { body_to_log(request.body) } end def log_response(response) - logger.info { "SOAP response (status #{response.code})" } + return response unless log? + logger.info { "SOAP response (status #{response.status})" } logger.debug { headers_to_log(response.headers) } if log_headers? logger.debug { body_to_log(response.body) } + response end + private + + def headers_to_log(headers) headers.map { |key, value| "#{key}: #{value}" }.join("\n") end diff --git a/spec/savon/operation_spec.rb b/spec/savon/operation_spec.rb index e10a6d61..24ee8cbc 100644 --- a/spec/savon/operation_spec.rb +++ b/spec/savon/operation_spec.rb @@ -48,7 +48,7 @@ def new_operation(operation_name, wsdl, globals) it "raises if the endpoint cannot be reached" do message = "Error!" - response = HTTPI::Response.new(500, {}, message) + response = Responses.mock_faraday(500, {}, message) error = Wasabi::Resolver::HTTPError.new(message, response) Wasabi::Document.any_instance.stubs(:soap_actions).raises(error) @@ -64,7 +64,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#build" do + describe "build" do it "returns the Builder" do operation = new_operation(:verify_address, wsdl, globals) builder = operation.build(:message => { :test => 'message' }) @@ -74,7 +74,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#call" do + describe "call" do it "returns a response object" do operation = new_operation(:verify_address, wsdl, globals) expect(operation.call).to be_a(Savon::Response) @@ -82,35 +82,32 @@ def new_operation(operation_name, wsdl, globals) it "uses the global :endpoint option for the request" do globals.endpoint("http://v1.example.com") - HTTPI::Request.any_instance.expects(:url=).with("http://v1.example.com") operation = new_operation(:verify_address, wsdl, globals) - - # stub the actual request - http_response = HTTPI::Response.new(200, {}, "") - operation.expects(:call_with_logging).returns(http_response) - + http_response = Responses.mock_faraday(200, {}, "") + Faraday::Connection.any_instance.expects(:post).with(globals[:endpoint]).returns(http_response) operation.call end it "falls back to use the WSDL's endpoint if the :endpoint option was not set" do globals_without_endpoint = Savon::GlobalOptions.new(:log => false) - HTTPI::Request.any_instance.expects(:url=).with(wsdl.endpoint) operation = new_operation(:verify_address, wsdl, globals_without_endpoint) # stub the actual request - http_response = HTTPI::Response.new(200, {}, "") - operation.expects(:call_with_logging).returns(http_response) + http_response = Responses.mock_faraday(200, {}, "") + Faraday::Connection.any_instance.expects(:post).with(wsdl.endpoint).returns(http_response) operation.call end it "sets the Content-Length header" do # XXX: probably the worst spec ever written. refactor! [dh, 2013-01-05] - http_request = HTTPI::Request.new - http_request.headers.expects(:[]=).with("Content-Length", "723") - Savon::SOAPRequest.any_instance.expects(:build).returns(http_request) + http_response = Responses.mock_faraday(200, {}, "") + + Faraday::Utils::Headers.any_instance.expects(:[]=).at_least_once + Faraday::Utils::Headers.any_instance.expects(:[]=).with('Content-Length', "723") + Faraday::Connection.any_instance.expects(:post).returns(http_response) new_operation(:verify_address, wsdl, globals).call end @@ -128,10 +125,10 @@ def new_operation(operation_name, wsdl, globals) it "uses the local :cookies option" do globals.endpoint @server.url(:inspect_request) - cookies = [HTTPI::Cookie.new("some-cookie=choc-chip")] - - HTTPI::Request.any_instance.expects(:set_cookies).with(cookies) + cookies = {'some-cookie': 'choc-chip'} + Faraday::Utils::Headers.any_instance.expects(:[]=).at_least_once + Faraday::Utils::Headers.any_instance.expects(:[]=).with('Cookie', 'some-cookie=choc-chip').at_least_once operation = new_operation(:verify_address, wsdl, globals) operation.call(:cookies => cookies) end @@ -191,7 +188,7 @@ def new_operation(operation_name, wsdl, globals) end end - describe "#request" do + describe "request" do it "returns the request" do operation = new_operation(:verify_address, wsdl, globals) request = operation.request diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index fcdc03f4..836923ed 100644 --- a/spec/savon/request_spec.rb +++ b/spec/savon/request_spec.rb @@ -5,17 +5,18 @@ RSpec.describe Savon::WSDLRequest do let(:globals) { Savon::GlobalOptions.new } - let(:http_request) { HTTPI::Request.new } + let(:http_connection) { Faraday::Connection.new } let(:ciphers) { OpenSSL::Cipher.ciphers } def new_wsdl_request - Savon::WSDLRequest.new(globals, http_request) + Savon::WSDLRequest.new(globals, http_connection) end - describe "#build" do - it "returns an HTTPI::Request" do + describe "build" do + it "returns an Faraday::Request" do wsdl_request = Savon::WSDLRequest.new(globals) - expect(wsdl_request.build).to be_an(HTTPI::Request) + result = wsdl_request.build + expect(result).to be_an(Faraday::Connection) end describe "headers" do @@ -35,13 +36,13 @@ def new_wsdl_request describe "proxy" do it "is set when specified" do globals.proxy("http://proxy.example.com") - http_request.expects(:proxy=).with("http://proxy.example.com") + http_connection.expects(:proxy=).with("http://proxy.example.com") new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:proxy=).never + http_connection.expects(:proxy=).never new_wsdl_request.build end end @@ -49,13 +50,13 @@ def new_wsdl_request describe "open timeout" do it "is set when specified" do globals.open_timeout(22) - http_request.expects(:open_timeout=).with(22) + http_connection.options.expects(:open_timeout=).with(22) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:open_timeout=).never + http_connection.options.expects(:open_timeout=).never new_wsdl_request.build end end @@ -63,13 +64,13 @@ def new_wsdl_request describe "read timeout" do it "is set when specified" do globals.read_timeout(33) - http_request.expects(:read_timeout=).with(33) + http_connection.options.expects(:read_timeout=).with(33) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.options.expects(:read_timeout=).never new_wsdl_request.build end end @@ -77,41 +78,41 @@ def new_wsdl_request describe "write timeout" do it "is set when specified" do globals.write_timeout(44) - http_request.expects(:write_timeout=).with(44) + http_connection.options.expects(:write_timeout=).with(44) new_wsdl_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.expects(:read_timeout=).never new_wsdl_request.build end end - describe "ssl version" do - it "is set when specified" do - globals.ssl_version(:TLSv1) - http_request.auth.ssl.expects(:ssl_version=).with(:TLSv1) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ssl_version=).never - new_wsdl_request.build - end - end + # describe "ssl version" do + # it "is set when specified" do + # globals.ssl_version() + # http_connection.ssl.expects(:version).with(:TLSv1) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ssl_version=).never + # new_wsdl_request.build + # end + # end describe "ssl min_version" do it "is set when specified" do globals.ssl_min_version(:TLS1_2) - http_request.auth.ssl.expects(:min_version=).with(:TLS1_2) + http_connection.ssl.expects(:min_version=).with(:TLS1_2) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:min_version=).never + http_connection.ssl.expects(:min_version=).never new_wsdl_request.build end end @@ -119,13 +120,13 @@ def new_wsdl_request describe "ssl max_version" do it "is set when specified" do globals.ssl_max_version(:TLS1_2) - http_request.auth.ssl.expects(:max_version=).with(:TLS1_2) + http_connection.ssl.expects(:max_version=).with(:TLS1_2) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:max_version=).never + http_connection.ssl.expects(:max_version=).never new_wsdl_request.build end end @@ -133,136 +134,136 @@ def new_wsdl_request describe "ssl verify mode" do it "is set when specified" do globals.ssl_verify_mode(:peer) - http_request.auth.ssl.expects(:verify_mode=).with(:peer) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:verify_mode=).never - new_wsdl_request.build - end - end - - describe "ssl ciphers" do - it "is set when specified" do - globals.ssl_ciphers(ciphers) - http_request.auth.ssl.expects(:ciphers=).with(ciphers) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ciphers=).never - new_wsdl_request.build - end - end - - describe "ssl cert key file" do - it "is set when specified" do - cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - globals.ssl_cert_key_file(cert_key) - http_request.auth.ssl.expects(:cert_key_file=).with(cert_key) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_file=).never - new_wsdl_request.build - end - end - - describe "ssl cert key password" do - it "is set when specified" do - the_pass = "secure-password!42" - globals.ssl_cert_key_password(the_pass) - http_request.auth.ssl.expects(:cert_key_password=).with(the_pass) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_password=).never - new_wsdl_request.build - end - end - - describe "ssl encrypted cert key file" do - describe "set with an invalid decrypting password" do - it "fails when attempting to use the SSL private key" do - skip("JRuby: find out why this does not raise an error!") if RUBY_PLATFORM == 'java' - pass = "wrong-password" - key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - - globals.ssl_cert_file(cert) - globals.ssl_cert_key_password(pass) - globals.ssl_cert_key_file(key) - - new_wsdl_request.build - - expect { http_request.auth.ssl.cert_key }.to raise_error OpenSSL::PKey::PKeyError - end - end - - describe "set with a valid decrypting password" do - it "handles SSL private keys properly" do - pass = "secure-password!42" - key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - - globals.ssl_cert_file(cert) - globals.ssl_cert_key_password(pass) - globals.ssl_cert_key_file(key) - - new_wsdl_request.build - - expect(http_request.auth.ssl.cert_key.to_s).to match(/BEGIN RSA PRIVATE KEY/) - end - end - end - - describe "ssl cert file" do - it "is set when specified" do - cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_cert_file(cert) - http_request.auth.ssl.expects(:cert_file=).with(cert) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_file=).never - new_wsdl_request.build - end - end - - describe "ssl ca cert file" do - it "is set when specified" do - ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_ca_cert_file(ca_cert) - http_request.auth.ssl.expects(:ca_cert_file=).with(ca_cert) + http_connection.ssl.expects(:verify_mode=).with(:peer) new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never - new_wsdl_request.build - end - end + http_connection.ssl.expects(:verify_mode=).never + new_wsdl_request.build + end + end + + # describe "ssl ciphers" do + # it "is set when specified" do + # globals.ssl_ciphers(ciphers) + # http_connection.ssl.expects(:ciphers=).with(ciphers) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ciphers=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl cert key file" do + # it "is set when specified" do + # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) + # globals.ssl_cert_key_file(cert_key) + # http_connection.ssl.expects(:cert_key_file=).with(cert_key) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_file=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl cert key password" do + # it "is set when specified" do + # the_pass = "secure-password!42" + # globals.ssl_cert_key_password(the_pass) + # http_connection.ssl.expects(:cert_key_password=).with(the_pass) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_password=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl encrypted cert key file" do + # describe "set with an invalid decrypting password" do + # it "fails when attempting to use the SSL private key" do + # skip("JRuby: find out why this does not raise an error!") if RUBY_PLATFORM == 'java' + # pass = "wrong-password" + # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) + # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) + # + # globals.ssl_cert_file(cert) + # globals.ssl_cert_key_password(pass) + # globals.ssl_cert_key_file(key) + # + # new_wsdl_request.build + # + # expect { http_connection.ssl.cert_key }.to raise_error OpenSSL::PKey::PKeyError + # end + # end + # + # describe "set with a valid decrypting password" do + # it "handles SSL private keys properly" do + # pass = "secure-password!42" + # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) + # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) + # + # globals.ssl_cert_file(cert) + # globals.ssl_cert_key_password(pass) + # globals.ssl_cert_key_file(key) + # + # new_wsdl_request.build + # + # expect(http_connection.ssl.cert_key.to_s).to match(/BEGIN RSA PRIVATE KEY/) + # end + # end + # end + + # describe "ssl cert file" do + # it "is set when specified" do + # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_cert_file(cert) + # http_connection.ssl.expects(:cert_file=).with(cert) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_file=).never + # new_wsdl_request.build + # end + # end + + # describe "ssl ca cert file" do + # it "is set when specified" do + # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_ca_cert_file(ca_cert) + # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) + # + # new_wsdl_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ca_cert_file=).never + # new_wsdl_request.build + # end + # end describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") - http_request.auth.expects(:basic).with("luke", "secret") + http_connection.expects(:request).with(:authorization, :basic,"luke", "secret") new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.expects(:basic).never + http_connection.expects(:request).with{|args| args.include?(:basic)}.never new_wsdl_request.build end end @@ -270,27 +271,33 @@ def new_wsdl_request describe "digest auth" do it "is set when specified" do globals.digest_auth("lea", "top-secret") - http_request.auth.expects(:digest).with("lea", "top-secret") + http_connection.expects(:request).with(:digest, "lea", "top-secret") new_wsdl_request.build end it "is not set otherwise" do - http_request.auth.expects(:digest).never + http_connection.expects(:request).with{|args| args.include?(:digest)}.never new_wsdl_request.build end end describe "ntlm auth" do - it "is set when specified" do + it 'tries to load ntlm when set' do globals.ntlm("han", "super-secret") - http_request.auth.expects(:ntlm).with("han", "super-secret") + new_wsdl_request.build + expect(require 'rubyntlm').to be(false) + end + + it "applies net-http-persistent when set" do + globals.ntlm("han", "super-secret") + http_connection.expects(:adapter).with{|params| params == :net_http_persistent}.at_least_once new_wsdl_request.build end - it "is not set otherwise" do - http_request.auth.expects(:ntlm).never + it "does not apply net-http-persistent when not set" do + http_connection.expects(:adapter).with(:net_http_persistent, pool_size: 5).never new_wsdl_request.build end end @@ -301,43 +308,49 @@ def new_wsdl_request RSpec.describe Savon::SOAPRequest do let(:globals) { Savon::GlobalOptions.new } - let(:http_request) { HTTPI::Request.new } + let(:http_connection) { Faraday::Connection.new } let(:ciphers) { OpenSSL::Cipher.ciphers } def new_soap_request - Savon::SOAPRequest.new(globals, http_request) + Savon::SOAPRequest.new(globals, http_connection) end - describe "#build" do - it "returns an HTTPI::Request" do + describe "build" do + it "returns an Faraday::Request" do soap_request = Savon::SOAPRequest.new(globals) - expect(soap_request.build).to be_an(HTTPI::Request) + expect(soap_request.build).to be_an(Faraday::Connection) end describe "proxy" do it "is set when specified" do globals.proxy("http://proxy.example.com") - http_request.expects(:proxy=).with("http://proxy.example.com") + http_connection.expects(:proxy=).with("http://proxy.example.com") new_soap_request.build end it "is not set otherwise" do - http_request.expects(:proxy=).never + http_connection.expects(:proxy=).never new_soap_request.build end end describe "cookies" do it "sets the given cookies" do - cookies = [HTTPI::Cookie.new("some-cookie=choc-chip; Path=/; HttpOnly")] - - http_request.expects(:set_cookies).with(cookies) + cookies = { + 'some-cookie': 'choc-chip', + path: '/', + _: ['HttpOnly'] + } + + http_connection.headers.expects(:[]=).at_least_once + http_connection.headers.expects(:[]=).with('Cookie', 'some-cookie=choc-chip; path=/; HttpOnly').at_least_once new_soap_request.build(:cookies => cookies) end it "does not set the cookies if there are none" do - http_request.expects(:set_cookies).never + http_connection.headers.expects(:[]=).at_least_once + http_connection.expects(:[]=).with('Cookie').never new_soap_request.build end end @@ -345,13 +358,13 @@ def new_soap_request describe "open timeout" do it "is set when specified" do globals.open_timeout(22) - http_request.expects(:open_timeout=).with(22) + http_connection.options.expects(:open_timeout=).with(22) new_soap_request.build end it "is not set otherwise" do - http_request.expects(:open_timeout=).never + http_connection.options.expects(:open_timeout=).never new_soap_request.build end end @@ -359,13 +372,13 @@ def new_soap_request describe "read timeout" do it "is set when specified" do globals.read_timeout(33) - http_request.expects(:read_timeout=).with(33) + http_connection.options.expects(:read_timeout=).with(33) new_soap_request.build end it "is not set otherwise" do - http_request.expects(:read_timeout=).never + http_connection.options.expects(:read_timeout=).never new_soap_request.build end end @@ -436,13 +449,13 @@ def new_soap_request describe "ssl version" do it "is set when specified" do globals.ssl_version(:TLSv1) - http_request.auth.ssl.expects(:ssl_version=).with(:TLSv1) + http_connection.ssl.expects(:version=).with(:TLSv1) new_soap_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ssl_version=).never + http_connection.ssl.expects(:version=).never new_soap_request.build end end @@ -450,101 +463,100 @@ def new_soap_request describe "ssl verify mode" do it "is set when specified" do globals.ssl_verify_mode(:peer) - http_request.auth.ssl.expects(:verify_mode=).with(:peer) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:verify_mode=).never - new_soap_request.build - end - end - - describe "ssl ciphers" do - it "is set when specified" do - globals.ssl_ciphers(ciphers) - http_request.auth.ssl.expects(:ciphers=).with(ciphers) + http_connection.ssl.expects(:verify_mode=).with(:peer) new_soap_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ciphers=).never + http_connection.ssl.expects(:verify_mode=).never new_soap_request.build end end - describe "ssl cert key file" do - it "is set when specified" do - cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - globals.ssl_cert_key_file(cert_key) - http_request.auth.ssl.expects(:cert_key_file=).with(cert_key) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_file=).never - new_soap_request.build - end - end - - describe "ssl cert key password" do - it "is set when specified" do - the_pass = "secure-password!42" - globals.ssl_cert_key_password(the_pass) - http_request.auth.ssl.expects(:cert_key_password=).with(the_pass) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_key_password=).never - new_soap_request.build - end - end - - describe "ssl cert file" do - it "is set when specified" do - cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_cert_file(cert) - http_request.auth.ssl.expects(:cert_file=).with(cert) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:cert_file=).never - new_soap_request.build - end - end - - describe "ssl ca cert file" do - it "is set when specified" do - ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - globals.ssl_ca_cert_file(ca_cert) - http_request.auth.ssl.expects(:ca_cert_file=).with(ca_cert) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never - new_soap_request.build - end - end + # describe "ssl ciphers" do + # it "is set when specified" do + # globals.ssl_ciphers(ciphers) + # http_connection.ssl.expects(:ciphers=).with(ciphers) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ciphers=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert key file" do + # it "is set when specified" do + # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) + # globals.ssl_cert_key_file(cert_key) + # http_connection.ssl.expects(:cert_key_file=).with(cert_key) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_file=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert key password" do + # it "is set when specified" do + # the_pass = "secure-password!42" + # globals.ssl_cert_key_password(the_pass) + # http_connection.ssl.expects(:cert_key_password=).with(the_pass) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_key_password=).never + # new_soap_request.build + # end + # end + + # describe "ssl cert file" do + # it "is set when specified" do + # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_cert_file(cert) + # http_connection.ssl.expects(:cert_file=).with(cert) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:cert_file=).never + # new_soap_request.build + # end + # end + + # describe "ssl ca cert file" do + # it "is set when specified" do + # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) + # globals.ssl_ca_cert_file(ca_cert) + # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) + # + # new_soap_request.build + # end + # + # it "is not set otherwise" do + # http_connection.ssl.expects(:ca_cert_file=).never + # new_soap_request.build + # end + # end describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") - http_request.auth.expects(:basic).with("luke", "secret") - + http_connection.expects(:request).with(:authorization, :basic, "luke", "secret") new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:basic).never + http_connection.expects(:request).with(:authorization, :basic, "luke", 'secret').never new_soap_request.build end end @@ -552,27 +564,26 @@ def new_soap_request describe "digest auth" do it "is set when specified" do globals.digest_auth("lea", "top-secret") - http_request.auth.expects(:digest).with("lea", "top-secret") + http_connection.expects(:request).with(:digest, "lea", "top-secret") new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:digest).never + http_connection.expects(:request).with(:digest, "lea", 'top-secret').never new_soap_request.build end end describe "ntlm auth" do - it "is set when specified" do + it "uses the net-http-persistent adapter in faraday" do globals.ntlm("han", "super-secret") - http_request.auth.expects(:ntlm).with("han", "super-secret") - + http_connection.expects(:adapter).with(:net_http_persistent, {:pool_size => 5}) new_soap_request.build end it "is not set otherwise" do - http_request.auth.expects(:ntlm).never + http_connection.expects(:adapter).with(:net_http_persistent, {:pool_size => 5}).never new_soap_request.build end end diff --git a/spec/savon/soap_fault_spec.rb b/spec/savon/soap_fault_spec.rb index 1dde0501..6e38dbe0 100644 --- a/spec/savon/soap_fault_spec.rb +++ b/spec/savon/soap_fault_spec.rb @@ -9,7 +9,7 @@ let(:soap_fault_nc) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault)), nori_no_convert } let(:soap_fault_nc2) { Savon::SOAPFault.new new_response(:body => Fixture.response(:soap_fault12)), nori_no_convert } let(:another_soap_fault) { Savon::SOAPFault.new new_response(:body => Fixture.response(:another_soap_fault)), nori } - let(:soap_fault_no_body) { Savon::SOAPFault.new new_response(:body => {}), nori } + let(:soap_fault_no_body) { Savon::SOAPFault.new new_response(:body => ''), nori } let(:no_fault) { Savon::SOAPFault.new new_response, nori } let(:nori) { Nori.new(:strip_namespaces => true, :convert_tags_to => lambda { |tag| Savon::StringUtils.snakecase(tag).to_sym }) } @@ -19,9 +19,9 @@ expect(Savon::SOAPFault.ancestors).to include(Savon::Error) end - describe "#http" do - it "returns the HTTPI::Response" do - expect(soap_fault.http).to be_an(HTTPI::Response) + describe "http" do + it "returns the Faraday::Response" do + expect(soap_fault.http).to be_an(Faraday::Response) end end @@ -52,7 +52,7 @@ end [:message, :to_s].each do |method| - describe "##{method}" do + describe "#{method}" do it "returns a SOAP 1.1 fault message" do expect(soap_fault.send method).to eq("(soap:Server) Fault occurred while processing.") end @@ -83,7 +83,7 @@ end end - describe "#to_hash" do + describe "to_hash" do it "returns the SOAP response as a Hash unless a SOAP fault is present" do expect(no_fault.to_hash[:authenticate_response][:return][:success]).to be_truthy end @@ -141,7 +141,7 @@ def new_response(options = {}) defaults = { :code => 500, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - HTTPI::Response.new response[:code], response[:headers], response[:body] + Responses.mock_faraday response[:code], response[:headers], response[:body] end end From b70b003a7ee8509b2cec4936971fec6b4c6acb22 Mon Sep 17 00:00:00 2001 From: lridge Date: Fri, 5 Jan 2024 07:42:42 -0500 Subject: [PATCH 14/36] update dependency version constraints so CI errors out for a more clear reason --- savon.gemspec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/savon.gemspec b/savon.gemspec index e57ef62e..cafa6e5f 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -17,19 +17,19 @@ Gem::Specification.new do |s| s.license = 'MIT' s.add_dependency "nori", "~> 2.4" - s.add_dependency "faraday", ">= 2.8" - s.add_dependency "faraday-gzip", ">= 2.0" - s.add_dependency "faraday-digestauth", ">= 0.2" - s.add_dependency "faraday-net_http_persistent", ">= 2.1" - s.add_dependency "faraday-follow_redirects", ">= 0.3" - s.add_dependency "rubyntlm", ">= 0.6" - s.add_dependency "wasabi", ">= 3.7" + s.add_dependency "faraday", "~> 2.8" + s.add_dependency "faraday-gzip", "~> 2.0" + s.add_dependency "faraday-follow_redirects", "~> 0.3" + s.add_dependency "wasabi", "~> 5.0" s.add_dependency "akami", "~> 1.2" s.add_dependency "gyoku", "~> 1.2" s.add_dependency "builder", ">= 2.1.2" s.add_dependency "nokogiri", ">= 1.8.1" s.add_dependency "mail", "~> 2.5" + s.add_development_dependency "faraday-digestauth", "~> 0.4" + s.add_development_dependency "faraday-net_http_persistent", "~> 2.1" + s.add_development_dependency "rubyntlm", ">= 0.6" s.add_development_dependency "rack", " < 4" s.add_development_dependency "puma", ">= 4.3.8", "< 7" From e9192efafc7538e6be413ceb3b11f7197456dac9 Mon Sep 17 00:00:00 2001 From: lridge Date: Fri, 5 Jan 2024 07:47:48 -0500 Subject: [PATCH 15/36] uncomment a spec that i accidentally left commented --- spec/savon/options_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/savon/options_spec.rb b/spec/savon/options_spec.rb index 7a262720..a6067165 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -446,14 +446,14 @@ def to_s end end - # context "global :ssl_version" do - # it "sets the SSL version to use" do - # Faraday::SSLOptions.any_instance.expects(:ssl_version=).with(:TLSv1).twice - # - # client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1) - # client.call(:authenticate) - # end - # end + context "global :ssl_version" do + it "sets the SSL version to use" do + Faraday::SSLOptions.any_instance.expects(:version=).with(:TLSv1).twice + + client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1) + client.call(:authenticate) + end + end context "global :ssl_min_version" do it "sets the SSL min_version to use" do From 3b6a87fdbfeebcbd581386da11016779bc1079c6 Mon Sep 17 00:00:00 2001 From: lridge Date: Fri, 5 Jan 2024 08:00:31 -0500 Subject: [PATCH 16/36] fix ssl version spec, remove specs for functionality unsupported by faraday --- spec/savon/request_spec.rb | 209 +++---------------------------------- 1 file changed, 13 insertions(+), 196 deletions(-) diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index 836923ed..6eb4fe42 100644 --- a/spec/savon/request_spec.rb +++ b/spec/savon/request_spec.rb @@ -89,19 +89,19 @@ def new_wsdl_request end end - # describe "ssl version" do - # it "is set when specified" do - # globals.ssl_version() - # http_connection.ssl.expects(:version).with(:TLSv1) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:ssl_version=).never - # new_wsdl_request.build - # end - # end + describe "ssl version" do + it "is set when specified" do + globals.ssl_version(:TLSv1) + http_connection.ssl.expects(:version=).with(:TLSv1) + + new_wsdl_request.build + end + + it "is not set otherwise" do + http_connection.ssl.expects(:version=).never + new_wsdl_request.build + end + end describe "ssl min_version" do it "is set when specified" do @@ -145,115 +145,6 @@ def new_wsdl_request end end - # describe "ssl ciphers" do - # it "is set when specified" do - # globals.ssl_ciphers(ciphers) - # http_connection.ssl.expects(:ciphers=).with(ciphers) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:ciphers=).never - # new_wsdl_request.build - # end - # end - - # describe "ssl cert key file" do - # it "is set when specified" do - # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - # globals.ssl_cert_key_file(cert_key) - # http_connection.ssl.expects(:cert_key_file=).with(cert_key) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_key_file=).never - # new_wsdl_request.build - # end - # end - - # describe "ssl cert key password" do - # it "is set when specified" do - # the_pass = "secure-password!42" - # globals.ssl_cert_key_password(the_pass) - # http_connection.ssl.expects(:cert_key_password=).with(the_pass) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_key_password=).never - # new_wsdl_request.build - # end - # end - - # describe "ssl encrypted cert key file" do - # describe "set with an invalid decrypting password" do - # it "fails when attempting to use the SSL private key" do - # skip("JRuby: find out why this does not raise an error!") if RUBY_PLATFORM == 'java' - # pass = "wrong-password" - # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - # - # globals.ssl_cert_file(cert) - # globals.ssl_cert_key_password(pass) - # globals.ssl_cert_key_file(key) - # - # new_wsdl_request.build - # - # expect { http_connection.ssl.cert_key }.to raise_error OpenSSL::PKey::PKeyError - # end - # end - # - # describe "set with a valid decrypting password" do - # it "handles SSL private keys properly" do - # pass = "secure-password!42" - # key = File.expand_path("../../fixtures/ssl/client_encrypted_key.pem", __FILE__) - # cert = File.expand_path("../../fixtures/ssl/client_encrypted_key_cert.pem", __FILE__) - # - # globals.ssl_cert_file(cert) - # globals.ssl_cert_key_password(pass) - # globals.ssl_cert_key_file(key) - # - # new_wsdl_request.build - # - # expect(http_connection.ssl.cert_key.to_s).to match(/BEGIN RSA PRIVATE KEY/) - # end - # end - # end - - # describe "ssl cert file" do - # it "is set when specified" do - # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - # globals.ssl_cert_file(cert) - # http_connection.ssl.expects(:cert_file=).with(cert) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_file=).never - # new_wsdl_request.build - # end - # end - - # describe "ssl ca cert file" do - # it "is set when specified" do - # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - # globals.ssl_ca_cert_file(ca_cert) - # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) - # - # new_wsdl_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:ca_cert_file=).never - # new_wsdl_request.build - # end - # end - describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") @@ -474,80 +365,6 @@ def new_soap_request end end - # describe "ssl ciphers" do - # it "is set when specified" do - # globals.ssl_ciphers(ciphers) - # http_connection.ssl.expects(:ciphers=).with(ciphers) - # - # new_soap_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:ciphers=).never - # new_soap_request.build - # end - # end - - # describe "ssl cert key file" do - # it "is set when specified" do - # cert_key = File.expand_path("../../fixtures/ssl/client_key.pem", __FILE__) - # globals.ssl_cert_key_file(cert_key) - # http_connection.ssl.expects(:cert_key_file=).with(cert_key) - # - # new_soap_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_key_file=).never - # new_soap_request.build - # end - # end - - # describe "ssl cert key password" do - # it "is set when specified" do - # the_pass = "secure-password!42" - # globals.ssl_cert_key_password(the_pass) - # http_connection.ssl.expects(:cert_key_password=).with(the_pass) - # - # new_soap_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_key_password=).never - # new_soap_request.build - # end - # end - - # describe "ssl cert file" do - # it "is set when specified" do - # cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - # globals.ssl_cert_file(cert) - # http_connection.ssl.expects(:cert_file=).with(cert) - # - # new_soap_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:cert_file=).never - # new_soap_request.build - # end - # end - - # describe "ssl ca cert file" do - # it "is set when specified" do - # ca_cert = File.expand_path("../../fixtures/ssl/client_cert.pem", __FILE__) - # globals.ssl_ca_cert_file(ca_cert) - # http_connection.ssl.expects(:ca_cert_file=).with(ca_cert) - # - # new_soap_request.build - # end - # - # it "is not set otherwise" do - # http_connection.ssl.expects(:ca_cert_file=).never - # new_soap_request.build - # end - # end - describe "basic auth" do it "is set when specified" do globals.basic_auth("luke", "secret") From 2799f0f5fada3429b59cf24a0fad6d21a8bfbdcf Mon Sep 17 00:00:00 2001 From: lridge Date: Fri, 5 Jan 2024 08:03:31 -0500 Subject: [PATCH 17/36] remove infinite recursion comment since it was an error in the spec --- lib/savon/request.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/savon/request.rb b/lib/savon/request.rb index c40f530d..78272248 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -44,8 +44,6 @@ def configure_ssl(connection) # connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers # connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password - # deprecated and seems to break faraday via infinite stack recursion... Expected to use max_version and min_version - # see: https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/SSL/SSLContext.html#method-i-ssl_version-3D end def configure_auth(connection) From 4cebdf866981c3e838ab2133c008d3756e5b2c26 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jan 2024 12:41:20 -0500 Subject: [PATCH 18/36] use from initializer for env for 2.7 compat --- spec/support/responses.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/responses.rb b/spec/support/responses.rb index f0c3ea2a..1d4b005e 100644 --- a/spec/support/responses.rb +++ b/spec/support/responses.rb @@ -1,7 +1,7 @@ class Responses class << self def mock_faraday(code, headers, body) - env = Faraday::Env.new(status: code, response_headers: headers, response_body: body) + env = Faraday::Env.from(status: code, response_headers: headers, response_body: body) Faraday::Response.new(env) end end From c5d8720b8b875fa97798d70a2119663427b96758 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 07:07:35 -0500 Subject: [PATCH 19/36] refactor to use the Responses helper to mock faraday responses --- lib/savon/mock/expectation.rb | 2 +- spec/savon/http_error_spec.rb | 3 +-- spec/savon/response_spec.rb | 7 ++----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/savon/mock/expectation.rb b/lib/savon/mock/expectation.rb index 10c2af2e..d0908b14 100644 --- a/lib/savon/mock/expectation.rb +++ b/lib/savon/mock/expectation.rb @@ -41,7 +41,7 @@ def response! unless @response raise ExpectationError, "This expectation was not set up with a response." end - env = Faraday::Env.new(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body]) + env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body]) Faraday::Response.new(env) end diff --git a/spec/savon/http_error_spec.rb b/spec/savon/http_error_spec.rb index f21bf283..0a0d0ad2 100644 --- a/spec/savon/http_error_spec.rb +++ b/spec/savon/http_error_spec.rb @@ -51,8 +51,7 @@ def new_response(options = {}) defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) - Faraday::Response.new(env) + Responses.mock_faraday(response[:code], response[:headers], response[:body]) end end diff --git a/spec/savon/response_spec.rb b/spec/savon/response_spec.rb index 9e8c0a85..9bab16af 100644 --- a/spec/savon/response_spec.rb +++ b/spec/savon/response_spec.rb @@ -252,9 +252,7 @@ def soap_response(options = {}) defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) } response = defaults.merge options - env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) - http_response = Faraday::Response.new(env) - + http_response = Responses.mock_faraday(response[:code], response[:headers], response[:body]) Savon::Response.new(http_response, globals, locals) end @@ -269,8 +267,7 @@ def http_error_response def invalid_soap_response(options = {}) defaults = { :code => 200, :headers => {}, :body => "I'm not SOAP" } response = defaults.merge options - env = Faraday::Env.new(status: response[:code], response_headers: response[:headers], response_body: response[:body]) - http_response = Faraday::Response.new(env) + http_response = Responses.mock_faraday(response[:code], response[:headers], response[:body]) Savon::Response.new(http_response, globals, locals) end From 2d57a45b8b7ee4649a9a7a46a94036a1d97997df Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 07:27:28 -0500 Subject: [PATCH 20/36] extraction refactor --- lib/savon/request.rb | 56 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 78272248..3b44c483 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -47,23 +47,29 @@ def configure_ssl(connection) end def configure_auth(connection) - connection.request :authorization, :basic, *@globals[:basic_auth] if @globals.include? :basic_auth - if @globals.include? :digest_auth - begin - require 'faraday/digestauth' - connection.request :digest, *@globals[:digest_auth] - rescue LoadError => e - raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' - end - end - if @globals.include?(:ntlm) - begin - require 'rubyntlm' - require 'faraday/net_http_persistent' - connection.adapter :net_http_persistent, pool_size: 5 - rescue LoadError => e - raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' - end + basic_auth(connection) if @globals.include?(:basic_auth) + digest_auth(connection) if @globals.include?(:digest_auth) + ntlm_auth(connection) if @globals.include?(:ntlm) + end + + def basic_auth(connection) + connection.request(:authorization, :basic, *@globals[:basic_auth]) + end + + def digest_auth(connection) + require 'faraday/digestauth' + connection.request :digest, *@globals[:digest_auth] + rescue LoadError => e + raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' + end + + def ntlm_auth(connection) + begin + require 'rubyntlm' + require 'faraday/net_http_persistent' + connection.adapter :net_http_persistent, pool_size: 5 + rescue LoadError => e + raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' end end @@ -78,15 +84,13 @@ def configure_redirect_handling(connection) class WSDLRequest < HTTPRequest def build - @connection.yield_self do |connection| - configure_proxy(connection) - configure_timeouts(connection) - configure_ssl(connection) - configure_auth(connection) - connection.adapter *@globals[:adapter] if !@globals[:adapter].nil? - connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] - configure_headers(connection) - end + configure_proxy(@connection) + configure_timeouts(@connection) + configure_ssl(@connection) + configure_auth(@connection) + connection.adapter *@globals[:adapter] unless @globals[:adapter].nil? + connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + configure_headers(connection) @connection end From 7776c4e139f260b9fca27470c4044d280526c57e Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 07:28:11 -0500 Subject: [PATCH 21/36] standardize parenthesis use a bit more --- lib/savon/request.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 3b44c483..8a7e2e99 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -88,8 +88,8 @@ def build configure_timeouts(@connection) configure_ssl(@connection) configure_auth(@connection) - connection.adapter *@globals[:adapter] unless @globals[:adapter].nil? - connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] + connection.adapter(*@globals[:adapter]) unless @globals[:adapter].nil? + connection.response(:logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level) if @globals[:log] configure_headers(connection) @connection end From a10ec735d7776074774ad2e4b5cc92b4e50f87a6 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 07:35:48 -0500 Subject: [PATCH 22/36] extract connection to a protected attribute --- lib/savon/request.rb | 80 ++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 8a7e2e99..59672cc3 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -11,17 +11,17 @@ def initialize(globals, connection = nil) private - def configure_proxy(connection) + def configure_proxy connection.proxy = @globals[:proxy] if @globals.include? :proxy end - def configure_timeouts(connection) + def configure_timeouts connection.options.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout connection.options.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout connection.options.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout end - def configure_ssl(connection) + def configure_ssl connection.ssl.verify = @globals[:ssl_verify] if @globals.include? :ssl_verify connection.ssl.ca_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file connection.ssl.verify_hostname = @globals[:verify_hostname] if @globals.include? :verify_hostname @@ -46,24 +46,24 @@ def configure_ssl(connection) end - def configure_auth(connection) - basic_auth(connection) if @globals.include?(:basic_auth) - digest_auth(connection) if @globals.include?(:digest_auth) - ntlm_auth(connection) if @globals.include?(:ntlm) + def configure_auth + basic_auth if @globals.include?(:basic_auth) + digest_auth if @globals.include?(:digest_auth) + ntlm_auth if @globals.include?(:ntlm) end - def basic_auth(connection) + def basic_auth connection.request(:authorization, :basic, *@globals[:basic_auth]) end - def digest_auth(connection) + def digest_auth require 'faraday/digestauth' connection.request :digest, *@globals[:digest_auth] rescue LoadError => e raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' end - def ntlm_auth(connection) + def ntlm_auth begin require 'rubyntlm' require 'faraday/net_http_persistent' @@ -73,30 +73,41 @@ def ntlm_auth(connection) end end - def configure_redirect_handling(connection) + def configure_redirect_handling if @globals[:follow_redirects] require 'faraday/follow_redirects' connection.response :follow_redirects end end + + def configure_adapter + connection.adapter(*@globals[:adapter]) unless @globals[:adapter].nil? + end + + def configure_logging + connection.response(:logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level) if @globals[:log] + end + + protected + attr_reader :connection end class WSDLRequest < HTTPRequest def build - configure_proxy(@connection) - configure_timeouts(@connection) - configure_ssl(@connection) - configure_auth(@connection) - connection.adapter(*@globals[:adapter]) unless @globals[:adapter].nil? - connection.response(:logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level) if @globals[:log] - configure_headers(connection) - @connection + configure_proxy + configure_timeouts + configure_ssl + configure_auth + configure_adapter + configure_logging + configure_headers + connection end private - def configure_headers(connection) + def configure_header connection.headers = @globals[:headers] if @globals.include? :headers end end @@ -108,27 +119,22 @@ class SOAPRequest < HTTPRequest 2 => "application/soap+xml;charset=%s" } - - def build(options = {}) - @connection.yield_self do |connection| - configure_proxy(connection) - configure_timeouts(connection) - configure_ssl(connection) - configure_auth(connection) - configure_headers(connection, options[:soap_action], options[:headers]) - configure_cookies(connection, options[:cookies]) - connection.adapter *@globals[:adapter] unless @globals[:adapter].nil? - connection.response :logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level if @globals[:log] - configure_redirect_handling(connection) - yield(connection) if block_given? - end - @connection + configure_proxy + configure_timeouts + configure_ssl + configure_auth + configure_headers(options[:soap_action], options[:headers]) + configure_cookies(options[:cookies]) + configure_adapter + configure_logging + configure_redirect_handling + yield(connection) if block_given? end private - def configure_cookies(connection, cookies) + def configure_cookies(cookies) connection.headers['Cookie'] = cookies.map do |key, value| if key == :_ value.join('; ') @@ -138,7 +144,7 @@ def configure_cookies(connection, cookies) end.join('; ') if cookies end - def configure_headers(connection, soap_action, headers) + def configure_headers(soap_action, headers) connection.headers = @globals[:headers] if @globals.include? :headers connection.headers.merge!(headers) if headers connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action From fead8e7cdd35579d5a997b91edbebce1ba076241 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 07:39:26 -0500 Subject: [PATCH 23/36] fix a typeo --- lib/savon/request.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 59672cc3..59f4b801 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -107,7 +107,7 @@ def build private - def configure_header + def configure_headers connection.headers = @globals[:headers] if @globals.include? :headers end end @@ -130,6 +130,7 @@ def build(options = {}) configure_logging configure_redirect_handling yield(connection) if block_given? + connection end private From 3bc87c7b5446722cd978cf2539aca712fd7c8712 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jan 2024 08:14:01 -0500 Subject: [PATCH 24/36] mixed janitorial work --- lib/savon/mock/expectation.rb | 2 +- lib/savon/options.rb | 4 ++-- lib/savon/request.rb | 4 ++-- lib/savon/soap_fault.rb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/savon/mock/expectation.rb b/lib/savon/mock/expectation.rb index d0908b14..b821a09a 100644 --- a/lib/savon/mock/expectation.rb +++ b/lib/savon/mock/expectation.rb @@ -75,7 +75,7 @@ def equals_except_any(msg_expected, msg_real) next if (expected_value == :any && msg_real.include?(key)) return false if expected_value != msg_real[key] end - return true + true end end end diff --git a/lib/savon/options.rb b/lib/savon/options.rb index ab2cab59..4dbf35c2 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -131,7 +131,7 @@ def namespace(namespace) @options[:namespace] = namespace end - # The namespace identifer. + # The namespace identifier. def namespace_identifier(identifier) @options[:namespace_identifier] = identifier end @@ -405,7 +405,7 @@ def initialize(options = {}) # The local SOAP header. Expected to be a Hash or respond to #to_s. # Will be merged with the global SOAP header if both are Hashes. - # Otherwise the local option will be prefered. + # Otherwise the local option will be preferred. def soap_header(header) @options[:soap_header] = header end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 59f4b801..bdd19a00 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -59,7 +59,7 @@ def basic_auth def digest_auth require 'faraday/digestauth' connection.request :digest, *@globals[:digest_auth] - rescue LoadError => e + rescue LoadError raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' end @@ -68,7 +68,7 @@ def ntlm_auth require 'rubyntlm' require 'faraday/net_http_persistent' connection.adapter :net_http_persistent, pool_size: 5 - rescue LoadError => e + rescue LoadError raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' end end diff --git a/lib/savon/soap_fault.rb b/lib/savon/soap_fault.rb index 69beafce..40e61dfc 100644 --- a/lib/savon/soap_fault.rb +++ b/lib/savon/soap_fault.rb @@ -5,7 +5,7 @@ class SOAPFault < Error def self.present?(http, xml = nil) xml ||= http.body fault_node = xml.include?("Fault>") - soap1_fault = xml.match(/faultcode\/?\>/) && xml.match(/faultstring\/?\>/) + soap1_fault = xml.match(/faultcode\/?>/) && xml.match(/faultstring\/?>/) soap2_fault = xml.include?("Code>") && xml.include?("Reason>") fault_node && (soap1_fault || soap2_fault) From 6562ecf2c2a5c5d924c1da7a37ed0c48ef3a858e Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 12:52:05 -0400 Subject: [PATCH 25/36] change cookies to make more sense, document new usage --- lib/savon/options.rb | 6 +++++- lib/savon/request.rb | 4 ++-- spec/savon/request_spec.rb | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index 4dbf35c2..ebc5005e 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -465,7 +465,11 @@ def soap_action(soap_action) @options[:soap_action] = soap_action end - # Cookies to be used for the next request. + # Cookies to be used for the next request + # @param [Hash] cookies cookies associated to nil will be appended as array cookies, if you need a cookie equal to + # and empty string, set it to "" + # @example cookies({accept: 'application/json', some-cookie: 'foo', HttpOnly: nil}) + # # => "accept=application/json; some-cookie=foo; HttpOnly" def cookies(cookies) @options[:cookies] = cookies end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index bdd19a00..8bfbdc0a 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -137,8 +137,8 @@ def build(options = {}) def configure_cookies(cookies) connection.headers['Cookie'] = cookies.map do |key, value| - if key == :_ - value.join('; ') + if value.nil? + key else "#{key}=#{value}" end diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index 6eb4fe42..2548bf29 100644 --- a/spec/savon/request_spec.rb +++ b/spec/savon/request_spec.rb @@ -231,7 +231,7 @@ def new_soap_request cookies = { 'some-cookie': 'choc-chip', path: '/', - _: ['HttpOnly'] + HttpOnly: nil } http_connection.headers.expects(:[]=).at_least_once From 1482387bed44fd013b3bd7b3099172ce6255f79f Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 12:53:49 -0400 Subject: [PATCH 26/36] also document empty string case --- lib/savon/options.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index ebc5005e..b809692e 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -468,8 +468,8 @@ def soap_action(soap_action) # Cookies to be used for the next request # @param [Hash] cookies cookies associated to nil will be appended as array cookies, if you need a cookie equal to # and empty string, set it to "" - # @example cookies({accept: 'application/json', some-cookie: 'foo', HttpOnly: nil}) - # # => "accept=application/json; some-cookie=foo; HttpOnly" + # @example cookies({accept: 'application/json', some-cookie: 'foo', "empty-cookie": "", HttpOnly: nil}) + # # => "accept=application/json; some-cookie=foo; empty-cookie=; HttpOnly" def cookies(cookies) @options[:cookies] = cookies end From 335adeb08b454886e0f43426f05c03d601a93bd8 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:00:08 -0400 Subject: [PATCH 27/36] use the correct wasabi version --- savon.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/savon.gemspec b/savon.gemspec index c5c1cc12..4dc41ee0 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.add_dependency "faraday", "~> 2.8" s.add_dependency "faraday-gzip", "~> 2.0" s.add_dependency "faraday-follow_redirects", "~> 0.3" - s.add_dependency "wasabi", ">= 6" + s.add_dependency "wasabi", " > 5" s.add_dependency "akami", "~> 1.2" s.add_dependency "gyoku", "~> 1.2" s.add_dependency "builder", ">= 2.1.2" From a381d7aaa14b9bc4b9ecad7a5ab589964eb85450 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:12:35 -0400 Subject: [PATCH 28/36] follow rack and remove support for digest auth --- lib/savon/options.rb | 1 + lib/savon/request.rb | 8 -------- spec/integration/support/application.rb | 16 ---------------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index b809692e..f9551f05 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -318,6 +318,7 @@ def basic_auth(*credentials) # HTTP digest auth credentials. def digest_auth(*credentials) + deprecate @options[:digest_auth] = credentials.flatten end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 8bfbdc0a..5197019e 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -48,7 +48,6 @@ def configure_ssl def configure_auth basic_auth if @globals.include?(:basic_auth) - digest_auth if @globals.include?(:digest_auth) ntlm_auth if @globals.include?(:ntlm) end @@ -56,13 +55,6 @@ def basic_auth connection.request(:authorization, :basic, *@globals[:basic_auth]) end - def digest_auth - require 'faraday/digestauth' - connection.request :digest, *@globals[:digest_auth] - rescue LoadError - raise LoadError, 'Using Digest Auth requests `faraday-digestauth`' - end - def ntlm_auth begin require 'rubyntlm' diff --git a/spec/integration/support/application.rb b/spec/integration/support/application.rb index 1052c169..7f261e28 100644 --- a/spec/integration/support/application.rb +++ b/spec/integration/support/application.rb @@ -63,22 +63,6 @@ def self.respond_with(options = {}) } end - map "/digest_auth" do - unprotected_app = lambda { |env| - IntegrationServer.respond_with :body => "digest-auth" - } - - realm = 'digest-realm' - app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username| - username == 'admin' ? Digest::MD5.hexdigest("admin:#{realm}:secret") : nil - end - app.realm = realm - app.opaque = 'this-should-be-secret' - app.passwords_hashed = true - - run app - end - map "/multipart" do run lambda { |env| boundary = 'mimepart_boundary' From 6b336a28bb6760d2adb659eaa8f1de609008e734 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:23:24 -0400 Subject: [PATCH 29/36] more digest removals, update the changelog --- CHANGELOG.md | 12 ++++++++++++ lib/savon/options.rb | 1 - savon.gemspec | 1 - spec/savon/options_spec.rb | 9 --------- spec/savon/request_spec.rb | 28 ---------------------------- 5 files changed, 12 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a22822f4..b5dfe556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## Unreleased * Ruby 3.0+ is required in the gemspec. +* Changes to utilize faraday instead of http +* BC BREAKING Cookies are handled differently now +* BC BREAKING Multiple pieces of functionality will rely on faraday libraries to be provided by the consuming codebase +* BC BREAKING Adapter overrides now utilize the faraday model +* BC BREAKING Multiple hard deprecations due to a lack of feature parity between Faraday and HTTPI + * Deprecates digest auth + * Deprecates ssl_cert_key_file auth, upgrade path is to read the key + in and provide it + * Deprecates encrypted ssl keys, upgrade path is to + decrypt the key and pass it to faraday in code + * Deprecates providing a ca cert, upgrade path is to provide a ca cert file + * deprecates overriding ssl ciphers, as faraday does not support this * Add your PR changelog line here ## 2.15.0 (2024-02-10) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index f9551f05..264a8db4 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -259,7 +259,6 @@ def ssl_verify_mode(verify_mode) # Sets the cert key file to use. def ssl_cert_key_file(file) - deprecate @options[:ssl_cert_key_file] = file end diff --git a/savon.gemspec b/savon.gemspec index 4dc41ee0..515f66e3 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -27,7 +27,6 @@ Gem::Specification.new do |s| s.add_dependency "nokogiri", ">= 1.8.1" s.add_dependency "mail", "~> 2.5" - s.add_development_dependency "faraday-digestauth", "~> 0.4" s.add_development_dependency "faraday-net_http_persistent", "~> 2.1" s.add_development_dependency "rubyntlm", ">= 0.6" s.add_development_dependency "rack", " < 4" diff --git a/spec/savon/options_spec.rb b/spec/savon/options_spec.rb index a6067165..51484f15 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -564,15 +564,6 @@ def to_s end end - context "global :digest_auth" do - it "sets the digest auth credentials" do - client = new_client(:endpoint => @server.url(:digest_auth), :digest_auth => ["admin", "secret"]) - response = client.call(:authenticate) - - expect(response.http.body).to eq("digest-auth") - end - end - context "global :ntlm" do it "sets the ntlm credentials to use" do credentials = ["admin", "secret"] diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index 2548bf29..12b7f310 100644 --- a/spec/savon/request_spec.rb +++ b/spec/savon/request_spec.rb @@ -159,20 +159,6 @@ def new_wsdl_request end end - describe "digest auth" do - it "is set when specified" do - globals.digest_auth("lea", "top-secret") - http_connection.expects(:request).with(:digest, "lea", "top-secret") - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_connection.expects(:request).with{|args| args.include?(:digest)}.never - new_wsdl_request.build - end - end - describe "ntlm auth" do it 'tries to load ntlm when set' do globals.ntlm("han", "super-secret") @@ -378,20 +364,6 @@ def new_soap_request end end - describe "digest auth" do - it "is set when specified" do - globals.digest_auth("lea", "top-secret") - http_connection.expects(:request).with(:digest, "lea", "top-secret") - - new_soap_request.build - end - - it "is not set otherwise" do - http_connection.expects(:request).with(:digest, "lea", 'top-secret').never - new_soap_request.build - end - end - describe "ntlm auth" do it "uses the net-http-persistent adapter in faraday" do globals.ntlm("han", "super-secret") From 3f1ecbf8961b08a5cce53f47f93f2c0f87626977 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:25:21 -0400 Subject: [PATCH 30/36] forgot to deprecate ssl cert key files --- lib/savon/options.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index 264a8db4..f9551f05 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -259,6 +259,7 @@ def ssl_verify_mode(verify_mode) # Sets the cert key file to use. def ssl_cert_key_file(file) + deprecate @options[:ssl_cert_key_file] = file end From a19bb896b487e543d0f2de64cc6304a2fb01a6e7 Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:28:06 -0400 Subject: [PATCH 31/36] add httpclient as a development dep so we can run our tests on 3.4 --- savon.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/savon.gemspec b/savon.gemspec index 515f66e3..94c0661c 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rubyntlm", ">= 0.6" s.add_development_dependency "rack", " < 4" s.add_development_dependency "puma", ">= 4.3.8", "< 7" + s.add_development_dependency "httpclient" s.add_development_dependency "byebug" s.add_development_dependency "rake", ">= 12.3.3" From 04eeea667d0997ec75c9c0a7e8f316665a0e848e Mon Sep 17 00:00:00 2001 From: lridge Date: Mon, 8 Jul 2024 13:29:42 -0400 Subject: [PATCH 32/36] add mutex_m as a development dep so we can run our tests on 3.4, since httpclient relies on it --- savon.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/savon.gemspec b/savon.gemspec index 94c0661c..0142bf15 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |s| s.add_development_dependency "rack", " < 4" s.add_development_dependency "puma", ">= 4.3.8", "< 7" s.add_development_dependency "httpclient" + s.add_development_dependency "mutex_m" s.add_development_dependency "byebug" s.add_development_dependency "rake", ">= 12.3.3" From 4fb9fed55a719920338967241c4f815dada6f8ac Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jul 2024 08:29:27 -0400 Subject: [PATCH 33/36] don't use reflection to find the calling method for deprecate since the api changes with 3.4 --- lib/savon/options.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/savon/options.rb b/lib/savon/options.rb index f9551f05..974c12e6 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -9,8 +9,7 @@ def initialize(options = {}) assign options end - def deprecate - option = caller_locations[0].label + def deprecate(option) raise DeprecatedOptionError.new(option) end @@ -259,7 +258,7 @@ def ssl_verify_mode(verify_mode) # Sets the cert key file to use. def ssl_cert_key_file(file) - deprecate + deprecate('ssl_cert_key_file') @options[:ssl_cert_key_file] = file end @@ -270,13 +269,13 @@ def ssl_cert_key(key) # Sets the cert key password to use. def ssl_cert_key_password(password) - deprecate + deprecate('ssl_cert_key_password') @options[:ssl_cert_key_password] = password end # Sets the cert file to use. def ssl_cert_file(file) - deprecate + deprecate('ssl_cert_file') @options[:ssl_cert_file] = file end @@ -292,12 +291,12 @@ def ssl_ca_cert_file(file) # Sets the ca cert to use. def ssl_ca_cert(cert) - deprecate + deprecate('ssl_ca_cert') @options[:ssl_ca_cert] = cert end def ssl_ciphers(ciphers) - deprecate + deprecate('ssl_ciphers') @options[:ssl_ciphers] = ciphers end @@ -318,7 +317,7 @@ def basic_auth(*credentials) # HTTP digest auth credentials. def digest_auth(*credentials) - deprecate + deprecate('digest_auth') @options[:digest_auth] = credentials.flatten end From 892f1fb931c4a9cce038a5d9010cee851b1298a1 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jul 2024 08:35:57 -0400 Subject: [PATCH 34/36] returns the faraday response --- spec/savon/http_error_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/savon/http_error_spec.rb b/spec/savon/http_error_spec.rb index 0a0d0ad2..fdce475d 100644 --- a/spec/savon/http_error_spec.rb +++ b/spec/savon/http_error_spec.rb @@ -22,7 +22,7 @@ end describe "#http" do - it "returns the HTTPI::Response" do + it "returns the Faraday::Response" do expect(http_error.http).to be_a(Faraday::Response) end end From 959b618de0969c858f657f3051c80f1f3a013bcf Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jul 2024 08:40:40 -0400 Subject: [PATCH 35/36] less magic, re-enable open timeout test --- spec/savon/options_spec.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spec/savon/options_spec.rb b/spec/savon/options_spec.rb index 51484f15..730310e3 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -7,8 +7,7 @@ RSpec.describe "Options" do - shared_examples(:deprecation) do - option = self.superclass.description[/:\w+/][1..-1].to_sym + shared_examples(:deprecation) do |option| it "Raises a deprecation error" do expect { new_client(:endpoint => @server.url, option => :none) }.to( raise_error(Savon::DeprecatedOptionError) {|e| @@ -155,6 +154,7 @@ end # This feature is non-functional in 3.2 and 3.0 due to https://github.com/ruby/ruby/pull/9374 (works in 3.1... unknown why.) + # TODO 3.4 contains a fix for this when it is released. context "global :open_timeout" do let(:open_timeout) { 0.1 } it "makes the client timeout after n seconds" do @@ -483,12 +483,11 @@ def to_s end context "global :ssl_ciphers" do - it_behaves_like(:deprecation) + it_behaves_like(:deprecation, :ssl_ciphers) end context "global :ssl_cert_key_file" do - it_behaves_like(:deprecation) - + it_behaves_like(:deprecation, :ssl_cert_key_file) end context "global :ssl_cert_key" do @@ -503,11 +502,11 @@ def to_s context "global :ssl_cert_key_password" do - it_behaves_like(:deprecation) + it_behaves_like(:deprecation, :ssl_cert_key_password) end context "global :ssl_cert_file" do - it_behaves_like(:deprecation) + it_behaves_like(:deprecation, :ssl_cert_file) end context "global :ssl_cert" do @@ -551,7 +550,7 @@ def to_s end context "global :ssl_ca_cert" do - it_behaves_like(:deprecation) + it_behaves_like(:deprecation, :ssl_ca_cert) end From a70a1e6ba068cbe45c03533b54ee009519716147 Mon Sep 17 00:00:00 2001 From: lridge Date: Tue, 9 Jul 2024 08:40:52 -0400 Subject: [PATCH 36/36] less magic, re-enable open timeout test --- spec/savon/options_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/savon/options_spec.rb b/spec/savon/options_spec.rb index 730310e3..a6be4d18 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -153,12 +153,9 @@ end end - # This feature is non-functional in 3.2 and 3.0 due to https://github.com/ruby/ruby/pull/9374 (works in 3.1... unknown why.) - # TODO 3.4 contains a fix for this when it is released. context "global :open_timeout" do let(:open_timeout) { 0.1 } it "makes the client timeout after n seconds" do - skip 'https://github.com/ruby/ruby/pull/9374' non_routable_ip = "http://192.0.2.0" client = new_client(:endpoint => non_routable_ip, :open_timeout => open_timeout) start_time = Time.now