webmock 3.0.1 → 3.20.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 (138) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +558 -2
  3. data/README.md +200 -42
  4. data/lib/webmock/api.rb +14 -0
  5. data/lib/webmock/assertion_failure.rb +2 -0
  6. data/lib/webmock/callback_registry.rb +2 -0
  7. data/lib/webmock/config.rb +2 -0
  8. data/lib/webmock/cucumber.rb +2 -0
  9. data/lib/webmock/deprecation.rb +2 -0
  10. data/lib/webmock/errors.rb +2 -0
  11. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +223 -0
  12. data/lib/webmock/http_lib_adapters/curb_adapter.rb +21 -5
  13. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +20 -9
  14. data/lib/webmock/http_lib_adapters/excon_adapter.rb +7 -2
  15. data/lib/webmock/http_lib_adapters/http_lib_adapter.rb +2 -0
  16. data/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +2 -0
  17. data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
  18. data/lib/webmock/http_lib_adapters/http_rb/request.rb +19 -1
  19. data/lib/webmock/http_lib_adapters/http_rb/response.rb +29 -3
  20. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +11 -3
  21. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +9 -3
  22. data/lib/webmock/http_lib_adapters/http_rb_adapter.rb +2 -0
  23. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +30 -9
  24. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +35 -15
  25. data/lib/webmock/http_lib_adapters/net_http.rb +38 -89
  26. data/lib/webmock/http_lib_adapters/net_http_response.rb +3 -1
  27. data/lib/webmock/http_lib_adapters/patron_adapter.rb +6 -4
  28. data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +18 -2
  29. data/lib/webmock/matchers/any_arg_matcher.rb +15 -0
  30. data/lib/webmock/matchers/hash_argument_matcher.rb +23 -0
  31. data/lib/webmock/matchers/hash_excluding_matcher.rb +17 -0
  32. data/lib/webmock/matchers/hash_including_matcher.rb +6 -23
  33. data/lib/webmock/minitest.rb +2 -0
  34. data/lib/webmock/rack_response.rb +3 -1
  35. data/lib/webmock/request_body_diff.rb +3 -1
  36. data/lib/webmock/request_execution_verifier.rb +4 -3
  37. data/lib/webmock/request_pattern.rb +132 -52
  38. data/lib/webmock/request_registry.rb +3 -1
  39. data/lib/webmock/request_signature.rb +5 -3
  40. data/lib/webmock/request_signature_snippet.rb +6 -4
  41. data/lib/webmock/request_stub.rb +34 -0
  42. data/lib/webmock/response.rb +22 -14
  43. data/lib/webmock/responses_sequence.rb +2 -0
  44. data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +2 -0
  45. data/lib/webmock/rspec/matchers/webmock_matcher.rb +2 -0
  46. data/lib/webmock/rspec/matchers.rb +2 -0
  47. data/lib/webmock/rspec.rb +12 -3
  48. data/lib/webmock/stub_registry.rb +28 -11
  49. data/lib/webmock/stub_request_snippet.rb +12 -6
  50. data/lib/webmock/test_unit.rb +3 -3
  51. data/lib/webmock/util/hash_counter.rb +15 -9
  52. data/lib/webmock/util/hash_keys_stringifier.rb +2 -0
  53. data/lib/webmock/util/hash_validator.rb +2 -0
  54. data/lib/webmock/util/headers.rb +39 -11
  55. data/lib/webmock/util/json.rb +3 -2
  56. data/lib/webmock/util/query_mapper.rb +11 -7
  57. data/lib/webmock/util/uri.rb +13 -11
  58. data/lib/webmock/util/values_stringifier.rb +22 -0
  59. data/lib/webmock/util/version_checker.rb +7 -5
  60. data/lib/webmock/version.rb +3 -1
  61. data/lib/webmock/webmock.rb +22 -3
  62. data/lib/webmock.rb +55 -48
  63. metadata +106 -175
  64. data/.gemtest +0 -0
  65. data/.gitignore +0 -34
  66. data/.rspec-tm +0 -2
  67. data/.travis.yml +0 -20
  68. data/Gemfile +0 -9
  69. data/Rakefile +0 -30
  70. data/minitest/test_helper.rb +0 -34
  71. data/minitest/test_webmock.rb +0 -9
  72. data/minitest/webmock_spec.rb +0 -60
  73. data/spec/acceptance/curb/curb_spec.rb +0 -466
  74. data/spec/acceptance/curb/curb_spec_helper.rb +0 -147
  75. data/spec/acceptance/em_http_request/em_http_request_spec.rb +0 -406
  76. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +0 -77
  77. data/spec/acceptance/excon/excon_spec.rb +0 -75
  78. data/spec/acceptance/excon/excon_spec_helper.rb +0 -50
  79. data/spec/acceptance/http_rb/http_rb_spec.rb +0 -73
  80. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +0 -51
  81. data/spec/acceptance/httpclient/httpclient_spec.rb +0 -210
  82. data/spec/acceptance/httpclient/httpclient_spec_helper.rb +0 -57
  83. data/spec/acceptance/manticore/manticore_spec.rb +0 -56
  84. data/spec/acceptance/manticore/manticore_spec_helper.rb +0 -35
  85. data/spec/acceptance/net_http/net_http_shared.rb +0 -153
  86. data/spec/acceptance/net_http/net_http_spec.rb +0 -317
  87. data/spec/acceptance/net_http/net_http_spec_helper.rb +0 -64
  88. data/spec/acceptance/net_http/real_net_http_spec.rb +0 -20
  89. data/spec/acceptance/patron/patron_spec.rb +0 -118
  90. data/spec/acceptance/patron/patron_spec_helper.rb +0 -54
  91. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +0 -313
  92. data/spec/acceptance/shared/callbacks.rb +0 -147
  93. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +0 -36
  94. data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +0 -95
  95. data/spec/acceptance/shared/precedence_of_stubs.rb +0 -15
  96. data/spec/acceptance/shared/request_expectations.rb +0 -916
  97. data/spec/acceptance/shared/returning_declared_responses.rb +0 -388
  98. data/spec/acceptance/shared/stubbing_requests.rb +0 -583
  99. data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +0 -135
  100. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +0 -60
  101. data/spec/acceptance/webmock_shared.rb +0 -41
  102. data/spec/fixtures/test.txt +0 -1
  103. data/spec/quality_spec.rb +0 -84
  104. data/spec/spec_helper.rb +0 -48
  105. data/spec/support/example_curl_output.txt +0 -22
  106. data/spec/support/failures.rb +0 -9
  107. data/spec/support/my_rack_app.rb +0 -53
  108. data/spec/support/network_connection.rb +0 -19
  109. data/spec/support/webmock_server.rb +0 -69
  110. data/spec/unit/api_spec.rb +0 -75
  111. data/spec/unit/errors_spec.rb +0 -129
  112. data/spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb +0 -17
  113. data/spec/unit/http_lib_adapters/http_lib_adapter_spec.rb +0 -12
  114. data/spec/unit/matchers/hash_including_matcher_spec.rb +0 -87
  115. data/spec/unit/rack_response_spec.rb +0 -112
  116. data/spec/unit/request_body_diff_spec.rb +0 -90
  117. data/spec/unit/request_execution_verifier_spec.rb +0 -208
  118. data/spec/unit/request_pattern_spec.rb +0 -590
  119. data/spec/unit/request_registry_spec.rb +0 -95
  120. data/spec/unit/request_signature_snippet_spec.rb +0 -89
  121. data/spec/unit/request_signature_spec.rb +0 -155
  122. data/spec/unit/request_stub_spec.rb +0 -199
  123. data/spec/unit/response_spec.rb +0 -282
  124. data/spec/unit/stub_registry_spec.rb +0 -103
  125. data/spec/unit/stub_request_snippet_spec.rb +0 -95
  126. data/spec/unit/util/hash_counter_spec.rb +0 -39
  127. data/spec/unit/util/hash_keys_stringifier_spec.rb +0 -27
  128. data/spec/unit/util/headers_spec.rb +0 -28
  129. data/spec/unit/util/json_spec.rb +0 -33
  130. data/spec/unit/util/query_mapper_spec.rb +0 -144
  131. data/spec/unit/util/uri_spec.rb +0 -299
  132. data/spec/unit/util/version_checker_spec.rb +0 -65
  133. data/spec/unit/webmock_spec.rb +0 -11
  134. data/test/http_request.rb +0 -24
  135. data/test/shared_test.rb +0 -95
  136. data/test/test_helper.rb +0 -23
  137. data/test/test_webmock.rb +0 -6
  138. data/webmock.gemspec +0 -46
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
 
