webmock 3.0.1 → 3.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +558 -2
  3. data/README.md +200 -42
  4. data/lib/webmock/api.rb +14 -0
  5. data/lib/webmock/assertion_failure.rb +2 -0
  6. data/lib/webmock/callback_registry.rb +2 -0
  7. data/lib/webmock/config.rb +2 -0
  8. data/lib/webmock/cucumber.rb +2 -0
  9. data/lib/webmock/deprecation.rb +2 -0
  10. data/lib/webmock/errors.rb +2 -0
  11. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +223 -0
  12. data/lib/webmock/http_lib_adapters/curb_adapter.rb +21 -5
  13. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +20 -9
  14. data/lib/webmock/http_lib_adapters/excon_adapter.rb +7 -2
  15. data/lib/webmock/http_lib_adapters/http_lib_adapter.rb +2 -0
  16. data/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +2 -0
  17. data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
  18. data/lib/webmock/http_lib_adapters/http_rb/request.rb +19 -1
  19. data/lib/webmock/http_lib_adapters/http_rb/response.rb +29 -3
  20. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +11 -3
  21. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +9 -3
  22. data/lib/webmock/http_lib_adapters/http_rb_adapter.rb +2 -0
  23. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +30 -9
  24. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +35 -15
  25. data/lib/webmock/http_lib_adapters/net_http.rb +38 -89
  26. data/lib/webmock/http_lib_adapters/net_http_response.rb +3 -1
  27. data/lib/webmock/http_lib_adapters/patron_adapter.rb +6 -4
  28. data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +18 -2
  29. data/lib/webmock/matchers/any_arg_matcher.rb +15 -0
  30. data/lib/webmock/matchers/hash_argument_matcher.rb +23 -0
  31. data/lib/webmock/matchers/hash_excluding_matcher.rb +17 -0
  32. data/lib/webmock/matchers/hash_including_matcher.rb +6 -23
  33. data/lib/webmock/minitest.rb +2 -0
  34. data/lib/webmock/rack_response.rb +3 -1
  35. data/lib/webmock/request_body_diff.rb +3 -1
  36. data/lib/webmock/request_execution_verifier.rb +4 -3
  37. data/lib/webmock/request_pattern.rb +132 -52
  38. data/lib/webmock/request_registry.rb +3 -1
  39. data/lib/webmock/request_signature.rb +5 -3
  40. data/lib/webmock/request_signature_snippet.rb +6 -4
  41. data/lib/webmock/request_stub.rb +34 -0
  42. data/lib/webmock/response.rb +22 -14
  43. data/lib/webmock/responses_sequence.rb +2 -0
  44. data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +2 -0
  45. data/lib/webmock/rspec/matchers/webmock_matcher.rb +2 -0
  46. data/lib/webmock/rspec/matchers.rb +2 -0
  47. data/lib/webmock/rspec.rb +12 -3
  48. data/lib/webmock/stub_registry.rb +28 -11
  49. data/lib/webmock/stub_request_snippet.rb +12 -6
  50. data/lib/webmock/test_unit.rb +3 -3
  51. data/lib/webmock/util/hash_counter.rb +15 -9
  52. data/lib/webmock/util/hash_keys_stringifier.rb +2 -0
  53. data/lib/webmock/util/hash_validator.rb +2 -0
  54. data/lib/webmock/util/headers.rb +39 -11
  55. data/lib/webmock/util/json.rb +3 -2
  56. data/lib/webmock/util/query_mapper.rb +11 -7
  57. data/lib/webmock/util/uri.rb +13 -11
  58. data/lib/webmock/util/values_stringifier.rb +22 -0
  59. data/lib/webmock/util/version_checker.rb +7 -5
  60. data/lib/webmock/version.rb +3 -1
  61. data/lib/webmock/webmock.rb +22 -3
  62. data/lib/webmock.rb +55 -48
  63. metadata +106 -175
  64. data/.gemtest +0 -0
  65. data/.gitignore +0 -34
  66. data/.rspec-tm +0 -2
  67. data/.travis.yml +0 -20
  68. data/Gemfile +0 -9
  69. data/Rakefile +0 -30
  70. data/minitest/test_helper.rb +0 -34
  71. data/minitest/test_webmock.rb +0 -9
  72. data/minitest/webmock_spec.rb +0 -60
  73. data/spec/acceptance/curb/curb_spec.rb +0 -466
  74. data/spec/acceptance/curb/curb_spec_helper.rb +0 -147
  75. data/spec/acceptance/em_http_request/em_http_request_spec.rb +0 -406
  76. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +0 -77
  77. data/spec/acceptance/excon/excon_spec.rb +0 -75
  78. data/spec/acceptance/excon/excon_spec_helper.rb +0 -50
  79. data/spec/acceptance/http_rb/http_rb_spec.rb +0 -73
  80. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +0 -51
  81. data/spec/acceptance/httpclient/httpclient_spec.rb +0 -210
  82. data/spec/acceptance/httpclient/httpclient_spec_helper.rb +0 -57
  83. data/spec/acceptance/manticore/manticore_spec.rb +0 -56
  84. data/spec/acceptance/manticore/manticore_spec_helper.rb +0 -35
  85. data/spec/acceptance/net_http/net_http_shared.rb +0 -153
  86. data/spec/acceptance/net_http/net_http_spec.rb +0 -317
  87. data/spec/acceptance/net_http/net_http_spec_helper.rb +0 -64
  88. data/spec/acceptance/net_http/real_net_http_spec.rb +0 -20
  89. data/spec/acceptance/patron/patron_spec.rb +0 -118
  90. data/spec/acceptance/patron/patron_spec_helper.rb +0 -54
  91. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +0 -313
  92. data/spec/acceptance/shared/callbacks.rb +0 -147
  93. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +0 -36
  94. data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +0 -95
  95. data/spec/acceptance/shared/precedence_of_stubs.rb +0 -15
  96. data/spec/acceptance/shared/request_expectations.rb +0 -916
  97. data/spec/acceptance/shared/returning_declared_responses.rb +0 -388
  98. data/spec/acceptance/shared/stubbing_requests.rb +0 -583
  99. data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +0 -135
  100. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +0 -60
  101. data/spec/acceptance/webmock_shared.rb +0 -41
  102. data/spec/fixtures/test.txt +0 -1
  103. data/spec/quality_spec.rb +0 -84
  104. data/spec/spec_helper.rb +0 -48
  105. data/spec/support/example_curl_output.txt +0 -22
  106. data/spec/support/failures.rb +0 -9
  107. data/spec/support/my_rack_app.rb +0 -53
  108. data/spec/support/network_connection.rb +0 -19
  109. data/spec/support/webmock_server.rb +0 -69
  110. data/spec/unit/api_spec.rb +0 -75
  111. data/spec/unit/errors_spec.rb +0 -129
  112. data/spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb +0 -17
  113. data/spec/unit/http_lib_adapters/http_lib_adapter_spec.rb +0 -12
  114. data/spec/unit/matchers/hash_including_matcher_spec.rb +0 -87
  115. data/spec/unit/rack_response_spec.rb +0 -112
  116. data/spec/unit/request_body_diff_spec.rb +0 -90
  117. data/spec/unit/request_execution_verifier_spec.rb +0 -208
  118. data/spec/unit/request_pattern_spec.rb +0 -590
  119. data/spec/unit/request_registry_spec.rb +0 -95
  120. data/spec/unit/request_signature_snippet_spec.rb +0 -89
  121. data/spec/unit/request_signature_spec.rb +0 -155
  122. data/spec/unit/request_stub_spec.rb +0 -199
  123. data/spec/unit/response_spec.rb +0 -282
  124. data/spec/unit/stub_registry_spec.rb +0 -103
  125. data/spec/unit/stub_request_snippet_spec.rb +0 -95
  126. data/spec/unit/util/hash_counter_spec.rb +0 -39
  127. data/spec/unit/util/hash_keys_stringifier_spec.rb +0 -27
  128. data/spec/unit/util/headers_spec.rb +0 -28
  129. data/spec/unit/util/json_spec.rb +0 -33
  130. data/spec/unit/util/query_mapper_spec.rb +0 -144
  131. data/spec/unit/util/uri_spec.rb +0 -299
  132. data/spec/unit/util/version_checker_spec.rb +0 -65
  133. data/spec/unit/webmock_spec.rb +0 -11
  134. data/test/http_request.rb +0 -24
  135. data/test/shared_test.rb +0 -95
  136. data/test/test_helper.rb +0 -23
  137. data/test/test_webmock.rb +0 -6
  138. data/webmock.gemspec +0 -46
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'curb'
3
5
  rescue LoadError
