webmock 3.6.0 → 3.7.3
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 +3 -2
- data/CHANGELOG.md +56 -0
- data/README.md +18 -0
- 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/em_http_request_adapter.rb +1 -1
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +25 -14
- data/lib/webmock/http_lib_adapters/net_http.rb +3 -2
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +1 -1
- data/lib/webmock/request_pattern.rb +1 -2
- data/lib/webmock/util/uri.rb +8 -8
- data/lib/webmock/version.rb +1 -1
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +349 -0
- data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
- data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
- data/spec/acceptance/manticore/manticore_spec.rb +19 -0
- data/spec/acceptance/shared/callbacks.rb +2 -1
- data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
- data/spec/support/webmock_server.rb +1 -0
- data/spec/unit/request_pattern_spec.rb +5 -0
- data/spec/unit/util/uri_spec.rb +74 -2
- data/webmock.gemspec +8 -1
- metadata +27 -3
@@ -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
|
@@ -183,8 +183,7 @@ module WebMock
|
|
183
183
|
def matches_with_variations?(uri)
|
184
184
|
normalized_template = Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
|
185
185
|
|
186
|
-
WebMock::Util::URI.variations_of_uri_as_strings(uri
|
187
|
-
.any? { |u| normalized_template.match(u) }
|
186
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| normalized_template.match(u) }
|
188
187
|
end
|
189
188
|
end
|
190
189
|
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -41,12 +41,12 @@ module WebMock
|
|
41
41
|
uris = uris_with_trailing_slash_and_without(uris)
|
42
42
|
end
|
43
43
|
|
44
|
-
uris = uris_encoded_and_unencoded(uris)
|
45
|
-
|
46
44
|
if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]
|
47
45
|
uris = uris_with_inferred_port_and_without(uris)
|
48
46
|
end
|
49
47
|
|
48
|
+
uris = uris_encoded_and_unencoded(uris)
|
49
|
+
|
50
50
|
if normalized_uri.scheme == "http" && !only_with_scheme
|
51
51
|
uris = uris_with_scheme_and_without(uris)
|
52
52
|
end
|
@@ -80,27 +80,27 @@ module WebMock
|
|
80
80
|
|
81
81
|
def self.uris_with_inferred_port_and_without(uris)
|
82
82
|
uris.map { |uri|
|
83
|
-
|
84
|
-
[ uri, uri.gsub(%r{(:80)|(:443)}, "").freeze ]
|
83
|
+
[ uri, uri.omit(:port)]
|
85
84
|
}.flatten
|
86
85
|
end
|
87
86
|
|
88
87
|
def self.uris_encoded_and_unencoded(uris)
|
89
88
|
uris.map do |uri|
|
90
|
-
[
|
89
|
+
[
|
90
|
+
uri.to_s.force_encoding(Encoding::ASCII_8BIT),
|
91
|
+
Addressable::URI.unencode(uri, String).force_encoding(Encoding::ASCII_8BIT).freeze
|
92
|
+
]
|
91
93
|
end.flatten
|
92
94
|
end
|
93
95
|
|
94
96
|
def self.uris_with_scheme_and_without(uris)
|
95
97
|
uris.map { |uri|
|
96
|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
|
97
98
|
[ uri, uri.gsub(%r{^https?://},"").freeze ]
|
98
99
|
}.flatten
|
99
100
|
end
|
100
101
|
|
101
102
|
def self.uris_with_trailing_slash_and_without(uris)
|
102
|
-
uris
|
103
|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
|
103
|
+
uris.map { |uri|
|
104
104
|
[ uri, uri.omit(:path).freeze ]
|
105
105
|
}.flatten
|
106
106
|
end
|
data/lib/webmock/version.rb
CHANGED
@@ -0,0 +1,349 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'acceptance/webmock_shared'
|
4
|
+
require_relative './async_http_client_spec_helper'
|
5
|
+
|
6
|
+
require 'protocol/http/body/file'
|
7
|
+
|
8
|
+
Async.logger.debug! if ENV['ASYNC_LOGGER_DEBUG']
|
9
|
+
|
10
|
+
unless RUBY_PLATFORM =~ /java/
|
11
|
+
describe 'Async::HTTP::Client' do
|
12
|
+
include AsyncHttpClientSpecHelper
|
13
|
+
|
14
|
+
include_context "with WebMock",
|
15
|
+
:no_status_message,
|
16
|
+
:no_url_auth,
|
17
|
+
:no_content_length_header
|
18
|
+
|
19
|
+
it 'works' do
|
20
|
+
stub_request(:get, 'http://www.example.com')
|
21
|
+
expect(make_request(:get, 'http://www.example.com')).to eq(
|
22
|
+
status: 200,
|
23
|
+
headers: {},
|
24
|
+
body: nil
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'works with request path' do
|
29
|
+
stub_request(:get, 'http://www.example.com/foo')
|
30
|
+
expect(make_request(:get, 'http://www.example.com/foo')).to eq(
|
31
|
+
status: 200,
|
32
|
+
headers: {},
|
33
|
+
body: nil
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'works with request query' do
|
38
|
+
stub_request(:get, 'http://www.example.com/').with(
|
39
|
+
query: {
|
40
|
+
'foo' => 'bar'
|
41
|
+
}
|
42
|
+
)
|
43
|
+
expect(make_request(:get, 'http://www.example.com/?foo=bar')).to eq(
|
44
|
+
status: 200,
|
45
|
+
headers: {},
|
46
|
+
body: nil
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'works with request headers' do
|
51
|
+
stub_request(:get, 'http://www.example.com').with(
|
52
|
+
headers: {
|
53
|
+
'X-Token' => 'Token'
|
54
|
+
}
|
55
|
+
)
|
56
|
+
expect(
|
57
|
+
make_request :get, 'http://www.example.com',
|
58
|
+
headers: {
|
59
|
+
'X-Token' => 'Token'
|
60
|
+
}
|
61
|
+
).to eq(
|
62
|
+
status: 200,
|
63
|
+
headers: {},
|
64
|
+
body: nil
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'works with request body as text' do
|
69
|
+
stub_request(:post, 'http://www.example.com').with(
|
70
|
+
body: 'x'*10_000
|
71
|
+
)
|
72
|
+
expect(
|
73
|
+
make_request :post, 'http://www.example.com',
|
74
|
+
body: 'x'*10_000
|
75
|
+
).to eq(
|
76
|
+
status: 200,
|
77
|
+
headers: {},
|
78
|
+
body: nil
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'works with request body as file' do
|
83
|
+
stub_request(:post, "www.example.com").with(
|
84
|
+
body: File.read(__FILE__)
|
85
|
+
)
|
86
|
+
expect(
|
87
|
+
make_request :post, "http://www.example.com",
|
88
|
+
body: ::Protocol::HTTP::Body::File.open(__FILE__, block_size: 32)
|
89
|
+
).to eq(
|
90
|
+
status: 200,
|
91
|
+
headers: {},
|
92
|
+
body: nil
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'works with response status' do
|
97
|
+
stub_request(:get, 'http://www.example.com').to_return(
|
98
|
+
status: 400
|
99
|
+
)
|
100
|
+
expect(make_request(:get, 'http://www.example.com')).to eq(
|
101
|
+
status: 400,
|
102
|
+
headers: {},
|
103
|
+
body: nil
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'works with response headers' do
|
108
|
+
stub_request(:get, 'http://www.example.com').to_return(
|
109
|
+
headers: {
|
110
|
+
'X-Token' => 'TOKEN'
|
111
|
+
}
|
112
|
+
)
|
113
|
+
expect(make_request(:get, 'http://www.example.com')).to eq(
|
114
|
+
status: 200,
|
115
|
+
headers: {
|
116
|
+
'x-token' => ['TOKEN']
|
117
|
+
},
|
118
|
+
body: nil
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'works with response body' do
|
123
|
+
stub_request(:get, 'http://www.example.com').to_return(
|
124
|
+
body: 'abc'
|
125
|
+
)
|
126
|
+
expect(make_request(:get, 'http://www.example.com')).to eq(
|
127
|
+
status: 200,
|
128
|
+
headers: {},
|
129
|
+
body: 'abc'
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'works with to_timeout' do
|
134
|
+
stub_request(:get, 'http://www.example.com').to_timeout
|
135
|
+
expect { make_request(:get, 'http://www.example.com') }.to raise_error Async::TimeoutError
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'scheme and protocol' do
|
139
|
+
before do
|
140
|
+
stub_request(
|
141
|
+
:get, "#{scheme}://www.example.com"
|
142
|
+
).and_return(
|
143
|
+
body: 'BODY'
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
subject do
|
148
|
+
make_request(:get, "#{scheme}://www.example.com", protocol: protocol)
|
149
|
+
end
|
150
|
+
|
151
|
+
shared_examples :common do
|
152
|
+
specify do
|
153
|
+
expect(subject).to eq(
|
154
|
+
status: 200,
|
155
|
+
headers: {},
|
156
|
+
body: 'BODY'
|
157
|
+
)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'http scheme' do
|
162
|
+
let(:scheme) { 'http' }
|
163
|
+
|
164
|
+
context 'default protocol' do
|
165
|
+
let(:protocol) { nil }
|
166
|
+
|
167
|
+
include_examples :common
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'HTTP10 protocol' do
|
171
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP10 }
|
172
|
+
|
173
|
+
include_examples :common
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'HTTP11 protocol' do
|
177
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP11 }
|
178
|
+
|
179
|
+
include_examples :common
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'HTTP2 protocol' do
|
183
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
184
|
+
|
185
|
+
include_examples :common
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'https scheme' do
|
190
|
+
let(:scheme) { 'https' }
|
191
|
+
|
192
|
+
context 'default protocol' do
|
193
|
+
let(:protocol) { nil }
|
194
|
+
|
195
|
+
include_examples :common
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'HTTP10 protocol' do
|
199
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP10 }
|
200
|
+
|
201
|
+
include_examples :common
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'HTTP11 protocol' do
|
205
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP11 }
|
206
|
+
|
207
|
+
include_examples :common
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'HTTP2 protocol' do
|
211
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
212
|
+
|
213
|
+
include_examples :common
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'HTTPS protocol' do
|
217
|
+
let(:protocol) { Async::HTTP::Protocol::HTTPS }
|
218
|
+
|
219
|
+
include_examples :common
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'multiple requests' do
|
225
|
+
let(:endpoint) { Async::HTTP::Endpoint.parse('http://www.example.com') }
|
226
|
+
let(:requests_count) { 3 }
|
227
|
+
|
228
|
+
shared_examples :common do
|
229
|
+
before do
|
230
|
+
requests_count.times do |index|
|
231
|
+
stub_request(
|
232
|
+
:get, "http://www.example.com/foo#{index}"
|
233
|
+
).to_return(
|
234
|
+
status: 200 + index,
|
235
|
+
headers: {'X-Token' => "foo#{index}"},
|
236
|
+
body: "FOO#{index}"
|
237
|
+
)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
specify do
|
242
|
+
expect(subject).to eq(
|
243
|
+
0 => {
|
244
|
+
status: 200,
|
245
|
+
headers: {'x-token' => ['foo0']},
|
246
|
+
body: 'FOO0'
|
247
|
+
},
|
248
|
+
1 => {
|
249
|
+
status: 201,
|
250
|
+
headers: {'x-token' => ['foo1']},
|
251
|
+
body: 'FOO1'
|
252
|
+
},
|
253
|
+
2 => {
|
254
|
+
status: 202,
|
255
|
+
headers: {'x-token' => ['foo2']},
|
256
|
+
body: 'FOO2'
|
257
|
+
}
|
258
|
+
)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'sequential' do
|
263
|
+
subject do
|
264
|
+
responses = {}
|
265
|
+
Async do |task|
|
266
|
+
Async::HTTP::Client.open(endpoint, protocol) do |client|
|
267
|
+
requests_count.times do |index|
|
268
|
+
response = client.get "/foo#{index}"
|
269
|
+
responses[index] = response_to_hash(response)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
responses
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'HTTP1 protocol' do
|
277
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP1 }
|
278
|
+
|
279
|
+
include_examples :common
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'HTTP2 protocol' do
|
283
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
284
|
+
|
285
|
+
include_examples :common
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context 'asynchronous' do
|
290
|
+
subject do
|
291
|
+
responses = {}
|
292
|
+
Async do |task|
|
293
|
+
Async::HTTP::Client.open(endpoint, protocol) do |client|
|
294
|
+
tasks = requests_count.times.map do |index|
|
295
|
+
task.async do
|
296
|
+
response = client.get "/foo#{index}"
|
297
|
+
responses[index] = response_to_hash(response)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
tasks.map(&:wait)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
responses
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'HTTP1 protocol' do
|
308
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP1 }
|
309
|
+
|
310
|
+
include_examples :common
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'HTTP2 protocol' do
|
314
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
315
|
+
|
316
|
+
include_examples :common
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def make_request(method, url, protocol: nil, headers: {}, body: nil)
|
322
|
+
Async do
|
323
|
+
endpoint = Async::HTTP::Endpoint.parse(url)
|
324
|
+
|
325
|
+
begin
|
326
|
+
Async::HTTP::Client.open(endpoint, protocol || endpoint.protocol) do |client|
|
327
|
+
response = client.send(
|
328
|
+
method,
|
329
|
+
endpoint.path,
|
330
|
+
headers,
|
331
|
+
body
|
332
|
+
)
|
333
|
+
response_to_hash(response)
|
334
|
+
end
|
335
|
+
rescue Async::TimeoutError => e
|
336
|
+
e
|
337
|
+
end
|
338
|
+
end.wait
|
339
|
+
end
|
340
|
+
|
341
|
+
def response_to_hash(response)
|
342
|
+
{
|
343
|
+
status: response.status,
|
344
|
+
headers: response.headers.to_h,
|
345
|
+
body: response.read
|
346
|
+
}
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AsyncHttpClientSpecHelper
|
2
|
+
def http_request(method, url, options = {}, &block)
|
3
|
+
endpoint = Async::HTTP::Endpoint.parse(url)
|
4
|
+
|
5
|
+
path = endpoint.path
|
6
|
+
path = path + "?" + options[:query] if options[:query]
|
7
|
+
|
8
|
+
headers = (options[:headers] || {}).each_with_object([]) do |(k, v), o|
|
9
|
+
Array(v).each do |v|
|
10
|
+
o.push [k, v]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
headers.push(
|
14
|
+
['authorization', 'Basic ' + Base64.strict_encode64(options[:basic_auth].join(':'))]
|
15
|
+
) if options[:basic_auth]
|
16
|
+
|
17
|
+
body = options[:body]
|
18
|
+
|
19
|
+
Async do
|
20
|
+
begin
|
21
|
+
Async::HTTP::Client.open(endpoint) do |client|
|
22
|
+
response = client.send(
|
23
|
+
method,
|
24
|
+
path,
|
25
|
+
headers,
|
26
|
+
body
|
27
|
+
)
|
28
|
+
|
29
|
+
OpenStruct.new(
|
30
|
+
build_hash_response(response)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
rescue Exception => e
|
34
|
+
e
|
35
|
+
end
|
36
|
+
end.wait
|
37
|
+
end
|
38
|
+
|
39
|
+
def client_timeout_exception_class
|
40
|
+
Async::TimeoutError
|
41
|
+
end
|
42
|
+
|
43
|
+
def connection_refused_exception_class
|
44
|
+
Errno::ECONNREFUSED
|
45
|
+
end
|
46
|
+
|
47
|
+
def http_library
|
48
|
+
:async_http_client
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def build_hash_response(response)
|
54
|
+
{
|
55
|
+
|
56
|
+
status: response.status.to_s,
|
57
|
+
message: Protocol::HTTP1::Reason::DESCRIPTIONS[response.status],
|
58
|
+
headers: build_response_headers(response),
|
59
|
+
body: response.read
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_response_headers(response)
|
64
|
+
response.headers.each.each_with_object({}) do |(k, v), o|
|
65
|
+
o[k] ||= []
|
66
|
+
o[k] << v
|
67
|
+
end.tap do |o|
|
68
|
+
o.each do |k, v|
|
69
|
+
o[k] = v.join(', ')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|