3
5
  module RSpecMatcherDetector
4
6
  def rSpecHashIncludingMatcher?(matcher)
5
7
  matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
6
8
  end
9
+
10
+ def rSpecHashExcludingMatcher?(matcher)
11
+ matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashExcludingMatcher/
12
+ end
7
13
  end
8
14
 
9
15
  class RequestPattern
@@ -20,7 +26,7 @@ module WebMock
20
26
  end
21
27
 
22
28
  def with(options = {}, &block)
23
- raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified.') if options.empty? && !block_given?
29
+ 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?
24
30
  assign_options(options)
25
31
  @with_block = block
26
32
  self
@@ -37,7 +43,7 @@ module WebMock
37
43
  end
38
44
 
39
45
  def to_s
40
- string = "#{@method_pattern.to_s.upcase}"
46
+ string = "#{@method_pattern.to_s.upcase}".dup
41
47
  string << " #{@uri_pattern.to_s}"
42
48
  string << " with body #{@body_pattern.to_s}" if @body_pattern
43
49
  string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
@@ -76,6 +82,8 @@ module WebMock
76
82
  URIRegexpPattern.new(uri)
77
83
  elsif uri.is_a?(Addressable::Template)
78
84
  URIAddressablePattern.new(uri)
85
+ elsif uri.respond_to?(:call)
86
+ URICallablePattern.new(uri)
79
87
  else