@@ -5,7 +7,7 @@ rescue LoadError
5
7
  end
6
8
 
7
9
  if defined?(Curl)
8
- WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', '0.9.1', ['0.8.7']).check_version!
10
+ WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', '1.0.1', ['0.8.7']).check_version!
9
11
 
10
12
  module WebMock
11
13
  module HttpLibAdapters
@@ -54,7 +56,6 @@ if defined?(Curl)
54
56
 
55
57
  module Curl
56
58
  class WebMockCurlEasy < Curl::Easy
57
-
58
59
  def curb_or_webmock
59
60
  request_signature = build_request_signature
60
61
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -129,7 +130,7 @@ if defined?(Curl)
129
130
  def headers_as_hash(headers)
130
131
  if headers.is_a?(Array)
131
132
  headers.inject({}) {|hash, header|
132
- name, value = header.split(":").map(&:strip)
133
+ name, value = header.split(":", 2).map(&:strip)
133
134
  hash[name] = value
134
135
  hash
135
136
  }
@@ -153,7 +154,7 @@ if defined?(Curl)
153
154
  @body_str = webmock_response.body
154
155
  @response_code = webmock_response.status[0]
155
156
 
156
- @header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n"
157
+ @header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n".dup
157
158
 
158
159
  @on_debug.call(@header_str, 1) if defined?( @on_debug )
