webmock 3.5.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +38 -0
  3. data/CHANGELOG.md +343 -2
  4. data/Gemfile +1 -1
  5. data/README.md +116 -32
  6. data/Rakefile +12 -4
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +221 -0
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +12 -3
  9. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
  10. data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
  11. data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
  12. data/lib/webmock/http_lib_adapters/http_rb/response.rb +27 -3
  13. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +5 -3
  14. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +6 -2
  15. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
  16. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +33 -15
  17. data/lib/webmock/http_lib_adapters/net_http.rb +35 -103
  18. data/lib/webmock/http_lib_adapters/patron_adapter.rb +1 -1
  19. data/lib/webmock/request_body_diff.rb +1 -1
  20. data/lib/webmock/request_pattern.rb +106 -56
  21. data/lib/webmock/request_signature.rb +2 -2
  22. data/lib/webmock/request_stub.rb +15 -0
  23. data/lib/webmock/response.rb +19 -13
  24. data/lib/webmock/rspec.rb +2 -1
  25. data/lib/webmock/stub_registry.rb +26 -11
  26. data/lib/webmock/test_unit.rb +1 -3
  27. data/lib/webmock/util/query_mapper.rb +4 -2
  28. data/lib/webmock/util/uri.rb +8 -8
  29. data/lib/webmock/version.rb +1 -1
  30. data/lib/webmock/webmock.rb +20 -3
  31. data/lib/webmock.rb +1 -0
  32. data/minitest/webmock_spec.rb +1 -1
  33. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  34. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  35. data/spec/acceptance/curb/curb_spec.rb +34 -5
  36. data/spec/acceptance/em_http_request/em_http_request_spec.rb +57 -1
  37. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +2 -2
  38. data/spec/acceptance/excon/excon_spec.rb +2 -2
  39. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  40. data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
  41. data/spec/acceptance/manticore/manticore_spec.rb +51 -0
  42. data/spec/acceptance/net_http/net_http_shared.rb +46 -9
  43. data/spec/acceptance/net_http/net_http_spec.rb +87 -23
  44. data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
  45. data/spec/acceptance/patron/patron_spec.rb +19 -21
  46. data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
  47. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +14 -14
  48. data/spec/acceptance/shared/callbacks.rb +3 -2
  49. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
  50. data/spec/acceptance/shared/request_expectations.rb +7 -0
  51. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  52. data/spec/acceptance/shared/stubbing_requests.rb +40 -0
  53. data/spec/support/webmock_server.rb +1 -0
  54. data/spec/unit/request_pattern_spec.rb +201 -49
  55. data/spec/unit/request_signature_spec.rb +21 -1
  56. data/spec/unit/request_stub_spec.rb +35 -0
  57. data/spec/unit/response_spec.rb +51 -19
  58. data/spec/unit/util/query_mapper_spec.rb +7 -0
  59. data/spec/unit/util/uri_spec.rb +74 -2
  60. data/spec/unit/webmock_spec.rb +108 -5
  61. data/test/test_webmock.rb +6 -0
  62. data/webmock.gemspec +15 -7
  63. metadata +78 -35
  64. data/.travis.yml +0 -21
@@ -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)
@@ -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,81 +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, *args)
260
- io = case io
261
- when Socket, OpenSSL::SSL::SSLSocket, IO
262
- io
263
- when StringIO
264
- PatchedStringIO.new(io.string)
265
- when String
266
- PatchedStringIO.new(io)
267
- end
268
- raise "Unable to create local socket" unless io
269
-
270
- super
271
- end
272
-
273
- if RUBY_VERSION >= '2.6.0'
274
- def rbuf_fill
275
- current_thread_id = Thread.current.object_id
276
-
277
- trace = TracePoint.trace(:line) do |tp|
278
- next unless Thread.current.object_id == current_thread_id
279
- if tp.binding.local_variable_defined?(:tmp)
280
- tp.binding.local_variable_set(:tmp, nil)
281
- end
282
- end
283
-
284
- super
285
- ensure
286
- trace.disable
287
- end
288
- end
226
+ def io
227
+ @io ||= StubIO.new
289
228
  end
290
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
291
237
  end
292
238
 
293
-
294
239
  module WebMock
295
240
  module NetHTTPUtility
296
241
 
297
242
  def self.request_signature_from_request(net_http, request, body = nil)
298
- protocol = net_http.use_ssl? ? "https" : "http"
299
-
300
243
  path = request.path
301
244
 
302
245
  if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
@@ -305,11 +248,10 @@ module WebMock
305
248
 
306
249
  path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
307
250
 
308
- uri = "#{protocol}://#{net_http.address}:#{net_http.port}#{path}"
251
+ uri = get_uri(net_http, path)
309
252
  method = request.method.downcase.to_sym
310
253
 