80
88
  URIStringPattern.new(uri)
81
89
  end
@@ -103,11 +111,13 @@ module WebMock
103
111
  include RSpecMatcherDetector
104
112
 
105
113
  def initialize(pattern)
106
- @pattern = case pattern
107
- when Addressable::URI, Addressable::Template
114
+ @pattern = if pattern.is_a?(Addressable::URI) \
115
+ || pattern.is_a?(Addressable::Template)
116
+ pattern
117
+ elsif pattern.respond_to?(:call)
108
118
  pattern
109
119
  else
110
- WebMock::Util::URI.normalize_uri(pattern)
120
+ WebMock::Util::URI.normalize_uri(pattern)
111
121
  end
112
122
  @query_params = nil
113
123
  end
@@ -115,65 +125,116 @@ module WebMock
115
125
  def add_query_params(query_params)
116
126
  @query_params = if query_params.is_a?(Hash)
117
127
  query_params
118
- elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher)
128
+ elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher) \
129
+ || query_params.is_a?(WebMock::Matchers::HashExcludingMatcher)
119
130
  query_params
120
131
  elsif rSpecHashIncludingMatcher?(query_params)
121
132
  WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
133
+ elsif rSpecHashExcludingMatcher?(query_params)
134
+ WebMock::Matchers::HashExcludingMatcher.from_rspec_matcher(query_params)
122
135
  else
123
136
  WebMock::Util::QueryMapper.query_to_values(query_params, notation: Config.instance.query_values_notation)
124
137
  end
125
138
  end
126
139
 
140
+ def matches?(uri)
141
+ pattern_matches?(uri) && query_params_matches?(uri)
142
+ end
143
+
127
144
  def to_s
128
- str = @pattern.inspect
145
+ str = pattern_inspect
129
146
  str += " with query params #{@query_params.inspect}" if @query_params
130
147
  str
131
148
  end
149
+
150
+ private
151
+
152
+ def pattern_inspect
153
+ @pattern.inspect
154
+ end
155
+
156
+ def query_params_matches?(uri)
157
+ @query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation)
158
+ end
132
159
  end
133
160
 