159
160
 
@@ -183,7 +184,7 @@ if defined?(Curl)
183
184
  self.url = location
184
185
 
185
186
  curb_or_webmock do
186
- send( "http_#{@webmock_method}_without_webmock" )
187
+ send( :http, {'method' => @webmock_method} )
187
188
  end
188
189
 
189
190
  self.url = first_url
@@ -271,6 +272,8 @@ if defined?(Curl)
271
272
  def perform
272
273
  @webmock_method ||= :get
273
274
  curb_or_webmock { super }
275
+ ensure
276
+ reset_webmock_method
274
277
  end
275
278
 
276
279
  def put_data= data
@@ -332,6 +335,19 @@ if defined?(Curl)
332
335
  end
333
336
  METHOD
334
337
  end
338
+
339
+ def reset_webmock_method
340
+ @webmock_method = :get
341
+ end
342
+
343
+ def reset
344
+ instance_variable_set(:@body_str, nil)
345
+ instance_variable_set(:@content_type, nil)
346
+ instance_variable_set(:@header_str, nil)
347
+ instance_variable_set(:@last_effective_url, nil)
348
+ instance_variable_set(:@response_code, nil)
349
+ super
350
+ end
335
351
  end
336
352
  end
337
353
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'em-http-request'
3
5
  rescue LoadError
@@ -99,6 +101,11 @@ if defined?(EventMachine::HttpClient)
99
101
  end
100
102
  end
101
103
 
104
+ def connection_completed
105
+ @state = :response_header
106
+ send_request(*headers_and_body_processed_by_middleware)
107
+ end
108
+
102
109
  def send_request(head, body)
103
110
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
104
111
 
@@ -107,7 +114,7 @@ if defined?(EventMachine::HttpClient)
107
114
  @uri ||= nil
108
115
  EM.next_tick {
109
116
  setup(make_raw_response(stubbed_webmock_response), @uri,
110
- stubbed_webmock_response.should_timeout ? "WebMock timeout error" : nil)
117
+ stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
111
118
  }
112
119
  self
113
120
  elsif WebMock.net_connect_allowed?(request_signature.uri)
@@ -149,7 +156,7 @@ if defined?(EventMachine::HttpClient)
149
156
  raw_cookie = response_header.cookie
150
157
  raw_cookie = [raw_cookie] if raw_cookie.is_a? String
151
158
 
152
- cookie = raw_cookie.select { |c| c.start_with? name }.first
159
+ cookie = raw_cookie.detect { |c| c.start_with? name }
153
160
  cookie and cookie.split('=', 2)[1]
154
161
  end
155
162
 
@@ -163,12 +170,18 @@ if defined?(EventMachine::HttpClient)
163
170
  webmock_response
164
171
  end
165
172
 
166
- def build_request_signature
167
- headers, body = @req.headers, @req.body
168
-
169
- @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
170
- headers, body = m.request(self, headers, body)
173
+ def headers_and_body_processed_by_middleware
174
+ @headers_and_body_processed_by_middleware ||= begin
175
+ head, body = build_request, @req.body
176
+ @conn.middleware.each do |m|
177
+ head, body = m.request(self, head, body) if m.respond_to?(:request)
178
+ end
179
+ [head, body]
171
180
  end
181
+ end
182
+
183
+ def build_request_signature
184
+ headers, body = headers_and_body_processed_by_middleware
172
185
 
