File: patron_adapter.rb

package info (click to toggle)
ruby-webmock 3.25.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,172 kB
  • sloc: ruby: 12,829; makefile: 6
file content (132 lines) | stat: -rw-r--r-- 4,782 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true

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

if defined?(::Patron::Session)
  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