webmock 3.0.1 → 3.20.0

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