311
254
  headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
312
- validate_headers(headers)
313
255
 
314
256
  if request.body_stream
315
257
  body = request.body_stream.read
@@ -325,23 +267,13 @@ module WebMock
325
267
  WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
326
268
  end
327
269
 
328
- def self.validate_headers(headers)
329
- # For Ruby versions < 2.3.0, if you make a request with headers that are symbols
330
- # Net::HTTP raises a NoMethodError
331
- #
332
- # WebMock normalizes headers when creating a RequestSignature,
333
- # and will update all headers from symbols to strings.
334
- #
335
- # This could create a false positive in a test suite with WebMock.
336
- #
337
- # So before this point, WebMock raises an ArgumentError if any of the headers are symbols
338
- # instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
339
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
340
- header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
341
- if header_as_symbol
342
- raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
343
- end
344
- 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}"
345
277
  end
346
278
 
347
279
  def self.check_right_http_connection
@@ -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
@@ -12,7 +12,7 @@ module WebMock
12
12
  def body_diff
13
13
  return {} unless request_signature_diffable? && request_stub_diffable?
14
14
 
15
- HashDiff.diff(request_signature_body_hash, request_stub_body_hash)
15
+ Hashdiff.diff(request_signature_body_hash, request_stub_body_hash)
16
16
  end
17
17
 
18
18
  attr_reader :request_signature, :request_stub
@@ -24,7 +24,7 @@ module WebMock
24
24
  end
25
25
 
26
26
  def with(options = {}, &block)
27
- raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified.') if options.empty? && !block_given?
27
+ raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified. Created a block with do..end? Try creating it with curly braces {} instead.') if options.empty? && !block_given?
28
28
  assign_options(options)
29
29
  @with_block = block
30
30
  self
@@ -80,6 +80,8 @@ module WebMock
80
80
  URIRegexpPattern.new(uri)
81
81
  elsif uri.is_a?(Addressable::Template)
82
82
  URIAddressablePattern.new(uri)
83
+ elsif uri.respond_to?(:call)
84
+ URICallablePattern.new(uri)
83
85
  else
84
86
  URIStringPattern.new(uri)
85
87
  end
@@ -107,11 +109,13 @@ module WebMock
107
109
  include RSpecMatcherDetector
108
110
 
109
111
  def initialize(pattern)
110
- @pattern = case pattern
111
- when Addressable::URI, Addressable::Template
112
+ @pattern = if pattern.is_a?(Addressable::URI) \
113
+ || pattern.is_a?(Addressable::Template)
114
+ pattern
115
+ elsif pattern.respond_to?(:call)
112
116
  pattern
113
117
  else
114
- WebMock::Util::URI.normalize_uri(pattern)
118
+ WebMock::Util::URI.normalize_uri(pattern)
115
119
  end
116
120
  @query_params = nil
117
121
  end
@@ -131,38 +135,44 @@ module WebMock
131
135
  end
132
136
  end
133
137
 
138
+ def matches?(uri)
139
+ pattern_matches?(uri) && query_params_matches?(uri)
140
+ end
141
+
134
142
  def to_s
135
- str = @pattern.inspect
143
+ str = pattern_inspect
136
144
  str += " with query params #{@query_params.inspect}" if @query_params
137
145
  str
138
146
  end
139
- end
140
147
 
141
- class URIRegexpPattern < URIPattern
142
- def matches?(uri)
143
- WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
144
- (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
148
+ private
149
+
150
+ def pattern_inspect
151
+ @pattern.inspect
145
152
  end
146
153
 
147
- def to_s
148
- str = @pattern.inspect
149
- str += " with query params #{@query_params.inspect}" if @query_params
150
- str
154
+ def query_params_matches?(uri)
155
+ @query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation)
151
156
  end
152
157
  end
153
158
 
154
- class URIAddressablePattern < URIPattern
155
- def matches?(uri)
156
- if @query_params.nil?
157
- # Let Addressable check the whole URI
158
- matches_with_variations?(uri)
159
- else
160
- # WebMock checks the query, Addressable checks everything else
161
- matches_with_variations?(uri.omit(:query)) &&
162
- @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)
163
- end
159
+ class URICallablePattern < URIPattern
160
+ private
161
+
162
+ def pattern_matches?(uri)
163
+ @pattern.call(uri)
164
+ end
165
+ end
166
+
167
+ class URIRegexpPattern < URIPattern
168
+ private
169
+
170
+ def pattern_matches?(uri)
171
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
164
172
  end
173
+ end
165
174
 
175
+ class URIAddressablePattern < URIPattern
166
176
  def add_query_params(query_params)
167
177
  @@add_query_params_warned ||= false
168
178
  if not @@add_query_params_warned
@@ -172,28 +182,57 @@ module WebMock
172
182
  super(query_params)
