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,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 = {}
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
response_lock = Mutex.new
|
31
|
+
|
32
|
+
stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request|
|
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
|
@@ -161,7 +161,7 @@ module WebMock::Util
|
|
161
161
|
else
|
162
162
|
if array_value
|
163
163
|
current_node[last_key] ||= []
|
164
|
-
current_node[last_key] << value
|
164
|
+
current_node[last_key] << value unless value.nil?
|
165
165
|
else
|
166
166
|
current_node[last_key] = value
|
167
167
|
end
|
@@ -186,7 +186,9 @@ module WebMock::Util
|
|
186
186
|
new_query_values = new_query_values.to_hash
|
187
187
|
new_query_values = new_query_values.inject([]) do |object, (key, value)|
|
188
188
|
key = key.to_s if key.is_a?(::Symbol) || key.nil?
|
189
|
-
if value.is_a?(Array)
|
189
|
+
if value.is_a?(Array) && value.empty?
|
190
|
+
object << [key.to_s + '[]']
|
191
|
+
elsif value.is_a?(Array)
|
190
192
|
value.each { |v| object << [key.to_s + '[]', v] }
|
191
193
|
elsif value.is_a?(Hash)
|
192
194
|
value.each { |k, v| object << ["#{key.to_s}[#{k}]", v]}
|
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
data/lib/webmock/webmock.rb
CHANGED
@@ -53,16 +53,33 @@ module WebMock
|
|
53
53
|
Config.instance.net_http_connect_on_start = options[:net_http_connect_on_start]
|
54
54
|
end
|
55
55
|
|
56
|
+
class << self
|
57
|
+
alias :enable_net_connect! :allow_net_connect!
|
58
|
+
alias :disallow_net_connect! :disable_net_connect!
|
59
|
+
end
|
60
|
+
|
56
61
|
def self.net_connect_allowed?(uri = nil)
|
62
|
+
return !!Config.instance.allow_net_connect if uri.nil?
|
63
|
+
|
57
64
|
if uri.is_a?(String)
|
58
65
|
uri = WebMock::Util::URI.normalize_uri(uri)
|
59
66
|
end
|
60
67
|
|
61
|
-
Config.instance.allow_net_connect ||
|
68
|
+
!!Config.instance.allow_net_connect ||
|
62
69
|
( Config.instance.allow_localhost && WebMock::Util::URI.is_uri_localhost?(uri) ||
|
63
70
|
Config.instance.allow && net_connect_explicit_allowed?(Config.instance.allow, uri) )
|
64
71
|
end
|
65
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
|
+
|
66
83
|
def self.net_connect_explicit_allowed?(allowed, uri=nil)
|
67
84
|
case allowed
|
68
85
|
when Array
|
@@ -133,8 +150,8 @@ module WebMock
|
|
133
150
|
puts WebMock::RequestExecutionVerifier.executed_requests_message
|
134
151
|
end
|
135
152
|
|
136
|
-
def self.globally_stub_request(&block)
|
137
|
-
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)
|
138
155
|
end
|
139
156
|
|
140
157
|
%w(
|
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'
|
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
|
@@ -0,0 +1,375 @@
|
|
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
|
+
it 'does not invoke "after real request" callbacks for stubbed requests' do
|
139
|
+
WebMock.allow_net_connect!
|
140
|
+
stub_request(:get, 'http://www.example.com').to_return(body: 'abc')
|
141
|
+
|
142
|
+
callback_invoked = false
|
143
|
+
WebMock.after_request(real_requests_only: true) { |_| callback_invoked = true }
|
144
|
+
|
145
|
+
make_request(:get, 'http://www.example.com')
|
146
|
+
expect(callback_invoked).to eq(false)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'does invoke "after request" callbacks for stubbed requests' do
|
150
|
+
WebMock.allow_net_connect!
|
151
|
+
stub_request(:get, 'http://www.example.com').to_return(body: 'abc')
|
152
|
+
|
153
|
+
callback_invoked = false
|
154
|
+
WebMock.after_request(real_requests_only: false) { |_| callback_invoked = true }
|
155
|
+
|
156
|
+
make_request(:get, 'http://www.example.com')
|
157
|
+
expect(callback_invoked).to eq(true)
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'scheme and protocol' do
|
161
|
+
let(:default_response_headers) { {} }
|
162
|
+
|
163
|
+
before do
|
164
|
+
stub_request(
|
165
|
+
:get, "#{scheme}://www.example.com"
|
166
|
+
).and_return(
|
167
|
+
body: 'BODY'
|
168
|
+
)
|
169
|
+
end
|
170
|
+
|
171
|
+
subject do
|
172
|
+
make_request(:get, "#{scheme}://www.example.com", protocol: protocol)
|
173
|
+
end
|
174
|
+
|
175
|
+
shared_examples :common do
|
176
|
+
specify do
|
177
|
+
expect(subject).to eq(
|
178
|
+
status: 200,
|
179
|
+
headers: default_response_headers,
|
180
|
+
body: 'BODY'
|
181
|
+
)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'http scheme' do
|
186
|
+
let(:scheme) { 'http' }
|
187
|
+
|
188
|
+
context 'default protocol' do
|
189
|
+
let(:protocol) { nil }
|
190
|
+
|
191
|
+
include_examples :common
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'HTTP10 protocol' do
|
195
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP10 }
|
196
|
+
let(:default_response_headers) { {"connection"=>["keep-alive"]} }
|
197
|
+
|
198
|
+
include_examples :common
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'HTTP11 protocol' do
|
202
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP11 }
|
203
|
+
|
204
|
+
include_examples :common
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'HTTP2 protocol' do
|
208
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
209
|
+
|
210
|
+
include_examples :common
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'https scheme' do
|
215
|
+
let(:scheme) { 'https' }
|
216
|
+
|
217
|
+
context 'default protocol' do
|
218
|
+
let(:protocol) { nil }
|
219
|
+
|
220
|
+
include_examples :common
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'HTTP10 protocol' do
|
224
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP10 }
|
225
|
+
let(:default_response_headers) { {"connection"=>["keep-alive"]} }
|
226
|
+
|
227
|
+
include_examples :common
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'HTTP11 protocol' do
|
231
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP11 }
|
232
|
+
|
233
|
+
include_examples :common
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'HTTP2 protocol' do
|
237
|
+
let(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
238
|
+
|
239
|
+
include_examples :common
|
240
|
+
end
|
241
|
+
|
242
|
+
context 'HTTPS protocol' do
|
243
|
+
let(:protocol) { Async::HTTP::Protocol::HTTPS }
|
244
|
+
|
245
|
+
include_examples :common
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'multiple requests' do
|
251
|
+
let!(:endpoint) { Async::HTTP::Endpoint.parse('http://www.example.com') }
|
252
|
+
let(:requests_count) { 3 }
|
253
|
+
|
254
|
+
shared_examples :common do
|
255
|
+
before do
|
256
|
+
requests_count.times do |index|
|
257
|
+
stub_request(
|
258
|
+
:get, "http://www.example.com/foo#{index}"
|
259
|
+
).to_return(
|
260
|
+
status: 200 + index,
|
261
|
+
headers: {'X-Token' => "foo#{index}"},
|
262
|
+
body: "FOO#{index}"
|
263
|
+
)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
specify do
|
268
|
+
expect(subject).to eq(
|
269
|
+
0 => {
|
270
|
+
status: 200,
|
271
|
+
headers: {'x-token' => ['foo0']},
|
272
|
+
body: 'FOO0'
|
273
|
+
},
|
274
|
+
1 => {
|
275
|
+
status: 201,
|
276
|
+
headers: {'x-token' => ['foo1']},
|
277
|
+
body: 'FOO1'
|
278
|
+
},
|
279
|
+
2 => {
|
280
|
+
status: 202,
|
281
|
+
headers: {'x-token' => ['foo2']},
|
282
|
+
body: 'FOO2'
|
283
|
+
}
|
284
|
+
)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context 'sequential' do
|
289
|
+
subject do
|
290
|
+
responses = {}
|
291
|
+
Async do |task|
|
292
|
+
Async::HTTP::Client.open(endpoint, protocol) do |client|
|
293
|
+
requests_count.times do |index|
|
294
|
+
response = client.get "/foo#{index}"
|
295
|
+
responses[index] = response_to_hash(response)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
responses
|
300
|
+
end
|
301
|
+
|
302
|
+
context 'HTTP1 protocol' do
|
303
|
+
let!(:protocol) { Async::HTTP::Protocol::HTTP1 }
|
304
|
+
|
305
|
+
include_examples :common
|
306
|
+
end
|
307
|
+
|
308
|
+
context 'HTTP2 protocol' do
|
309
|
+
let!(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
310
|
+
|
311
|
+
include_examples :common
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context 'asynchronous' do
|
316
|
+
subject do
|
317
|
+
responses = {}
|
318
|
+
Async do |task|
|
319
|
+
Async::HTTP::Client.open(endpoint, protocol) do |client|
|
320
|
+
tasks = requests_count.times.map do |index|
|
321
|
+
task.async do
|
322
|
+
response = client.get "/foo#{index}"
|
323
|
+
responses[index] = response_to_hash(response)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
tasks.map(&:wait)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
responses
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'HTTP1 protocol' do
|
334
|
+
let!(:protocol) { Async::HTTP::Protocol::HTTP1 }
|
335
|
+
|
336
|
+
include_examples :common
|
337
|
+
end
|
338
|
+
|
339
|
+
context 'HTTP2 protocol' do
|
340
|
+
let!(:protocol) { Async::HTTP::Protocol::HTTP2 }
|
341
|
+
|
342
|
+
include_examples :common
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def make_request(method, url, protocol: nil, headers: {}, body: nil)
|
348
|
+
Async do
|
349
|
+
endpoint = Async::HTTP::Endpoint.parse(url)
|
350
|
+
|
351
|
+
begin
|
352
|
+
Async::HTTP::Client.open(endpoint, protocol || endpoint.protocol) do |client|
|
353
|
+
response = client.send(
|
354
|
+
method,
|
355
|
+
endpoint.path,
|
356
|
+
headers,
|
357
|
+
body
|
358
|
+
)
|
359
|
+
response_to_hash(response)
|
360
|
+
end
|
361
|
+
rescue Async::TimeoutError => e
|
362
|
+
e
|
363
|
+
end
|
364
|
+
end.wait
|
365
|
+
end
|
366
|
+
|
367
|
+
def response_to_hash(response)
|
368
|
+
{
|
369
|
+
status: response.status,
|
370
|
+
headers: response.headers.to_h,
|
371
|
+
body: response.read
|
372
|
+
}
|
373
|
+
end
|
374
|
+
end
|
375
|
+
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
|