webmock 3.0.0 → 3.14.0

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 (84) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/CI.yml +37 -0
  3. data/CHANGELOG.md +416 -0
  4. data/Gemfile +1 -1
  5. data/README.md +157 -31
  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 +216 -0
  9. data/lib/webmock/http_lib_adapters/curb_adapter.rb +17 -3
  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 +4 -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 +24 -3
  15. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +6 -2
  16. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +2 -2
  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 +54 -14
  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 +108 -46
  30. data/lib/webmock/request_registry.rb +1 -1
  31. data/lib/webmock/request_signature.rb +1 -1
  32. data/lib/webmock/request_signature_snippet.rb +4 -4
  33. data/lib/webmock/response.rb +11 -5
  34. data/lib/webmock/rspec.rb +10 -3
  35. data/lib/webmock/stub_registry.rb +26 -11
  36. data/lib/webmock/stub_request_snippet.rb +10 -6
  37. data/lib/webmock/test_unit.rb +1 -3
  38. data/lib/webmock/util/hash_counter.rb +4 -4
  39. data/lib/webmock/util/headers.rb +17 -2
  40. data/lib/webmock/util/json.rb +1 -2
  41. data/lib/webmock/util/query_mapper.rb +9 -7
  42. data/lib/webmock/util/uri.rb +10 -10
  43. data/lib/webmock/util/values_stringifier.rb +20 -0
  44. data/lib/webmock/version.rb +1 -1
  45. data/lib/webmock/webmock.rb +10 -3
  46. data/lib/webmock.rb +53 -48
  47. data/minitest/webmock_spec.rb +2 -2
  48. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  49. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  50. data/spec/acceptance/curb/curb_spec.rb +33 -0
  51. data/spec/acceptance/em_http_request/em_http_request_spec.rb +56 -0
  52. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
  53. data/spec/acceptance/excon/excon_spec.rb +4 -2
  54. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  55. data/spec/acceptance/http_rb/http_rb_spec.rb +20 -0
  56. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +5 -2
  57. data/spec/acceptance/httpclient/httpclient_spec.rb +8 -1
  58. data/spec/acceptance/manticore/manticore_spec.rb +51 -0
  59. data/spec/acceptance/net_http/net_http_shared.rb +1 -1
  60. data/spec/acceptance/net_http/net_http_spec.rb +53 -1
  61. data/spec/acceptance/patron/patron_spec.rb +7 -0
  62. data/spec/acceptance/patron/patron_spec_helper.rb +3 -3
  63. data/spec/acceptance/shared/callbacks.rb +3 -2
  64. data/spec/acceptance/shared/request_expectations.rb +14 -0
  65. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  66. data/spec/acceptance/shared/stubbing_requests.rb +95 -0
  67. data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +1 -1
  68. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +1 -1
  69. data/spec/support/webmock_server.rb +1 -0
  70. data/spec/unit/api_spec.rb +103 -3
  71. data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
  72. data/spec/unit/request_execution_verifier_spec.rb +12 -12
  73. data/spec/unit/request_pattern_spec.rb +195 -49
  74. data/spec/unit/request_signature_snippet_spec.rb +2 -2
  75. data/spec/unit/response_spec.rb +22 -18
  76. data/spec/unit/stub_request_snippet_spec.rb +30 -10
  77. data/spec/unit/util/query_mapper_spec.rb +13 -0
  78. data/spec/unit/util/uri_spec.rb +74 -2
  79. data/spec/unit/webmock_spec.rb +54 -5
  80. data/test/shared_test.rb +15 -2
  81. data/test/test_webmock.rb +6 -0
  82. data/webmock.gemspec +11 -3
  83. metadata +66 -17
  84. data/.travis.yml +0 -20
@@ -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,41 @@ 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(Streamer.new(webmock_response.body), encoding: webmock_response.body.encoding)
28
+ end
29
+
20
30
  return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
21
31
 
32
+ # 5.0.0 had a breaking change to require request instead of uri.
33
+ if HTTP::VERSION < '5.0.0'
34
+ return new({
35
+ status: status,
36
+ version: "1.1",
37
+ headers: headers,
38
+ body: body,
39
+ uri: uri
40
+ })
41
+ end
42
+
22
43
  new({
23
44
  status: status,
24
45
  version: "1.1",
25
46
  headers: headers,
26
47
  body: body,
27
- uri: uri
48
+ request: request,
28
49
  })
29
50
  end
30
51
 
