webmock 3.0.1 → 3.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/CI.yml +38 -0
  3. data/CHANGELOG.md +496 -2
  4. data/Gemfile +1 -1
  5. data/README.md +169 -34
  6. data/Rakefile +12 -4
  7. data/lib/webmock/api.rb +12 -0
  8. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +221 -0
  9. data/lib/webmock/http_lib_adapters/curb_adapter.rb +19 -5
  10. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
  11. data/lib/webmock/http_lib_adapters/excon_adapter.rb +5 -2
  12. data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
  13. data/lib/webmock/http_lib_adapters/http_rb/request.rb +7 -1
  14. data/lib/webmock/http_lib_adapters/http_rb/response.rb +27 -3
  15. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +9 -3
  16. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +7 -3
  17. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +28 -9
  18. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +33 -15
  19. data/lib/webmock/http_lib_adapters/net_http.rb +36 -89
  20. data/lib/webmock/http_lib_adapters/net_http_response.rb +1 -1
  21. data/lib/webmock/http_lib_adapters/patron_adapter.rb +4 -4
  22. data/lib/webmock/matchers/any_arg_matcher.rb +13 -0
  23. data/lib/webmock/matchers/hash_argument_matcher.rb +21 -0
  24. data/lib/webmock/matchers/hash_excluding_matcher.rb +15 -0
  25. data/lib/webmock/matchers/hash_including_matcher.rb +4 -23
  26. data/lib/webmock/rack_response.rb +1 -1
  27. data/lib/webmock/request_body_diff.rb +1 -1
  28. data/lib/webmock/request_execution_verifier.rb +2 -3
  29. data/lib/webmock/request_pattern.rb +129 -51
  30. data/lib/webmock/request_registry.rb +1 -1
  31. data/lib/webmock/request_signature.rb +3 -3
  32. data/lib/webmock/request_signature_snippet.rb +4 -4
  33. data/lib/webmock/request_stub.rb +15 -0
  34. data/lib/webmock/response.rb +19 -13
  35. data/lib/webmock/rspec.rb +10 -3
  36. data/lib/webmock/stub_registry.rb +26 -11
  37. data/lib/webmock/stub_request_snippet.rb +10 -6
  38. data/lib/webmock/test_unit.rb +1 -3
  39. data/lib/webmock/util/hash_counter.rb +3 -3
  40. data/lib/webmock/util/headers.rb +17 -2
  41. data/lib/webmock/util/json.rb +1 -2
  42. data/lib/webmock/util/query_mapper.rb +9 -7
  43. data/lib/webmock/util/uri.rb +10 -10
  44. data/lib/webmock/util/values_stringifier.rb +20 -0
  45. data/lib/webmock/version.rb +1 -1
  46. data/lib/webmock/webmock.rb +20 -3
  47. data/lib/webmock.rb +53 -48
  48. data/minitest/webmock_spec.rb +3 -3
  49. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  50. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  51. data/spec/acceptance/curb/curb_spec.rb +44 -0
  52. data/spec/acceptance/em_http_request/em_http_request_spec.rb +57 -1
  53. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +2 -2
  54. data/spec/acceptance/excon/excon_spec.rb +4 -2
  55. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  56. data/spec/acceptance/http_rb/http_rb_spec.rb +20 -0
  57. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +5 -2
  58. data/spec/acceptance/httpclient/httpclient_spec.rb +8 -1
  59. data/spec/acceptance/manticore/manticore_spec.rb +51 -0
  60. data/spec/acceptance/net_http/net_http_shared.rb +47 -10
  61. data/spec/acceptance/net_http/net_http_spec.rb +102 -24
  62. data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
  63. data/spec/acceptance/patron/patron_spec.rb +26 -21
  64. data/spec/acceptance/patron/patron_spec_helper.rb +3 -3
  65. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +14 -14
  66. data/spec/acceptance/shared/callbacks.rb +3 -2
  67. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
  68. data/spec/acceptance/shared/request_expectations.rb +14 -0
  69. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  70. data/spec/acceptance/shared/stubbing_requests.rb +95 -0
  71. data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +1 -1
  72. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +1 -1
  73. data/spec/support/webmock_server.rb +1 -0
  74. data/spec/unit/api_spec.rb +103 -3
  75. data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
  76. data/spec/unit/request_execution_verifier_spec.rb +12 -12
  77. data/spec/unit/request_pattern_spec.rb +207 -49
  78. data/spec/unit/request_signature_snippet_spec.rb +2 -2
  79. data/spec/unit/request_signature_spec.rb +21 -1
  80. data/spec/unit/request_stub_spec.rb +35 -0
  81. data/spec/unit/response_spec.rb +51 -19
  82. data/spec/unit/stub_request_snippet_spec.rb +30 -10
  83. data/spec/unit/util/query_mapper_spec.rb +13 -0
  84. data/spec/unit/util/uri_spec.rb +74 -2
  85. data/spec/unit/webmock_spec.rb +108 -5
  86. data/test/shared_test.rb +15 -2
  87. data/test/test_webmock.rb +6 -0
  88. data/webmock.gemspec +15 -7
  89. metadata +86 -37
  90. data/.travis.yml +0 -20
