webmock 3.5.1 → 3.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +15 -12
- data/CHANGELOG.md +211 -0
- data/README.md +92 -26
- data/Rakefile +0 -2
- data/lib/webmock.rb +1 -0
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +214 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +10 -1
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +1 -1
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +11 -1
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +25 -14
- data/lib/webmock/http_lib_adapters/net_http.rb +35 -17
- 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 +76 -48
- data/lib/webmock/response.rb +11 -5
- 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 +10 -3
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +353 -0
- data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
- data/spec/acceptance/curb/curb_spec.rb +23 -5
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
- 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 +19 -0
- data/spec/acceptance/net_http/net_http_spec.rb +12 -0
- data/spec/acceptance/shared/callbacks.rb +2 -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 +119 -3
- data/spec/unit/response_spec.rb +22 -18
- 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 +54 -5
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +9 -2
- metadata +39 -10
data/Rakefile
CHANGED
data/lib/webmock.rb
CHANGED
@@ -54,5 +54,6 @@ require_relative 'webmock/http_lib_adapters/em_http_request_adapter'
|
|
54
54
|
require_relative 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
|
55
55
|
require_relative 'webmock/http_lib_adapters/excon_adapter'
|
56
56
|
require_relative 'webmock/http_lib_adapters/manticore_adapter'
|
57
|
+
require_relative 'webmock/http_lib_adapters/async_http_client_adapter'
|
57
58
|
|
58
59
|
require_relative 'webmock/webmock'
|
@@ -0,0 +1,214 @@
|
|
1
|
+
begin
|
2
|
+
require 'async'
|
3
|
+
require 'async/http'
|
4
|
+
rescue LoadError
|
5
|
+
# async-http not found
|
6
|
+
end
|
7
|
+
|
8
|
+
if defined?(Async::HTTP)
|
9
|
+
module WebMock
|
10
|
+
module HttpLibAdapters
|
11
|
+
class AsyncHttpClientAdapter < HttpLibAdapter
|
12
|
+
adapter_for :async_http_client
|
13
|
+
|
14
|
+
OriginalAsyncHttpClient = Async::HTTP::Client unless const_defined?(:OriginalAsyncHttpClient)
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def enable!
|
18
|
+
Async::HTTP.send(:remove_const, :Client)
|
19
|
+
Async::HTTP.send(:const_set, :Client, Async::HTTP::WebMockClientWrapper)
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable!
|
23
|
+
Async::HTTP.send(:remove_const, :Client)
|
24
|
+
Async::HTTP.send(:const_set, :Client, OriginalAsyncHttpClient)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Async
|
32
|
+
module HTTP
|
33
|
+
class WebMockClientWrapper < Client
|
34
|
+
def initialize(
|
35
|
+
endpoint,
|
36
|
+
protocol = endpoint.protocol,
|
37
|
+
scheme = endpoint.scheme,
|
38
|
+
authority = endpoint.authority,
|
39
|
+
**options
|
40
|
+
)
|
41
|
+
webmock_endpoint = WebMockEndpoint.new(scheme, authority, protocol)
|
42
|
+
|
43
|
+
@network_client = WebMockClient.new(endpoint, protocol, scheme, authority, **options)
|
44
|
+
@webmock_client = WebMockClient.new(webmock_endpoint, protocol, scheme, authority, **options)
|
45
|
+
|
46
|
+
@scheme = scheme
|
47
|
+
@authority = authority
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(request)
|
51
|
+
request.scheme ||= self.scheme
|
52
|
+
request.authority ||= self.authority
|
53
|
+
|
54
|
+
request_signature = build_request_signature(request)
|
55
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
56
|
+
webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
|
57
|
+
net_connect_allowed = WebMock.net_connect_allowed?(request_signature.uri)
|
58
|
+
|
59
|
+
if webmock_response
|
60
|
+
webmock_response.raise_error_if_any
|
61
|
+
raise Async::TimeoutError, 'WebMock timeout error' if webmock_response.should_timeout
|
62
|
+
WebMockApplication.add_webmock_response(request, webmock_response)
|
63
|
+
response = @webmock_client.call(request)
|
64
|
+
elsif net_connect_allowed
|
65
|
+
response = @network_client.call(request)
|
66
|
+
else
|
67
|
+
raise WebMock::NetConnectNotAllowedError.new(request_signature) unless webmock_response
|
68
|
+
end
|
69
|
+
|
70
|
+
if WebMock::CallbackRegistry.any_callbacks?
|
71
|
+
webmock_response ||= build_webmock_response(response)
|
72
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
73
|
+
{
|
74
|
+
lib: :async_http_client,
|
75
|
+
real_request: net_connect_allowed
|
76
|
+
},
|
77
|
+
request_signature,
|
78
|
+
webmock_response
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
response
|
83
|
+
end
|
84
|
+
|
85
|
+
def close
|
86
|
+
@network_client.close
|
87
|
+
@webmock_client.close
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def build_request_signature(request)
|
93
|
+
body = request.read
|
94
|
+
request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
|
95
|
+
WebMock::RequestSignature.new(
|
96
|
+
request.method.downcase.to_sym,
|
97
|
+
"#{request.scheme}://#{request.authority}#{request.path}",
|
98
|
+
headers: request.headers.to_h,
|
99
|
+
body: body
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_webmock_response(response)
|
104
|
+
body = response.read
|
105
|
+
response.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
|
106
|
+
|
107
|
+
webmock_response = WebMock::Response.new
|
108
|
+
webmock_response.status = [
|
109
|
+
response.status,
|
110
|
+
::Protocol::HTTP1::Reason::DESCRIPTIONS[response.status]
|
111
|
+
]
|
112
|
+
webmock_response.headers = build_webmock_response_headers(response)
|
113
|
+
webmock_response.body = body
|
114
|
+
webmock_response
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_webmock_response_headers(response)
|
118
|
+
response.headers.each.each_with_object({}) do |(k, v), o|
|
119
|
+
o[k] ||= []
|
120
|
+
o[k] << v
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class WebMockClient < Client
|
126
|
+
end
|
127
|
+
|
128
|
+
class WebMockEndpoint
|
129
|
+
def initialize(scheme, authority, protocol)
|
130
|
+
@scheme = scheme
|
131
|
+
@authority = authority
|
132
|
+
@protocol = protocol
|
133
|
+
end
|
134
|
+
|
135
|
+
attr :scheme, :authority, :protocol
|
136
|
+
|
137
|
+
def connect
|
138
|
+
server_socket, client_socket = create_connected_sockets
|
139
|
+
Async(transient: true) do
|
140
|
+
accept_socket(server_socket)
|
141
|
+
end
|
142
|
+
client_socket
|
143
|
+
end
|
144
|
+
|
145
|
+
def inspect
|
146
|
+
"\#<#{self.class}> #{scheme}://#{authority} protocol=#{protocol}"
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def create_connected_sockets
|
152
|
+
Async::IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM).tap do |sockets|
|
153
|
+
sockets.each do |socket|
|
154
|
+
socket.instance_variable_set :@alpn_protocol, nil
|
155
|
+
socket.instance_eval do
|
156
|
+
def alpn_protocol
|
157
|
+
nil # means HTTP11 will be used for HTTPS
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def accept_socket(socket)
|
165
|
+
server = Async::HTTP::Server.new(WebMockApplication, self)
|
166
|
+
server.accept(socket, socket.remote_address)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
module WebMockApplication
|
171
|
+
WEBMOCK_REQUEST_ID_HEADER = 'x-webmock-request-id'.freeze
|
172
|
+
|
173
|
+
class << self
|
174
|
+
def call(request)
|
175
|
+
request.read
|
176
|
+
webmock_response = get_webmock_response(request)
|
177
|
+
build_response(webmock_response)
|
178
|
+
end
|
179
|
+
|
180
|
+
def add_webmock_response(request, webmock_response)
|
181
|
+
webmock_request_id = request.object_id.to_s
|
182
|
+
request.headers.add(WEBMOCK_REQUEST_ID_HEADER, webmock_request_id)
|
183
|
+
webmock_responses[webmock_request_id] = webmock_response
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_webmock_response(request)
|
187
|
+
webmock_request_id = request.headers[WEBMOCK_REQUEST_ID_HEADER][0]
|
188
|
+
webmock_responses.fetch(webmock_request_id)
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def webmock_responses
|
194
|
+
@webmock_responses ||= {}
|
195
|
+
end
|
196
|
+
|
197
|
+
def build_response(webmock_response)
|
198
|
+
headers = (webmock_response.headers || {}).each_with_object([]) do |(k, value), o|
|
199
|
+
Array(value).each do |v|
|
200
|
+
o.push [k, v]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
::Protocol::HTTP::Response[
|
205
|
+
webmock_response.status[0],
|
206
|
+
headers,
|
207
|
+
webmock_response.body
|
208
|
+
]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -54,7 +54,6 @@ if defined?(Curl)
|
|
54
54
|
|
55
55
|
module Curl
|
56
56
|
class WebMockCurlEasy < Curl::Easy
|
57
|
-
|
58
57
|
def curb_or_webmock
|
59
58
|
request_signature = build_request_signature
|
60
59
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
@@ -271,6 +270,8 @@ if defined?(Curl)
|
|
271
270
|
def perform
|
272
271
|
@webmock_method ||= :get
|
273
272
|
curb_or_webmock { super }
|
273
|
+
ensure
|
274
|
+
reset_webmock_method
|
274
275
|
end
|
275
276
|
|
276
277
|
def put_data= data
|
@@ -333,8 +334,16 @@ if defined?(Curl)
|
|
333
334
|
METHOD
|
334
335
|
end
|
335
336
|
|
337
|
+
def reset_webmock_method
|
338
|
+
@webmock_method = :get
|
339
|
+
end
|
340
|
+
|
336
341
|
def reset
|
337
342
|
instance_variable_set(:@body_str, nil)
|
343
|
+
instance_variable_set(:@content_type, nil)
|
344
|
+
instance_variable_set(:@header_str, nil)
|
345
|
+
instance_variable_set(:@last_effective_url, nil)
|
346
|
+
instance_variable_set(:@response_code, nil)
|
338
347
|
super
|
339
348
|
end
|
340
349
|
end
|
@@ -107,7 +107,7 @@ if defined?(EventMachine::HttpClient)
|
|
107
107
|
@uri ||= nil
|
108
108
|
EM.next_tick {
|
109
109
|
setup(make_raw_response(stubbed_webmock_response), @uri,
|
110
|
-
stubbed_webmock_response.should_timeout ?
|
110
|
+
stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
|
111
111
|
}
|
112
112
|
self
|
113
113
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
@@ -4,7 +4,10 @@ module HTTP
|
|
4
4
|
|
5
5
|
def perform(request, options)
|
6
6
|
return __perform__(request, options) unless webmock_enabled?
|
7
|
-
|
7
|
+
|
8
|
+
response = WebMockPerform.new(request) { __perform__(request, options) }.exec
|
9
|
+
options.features.each { |_name, feature| response = feature.wrap_response(response) }
|
10
|
+
response
|
8
11
|
end
|
9
12
|
|
10
13
|
def webmock_enabled?
|
@@ -14,9 +14,19 @@ module HTTP
|
|
14
14
|
def from_webmock(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(Streamer.new(webmock_response.body), encoding: webmock_response.body.encoding)
|
28
|
+
end
|
29
|
+
|
20
30
|
return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
|
21
31
|
|
22
32
|
new({
|
@@ -5,7 +5,7 @@ module HTTP
|
|
5
5
|
@io = StringIO.new str
|
6
6
|
end
|
7
7
|
|
8
|
-
def readpartial(size = nil)
|
8
|
+
def readpartial(size = nil, outbuf = nil)
|
9
9
|
unless size
|
10
10
|
if defined?(HTTP::Client::BUFFER_SIZE)
|
11
11
|
size = HTTP::Client::BUFFER_SIZE
|
@@ -14,7 +14,7 @@ module HTTP
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
@io.read size
|
17
|
+
@io.read size, outbuf
|
18
18
|
end
|
19
19
|
|
20
20
|
def close
|
@@ -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
|
@@ -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,14 +112,16 @@ 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)
|