webmock 1.8.6 → 3.14.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 +7 -0
  2. data/.github/workflows/CI.yml +37 -0
  3. data/.gitignore +6 -0
  4. data/CHANGELOG.md +1198 -0
  5. data/Gemfile +3 -15
  6. data/README.md +761 -305
  7. data/Rakefile +13 -40
  8. data/lib/webmock/api.rb +63 -17
  9. data/lib/webmock/callback_registry.rb +1 -1
  10. data/lib/webmock/config.rb +8 -0
  11. data/lib/webmock/cucumber.rb +2 -0
  12. data/lib/webmock/errors.rb +8 -24
  13. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +216 -0
  14. data/lib/webmock/http_lib_adapters/curb_adapter.rb +148 -84
  15. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +224 -4
  16. data/lib/webmock/http_lib_adapters/excon_adapter.rb +104 -34
  17. data/lib/webmock/http_lib_adapters/http_rb/client.rb +17 -0
  18. data/lib/webmock/http_lib_adapters/http_rb/request.rb +16 -0
  19. data/lib/webmock/http_lib_adapters/http_rb/response.rb +64 -0
  20. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +29 -0
  21. data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +68 -0
  22. data/lib/webmock/http_lib_adapters/http_rb_adapter.rb +37 -0
  23. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +152 -86
  24. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +145 -0
  25. data/lib/webmock/http_lib_adapters/net_http.rb +155 -46
  26. data/lib/webmock/http_lib_adapters/net_http_response.rb +1 -1
  27. data/lib/webmock/http_lib_adapters/patron_adapter.rb +16 -15
  28. data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +76 -82
  29. data/lib/webmock/matchers/any_arg_matcher.rb +13 -0
  30. data/lib/webmock/matchers/hash_argument_matcher.rb +21 -0
  31. data/lib/webmock/matchers/hash_excluding_matcher.rb +15 -0
  32. data/lib/webmock/matchers/hash_including_matcher.rb +4 -12
  33. data/lib/webmock/minitest.rb +29 -3
  34. data/lib/webmock/rack_response.rb +14 -7
  35. data/lib/webmock/request_body_diff.rb +64 -0
  36. data/lib/webmock/request_execution_verifier.rb +38 -17
  37. data/lib/webmock/request_pattern.rb +158 -38
  38. data/lib/webmock/request_registry.rb +3 -3
  39. data/lib/webmock/request_signature.rb +7 -3
  40. data/lib/webmock/request_signature_snippet.rb +61 -0
  41. data/lib/webmock/request_stub.rb +9 -6
  42. data/lib/webmock/response.rb +30 -15
  43. data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +38 -2
  44. data/lib/webmock/rspec/matchers/webmock_matcher.rb +23 -2
  45. data/lib/webmock/rspec/matchers.rb +0 -1
  46. data/lib/webmock/rspec.rb +11 -2
  47. data/lib/webmock/stub_registry.rb +31 -10
  48. data/lib/webmock/stub_request_snippet.rb +14 -6
  49. data/lib/webmock/test_unit.rb +4 -4
  50. data/lib/webmock/util/hash_counter.rb +20 -6
  51. data/lib/webmock/util/hash_keys_stringifier.rb +5 -3
  52. data/lib/webmock/util/hash_validator.rb +17 -0
  53. data/lib/webmock/util/headers.rb +23 -2
  54. data/lib/webmock/util/json.rb +20 -7
  55. data/lib/webmock/util/query_mapper.rb +281 -0
  56. data/lib/webmock/util/uri.rb +29 -19
  57. data/lib/webmock/util/values_stringifier.rb +20 -0
  58. data/lib/webmock/util/version_checker.rb +40 -2
  59. data/lib/webmock/version.rb +1 -1
  60. data/lib/webmock/webmock.rb +56 -17
  61. data/lib/webmock.rb +56 -46
  62. data/minitest/test_helper.rb +8 -3
  63. data/minitest/test_webmock.rb +4 -1
  64. data/minitest/webmock_spec.rb +16 -6
  65. data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
  66. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  67. data/spec/acceptance/curb/curb_spec.rb +227 -68
  68. data/spec/acceptance/curb/curb_spec_helper.rb +11 -8
  69. data/spec/acceptance/em_http_request/em_http_request_spec.rb +322 -28
  70. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +15 -10
  71. data/spec/acceptance/excon/excon_spec.rb +66 -4
  72. data/spec/acceptance/excon/excon_spec_helper.rb +21 -7
  73. data/spec/acceptance/http_rb/http_rb_spec.rb +93 -0
  74. data/spec/acceptance/http_rb/http_rb_spec_helper.rb +54 -0
  75. data/spec/acceptance/httpclient/httpclient_spec.rb +152 -11
  76. data/spec/acceptance/httpclient/httpclient_spec_helper.rb +25 -16
  77. data/spec/acceptance/manticore/manticore_spec.rb +107 -0
  78. data/spec/acceptance/manticore/manticore_spec_helper.rb +35 -0
  79. data/spec/acceptance/net_http/net_http_shared.rb +52 -24
  80. data/spec/acceptance/net_http/net_http_spec.rb +164 -50
  81. data/spec/acceptance/net_http/net_http_spec_helper.rb +19 -10
  82. data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
  83. data/spec/acceptance/patron/patron_spec.rb +29 -40
  84. data/spec/acceptance/patron/patron_spec_helper.rb +15 -11
  85. data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +229 -58
  86. data/spec/acceptance/shared/callbacks.rb +32 -30
  87. data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +20 -5
  88. data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +14 -14
  89. data/spec/acceptance/shared/precedence_of_stubs.rb +6 -6
  90. data/spec/acceptance/shared/request_expectations.rb +560 -296
  91. data/spec/acceptance/shared/returning_declared_responses.rb +180 -138
  92. data/spec/acceptance/shared/stubbing_requests.rb +385 -154
  93. data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +78 -17
  94. data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +19 -15
  95. data/spec/acceptance/webmock_shared.rb +2 -2
  96. data/spec/fixtures/test.txt +1 -0
  97. data/spec/quality_spec.rb +27 -3
  98. data/spec/spec_helper.rb +11 -20
  99. data/spec/support/failures.rb +9 -0
  100. data/spec/support/my_rack_app.rb +8 -3
  101. data/spec/support/network_connection.rb +7 -13
  102. data/spec/support/webmock_server.rb +8 -3
  103. data/spec/unit/api_spec.rb +175 -0
  104. data/spec/unit/errors_spec.rb +116 -19
  105. data/spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb +1 -1
  106. data/spec/unit/http_lib_adapters/http_lib_adapter_spec.rb +2 -2
  107. data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
  108. data/spec/unit/matchers/hash_including_matcher_spec.rb +87 -0
  109. data/spec/unit/rack_response_spec.rb +54 -16
  110. data/spec/unit/request_body_diff_spec.rb +90 -0
  111. data/spec/unit/request_execution_verifier_spec.rb +147 -39
  112. data/spec/unit/request_pattern_spec.rb +462 -198
  113. data/spec/unit/request_registry_spec.rb +29 -9
  114. data/spec/unit/request_signature_snippet_spec.rb +89 -0
  115. data/spec/unit/request_signature_spec.rb +91 -49
  116. data/spec/unit/request_stub_spec.rb +71 -70
  117. data/spec/unit/response_spec.rb +100 -81
  118. data/spec/unit/stub_registry_spec.rb +37 -20
  119. data/spec/unit/stub_request_snippet_spec.rb +51 -31
  120. data/spec/unit/util/hash_counter_spec.rb +6 -6
  121. data/spec/unit/util/hash_keys_stringifier_spec.rb +4 -4
  122. data/spec/unit/util/headers_spec.rb +4 -4
  123. data/spec/unit/util/json_spec.rb +29 -3
  124. data/spec/unit/util/query_mapper_spec.rb +157 -0
  125. data/spec/unit/util/uri_spec.rb +150 -36
  126. data/spec/unit/util/version_checker_spec.rb +15 -9
  127. data/spec/unit/webmock_spec.rb +57 -4
  128. data/test/http_request.rb +3 -3
  129. data/test/shared_test.rb +45 -13
  130. data/test/test_helper.rb +1 -1
  131. data/test/test_webmock.rb +6 -0
  132. data/webmock.gemspec +30 -11
  133. metadata +308 -199
  134. data/.rvmrc +0 -1
  135. data/.travis.yml +0 -11
  136. data/Guardfile +0 -24
  137. data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb +0 -151
  138. data/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +0 -210
