webmock 3.0.1 → 3.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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