webmock 3.0.0 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
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)