@@ -1,7 +1,9 @@
1
1
  begin
2
2
  require 'httpclient'
3
+ require 'jsonclient' # defined in 'httpclient' gem as well
3
4
  rescue LoadError
4
5
  # httpclient not found
6
+ # or jsonclient not defined (in old versions of httclient gem)
5
7
  end
6
8
 
7
9
  if defined?(::HTTPClient)
@@ -11,59 +13,85 @@ if defined?(::HTTPClient)
11
13
  class HTTPClientAdapter < HttpLibAdapter
12
14
  adapter_for :httpclient
13
15
 
14
- OriginalHttpClient = ::HTTPClient unless const_defined?(:OriginalHttpClient)
16
+ unless const_defined?(:OriginalHttpClient)
17
+ OriginalHttpClient = ::HTTPClient
18
+ end
19
+
20
+ unless const_defined?(:OriginalJsonClient)
21
+ OriginalJsonClient = ::JSONClient if defined?(::JSONClient)
22
+ end
15
23
 
16
24
  def self.enable!
17
25
  Object.send(:remove_const, :HTTPClient)
18
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
19
31
  end
20
32
 
21
33
  def self.disable!
22
34
  Object.send(:remove_const, :HTTPClient)
23
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
24
40
  end
25
41
  end
26
42
  end