173
183
  end
174
184
 
175
- def to_s
176
- str = @pattern.pattern.inspect
177
- str += " with variables #{@pattern.variables.inspect}" if @pattern.variables
178
- str
185
+ private
186
+
187
+ def pattern_matches?(uri)
188
+ if @query_params.nil?
189
+ # Let Addressable check the whole URI
190
+ matches_with_variations?(uri)
191
+ else
192
+ # WebMock checks the query, Addressable checks everything else
193
+ matches_with_variations?(uri.omit(:query))
194
+ end
179
195
  end
180
196
 
181
- private
197
+ def pattern_inspect
198
+ @pattern.pattern.inspect
199
+ end
182
200
 
183
201
  def matches_with_variations?(uri)
184
- normalized_template = Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
202
+ template =
203
+ begin
204
+ Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
205
+ rescue Addressable::URI::InvalidURIError
206
+ Addressable::Template.new(@pattern.pattern)
207
+ end
208
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u|
209
+ template_matches_uri?(template, u)
210
+ }
211
+ end
185
212
 
186
- WebMock::Util::URI.variations_of_uri_as_strings(uri, only_with_scheme: true)
187
- .any? { |u| normalized_template.match(u) }
213
+ def template_matches_uri?(template, uri)
214
+ template.match(uri)
215
+ rescue Addressable::URI::InvalidURIError
216
+ false
188
217
  end
189
218
  end
190
219
 
191
220
  class URIStringPattern < URIPattern
192
- def matches?(uri)
221
+ def add_query_params(query_params)
222
+ super
223
+ if @query_params.is_a?(Hash) || @query_params.is_a?(String)
224
+ query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
225
+ @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
226
+ @query_params = nil
227
+ end
228
+ end
229
+
230
+ private
231
+
232
+ def pattern_matches?(uri)
193
233
  if @pattern.is_a?(Addressable::URI)
194
234
  if @query_params