@@ -5,7 +5,7 @@ module HTTP
5
5
  @io = StringIO.new str
6
6
  end
7
7
 
8
- def readpartial(size = nil)
8
+ def readpartial(size = nil, outbuf = nil)
9
9
  unless size
10
10
  if defined?(HTTP::Client::BUFFER_SIZE)
11
11
  size = HTTP::Client::BUFFER_SIZE
@@ -14,7 +14,11 @@ module HTTP
14
14
  end
15
15
  end
16
16
 
17
- @io.read size
17
+ @io.read size, outbuf
18
+ end
19
+
20
+ def close
21
+ @io.close
18
22
  end
19
23
 
20
24
  def sequence_id
@@ -38,12 +38,12 @@ module HTTP
38
38
  webmock_response.raise_error_if_any
39
39
 
40
40
  invoke_callbacks(webmock_response, real_request: false)
41
- ::HTTP::Response.from_webmock webmock_response, request_signature
41
+ ::HTTP::Response.from_webmock @request, webmock_response, request_signature
42
42
  end
43
43
 
44
44
  def raise_timeout_error
45
45
  raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0"
46
- raise HTTP::ConnectionError, "connection error: #{Errno::ETIMEDOUT.new}"
46
+ raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}"
47
47
  end
48
48
 
49
49
  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
@@ -154,7 +154,7 @@ module WebMock
154
154
  def build_net_http_response(webmock_response, &block)
155
155
  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
156
  body = webmock_response.body
157
- body = nil if body.to_s == ''
157
+ body = nil if webmock_response.status[0].to_s == '204'
158
158
 
159
159
  response.instance_variable_set(:@body, body)
160
160
  webmock_response.headers.to_a.each do |name, values|
@@ -228,15 +228,16 @@ class PatchedStringIO < StringIO #:nodoc:
228
228
 
229
229
  alias_method :orig_read_nonblock, :read_nonblock
230
230
 
231
- def read_nonblock(size, *args)
232
- orig_read_nonblock(size)
231
+ def read_nonblock(size, *args, **kwargs)
232
+ args.reject! {|arg| !arg.is_a?(Hash)}
233
+ orig_read_nonblock(size, *args, **kwargs)
233
234
  end
234
235
 
235
236
  end
236
237
 
237
238
  class StubSocket #:nodoc:
238
239
 
239
- attr_accessor :read_timeout, :continue_timeout
240
+ attr_accessor :read_timeout, :continue_timeout, :write_timeout
240
241
 
241
242
  def initialize(*args)
242
243
  end
@@ -251,17 +252,20 @@ class StubSocket #:nodoc:
251
252
  def readuntil(*args)
252
253
  end
253
254
 
255
+ def io
256
+ @io ||= StubIO.new
257
+ end
258
+
259
+ class StubIO
260
+ def setsockopt(*args); end
261
+ end
254
262
  end
255
263
 
256
264
  module Net #:nodoc: all
257
265
 
258
266
  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
267
+ def initialize(io, *args, **kwargs)
268
+ io = case io
265
269
  when Socket, OpenSSL::SSL::SSLSocket, IO
266
270
  io
267
271
  when StringIO
@@ -269,7 +273,36 @@ module Net #:nodoc: all
269
273
  when String
270
274
  PatchedStringIO.new(io)
271
275
  end
272
- raise "Unable to create local socket" unless @io
276
+ raise "Unable to create local socket" unless io
277
+
278
+ # Prior to 2.4.0 `BufferedIO` only takes a single argument (`io`) with no
279
+ # options. Here we pass through our full set of arguments only if we're
280
+ # on 2.4.0 or later, and use a simplified invocation otherwise.
281
+ if RUBY_VERSION >= '2.4.0'
282
+ super
283
+ else
284
+ super(io)
285
+ end
286
+ end
287
+
288
+ if RUBY_VERSION >= '2.6.0'
289
+ # https://github.com/ruby/ruby/blob/7d02441f0d6e5c9d0a73a024519eba4f69e36dce/lib/net/protocol.rb#L208
290
+ # Modified version of method from ruby, so that nil is always passed into orig_read_nonblock to avoid timeout
291
+ def rbuf_fill
292
+ case rv = @io.read_nonblock(BUFSIZE, nil, exception: false)
293
+ when String
294
+ return if rv.nil?
295
+ @rbuf << rv
296
+ rv.clear
297
+ return
298
+ when :wait_readable
299
+ @io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout
300
+ when :wait_writable
301
+ @io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
302
+ when nil
303
+ raise EOFError, 'end of file reached'
304
+ end while true
305
+ end
273
306
  end
