bblimke/webmock

View on GitHub
lib/webmock/http_lib_adapters/patron_adapter.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

begin
  require 'patron'
rescue LoadError
  # patron not found
end

if defined?(::Patron)
  module WebMock
    module HttpLibAdapters
      class PatronAdapter < ::WebMock::HttpLibAdapter
        adapter_for :patron

        OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)

        class WebMockPatronSession < ::Patron::Session
          def handle_request(req)
            request_signature =
              WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)

            WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)

            if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
              WebMock::HttpLibAdapters::PatronAdapter.
                handle_file_name(req, webmock_response)
              res = WebMock::HttpLibAdapters::PatronAdapter.
                build_patron_response(webmock_response, default_response_charset)
              WebMock::CallbackRegistry.invoke_callbacks(
                {lib: :patron}, request_signature, webmock_response)
              res
            elsif WebMock.net_connect_allowed?(request_signature.uri)
              res = super
              if WebMock::CallbackRegistry.any_callbacks?
                webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
                  build_webmock_response(res)
                WebMock::CallbackRegistry.invoke_callbacks(
                  {lib: :patron, real_request: true}, request_signature,
                    webmock_response)
              end
              res
            else
              raise WebMock::NetConnectNotAllowedError.new(request_signature)
            end
          end
        end

        def self.enable!
          Patron.send(:remove_const, :Session)
          Patron.send(:const_set, :Session, WebMockPatronSession)
        end

        def self.disable!
          Patron.send(:remove_const, :Session)
          Patron.send(:const_set, :Session, OriginalPatronSession)
        end

        def self.handle_file_name(req, webmock_response)
          if req.action == :get && req.file_name
            begin
              File.open(req.file_name, "w") do |f|
                f.write webmock_response.body
              end
            rescue Errno::EACCES
              raise ArgumentError.new("Unable to open specified file.")
            end
          end
        end

        def self.build_request_signature(req)
          uri = WebMock::Util::URI.heuristic_parse(req.url)
          uri.path = uri.normalized_path.gsub("[^:]//","/")

          if [:put, :post, :patch].include?(req.action)
            if req.file_name
              if !File.exist?(req.file_name) || !File.readable?(req.file_name)
                raise ArgumentError.new("Unable to open specified file.")
              end
              request_body = File.read(req.file_name)
            elsif req.upload_data
              request_body = req.upload_data
            else
              raise ArgumentError.new("Must provide either data or a filename when doing a PUT or POST")
            end
          end

          headers = req.headers

          if req.credentials
            headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(req.credentials)
          end

          request_signature = WebMock::RequestSignature.new(
            req.action,
            uri.to_s,
            body: request_body,
            headers: headers
          )
          request_signature
        end

        def self.build_patron_response(webmock_response, default_response_charset)
          raise ::Patron::TimeoutError if webmock_response.should_timeout
          webmock_response.raise_error_if_any

          header_fields = (webmock_response.headers || []).map { |(k, vs)| Array(vs).map { |v| "#{k}: #{v}" } }.flatten
          status_line   = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}"
          header_data   = ([status_line] + header_fields).join("\r\n")

          ::Patron::Response.new(
            "".dup,
            webmock_response.status[0],
            0,
            header_data,
            webmock_response.body.dup,
            default_response_charset
          )
        end

        def self.build_webmock_response(patron_response)
          webmock_response = WebMock::Response.new
          reason = patron_response.status_line.
            scan(%r(\AHTTP/(\d+(?:\.\d+)?)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
          webmock_response.status = [patron_response.status, reason]
          webmock_response.body = patron_response.body
          webmock_response.headers = patron_response.headers
          webmock_response
        end
      end
    end
  end
end