From 725855d6f384bae65bd5e3ce36cc5d1456e9212d Mon Sep 17 00:00:00 2001 From: Lucas Ridge <74679969+LukeIGS@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:48:28 -0400 Subject: [PATCH] Faraday (#998) * replace httpi with faraday, pull in rubyntlm since it'll be needed for ntlm auth handshakes --- CHANGELOG.md | 12 + lib/savon.rb | 8 + lib/savon/http_error.rb | 6 +- lib/savon/mock/expectation.rb | 8 +- lib/savon/operation.rb | 70 +++-- lib/savon/options.rb | 30 ++- lib/savon/request.rb | 128 +++++---- lib/savon/request_logger.rb | 13 +- lib/savon/soap_fault.rb | 2 +- savon.gemspec | 10 +- spec/integration/zipcode_example_spec.rb | 30 +-- spec/savon/client_spec.rb | 7 +- spec/savon/http_error_spec.rb | 6 +- spec/savon/mock_spec.rb | 2 +- spec/savon/observers_spec.rb | 6 +- spec/savon/operation_spec.rb | 35 ++- spec/savon/options_spec.rb | 175 ++++++------ spec/savon/request_spec.rb | 324 +++++------------------ spec/savon/response_spec.rb | 9 +- spec/savon/soap_fault_spec.rb | 14 +- spec/spec_helper.rb | 1 - spec/support/adapters.rb | 49 ---- spec/support/responses.rb | 8 + 23 files changed, 398 insertions(+), 555 deletions(-) delete mode 100644 spec/support/adapters.rb create mode 100644 spec/support/responses.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c0b9d7..bd4021fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Savon changelog ## Unreleased +* 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.1 (2024-07-08) 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/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/lib/savon/mock/expectation.rb b/lib/savon/mock/expectation.rb index f9623161..b821a09a 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.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body]) + Faraday::Response.new(env) end private @@ -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/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/options.rb b/lib/savon/options.rb index 71d78666..974c12e6 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,10 @@ def initialize(options = {}) assign options end + def deprecate(option) + raise DeprecatedOptionError.new(option) + end + attr_reader :option_type def [](option) @@ -127,7 +130,7 @@ def namespace(namespace) @options[:namespace] = namespace end - # The namespace identifer. + # The namespace identifier. def namespace_identifier(identifier) @options[:namespace_identifier] = identifier end @@ -198,13 +201,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 +258,7 @@ def ssl_verify_mode(verify_mode) # Sets the cert key file to use. def ssl_cert_key_file(file) + deprecate('ssl_cert_key_file') @options[:ssl_cert_key_file] = file end @@ -267,11 +269,13 @@ def ssl_cert_key(key) # Sets the cert key password to use. def ssl_cert_key_password(password) + deprecate('ssl_cert_key_password') @options[:ssl_cert_key_password] = password end # Sets the cert file to use. def ssl_cert_file(file) + deprecate('ssl_cert_file') @options[:ssl_cert_file] = file end @@ -287,10 +291,12 @@ def ssl_ca_cert_file(file) # Sets the ca cert to use. def ssl_ca_cert(cert) + deprecate('ssl_ca_cert') @options[:ssl_ca_cert] = cert end def ssl_ciphers(ciphers) + deprecate('ssl_ciphers') @options[:ssl_ciphers] = ciphers end @@ -311,6 +317,7 @@ def basic_auth(*credentials) # HTTP digest auth credentials. def digest_auth(*credentials) + deprecate('digest_auth') @options[:digest_auth] = credentials.flatten 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) @@ -397,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 @@ -457,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', "empty-cookie": "", HttpOnly: nil}) + # # => "accept=application/json; some-cookie=foo; empty-cookie=; HttpOnly" def cookies(cookies) @options[:cookies] = cookies end @@ -485,5 +497,9 @@ def multipart(multipart) def headers(headers) @options[:headers] = headers end + + def body(body) + @options[:body] = body + end end end diff --git a/lib/savon/request.rb b/lib/savon/request.rb index 48c916a0..5197019e 100644 --- a/lib/savon/request.rb +++ b/lib/savon/request.rb @@ -1,61 +1,87 @@ # 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 + 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 + 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 + 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 + 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 + basic_auth if @globals.include?(:basic_auth) + ntlm_auth if @globals.include?(:ntlm) + end + + def basic_auth + connection.request(:authorization, :basic, *@globals[:basic_auth]) + end + + def ntlm_auth + begin + require 'rubyntlm' + require 'faraday/net_http_persistent' + connection.adapter :net_http_persistent, pool_size: 5 + rescue LoadError + raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.' + end end def configure_redirect_handling - if @globals.include? :follow_redirects - @http_request.follow_redirect = @globals[:follow_redirects] + 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 @@ -63,18 +89,18 @@ class WSDLRequest < HTTPRequest def build configure_proxy configure_timeouts - configure_headers configure_ssl configure_auth - configure_redirect_handling - - @http_request + configure_adapter + configure_logging + configure_headers + connection end private def configure_headers - @http_request.headers = @globals[:headers] if @globals.include? :headers + connection.headers = @globals[:headers] if @globals.include? :headers end end @@ -88,26 +114,34 @@ class SOAPRequest < HTTPRequest def build(options = {}) configure_proxy configure_timeouts - configure_headers options[:soap_action], options[:headers] - configure_cookies options[:cookies] configure_ssl configure_auth + configure_headers(options[:soap_action], options[:headers]) + configure_cookies(options[:cookies]) + configure_adapter + configure_logging configure_redirect_handling - - @http_request + yield(connection) if block_given? + connection end private def configure_cookies(cookies) - @http_request.set_cookies(cookies) if cookies + connection.headers['Cookie'] = cookies.map do |key, value| + if value.nil? + key + 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] + 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/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) diff --git a/savon.gemspec b/savon.gemspec index 0b9c9217..0142bf15 100644 --- a/savon.gemspec +++ b/savon.gemspec @@ -17,16 +17,22 @@ Gem::Specification.new do |s| s.license = 'MIT' s.add_dependency "nori", "~> 2.4" - s.add_dependency "httpi", ">= 4", " < 5" - s.add_dependency "wasabi", ">= 3.7", " < 6" + 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" 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-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" + s.add_development_dependency "httpclient" + s.add_development_dependency "mutex_m" s.add_development_dependency "byebug" s.add_development_dependency "rake", ">= 12.3.3" 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 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 = {}) diff --git a/spec/savon/http_error_spec.rb b/spec/savon/http_error_spec.rb index fc107b81..fdce475d 100644 --- a/spec/savon/http_error_spec.rb +++ b/spec/savon/http_error_spec.rb @@ -22,8 +22,8 @@ end describe "#http" do - it "returns the HTTPI::Response" do - expect(http_error.http).to be_a(HTTPI::Response) + it "returns the Faraday::Response" do + expect(http_error.http).to be_a(Faraday::Response) end end @@ -51,7 +51,7 @@ 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] + Responses.mock_faraday(response[:code], response[:headers], response[:body]) end end 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 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 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/options_spec.rb b/spec/savon/options_spec.rb index d56e6946..a6be4d18 100644 --- a/spec/savon/options_spec.rb +++ b/spec/savon/options_spec.rb @@ -7,6 +7,15 @@ RSpec.describe "Options" do + 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| + expect(e.option).to eql(option.to_s) + }) + end + end + before :all do @server = IntegrationServer.run end @@ -86,20 +95,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 +120,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 @@ -136,10 +154,11 @@ end context "global :open_timeout" do + let(:open_timeout) { 0.1 } it "makes the client timeout after n seconds" do 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 +169,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 +182,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 +334,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 +349,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 +369,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) + 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) - client = new_client(:logger => custom_logger, :log => true) - - expect(HTTPI.logger).to be custom_logger + client = new_client(:endpoint => @server.url, :logger => custom_logger, :log => true) + client.call(:authenticate) + } end end @@ -421,7 +445,7 @@ def to_s context "global :ssl_version" do it "sets the SSL version to use" do - HTTPI::Auth::SSL.any_instance.expects(:ssl_version=).with(:TLSv1).twice + Faraday::SSLOptions.any_instance.expects(:version=).with(:TLSv1).twice client = new_client(:endpoint => @server.url, :ssl_version => :TLSv1) client.call(:authenticate) @@ -430,7 +454,7 @@ def to_s 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 +463,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 +472,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 +480,17 @@ 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, :ssl_ciphers) 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 - - client = new_client(:endpoint => @server.url, :ssl_cert_key_file => cert_key) - client.call(:authenticate) - end + it_behaves_like(:deprecation, :ssl_cert_key_file) 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 +499,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, :ssl_cert_key_password) 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, :ssl_cert_file) 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 +519,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 +529,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 +539,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 +547,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, :ssl_ca_cert) end @@ -575,7 +567,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 @@ -921,34 +913,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 @@ -1078,17 +1071,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" ) @@ -1142,4 +1135,6 @@ def inspect_request(response) OpenStruct.new(hash) end + + end diff --git a/spec/savon/request_spec.rb b/spec/savon/request_spec.rb index fcdc03f4..12b7f310 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,13 +78,13 @@ 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 @@ -91,13 +92,13 @@ def new_wsdl_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_wsdl_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ssl_version=).never + http_connection.ssl.expects(:version=).never new_wsdl_request.build end end @@ -105,13 +106,13 @@ def new_wsdl_request 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,122 +134,13 @@ 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) + http_connection.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) - - new_wsdl_request.build - end - - it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never + http_connection.ssl.expects(:verify_mode=).never new_wsdl_request.build end end @@ -256,41 +148,33 @@ def new_wsdl_request 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 - 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") - + describe "ntlm auth" do + it 'tries to load ntlm when set' do + globals.ntlm("han", "super-secret") new_wsdl_request.build + expect(require 'rubyntlm').to be(false) end - it "is not set otherwise" do - http_request.auth.expects(:digest).never - new_wsdl_request.build - end - end - - describe "ntlm auth" do - it "is set when specified" do + it "applies net-http-persistent when set" do globals.ntlm("han", "super-secret") - http_request.auth.expects(:ntlm).with("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 +185,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: nil + } + + 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 +235,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 +249,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 +326,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,87 +340,13 @@ 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) - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.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_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) + http_connection.ssl.expects(:verify_mode=).with(:peer) new_soap_request.build end it "is not set otherwise" do - http_request.auth.ssl.expects(:ca_cert_file=).never + http_connection.ssl.expects(:verify_mode=).never new_soap_request.build end end @@ -538,41 +354,25 @@ def new_soap_request 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 - new_soap_request.build - end - end - - 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") - - new_soap_request.build - end - - it "is not set otherwise" do - http_request.auth.expects(:digest).never + http_connection.expects(:request).with(:authorization, :basic, "luke", '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/response_spec.rb b/spec/savon/response_spec.rb index 5cb874dd..9bab16af 100644 --- a/spec/savon/response_spec.rb +++ b/spec/savon/response_spec.rb @@ -244,16 +244,15 @@ 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]) - + http_response = Responses.mock_faraday(response[:code], response[:headers], response[:body]) Savon::Response.new(http_response, globals, locals) end @@ -268,7 +267,7 @@ 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]) + http_response = Responses.mock_faraday(response[:code], response[:headers], response[:body]) Savon::Response.new(http_response, globals, locals) 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 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 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 diff --git a/spec/support/responses.rb b/spec/support/responses.rb new file mode 100644 index 00000000..1d4b005e --- /dev/null +++ b/spec/support/responses.rb @@ -0,0 +1,8 @@ +class Responses + class << self + def mock_faraday(code, headers, body) + env = Faraday::Env.from(status: code, response_headers: headers, response_body: body) + Faraday::Response.new(env) + end + end +end \ No newline at end of file