134
- class URIRegexpPattern < URIPattern
135
- def matches?(uri)
136
- WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
137
- (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
161
+ class URICallablePattern < URIPattern
162
+ private
163
+
164
+ def pattern_matches?(uri)
165
+ @pattern.call(uri)
138
166
  end
167
+ end
139
168
 
140
- def to_s
141
- str = @pattern.inspect
142
- str += " with query params #{@query_params.inspect}" if @query_params
143
- str
169
+ class URIRegexpPattern < URIPattern
170
+ private
171
+
172
+ def pattern_matches?(uri)
173
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
144
174
  end
145
175
  end
146
176
 
147
177
  class URIAddressablePattern < URIPattern
148
- def matches?(uri)
178
+ def add_query_params(query_params)
179
+ @@add_query_params_warned ||= false
180
+ if not @@add_query_params_warned
181
+ @@add_query_params_warned = true
182
+ warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
183
+ end
184
+ super(query_params)
185
+ end
186
+
187
+ private
188
+
189
+ def pattern_matches?(uri)
149
190
  if @query_params.nil?
150
191
  # Let Addressable check the whole URI
151
- WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| @pattern.match(u) }
192
+ matches_with_variations?(uri)
152
193
  else
153
194
  # WebMock checks the query, Addressable checks everything else
154
- WebMock::Util::URI.variations_of_uri_as_strings(uri.omit(:query)).any? { |u| @pattern.match(u) } &&
155
- @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)
195
+ matches_with_variations?(uri.omit(:query))
156
196
  end
157
197
  end
158
198
 
159
- def add_query_params(query_params)
160
- warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
161
- super(query_params)
199
+ def pattern_inspect
200
+ @pattern.pattern.inspect
162
201
  end
163
202
 
164
- def to_s
165
- str = @pattern.pattern.inspect
166
- str += " with variables #{@pattern.variables.inspect}" if @pattern.variables
167
- str
203
+ def matches_with_variations?(uri)
204
+ template =
205
+ begin
206
+ Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
207
+ rescue Addressable::URI::InvalidURIError
208
+ Addressable::Template.new(@pattern.pattern)
209
+ end
210
+ WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u|
211
+ template_matches_uri?(template, u)
212
+ }
213
+ end
214
+
215
+ def template_matches_uri?(template, uri)
216
+ template.match(uri)
217
+ rescue Addressable::URI::InvalidURIError
218
+ false
168
219
  end
169
220
  end
170
221
 
171
222
  class URIStringPattern < URIPattern
172
- def matches?(uri)
223
+ def add_query_params(query_params)
224
+ super
225
+ if @query_params.is_a?(Hash) || @query_params.is_a?(String)
226
+ query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
227
+ @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
228
+ @query_params = nil
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def pattern_matches?(uri)
173
235
  if @pattern.is_a?(Addressable::URI)
174
236
  if @query_params