173
186
  method = @req.method
174
187
  uri = @req.uri.clone
@@ -178,8 +191,6 @@ if defined?(EventMachine::HttpClient)
178
191
 
179
192
  body = form_encode_body(body) if body.is_a?(Hash)
180
193
 
181
- headers = @req.headers
182
-
183
194
  if headers['authorization'] && headers['authorization'].is_a?(Array)
184
195
  headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(headers.delete('authorization'))
185
196
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'excon'
3
5
  rescue LoadError
@@ -92,7 +94,7 @@ if defined?(Excon)
92
94
  end
93
95
 
94
96
  def self.to_query(hash)
95
- string = ""
97
+ string = "".dup
96
98
  for key, values in hash
97
99
  if values.nil?
98
100
  string << key.to_s << '&'
@@ -152,11 +154,14 @@ if defined?(Excon)
152
154
  end
153
155
 
154
156
  Excon::Connection.class_eval do
155
- def self.new(args)
157
+ def self.new(args = {})
156
158
  args.delete(:__construction_args)
157
159
  super(args).tap do |instance|
158
160
  instance.data[:__construction_args] = args
159
161
  end
160
162
  end
161
163
  end
164
+
165
+ # Suppresses Excon connection argument validation warning
166
+ Excon::VALID_CONNECTION_KEYS << :__construction_args
162
167
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
  class HttpLibAdapter
3
5
  def self.adapter_for(lib)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module WebMock
2
4
  class HttpLibAdapterRegistry
3
5
  include Singleton
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  class Client
3
5
  alias_method :__perform__, :perform
4
6
 
5
7
  def perform(request, options)
6
8
  return __perform__(request, options) unless webmock_enabled?
7
- WebMockPerform.new(request) { __perform__(request, options) }.exec
9
+
10
+ WebMockPerform.new(request, options) { __perform__(request, options) }.exec
8
11
  end
9
12
 
10
13
  def webmock_enabled?
@@ -1,9 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  class Request
3
5
  def webmock_signature
6
+ request_body = nil
7
+
8
+ if defined?(HTTP::Request::Body)
9
+ request_body = String.new
10
+ first_chunk_encoding = nil
11
+ body.each do |part|
12
+ request_body << part
13
+ first_chunk_encoding ||= part.encoding
14
+ end
15
+
16
+ request_body.force_encoding(first_chunk_encoding) if first_chunk_encoding
17
+ request_body
18
+ else
19
+ request_body = body
20
+ end
21
+
4
22
  ::WebMock::RequestSignature.new(verb, uri.to_s, {
5
23
  headers: headers.to_h,
6
- body: body
24
+ body: request_body
7
25
  })
8
26
  end
9
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  class Response
3
5
  def to_webmock
@@ -11,20 +13,44 @@ module HTTP
11
13
  end
12
14
 
13
15
  class << self
14
- def from_webmock(webmock_response, request_signature = nil)
16
+ def from_webmock(request, webmock_response, request_signature = nil)
15
17
  status = Status.new(webmock_response.status.first)
16
18
  headers = webmock_response.headers || {}
17
- body = Body.new Streamer.new webmock_response.body
18
19
  uri = normalize_uri(request_signature && request_signature.uri)
19
20
 
21
+ # HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
22
+ # and 2.x use a positional argument, and 0.x don't support supplying
23
+ # the encoding.
24
+ body = if HTTP::VERSION < "1.0.0"
25
+ Body.new(Streamer.new(webmock_response.body))
26
+ elsif HTTP::VERSION < "3.0.0"
27
+ Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
28
+ else
29
+ Body.new(
30
+ Streamer.new(webmock_response.body, encoding: webmock_response.body.encoding),
31
+ encoding: webmock_response.body.encoding
32
+ )
33
+ end
34
+
20
35
  return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
21
36
 
37
+ # 5.0.0 had a breaking change to require request instead of uri.
38
+ if HTTP::VERSION < '5.0.0'
39
+ return new({
40
+ status: status,
41
+ version: "1.1",
42
+ headers: headers,
43
+ body: body,
44
+ uri: uri
45
+ })
46
+ end
47
+
22
48
  new({
23
49
  status: status,
24
50
  version: "1.1",
25
51
  headers: headers,
26
52
  body: body,
27
- uri: uri
53
+ request: request,
28
54
  })
29
55
  end
30
56
 
@@ -1,11 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  class Response
3
5
  class Streamer