@@ -99,6 +99,11 @@ if defined?(EventMachine::HttpClient)
99
99
  end
100
100
  end
101
101
 
102
+ def connection_completed
103
+ @state = :response_header
104
+ send_request(request_signature.headers, request_signature.body)
105
+ end
106
+
102
107
  def send_request(head, body)
103
108
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
104
109
 
@@ -107,7 +112,7 @@ if defined?(EventMachine::HttpClient)
107
112
  @uri ||= nil
108
113
  EM.next_tick {
109
114
  setup(make_raw_response(stubbed_webmock_response), @uri,
110
- stubbed_webmock_response.should_timeout ? "WebMock timeout error" : nil)
115
+ stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
111
116
  }
112
117
  self
113
118
  elsif WebMock.net_connect_allowed?(request_signature.uri)
@@ -164,7 +169,7 @@ if defined?(EventMachine::HttpClient)
164
169
  end
165
170
 
166
171
  def build_request_signature
167
- headers, body = @req.headers, @req.body
172
+ headers, body = build_request, @req.body
168
173
 
169
174
  @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
170
175
  headers, body = m.request(self, headers, body)
@@ -178,8 +183,6 @@ if defined?(EventMachine::HttpClient)
178
183
 
179
184
  body = form_encode_body(body) if body.is_a?(Hash)
180
185
 
181
- headers = @req.headers
182
-
183
186
  if headers['authorization'] && headers['authorization'].is_a?(Array)
184
187
  headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(headers.delete('authorization'))
185
188
  end
@@ -92,7 +92,7 @@ if defined?(Excon)
92
92
  end
93
93
 
94
94
  def self.to_query(hash)
95
- string = ""
95
+ string = "".dup
96
96
  for key, values in hash
97
97
  if values.nil?
98
98
  string << key.to_s << '&'
@@ -152,11 +152,14 @@ if defined?(Excon)
152
152
  end
153
153
 
154
154
  Excon::Connection.class_eval do
155
- def self.new(args)
155
+ def self.new(args = {})
156
156
  args.delete(:__construction_args)
157
157
  super(args).tap do |instance|
158
158
  instance.data[:__construction_args] = args
159
159
  end
160
160
  end
161
161
  end
162
+
163
+ # Suppresses Excon connection argument validation warning
164
+ Excon::VALID_CONNECTION_KEYS << :__construction_args
162
165
  end
@@ -4,7 +4,8 @@ module HTTP
4
4
 
5
5
  def perform(request, options)
6
6
  return __perform__(request, options) unless webmock_enabled?
7
- WebMockPerform.new(request) { __perform__(request, options) }.exec
7
+
8
+ WebMockPerform.new(request, options) { __perform__(request, options) }.exec
8
9
  end
9
10
 
10
11
  def webmock_enabled?
@@ -1,9 +1,15 @@
1
1
  module HTTP
2
2
  class Request
3
3
  def webmock_signature
4
+ request_body = if defined?(HTTP::Request::Body)
5
+ ''.tap { |string| body.each { |part| string << part } }
6
+ else
7
+ body
8
+ end
9
+
4
10
  ::WebMock::RequestSignature.new(verb, uri.to_s, {
5
11
  headers: headers.to_h,
6
- body: body
12
+ body: request_body
7
13
  })
8
14
  end
9
15
  end
@@ -11,20 +11,44 @@ module HTTP
11
11
  end