27
43
  end
28
44
 
45
+ module WebMockHTTPClients
29
46
 
30
- class WebMockHTTPClient < HTTPClient
47
+ REQUEST_RESPONSE_LOCK = Mutex.new
31
48
 
32
- def do_get_block_with_webmock(req, proxy, conn, &block)
33
- do_get_with_webmock(req, proxy, conn, false, &block)
49
+ def do_get_block(req, proxy, conn, &block)
50
+ do_get(req, proxy, conn, false, &block)
34
51
  end
35
52
 
36
- def do_get_stream_with_webmock(req, proxy, conn, &block)
37
- do_get_with_webmock(req, proxy, conn, true, &block)
53
+ def do_get_stream(req, proxy, conn, &block)
54
+ do_get(req, proxy, conn, true, &block)
38
55
  end
39
56
 
40
- def do_get_with_webmock(req, proxy, conn, stream = false, &block)
57
+ def do_get(req, proxy, conn, stream = false, &block)
41
58
  request_signature = build_request_signature(req, :reuse_existing)
42
59
 
43
60
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
44
61
 
45
62
  if webmock_responses[request_signature]
46
- webmock_response = webmock_responses.delete(request_signature)
47
- response = build_httpclient_response(webmock_response, stream, &block)
63
+ webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
64
+ response = build_httpclient_response(webmock_response, stream, req.header, &block)
48
65
  @request_filter.each do |filter|
49
66
  filter.filter_response(req, response)
50
67
  end
51
68
  res = conn.push(response)
52
69
  WebMock::CallbackRegistry.invoke_callbacks(
53
- {:lib => :httpclient}, request_signature, webmock_response)
70
+ {lib: :httpclient}, request_signature, webmock_response)
54
71
  res
55
72
  elsif WebMock.net_connect_allowed?(request_signature.uri)
73
+ # in case there is a nil entry in the hash...
74
+ synchronize_request_response { webmock_responses.delete(request_signature) }
75
+
56
76
  res = if stream
57
77
  do_get_stream_without_webmock(req, proxy, conn, &block)
78
+ elsif block
79
+ body = ''
80
+ do_get_block_without_webmock(req, proxy, conn) do |http_res, chunk|
81
+ if chunk && chunk.bytesize > 0
82
+ body += chunk
83
+ block.call(http_res, chunk)
84
+ end
85
+ end
58
86
  else
59
- do_get_block_without_webmock(req, proxy, conn, &block)
87
+ do_get_block_without_webmock(req, proxy, conn)
60
88
  end
61
89
  res = conn.pop
62
90
  conn.push(res)
63
91
  if WebMock::CallbackRegistry.any_callbacks?
64
- webmock_response = build_webmock_response(res)
92
+ webmock_response = build_webmock_response(res, body)
65
93
  WebMock::CallbackRegistry.invoke_callbacks(
66
- {:lib => :httpclient, :real_request => true}, request_signature,
94
+ {lib: :httpclient, real_request: true}, request_signature,
67
95
  webmock_response)
68
96
  end
69
97
  res
@@ -72,30 +100,21 @@ if defined?(::HTTPClient)
72
100
  end
73
101
  end
74
102
 
