webmock 1.8.6 → 3.14.0

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.
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
@@ -5,7 +5,7 @@ rescue LoadError
5
5
  end
6
6
 
7
7
  if defined?(Curl)
8
- WebMock::VersionChecker.new('Curb', Gem.loaded_specs['curb'].version.to_s, '0.7.16').check_version!
8
+ WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', '0.9.1', ['0.8.7']).check_version!
9
9
 
10
10
  module WebMock
11
11
  module HttpLibAdapters
@@ -55,14 +55,13 @@ if defined?(Curl)
55
55
  module Curl
56
56
  class WebMockCurlEasy < Curl::Easy
57
57
  def curb_or_webmock
58
-
59
58
  request_signature = build_request_signature
60
59
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
61
60
 
62
61
  if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
63
62
  build_curb_response(webmock_response)
64
63
  WebMock::CallbackRegistry.invoke_callbacks(
65
- {:lib => :curb}, request_signature, webmock_response)
64
+ {lib: :curb}, request_signature, webmock_response)
66
65
  invoke_curb_callbacks
67
66
  true
68
67
  elsif WebMock.net_connect_allowed?(request_signature.uri)
@@ -70,7 +69,7 @@ if defined?(Curl)
70
69
  if WebMock::CallbackRegistry.any_callbacks?
71
70
  webmock_response = build_webmock_response
72
71
  WebMock::CallbackRegistry.invoke_callbacks(
73
- {:lib => :curb, :real_request => true}, request_signature,
72
+ {lib: :curb, real_request: true}, request_signature,
74
73
  webmock_response)
75
74
  end
76
75
  res
@@ -84,11 +83,11 @@ if defined?(Curl)
84
83
 
85
84
  uri = WebMock::Util::URI.heuristic_parse(self.url)
86
85
  uri.path = uri.normalized_path.gsub("[^:]//","/")
87
- uri.user = self.username
88
- uri.password = self.password
86
+
87
+ headers = headers_as_hash(self.headers).merge(basic_auth_headers)
89
88
 
90
89
  request_body = case method
91
- when :post
90
+ when :post, :patch
92
91
  self.post_body || @post_body
93
92
  when :put
94
93
  @put_data
@@ -96,15 +95,56 @@ if defined?(Curl)
96
95
  nil
97
96
  end
98
97
 
98
+ if defined?( @on_debug )
99
+ @on_debug.call("Trying 127.0.0.1...\r\n", 0)
100
+ @on_debug.call('Connected to ' + uri.hostname + "\r\n", 0)
101
+ @debug_method = method.upcase
102
+ @debug_path = uri.path
103
+ @debug_host = uri.hostname
104
+ http_request = ["#{@debug_method} #{@debug_path} HTTP/1.1"]
105
+ http_request << "Host: #{uri.hostname}"
106
+ headers.each do |name, value|
107
+ http_request << "#{name}: #{value}"
108
+ end
109
+ @on_debug.call(http_request.join("\r\n") + "\r\n\r\n", 2)
110
+ if request_body
111
+ @on_debug.call(request_body + "\r\n", 4)
112
+ @on_debug.call(
113
+ "upload completely sent off: #{request_body.bytesize}"\
114
+ " out of #{request_body.bytesize} bytes\r\n", 0
115
+ )
116
+ end
117
+ end
118
+
99
119
  request_signature = WebMock::RequestSignature.new(
100
120
  method,
101
121
  uri.to_s,
102
- :body => request_body,
103
- :headers => self.headers
122
+ body: request_body,
123
+ headers: headers
104
124
  )
105
125
  request_signature
106
126
  end
107
127
 
128
+ def headers_as_hash(headers)
129
+ if headers.is_a?(Array)
130
+ headers.inject({}) {|hash, header|
131
+ name, value = header.split(":").map(&:strip)
132
+ hash[name] = value
133
+ hash
134
+ }
135
+ else
136
+ headers
137
+ end
138
+ end
139
+
140
+ def basic_auth_headers
141
+ if self.username
142
+ {'Authorization' => WebMock::Util::Headers.basic_auth_header(self.username, self.password)}
143
+ else
144
+ {}
145
+ end
146
+ end
147
+
108
148
  def build_curb_response(webmock_response)
109
149
  raise Curl::Err::TimeoutError if webmock_response.should_timeout
110
150
  webmock_response.raise_error_if_any
@@ -112,11 +152,17 @@ if defined?(Curl)
112
152
  @body_str = webmock_response.body
113
153
  @response_code = webmock_response.status[0]
114
154
 