175
- uri.omit(:query) === @pattern &&
176
- (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
237
+ uri.omit(:query) === @pattern
177
238
  else
178
239
  uri === @pattern
179
240
  end
@@ -182,19 +243,8 @@ module WebMock
182
243
  end
183
244
  end
184
245
 
185
- def add_query_params(query_params)
186
- super
187
- if @query_params.is_a?(Hash) || @query_params.is_a?(String)
188
- query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
189
- @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
190
- @query_params = nil
191
- end
192
- end
193
-
194
- def to_s
195
- str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
196
- str += " with query params #{@query_params.inspect}" if @query_params
197
- str
246
+ def pattern_inspect
247
+ WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
198
248
  end
199
249
  end
200
250
 
@@ -232,8 +282,10 @@ module WebMock
232
282
 
233
283
  if (@pattern).is_a?(Hash)
234
284
  return true if @pattern.empty?
235
- matching_hashes?(body_as_hash(body, content_type), @pattern)
236
- elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
285
+ matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
286
+ elsif (@pattern).is_a?(Array)
287
+ matching_body_array?(body_as_hash(body, content_type), @pattern, content_type)
288
+ elsif (@pattern).is_a?(WebMock::Matchers::HashArgumentMatcher)
237
289
  @pattern == body_as_hash(body, content_type)
238
290
  else
239
291
  empty_string?(@pattern) && empty_string?(body) ||
@@ -247,8 +299,9 @@ module WebMock
247
299
  end
248
300
 
249
301
  private
302
+
250
303
  def body_as_hash(body, content_type)
251
- case BODY_FORMATS[content_type]
304
+ case body_format(content_type)
252
305
  when :json then
253
306
  WebMock::Util::JSON.parse(body)
254
307
  when :xml then
@@ -258,6 +311,11 @@ module WebMock
258
311
  end
259
312
  end
260
313
 
314
+ def body_format(content_type)
315
+ normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
316
+ BODY_FORMATS[normalized_content_type]
317
+ end
318
+
261
319
  def assert_non_multipart_body(content_type)
262
320
  if content_type =~ %r{^multipart/form-data}
263
321
  raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
@@ -287,21 +345,36 @@ module WebMock
287
345
  #
288
346
  # @return [Boolean] true if the paramaters match the comparison
289
347
  # hash, false if not.
290
- def matching_hashes?(query_parameters, pattern)
348
+ def matching_body_hashes?(query_parameters, pattern, content_type)
291
349
  return false unless query_parameters.is_a?(Hash)
292
350
  return false unless query_parameters.keys.sort == pattern.keys.sort
293
- query_parameters.each do |key, actual|
351
+
352
+ query_parameters.all? do |key, actual|
294
353
  expected = pattern[key]
354
+ matching_values(actual, expected, content_type)
355
+ end
356
+ end
295
357
 
296
- if actual.is_a?(Hash) && expected.is_a?(Hash)
297
- return false unless matching_hashes?(actual, expected)
298
- else
299
- return false unless expected === actual
300
- end
358
+ def matching_body_array?(query_parameters, pattern, content_type)
359
+ return false unless query_parameters.is_a?(Array)
360
+ return false unless query_parameters.length == pattern.length
361
+
362
+ query_parameters.each_with_index do |actual, index|
363
+ expected = pattern[index]
364
+ return false unless matching_values(actual, expected, content_type)
301
365
  end
366
+
302
367
  true
303
368
  end
304
369
 
370
+ def matching_values(actual, expected, content_type)
371
+ return matching_body_hashes?(actual, expected, content_type) if actual.is_a?(Hash) && expected.is_a?(Hash)
372
+ return matching_body_array?(actual, expected, content_type) if actual.is_a?(Array) && expected.is_a?(Array)
373
+
374
+ expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
375
+ expected === actual
376
+ end
377
+
305
378
  def empty_string?(string)
306
379
  string.nil? || string == ""
307
380
  end
@@ -310,6 +383,9 @@ module WebMock
310
383
  Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true).sort]
311
384
  end
312
385
 
386
+ def url_encoded_body?(content_type)
387
+ content_type =~ %r{^application/x-www-form-urlencoded}
388
+ end
313
389
  end
314
390
 
315
391
  class HeadersPattern
@@ -333,6 +409,10 @@ module WebMock
333
409
  WebMock::Util::Headers.sorted_headers_string(@pattern)
334
410
  end
335
411
 
412
+ def pp_to_s
413
+ WebMock::Util::Headers.pp_headers_string(@pattern)
414
+ end
415
+
336
416
  private
337
417
 
338
418
  def empty_headers?(headers)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
 
3
5
  class RequestRegistry
@@ -23,7 +25,7 @@ module WebMock
23
25
  if requested_signatures.hash.empty?
24
26
  "No requests were made."
25
27
  else
26
- text = ""
28
+ text = "".dup
27
29
  self.requested_signatures.each do |request_signature, times_executed|
28
30
  text << "#{request_signature} was made #{times_executed} time#{times_executed == 1 ? '' : 's' }\n"
29
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
 
3
5
  class RequestSignature
@@ -12,7 +14,7 @@ module WebMock
12
14
  end
13
15
 
14
16
  def to_s
15
- string = "#{self.method.to_s.upcase}"
17
+ string = "#{self.method.to_s.upcase}".dup
16
18
  string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
17
19
  string << " with body '#{body.to_s}'" if body && body.to_s != ''
18
20
  if headers && !headers.empty?
@@ -35,11 +37,11 @@ module WebMock
35
37
  alias == eql?
36
38
 
37
39
  def url_encoded?
38
- !!(headers && headers['Content-Type'] == 'application/x-www-form-urlencoded')
40
+ !!(headers&.fetch('Content-Type', nil)&.start_with?('application/x-www-form-urlencoded'))
39
41
  end
40
42
 
41
43
  def json_headers?