75
- def do_request_async_with_webmock(method, uri, query, body, extheader)
103
+ def do_request_async(method, uri, query, body, extheader)
76
104
  req = create_request(method, uri, query, body, extheader)
77
105
  request_signature = build_request_signature(req)
78
- webmock_request_signatures << request_signature
106
+ synchronize_request_response { webmock_request_signatures << request_signature }
79
107
 
80
108
  if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
81
- do_request_async_without_webmock(method, uri, query, body, extheader)
109
+ super
82
110
  else
83
111
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
84
112
  end
85
113
  end
86
114
 
87
- alias_method :do_get_block_without_webmock, :do_get_block
88
- alias_method :do_get_block, :do_get_block_with_webmock
89
-
90
- alias_method :do_get_stream_without_webmock, :do_get_stream
91
- alias_method :do_get_stream, :do_get_stream_with_webmock
92
-
93
- alias_method :do_request_async_without_webmock, :do_request_async
94
- alias_method :do_request_async, :do_request_async_with_webmock
95
-
96
- def build_httpclient_response(webmock_response, stream = false, &block)
115
+ def build_httpclient_response(webmock_response, stream = false, req_header = nil, &block)
97
116
  body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
98
- response = HTTP::Message.new_response(body)
117
+ response = HTTP::Message.new_response(body, req_header)
99
118
  response.header.init_response(webmock_response.status[0])
100
119
  response.reason=webmock_response.status[1]
101
120
  webmock_response.headers.to_a.each { |name, value| response.header.set(name, value) }
@@ -103,91 +122,138 @@ if defined?(::HTTPClient)
103
122
  raise HTTPClient::TimeoutError if webmock_response.should_timeout
104
123
  webmock_response.raise_error_if_any
105
124
 
106
- block.call(response, body) if block
125
+ block.call(response, body) if block && body && body.bytesize > 0
107
126
 
108
127
  response
109
128
  end
110
- end
111
129
 
112
- def build_webmock_response(httpclient_response)
113
- webmock_response = WebMock::Response.new
114
- webmock_response.status = [httpclient_response.status, httpclient_response.reason]
115
-
116
- webmock_response.headers = {}.tap do |hash|
117
- httpclient_response.header.all.each do |(key, value)|
118
- if hash.has_key?(key)
119
- hash[key] = Array(hash[key]) + [value]
120
- else
121
- hash[key] = value
130
+ def build_webmock_response(httpclient_response, body = nil)
131
+ webmock_response = WebMock::Response.new
132
+ webmock_response.status = [httpclient_response.status, httpclient_response.reason]
133
+
134
+ webmock_response.headers = {}.tap do |hash|
135
+ httpclient_response.header.all.each do |(key, value)|
136
+ if hash.has_key?(key)
137
+ hash[key] = Array(hash[key]) + [value]
138
+ else
139
+ hash[key] = value
140
+ end
122
141
  end
123
142
  end
124
- end
125
143
 
126
- if httpclient_response.content.respond_to?(:read)
127
- webmock_response.body = httpclient_response.content.read
128
- body = HTTP::Message::Body.new
129
- body.init_response(StringIO.new(webmock_response.body))
130
- httpclient_response.body = body
131
- else
132
- webmock_response.body = httpclient_response.content
144
+ if body
145
+ webmock_response.body = body
146
+ elsif httpclient_response.content.respond_to?(:read)
147
+ webmock_response.body = httpclient_response.content.read
148
+ body = HTTP::Message::Body.new
149
+ body.init_response(StringIO.new(webmock_response.body))
150
+ httpclient_response.body = body
151
+ else
152
+ webmock_response.body = httpclient_response.content
153
+ end
154
+ webmock_response
133
155
  end
134
- webmock_response
135
- end
136
156
 
137
- def build_request_signature(req, reuse_existing = false)
138
- uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
139
- uri.query_values = req.header.request_query if req.header.request_query
140
- uri.port = req.header.request_uri.port
141
- uri = uri.omit(:userinfo)
157
+ def build_request_signature(req, reuse_existing = false)
158
+ uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
159
+ uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query, notation: WebMock::Config.instance.query_values_notation) if req.header.request_query
160
+ uri.port = req.header.request_uri.port
161
+
162
+ @request_filter.each do |filter|
163
+ filter.filter_request(req)
164
+ end
165
+
166
+ headers = req.header.all.inject({}) do |hdrs, header|
167
+ hdrs[header[0]] ||= []
168
+ hdrs[header[0]] << header[1]
169
+ hdrs
170
+ end
171
+ headers = headers_from_session(uri).merge(headers)
172
+
173
+ signature = WebMock::RequestSignature.new(
174
+ req.header.request_method.downcase.to_sym,
175
+ uri.to_s,
176
+ body: req.http_body.dump,
177
+ headers: headers
178
+ )
179
+
180
+ # reuse a previous identical signature object if we stored one for later use
181
+ if reuse_existing && previous_signature = previous_signature_for(signature)
182
+ return previous_signature
183
+ end
142
184
 