4
- def initialize(str)
6
+ def initialize(str, encoding: Encoding::BINARY)
5
7
  @io = StringIO.new str
8
+ @encoding = encoding
6
9
  end
7
10
 
8
- def readpartial(size = nil)
11
+ def readpartial(size = nil, outbuf = nil)
9
12
  unless size
10
13
  if defined?(HTTP::Client::BUFFER_SIZE)
11
14
  size = HTTP::Client::BUFFER_SIZE
@@ -14,7 +17,12 @@ module HTTP
14
17
  end
15
18
  end
16
19
 
17
- @io.read size
20
+ chunk = @io.read size, outbuf
21
+ chunk.force_encoding(@encoding) if chunk
22
+ end
23
+
24
+ def close
25
+ @io.close
18
26
  end
19
27
 
20
28
  def sequence_id
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTTP
2
4
  class WebMockPerform
3
- def initialize(request, &perform)
5
+ def initialize(request, options, &perform)
4
6
  @request = request
7
+ @options = options
5
8
  @perform = perform
6
9
  @request_signature = nil
7
10
  end
@@ -38,12 +41,15 @@ module HTTP
38
41
  webmock_response.raise_error_if_any
39
42
 
40
43
  invoke_callbacks(webmock_response, real_request: false)
41
- ::HTTP::Response.from_webmock webmock_response, request_signature
44
+ response = ::HTTP::Response.from_webmock @request, webmock_response, request_signature
45
+
46
+ @options.features.each { |_name, feature| response = feature.wrap_response(response) }
47
+ response
42
48
  end
43
49
 
44
50
  def raise_timeout_error
45
51
  raise Errno::ETIMEDOUT if HTTP::VERSION < "1.0.0"
46
- raise HTTP::ConnectionError, "connection error: #{Errno::ETIMEDOUT.new}"
52
+ raise HTTP::TimeoutError, "connection error: #{Errno::ETIMEDOUT.new}"
47
53
  end
48
54
 
49
55
  def perform
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require "http"
3
5
  rescue LoadError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'httpclient'
3
5
  require 'jsonclient' # defined in 'httpclient' gem as well
@@ -43,6 +45,9 @@ if defined?(::HTTPClient)
43
45
  end
44
46
 
45
47
  module WebMockHTTPClients
48
+
49
+ REQUEST_RESPONSE_LOCK = Mutex.new
50
+
46
51
  def do_get_block(req, proxy, conn, &block)
47
52
  do_get(req, proxy, conn, false, &block)
48
53
  end
@@ -57,7 +62,7 @@ if defined?(::HTTPClient)
57
62
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
58
63
 
59
64
  if webmock_responses[request_signature]
60
- webmock_response = webmock_responses.delete(request_signature)
65
+ webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
61
66
  response = build_httpclient_response(webmock_response, stream, req.header, &block)
62
67
  @request_filter.each do |filter|
63
68
  filter.filter_response(req, response)
@@ -68,15 +73,17 @@ if defined?(::HTTPClient)
68
73
  res
69
74
  elsif WebMock.net_connect_allowed?(request_signature.uri)
70
75
  # in case there is a nil entry in the hash...
71
- webmock_responses.delete(request_signature)
76
+ synchronize_request_response { webmock_responses.delete(request_signature) }
72
77
 
73
78
  res = if stream
74
79
  do_get_stream_without_webmock(req, proxy, conn, &block)
75
80
  elsif block
76
81
  body = ''
77
82
  do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
78
- body += chunk
79
- block.call(http_res, chunk)
83
+ if chunk && chunk.bytesize > 0
84
+ body += chunk
85
+ block.call(http_res, chunk)
86
+ end
80
87
  end
81
88
  else
82
89
  do_get_block_without_webmock(req, proxy, conn)
@@ -98,7 +105,7 @@ if defined?(::HTTPClient)
98
105
  def do_request_async(method, uri, query, body, extheader)
99
106
  req = create_request(method, uri, query, body, extheader)
100
107
  request_signature = build_request_signature(req)
101
- webmock_request_signatures << request_signature
108
+ synchronize_request_response { webmock_request_signatures << request_signature }
102
109
 
103
110
  if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
104
111
  super
@@ -117,7 +124,7 @@ if defined?(::HTTPClient)
117
124
  raise HTTPClient::TimeoutError if webmock_response.should_timeout
118
125
  webmock_response.raise_error_if_any
119
126
 
120
- block.call(response, body) if block
127
+ block.call(response, body) if block && body && body.bytesize > 0
121
128
 
