webmock 3.6.2 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +37 -0
  3. data/CHANGELOG.md +214 -0
  4. data/Gemfile +1 -1
  5. data/README.md +85 -32
  6. data/Rakefile +12 -2
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +216 -0
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +4 -0
  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 +4 -1
  12. data/lib/webmock/http_lib_adapters/http_rb/response.rb +24 -3
  13. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
  14. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +1 -1
  15. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
  16. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +24 -9
  17. data/lib/webmock/http_lib_adapters/net_http.rb +43 -19
  18. data/lib/webmock/request_pattern.rb +82 -47
  19. data/lib/webmock/response.rb +11 -5
  20. data/lib/webmock/rspec.rb +2 -1
  21. data/lib/webmock/stub_registry.rb +26 -11
  22. data/lib/webmock/test_unit.rb +1 -3
  23. data/lib/webmock/util/uri.rb +5 -4
  24. data/lib/webmock/version.rb +1 -1
  25. data/lib/webmock/webmock.rb +5 -3
  26. data/lib/webmock.rb +1 -0
  27. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  28. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  29. data/spec/acceptance/curb/curb_spec.rb +12 -5
  30. data/spec/acceptance/em_http_request/em_http_request_spec.rb +56 -0
  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 +51 -0
  35. data/spec/acceptance/net_http/net_http_spec.rb +38 -0
  36. data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
  37. data/spec/acceptance/shared/callbacks.rb +2 -1
  38. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  39. data/spec/acceptance/shared/stubbing_requests.rb +35 -0
  40. data/spec/unit/request_pattern_spec.rb +183 -48
  41. data/spec/unit/response_spec.rb +22 -18
  42. data/spec/unit/util/uri_spec.rb +10 -0
  43. data/spec/unit/webmock_spec.rb +52 -11
  44. data/test/test_webmock.rb +6 -0
  45. data/webmock.gemspec +11 -1
  46. metadata +48 -10
  47. data/.travis.yml +0 -19
@@ -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,27 +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).any? { |u| normalized_template.match(u) }
213
+ def template_matches_uri?(template, uri)
214
+ template.match(uri)
215
+ rescue Addressable::URI::InvalidURIError
216
+ false
187
217
  end
188
218
  end
189
219
 
190
220
  class URIStringPattern < URIPattern
191
- 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)
192
233
  if @pattern.is_a?(Addressable::URI)
193
234
  if @query_params
194
- uri.omit(:query) === @pattern &&
195
- (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
235
+ uri.omit(:query) === @pattern
196
236
  else
197
237
  uri === @pattern
198
238
  end
@@ -201,19 +241,8 @@ module WebMock
201
241
  end
202
242
  end
203
243
 
204
- def add_query_params(query_params)
205
- super
206
- if @query_params.is_a?(Hash) || @query_params.is_a?(String)
207
- query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
208
- @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
209
- @query_params = nil
210
- end
211
- end
212
-
213
- def to_s
214
- str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
215
- str += " with query params #{@query_params.inspect}" if @query_params
216
- str
244
+ def pattern_inspect
245
+ WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
217
246
  end
218
247
  end
219
248
 
@@ -266,8 +295,9 @@ module WebMock
266
295
  end
267
296
 
268
297
  private
298
+
269
299
  def body_as_hash(body, content_type)
270
- case BODY_FORMATS[content_type]
300
+ case body_format(content_type)
271
301
  when :json then
272
302
  WebMock::Util::JSON.parse(body)
273
303
  when :xml then
@@ -277,6 +307,11 @@ module WebMock
277
307
  end
278
308
  end
279
309
 
310
+ def body_format(content_type)
311
+ normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
312
+ BODY_FORMATS[normalized_content_type]
313
+ end
314
+
280
315
  def assert_non_multipart_body(content_type)
281
316
  if content_type =~ %r{^multipart/form-data}
282
317
  raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
@@ -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)
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
  }
@@ -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
@@ -86,20 +86,21 @@ module WebMock
86
86
 
87
87
  def self.uris_encoded_and_unencoded(uris)
88
88
  uris.map do |uri|
89
- [ 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
+ ]
90
93
  end.flatten
91
94
  end
92
95
 
93
96
  def self.uris_with_scheme_and_without(uris)
94
97
  uris.map { |uri|
95
- uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
96
98
  [ uri, uri.gsub(%r{^https?://},"").freeze ]
97
99
  }.flatten
98
100
  end
99
101
 
100
102
  def self.uris_with_trailing_slash_and_without(uris)
101
- uris = uris.map { |uri|
102
- uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
103
+ uris.map { |uri|
103
104
  [ uri, uri.omit(:path).freeze ]
104
105
  }.flatten
105
106
  end
@@ -1,3 +1,3 @@
1
1
  module WebMock
2
- VERSION = '3.6.2' unless defined?(::WebMock::VERSION)
2
+ VERSION = '3.14.0' unless defined?(::WebMock::VERSION)
3
3
  end
@@ -59,11 +59,13 @@ module WebMock
59
59
  end
60
60
 
61
61
  def self.net_connect_allowed?(uri = nil)
62
+ return !!Config.instance.allow_net_connect if uri.nil?
63
+
62
64
  if uri.is_a?(String)
63
65
  uri = WebMock::Util::URI.normalize_uri(uri)
64
66
  end
65
67
 
66
- Config.instance.allow_net_connect ||
68
+ !!Config.instance.allow_net_connect ||
67
69
  ( Config.instance.allow_localhost && WebMock::Util::URI.is_uri_localhost?(uri) ||
68
70
  Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) )
69
71
  end
@@ -138,8 +140,8 @@ module WebMock
138
140
  puts WebMock::RequestExecutionVerifier.executed_requests_message
139
141
  end
140
142
 
141
- def self.globally_stub_request(&block)
142
- WebMock::StubRegistry.instance.register_global_stub(&block)
143
+ def self.globally_stub_request(order = :before_local_stubs, &block)
144
+ WebMock::StubRegistry.instance.register_global_stub(order, &block)
143
145
  end
144
146
 
145
147
  %w(
data/lib/webmock.rb CHANGED
@@ -54,5 +54,6 @@ require_relative 'webmock/http_lib_adapters/em_http_request_adapter'
54
54
  require_relative 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
55
55
  require_relative 'webmock/http_lib_adapters/excon_adapter'
56
56
  require_relative 'webmock/http_lib_adapters/manticore_adapter'
57
+ require_relative 'webmock/http_lib_adapters/async_http_client_adapter'
57
58
 
58
59
  require_relative 'webmock/webmock'