143
- auth = www_auth.basic_auth
144
- auth.challenge(req.header.request_uri, nil)
185
+ signature
186
+ end
145
187
 
146
- @request_filter.each do |filter|
147
- filter.filter_request(req)
188
+ def webmock_responses
189
+ @webmock_responses ||= Hash.new do |hash, request_signature|
190
+ synchronize_request_response do
191
+ hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
192
+ end
193
+ end
148
194
  end
149
195
 
150
- headers = req.header.all.inject({}) do |hdrs, header|
151
- hdrs[header[0]] ||= []
152
- hdrs[header[0]] << header[1]
153
- hdrs
196
+ def webmock_request_signatures
197
+ @webmock_request_signatures ||= []
154
198
  end
155
199
 
156
- if (auth_cred = auth.get(req)) && auth.scheme == 'Basic'
157
- userinfo = WebMock::Util::Headers.decode_userinfo_from_header(auth_cred)
158
- userinfo = WebMock::Util::URI.encode_unsafe_chars_in_userinfo(userinfo)
159
- headers.reject! {|k,v| k =~ /[Aa]uthorization/ && v =~ /^Basic / } #we added it to url userinfo
160
- uri.userinfo = userinfo
200
+ def previous_signature_for(signature)
201
+ synchronize_request_response do
202
+ return nil unless index = webmock_request_signatures.index(signature)
203
+ webmock_request_signatures.delete_at(index)
204
+ end
161
205
  end
162
206
 
163
- signature = WebMock::RequestSignature.new(
164
- req.header.request_method.downcase.to_sym,
165
- uri.to_s,
166
- :body => req.content,
167
- :headers => headers
168
- )
207
+ private
169
208
 
170
- # reuse a previous identical signature object if we stored one for later use
171
- if reuse_existing && previous_signature = previous_signature_for(signature)
172
- return previous_signature
209
+ # some of the headers sent by HTTPClient are derived from
210
+ # the client session
211
+ def headers_from_session(uri)
212
+ session_headers = HTTP::Message::Headers.new
213
+ @session_manager.send(:open, uri).send(:set_header, MessageMock.new(session_headers))
214
+ session_headers.all.inject({}) do |hdrs, header|
215
+ hdrs[header[0]] = header[1]
216
+ hdrs
217
+ end
173
218
  end
174
219
 
175
- signature
220
+ def synchronize_request_response
221
+ if REQUEST_RESPONSE_LOCK.owned?
222
+ yield
223
+ else
224
+ REQUEST_RESPONSE_LOCK.synchronize do
225
+ yield
226
+ end
227
+ end
228
+ end
176
229
  end
177
230
 
178
- def webmock_responses
179
- @webmock_responses ||= Hash.new do |hash, request_signature|
180
- hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
181
- end
231
+ class WebMockHTTPClient < HTTPClient
232
+ alias_method :do_get_block_without_webmock, :do_get_block
233
+ alias_method :do_get_stream_without_webmock, :do_get_stream
234
+
235
+ include WebMockHTTPClients
182
236
  end
183
237
 
184
- def webmock_request_signatures
185
- @webmock_request_signatures ||= []
238
+ if defined? ::JSONClient
239
+ class WebMockJSONClient < JSONClient
240
+ alias_method :do_get_block_without_webmock, :do_get_block
241
+ alias_method :do_get_stream_without_webmock, :do_get_stream
242
+
243
+ include WebMockHTTPClients
244
+ end
186
245
  end
187
246
 
188
- def previous_signature_for(signature)
189
- return nil unless index = webmock_request_signatures.index(signature)
190
- webmock_request_signatures.delete_at(index)
247
+
248
+ # Mocks a HTTPClient HTTP::Message
249
+ class MessageMock
250
+ attr_reader :header
251
+
252
+ def initialize(headers)
253
+ @header = headers
254
+ end
255
+
256
+ def http_version=(value);end
191
257
  end
