webmock 3.7.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 +7 -0
- data/.gemtest +0 -0
- data/.gitignore +34 -0
- data/.rspec-tm +2 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +1698 -0
- data/Gemfile +9 -0
- data/LICENSE +20 -0
- data/README.md +1125 -0
- data/Rakefile +28 -0
- data/lib/webmock.rb +59 -0
- data/lib/webmock/api.rb +109 -0
- data/lib/webmock/assertion_failure.rb +11 -0
- data/lib/webmock/callback_registry.rb +35 -0
- data/lib/webmock/config.rb +18 -0
- data/lib/webmock/cucumber.rb +10 -0
- data/lib/webmock/deprecation.rb +9 -0
- data/lib/webmock/errors.rb +17 -0
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +214 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +347 -0
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +228 -0
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +162 -0
- data/lib/webmock/http_lib_adapters/http_lib_adapter.rb +7 -0
- data/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +19 -0
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +14 -0
- data/lib/webmock/http_lib_adapters/http_rb/request.rb +16 -0
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +43 -0
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +29 -0
- data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +68 -0
- data/lib/webmock/http_lib_adapters/http_rb_adapter.rb +37 -0
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +242 -0
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +130 -0
- data/lib/webmock/http_lib_adapters/net_http.rb +361 -0
- data/lib/webmock/http_lib_adapters/net_http_response.rb +34 -0
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +130 -0
- data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +174 -0
- data/lib/webmock/matchers/any_arg_matcher.rb +13 -0
- data/lib/webmock/matchers/hash_argument_matcher.rb +21 -0
- data/lib/webmock/matchers/hash_excluding_matcher.rb +15 -0
- data/lib/webmock/matchers/hash_including_matcher.rb +17 -0
- data/lib/webmock/minitest.rb +41 -0
- data/lib/webmock/rack_response.rb +69 -0
- data/lib/webmock/request_body_diff.rb +64 -0
- data/lib/webmock/request_execution_verifier.rb +77 -0
- data/lib/webmock/request_pattern.rb +370 -0
- data/lib/webmock/request_registry.rb +35 -0
- data/lib/webmock/request_signature.rb +54 -0
- data/lib/webmock/request_signature_snippet.rb +61 -0
- data/lib/webmock/request_stub.rb +100 -0
- data/lib/webmock/response.rb +153 -0
- data/lib/webmock/responses_sequence.rb +40 -0
- data/lib/webmock/rspec.rb +41 -0
- data/lib/webmock/rspec/matchers.rb +27 -0
- data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +78 -0
- data/lib/webmock/rspec/matchers/webmock_matcher.rb +67 -0
- data/lib/webmock/stub_registry.rb +67 -0
- data/lib/webmock/stub_request_snippet.rb +38 -0
- data/lib/webmock/test_unit.rb +22 -0
- data/lib/webmock/util/hash_counter.rb +39 -0
- data/lib/webmock/util/hash_keys_stringifier.rb +25 -0
- data/lib/webmock/util/hash_validator.rb +17 -0
- data/lib/webmock/util/headers.rb +64 -0
- data/lib/webmock/util/json.rb +67 -0
- data/lib/webmock/util/query_mapper.rb +281 -0
- data/lib/webmock/util/uri.rb +110 -0
- data/lib/webmock/util/values_stringifier.rb +20 -0
- data/lib/webmock/util/version_checker.rb +111 -0
- data/lib/webmock/version.rb +3 -0
- data/lib/webmock/webmock.rb +161 -0
- data/minitest/test_helper.rb +34 -0
- data/minitest/test_webmock.rb +9 -0
- data/minitest/webmock_spec.rb +60 -0
- 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/curb/curb_spec.rb +492 -0
- data/spec/acceptance/curb/curb_spec_helper.rb +147 -0
- data/spec/acceptance/em_http_request/em_http_request_spec.rb +406 -0
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +77 -0
- data/spec/acceptance/excon/excon_spec.rb +77 -0
- data/spec/acceptance/excon/excon_spec_helper.rb +50 -0
- data/spec/acceptance/http_rb/http_rb_spec.rb +82 -0
- data/spec/acceptance/http_rb/http_rb_spec_helper.rb +54 -0
- data/spec/acceptance/httpclient/httpclient_spec.rb +217 -0
- data/spec/acceptance/httpclient/httpclient_spec_helper.rb +57 -0
- data/spec/acceptance/manticore/manticore_spec.rb +56 -0
- data/spec/acceptance/manticore/manticore_spec_helper.rb +35 -0
- data/spec/acceptance/net_http/net_http_shared.rb +153 -0
- data/spec/acceptance/net_http/net_http_spec.rb +331 -0
- data/spec/acceptance/net_http/net_http_spec_helper.rb +64 -0
- data/spec/acceptance/net_http/real_net_http_spec.rb +20 -0
- data/spec/acceptance/patron/patron_spec.rb +125 -0
- data/spec/acceptance/patron/patron_spec_helper.rb +54 -0
- data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +313 -0
- data/spec/acceptance/shared/callbacks.rb +148 -0
- data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +36 -0
- data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +95 -0
- data/spec/acceptance/shared/precedence_of_stubs.rb +15 -0
- data/spec/acceptance/shared/request_expectations.rb +930 -0
- data/spec/acceptance/shared/returning_declared_responses.rb +409 -0
- data/spec/acceptance/shared/stubbing_requests.rb +643 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +135 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +60 -0
- data/spec/acceptance/webmock_shared.rb +41 -0
- data/spec/fixtures/test.txt +1 -0
- data/spec/quality_spec.rb +84 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/support/example_curl_output.txt +22 -0
- data/spec/support/failures.rb +9 -0
- data/spec/support/my_rack_app.rb +53 -0
- data/spec/support/network_connection.rb +19 -0
- data/spec/support/webmock_server.rb +70 -0
- data/spec/unit/api_spec.rb +175 -0
- data/spec/unit/errors_spec.rb +129 -0
- data/spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb +17 -0
- data/spec/unit/http_lib_adapters/http_lib_adapter_spec.rb +12 -0
- data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
- data/spec/unit/matchers/hash_including_matcher_spec.rb +87 -0
- data/spec/unit/rack_response_spec.rb +112 -0
- data/spec/unit/request_body_diff_spec.rb +90 -0
- data/spec/unit/request_execution_verifier_spec.rb +208 -0
- data/spec/unit/request_pattern_spec.rb +601 -0
- data/spec/unit/request_registry_spec.rb +95 -0
- data/spec/unit/request_signature_snippet_spec.rb +89 -0
- data/spec/unit/request_signature_spec.rb +155 -0
- data/spec/unit/request_stub_spec.rb +199 -0
- data/spec/unit/response_spec.rb +282 -0
- data/spec/unit/stub_registry_spec.rb +103 -0
- data/spec/unit/stub_request_snippet_spec.rb +115 -0
- data/spec/unit/util/hash_counter_spec.rb +39 -0
- data/spec/unit/util/hash_keys_stringifier_spec.rb +27 -0
- data/spec/unit/util/headers_spec.rb +28 -0
- data/spec/unit/util/json_spec.rb +33 -0
- data/spec/unit/util/query_mapper_spec.rb +157 -0
- data/spec/unit/util/uri_spec.rb +361 -0
- data/spec/unit/util/version_checker_spec.rb +65 -0
- data/spec/unit/webmock_spec.rb +19 -0
- data/test/http_request.rb +24 -0
- data/test/shared_test.rb +108 -0
- data/test/test_helper.rb +23 -0
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +45 -0
- metadata +496 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'excon'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# excon not found
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
if defined?(Excon)
|
|
8
|
+
WebMock::VersionChecker.new('Excon', Excon::VERSION, '0.27.5').check_version!
|
|
9
|
+
|
|
10
|
+
module WebMock
|
|
11
|
+
module HttpLibAdapters
|
|
12
|
+
|
|
13
|
+
class ExconAdapter < HttpLibAdapter
|
|
14
|
+
PARAMS_TO_DELETE = [:expects, :idempotent,
|
|
15
|
+
:instrumentor_name, :instrumentor,
|
|
16
|
+
:response_block,
|
|
17
|
+
:__construction_args, :stack,
|
|
18
|
+
:connection, :response]
|
|
19
|
+
|
|
20
|
+
adapter_for :excon
|
|
21
|
+
|
|
22
|
+
instance_exec do
|
|
23
|
+
@original_excon_mock_default = nil
|
|
24
|
+
@stub = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.enable!
|
|
28
|
+
self.add_excon_stub
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.disable!
|
|
32
|
+
self.remove_excon_stub
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.add_excon_stub
|
|
36
|
+
if not @stub
|
|
37
|
+
@original_excon_mock_default = ::Excon.defaults[:mock]
|
|
38
|
+
::Excon.defaults[:mock] = true
|
|
39
|
+
@stub = ::Excon.stub({}) do |params|
|
|
40
|
+
self.handle_request(params)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.remove_excon_stub
|
|
46
|
+
::Excon.defaults[:mock] = @original_excon_mock_default
|
|
47
|
+
@original_excon_mock_default = nil
|
|
48
|
+
Excon.stubs.delete(@stub)
|
|
49
|
+
@stub = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.handle_request(params)
|
|
53
|
+
mock_request = self.build_request params.dup
|
|
54
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(mock_request)
|
|
55
|
+
|
|
56
|
+
if mock_response = WebMock::StubRegistry.instance.response_for_request(mock_request)
|
|
57
|
+
self.perform_callbacks(mock_request, mock_response, real_request: false)
|
|
58
|
+
response = self.real_response(mock_response)
|
|
59
|
+
response
|
|
60
|
+
elsif WebMock.net_connect_allowed?(mock_request.uri)
|
|
61
|
+
conn = new_excon_connection(params)
|
|
62
|
+
real_response = conn.request(request_params_from(params.merge(mock: false)))
|
|
63
|
+
|
|
64
|
+
ExconAdapter.perform_callbacks(mock_request, ExconAdapter.mock_response(real_response), real_request: true)
|
|
65
|
+
|
|
66
|
+
real_response.data
|
|
67
|
+
else
|
|
68
|
+
raise WebMock::NetConnectNotAllowedError.new(mock_request)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.new_excon_connection(params)
|
|
73
|
+
# Ensure the connection is constructed with the exact same args
|
|
74
|
+
# that the orginal connection was constructed with.
|
|
75
|
+
args = params.fetch(:__construction_args)
|
|
76
|
+
::Excon::Connection.new(connection_params_from args.merge(mock: false))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.connection_params_from(hash)
|
|
80
|
+
hash = hash.dup
|
|
81
|
+
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
|
|
82
|
+
hash
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.request_params_from(hash)
|
|
86
|
+
hash = hash.dup
|
|
87
|
+
if defined?(Excon::VALID_REQUEST_KEYS)
|
|
88
|
+
hash.reject! {|key,_| !Excon::VALID_REQUEST_KEYS.include?(key) }
|
|
89
|
+
end
|
|
90
|
+
PARAMS_TO_DELETE.each { |key| hash.delete(key) }
|
|
91
|
+
hash
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.to_query(hash)
|
|
95
|
+
string = "".dup
|
|
96
|
+
for key, values in hash
|
|
97
|
+
if values.nil?
|
|
98
|
+
string << key.to_s << '&'
|
|
99
|
+
else
|
|
100
|
+
for value in [*values]
|
|
101
|
+
string << key.to_s << '=' << CGI.escape(value.to_s) << '&'
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
string.chop! # remove trailing '&'
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.build_request(params)
|
|
109
|
+
params = params.dup
|
|
110
|
+
params.delete(:user)
|
|
111
|
+
params.delete(:password)
|
|
112
|
+
method = (params.delete(:method) || :get).to_s.downcase.to_sym
|
|
113
|
+
params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash)
|
|
114
|
+
uri = Addressable::URI.new(params).to_s
|
|
115
|
+
WebMock::RequestSignature.new method, uri, body: body_from(params), headers: params[:headers]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.body_from(params)
|
|
119
|
+
body = params[:body]
|
|
120
|
+
return body unless body.respond_to?(:read)
|
|
121
|
+
|
|
122
|
+
contents = body.read
|
|
123
|
+
body.rewind if body.respond_to?(:rewind)
|
|
124
|
+
contents
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.real_response(mock)
|
|
128
|
+
raise Excon::Errors::Timeout if mock.should_timeout
|
|
129
|
+
mock.raise_error_if_any
|
|
130
|
+
{
|
|
131
|
+
body: mock.body,
|
|
132
|
+
status: mock.status[0].to_i,
|
|
133
|
+
reason_phrase: mock.status[1],
|
|
134
|
+
headers: mock.headers || {}
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def self.mock_response(real)
|
|
139
|
+
mock = WebMock::Response.new
|
|
140
|
+
mock.status = [real.status, real.reason_phrase]
|
|
141
|
+
mock.headers = real.headers
|
|
142
|
+
mock.body = real.body.dup
|
|
143
|
+
mock
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.perform_callbacks(request, response, options = {})
|
|
147
|
+
return unless WebMock::CallbackRegistry.any_callbacks?
|
|
148
|
+
WebMock::CallbackRegistry.invoke_callbacks(options.merge(lib: :excon), request, response)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
Excon::Connection.class_eval do
|
|
155
|
+
def self.new(args = {})
|
|
156
|
+
args.delete(:__construction_args)
|
|
157
|
+
super(args).tap do |instance|
|
|
158
|
+
instance.data[:__construction_args] = args
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
class HttpLibAdapterRegistry
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
attr_accessor :http_lib_adapters
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@http_lib_adapters = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def register(lib, adapter)
|
|
12
|
+
@http_lib_adapters[lib] = adapter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def each_adapter(&block)
|
|
16
|
+
@http_lib_adapters.each(&block)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
class Client
|
|
3
|
+
alias_method :__perform__, :perform
|
|
4
|
+
|
|
5
|
+
def perform(request, options)
|
|
6
|
+
return __perform__(request, options) unless webmock_enabled?
|
|
7
|
+
WebMockPerform.new(request) { __perform__(request, options) }.exec
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def webmock_enabled?
|
|
11
|
+
::WebMock::HttpLibAdapters::HttpRbAdapter.enabled?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
class Request
|
|
3
|
+
def webmock_signature
|
|
4
|
+
request_body = if defined?(HTTP::Request::Body)
|
|
5
|
+
''.tap { |string| body.each { |part| string << part } }
|
|
6
|
+
else
|
|
7
|
+
body
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
::WebMock::RequestSignature.new(verb, uri.to_s, {
|
|
11
|
+
headers: headers.to_h,
|
|
12
|
+
body: request_body
|
|
13
|
+
})
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
class Response
|
|
3
|
+
def to_webmock
|
|
4
|
+
webmock_response = ::WebMock::Response.new
|
|
5
|
+
|
|
6
|
+
webmock_response.status = [status.to_i, reason]
|
|
7
|
+
webmock_response.body = body.to_s
|
|
8
|
+
webmock_response.headers = headers.to_h
|
|
9
|
+
|
|
10
|
+
webmock_response
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def from_webmock(webmock_response, request_signature = nil)
|
|
15
|
+
status = Status.new(webmock_response.status.first)
|
|
16
|
+
headers = webmock_response.headers || {}
|
|
17
|
+
body = Body.new Streamer.new webmock_response.body
|
|
18
|
+
uri = normalize_uri(request_signature && request_signature.uri)
|
|
19
|
+
|
|
20
|
+
return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
|
|
21
|
+
|
|
22
|
+
new({
|
|
23
|
+
status: status,
|
|
24
|
+
version: "1.1",
|
|
25
|
+
headers: headers,
|
|
26
|
+
body: body,
|
|
27
|
+
uri: uri
|
|
28
|
+
})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def normalize_uri(uri)
|
|
34
|
+
return unless uri
|
|
35
|
+
|
|
36
|
+
uri = Addressable::URI.parse uri
|
|
37
|
+
uri.port = nil if uri.default_port && uri.port == uri.default_port
|
|
38
|
+
|
|
39
|
+
uri
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
class Response
|
|
3
|
+
class Streamer
|
|
4
|
+
def initialize(str)
|
|
5
|
+
@io = StringIO.new str
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def readpartial(size = nil)
|
|
9
|
+
unless size
|
|
10
|
+
if defined?(HTTP::Client::BUFFER_SIZE)
|
|
11
|
+
size = HTTP::Client::BUFFER_SIZE
|
|
12
|
+
elsif defined?(HTTP::Connection::BUFFER_SIZE)
|
|
13
|
+
size = HTTP::Connection::BUFFER_SIZE
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@io.read size
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close
|
|
21
|
+
@io.close
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def sequence_id
|
|
25
|
+
-1
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module HTTP
|
|
2
|
+
class WebMockPerform
|
|
3
|
+
def initialize(request, &perform)
|
|
4
|
+
@request = request
|
|
5
|
+
@perform = perform
|
|
6
|
+
@request_signature = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def exec
|
|
10
|
+
replay || perform || halt
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def request_signature
|
|
14
|
+
unless @request_signature
|
|
15
|
+
@request_signature = @request.webmock_signature
|
|
16
|
+
register_request(@request_signature)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@request_signature
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def response_for_request(signature)
|
|
25
|
+
::WebMock::StubRegistry.instance.response_for_request(signature)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def register_request(signature)
|
|
29
|
+
::WebMock::RequestRegistry.instance.requested_signatures.put(signature)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def replay
|
|
33
|
+
webmock_response = response_for_request request_signature
|
|
34
|
+
|
|
35
|
+
return unless webmock_response
|
|
36
|
+
|
|
37
|
+
raise_timeout_error if webmock_response.should_timeout
|
|
38
|
+
webmock_response.raise_error_if_any
|
|
39
|
+
|
|
40
|
+
invoke_callbacks(webmock_response, real_request: false)
|
|
41
|
+
::HTTP::Response.from_webmock webmock_response, request_signature
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def raise_timeout_error
|
|
45
|
+
raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0"
|
|
46
|
+
raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def perform
|
|
50
|
+
return unless ::WebMock.net_connect_allowed?(request_signature.uri)
|
|
51
|
+
response = @perform.call
|
|
52
|
+
invoke_callbacks(response.to_webmock, real_request: true)
|
|
53
|
+
response
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def halt
|
|
57
|
+
raise ::WebMock::NetConnectNotAllowedError.new request_signature
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def invoke_callbacks(webmock_response, options = {})
|
|
61
|
+
::WebMock::CallbackRegistry.invoke_callbacks(
|
|
62
|
+
options.merge({ lib: :http_rb }),
|
|
63
|
+
request_signature,
|
|
64
|
+
webmock_response
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require "http"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# HTTP gem not found
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
if defined?(HTTP) && defined?(HTTP::VERSION)
|
|
8
|
+
WebMock::VersionChecker.new("HTTP Gem", HTTP::VERSION, "0.6.0").check_version!
|
|
9
|
+
|
|
10
|
+
module WebMock
|
|
11
|
+
module HttpLibAdapters
|
|
12
|
+
class HttpRbAdapter < HttpLibAdapter
|
|
13
|
+
adapter_for :http_rb
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def enable!
|
|
17
|
+
@enabled = true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def disable!
|
|
21
|
+
@enabled = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enabled?
|
|
25
|
+
@enabled
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
require "webmock/http_lib_adapters/http_rb/client"
|
|
33
|
+
require "webmock/http_lib_adapters/http_rb/request"
|
|
34
|
+
require "webmock/http_lib_adapters/http_rb/response"
|
|
35
|
+
require "webmock/http_lib_adapters/http_rb/streamer"
|
|
36
|
+
require "webmock/http_lib_adapters/http_rb/webmock"
|
|
37
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'httpclient'
|
|
3
|
+
require 'jsonclient' # defined in 'httpclient' gem as well
|
|
4
|
+
rescue LoadError
|
|
5
|
+
# httpclient not found
|
|
6
|
+
# or jsonclient not defined (in old versions of httclient gem)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
if defined?(::HTTPClient)
|
|
10
|
+
|
|
11
|
+
module WebMock
|
|
12
|
+
module HttpLibAdapters
|
|
13
|
+
class HTTPClientAdapter < HttpLibAdapter
|
|
14
|
+
adapter_for :httpclient
|
|
15
|
+
|
|
16
|
+
unless const_defined?(:OriginalHttpClient)
|
|
17
|
+
OriginalHttpClient = ::HTTPClient
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
unless const_defined?(:OriginalJsonClient)
|
|
21
|
+
OriginalJsonClient = ::JSONClient if defined?(::JSONClient)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.enable!
|
|
25
|
+
Object.send(:remove_const, :HTTPClient)
|
|
26
|
+
Object.send(:const_set, :HTTPClient, WebMockHTTPClient)
|
|
27
|
+
if defined? ::JSONClient
|
|
28
|
+
Object.send(:remove_const, :JSONClient)
|
|
29
|
+
Object.send(:const_set, :JSONClient, WebMockJSONClient)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.disable!
|
|
34
|
+
Object.send(:remove_const, :HTTPClient)
|
|
35
|
+
Object.send(:const_set, :HTTPClient, OriginalHttpClient)
|
|
36
|
+
if defined? ::JSONClient
|
|
37
|
+
Object.send(:remove_const, :JSONClient)
|
|
38
|
+
Object.send(:const_set, :JSONClient, OriginalJsonClient)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module WebMockHTTPClients
|
|
46
|
+
def do_get_block(req, proxy, conn, &block)
|
|
47
|
+
do_get(req, proxy, conn, false, &block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def do_get_stream(req, proxy, conn, &block)
|
|
51
|
+
do_get(req, proxy, conn, true, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def do_get(req, proxy, conn, stream = false, &block)
|
|
55
|
+
request_signature = build_request_signature(req, :reuse_existing)
|
|
56
|
+
|
|
57
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
|
58
|
+
|
|
59
|
+
if webmock_responses[request_signature]
|
|
60
|
+
webmock_response = webmock_responses.delete(request_signature)
|
|
61
|
+
response = build_httpclient_response(webmock_response, stream, req.header, &block)
|
|
62
|
+
@request_filter.each do |filter|
|
|
63
|
+
filter.filter_response(req, response)
|
|
64
|
+
end
|
|
65
|
+
res = conn.push(response)
|
|
66
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
|
67
|
+
{lib: :httpclient}, request_signature, webmock_response)
|
|
68
|
+
res
|
|
69
|
+
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
|
70
|
+
# in case there is a nil entry in the hash...
|
|
71
|
+
webmock_responses.delete(request_signature)
|
|
72
|
+
|
|
73
|
+
res = if stream
|
|
74
|
+
do_get_stream_without_webmock(req, proxy, conn, &block)
|
|
75
|
+
elsif block
|
|
76
|
+
body = ''
|
|
77
|
+
do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
|
|
78
|
+
if chunk && chunk.bytesize > 0
|
|
79
|
+
body += chunk
|
|
80
|
+
block.call(http_res, chunk)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
do_get_block_without_webmock(req, proxy, conn)
|
|
85
|
+
end
|
|
86
|
+
res = conn.pop
|
|
87
|
+
conn.push(res)
|
|
88
|
+
if WebMock::CallbackRegistry.any_callbacks?
|
|
89
|
+
webmock_response = build_webmock_response(res, body)
|
|
90
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
|
91
|
+
{lib: :httpclient, real_request: true}, request_signature,
|
|
92
|
+
webmock_response)
|
|
93
|
+
end
|
|
94
|
+
res
|
|
95
|
+
else
|
|
96
|
+
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def do_request_async(method, uri, query, body, extheader)
|
|
101
|
+
req = create_request(method, uri, query, body, extheader)
|
|
102
|
+
request_signature = build_request_signature(req)
|
|
103
|
+
webmock_request_signatures << request_signature
|
|
104
|
+
|
|
105
|
+
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
|
|
106
|
+
super
|
|
107
|
+
else
|
|
108
|
+
raise WebMock::NetConnectNotAllowedError.new(request_signature)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_httpclient_response(webmock_response, stream = false, req_header = nil, &block)
|
|
113
|
+
body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
|
|
114
|
+
response = HTTP::Message.new_response(body, req_header)
|
|
115
|
+
response.header.init_response(webmock_response.status[0])
|
|
116
|
+
response.reason=webmock_response.status[1]
|
|
117
|
+
webmock_response.headers.to_a.each { |name, value| response.header.set(name, value) }
|
|
118
|
+
|
|
119
|
+
raise HTTPClient::TimeoutError if webmock_response.should_timeout
|
|
120
|
+
webmock_response.raise_error_if_any
|
|
121
|
+
|
|
122
|
+
block.call(response, body) if block && body && body.bytesize > 0
|
|
123
|
+
|
|
124
|
+
response
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def build_webmock_response(httpclient_response, body = nil)
|
|
128
|
+
webmock_response = WebMock::Response.new
|
|
129
|
+
webmock_response.status = [httpclient_response.status, httpclient_response.reason]
|
|
130
|
+
|
|
131
|
+
webmock_response.headers = {}.tap do |hash|
|
|
132
|
+
httpclient_response.header.all.each do |(key, value)|
|
|
133
|
+
if hash.has_key?(key)
|
|
134
|
+
hash[key] = Array(hash[key]) + [value]
|
|
135
|
+
else
|
|
136
|
+
hash[key] = value
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if body
|
|
142
|
+
webmock_response.body = body
|
|
143
|
+
elsif httpclient_response.content.respond_to?(:read)
|
|
144
|
+
webmock_response.body = httpclient_response.content.read
|
|
145
|
+
body = HTTP::Message::Body.new
|
|
146
|
+
body.init_response(StringIO.new(webmock_response.body))
|
|
147
|
+
httpclient_response.body = body
|
|
148
|
+
else
|
|
149
|
+
webmock_response.body = httpclient_response.content
|
|
150
|
+
end
|
|
151
|
+
webmock_response
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def build_request_signature(req, reuse_existing = false)
|
|
155
|
+
uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
|
|
156
|
+
uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query, notation: WebMock::Config.instance.query_values_notation) if req.header.request_query
|
|
157
|
+
uri.port = req.header.request_uri.port
|
|
158
|
+
|
|
159
|
+
@request_filter.each do |filter|
|
|
160
|
+
filter.filter_request(req)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
headers = req.header.all.inject({}) do |hdrs, header|
|
|
164
|
+
hdrs[header[0]] ||= []
|
|
165
|
+
hdrs[header[0]] << header[1]
|
|
166
|
+
hdrs
|
|
167
|
+
end
|
|
168
|
+
headers = headers_from_session(uri).merge(headers)
|
|
169
|
+
|
|
170
|
+
signature = WebMock::RequestSignature.new(
|
|
171
|
+
req.header.request_method.downcase.to_sym,
|
|
172
|
+
uri.to_s,
|
|
173
|
+
body: req.http_body.dump,
|
|
174
|
+
headers: headers
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# reuse a previous identical signature object if we stored one for later use
|
|
178
|
+
if reuse_existing && previous_signature = previous_signature_for(signature)
|
|
179
|
+
return previous_signature
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
signature
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def webmock_responses
|
|
186
|
+
@webmock_responses ||= Hash.new do |hash, request_signature|
|
|
187
|
+
hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def webmock_request_signatures
|
|
192
|
+
@webmock_request_signatures ||= []
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def previous_signature_for(signature)
|
|
196
|
+
return nil unless index = webmock_request_signatures.index(signature)
|
|
197
|
+
webmock_request_signatures.delete_at(index)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
# some of the headers sent by HTTPClient are derived from
|
|
203
|
+
# the client session
|
|
204
|
+
def headers_from_session(uri)
|
|
205
|
+
session_headers = HTTP::Message::Headers.new
|
|
206
|
+
@session_manager.send(:open, uri).send(:set_header, MessageMock.new(session_headers))
|
|
207
|
+
session_headers.all.inject({}) do |hdrs, header|
|
|
208
|
+
hdrs[header[0]] = header[1]
|
|
209
|
+
hdrs
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class WebMockHTTPClient < HTTPClient
|
|
215
|
+
alias_method :do_get_block_without_webmock, :do_get_block
|
|
216
|
+
alias_method :do_get_stream_without_webmock, :do_get_stream
|
|
217
|
+
|
|
218
|
+
include WebMockHTTPClients
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if defined? ::JSONClient
|
|
222
|
+
class WebMockJSONClient < JSONClient
|
|
223
|
+
alias_method :do_get_block_without_webmock, :do_get_block
|
|
224
|
+
alias_method :do_get_stream_without_webmock, :do_get_stream
|
|
225
|
+
|
|
226
|
+
include WebMockHTTPClients
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Mocks a HTTPClient HTTP::Message
|
|
232
|
+
class MessageMock
|
|
233
|
+
attr_reader :header
|
|
234
|
+
|
|
235
|
+
def initialize(headers)
|
|
236
|
+
@header = headers
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def http_version=(value);end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
end
|