12
12
 
13
13
  class << self
14
- def from_webmock(webmock_response, request_signature = nil)
14
+ def from_webmock(request, webmock_response, request_signature = nil)
15
15
  status = Status.new(webmock_response.status.first)
16
16
  headers = webmock_response.headers || {}
17
- body = Body.new Streamer.new webmock_response.body
18
17
  uri = normalize_uri(request_signature && request_signature.uri)
19
18
 
19
+ # HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
20
+ # and 2.x use a positional argument, and 0.x don't support supplying
21
+ # the encoding.
22
+ body = if HTTP::VERSION < "1.0.0"
23
+ Body.new(Streamer.new(webmock_response.body))
24
+ elsif HTTP::VERSION < "3.0.0"
25
+ Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
26
+ else
27
+ Body.new(
28
+ Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding),
29
+ encoding: webmock_response.body.encoding
30
+ )
31
+ end
32
+
20
33
  return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
21
34
 
35
+ # 5.0.0 had a breaking change to require request instead of uri.
36
+ if HTTP::VERSION < '5.0.0'
37
+ return new({
38
+ status: status,
39
+ version: "1.1",
40
+ headers: headers,
41
+ body: body,
42
+ uri: uri
43
+ })
44
+ end
45
+
22
46
  new({
23
47
  status: status,
24
48
  version: "1.1",
25
49
  headers: headers,
26
50
  body: body,
27
- uri: uri
51
+ request: request,
28
52
  })
29
53
  end
30
54
 
@@ -1,11 +1,12 @@
1
1
  module HTTP
2
2
  class Response
3
3
  class Streamer
4
- def initialize(str)
4
+ def initialize(str, encoding: Encoding::BINARY)
5
5
  @io = StringIO.new str
6
+ @encoding = encoding
6
7
  end
7
8
 
8
- def readpartial(size = nil)
9
+ def readpartial(size = nil, outbuf = nil)
9
10
  unless size
10
11
  if defined?(HTTP::Client::BUFFER_SIZE)
11
12
  size = HTTP::Client::BUFFER_SIZE
@@ -14,7 +15,12 @@ module HTTP
14
15
  end
15
16
  end
16
17
 
17
- @io.read size
18
+ chunk = @io.read size, outbuf
19
+ chunk.force_encoding(@encoding) if chunk
20
+ end
21
+
22
+ def close
23
+ @io.close
18
24
  end
19
25
 
20
26
  def sequence_id
@@ -1,7 +1,8 @@
1
1
  module HTTP
2
2
  class WebMockPerform
3
- def initialize(request, &perform)
3
+ def initialize(request, options, &perform)
4
4
  @request = request
5
+ @options = options
5
6
  @perform = perform
6
7
  @request_signature = nil
7
8
  end
@@ -38,12 +39,15 @@ module HTTP
38
39
  webmock_response.raise_error_if_any
39
40
 
40
41
  invoke_callbacks(webmock_response, real_request: false)
41
- ::HTTP::Response.from_webmock webmock_response, request_signature
42
+ response = ::HTTP::Response.from_webmock @request, webmock_response, request_signature
43
+
44
+ @options.features.each { |_name, feature| response = feature.wrap_response(response) }
45
+ response
42
46
  end
43
47
 
44
48
  def raise_timeout_error
45
49
  raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0"
46
- raise HTTP::ConnectionError, "connection error: #{Errno::ETIMEDOUT.new}"
50
+ raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}"
47
51
  end
48
52
 
49
53
  def perform
@@ -43,6 +43,9 @@ if defined?(::HTTPClient)
43
43
  end
44
44
 
45
45
  module WebMockHTTPClients
46
+
47
+ REQUEST_RESPONSE_LOCK = Mutex.new
48
+
46
49
  def do_get_block(req, proxy, conn, &block)
47
50
  do_get(req, proxy, conn, false, &block)
48
51
  end
@@ -57,7 +60,7 @@ if defined?(::HTTPClient)
57
60
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
58
61
 
59
62
  if webmock_responses[request_signature]
60
- webmock_response = webmock_responses.delete(request_signature)
63
+ webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
61
64
  response = build_httpclient_response(webmock_response, stream, req.header, &block)
62
65
  @request_filter.each do |filter|