115
- @header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n"
155
+ @header_str = "HTTP/1.1 #{webmock_response.status[0]} #{webmock_response.status[1]}\r\n".dup
156
+
157
+ @on_debug.call(@header_str, 1) if defined?( @on_debug )
158
+
116
159
  if webmock_response.headers
117
160
  @header_str << webmock_response.headers.map do |k,v|
118
- "#{k}: #{v.is_a?(Array) ? v.join(", ") : v}"
161
+ header = "#{k}: #{v.is_a?(Array) ? v.join(", ") : v}"
162
+ @on_debug.call(header + "\r\n", 1) if defined?( @on_debug )
163
+ header
119
164
  end.join("\r\n")
165
+ @on_debug.call("\r\n", 1) if defined?( @on_debug )
120
166
 
121
167
  location = webmock_response.headers['Location']
122
168
  if self.follow_location? && location
@@ -125,6 +171,7 @@ if defined?(Curl)
125
171
  end
126
172
 
127
173
  @content_type = webmock_response.headers["Content-Type"]
174
+ @transfer_encoding = webmock_response.headers["Transfer-Encoding"]
128
175
  end
129
176
 
130
177
  @last_effective_url ||= self.url
@@ -135,30 +182,54 @@ if defined?(Curl)
135
182
  self.url = location
136
183
 
137
184
  curb_or_webmock do
138
- send( "http_#{@webmock_method}_without_webmock" )
185
+ send( :http, {'method' => @webmock_method} )
139
186
  end
140
187
 
141
188
  self.url = first_url
142
189
  end
143
190
 
144
191
  def invoke_curb_callbacks
145
- @on_progress.call(0.0,1.0,0.0,1.0) if @on_progress
146
- @on_header.call(self.header_str) if @on_header
147
- @on_body.call(self.body_str) if @on_body
148
- @on_complete.call(self) if @on_complete
192
+ @on_progress.call(0.0,1.0,0.0,1.0) if defined?( @on_progress )
193
+ self.header_str.lines.each { |header_line| @on_header.call header_line } if defined?( @on_header )
194
+ if defined?( @on_body )
195
+ if chunked_response?
196
+ self.body_str.each do |chunk|
197
+ @on_body.call(chunk)
198
+ end
199
+ else
200
+ @on_body.call(self.body_str)
201
+ end
202
+ end
203
+ @on_complete.call(self) if defined?( @on_complete )
149
204
 
150
205
  case response_code
151
206
  when 200..299
152
- @on_success.call(self) if @on_success
153
- when 400..599
154
- @on_failure.call(self, self.response_code) if @on_failure
207
+ @on_success.call(self) if defined?( @on_success )
208
+ when 400..499
209
+ @on_missing.call(self, self.response_code) if defined?( @on_missing )
210
+ when 500..599
211
+ @on_failure.call(self, self.response_code) if defined?( @on_failure )
155
212
  end
156
213
  end
157
214
 
215
+ def chunked_response?
216
+ defined?( @transfer_encoding ) && @transfer_encoding == 'chunked' && self.body_str.respond_to?(:each)
217
+ end
218
+
158
219
  def build_webmock_response
159
220
  status, headers =
160
221
  WebMock::HttpLibAdapters::CurbAdapter.parse_header_string(self.header_str)
161
222
 
223
+ if defined?( @on_debug )
224
+ http_response = ["HTTP/1.0 #{@debug_method} #{@debug_path}"]
225
+ headers.each do |name, value|
226
+ http_response << "#{name}: #{value}"
227
+ end
228
+ http_response << self.body_str
229
+ @on_debug.call(http_response.join("\r\n") + "\r\n", 3)
230
+ @on_debug.call("Connection #0 to host #{@debug_host} left intact\r\n", 0)
231
+ end
232
+
162
233
  webmock_response = WebMock::Response.new
163
234
  webmock_response.status = [self.response_code, status]
164
235
  webmock_response.body = self.body_str
@@ -170,117 +241,110 @@ if defined?(Curl)
170
241
  ### Mocks of Curl::Easy methods below here.
171
242
  ###
172
243
 
173
- def http_with_webmock(method)
244
+ def http(method)
174
245
  @webmock_method = method
175
- http_without_webmock(method)
246
+ super
176
247
  end
177
- alias_method :http_without_webmock, :http
178
- alias_method :http, :http_with_webmock
179
248
 
180
249
  %w[ get head delete ].each do |verb|
181
- define_method "http_#{verb}_with_webmock" do
250
+ define_method "http_#{verb}" do
182
251
  @webmock_method = verb