195
- uri.omit(:query) === @pattern &&
196
- (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
235
+ uri.omit(:query) === @pattern
197
236
  else
198
237
  uri === @pattern
199
238
  end
@@ -202,19 +241,8 @@ module WebMock
202
241
  end
203
242
  end
204
243
 
205
- def add_query_params(query_params)
206
- super
207
- if @query_params.is_a?(Hash) || @query_params.is_a?(String)
208
- query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
209
- @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
210
- @query_params = nil
211
- end
212
- end
213
-
214
- def to_s
215
- str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
216
- str += " with query params #{@query_params.inspect}" if @query_params
217
- str
244
+ def pattern_inspect
245
+ WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
218
246
  end
219
247
  end
220
248
 
@@ -253,6 +281,8 @@ module WebMock
253
281
  if (@pattern).is_a?(Hash)
254
282
  return true if @pattern.empty?
255
283
  matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
284
+ elsif (@pattern).is_a?(Array)
285
+ matching_body_array?(body_as_hash(body, content_type), @pattern, content_type)
256
286
  elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
257
287
  @pattern == body_as_hash(body, content_type)
258
288
  else
@@ -267,8 +297,9 @@ module WebMock
267
297
  end
268
298
 
269
299
  private
300
+
270
301
  def body_as_hash(body, content_type)
271
- case BODY_FORMATS[content_type]
302
+ case body_format(content_type)
272
303
  when :json then
273
304
  WebMock::Util::JSON.parse(body)
274
305
  when :xml then
@@ -278,6 +309,11 @@ module WebMock
278
309
  end
279
310
  end
280
311
 
312
+ def body_format(content_type)
313
+ normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
314
+ BODY_FORMATS[normalized_content_type]
315
+ end
316
+
281
317
  def assert_non_multipart_body(content_type)
282
318
  if content_type =~ %r{^multipart/form-data}
283
319
  raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
@@ -310,19 +346,33 @@ module WebMock
310
346
  def matching_body_hashes?(query_parameters, pattern, content_type)
311
347
  return false unless query_parameters.is_a?(Hash)
312
348
  return false unless query_parameters.keys.sort == pattern.keys.sort
313
- query_parameters.each do |key, actual|
349
+
350
+ query_parameters.all? do |key, actual|
314
351
  expected = pattern[key]
352
+ matching_values(actual, expected, content_type)
353
+ end
354
+ end
315
355
 
316
- if actual.is_a?(Hash) && expected.is_a?(Hash)
317
- return false unless matching_body_hashes?(actual, expected, content_type)
318
- else
319
- expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
320
- return false unless expected === actual
321
- end
356
+ def matching_body_array?(query_parameters, pattern, content_type)
357
+ return false unless query_parameters.is_a?(Array)
358
+ return false unless query_parameters.length == pattern.length
359
+
360
+ query_parameters.each_with_index do |actual, index|
361
+ expected = pattern[index]
362
+ return false unless matching_values(actual, expected, content_type)
322
363
  end
364
+
323
365
  true
324
366
  end
325
367
 
368
+ def matching_values(actual, expected, content_type)
369
+ return matching_body_hashes?(actual, expected, content_type) if actual.is_a?(Hash) && expected.is_a?(Hash)
370
+ return matching_body_array?(actual, expected, content_type) if actual.is_a?(Array) && expected.is_a?(Array)
371
+
372
+ expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
373
+ expected === actual
374
+ end
375
+
326
376
  def empty_string?(string)
327
377
  string.nil? || string == ""
328
378
  end
@@ -35,11 +35,11 @@ module WebMock
35
35
  alias == eql?
36
36
 
37
37
  def url_encoded?
38
- !!(headers && headers['Content-Type'] == 'application/x-www-form-urlencoded')
38
+ !!(headers&.fetch('Content-Type', nil)&.start_with?('application/x-www-form-urlencoded'))
39
39
  end
40
40
 
41
41
  def json_headers?
42
- !!(headers && headers['Content-Type'] == 'application/json')
42
+ !!(headers&.fetch('Content-Type', nil)&.start_with?('application/json'))
43
43
  end
44
44
 
45
45
  private
@@ -24,6 +24,21 @@ module WebMock
24
24
  end
25
25
  alias_method :and_return, :to_return
26
26
 
27
+ def to_return_json(*response_hashes)
28
+ raise ArgumentError, '#to_return_json does not support passing a block' if block_given?
29
+
30
+ json_response_hashes = [*response_hashes].flatten.map do |resp_h|
31
+ headers, body = resp_h.values_at(:headers, :body)
32
+ resp_h.merge(
33
+ headers: {content_type: 'application/json'}.merge(headers.to_h),
34
+ body: body.is_a?(Hash) ? body.to_json : body
35
+ )
36
+ end
37
+
38
+ to_return(json_response_hashes)
39
+ end
40
+ alias_method :and_return_json, :to_return_json
41
+
27
42
  def to_rack(app, options={})
28
43
  @responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
29
44
  end
@@ -14,8 +14,11 @@ module WebMock
14
14
 
15
15
  class Response
16
16
  def initialize(options = {})
17
- if options.is_a?(IO) || options.is_a?(String)
17
+ case options
18
+ when IO, StringIO
18
19
  self.options = read_raw_response(options)
20
+ when String
21
+ self.options = read_raw_response(StringIO.new(options))
19
22
  else
20
23
  self.options = options
21
24
  end
@@ -91,10 +94,10 @@ module WebMock
91
94
 
92
95
  def ==(other)
93
96
  self.body == other.body &&
94
- self.headers === other.headers &&
95
- self.status == other.status &&
96
- self.exception == other.exception &&
97
- self.should_timeout == other.should_timeout
97
+ self.headers === other.headers &&
98
+ self.status == other.status &&
99
+ self.exception == other.exception &&
100
+ self.should_timeout == other.should_timeout
98
101
  end
99
102
 
100
103
  private
@@ -111,16 +114,17 @@ module WebMock
111
114
  valid_types = [Proc, IO, Pathname, String, Array]
112
115
  return if @body.nil?
113
116
  return if valid_types.any? { |c| @body.is_a?(c) }
114
- raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
115
- end
116
117
 
117
- def read_raw_response(raw_response)
118
- if raw_response.is_a?(IO)
119
- string = raw_response.read
120
- raw_response.close
121
- raw_response = string
118
+ if @body.class.is_a?(Hash)
119
+ raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}' instead." \
120
+ "\n What shall we encode it to? try calling .to_json .to_xml instead on the hash instead, or otherwise convert it to a string."
121
+ else
122
+ raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
122
123
  end
123
- socket = ::Net::BufferedIO.new(raw_response)
124
+ end
125
+
126
+ def read_raw_response(io)
127
+ socket = ::Net::BufferedIO.new(io)
124
128
  response = ::Net::HTTPResponse.read_new(socket)
125
129
  transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
126
130
  response.reading_body(socket, true) {}
@@ -132,6 +136,8 @@ module WebMock
132
136
  options[:body] = response.read_body
133
137
  options[:status] = [response.code.to_i, response.message]
134
138
  options
139
+ ensure
140
+ socket.close
135
141
  end
136
142
 
137
143
  InvalidBody = Class.new(StandardError)
data/lib/webmock/rspec.rb CHANGED
@@ -33,7 +33,8 @@ RSPEC_CONFIGURER.configure { |config|
33
33
  WebMock.disable!
34
34
  end
35
35
 
36
- config.after(:each) do
36
+ config.around(:each) do |example|
37
+ example.run
37
38
  WebMock.reset!
38
39
  end
39
40
  }