63
66
  filter.filter_response(req, response)
@@ -68,15 +71,17 @@ if defined?(::HTTPClient)
68
71
  res
69
72
  elsif WebMock.net_connect_allowed?(request_signature.uri)
70
73
  # in case there is a nil entry in the hash...
71
- webmock_responses.delete(request_signature)
74
+ synchronize_request_response { webmock_responses.delete(request_signature) }
72
75
 
73
76
  res = if stream
74
77
  do_get_stream_without_webmock(req, proxy, conn, &block)
75
78
  elsif block
76
79
  body = ''
77
80
  do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
78
- body += chunk
79
- block.call(http_res, chunk)
81
+ if chunk && chunk.bytesize > 0
82
+ body += chunk
83
+ block.call(http_res, chunk)
84
+ end
80
85
  end
81
86
  else
82
87
  do_get_block_without_webmock(req, proxy, conn)
@@ -98,7 +103,7 @@ if defined?(::HTTPClient)
98
103
  def do_request_async(method, uri, query, body, extheader)
99
104
  req = create_request(method, uri, query, body, extheader)
100
105
  request_signature = build_request_signature(req)
101
- webmock_request_signatures << request_signature
106
+ synchronize_request_response { webmock_request_signatures << request_signature }
102
107
 
103
108
  if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
104
109
  super
@@ -117,7 +122,7 @@ if defined?(::HTTPClient)
117
122
  raise HTTPClient::TimeoutError if webmock_response.should_timeout
118
123
  webmock_response.raise_error_if_any
119
124
 
120
- block.call(response, body) if block
125
+ block.call(response, body) if block && body && body.bytesize > 0
121
126
 
122
127
  response
123
128
  end
@@ -182,7 +187,9 @@ if defined?(::HTTPClient)
182
187
 
183
188
  def webmock_responses
184
189
  @webmock_responses ||= Hash.new do |hash, request_signature|
185
- hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
190
+ synchronize_request_response do
191
+ hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
192
+ end
186
193
  end
187
194
  end
188
195
 
@@ -191,8 +198,10 @@ if defined?(::HTTPClient)
191
198
  end
192
199
 
193
200
  def previous_signature_for(signature)
194
- return nil unless index = webmock_request_signatures.index(signature)
195
- webmock_request_signatures.delete_at(index)
201
+ synchronize_request_response do
202
+ return nil unless index = webmock_request_signatures.index(signature)
203
+ webmock_request_signatures.delete_at(index)
204
+ end
196
205
  end
197
206
 
198
207
  private
@@ -207,6 +216,16 @@ if defined?(::HTTPClient)
207
216
  hdrs
208
217
  end
209
218
  end
219
+
220
+ def synchronize_request_response
221
+ if REQUEST_RESPONSE_LOCK.owned?
222
+ yield
223
+ else
224
+ REQUEST_RESPONSE_LOCK.synchronize do
225
+ yield
226
+ end
227
+ end
228
+ end
210
229
  end
211
230
 
212
231
  class WebMockHTTPClient < HTTPClient
@@ -24,6 +24,12 @@ if defined?(Manticore)
24
24
  Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
25
25
  end
26
26
 
27
+ class StubbedTimeoutResponse < Manticore::StubbedResponse
28
+ def call
29
+ @handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)"))
30
+ end
31
+ end
32
+
27
33
  class WebMockManticoreClient < Manticore::Client
28
34
  def request(klass, url, options={}, &block)
29
35
  super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
@@ -50,19 +56,22 @@ if defined?(Manticore)
50
56
 
51
57
  if webmock_response = registered_response_for(request_signature)
52
58
  webmock_response.raise_error_if_any
53
- manticore_response = generate_manticore_response(webmock_response).call
54
- real_request = false
59
+ manticore_response = generate_manticore_response(webmock_response)
60
+ manticore_response.on_success do
61
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: false}, request_signature, webmock_response)
62
+ end
55
63
 
56
64
  elsif real_request_allowed?(request_signature.uri)
57
- manticore_response = Manticore::Response.new(self, request, context, &block).call
58
- webmock_response = generate_webmock_response(manticore_response)
59
- real_request = true
65
+ manticore_response = Manticore::Response.new(self, request, context, &block)
66
+ manticore_response.on_complete do |completed_response|
67
+ webmock_response = generate_webmock_response(completed_response)
68
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: true}, request_signature, webmock_response)
69
+ end
60
70
 