122
129
  response
123
130
  end
@@ -182,7 +189,9 @@ if defined?(::HTTPClient)
182
189
 
183
190
  def webmock_responses
184
191
  @webmock_responses ||= Hash.new do |hash, request_signature|
185
- hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
192
+ synchronize_request_response do
193
+ hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
194
+ end
186
195
  end
187
196
  end
188
197
 
@@ -191,8 +200,10 @@ if defined?(::HTTPClient)
191
200
  end
192
201
 
193
202
  def previous_signature_for(signature)
194
- return nil unless index = webmock_request_signatures.index(signature)
195
- webmock_request_signatures.delete_at(index)
203
+ synchronize_request_response do
204
+ return nil unless index = webmock_request_signatures.index(signature)
205
+ webmock_request_signatures.delete_at(index)
206
+ end
196
207
  end
197
208
 
198
209
  private
@@ -207,6 +218,16 @@ if defined?(::HTTPClient)
207
218
  hdrs
208
219
  end
209
220
  end
221
+
222
+ def synchronize_request_response
223
+ if REQUEST_RESPONSE_LOCK.owned?
224
+ yield
225
+ else
226
+ REQUEST_RESPONSE_LOCK.synchronize do
227
+ yield
228
+ end
229
+ end
230
+ end
210
231
  end
211
232
 
212
233
  class WebMockHTTPClient < HTTPClient
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'manticore'
3
5
  rescue LoadError
@@ -24,6 +26,12 @@ if defined?(Manticore)
24
26
  Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
25
27
  end
26
28
 
29
+ class StubbedTimeoutResponse < Manticore::StubbedResponse
30
+ def call
31
+ @handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)"))
32
+ end
33
+ end
34
+
27
35
  class WebMockManticoreClient < Manticore::Client
28
36
  def request(klass, url, options={}, &block)
29
37
  super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
@@ -50,19 +58,22 @@ if defined?(Manticore)
50
58
 
51
59
  if webmock_response = registered_response_for(request_signature)
52
60
  webmock_response.raise_error_if_any
53
- manticore_response = generate_manticore_response(webmock_response).call
54
- real_request = false
61
+ manticore_response = generate_manticore_response(webmock_response)
62
+ manticore_response.on_success do
63
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: false}, request_signature, webmock_response)
64
+ end
55
65
 
56
66
  elsif real_request_allowed?(request_signature.uri)
57
- manticore_response = Manticore::Response.new(self, request, context, &block).call
58
- webmock_response = generate_webmock_response(manticore_response)
59
- real_request = true
67
+ manticore_response = Manticore::Response.new(self, request, context, &block)
68
+ manticore_response.on_complete do |completed_response|
69
+ webmock_response = generate_webmock_response(completed_response)
70
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: true}, request_signature, webmock_response)
71
+ end
60
72
 
61
73
  else
62
74
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
63
75
  end
64
76
 
65
- WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: real_request}, request_signature, webmock_response)
66
77
  manticore_response
67
78
  end
68
79
 
@@ -103,21 +114,30 @@ if defined?(Manticore)
103
114
  end
104
115
 
105
116
  def generate_manticore_response(webmock_response)
106
- raise Manticore::ConnectTimeout if webmock_response.should_timeout
107
-
108
- Manticore::StubbedResponse.stub(
109
- code: webmock_response.status[0],
110
- body: webmock_response.body,
111
- headers: webmock_response.headers,
112
- cookies: {}
113
- )
117
+ if webmock_response.should_timeout
118
+ StubbedTimeoutResponse.new
119
+ else
120
+ Manticore::StubbedResponse.stub(
121
+ code: webmock_response.status[0],
122
+ body: webmock_response.body,
123
+ headers: webmock_response.headers,
124
+ cookies: {}
125
+ )
126
+ end
114
127
  end
115
128
 
116
129
  def generate_webmock_response(manticore_response)
117
130
  webmock_response = WebMock::Response.new
118
131
  webmock_response.status = [manticore_response.code, manticore_response.message]
119
- webmock_response.body = manticore_response.body
120
132
  webmock_response.headers = manticore_response.headers
133
+
134
+ # The attempt to read the body could fail if manticore is used in a streaming mode
135
+ webmock_response.body = begin
136
+ manticore_response.body
137
+ rescue ::Manticore::StreamClosedException
138
+ nil
139
+ end
140
+
121
141
  webmock_response
122
142
  end
123
143
  end