274
307
  end
275
308
 
@@ -280,8 +313,6 @@ module WebMock
280
313
  module NetHTTPUtility
281
314
 
282
315
  def self.request_signature_from_request(net_http, request, body = nil)
283
- protocol = net_http.use_ssl? ? "https" : "http"
284
-
285
316
  path = request.path
286
317
 
287
318
  if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
@@ -290,7 +321,7 @@ module WebMock
290
321
 
291
322
  path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
292
323
 
293
- uri = "#{protocol}://#{net_http.address}:#{net_http.port}#{path}"
324
+ uri = get_uri(net_http, path)
294
325
  method = request.method.downcase.to_sym
295
326
 
296
327
  headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
@@ -310,6 +341,15 @@ module WebMock
310
341
  WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
311
342
  end
312
343
 
344
+ def self.get_uri(net_http, path)
345
+ protocol = net_http.use_ssl? ? "https" : "http"
346
+
347
+ hostname = net_http.address
348
+ hostname = "[#{hostname}]" if /\A\[.*\]\z/ !~ hostname && /:/ =~ hostname
349
+
350
+ "#{protocol}://#{hostname}:#{net_http.port}#{path}"
351
+ end
352
+
313
353
  def self.validate_headers(headers)
314
354
  # For Ruby versions < 2.3.0, if you make a request with headers that are symbols
315
355
  # Net::HTTP raises a NoMethodError
@@ -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
@@ -0,0 +1,21 @@
1
+ module WebMock
2
+ module Matchers
3
+ # Base class for Hash matchers
4
+ # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
5
+ class HashArgumentMatcher
6
+ def initialize(expected)
7
+ @expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, deep: true).sort]
8
+ end
9
+
10
+ def ==(_actual, &block)
11
+ @expected.all?(&block)
12
+ rescue NoMethodError
13
+ false
14
+ end
15
+
16
+ def self.from_rspec_matcher(matcher)
17
+ new(matcher.instance_variable_get(:@expected))
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module WebMock
2
+ module Matchers
3
+ # this is a based on RSpec::Mocks::ArgumentMatchers::HashExcludingMatcher
4
+ # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
5
+ class HashExcludingMatcher < HashArgumentMatcher
6
+ def ==(actual)
7
+ super { |key, value| !actual.key?(key) || value != actual[key] }
8
+ end
9
+
10
+ def inspect
11
+ "hash_excluding(#{@expected.inspect})"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,14 +1,10 @@
1
1
  module WebMock
2
2
  module Matchers
3
- #this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
4
- #https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
5
- class HashIncludingMatcher
6
- def initialize(expected)
7
- @expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, deep: true).sort]
8
- end
9
-
3
+ # this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
4
+ # https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
5
+ class HashIncludingMatcher < HashArgumentMatcher
10
6
  def ==(actual)
11
- @expected.all? {|k,v| actual.has_key?(k) && v === actual[k]}
7
+ super { |key, value| actual.key?(key) && value === actual[key] }
12
8
  rescue NoMethodError
13
9
  false
14
10
  end
@@ -16,21 +12,6 @@ module WebMock
16
12
  def inspect
17
13
  "hash_including(#{@expected.inspect})"
18
14
  end
19
-
20
- def self.from_rspec_matcher(matcher)
21
- new(matcher.instance_variable_get(:@expected))
22
- end
23
- end
24
-
25
- #this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher
26
- class AnyArgMatcher
27
- def initialize(ignore)
28
- end
29
-
30
- def ==(other)
31
- true
32
- end
33
15
  end
34
-
35
16
  end
36
17
  end
@@ -17,7 +17,7 @@ module WebMock
17
17
  end
18
18
 
19
19
  def body_from_rack_response(response)
20
- body = ""
20
+ body = "".dup
21
21
  response.each { |line| body << line }
22
22
  response.close if response.respond_to?(:close)
23
23
  return body
@@ -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
@@ -53,9 +53,8 @@ module WebMock
53
53
 
54
54
  def failure_message_phrase(is_negated=false)
55
55
  negation = is_negated ? "was not" : "was"
56
- text = "The request #{request_pattern} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}"
57
- text << self.class.executed_requests_message
58
- text
56
+ "The request #{request_pattern} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}" +
57
+ self.class.executed_requests_message
59
58
  end
60
59
 
61
60
  def quantity_phrase(is_negated=false)