61
71
  else
62
72
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
63
73
  end
64
74
 
65
- WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: real_request}, request_signature, webmock_response)
66
75
  manticore_response
67
76
  end
68
77
 
@@ -103,21 +112,30 @@ if defined?(Manticore)
103
112
  end
104
113
 
105
114
  def generate_manticore_response(webmock_response)
106
- raise Manticore::ConnectTimeout if webmock_response.should_timeout
107
-
108
- Manticore::StubbedResponse.stub(
109
- code: webmock_response.status[0],
110
- body: webmock_response.body,
111
- headers: webmock_response.headers,
112
- cookies: {}
113
- )
115
+ if webmock_response.should_timeout
116
+ StubbedTimeoutResponse.new
117
+ else
118
+ Manticore::StubbedResponse.stub(
119
+ code: webmock_response.status[0],
120
+ body: webmock_response.body,
121
+ headers: webmock_response.headers,
122
+ cookies: {}
123
+ )
124
+ end
114
125
  end
115
126
 
116
127
  def generate_webmock_response(manticore_response)
117
128
  webmock_response = WebMock::Response.new
118
129
  webmock_response.status = [manticore_response.code, manticore_response.message]
119
- webmock_response.body = manticore_response.body
120
130
  webmock_response.headers = manticore_response.headers
131
+
132
+ # The attempt to read the body could fail if manticore is used in a streaming mode
133
+ webmock_response.body = begin
134
+ manticore_response.body
135
+ rescue ::Manticore::StreamClosedException
136
+ nil
137
+ end
138
+
121
139
  webmock_response
122
140
  end
123
141
  end
@@ -10,24 +10,19 @@ module WebMock
10
10
  adapter_for :net_http
11
11
 
12
12
  OriginalNetHTTP = Net::HTTP unless const_defined?(:OriginalNetHTTP)
13
- OriginalNetBufferedIO = Net::BufferedIO unless const_defined?(:OriginalNetBufferedIO)
14
13
 
15
14
  def self.enable!
16
- Net.send(:remove_const, :BufferedIO)
17
15
  Net.send(:remove_const, :HTTP)
18
16
  Net.send(:remove_const, :HTTPSession)
19
17
  Net.send(:const_set, :HTTP, @webMockNetHTTP)
20
18
  Net.send(:const_set, :HTTPSession, @webMockNetHTTP)
21
- Net.send(:const_set, :BufferedIO, Net::WebMockNetBufferedIO)
22
19
  end
23
20
 
24
21
  def self.disable!
25
- Net.send(:remove_const, :BufferedIO)
26
22
  Net.send(:remove_const, :HTTP)
27
23
  Net.send(:remove_const, :HTTPSession)
28
24
  Net.send(:const_set, :HTTP, OriginalNetHTTP)
29
25
  Net.send(:const_set, :HTTPSession, OriginalNetHTTP)
30
- Net.send(:const_set, :BufferedIO, OriginalNetBufferedIO)
31
26
 
32
27
  #copy all constants from @webMockNetHTTP to original Net::HTTP
33
28
  #in case any constants were added to @webMockNetHTTP instead of Net::HTTP
@@ -98,13 +93,8 @@ module WebMock
98
93
  after_request.call(response)
99
94
  }
100
95
  if started?
101
- if WebMock::Config.instance.net_http_connect_on_start
102
- super_with_after_request.call
103
- else
104
- start_with_connect_without_finish {
105
- super_with_after_request.call
106
- }
107
- end
96
+ ensure_actual_connection
97
+ super_with_after_request.call
108
98
  else