183
- send( "http_#{verb}_without_webmock" )
252
+ super()
184
253
  end
185
-
186
- alias_method "http_#{verb}_without_webmock", "http_#{verb}"
187
- alias_method "http_#{verb}", "http_#{verb}_with_webmock"
188
254
  end
189
255
 
190
- def http_put_with_webmock data = nil
256
+ def http_put data = nil
191
257
  @webmock_method = :put
192
258
  @put_data = data if data
193
- http_put_without_webmock(data)
259
+ super
194
260
  end
195
- alias_method :http_put_without_webmock, :http_put
196
- alias_method :http_put, :http_put_with_webmock
261
+ alias put http_put
197
262
 
198
- def http_post_with_webmock *data
263
+ def http_post *data
199
264
  @webmock_method = :post
200
265
  @post_body = data.join('&') if data && !data.empty?
201
- http_post_without_webmock(*data)
266
+ super
202
267
  end
203
- alias_method :http_post_without_webmock, :http_post
204
- alias_method :http_post, :http_post_with_webmock
268
+ alias post http_post
205
269
 
206
-
207
- def perform_with_webmock
270
+ def perform
208
271
  @webmock_method ||= :get
209
- curb_or_webmock do
210
- perform_without_webmock
211
- end
272
+ curb_or_webmock { super }
273
+ ensure
274
+ reset_webmock_method
212
275
  end
213
- alias :perform_without_webmock :perform
214
- alias :perform :perform_with_webmock
215
276
 
216
- def put_data_with_webmock= data
277
+ def put_data= data
217
278
  @webmock_method = :put
218
279
  @put_data = data
219
- self.put_data_without_webmock = data
280
+ super
220
281
  end
221
- alias_method :put_data_without_webmock=, :put_data=
222
- alias_method :put_data=, :put_data_with_webmock=
223
282
 
224
- def post_body_with_webmock= data
283
+ def post_body= data
225
284
  @webmock_method = :post
226
- self.post_body_without_webmock = data
285
+ super
227
286
  end
228
- alias_method :post_body_without_webmock=, :post_body=
229
- alias_method :post_body=, :post_body_with_webmock=
230
287
 
231
- def delete_with_webmock= value
288
+ def delete= value
232
289
  @webmock_method = :delete if value
233
- self.delete_without_webmock = value
290
+ super
234
291
  end
235
- alias_method :delete_without_webmock=, :delete=
236
- alias_method :delete=, :delete_with_webmock=
237
292
 
238
- def head_with_webmock= value
293
+ def head= value
239
294
  @webmock_method = :head if value
240
- self.head_without_webmock = value
295
+ super
296
+ end
297
+
298
+ def verbose=(verbose)
299
+ @verbose = verbose
300
+ end
301
+
302
+ def verbose?
303
+ @verbose ||= false
241
304
  end
242
- alias_method :head_without_webmock=, :head=
243
- alias_method :head=, :head_with_webmock=
244
305
 
245
- def body_str_with_webmock
246
- @body_str || body_str_without_webmock
306
+ def body_str
307
+ @body_str ||= super
247
308
  end
248
- alias :body_str_without_webmock :body_str
249
- alias :body_str :body_str_with_webmock
309
+ alias body body_str
250
310
 
251
- def response_code_with_webmock
252
- @response_code || response_code_without_webmock
311
+ def response_code
312
+ @response_code ||= super
253
313
  end
254
- alias :response_code_without_webmock :response_code
255
- alias :response_code :response_code_with_webmock
256
314
 
257
- def header_str_with_webmock
258
- @header_str || header_str_without_webmock
315
+ def header_str
316
+ @header_str ||= super
259
317
  end
260
- alias :header_str_without_webmock :header_str
261
- alias :header_str :header_str_with_webmock
318
+ alias head header_str
262
319
 
263
- def last_effective_url_with_webmock
264
- @last_effective_url || last_effective_url_without_webmock
320
+ def last_effective_url
321
+ @last_effective_url ||= super
265
322
  end
266
- alias :last_effective_url_without_webmock :last_effective_url
267
- alias :last_effective_url :last_effective_url_with_webmock
268
323
 
269
- def content_type_with_webmock
270
- @content_type || content_type_without_webmock
324
+ def content_type
325
+ @content_type ||= super
271
326
  end
272
- alias :content_type_without_webmock :content_type
273
- alias :content_type :content_type_with_webmock
274
327
 
275
- %w[ success failure header body complete progress ].each do |callback|
328
+ %w[ success failure missing header body complete progress debug ].each do |callback|
276
329
  class_eval <<-METHOD, __FILE__, __LINE__