42
- !!(headers && headers['Content-Type'] == 'application/json')
44
+ !!(headers&.fetch('Content-Type', nil)&.start_with?('application/json'))
43
45
  end
44
46
 
45
47
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pp"
2
4
 
3
5
  module WebMock
@@ -13,14 +15,14 @@ module WebMock
13
15
  def stubbing_instructions
14
16
  return unless WebMock.show_stubbing_instructions?
15
17
 
16
- text = "You can stub this request with the following snippet:\n\n"
17
- text << WebMock::StubRequestSnippet.new(request_stub).to_s
18
+ "You can stub this request with the following snippet:\n\n" +
19
+ WebMock::StubRequestSnippet.new(request_stub).to_s
18
20
  end
19
21
 
20
22
  def request_stubs
21
23
  return if WebMock::StubRegistry.instance.request_stubs.empty?
22
24
 
23
- text = "registered request stubs:\n"
25
+ text = "registered request stubs:\n".dup
24
26
  WebMock::StubRegistry.instance.request_stubs.each do |stub|
25
27
  text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}"
26
28
  add_body_diff(stub, text) if WebMock.show_body_diff?
@@ -50,7 +52,7 @@ module WebMock
50
52
  end
51
53
 
52
54
  def pretty_print_to_string(string_to_print)
53
- StringIO.open("") do |stream|
55
+ StringIO.open("".dup) do |stream|
54
56
  PP.pp(string_to_print, stream)
55
57
  stream.rewind
56
58
  stream.read
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
  class RequestStub
3
5
 
@@ -24,6 +26,38 @@ module WebMock
24
26
  end
25
27
  alias_method :and_return, :to_return
26
28
 
29
+ def to_return_json(*response_hashes)
30
+ raise ArgumentError, '#to_return_json does not support passing a block' if block_given?
31
+
32
+ json_response_hashes = [*response_hashes].flatten.map do |resp_h|
33
+ headers, body = resp_h.values_at(:headers, :body)
34
+
35
+ json_body = if body.respond_to?(:call)
36
+ ->(request_signature) {
37
+ b = if body.respond_to?(:arity) && body.arity == 1
38
+ body.call(request_signature)
39
+ else
40
+ body.call
41
+ end
42
+ b = b.to_json unless b.is_a?(String)
43
+ b
44
+ }
45
+ elsif !body.is_a?(String)
46
+ body.to_json
47
+ else
48
+ body
49
+ end
50
+
51
+ resp_h.merge(
52
+ headers: {content_type: 'application/json'}.merge(headers.to_h),
53
+ body: json_body
54
+ )
55
+ end
56
+
57
+ to_return(json_response_hashes)
58
+ end
59
+ alias_method :and_return_json, :to_return_json
60
+
27
61
  def to_rack(app, options={})
28
62
  @responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
29
63
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pathname"
2
4
 
3
5
  module WebMock
@@ -14,8 +16,11 @@ module WebMock
14
16
 
15
17
  class Response
16
18
  def initialize(options = {})
17
- if options.is_a?(IO) || options.is_a?(String)
19
+ case options
20
+ when IO, StringIO
18
21
  self.options = read_raw_response(options)
22
+ when String
23
+ self.options = read_raw_response(StringIO.new(options))
19
24
  else
20
25
  self.options = options
21
26
  end
@@ -33,7 +38,7 @@ module WebMock
33
38
  end
34
39
 
35
40
  def body
36
- @body || ''
41
+ @body || String.new("")
37
42
  end
38
43
 
39
44
  def body=(body)
@@ -91,10 +96,10 @@ module WebMock
91
96
 
92
97
  def ==(other)
93
98
  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
99
+ self.headers === other.headers &&
100
+ self.status == other.status &&
101
+ self.exception == other.exception &&
102
+ self.should_timeout == other.should_timeout
98
103
  end
99
104
 
100
105
  private
@@ -111,16 +116,17 @@ module WebMock
111
116
  valid_types = [Proc, IO, Pathname, String, Array]
112
117
  return if @body.nil?
113
118
  return if valid_types.any? { |c| @body.is_a?(c) }
114
- raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
115
- end
116
119
 