109
99
  start_with_connect {
110
100
  super_with_after_request.call
@@ -119,32 +109,33 @@ module WebMock
119
109
  raise IOError, 'HTTP session already opened' if @started
120
110
  if block_given?
121
111
  begin
112
+ @socket = Net::HTTP.socket_type.new
122
113
  @started = true
123
114
  return yield(self)
124
115
  ensure
125
116
  do_finish
126
117
  end
127
118
  end
119
+ @socket = Net::HTTP.socket_type.new
128
120
  @started = true
129
121
  self
130
122
  end
131
123
 
132
124
 
133
- def start_with_connect_without_finish # :yield: http
134
- if block_given?
135
- begin
136
- do_start
137
- return yield(self)
138
- end
125
+ def ensure_actual_connection
126
+ if @socket.is_a?(StubSocket)
127
+ @socket&.close
128
+ @socket = nil
129
+ do_start
139
130
  end
140
- do_start
141
- self
142
131
  end
143
132
 
144
133
  alias_method :start_with_connect, :start
145
134
 
146
135
  def start(&block)
147
- if WebMock::Config.instance.net_http_connect_on_start
136
+ uri = Addressable::URI.parse(WebMock::NetHTTPUtility.get_uri(self))
137
+
138
+ if WebMock.net_http_connect_on_start?(uri)
148
139
  super(&block)
149
140
  else
150
141
  start_without_connect(&block)
@@ -154,7 +145,7 @@ module WebMock
154
145
  def build_net_http_response(webmock_response, &block)
155
146
  response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
156
147
  body = webmock_response.body
157
- body = nil if body.to_s == ''
148
+ body = nil if webmock_response.status[0].to_s == '204'
158
149
 
159
150
  response.instance_variable_set(:@body, body)
160
151
  webmock_response.headers.to_a.each do |name, values|
@@ -169,7 +160,7 @@ module WebMock
169
160
  response.extend Net::WebMockHTTPResponse
170
161
 
171
162
  if webmock_response.should_timeout
172
- raise timeout_exception, "execution expired"
163
+ raise Net::OpenTimeout, "execution expired"
173
164
  end
174
165
 
175
166
  webmock_response.raise_error_if_any
@@ -179,16 +170,6 @@ module WebMock
179
170
  response
180
171
  end
181
172
 
182
- def timeout_exception
183
- if defined?(Net::OpenTimeout)
184
- # Ruby 2.x
185
- Net::OpenTimeout
186
- else
187
- # Fallback, if things change
188
- Timeout::Error
189
- end
190
- end
191
-
192
173
  def build_webmock_response(net_http_response)
193
174
  webmock_response = WebMock::Response.new
194
175
  webmock_response.status = [
@@ -222,66 +203,43 @@ module WebMock
222
203
  end
223
204
  end
224
205
 
225
- # patch for StringIO behavior in Ruby 2.2.3
226
- # https://github.com/bblimke/webmock/issues/558
227
- class PatchedStringIO < StringIO #:nodoc:
228
-
229
- alias_method :orig_read_nonblock, :read_nonblock
230
-
231
- def read_nonblock(size, *args)
232
- orig_read_nonblock(size)
233
- end
234
-
235
- end
236
-
237
206
  class StubSocket #:nodoc:
238
207
 
239
- attr_accessor :read_timeout, :continue_timeout
208
+ attr_accessor :read_timeout, :continue_timeout, :write_timeout
240
209
 
241
210
  def initialize(*args)
211
+ @closed = false
242
212
  end
243
213
 
244
214
  def closed?
245
- @closed ||= true
215
+ @closed
246
216
  end
247
217
 
248
218
  def close
219
+ @closed = true
220
+ nil
249
221
  end
250
222
 
251
223
  def readuntil(*args)
252
224
  end
253
225
 
254
- end
255
-
256
- module Net #:nodoc: all
257
-
258
- class WebMockNetBufferedIO < BufferedIO
259
- def initialize(io, read_timeout: 60, continue_timeout: nil, debug_output: nil)
260
- @read_timeout = read_timeout
261
- @rbuf = ''
262
- @debug_output = debug_output
263
-
264
- @io = case io
265
- when Socket, OpenSSL::SSL::SSLSocket, IO
266
- io
267
- when StringIO
268
- PatchedStringIO.new(io.string)
269
- when String
270
- PatchedStringIO.new(io)
271
- end
272
- raise "Unable to create local socket" unless @io
273
- end
226
+ def io
227
+ @io ||= StubIO.new
274
228
  end
275
229
 
230
+ class StubIO
231
+ def setsockopt(*args); end
232
+ def peer_cert; end
233
+ def peeraddr; ["AF_INET", 443, "127.0.0.1", "127.0.0.1"] end
234
+ def ssl_version; "TLSv1.3" end
235
+ def cipher; ["TLS_AES_128_GCM_SHA256", "TLSv1.3", 128, 128] end
236
+ end
276
237
  end
277
238
 
278
-
279
239
  module WebMock
280
240
  module NetHTTPUtility
281
241
 
282
242
  def self.request_signature_from_request(net_http, request, body = nil)
283
- protocol = net_http.use_ssl? ? "https" : "http"
284
-
285
243
  path = request.path
286
244
 
287
245
  if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
@@ -290,11 +248,10 @@ module WebMock
290
248
 
291
249
  path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
292
250
 
293
- uri = "#{protocol}://#{net_http.address}:#{net_http.port}#{path}"
251
+ uri = get_uri(net_http, path)
294
252
  method = request.method.downcase.to_sym
295
253
 
296
254
  headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
297
- validate_headers(headers)
298
255
 
299
256
  if request.body_stream
300
257
  body = request.body_stream.read
@@ -310,23 +267,13 @@ module WebMock
310
267
  WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
311
268
  end
312
269
 
313
- def self.validate_headers(headers)
314
- # For Ruby versions < 2.3.0, if you make a request with headers that are symbols
315
- # Net::HTTP raises a NoMethodError
316
- #
317
- # WebMock normalizes headers when creating a RequestSignature,
318
- # and will update all headers from symbols to strings.
319
- #
320
- # This could create a false positive in a test suite with WebMock.
321
- #
322
- # So before this point, WebMock raises an ArgumentError if any of the headers are symbols
323
- # instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
324
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
325
- header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
326
- if header_as_symbol
327
- raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
328
- end
329
- end
270
+ def self.get_uri(net_http, path = nil)
271
+ protocol = net_http.use_ssl? ? "https" : "http"
272
+
273
+ hostname = net_http.address
274
+ hostname = "[#{hostname}]" if /\A\[.*\]\z/ !~ hostname && /:/ =~ hostname
275
+
276
+ "#{protocol}://#{hostname}:#{net_http.port}#{path}"
330
277
  end
331
278
 
332
279
  def self.check_right_http_connection
@@ -23,7 +23,7 @@ module Net
23
23
  return nil if @body.nil?
24
24
 
25
25
  dest ||= ::Net::ReadAdapter.new(block)
26
- dest << @body
26
+ dest << @body.dup
27
27
  @body = dest
28
28
  ensure
29
29
  # allow subsequent calls to #read_body to proceed as normal, without our hack...
@@ -69,7 +69,7 @@ if defined?(::Patron)
69
69
  uri = WebMock::Util::URI.heuristic_parse(req.url)
70
70
  uri.path = uri.normalized_path.gsub("[^:]//","/")
71
71
 
72
- if [:put, :post].include?(req.action)
72
+ if [:put, :post, :patch].include?(req.action)
73
73
  if req.file_name
74
74
  if !File.exist?(req.file_name) || !File.readable?(req.file_name)
75
75
  raise ArgumentError.new("Unable to open specified file.")
@@ -106,11 +106,11 @@ if defined?(::Patron)
106
106
  header_data = ([status_line] + header_fields).join("\r\n")
107
107
 
108
108
  ::Patron::Response.new(
109
- "",
109
+ "".dup,
110
110
  webmock_response.status[0],
111
111
  0,
112
112
  header_data,
113
- webmock_response.body,
113
+ webmock_response.body.dup,
114
114
  default_response_charset
115
115
  )
116
116
  end
@@ -118,7 +118,7 @@ if defined?(::Patron)
118
118
  def self.build_webmock_response(patron_response)
119
119
  webmock_response = WebMock::Response.new
120
120
  reason = patron_response.status_line.
121
- scan(%r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
121
+ scan(%r(\AHTTP/(\d+(?:\.\d+)?)\s+(\d\d\d)\s*([^\r\n]+)?))[0][2]
122
122
  webmock_response.status = [patron_response.status, reason]
123
123
  webmock_response.body = patron_response.body
124
124
  webmock_response.headers = patron_response.headers
@@ -0,0 +1,13 @@
1
+ module WebMock
2
+ module Matchers
3
+ # this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher
4
+ class AnyArgMatcher
5
+ def initialize(ignore)
6
+ end
7
+
8
+ def ==(other)
9
+ true
10
+ end
11
+ end
12
+ end
13
+ end