277
- def on_#{callback}_with_webmock &block
330
+ def on_#{callback} &block
278
331
  @on_#{callback} = block
279
- on_#{callback}_without_webmock &block
332
+ super
280
333
  end
281
334
  METHOD
282
- alias_method "on_#{callback}_without_webmock", "on_#{callback}"
283
- alias_method "on_#{callback}", "on_#{callback}_with_webmock"
335
+ end
336
+
337
+ def reset_webmock_method
338
+ @webmock_method = :get
339
+ end
340
+
341
+ def reset
342
+ instance_variable_set(:@body_str, nil)
343
+ instance_variable_set(:@content_type, nil)
344
+ instance_variable_set(:@header_str, nil)
345
+ instance_variable_set(:@last_effective_url, nil)
346
+ instance_variable_set(:@response_code, nil)
347
+ super
284
348
  end
285
349
  end
286
350
  end
@@ -4,8 +4,228 @@ rescue LoadError
4
4
  # em-http-request not found
5
5
  end
6
6
 
7
- if defined?(EventMachine::HttpConnection)
8
- require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_1_x')
9
- else
10
- require File.expand_path(File.dirname(__FILE__) + '/em_http_request/em_http_request_0_x')
7
+ if defined?(EventMachine::HttpClient)
8
+ module WebMock
9
+ module HttpLibAdapters
10
+ class EmHttpRequestAdapter < HttpLibAdapter
11
+ adapter_for :em_http_request
12
+
13
+ OriginalHttpClient = EventMachine::HttpClient unless const_defined?(:OriginalHttpClient)
14
+ OriginalHttpConnection = EventMachine::HttpConnection unless const_defined?(:OriginalHttpConnection)
15
+
16
+ def self.enable!
17
+ EventMachine.send(:remove_const, :HttpConnection)
18
+ EventMachine.send(:const_set, :HttpConnection, EventMachine::WebMockHttpConnection)
19
+ EventMachine.send(:remove_const, :HttpClient)
20
+ EventMachine.send(:const_set, :HttpClient, EventMachine::WebMockHttpClient)
21
+ end
22
+
23
+ def self.disable!
24
+ EventMachine.send(:remove_const, :HttpConnection)
25
+ EventMachine.send(:const_set, :HttpConnection, OriginalHttpConnection)
26
+ EventMachine.send(:remove_const, :HttpClient)
27
+ EventMachine.send(:const_set, :HttpClient, OriginalHttpClient)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module EventMachine
34
+ if defined?(Synchrony) && HTTPMethods.instance_methods.include?(:aget)
35
+ # have to make the callbacks fire on the next tick in order
36
+ # to avoid the dreaded "double resume" exception
37
+ module HTTPMethods
38
+ %w[get head post delete put].each do |type|
39
+ class_eval %[
40
+ def #{type}(options = {}, &blk)
41
+ f = Fiber.current
42
+
43
+ conn = setup_request(:#{type}, options, &blk)
44
+ conn.callback { EM.next_tick { f.resume(conn) } }
45
+ conn.errback { EM.next_tick { f.resume(conn) } }
46
+
47
+ Fiber.yield
48
+ end
49
+ ]
50
+ end
51
+ end
52
+ end
53
+
54
+ class WebMockHttpConnection < HttpConnection
55
+ def activate_connection(client)
56
+ request_signature = client.request_signature
57
+
58
+ if client.stubbed_webmock_response
59
+ conn = HttpStubConnection.new rand(10000)
60
+ post_init
61
+
62
+ @deferred = false
63
+ @conn = conn
64
+
65
+ conn.parent = self
66
+ conn.pending_connect_timeout = @connopts.connect_timeout
67
+ conn.comm_inactivity_timeout = @connopts.inactivity_timeout
68
+
69
+ finalize_request(client)
70
+ @conn.set_deferred_status :succeeded
71
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
72
+ super
73
+ else
74
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
75
+ end
76
+ end
77
+
78
+ def drop_client
79
+ @clients.shift
80
+ end
81
+ end
82
+
83
+ class WebMockHttpClient < EventMachine::HttpClient
84
+ include HttpEncoding
85
+
86
+ def uri
87
+ @req.uri
88
+ end
89
+
90
+ def setup(response, uri, error = nil)
91
+ @last_effective_url = @uri = uri
92
+ if error
93
+ on_error(error)
94
+ @conn.drop_client
95
+ fail(self)
96
+ else
97
+ @conn.receive_data(response)
98
+ succeed(self)
99
+ end
100
+ end
101
+
102
+ def connection_completed
103
+ @state = :response_header
104
+ send_request(request_signature.headers, request_signature.body)
105
+ end
106
+
107
+ def send_request(head, body)
108
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
109
+
110
+ if stubbed_webmock_response
111
+ WebMock::CallbackRegistry.invoke_callbacks({lib: :em_http_request}, request_signature, stubbed_webmock_response)
112
+ @uri ||= nil
113
+ EM.next_tick {
114
+ setup(make_raw_response(stubbed_webmock_response), @uri,
115
+ stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
116
+ }
117
+ self
118
+ elsif WebMock.net_connect_allowed?(request_signature.uri)
119
+ super
120
+ else
121
+ raise WebMock::NetConnectNotAllowedError.new(request_signature)
122
+ end
123
+ end
124
+
125
+ def unbind(reason = nil)
126
+ if !stubbed_webmock_response && WebMock::CallbackRegistry.any_callbacks?
127
+ webmock_response = build_webmock_response
128
+ WebMock::CallbackRegistry.invoke_callbacks(
129
+ {lib: :em_http_request, real_request: true},
130
+ request_signature,
131
+ webmock_response)
132
+ end
133
+ @request_signature = nil
134
+ remove_instance_variable(:@stubbed_webmock_response)
135
+
136
+ super
137
+ end
138
+
139
+ def request_signature
140
+ @request_signature ||= build_request_signature
141
+ end
142
+
143
+ def stubbed_webmock_response
144
+ unless defined?(@stubbed_webmock_response)
145
+ @stubbed_webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
146
+ end
147
+
148
+ @stubbed_webmock_response
149
+ end
150
+
151
+ def get_response_cookie(name)
152
+ name = name.to_s
153
+
154
+ raw_cookie = response_header.cookie
155
+ raw_cookie = [raw_cookie] if raw_cookie.is_a? String
156
+
157
+ cookie = raw_cookie.select { |c| c.start_with? name }.first
158
+ cookie and cookie.split('=', 2)[1]
159
+ end
160
+
161
+ private
162
+
163
+ def build_webmock_response
164
+ webmock_response = WebMock::Response.new
165
+ webmock_response.status = [response_header.status, response_header.http_reason]
166
+ webmock_response.headers = response_header
167
+ webmock_response.body = response
168
+ webmock_response
169
+ end
170
+
171
+ def build_request_signature
172
+ headers, body = build_request, @req.body
173
+
174
+ @conn.middleware.select {|m| m.respond_to?(:request) }.each do |m|
175
+ headers, body = m.request(self, headers, body)
176
+ end
177
+
178
+ method = @req.method
179
+ uri = @req.uri.clone
180
+ query = @req.query
181
+
182
+ uri.query = encode_query(@req.uri, query).slice(/\?(.*)/, 1)
183
+
184
+ body = form_encode_body(body) if body.is_a?(Hash)
185
+
186
+ if headers['authorization'] && headers['authorization'].is_a?(Array)
187
+ headers['Authorization'] = WebMock::Util::Headers.basic_auth_header(headers.delete('authorization'))
188
+ end
189
+
190
+ WebMock::RequestSignature.new(
191
+ method.downcase.to_sym,
192
+ uri.to_s,
193
+ body: body || (@req.file && File.read(@req.file)),
194
+ headers: headers
195
+ )
196
+ end
197
+
198
+ def make_raw_response(response)
199
+ response.raise_error_if_any
200
+
201
+ status, headers, body = response.status, response.headers, response.body
202
+ headers ||= {}
203
+
204
+ response_string = []
205
+ response_string << "HTTP/1.1 #{status[0]} #{status[1]}"
206
+
207
+ headers["Content-Length"] = body.bytesize unless headers["Content-Length"]
208
+ headers.each do |header, value|
209
+ if header =~ /set-cookie/i
210
+ [value].flatten.each do |cookie|
211
+ response_string << "#{header}: #{cookie}"
212
+ end
213
+ else
214
+ value = value.join(", ") if value.is_a?(Array)
215
+
216
+ # WebMock's internal processing will not handle the body
217
+ # correctly if the header indicates that it is chunked, unless
218
+ # we also create all the chunks.
219
+ # It's far easier just to remove the header.
220
+ next if header =~ /transfer-encoding/i && value =~/chunked/i
221
+
222
+ response_string << "#{header}: #{value}"
223
+ end
224
+ end if headers
225
+
226
+ response_string << "" << body
227
+ response_string.join("\n")
228
+ end
229
+ end
230
+ end
11
231
  end