webmock 3.5.1 → 3.18.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/CI.yml +38 -0
- data/CHANGELOG.md +343 -2
- data/Gemfile +1 -1
- data/README.md +116 -32
- data/Rakefile +12 -4
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +221 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +12 -3
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +27 -3
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +5 -3
- data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +6 -2
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +33 -15
- data/lib/webmock/http_lib_adapters/net_http.rb +35 -103
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +1 -1
- data/lib/webmock/request_body_diff.rb +1 -1
- data/lib/webmock/request_pattern.rb +106 -56
- data/lib/webmock/request_signature.rb +2 -2
- data/lib/webmock/request_stub.rb +15 -0
- data/lib/webmock/response.rb +19 -13
- data/lib/webmock/rspec.rb +2 -1
- data/lib/webmock/stub_registry.rb +26 -11
- data/lib/webmock/test_unit.rb +1 -3
- data/lib/webmock/util/query_mapper.rb +4 -2
- data/lib/webmock/util/uri.rb +8 -8
- data/lib/webmock/version.rb +1 -1
- data/lib/webmock/webmock.rb +20 -3
- data/lib/webmock.rb +1 -0
- data/minitest/webmock_spec.rb +1 -1
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
- data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
- data/spec/acceptance/curb/curb_spec.rb +34 -5
- data/spec/acceptance/em_http_request/em_http_request_spec.rb +57 -1
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +2 -2
- data/spec/acceptance/excon/excon_spec.rb +2 -2
- data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
- data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
- data/spec/acceptance/manticore/manticore_spec.rb +51 -0
- data/spec/acceptance/net_http/net_http_shared.rb +46 -9
- data/spec/acceptance/net_http/net_http_spec.rb +87 -23
- data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
- data/spec/acceptance/patron/patron_spec.rb +19 -21
- data/spec/acceptance/patron/patron_spec_helper.rb +2 -2
- data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +14 -14
- data/spec/acceptance/shared/callbacks.rb +3 -2
- data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
- data/spec/acceptance/shared/request_expectations.rb +7 -0
- data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
- data/spec/acceptance/shared/stubbing_requests.rb +40 -0
- data/spec/support/webmock_server.rb +1 -0
- data/spec/unit/request_pattern_spec.rb +201 -49
- data/spec/unit/request_signature_spec.rb +21 -1
- data/spec/unit/request_stub_spec.rb +35 -0
- data/spec/unit/response_spec.rb +51 -19
- data/spec/unit/util/query_mapper_spec.rb +7 -0
- data/spec/unit/util/uri_spec.rb +74 -2
- data/spec/unit/webmock_spec.rb +108 -5
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +15 -7
- metadata +78 -35
- data/.travis.yml +0 -21
@@ -10,24 +10,19 @@ module WebMock
|
|
10
10
|
adapter_for :net_http
|
11
11
|
|
12
12
|
OriginalNetHTTP = Net::HTTP unless const_defined?(:OriginalNetHTTP)
|
13
|
-
OriginalNetBufferedIO = Net::BufferedIO unless const_defined?(:OriginalNetBufferedIO)
|
14
13
|
|
15
14
|
def self.enable!
|
16
|
-
Net.send(:remove_const, :BufferedIO)
|
17
15
|
Net.send(:remove_const, :HTTP)
|
18
16
|
Net.send(:remove_const, :HTTPSession)
|
19
17
|
Net.send(:const_set, :HTTP, @webMockNetHTTP)
|
20
18
|
Net.send(:const_set, :HTTPSession, @webMockNetHTTP)
|
21
|
-
Net.send(:const_set, :BufferedIO, Net::WebMockNetBufferedIO)
|
22
19
|
end
|
23
20
|
|
24
21
|
def self.disable!
|
25
|
-
Net.send(:remove_const, :BufferedIO)
|
26
22
|
Net.send(:remove_const, :HTTP)
|
27
23
|
Net.send(:remove_const, :HTTPSession)
|
28
24
|
Net.send(:const_set, :HTTP, OriginalNetHTTP)
|
29
25
|
Net.send(:const_set, :HTTPSession, OriginalNetHTTP)
|
30
|
-
Net.send(:const_set, :BufferedIO, OriginalNetBufferedIO)
|
31
26
|
|
32
27
|
#copy all constants from @webMockNetHTTP to original Net::HTTP
|
33
28
|
#in case any constants were added to @webMockNetHTTP instead of Net::HTTP
|
@@ -98,13 +93,8 @@ module WebMock
|
|
98
93
|
after_request.call(response)
|
99
94
|
}
|
100
95
|
if started?
|
101
|
-
|
102
|
-
|
103
|
-
else
|
104
|
-
start_with_connect_without_finish {
|
105
|
-
super_with_after_request.call
|
106
|
-
}
|
107
|
-
end
|
96
|
+
ensure_actual_connection
|
97
|
+
super_with_after_request.call
|
108
98
|
else
|
109
99
|
start_with_connect {
|
110
100
|
super_with_after_request.call
|
@@ -119,32 +109,33 @@ module WebMock
|
|
119
109
|
raise IOError, 'HTTP session already opened' if @started
|
120
110
|
if block_given?
|
121
111
|
begin
|
112
|
+
@socket = Net::HTTP.socket_type.new
|
122
113
|
@started = true
|
123
114
|
return yield(self)
|
124
115
|
ensure
|
125
116
|
do_finish
|
126
117
|
end
|
127
118
|
end
|
119
|
+
@socket = Net::HTTP.socket_type.new
|
128
120
|
@started = true
|
129
121
|
self
|
130
122
|
end
|
131
123
|
|
132
124
|
|
133
|
-
def
|
134
|
-
if
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
end
|
125
|
+
def ensure_actual_connection
|
126
|
+
if @socket.is_a?(StubSocket)
|
127
|
+
@socket&.close
|
128
|
+
@socket = nil
|
129
|
+
do_start
|
139
130
|
end
|
140
|
-
do_start
|
141
|
-
self
|
142
131
|
end
|
143
132
|
|
144
133
|
alias_method :start_with_connect, :start
|
145
134
|
|
146
135
|
def start(&block)
|
147
|
-
|
136
|
+
uri = Addressable::URI.parse(WebMock::NetHTTPUtility.get_uri(self))
|
137
|
+
|
138
|
+
if WebMock.net_http_connect_on_start?(uri)
|
148
139
|
super(&block)
|
149
140
|
else
|
150
141
|
start_without_connect(&block)
|
@@ -169,7 +160,7 @@ module WebMock
|
|
169
160
|
response.extend Net::WebMockHTTPResponse
|
170
161
|
|
171
162
|
if webmock_response.should_timeout
|
172
|
-
raise
|
163
|
+
raise Net::OpenTimeout, "execution expired"
|
173
164
|
end
|
174
165
|
|
175
166
|
webmock_response.raise_error_if_any
|
@@ -179,16 +170,6 @@ module WebMock
|
|
179
170
|
response
|
180
171
|
end
|
181
172
|
|
182
|
-
def timeout_exception
|
183
|
-
if defined?(Net::OpenTimeout)
|
184
|
-
# Ruby 2.x
|
185
|
-
Net::OpenTimeout
|
186
|
-
else
|
187
|
-
# Fallback, if things change
|
188
|
-
Timeout::Error
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
173
|
def build_webmock_response(net_http_response)
|
193
174
|
webmock_response = WebMock::Response.new
|
194
175
|
webmock_response.status = [
|
@@ -222,81 +203,43 @@ module WebMock
|
|
222
203
|
end
|
223
204
|
end
|
224
205
|
|
225
|
-
# patch for StringIO behavior in Ruby 2.2.3
|
226
|
-
# https://github.com/bblimke/webmock/issues/558
|
227
|
-
class PatchedStringIO < StringIO #:nodoc:
|
228
|
-
|
229
|
-
alias_method :orig_read_nonblock, :read_nonblock
|
230
|
-
|
231
|
-
def read_nonblock(size, *args)
|
232
|
-
orig_read_nonblock(size)
|
233
|
-
end
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
206
|
class StubSocket #:nodoc:
|
238
207
|
|
239
|
-
attr_accessor :read_timeout, :continue_timeout
|
208
|
+
attr_accessor :read_timeout, :continue_timeout, :write_timeout
|
240
209
|
|
241
210
|
def initialize(*args)
|
211
|
+
@closed = false
|
242
212
|
end
|
243
213
|
|
244
214
|
def closed?
|
245
|
-
@closed
|
215
|
+
@closed
|
246
216
|
end
|
247
217
|
|
248
218
|
def close
|
219
|
+
@closed = true
|
220
|
+
nil
|
249
221
|
end
|
250
222
|
|
251
223
|
def readuntil(*args)
|
252
224
|
end
|
253
225
|
|
254
|
-
|
255
|
-
|
256
|
-
module Net #:nodoc: all
|
257
|
-
|
258
|
-
class WebMockNetBufferedIO < BufferedIO
|
259
|
-
def initialize(io, *args)
|
260
|
-
io = case io
|
261
|
-
when Socket, OpenSSL::SSL::SSLSocket, IO
|
262
|
-
io
|
263
|
-
when StringIO
|
264
|
-
PatchedStringIO.new(io.string)
|
265
|
-
when String
|
266
|
-
PatchedStringIO.new(io)
|
267
|
-
end
|
268
|
-
raise "Unable to create local socket" unless io
|
269
|
-
|
270
|
-
super
|
271
|
-
end
|
272
|
-
|
273
|
-
if RUBY_VERSION >= '2.6.0'
|
274
|
-
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
|
287
|
-
end
|
288
|
-
end
|
226
|
+
def io
|
227
|
+
@io ||= StubIO.new
|
289
228
|
end
|
290
229
|
|
230
|
+
class StubIO
|
231
|
+
def setsockopt(*args); end
|
232
|
+
def peer_cert; end
|
233
|
+
def peeraddr; ["AF_INET", 443, "127.0.0.1", "127.0.0.1"] end
|
234
|
+
def ssl_version; "TLSv1.3" end
|
235
|
+
def cipher; ["TLS_AES_128_GCM_SHA256", "TLSv1.3", 128, 128] end
|
236
|
+
end
|
291
237
|
end
|
292
238
|
|
293
|
-
|
294
239
|
module WebMock
|
295
240
|
module NetHTTPUtility
|
296
241
|
|
297
242
|
def self.request_signature_from_request(net_http, request, body = nil)
|
298
|
-
protocol = net_http.use_ssl? ? "https" : "http"
|
299
|
-
|
300
243
|
path = request.path
|
301
244
|
|
302
245
|
if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
|
@@ -305,11 +248,10 @@ module WebMock
|
|
305
248
|
|
306
249
|
path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
|
307
250
|
|
308
|
-
uri =
|
251
|
+
uri = get_uri(net_http, path)
|
309
252
|
method = request.method.downcase.to_sym
|
310
253
|
|
311
254
|
headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
|
312
|
-
validate_headers(headers)
|
313
255
|
|
314
256
|
if request.body_stream
|
315
257
|
body = request.body_stream.read
|
@@ -325,23 +267,13 @@ module WebMock
|
|
325
267
|
WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
|
326
268
|
end
|
327
269
|
|
328
|
-
def self.
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
#
|
333
|
-
|
334
|
-
#
|
335
|
-
# This could create a false positive in a test suite with WebMock.
|
336
|
-
#
|
337
|
-
# So before this point, WebMock raises an ArgumentError if any of the headers are symbols
|
338
|
-
# instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
|
339
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
|
340
|
-
header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
|
341
|
-
if header_as_symbol
|
342
|
-
raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
|
343
|
-
end
|
344
|
-
end
|
270
|
+
def self.get_uri(net_http, path = nil)
|
271
|
+
protocol = net_http.use_ssl? ? "https" : "http"
|
272
|
+
|
273
|
+
hostname = net_http.address
|
274
|
+
hostname = "[#{hostname}]" if /\A\[.*\]\z/ !~ hostname && /:/ =~ hostname
|
275
|
+
|
276
|
+
"#{protocol}://#{hostname}:#{net_http.port}#{path}"
|
345
277
|
end
|
346
278
|
|
347
279
|
def self.check_right_http_connection
|
@@ -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
|
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
|
-
|
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 =
|
111
|
-
|
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
|
-
|
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 =
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
148
|
+
private
|
149
|
+
|
150
|
+
def pattern_inspect
|
151
|
+
@pattern.inspect
|
145
152
|
end
|
146
153
|
|
147
|
-
def
|
148
|
-
|
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
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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,28 +182,57 @@ module WebMock
|
|
172
182
|
super(query_params)
|
173
183
|
end
|
174
184
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
197
|
+
def pattern_inspect
|
198
|
+
@pattern.pattern.inspect
|
199
|
+
end
|
182
200
|
|
183
201
|
def matches_with_variations?(uri)
|
184
|
-
|
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
|
-
|
187
|
-
|
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
|
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
|
206
|
-
|
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
|
|
@@ -253,6 +281,8 @@ module WebMock
|
|
253
281
|
if (@pattern).is_a?(Hash)
|
254
282
|
return true if @pattern.empty?
|
255
283
|
matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
|
284
|
+
elsif (@pattern).is_a?(Array)
|
285
|
+
matching_body_array?(body_as_hash(body, content_type), @pattern, content_type)
|
256
286
|
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
|
257
287
|
@pattern == body_as_hash(body, content_type)
|
258
288
|
else
|
@@ -267,8 +297,9 @@ module WebMock
|
|
267
297
|
end
|
268
298
|
|
269
299
|
private
|
300
|
+
|
270
301
|
def body_as_hash(body, content_type)
|
271
|
-
case
|
302
|
+
case body_format(content_type)
|
272
303
|
when :json then
|
273
304
|
WebMock::Util::JSON.parse(body)
|
274
305
|
when :xml then
|
@@ -278,6 +309,11 @@ module WebMock
|
|
278
309
|
end
|
279
310
|
end
|
280
311
|
|
312
|
+
def body_format(content_type)
|
313
|
+
normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
|
314
|
+
BODY_FORMATS[normalized_content_type]
|
315
|
+
end
|
316
|
+
|
281
317
|
def assert_non_multipart_body(content_type)
|
282
318
|
if content_type =~ %r{^multipart/form-data}
|
283
319
|
raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
|
@@ -310,19 +346,33 @@ module WebMock
|
|
310
346
|
def matching_body_hashes?(query_parameters, pattern, content_type)
|
311
347
|
return false unless query_parameters.is_a?(Hash)
|
312
348
|
return false unless query_parameters.keys.sort == pattern.keys.sort
|
313
|
-
|
349
|
+
|
350
|
+
query_parameters.all? do |key, actual|
|
314
351
|
expected = pattern[key]
|
352
|
+
matching_values(actual, expected, content_type)
|
353
|
+
end
|
354
|
+
end
|
315
355
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
356
|
+
def matching_body_array?(query_parameters, pattern, content_type)
|
357
|
+
return false unless query_parameters.is_a?(Array)
|
358
|
+
return false unless query_parameters.length == pattern.length
|
359
|
+
|
360
|
+
query_parameters.each_with_index do |actual, index|
|
361
|
+
expected = pattern[index]
|
362
|
+
return false unless matching_values(actual, expected, content_type)
|
322
363
|
end
|
364
|
+
|
323
365
|
true
|
324
366
|
end
|
325
367
|
|
368
|
+
def matching_values(actual, expected, content_type)
|
369
|
+
return matching_body_hashes?(actual, expected, content_type) if actual.is_a?(Hash) && expected.is_a?(Hash)
|
370
|
+
return matching_body_array?(actual, expected, content_type) if actual.is_a?(Array) && expected.is_a?(Array)
|
371
|
+
|
372
|
+
expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
|
373
|
+
expected === actual
|
374
|
+
end
|
375
|
+
|
326
376
|
def empty_string?(string)
|
327
377
|
string.nil? || string == ""
|
328
378
|
end
|
@@ -35,11 +35,11 @@ module WebMock
|
|
35
35
|
alias == eql?
|
36
36
|
|
37
37
|
def url_encoded?
|
38
|
-
!!(headers
|
38
|
+
!!(headers&.fetch('Content-Type', nil)&.start_with?('application/x-www-form-urlencoded'))
|
39
39
|
end
|
40
40
|
|
41
41
|
def json_headers?
|
42
|
-
!!(headers
|
42
|
+
!!(headers&.fetch('Content-Type', nil)&.start_with?('application/json'))
|
43
43
|
end
|
44
44
|
|
45
45
|
private
|
data/lib/webmock/request_stub.rb
CHANGED
@@ -24,6 +24,21 @@ module WebMock
|
|
24
24
|
end
|
25
25
|
alias_method :and_return, :to_return
|
26
26
|
|
27
|
+
def to_return_json(*response_hashes)
|
28
|
+
raise ArgumentError, '#to_return_json does not support passing a block' if block_given?
|
29
|
+
|
30
|
+
json_response_hashes = [*response_hashes].flatten.map do |resp_h|
|
31
|
+
headers, body = resp_h.values_at(:headers, :body)
|
32
|
+
resp_h.merge(
|
33
|
+
headers: {content_type: 'application/json'}.merge(headers.to_h),
|
34
|
+
body: body.is_a?(Hash) ? body.to_json : body
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
to_return(json_response_hashes)
|
39
|
+
end
|
40
|
+
alias_method :and_return_json, :to_return_json
|
41
|
+
|
27
42
|
def to_rack(app, options={})
|
28
43
|
@responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
|
29
44
|
end
|
data/lib/webmock/response.rb
CHANGED
@@ -14,8 +14,11 @@ module WebMock
|
|
14
14
|
|
15
15
|
class Response
|
16
16
|
def initialize(options = {})
|
17
|
-
|
17
|
+
case options
|
18
|
+
when IO, StringIO
|
18
19
|
self.options = read_raw_response(options)
|
20
|
+
when String
|
21
|
+
self.options = read_raw_response(StringIO.new(options))
|
19
22
|
else
|
20
23
|
self.options = options
|
21
24
|
end
|
@@ -91,10 +94,10 @@ module WebMock
|
|
91
94
|
|
92
95
|
def ==(other)
|
93
96
|
self.body == other.body &&
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
97
|
+
self.headers === other.headers &&
|
98
|
+
self.status == other.status &&
|
99
|
+
self.exception == other.exception &&
|
100
|
+
self.should_timeout == other.should_timeout
|
98
101
|
end
|
99
102
|
|
100
103
|
private
|
@@ -111,16 +114,17 @@ module WebMock
|
|
111
114
|
valid_types = [Proc, IO, Pathname, String, Array]
|
112
115
|
return if @body.nil?
|
113
116
|
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
117
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
if @body.class.is_a?(Hash)
|
119
|
+
raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}' instead." \
|
120
|
+
"\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."
|
121
|
+
else
|
122
|
+
raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
|
122
123
|
end
|
123
|
-
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_raw_response(io)
|
127
|
+
socket = ::Net::BufferedIO.new(io)
|
124
128
|
response = ::Net::HTTPResponse.read_new(socket)
|
125
129
|
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
|
126
130
|
response.reading_body(socket, true) {}
|
@@ -132,6 +136,8 @@ module WebMock
|
|
132
136
|
options[:body] = response.read_body
|
133
137
|
options[:status] = [response.code.to_i, response.message]
|
134
138
|
options
|
139
|
+
ensure
|
140
|
+
socket.close
|
135
141
|
end
|
136
142
|
|
137
143
|
InvalidBody = Class.new(StandardError)
|