117
- def read_raw_response(raw_response)
118
- if raw_response.is_a?(IO)
119
- string = raw_response.read
120
- raw_response.close
121
- raw_response = string
120
+ if @body.class.is_a?(Hash)
121
+ raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}' instead." \
122
+ "\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."
123
+ else
124
+ raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
122
125
  end
123
- socket = ::Net::BufferedIO.new(raw_response)
126
+ end
127
+
128
+ def read_raw_response(io)
129
+ socket = ::Net::BufferedIO.new(io)
124
130
  response = ::Net::HTTPResponse.read_new(socket)
125
131
  transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
126
132
  response.reading_body(socket, true) {}
@@ -132,6 +138,8 @@ module WebMock
132
138
  options[:body] = response.read_body
133
139
  options[:status] = [response.code.to_i, response.message]
134
140
  options
141
+ ensure
142
+ socket.close
135
143
  end
136
144
 
137
145
  InvalidBody = Class.new(StandardError)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
 
3
5
  class ResponsesSequence
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
  class RequestPatternMatcher
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
  class WebMockMatcher
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'webmock'
2
4
  require 'webmock/rspec/matchers/request_pattern_matcher'
3
5
  require 'webmock/rspec/matchers/webmock_matcher'
data/lib/webmock/rspec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'webmock'
2
4
 
3
5
  # RSpec 1.x and 2.x compatibility
@@ -20,14 +22,21 @@ end
20
22
 
21
23
  require 'webmock/rspec/matchers'
22
24
 
23
- WebMock.enable!
24
-
25
25
  RSPEC_CONFIGURER.configure { |config|
26
26
 
27
27
  config.include WebMock::API
28
28
  config.include WebMock::Matchers
29
29
 
30
- config.after(:each) do
30
+ config.before(:suite) do
31
+ WebMock.enable!
32
+ end
33
+
34
+ config.after(:suite) do
35
+ WebMock.disable!
36
+ end
37
+
38
+ config.around(:each) do |example|
39
+ example.run
31
40
  WebMock.reset!
32
41
  end
33
42
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
 
3
5
  class StubRegistry
@@ -10,25 +12,39 @@ module WebMock
10
12
  end
11
13
 
12
14
  def global_stubs
13
- @global_stubs ||= []
15
+ @global_stubs ||= Hash.new { |h, k| h[k] = [] }
14
16
  end
15
17
 
16
18
  def reset!
17
19
  self.request_stubs = []
18
20
  end
19
21
 
20
- def register_global_stub(&block)
22
+ def register_global_stub(order = :before_local_stubs, &block)
23
+ unless %i[before_local_stubs after_local_stubs].include?(order)
24
+ raise ArgumentError.new("Wrong order. Use :before_local_stubs or :after_local_stubs")
25
+ end
26
+
21
27
  # This hash contains the responses returned by the block,
22
28
  # keyed by the exact request (using the object_id).
23
29
  # That way, there's no race condition in case #to_return
24
30
  # doesn't run immediately after stub.with.
25
31
  responses = {}
26
-
27
- stub = ::WebMock::RequestStub.new(:any, /.*/).with { |request|
28
- responses[request.object_id] = block.call(request)
29
- }.to_return(lambda { |request| responses.delete(request.object_id) })
30
-
31
- global_stubs.push stub
32
+ response_lock = Mutex.new
33
+
34
+ stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request|
35
+ update_response = -> { responses[request.object_id] = yield(request) }
36
+
37
+ # The block can recurse, so only lock if we don't already own it
38
+ if response_lock.owned?
39
+ update_response.call
40
+ else
41
+ response_lock.synchronize(&update_response)
42
+ end
43
+ }.to_return(lambda { |request|
44
+ response_lock.synchronize { responses.delete(request.object_id) }
45
+ })
46
+
47
+ global_stubs[order].push stub
32
48
  end
33
49
 
34
50
  def register_request_stub(stub)
@@ -54,9 +70,10 @@ module WebMock
54
70
  private
55
71
 
56
72
  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
- }
73
+ (global_stubs[:before_local_stubs] + request_stubs + global_stubs[:after_local_stubs])
74
+ .detect { |registered_request_stub|
75
+ registered_request_stub.request_pattern.matches?(request_signature)
76
+ }
60
77
  end
61
78
 
62
79
  def evaluate_response_for_request(response, request_signature)