webmock 3.6.2 → 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/CHANGELOG.md +36 -0
- data/README.md +7 -1
- 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 +16 -8
- data/lib/webmock/util/uri.rb +5 -4
- 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/unit/util/uri_spec.rb +10 -0
- data/webmock.gemspec +9 -0
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6023ac450eb0fc7d307ffd764f811e9d409e28fdfabdb215bec15d4e4ea7a363
|
4
|
+
data.tar.gz: 2417fb3aecf50a9b48dc84ee1eb55da9493cff52c68c8a292e0d9215f1c52379
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a83cdbc53a9c75e6d4db3f01e727cc2cb27fb8097dfbad2cd36b39e263bfdc570f60b1c2b924845f81bff2c89dbd91758061c830293c86d0f3a2ba2bb01d619a
|
7
|
+
data.tar.gz: 07cc56e80c126f76ecd53a00b218e80c0a07760d478b1d8354d85f7057828256f4dcc2e857312c83a2bfadbdbcfda0bb20c82510949cabdb24fd3f50657fffbb
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,41 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 3.7.3
|
4
|
+
|
5
|
+
* Fix for http.rb. Allow passing an output buffer to HTTP::Response::Body#readpartial
|
6
|
+
|
7
|
+
Thanks to [George Claghorn](https://github.com/georgeclaghorn)
|
8
|
+
|
9
|
+
* Fixed Manticore adapter to invoke Manticore failure handler on stubbed timeout
|
10
|
+
|
11
|
+
Thanks to [Alex Junger](https://github.com/alexJunger)
|
12
|
+
|
13
|
+
* Added project metadata to the gemspec
|
14
|
+
|
15
|
+
Thanks to [Orien Madgwick](https://github.com/orien)
|
16
|
+
|
17
|
+
## 3.7.2
|
18
|
+
|
19
|
+
* Fixed handling of non UTF-8 encoded urls
|
20
|
+
|
21
|
+
Thanks to [Rafael França](https://github.com/rafaelfranca)
|
22
|
+
|
23
|
+
* Fixed "shadowing outer local variable" warning
|
24
|
+
|
25
|
+
Thanks to [y-yagi](https://github.com/y-yagi)
|
26
|
+
|
27
|
+
## 3.7.1
|
28
|
+
|
29
|
+
* Fixed Async::HTTP::Client adapter code to not cause Ruby warning
|
30
|
+
|
31
|
+
Thanks to [y-yagi](https://github.com/y-yagi)
|
32
|
+
|
33
|
+
## 3.7.0
|
34
|
+
|
35
|
+
* Support for Async::HTTP::Client
|
36
|
+
|
37
|
+
Thanks to [Andriy Yanko](https://github.com/ayanko)
|
38
|
+
|
3
39
|
## 3.6.2
|
4
40
|
|
5
41
|
* Fixed Patron adapter to handle HTTP/2 status line.
|
data/README.md
CHANGED
@@ -33,6 +33,7 @@ Supported HTTP libraries
|
|
33
33
|
* Excon
|
34
34
|
* HTTP Gem
|
35
35
|
* Manticore
|
36
|
+
* Async::HTTP::Client
|
36
37
|
|
37
38
|
Supported Ruby Interpreters
|
38
39
|
---------------------------
|
@@ -1105,7 +1106,12 @@ People who submitted patches and new features or suggested improvements. Many th
|
|
1105
1106
|
* Csaba Apagyi
|
1106
1107
|
* Frederick Cheung
|
1107
1108
|
* Fábio D. Batista
|
1108
|
-
|
1109
|
+
* Andriy Yanko
|
1110
|
+
* y-yagi
|
1111
|
+
* Rafael França
|
1112
|
+
* George Claghorn
|
1113
|
+
* Alex Junger
|
1114
|
+
* Orien Madgwick
|
1109
1115
|
|
1110
1116
|
|
1111
1117
|
|
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 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, @alpn_protocol
|
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
|
@@ -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)
|
@@ -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
|
@@ -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))
|
@@ -106,14 +112,16 @@ if defined?(Manticore)
|
|
106
112
|
end
|
107
113
|
|
108
114
|
def generate_manticore_response(webmock_response)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
117
125
|
end
|
118
126
|
|
119
127
|
def generate_webmock_response(manticore_response)
|
data/lib/webmock/util/uri.rb
CHANGED
@@ -86,20 +86,21 @@ module WebMock
|
|
86
86
|
|
87
87
|
def self.uris_encoded_and_unencoded(uris)
|
88
88
|
uris.map do |uri|
|
89
|
-
[
|
89
|
+
[
|
90
|
+
uri.to_s.force_encoding(Encoding::ASCII_8BIT),
|
91
|
+
Addressable::URI.unencode(uri, String).force_encoding(Encoding::ASCII_8BIT).freeze
|
92
|
+
]
|
90
93
|
end.flatten
|
91
94
|
end
|
92
95
|
|
93
96
|
def self.uris_with_scheme_and_without(uris)
|
94
97
|
uris.map { |uri|
|
95
|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
|
96
98
|
[ uri, uri.gsub(%r{^https?://},"").freeze ]
|
97
99
|
}.flatten
|
98
100
|
end
|
99
101
|
|
100
102
|
def self.uris_with_trailing_slash_and_without(uris)
|
101
|
-
uris
|
102
|
-
uri = uri.dup.force_encoding(Encoding::ASCII_8BIT) if uri.respond_to?(:force_encoding)
|
103
|
+
uris.map { |uri|
|
103
104
|
[ uri, uri.omit(:path).freeze ]
|
104
105
|
}.flatten
|
105
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
|
@@ -72,6 +72,17 @@ describe "HTTP.rb" do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
context "streamer" do
|
75
|
+
it "can be read to a provided buffer" do
|
76
|
+
stub_request(:get, "example.com/foo")
|
77
|
+
.to_return(status: 200, body: "Hello world!")
|
78
|
+
response = HTTP.get "http://example.com/foo"
|
79
|
+
|
80
|
+
buffer = ""
|
81
|
+
response.body.readpartial(1024, buffer)
|
82
|
+
|
83
|
+
expect(buffer).to eq "Hello world!"
|
84
|
+
end
|
85
|
+
|
75
86
|
it "can be closed" do
|
76
87
|
stub_request :get, "example.com/foo"
|
77
88
|
response = HTTP.get "http://example.com/foo"
|
@@ -51,6 +51,25 @@ if RUBY_PLATFORM =~ /java/
|
|
51
51
|
response = Manticore.head("http://example-foo.com")
|
52
52
|
expect(response.code).to eq(204)
|
53
53
|
end
|
54
|
+
|
55
|
+
context "when a custom failure handler is defined" do
|
56
|
+
let(:failure_handler) { proc {} }
|
57
|
+
|
58
|
+
before do
|
59
|
+
allow(failure_handler).to receive(:call).with(kind_of(Manticore::Timeout)) do |ex|
|
60
|
+
raise ex
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "handles timeouts by invoking the failure handler" do
|
65
|
+
stub_request(:get, "http://example-foo.com").to_timeout
|
66
|
+
request = Manticore.get("http://example-foo.com").tap do |req|
|
67
|
+
req.on_failure(&failure_handler)
|
68
|
+
end
|
69
|
+
expect { request.call }.to raise_error(Manticore::Timeout)
|
70
|
+
expect(failure_handler).to have_received(:call)
|
71
|
+
end
|
72
|
+
end
|
54
73
|
end
|
55
74
|
end
|
56
75
|
end
|
@@ -111,7 +111,8 @@ shared_context "callbacks" do |*adapter_info|
|
|
111
111
|
end
|
112
112
|
|
113
113
|
it "should pass real response to callback with headers" do
|
114
|
-
expect(@response.headers["
|
114
|
+
expect(@response.headers["X-Powered-By"]).to eq( "ASP.NET")
|
115
|
+
expect(@response.headers["Content-Length"]).to eq("11") unless adapter_info.include?(:no_content_length_header)
|
115
116
|
end
|
116
117
|
|
117
118
|
it "should pass response to callback with body" do
|
@@ -64,7 +64,10 @@ shared_context "declared responses" do |*adapter_info|
|
|
64
64
|
it "should return response with declared headers" do
|
65
65
|
stub_request(:get, "www.example.com").to_return(headers: SAMPLE_HEADERS)
|
66
66
|
response = http_request(:get, "http://www.example.com/")
|
67
|
-
expect(response.headers["
|
67
|
+
expect(response.headers["Accept"]).to eq("application/json")
|
68
|
+
unless adapter_info.include?(:no_content_length_header)
|
69
|
+
expect(response.headers["Content-Length"]).to eq("8888")
|
70
|
+
end
|
68
71
|
end
|
69
72
|
|
70
73
|
it "should return response with declared headers even if there are multiple headers with the same key" do
|
@@ -171,13 +174,22 @@ shared_context "declared responses" do |*adapter_info|
|
|
171
174
|
end
|
172
175
|
|
173
176
|
it "should return recorded headers" do
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
177
|
+
if adapter_info.include?(:no_content_length_header)
|
178
|
+
expect(@response.headers).to eq({
|
179
|
+
"Date"=>"Sat, 23 Jan 2010 01:01:05 GMT",
|
180
|
+
"Content-Type"=>"text/html; charset=UTF-8",
|
181
|
+
"Connection"=>"Keep-Alive",
|
182
|
+
"Accept"=>"image/jpeg, image/png"
|
183
|
+
})
|
184
|
+
else
|
185
|
+
expect(@response.headers).to eq({
|
186
|
+
"Date"=>"Sat, 23 Jan 2010 01:01:05 GMT",
|
187
|
+
"Content-Type"=>"text/html; charset=UTF-8",
|
188
|
+
"Content-Length"=>"419",
|
189
|
+
"Connection"=>"Keep-Alive",
|
190
|
+
"Accept"=>"image/jpeg, image/png"
|
191
|
+
})
|
192
|
+
end
|
181
193
|
end
|
182
194
|
|
183
195
|
it "should return recorded body" do
|
@@ -205,13 +217,22 @@ shared_context "declared responses" do |*adapter_info|
|
|
205
217
|
end
|
206
218
|
|
207
219
|
it "should return recorded headers" do
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
220
|
+
if adapter_info.include?(:no_content_length_header)
|
221
|
+
expect(@response.headers).to eq({
|
222
|
+
"Date"=>"Sat, 23 Jan 2010 01:01:05 GMT",
|
223
|
+
"Content-Type"=>"text/html; charset=UTF-8",
|
224
|
+
"Connection"=>"Keep-Alive",
|
225
|
+
"Accept"=>"image/jpeg, image/png"
|
226
|
+
})
|
227
|
+
else
|
228
|
+
expect(@response.headers).to eq({
|
229
|
+
"Date"=>"Sat, 23 Jan 2010 01:01:05 GMT",
|
230
|
+
"Content-Type"=>"text/html; charset=UTF-8",
|
231
|
+
"Content-Length"=>"419",
|
232
|
+
"Connection"=>"Keep-Alive",
|
233
|
+
"Accept"=>"image/jpeg, image/png"
|
234
|
+
})
|
235
|
+
end
|
215
236
|
end
|
216
237
|
|
217
238
|
it "should return recorded body" do
|
data/spec/unit/util/uri_spec.rb
CHANGED
@@ -177,6 +177,16 @@ describe WebMock::Util::URI do
|
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
180
|
+
it "should find all variations of uris with https, basic auth, a non-standard port and a path" do
|
181
|
+
uri = "https://~%8A:pass@www.example.com:9000/foo"
|
182
|
+
variations = [
|
183
|
+
"https://~%8A:pass@www.example.com:9000/foo",
|
184
|
+
"https://~\x8A:pass@www.example.com:9000/foo".force_encoding(Encoding::ASCII_8BIT)
|
185
|
+
]
|
186
|
+
|
187
|
+
expect(WebMock::Util::URI.variations_of_uri_as_strings(uri)).to eq(variations)
|
188
|
+
end
|
189
|
+
|
180
190
|
end
|
181
191
|
|
182
192
|
describe "normalized uri equality" do
|
data/webmock.gemspec
CHANGED
@@ -13,6 +13,14 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{WebMock allows stubbing HTTP requests and setting expectations on HTTP requests.}
|
14
14
|
s.license = "MIT"
|
15
15
|
|
16
|
+
s.metadata = {
|
17
|
+
'bug_tracker_uri' => 'https://github.com/bblimke/webmock/issues',
|
18
|
+
'changelog_uri' => "https://github.com/bblimke/webmock/blob/v#{s.version}/CHANGELOG.md",
|
19
|
+
'documentation_uri' => "https://www.rubydoc.info/gems/webmock/#{s.version}",
|
20
|
+
'source_code_uri' => "https://github.com/bblimke/webmock/tree/v#{s.version}",
|
21
|
+
'wiki_uri' => 'https://github.com/bblimke/webmock/wiki'
|
22
|
+
}
|
23
|
+
|
16
24
|
s.required_ruby_version = '>= 2.0'
|
17
25
|
|
18
26
|
s.add_dependency 'addressable', '>= 2.3.6'
|
@@ -33,6 +41,7 @@ Gem::Specification.new do |s|
|
|
33
41
|
s.add_development_dependency 'em-http-request', '>= 1.0.2'
|
34
42
|
s.add_development_dependency 'em-synchrony', '>= 1.0.0'
|
35
43
|
s.add_development_dependency 'excon', '>= 0.27.5'
|
44
|
+
s.add_development_dependency 'async-http', '>= 0.48.0'
|
36
45
|
s.add_development_dependency 'minitest', '>= 5.0.0'
|
37
46
|
s.add_development_dependency 'test-unit', '>= 3.0.0'
|
38
47
|
s.add_development_dependency 'rdoc', '> 3.5.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webmock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bartosz Blimke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -198,6 +198,20 @@ dependencies:
|
|
198
198
|
- - ">="
|
199
199
|
- !ruby/object:Gem::Version
|
200
200
|
version: 0.27.5
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: async-http
|
203
|
+
requirement: !ruby/object:Gem::Requirement
|
204
|
+
requirements:
|
205
|
+
- - ">="
|
206
|
+
- !ruby/object:Gem::Version
|
207
|
+
version: 0.48.0
|
208
|
+
type: :development
|
209
|
+
prerelease: false
|
210
|
+
version_requirements: !ruby/object:Gem::Requirement
|
211
|
+
requirements:
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: 0.48.0
|
201
215
|
- !ruby/object:Gem::Dependency
|
202
216
|
name: minitest
|
203
217
|
requirement: !ruby/object:Gem::Requirement
|
@@ -265,6 +279,7 @@ files:
|
|
265
279
|
- lib/webmock/cucumber.rb
|
266
280
|
- lib/webmock/deprecation.rb
|
267
281
|
- lib/webmock/errors.rb
|
282
|
+
- lib/webmock/http_lib_adapters/async_http_client_adapter.rb
|
268
283
|
- lib/webmock/http_lib_adapters/curb_adapter.rb
|
269
284
|
- lib/webmock/http_lib_adapters/em_http_request_adapter.rb
|
270
285
|
- lib/webmock/http_lib_adapters/excon_adapter.rb
|
@@ -318,6 +333,8 @@ files:
|
|
318
333
|
- minitest/test_helper.rb
|
319
334
|
- minitest/test_webmock.rb
|
320
335
|
- minitest/webmock_spec.rb
|
336
|
+
- spec/acceptance/async_http_client/async_http_client_spec.rb
|
337
|
+
- spec/acceptance/async_http_client/async_http_client_spec_helper.rb
|
321
338
|
- spec/acceptance/curb/curb_spec.rb
|
322
339
|
- spec/acceptance/curb/curb_spec_helper.rb
|
323
340
|
- spec/acceptance/em_http_request/em_http_request_spec.rb
|
@@ -388,7 +405,12 @@ files:
|
|
388
405
|
homepage: http://github.com/bblimke/webmock
|
389
406
|
licenses:
|
390
407
|
- MIT
|
391
|
-
metadata:
|
408
|
+
metadata:
|
409
|
+
bug_tracker_uri: https://github.com/bblimke/webmock/issues
|
410
|
+
changelog_uri: https://github.com/bblimke/webmock/blob/v3.7.3/CHANGELOG.md
|
411
|
+
documentation_uri: https://www.rubydoc.info/gems/webmock/3.7.3
|
412
|
+
source_code_uri: https://github.com/bblimke/webmock/tree/v3.7.3
|
413
|
+
wiki_uri: https://github.com/bblimke/webmock/wiki
|
392
414
|
post_install_message:
|
393
415
|
rdoc_options: []
|
394
416
|
require_paths:
|
@@ -409,6 +431,8 @@ signing_key:
|
|
409
431
|
specification_version: 4
|
410
432
|
summary: Library for stubbing HTTP requests in Ruby.
|
411
433
|
test_files:
|
434
|
+
- spec/acceptance/async_http_client/async_http_client_spec.rb
|
435
|
+
- spec/acceptance/async_http_client/async_http_client_spec_helper.rb
|
412
436
|
- spec/acceptance/curb/curb_spec.rb
|
413
437
|
- spec/acceptance/curb/curb_spec_helper.rb
|
414
438
|
- spec/acceptance/em_http_request/em_http_request_spec.rb
|