webmock 3.0.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 +5 -5
- data/.github/workflows/CI.yml +38 -0
- data/CHANGELOG.md +496 -2
- data/Gemfile +1 -1
- data/README.md +169 -34
- data/Rakefile +12 -4
- data/lib/webmock/api.rb +12 -0
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +221 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +19 -5
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +5 -2
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
- data/lib/webmock/http_lib_adapters/http_rb/request.rb +7 -1
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +27 -3
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +9 -3
- data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +7 -3
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +28 -9
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +33 -15
- data/lib/webmock/http_lib_adapters/net_http.rb +36 -89
- data/lib/webmock/http_lib_adapters/net_http_response.rb +1 -1
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +4 -4
- data/lib/webmock/matchers/any_arg_matcher.rb +13 -0
- data/lib/webmock/matchers/hash_argument_matcher.rb +21 -0
- data/lib/webmock/matchers/hash_excluding_matcher.rb +15 -0
- data/lib/webmock/matchers/hash_including_matcher.rb +4 -23
- data/lib/webmock/rack_response.rb +1 -1
- data/lib/webmock/request_body_diff.rb +1 -1
- data/lib/webmock/request_execution_verifier.rb +2 -3
- data/lib/webmock/request_pattern.rb +129 -51
- data/lib/webmock/request_registry.rb +1 -1
- data/lib/webmock/request_signature.rb +3 -3
- data/lib/webmock/request_signature_snippet.rb +4 -4
- data/lib/webmock/request_stub.rb +15 -0
- data/lib/webmock/response.rb +19 -13
- data/lib/webmock/rspec.rb +10 -3
- data/lib/webmock/stub_registry.rb +26 -11
- data/lib/webmock/stub_request_snippet.rb +10 -6
- data/lib/webmock/test_unit.rb +1 -3
- data/lib/webmock/util/hash_counter.rb +3 -3
- data/lib/webmock/util/headers.rb +17 -2
- data/lib/webmock/util/json.rb +1 -2
- data/lib/webmock/util/query_mapper.rb +9 -7
- data/lib/webmock/util/uri.rb +10 -10
- data/lib/webmock/util/values_stringifier.rb +20 -0
- data/lib/webmock/version.rb +1 -1
- data/lib/webmock/webmock.rb +20 -3
- data/lib/webmock.rb +53 -48
- data/minitest/webmock_spec.rb +3 -3
- 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 +44 -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 +2 -2
- data/spec/acceptance/excon/excon_spec.rb +4 -2
- data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
- data/spec/acceptance/http_rb/http_rb_spec.rb +20 -0
- data/spec/acceptance/http_rb/http_rb_spec_helper.rb +5 -2
- data/spec/acceptance/httpclient/httpclient_spec.rb +8 -1
- data/spec/acceptance/manticore/manticore_spec.rb +51 -0
- data/spec/acceptance/net_http/net_http_shared.rb +47 -10
- data/spec/acceptance/net_http/net_http_spec.rb +102 -24
- data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
- data/spec/acceptance/patron/patron_spec.rb +26 -21
- data/spec/acceptance/patron/patron_spec_helper.rb +3 -3
- 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 +14 -0
- data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
- data/spec/acceptance/shared/stubbing_requests.rb +95 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +1 -1
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +1 -1
- data/spec/support/webmock_server.rb +1 -0
- data/spec/unit/api_spec.rb +103 -3
- data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
- data/spec/unit/request_execution_verifier_spec.rb +12 -12
- data/spec/unit/request_pattern_spec.rb +207 -49
- data/spec/unit/request_signature_snippet_spec.rb +2 -2
- 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/stub_request_snippet_spec.rb +30 -10
- data/spec/unit/util/query_mapper_spec.rb +13 -0
- data/spec/unit/util/uri_spec.rb +74 -2
- data/spec/unit/webmock_spec.rb +108 -5
- data/test/shared_test.rb +15 -2
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +15 -7
- metadata +86 -37
- data/.travis.yml +0 -20
|
@@ -99,6 +99,11 @@ if defined?(EventMachine::HttpClient)
|
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
+
def connection_completed
|
|
103
|
+
@state = :response_header
|
|
104
|
+
send_request(request_signature.headers, request_signature.body)
|
|
105
|
+
end
|
|
106
|
+
|
|
102
107
|
def send_request(head, body)
|
|
103
108
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
|
104
109
|
|
|
@@ -107,7 +112,7 @@ if defined?(EventMachine::HttpClient)
|
|
|
107
112
|
@uri ||= nil
|
|
108
113
|
EM.next_tick {
|
|
109
114
|
setup(make_raw_response(stubbed_webmock_response), @uri,
|
|
110
|
-
stubbed_webmock_response.should_timeout ?
|
|
115
|
+
stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
|
|
111
116
|
}
|
|
112
117
|
self
|
|
113
118
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
|
@@ -164,7 +169,7 @@ if defined?(EventMachine::HttpClient)
|
|
|
164
169
|
end
|
|
165
170
|
|
|
166
171
|
def build_request_signature
|
|
167
|
-
headers, body =
|
|
172
|
+
headers, body = build_request, @req.body
|
|
168
173
|
|
|
169
174
|
@conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
|
|
170
175
|
headers, body = m.request(self, headers, body)
|
|
@@ -178,8 +183,6 @@ if defined?(EventMachine::HttpClient)
|
|
|
178
183
|
|
|
179
184
|
body = form_encode_body(body) if body.is_a?(Hash)
|
|
180
185
|
|
|
181
|
-
headers = @req.headers
|
|
182
|
-
|
|
183
186
|
if headers['authorization'] && headers['authorization'].is_a?(Array)
|
|
184
187
|
headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(headers.delete('authorization'))
|
|
185
188
|
end
|
|
@@ -92,7 +92,7 @@ if defined?(Excon)
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def self.to_query(hash)
|
|
95
|
-
string = ""
|
|
95
|
+
string = "".dup
|
|
96
96
|
for key, values in hash
|
|
97
97
|
if values.nil?
|
|
98
98
|
string << key.to_s << '&'
|
|
@@ -152,11 +152,14 @@ if defined?(Excon)
|
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
Excon::Connection.class_eval do
|
|
155
|
-
def self.new(args)
|
|
155
|
+
def self.new(args = {})
|
|
156
156
|
args.delete(:__construction_args)
|
|
157
157
|
super(args).tap do |instance|
|
|
158
158
|
instance.data[:__construction_args] = args
|
|
159
159
|
end
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
|
+
|
|
163
|
+
# Suppresses Excon connection argument validation warning
|
|
164
|
+
Excon::VALID_CONNECTION_KEYS << :__construction_args
|
|
162
165
|
end
|
|
@@ -4,7 +4,8 @@ module HTTP
|
|
|
4
4
|
|
|
5
5
|
def perform(request, options)
|
|
6
6
|
return __perform__(request, options) unless webmock_enabled?
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
WebMockPerform.new(request, options) { __perform__(request, options) }.exec
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def webmock_enabled?
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
module HTTP
|
|
2
2
|
class Request
|
|
3
3
|
def webmock_signature
|
|
4
|
+
request_body = if defined?(HTTP::Request::Body)
|
|
5
|
+
''.tap { |string| body.each { |part| string << part } }
|
|
6
|
+
else
|
|
7
|
+
body
|
|
8
|
+
end
|
|
9
|
+
|
|
4
10
|
::WebMock::RequestSignature.new(verb, uri.to_s, {
|
|
5
11
|
headers: headers.to_h,
|
|
6
|
-
body:
|
|
12
|
+
body: request_body
|
|
7
13
|
})
|
|
8
14
|
end
|
|
9
15
|
end
|
|
@@ -11,20 +11,44 @@ module HTTP
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
|
-
def from_webmock(webmock_response, request_signature = nil)
|
|
14
|
+
def from_webmock(request, webmock_response, request_signature = nil)
|
|
15
15
|
status = Status.new(webmock_response.status.first)
|
|
16
16
|
headers = webmock_response.headers || {}
|
|
17
|
-
body = Body.new Streamer.new webmock_response.body
|
|
18
17
|
uri = normalize_uri(request_signature && request_signature.uri)
|
|
19
18
|
|
|
19
|
+
# HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
|
|
20
|
+
# and 2.x use a positional argument, and 0.x don't support supplying
|
|
21
|
+
# the encoding.
|
|
22
|
+
body = if HTTP::VERSION < "1.0.0"
|
|
23
|
+
Body.new(Streamer.new(webmock_response.body))
|
|
24
|
+
elsif HTTP::VERSION < "3.0.0"
|
|
25
|
+
Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
|
|
26
|
+
else
|
|
27
|
+
Body.new(
|
|
28
|
+
Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding),
|
|
29
|
+
encoding: webmock_response.body.encoding
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
20
33
|
return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
|
|
21
34
|
|
|
35
|
+
# 5.0.0 had a breaking change to require request instead of uri.
|
|
36
|
+
if HTTP::VERSION < '5.0.0'
|
|
37
|
+
return new({
|
|
38
|
+
status: status,
|
|
39
|
+
version: "1.1",
|
|
40
|
+
headers: headers,
|
|
41
|
+
body: body,
|
|
42
|
+
uri: uri
|
|
43
|
+
})
|
|
44
|
+
end
|
|
45
|
+
|
|
22
46
|
new({
|
|
23
47
|
status: status,
|
|
24
48
|
version: "1.1",
|
|
25
49
|
headers: headers,
|
|
26
50
|
body: body,
|
|
27
|
-
|
|
51
|
+
request: request,
|
|
28
52
|
})
|
|
29
53
|
end
|
|
30
54
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module HTTP
|
|
2
2
|
class Response
|
|
3
3
|
class Streamer
|
|
4
|
-
def initialize(str)
|
|
4
|
+
def initialize(str, encoding: Encoding::BINARY)
|
|
5
5
|
@io = StringIO.new str
|
|
6
|
+
@encoding = encoding
|
|
6
7
|
end
|
|
7
8
|
|
|
8
|
-
def readpartial(size = nil)
|
|
9
|
+
def readpartial(size = nil, outbuf = nil)
|
|
9
10
|
unless size
|
|
10
11
|
if defined?(HTTP::Client::BUFFER_SIZE)
|
|
11
12
|
size = HTTP::Client::BUFFER_SIZE
|
|
@@ -14,7 +15,12 @@ module HTTP
|
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
@io.read size
|
|
18
|
+
chunk = @io.read size, outbuf
|
|
19
|
+
chunk.force_encoding(@encoding) if chunk
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def close
|
|
23
|
+
@io.close
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def sequence_id
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
module HTTP
|
|
2
2
|
class WebMockPerform
|
|
3
|
-
def initialize(request, &perform)
|
|
3
|
+
def initialize(request, options, &perform)
|
|
4
4
|
@request = request
|
|
5
|
+
@options = options
|
|
5
6
|
@perform = perform
|
|
6
7
|
@request_signature = nil
|
|
7
8
|
end
|
|
@@ -38,12 +39,15 @@ module HTTP
|
|
|
38
39
|
webmock_response.raise_error_if_any
|
|
39
40
|
|
|
40
41
|
invoke_callbacks(webmock_response, real_request: false)
|
|
41
|
-
::HTTP::Response.from_webmock webmock_response, request_signature
|
|
42
|
+
response = ::HTTP::Response.from_webmock @request, webmock_response, request_signature
|
|
43
|
+
|
|
44
|
+
@options.features.each { |_name, feature| response = feature.wrap_response(response) }
|
|
45
|
+
response
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def raise_timeout_error
|
|
45
49
|
raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0"
|
|
46
|
-
raise HTTP::
|
|
50
|
+
raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}"
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def perform
|
|
@@ -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,15 +71,17 @@ 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)
|
|
75
78
|
elsif block
|
|
76
79
|
body = ''
|
|
77
80
|
do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
if chunk && chunk.bytesize > 0
|
|
82
|
+
body += chunk
|
|
83
|
+
block.call(http_res, chunk)
|
|
84
|
+
end
|
|
80
85
|
end
|
|
81
86
|
else
|
|
82
87
|
do_get_block_without_webmock(req, proxy, conn)
|
|
@@ -98,7 +103,7 @@ if defined?(::HTTPClient)
|
|
|
98
103
|
def do_request_async(method, uri, query, body, extheader)
|
|
99
104
|
req = create_request(method, uri, query, body, extheader)
|
|
100
105
|
request_signature = build_request_signature(req)
|
|
101
|
-
webmock_request_signatures << request_signature
|
|
106
|
+
synchronize_request_response { webmock_request_signatures << request_signature }
|
|
102
107
|
|
|
103
108
|
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
|
|
104
109
|
super
|
|
@@ -117,7 +122,7 @@ if defined?(::HTTPClient)
|
|
|
117
122
|
raise HTTPClient::TimeoutError if webmock_response.should_timeout
|
|
118
123
|
webmock_response.raise_error_if_any
|
|
119
124
|
|
|
120
|
-
block.call(response, body) if block
|
|
125
|
+
block.call(response, body) if block && body && body.bytesize > 0
|
|
121
126
|
|
|
122
127
|
response
|
|
123
128
|
end
|
|
@@ -182,7 +187,9 @@ if defined?(::HTTPClient)
|
|
|
182
187
|
|
|
183
188
|
def webmock_responses
|
|
184
189
|
@webmock_responses ||= Hash.new do |hash, request_signature|
|
|
185
|
-
|
|
190
|
+
synchronize_request_response do
|
|
191
|
+
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
|
|
192
|
+
end
|
|
186
193
|
end
|
|
187
194
|
end
|
|
188
195
|
|
|
@@ -191,8 +198,10 @@ if defined?(::HTTPClient)
|
|
|
191
198
|
end
|
|
192
199
|
|
|
193
200
|
def previous_signature_for(signature)
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
synchronize_request_response do
|
|
202
|
+
return nil unless index = webmock_request_signatures.index(signature)
|
|
203
|
+
webmock_request_signatures.delete_at(index)
|
|
204
|
+
end
|
|
196
205
|
end
|
|
197
206
|
|
|
198
207
|
private
|
|
@@ -207,6 +216,16 @@ if defined?(::HTTPClient)
|
|
|
207
216
|
hdrs
|
|
208
217
|
end
|
|
209
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
|
|
210
229
|
end
|
|
211
230
|
|
|
212
231
|
class WebMockHTTPClient < HTTPClient
|
|
@@ -24,6 +24,12 @@ if defined?(Manticore)
|
|
|
24
24
|
Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
class StubbedTimeoutResponse < Manticore::StubbedResponse
|
|
28
|
+
def call
|
|
29
|
+
@handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)"))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
27
33
|
class WebMockManticoreClient < Manticore::Client
|
|
28
34
|
def request(klass, url, options={}, &block)
|
|
29
35
|
super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
|
|
@@ -50,19 +56,22 @@ if defined?(Manticore)
|
|
|
50
56
|
|
|
51
57
|
if webmock_response = registered_response_for(request_signature)
|
|
52
58
|
webmock_response.raise_error_if_any
|
|
53
|
-
manticore_response = generate_manticore_response(webmock_response)
|
|
54
|
-
|
|
59
|
+
manticore_response = generate_manticore_response(webmock_response)
|
|
60
|
+
manticore_response.on_success do
|
|
61
|
+
WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: false}, request_signature, webmock_response)
|
|
62
|
+
end
|
|
55
63
|
|
|
56
64
|
elsif real_request_allowed?(request_signature.uri)
|
|
57
|
-
manticore_response = Manticore::Response.new(self, request, context, &block)
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
manticore_response = Manticore::Response.new(self, request, context, &block)
|
|
66
|
+
manticore_response.on_complete do |completed_response|
|
|
67
|
+
webmock_response = generate_webmock_response(completed_response)
|
|
68
|
+
WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: true}, request_signature, webmock_response)
|
|
69
|
+
end
|
|
60
70
|
|
|
61
71
|
else
|
|
62
72
|
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
|
63
73
|
end
|
|
64
74
|
|
|
65
|
-
WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: real_request}, request_signature, webmock_response)
|
|
66
75
|
manticore_response
|
|
67
76
|
end
|
|
68
77
|
|
|
@@ -103,21 +112,30 @@ if defined?(Manticore)
|
|
|
103
112
|
end
|
|
104
113
|
|
|
105
114
|
def generate_manticore_response(webmock_response)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
if webmock_response.should_timeout
|
|
116
|
+
StubbedTimeoutResponse.new
|
|
117
|
+
else
|
|
118
|
+
Manticore::StubbedResponse.stub(
|
|
119
|
+
code: webmock_response.status[0],
|
|
120
|
+
body: webmock_response.body,
|
|
121
|
+
headers: webmock_response.headers,
|
|
122
|
+
cookies: {}
|
|
123
|
+
)
|
|
124
|
+
end
|
|
114
125
|
end
|
|
115
126
|
|
|
116
127
|
def generate_webmock_response(manticore_response)
|
|
117
128
|
webmock_response = WebMock::Response.new
|
|
118
129
|
webmock_response.status = [manticore_response.code, manticore_response.message]
|
|
119
|
-
webmock_response.body = manticore_response.body
|
|
120
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
|
+
|
|
121
139
|
webmock_response
|
|
122
140
|
end
|
|
123
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)
|
|
@@ -154,7 +145,7 @@ module WebMock
|
|
|
154
145
|
def build_net_http_response(webmock_response, &block)
|
|
155
146
|
response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
|
|
156
147
|
body = webmock_response.body
|
|
157
|
-
body = nil if
|
|
148
|
+
body = nil if webmock_response.status[0].to_s == '204'
|
|
158
149
|
|
|
159
150
|
response.instance_variable_set(:@body, body)
|
|
160
151
|
webmock_response.headers.to_a.each do |name, values|
|
|
@@ -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,66 +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, read_timeout: 60, continue_timeout: nil, debug_output: nil)
|
|
260
|
-
@read_timeout = read_timeout
|
|
261
|
-
@rbuf = ''
|
|
262
|
-
@debug_output = debug_output
|
|
263
|
-
|
|
264
|
-
@io = case io
|
|
265
|
-
when Socket, OpenSSL::SSL::SSLSocket, IO
|
|
266
|
-
io
|
|
267
|
-
when StringIO
|
|
268
|
-
PatchedStringIO.new(io.string)
|
|
269
|
-
when String
|
|
270
|
-
PatchedStringIO.new(io)
|
|
271
|
-
end
|
|
272
|
-
raise "Unable to create local socket" unless @io
|
|
273
|
-
end
|
|
226
|
+
def io
|
|
227
|
+
@io ||= StubIO.new
|
|
274
228
|
end
|
|
275
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
|
|
276
237
|
end
|
|
277
238
|
|
|
278
|
-
|
|
279
239
|
module WebMock
|
|
280
240
|
module NetHTTPUtility
|
|
281
241
|
|
|
282
242
|
def self.request_signature_from_request(net_http, request, body = nil)
|
|
283
|
-
protocol = net_http.use_ssl? ? "https" : "http"
|
|
284
|
-
|
|
285
243
|
path = request.path
|
|
286
244
|
|
|
287
245
|
if path.respond_to?(:request_uri) #https://github.com/bblimke/webmock/issues/288
|
|
@@ -290,11 +248,10 @@ module WebMock
|
|
|
290
248
|
|
|
291
249
|
path = WebMock::Util::URI.heuristic_parse(path).request_uri if path =~ /^http/
|
|
292
250
|
|
|
293
|
-
uri =
|
|
251
|
+
uri = get_uri(net_http, path)
|
|
294
252
|
method = request.method.downcase.to_sym
|
|
295
253
|
|
|
296
254
|
headers = Hash[*request.to_hash.map {|k,v| [k, v]}.inject([]) {|r,x| r + x}]
|
|
297
|
-
validate_headers(headers)
|
|
298
255
|
|
|
299
256
|
if request.body_stream
|
|
300
257
|
body = request.body_stream.read
|
|
@@ -310,23 +267,13 @@ module WebMock
|
|
|
310
267
|
WebMock::RequestSignature.new(method, uri, body: request.body, headers: headers)
|
|
311
268
|
end
|
|
312
269
|
|
|
313
|
-
def self.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
#
|
|
320
|
-
# This could create a false positive in a test suite with WebMock.
|
|
321
|
-
#
|
|
322
|
-
# So before this point, WebMock raises an ArgumentError if any of the headers are symbols
|
|
323
|
-
# instead of the cryptic NoMethodError "undefined method `split' ...` from Net::HTTP
|
|
324
|
-
if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.3.0')
|
|
325
|
-
header_as_symbol = headers.keys.find {|header| header.is_a? Symbol}
|
|
326
|
-
if header_as_symbol
|
|
327
|
-
raise ArgumentError.new("Net:HTTP does not accept headers as symbols")
|
|
328
|
-
end
|
|
329
|
-
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}"
|
|
330
277
|
end
|
|
331
278
|
|
|
332
279
|
def self.check_right_http_connection
|
|
@@ -69,7 +69,7 @@ if defined?(::Patron)
|
|
|
69
69
|
uri = WebMock::Util::URI.heuristic_parse(req.url)
|
|
70
70
|
uri.path = uri.normalized_path.gsub("[^:]//","/")
|
|
71
71
|
|
|
72
|
-
if [:put, :post].include?(req.action)
|
|
72
|
+
if [:put, :post, :patch].include?(req.action)
|
|
73
73
|
if req.file_name
|
|
74
74
|
if !File.exist?(req.file_name) || !File.readable?(req.file_name)
|
|
75
75
|
raise ArgumentError.new("Unable to open specified file.")
|
|
@@ -106,11 +106,11 @@ if defined?(::Patron)
|
|
|
106
106
|
header_data = ([status_line] + header_fields).join("\r\n")
|
|
107
107
|
|
|
108
108
|
::Patron::Response.new(
|
|
109
|
-
"",
|
|
109
|
+
"".dup,
|
|
110
110
|
webmock_response.status[0],
|
|
111
111
|
0,
|
|
112
112
|
header_data,
|
|
113
|
-
webmock_response.body,
|
|
113
|
+
webmock_response.body.dup,
|
|
114
114
|
default_response_charset
|
|
115
115
|
)
|
|
116
116
|
end
|
|
@@ -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
|