webmock 3.5.1 → 3.11.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -12
  3. data/CHANGELOG.md +211 -0
  4. data/README.md +92 -26
  5. data/Rakefile +0 -2
  6. data/lib/webmock.rb +1 -0
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +214 -0
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +10 -1
  9. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +1 -1
  10. data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
  11. data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
  12. data/lib/webmock/http_lib_adapters/http_rb/response.rb +11 -1
  13. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
  14. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
  15. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +25 -14
  16. data/lib/webmock/http_lib_adapters/net_http.rb +35 -17
  17. data/lib/webmock/http_lib_adapters/patron_adapter.rb +1 -1
  18. data/lib/webmock/request_body_diff.rb +1 -1
  19. data/lib/webmock/request_pattern.rb +76 -48
  20. data/lib/webmock/response.rb +11 -5
  21. data/lib/webmock/rspec.rb +2 -1
  22. data/lib/webmock/stub_registry.rb +26 -11
  23. data/lib/webmock/test_unit.rb +1 -3
  24. data/lib/webmock/util/query_mapper.rb +4 -2
  25. data/lib/webmock/util/uri.rb +8 -8
  26. data/lib/webmock/version.rb +1 -1
  27. data/lib/webmock/webmock.rb +10 -3
  28. data/spec/acceptance/async_http_client/async_http_client_spec.rb +353 -0
  29. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  30. data/spec/acceptance/curb/curb_spec.rb +23 -5
  31. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
  32. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  33. data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
  34. data/spec/acceptance/manticore/manticore_spec.rb +19 -0
  35. data/spec/acceptance/net_http/net_http_spec.rb +12 -0
  36. data/spec/acceptance/shared/callbacks.rb +2 -1
  37. data/spec/acceptance/shared/request_expectations.rb +7 -0
  38. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  39. data/spec/acceptance/shared/stubbing_requests.rb +40 -0
  40. data/spec/support/webmock_server.rb +1 -0
  41. data/spec/unit/request_pattern_spec.rb +119 -3
  42. data/spec/unit/response_spec.rb +22 -18
  43. data/spec/unit/util/query_mapper_spec.rb +7 -0
  44. data/spec/unit/util/uri_spec.rb +74 -2
  45. data/spec/unit/webmock_spec.rb +54 -5
  46. data/test/test_webmock.rb +6 -0
  47. data/webmock.gemspec +9 -2
  48. metadata +39 -10
@@ -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,12 +252,19 @@ 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, *args)
267
+ def initialize(io, *args, **kwargs)
260
268
  io = case io
261
269
  when Socket, OpenSSL::SSL::SSLSocket, IO
262
270
  io
@@ -267,23 +275,33 @@ module Net #:nodoc: all
267
275
  end
268
276
  raise "Unable to create local socket" unless io
269
277
 
270
- super
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
271
286
  end
272
287
 
273
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
274
291
  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
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
287
305
  end
288
306
  end
289
307
  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
@@ -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
164
  end
165
+ end
166
+
167
+ class URIRegexpPattern < URIPattern
168
+ private
165
169
 
170
+ def pattern_matches?(uri)
171
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
172
+ end
173
+ end
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
 
@@ -91,10 +91,10 @@ module WebMock
91
91
 
92
92
  def ==(other)
93
93
  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
94
+ self.headers === other.headers &&
95
+ self.status == other.status &&
96
+ self.exception == other.exception &&
97
+ self.should_timeout == other.should_timeout
98
98
  end
99
99
 
100
100
  private
@@ -111,7 +111,13 @@ module WebMock
111
111
  valid_types = [Proc, IO, Pathname, String, Array]
112
112
  return if @body.nil?
113
113
  return if valid_types.any? { |c| @body.is_a?(c) }
114
- raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
114
+
115
+ if @body.class.is_a?(Hash)
116
+ raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}' instead." \
117
+ "\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."
118
+ else
119
+ raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
120
+ end
115
121
  end
116
122
 
117
123
  def read_raw_response(raw_response)
@@ -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
  }
@@ -10,25 +10,39 @@ module WebMock
10
10
  end
11
11
 
12
12
  def global_stubs
13
- @global_stubs ||= []
13
+ @global_stubs ||= Hash.new { |h, k| h[k] = [] }
14
14
  end
15
15
 
16
16
  def reset!
17
17
  self.request_stubs = []
18
18
  end
19
19
 
20
- def register_global_stub(&block)
20
+ def register_global_stub(order = :before_local_stubs, &block)
21
+ unless %i[before_local_stubs after_local_stubs].include?(order)
22
+ raise ArgumentError.new("Wrong order. Use :before_local_stubs or :after_local_stubs")
23
+ end
24
+
21
25
  # This hash contains the responses returned by the block,
22
26
  # keyed by the exact request (using the object_id).
23
27
  # That way, there's no race condition in case #to_return
24
28
  # doesn't run immediately after stub.with.
25
29
  responses = {}
