webmock 3.9.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 +180 -6
- data/Gemfile +1 -1
- data/README.md +63 -30
- data/Rakefile +12 -2
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +11 -4
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +2 -2
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +6 -3
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +17 -3
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +4 -2
- 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 +8 -1
- data/lib/webmock/http_lib_adapters/net_http.rb +29 -115
- data/lib/webmock/request_pattern.rb +30 -8
- 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/stub_registry.rb +24 -9
- data/lib/webmock/test_unit.rb +1 -3
- data/lib/webmock/version.rb +1 -1
- data/lib/webmock/webmock.rb +12 -2
- data/minitest/webmock_spec.rb +1 -1
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +27 -5
- data/spec/acceptance/curb/curb_spec.rb +11 -0
- 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 +1 -1
- data/spec/acceptance/excon/excon_spec.rb +2 -2
- data/spec/acceptance/manticore/manticore_spec.rb +32 -0
- data/spec/acceptance/net_http/net_http_shared.rb +46 -9
- data/spec/acceptance/net_http/net_http_spec.rb +75 -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 +2 -2
- data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
- data/spec/acceptance/shared/stubbing_requests.rb +17 -0
- data/spec/unit/request_pattern_spec.rb +82 -46
- 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/webmock_spec.rb +54 -0
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +6 -5
- metadata +49 -35
- data/.travis.yml +0 -24
@@ -43,6 +43,9 @@ if defined?(::HTTPClient)
|
|
43
43
|
end
|
44
44
|
|
45
45
|
module WebMockHTTPClients
|
46
|
+
|
47
|
+
REQUEST_RESPONSE_LOCK = Mutex.new
|
48
|
+
|
46
49
|
def do_get_block(req, proxy, conn, &block)
|
47
50
|
do_get(req, proxy, conn, false, &block)
|
48
51
|
end
|
@@ -57,7 +60,7 @@ if defined?(::HTTPClient)
|
|
57
60
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
58
61
|
|
59
62
|
if webmock_responses[request_signature]
|
60
|
-
webmock_response = webmock_responses.delete(request_signature)
|
63
|
+
webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
|
61
64
|
response = build_httpclient_response(webmock_response, stream, req.header, &block)
|
62
65
|
@request_filter.each do |filter|
|
63
66
|
filter.filter_response(req, response)
|
@@ -68,7 +71,7 @@ if defined?(::HTTPClient)
|
|
68
71
|
res
|
69
72
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
70
73
|
# in case there is a nil entry in the hash...
|
71
|
-
webmock_responses.delete(request_signature)
|
74
|
+
synchronize_request_response { webmock_responses.delete(request_signature) }
|
72
75
|
|
73
76
|
res = if stream
|
74
77
|
do_get_stream_without_webmock(req, proxy, conn, &block)
|
@@ -100,7 +103,7 @@ if defined?(::HTTPClient)
|
|
100
103
|
def do_request_async(method, uri, query, body, extheader)
|
101
104
|
req = create_request(method, uri, query, body, extheader)
|
102
105
|
request_signature = build_request_signature(req)
|
103
|
-
webmock_request_signatures << request_signature
|
106
|
+
synchronize_request_response { webmock_request_signatures << request_signature }
|
104
107
|
|
105
108
|
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
|
106
109
|
super
|
@@ -184,7 +187,9 @@ if defined?(::HTTPClient)
|
|
184
187
|
|
185
188
|
def webmock_responses
|
186
189
|
@webmock_responses ||= Hash.new do |hash, request_signature|
|
187
|
-
|
190
|
+
synchronize_request_response do
|
191
|
+
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
|
192
|
+
end
|
188
193
|
end
|
189
194
|
end
|
190
195
|
|
@@ -193,8 +198,10 @@ if defined?(::HTTPClient)
|
|
193
198
|
end
|
194
199
|
|
195
200
|
def previous_signature_for(signature)
|
196
|
-
|
197
|
-
|
201
|
+
synchronize_request_response do
|
202
|
+
return nil unless index = webmock_request_signatures.index(signature)
|
203
|
+
webmock_request_signatures.delete_at(index)
|
204
|
+
end
|
198
205
|
end
|
199
206
|
|
200
207
|
private
|
@@ -209,6 +216,16 @@ if defined?(::HTTPClient)
|
|
209
216
|
hdrs
|
210
217
|
end
|
211
218
|
end
|
219
|
+
|
220
|
+
def synchronize_request_response
|
221
|
+
if REQUEST_RESPONSE_LOCK.owned?
|
222
|
+
yield
|
223
|
+
else
|
224
|
+
REQUEST_RESPONSE_LOCK.synchronize do
|
225
|
+
yield
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
212
229
|
end
|
213
230
|
|
214
231
|
class WebMockHTTPClient < HTTPClient
|
@@ -127,8 +127,15 @@ if defined?(Manticore)
|
|
127
127
|
def generate_webmock_response(manticore_response)
|
128
128
|
webmock_response = WebMock::Response.new
|
129
129
|
webmock_response.status = [manticore_response.code, manticore_response.message]
|
130
|
-
webmock_response.body = manticore_response.body
|
131
130
|
webmock_response.headers = manticore_response.headers
|
131
|
+
|
132
|
+
# The attempt to read the body could fail if manticore is used in a streaming mode
|
133
|
+
webmock_response.body = begin
|
134
|
+
manticore_response.body
|
135
|
+
rescue ::Manticore::StreamClosedException
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
132
139
|
webmock_response
|
133
140
|
end
|
134
141
|
end
|
@@ -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,31 +203,21 @@ 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, **kwargs)
|
232
|
-
args.reject! {|arg| !arg.is_a?(Hash)}
|
233
|
-
orig_read_nonblock(size, *args, **kwargs)
|
234
|
-
end
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
206
|
class StubSocket #:nodoc:
|
239
207
|
|
240
208
|
attr_accessor :read_timeout, :continue_timeout, :write_timeout
|
241
209
|
|
242
210
|
def initialize(*args)
|
211
|
+
@closed = false
|
243
212
|
end
|
244
213
|
|
245
214
|
def closed?
|
246
|
-
@closed
|
215
|
+
@closed
|
247
216
|
end
|
248
217
|
|
249
218
|
def close
|
219
|
+
@closed = true
|
220
|
+
nil
|
250
221
|
end
|
251
222
|
|
252
223
|
def readuntil(*args)
|
@@ -258,63 +229,17 @@ class StubSocket #:nodoc:
|
|
258
229
|
|
259
230
|
class StubIO
|
260
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
|
261
236
|
end
|
262
237
|
end
|
263
238
|
|
264
|
-
module Net #:nodoc: all
|
265
|
-
|
266
|
-
class WebMockNetBufferedIO < BufferedIO
|
267
|
-
def initialize(io, *args, **kwargs)
|
268
|
-
io = case io
|
269
|
-
when Socket, OpenSSL::SSL::SSLSocket, IO
|
270
|
-
io
|
271
|
-
when StringIO
|
272
|
-
PatchedStringIO.new(io.string)
|
273
|
-
when String
|
274
|
-
PatchedStringIO.new(io)
|
275
|
-
end
|
276
|
-
raise "Unable to create local socket" unless io
|
277
|
-
|
278
|
-
# Prior to 2.4.0 `BufferedIO` only takes a single argument (`io`) with no
|
279
|
-
# options. Here we pass through our full set of arguments only if we're
|
280
|
-
# on 2.4.0 or later, and use a simplified invocation otherwise.
|
281
|
-
if RUBY_VERSION >= '2.4.0'
|
282
|
-
super
|
283
|
-
else
|
284
|
-
super(io)
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
if RUBY_VERSION >= '2.6.0'
|
289
|
-
# https://github.com/ruby/ruby/blob/7d02441f0d6e5c9d0a73a024519eba4f69e36dce/lib/net/protocol.rb#L208
|
290
|
-
# Modified version of method from ruby, so that nil is always passed into orig_read_nonblock to avoid timeout
|
291
|
-
def rbuf_fill
|
292
|
-
case rv = @io.read_nonblock(BUFSIZE, nil, exception: false)
|
293
|
-
when String
|
294
|
-
return if rv.nil?
|
295
|
-
@rbuf << rv
|
296
|
-
rv.clear
|
297
|
-
return
|
298
|
-
when :wait_readable
|
299
|
-
@io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout
|
300
|
-
when :wait_writable
|
301
|
-
@io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
|
302
|
-
when nil
|
303
|
-
raise EOFError, 'end of file reached'
|
304
|
-
end while true
|
305
|
-
end
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
end
|
310
|
-
|
311
|
-
|
312
239
|
module WebMock
|
313
240
|
module NetHTTPUtility
|
314
241
|
|
315
242
|
def self.request_signature_from_request(net_http, request, body = nil)
|
316
|
-
protocol = net_http.use_ssl? ? "https" : "http"
|
317
|
-
|
318
243
|
path = request.path
|
319
244
|
|
320
245
|
if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
|
@@ -323,11 +248,10 @@ module WebMock
|
|
323
248
|
|
324
249
|
path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
|
325
250
|
|
326
|
-
uri =
|
251
|
+
uri = get_uri(net_http, path)
|
327
252
|
method = request.method.downcase.to_sym
|
328
253
|
|
329
254
|
headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
|
330
|
-
validate_headers(headers)
|
331
255
|
|
332
256
|
if request.body_stream
|
333
257
|
body = request.body_stream.read
|
@@ -343,23 +267,13 @@ module WebMock
|
|
343
267
|
WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
|
344
268
|
end
|
345
269
|
|
346
|
-
def self.
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
#
|
351
|
-
|
352
|
-
#
|
353
|
-
# This could create a false positive in a test suite with WebMock.
|
354
|
-
#
|
355
|
-
# So before this point, WebMock raises an ArgumentError if any of the headers are symbols
|
356
|
-
# instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
|
357
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
|
358
|
-
header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
|
359
|
-
if header_as_symbol
|
360
|
-
raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
|
361
|
-
end
|
362
|
-
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}"
|
363
277
|
end
|
364
278
|
|
365
279
|
def self.check_right_http_connection
|
@@ -281,6 +281,8 @@ module WebMock
|
|
281
281
|
if (@pattern).is_a?(Hash)
|
282
282
|
return true if @pattern.empty?
|
283
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)
|
284
286
|
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
|
285
287
|
@pattern == body_as_hash(body, content_type)
|
286
288
|
else
|
@@ -295,8 +297,9 @@ module WebMock
|
|
295
297
|
end
|
296
298
|
|
297
299
|
private
|
300
|
+
|
298
301
|
def body_as_hash(body, content_type)
|
299
|
-
case
|
302
|
+
case body_format(content_type)
|
300
303
|
when :json then
|
301
304
|
WebMock::Util::JSON.parse(body)
|
302
305
|
when :xml then
|
@@ -306,6 +309,11 @@ module WebMock
|
|
306
309
|
end
|
307
310
|
end
|
308
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
|
+
|
309
317
|
def assert_non_multipart_body(content_type)
|
310
318
|
if content_type =~ %r{^multipart/form-data}
|
311
319
|
raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
|
@@ -338,19 +346,33 @@ module WebMock
|
|
338
346
|
def matching_body_hashes?(query_parameters, pattern, content_type)
|
339
347
|
return false unless query_parameters.is_a?(Hash)
|
340
348
|
return false unless query_parameters.keys.sort == pattern.keys.sort
|
341
|
-
|
349
|
+
|
350
|
+
query_parameters.all? do |key, actual|
|
342
351
|
expected = pattern[key]
|
352
|
+
matching_values(actual, expected, content_type)
|
353
|
+
end
|
354
|
+
end
|
343
355
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
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)
|
350
363
|
end
|
364
|
+
|
351
365
|
true
|
352
366
|
end
|
353
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
|
+
|
354
376
|
def empty_string?(string)
|
355
377
|
string.nil? || string == ""
|
356
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)
|
@@ -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 = {}
|
30
|
+
response_lock = Mutex.new
|
26
31
|
|
27
32
|
stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request|
|
28
|
-
responses[request.object_id] = yield(request)
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
58
|
-
registered_request_stub
|
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)
|
data/lib/webmock/test_unit.rb
CHANGED
@@ -8,12 +8,10 @@ module Test
|
|
8
8
|
class TestCase
|
9
9
|
include WebMock::API
|
10
10
|
|
11
|
-
|
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
|
data/lib/webmock/version.rb
CHANGED
data/lib/webmock/webmock.rb
CHANGED
@@ -70,6 +70,16 @@ module WebMock
|
|
70
70
|
Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) )
|
71
71
|
end
|
72
72
|
|
73
|
+
def self.net_http_connect_on_start?(uri)
|
74
|
+
allowed = Config.instance.net_http_connect_on_start || false
|
75
|
+
|
76
|
+
if [true, false].include?(allowed)
|
77
|
+
allowed
|
78
|
+
else
|
79
|
+
net_connect_explicit_allowed?(allowed, uri)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
73
83
|
def self.net_connect_explicit_allowed?(allowed, uri=nil)
|
74
84
|
case allowed
|
75
85
|
when Array
|
@@ -140,8 +150,8 @@ module WebMock
|
|
140
150
|
puts WebMock::RequestExecutionVerifier.executed_requests_message
|
141
151
|
end
|
142
152
|
|
143
|
-
def self.globally_stub_request(&block)
|
144
|
-
WebMock::StubRegistry.instance.register_global_stub(&block)
|
153
|
+
def self.globally_stub_request(order = :before_local_stubs, &block)
|
154
|
+
WebMock::StubRegistry.instance.register_global_stub(order, &block)
|
145
155
|
end
|
146
156
|
|
147
157
|
%w(
|
data/minitest/webmock_spec.rb
CHANGED
@@ -20,7 +20,7 @@ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should raise error on non stubbed request" do
|
23
|
-
|
23
|
+
expect { http_request(:get, "http://www.example.net/") }.must_raise(WebMock::NetConnectNotAllowedError)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "should verify that expected request occured" do
|