webmock 3.5.1 → 3.11.0

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