26
-
27
- stub = ::WebMock::RequestStub.new(:any, /.*/).with { |request|
28
- responses[request.object_id] = yield(request)
29
- }.to_return(lambda { |request| responses.delete(request.object_id) })
30
-
31
- global_stubs.push stub
30
+ response_lock = Mutex.new
31
+
32
+ stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request|
33
+ update_response = -> { responses[request.object_id] = yield(request) }
34
+
35
+ # The block can recurse, so only lock if we don't already own it
36
+ if response_lock.owned?
37
+ update_response.call
38
+ else
39
+ response_lock.synchronize(&update_response)
40
+ end
41
+ }.to_return(lambda { |request|
42
+ response_lock.synchronize { responses.delete(request.object_id) }
43
+ })
44
+
45
+ global_stubs[order].push stub
32
46
  end
33
47
 
34
48
  def register_request_stub(stub)
@@ -54,9 +68,10 @@ module WebMock
54
68
  private
55
69
 
56
70
  def request_stub_for(request_signature)
57
- (global_stubs + request_stubs).detect { |registered_request_stub|
58
- registered_request_stub.request_pattern.matches?(request_signature)
59
- }
71
+ (global_stubs[:before_local_stubs] + request_stubs + global_stubs[:after_local_stubs])
72
+ .detect { |registered_request_stub|
73
+ registered_request_stub.request_pattern.matches?(request_signature)
74
+ }
60
75
  end
61
76
 
62
77
  def evaluate_response_for_request(response, request_signature)
@@ -8,12 +8,10 @@ module Test
8
8
  class TestCase
9
9
  include WebMock::API
10
10
 
11
- alias_method :teardown_without_webmock, :teardown
11
+ teardown
12
12
  def teardown_with_webmock
13
- teardown_without_webmock
14
13
  WebMock.reset!
15
14
  end
16
- alias_method :teardown, :teardown_with_webmock
17
15
 
18
16
  end
19
17
  end
@@ -161,7 +161,7 @@ module WebMock::Util
161
161
  else
162
162
  if array_value
163
163
  current_node[last_key] ||= []
164
- current_node[last_key] << value
164
+ current_node[last_key] << value unless value.nil?
165
165
  else
166
166
  current_node[last_key] = value
167
167
  end
@@ -186,7 +186,9 @@ module WebMock::Util
186
186
  new_query_values = new_query_values.to_hash
187
187
  new_query_values = new_query_values.inject([]) do |object, (key, value)|
188
188
  key = key.to_s if key.is_a?(::Symbol) || key.nil?
189
- if value.is_a?(Array)
189
+ if value.is_a?(Array) && value.empty?
190
+ object << [key.to_s + '[]']
191
+ elsif value.is_a?(Array)
190
192
  value.each { |v| object << [key.to_s + '[]', v] }
191
193
  elsif value.is_a?(Hash)
192
194
  value.each { |k, v| object << ["#{key.to_s}[#{k}]", v]}
@@ -41,12 +41,12 @@ module WebMock
41
41
  uris = uris_with_trailing_slash_and_without(uris)
42
42
  end
43
43
 
44
- uris = uris_encoded_and_unencoded(uris)
45
-
46
44
  if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
47
45
  uris = uris_with_inferred_port_and_without(uris)
48
46
  end
49
47
 
48
+ uris = uris_encoded_and_unencoded(uris)
49
+
50
50
  if normalized_uri.scheme == "http" && !only_with_scheme
51
51
  uris = uris_with_scheme_and_without(uris)
52
52
  end
@@ -80,27 +80,27 @@ module WebMock
80
80
 
81
81
  def self.uris_with_inferred_port_and_without(uris)
82
82
  uris.map { |uri|
83
- uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
84
- [ uri, uri.gsub(%r{(:80)|(:443)}, "").freeze ]
83
+ [ uri, uri.omit(:port)]
85
84
  }.flatten
86
85
  end
87
86
 
88
87
  def self.uris_encoded_and_unencoded(uris)
89
88
  uris.map do |uri|
90
- [ uri.to_s, Addressable::URI.unencode(uri, String).freeze ]
89
+ [
90
+ uri.to_s.force_encoding(Encoding::ASCII_8BIT),
91
+ Addressable::URI.unencode(uri, String).force_encoding(Encoding::ASCII_8BIT).freeze
92
+ ]
91
93
  end.flatten
92
94
  end
93
95
 
94
96
  def self.uris_with_scheme_and_without(uris)
95
97
  uris.map { |uri|
96
- uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
97
98
  [ uri, uri.gsub(%r{^https?://},"").freeze ]
98
99
  }.flatten
99
100
  end
100
101
 
101
102
  def self.uris_with_trailing_slash_and_without(uris)
102
- uris = uris.map { |uri|
103
- uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
103
+ uris.map { |uri|
104
104
  [ uri, uri.omit(:path).freeze ]
105
105
  }.flatten
106
106
  end