192
258
 
193
259
  end
@@ -0,0 +1,145 @@
1
+ begin
2
+ require 'manticore'
3
+ rescue LoadError
4
+ # manticore not found
5
+ end
6
+
7
+ if defined?(Manticore)
8
+ module WebMock
9
+ module HttpLibAdapters
10
+ class ManticoreAdapter < HttpLibAdapter
11
+ adapter_for :manticore
12
+
13
+ OriginalManticoreClient = Manticore::Client
14
+
15
+ def self.enable!
16
+ Manticore.send(:remove_const, :Client)
17
+ Manticore.send(:const_set, :Client, WebMockManticoreClient)
18
+ Manticore.instance_variable_set(:@manticore_facade, WebMockManticoreClient.new)
19
+ end
20
+
21
+ def self.disable!
22
+ Manticore.send(:remove_const, :Client)
23
+ Manticore.send(:const_set, :Client, OriginalManticoreClient)
24
+ Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
25
+ end
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
+
33
+ class WebMockManticoreClient < Manticore::Client
34
+ def request(klass, url, options={}, &block)
35
+ super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
36
+ end
37
+
38
+ private
39
+
40
+ def format_options(options)
41
+ return options unless headers = options[:headers]
42
+
43
+ options.merge(headers: join_array_values(headers))
44
+ end
45
+
46
+ def join_array_values(headers)
47
+ headers.reduce({}) do |h, (k,v)|
48
+ v = v.join(', ') if v.is_a?(Array)
49
+ h.merge(k => v)
50
+ end
51
+ end
52
+
53
+ def response_object_for(request, context, &block)
54
+ request_signature = generate_webmock_request_signature(request, context)
55
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
56
+
57
+ if webmock_response = registered_response_for(request_signature)
58
+ webmock_response.raise_error_if_any
59
+ manticore_response = generate_manticore_response(webmock_response)
60
+ manticore_response.on_success do
61
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: false}, request_signature, webmock_response)
62
+ end
63
+
64
+ elsif real_request_allowed?(request_signature.uri)
65
+ manticore_response = Manticore::Response.new(self, request, context, &block)
66
+ manticore_response.on_complete do |completed_response|
67
+ webmock_response = generate_webmock_response(completed_response)
68
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: true}, request_signature, webmock_response)
69
+ end
70
+
71
+ else
72
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
73
+ end
74
+
75
+ manticore_response
76
+ end
77
+
78
+ def registered_response_for(request_signature)
79
+ WebMock::StubRegistry.instance.response_for_request(request_signature)
80
+ end
81
+
82
+ def real_request_allowed?(uri)
83
+ WebMock.net_connect_allowed?(uri)
84
+ end
85
+
86
+ def generate_webmock_request_signature(request, context)
87
+ method = request.method.downcase
88
+ uri = request.uri.to_s
89
+ body = read_body(request)
90
+ headers = split_array_values(request.headers)
91
+
92
+ if context.get_credentials_provider && credentials = context.get_credentials_provider.get_credentials(AuthScope::ANY)
93
+ headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(credentials.get_user_name,credentials.get_password)
94
+ end
95
+
96
+ WebMock::RequestSignature.new(method, uri, {body: body, headers: headers})
97
+ end
98
+
99
+ def read_body(request)
100
+ if request.respond_to?(:entity) && !request.entity.nil?
101
+ Manticore::EntityConverter.new.read_entity(request.entity)
102
+ end
103
+ end
104
+
105
+ def split_array_values(headers = [])
106
+ headers.each_with_object({}) do |(k, v), h|
107
+ h[k] = case v
108
+ when /,/ then v.split(',').map(&:strip)
109
+ else v
110
+ end
111
+ end
112
+ end
113
+
114
+ def generate_manticore_response(webmock_response)
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
125
+ end
126
+
127
+ def generate_webmock_response(manticore_response)
128
+ webmock_response = WebMock::Response.new
129
+ webmock_response.status = [manticore_response.code, manticore_response.message]
130
+ webmock_response.headers = manticore_response.headers
131
+
132
+ # The attempt to read the body could fail if manticore is used in a streaming mode
133
+ webmock_response.body = begin
134
+ manticore_response.body
135
+ rescue ::Manticore::StreamClosedException
136
+ nil
137
+ end
138
+
139
+ webmock_response
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end