webmock 3.5.1 → 3.11.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -12
  3. data/CHANGELOG.md +211 -0
  4. data/README.md +92 -26
  5. data/Rakefile +0 -2
  6. data/lib/webmock.rb +1 -0
  7. data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +214 -0
  8. data/lib/webmock/http_lib_adapters/curb_adapter.rb +10 -1
  9. data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +1 -1
  10. data/lib/webmock/http_lib_adapters/excon_adapter.rb +3 -0
  11. data/lib/webmock/http_lib_adapters/http_rb/client.rb +4 -1
  12. data/lib/webmock/http_lib_adapters/http_rb/response.rb +11 -1
  13. data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +2 -2
  14. data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +23 -6
  15. data/lib/webmock/http_lib_adapters/manticore_adapter.rb +25 -14
  16. data/lib/webmock/http_lib_adapters/net_http.rb +35 -17
  17. data/lib/webmock/http_lib_adapters/patron_adapter.rb +1 -1
  18. data/lib/webmock/request_body_diff.rb +1 -1
  19. data/lib/webmock/request_pattern.rb +76 -48
  20. data/lib/webmock/response.rb +11 -5
  21. data/lib/webmock/rspec.rb +2 -1
  22. data/lib/webmock/stub_registry.rb +26 -11
  23. data/lib/webmock/test_unit.rb +1 -3
  24. data/lib/webmock/util/query_mapper.rb +4 -2
  25. data/lib/webmock/util/uri.rb +8 -8
  26. data/lib/webmock/version.rb +1 -1
  27. data/lib/webmock/webmock.rb +10 -3
  28. data/spec/acceptance/async_http_client/async_http_client_spec.rb +353 -0
  29. data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
  30. data/spec/acceptance/curb/curb_spec.rb +23 -5
  31. data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +1 -1
  32. data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
  33. data/spec/acceptance/http_rb/http_rb_spec.rb +11 -0
  34. data/spec/acceptance/manticore/manticore_spec.rb +19 -0
  35. data/spec/acceptance/net_http/net_http_spec.rb +12 -0
  36. data/spec/acceptance/shared/callbacks.rb +2 -1
  37. data/spec/acceptance/shared/request_expectations.rb +7 -0
  38. data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
  39. data/spec/acceptance/shared/stubbing_requests.rb +40 -0
  40. data/spec/support/webmock_server.rb +1 -0
  41. data/spec/unit/request_pattern_spec.rb +119 -3
  42. data/spec/unit/response_spec.rb +22 -18
  43. data/spec/unit/util/query_mapper_spec.rb +7 -0
  44. data/spec/unit/util/uri_spec.rb +74 -2
  45. data/spec/unit/webmock_spec.rb +54 -5
  46. data/test/test_webmock.rb +6 -0
  47. data/webmock.gemspec +9 -2
  48. metadata +39 -10
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env rake
2
-
3
1
  require 'bundler'
4
2
  Bundler::GemHelper.install_tasks
5
3
 
@@ -54,5 +54,6 @@ require_relative 'webmock/http_lib_adapters/em_http_request_adapter'
54
54
  require_relative 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
55
55
  require_relative 'webmock/http_lib_adapters/excon_adapter'
56
56
  require_relative 'webmock/http_lib_adapters/manticore_adapter'
57
+ require_relative 'webmock/http_lib_adapters/async_http_client_adapter'
57
58
 
58
59
  require_relative 'webmock/webmock'
@@ -0,0 +1,214 @@
1
+ begin
2
+ require 'async'
3
+ require 'async/http'
4
+ rescue LoadError
5
+ # async-http not found
6
+ end
7
+
8
+ if defined?(Async::HTTP)
9
+ module WebMock
10
+ module HttpLibAdapters
11
+ class AsyncHttpClientAdapter < HttpLibAdapter
12
+ adapter_for :async_http_client
13
+
14
+ OriginalAsyncHttpClient = Async::HTTP::Client unless const_defined?(:OriginalAsyncHttpClient)
15
+
16
+ class << self
17
+ def enable!
18
+ Async::HTTP.send(:remove_const, :Client)
19
+ Async::HTTP.send(:const_set, :Client, Async::HTTP::WebMockClientWrapper)
20
+ end
21
+
22
+ def disable!
23
+ Async::HTTP.send(:remove_const, :Client)
24
+ Async::HTTP.send(:const_set, :Client, OriginalAsyncHttpClient)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module Async
32
+ module HTTP
33
+ class WebMockClientWrapper < Client
34
+ def initialize(
35
+ endpoint,
36
+ protocol = endpoint.protocol,
37
+ scheme = endpoint.scheme,
38
+ authority = endpoint.authority,
39
+ **options
40
+ )
41
+ webmock_endpoint = WebMockEndpoint.new(scheme, authority, protocol)
42
+
43
+ @network_client = WebMockClient.new(endpoint, protocol, scheme, authority, **options)
44
+ @webmock_client = WebMockClient.new(webmock_endpoint, protocol, scheme, authority, **options)
45
+
46
+ @scheme = scheme
47
+ @authority = authority
48
+ end
49
+
50
+ def call(request)
51
+ request.scheme ||= self.scheme
52
+ request.authority ||= self.authority
53
+
54
+ request_signature = build_request_signature(request)
55
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
56
+ webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
57
+ net_connect_allowed = WebMock.net_connect_allowed?(request_signature.uri)
58
+
59
+ if webmock_response
60
+ webmock_response.raise_error_if_any
61
+ raise Async::TimeoutError, 'WebMock timeout error' if webmock_response.should_timeout
62
+ WebMockApplication.add_webmock_response(request, webmock_response)
63
+ response = @webmock_client.call(request)
64
+ elsif net_connect_allowed
65
+ response = @network_client.call(request)
66
+ else
67
+ raise WebMock::NetConnectNotAllowedError.new(request_signature) unless webmock_response
68
+ end
69
+
70
+ if WebMock::CallbackRegistry.any_callbacks?
71
+ webmock_response ||= build_webmock_response(response)
72
+ WebMock::CallbackRegistry.invoke_callbacks(
73
+ {
74
+ lib: :async_http_client,
75
+ real_request: net_connect_allowed
76
+ },
77
+ request_signature,
78
+ webmock_response
79
+ )
80
+ end
81
+
82
+ response
83
+ end
84
+
85
+ def close
86
+ @network_client.close
87
+ @webmock_client.close
88
+ end
89
+
90
+ private
91
+
92
+ def build_request_signature(request)
93
+ body = request.read
94
+ request.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
95
+ WebMock::RequestSignature.new(
96
+ request.method.downcase.to_sym,
97
+ "#{request.scheme}://#{request.authority}#{request.path}",
98
+ headers: request.headers.to_h,
99
+ body: body
100
+ )
101
+ end
102
+
103
+ def build_webmock_response(response)
104
+ body = response.read
105
+ response.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
106
+
107
+ webmock_response = WebMock::Response.new
108
+ webmock_response.status = [
109
+ response.status,
110
+ ::Protocol::HTTP1::Reason::DESCRIPTIONS[response.status]
111
+ ]
112
+ webmock_response.headers = build_webmock_response_headers(response)
113
+ webmock_response.body = body
114
+ webmock_response
115
+ end
116
+
117
+ def build_webmock_response_headers(response)
118
+ response.headers.each.each_with_object({}) do |(k, v), o|
119
+ o[k] ||= []
120
+ o[k] << v
121
+ end
122
+ end
123
+ end
124
+
125
+ class WebMockClient < Client
126
+ end
127
+
128
+ class WebMockEndpoint
129
+ def initialize(scheme, authority, protocol)
130
+ @scheme = scheme
131
+ @authority = authority
132
+ @protocol = protocol
133
+ end
134
+
135
+ attr :scheme, :authority, :protocol
136
+
137
+ def connect
138
+ server_socket, client_socket = create_connected_sockets
139
+ Async(transient: true) do
140
+ accept_socket(server_socket)
141
+ end
142
+ client_socket
143
+ end
144
+
145
+ def inspect
146
+ "\#<#{self.class}> #{scheme}://#{authority} protocol=#{protocol}"
147
+ end
148
+
149
+ private
150
+
151
+ def create_connected_sockets
152
+ Async::IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM).tap do |sockets|
153
+ sockets.each do |socket|
154
+ socket.instance_variable_set :@alpn_protocol, nil
155
+ socket.instance_eval do
156
+ def alpn_protocol
157
+ nil # means HTTP11 will be used for HTTPS
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def accept_socket(socket)
165
+ server = Async::HTTP::Server.new(WebMockApplication, self)
166
+ server.accept(socket, socket.remote_address)
167
+ end
168
+ end
169
+
170
+ module WebMockApplication
171
+ WEBMOCK_REQUEST_ID_HEADER = 'x-webmock-request-id'.freeze
172
+
173
+ class << self
174
+ def call(request)
175
+ request.read
176
+ webmock_response = get_webmock_response(request)
177
+ build_response(webmock_response)
178
+ end
179
+
180
+ def add_webmock_response(request, webmock_response)
181
+ webmock_request_id = request.object_id.to_s
182
+ request.headers.add(WEBMOCK_REQUEST_ID_HEADER, webmock_request_id)
183
+ webmock_responses[webmock_request_id] = webmock_response
184
+ end
185
+
186
+ def get_webmock_response(request)
187
+ webmock_request_id = request.headers[WEBMOCK_REQUEST_ID_HEADER][0]
188
+ webmock_responses.fetch(webmock_request_id)
189
+ end
190
+
191
+ private
192
+
193
+ def webmock_responses
194
+ @webmock_responses ||= {}
195
+ end
196
+
197
+ def build_response(webmock_response)
198
+ headers = (webmock_response.headers || {}).each_with_object([]) do |(k, value), o|
199
+ Array(value).each do |v|
200
+ o.push [k, v]
201
+ end
202
+ end
203
+
204
+ ::Protocol::HTTP::Response[
205
+ webmock_response.status[0],
206
+ headers,
207
+ webmock_response.body
208
+ ]
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -54,7 +54,6 @@ if defined?(Curl)
54
54
 
55
55
  module Curl
56
56
  class WebMockCurlEasy < Curl::Easy
57
-
58
57
  def curb_or_webmock
59
58
  request_signature = build_request_signature
60
59
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -271,6 +270,8 @@ if defined?(Curl)
271
270
  def perform
272
271
  @webmock_method ||= :get
273
272
  curb_or_webmock { super }
273
+ ensure
274
+ reset_webmock_method
274
275
  end
275
276
 
276
277
  def put_data= data
@@ -333,8 +334,16 @@ if defined?(Curl)
333
334
  METHOD
334
335
  end
335
336
 
337
+ def reset_webmock_method
338
+ @webmock_method = :get
339
+ end
340
+
336
341
  def reset
337
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)
338
347
  super
339
348
  end
340
349
  end
@@ -107,7 +107,7 @@ if defined?(EventMachine::HttpClient)
107
107
  @uri ||= nil
108
108
  EM.next_tick {
109
109
  setup(make_raw_response(stubbed_webmock_response), @uri,
110
- stubbed_webmock_response.should_timeout ? "WebMock timeout error" : nil)
110
+ stubbed_webmock_response.should_timeout ? Errno::ETIMEDOUT : nil)
111
111
  }
112
112
  self
113
113
  elsif WebMock.net_connect_allowed?(request_signature.uri)
@@ -159,4 +159,7 @@ if defined?(Excon)
159
159
  end
160
160
  end
161
161
  end
162
+
163
+ # Suppresses Excon connection argument validation warning
164
+ Excon::VALID_CONNECTION_KEYS << :__construction_args
162
165
  end
@@ -4,7 +4,10 @@ module HTTP
4
4
 
5
5
  def perform(request, options)
6
6
  return __perform__(request, options) unless webmock_enabled?
7
- WebMockPerform.new(request) { __perform__(request, options) }.exec
7
+
8
+ response = WebMockPerform.new(request) { __perform__(request, options) }.exec
9
+ options.features.each { |_name, feature| response = feature.wrap_response(response) }
10
+ response
8
11
  end
9
12
 
10
13
  def webmock_enabled?
@@ -14,9 +14,19 @@ module HTTP
14
14
  def from_webmock(webmock_response, request_signature = nil)
15
15
  status = Status.new(webmock_response.status.first)
16
16
  headers = webmock_response.headers || {}
17
- body = Body.new Streamer.new webmock_response.body
18
17
  uri = normalize_uri(request_signature && request_signature.uri)
19
18
 
19
+ # HTTP.rb 3.0+ uses a keyword argument to pass the encoding, but 1.x
20
+ # and 2.x use a positional argument, and 0.x don't support supplying
21
+ # the encoding.
22
+ body = if HTTP::VERSION < "1.0.0"
23
+ Body.new(Streamer.new(webmock_response.body))
24
+ elsif HTTP::VERSION < "3.0.0"
25
+ Body.new(Streamer.new(webmock_response.body), webmock_response.body.encoding)
26
+ else
27
+ Body.new(Streamer.new(webmock_response.body), encoding: webmock_response.body.encoding)
28
+ end
29
+
20
30
  return new(status, "1.1", headers, body, uri) if HTTP::VERSION < "1.0.0"
21
31
 
22
32
  new({
@@ -5,7 +5,7 @@ module HTTP
5
5
  @io = StringIO.new str
6
6
  end
7
7
 
8
- def readpartial(size = nil)
8
+ def readpartial(size = nil, outbuf = nil)
9
9
  unless size
10
10
  if defined?(HTTP::Client::BUFFER_SIZE)
11
11
  size = HTTP::Client::BUFFER_SIZE
@@ -14,7 +14,7 @@ module HTTP
14
14
  end
15
15
  end
16
16
 
17
- @io.read size
17
+ @io.read size, outbuf
18
18
  end
19
19
 
20
20
  def close
@@ -43,6 +43,9 @@ if defined?(::HTTPClient)
43
43
  end
44
44
 
45
45
  module WebMockHTTPClients
46
+
47
+ REQUEST_RESPONSE_LOCK = Mutex.new
48
+
46
49
  def do_get_block(req, proxy, conn, &block)
47
50
  do_get(req, proxy, conn, false, &block)
48
51
  end
@@ -57,7 +60,7 @@ if defined?(::HTTPClient)
57
60
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
58
61
 
59
62
  if webmock_responses[request_signature]
60
- webmock_response = webmock_responses.delete(request_signature)
63
+ webmock_response = synchronize_request_response { webmock_responses.delete(request_signature) }
61
64
  response = build_httpclient_response(webmock_response, stream, req.header, &block)
62
65
  @request_filter.each do |filter|
63
66
  filter.filter_response(req, response)
@@ -68,7 +71,7 @@ if defined?(::HTTPClient)
68
71
  res
69
72
  elsif WebMock.net_connect_allowed?(request_signature.uri)
70
73
  # in case there is a nil entry in the hash...
71
- webmock_responses.delete(request_signature)
74
+ synchronize_request_response { webmock_responses.delete(request_signature) }
72
75
 
73
76
  res = if stream
74
77
  do_get_stream_without_webmock(req, proxy, conn, &block)
@@ -100,7 +103,7 @@ if defined?(::HTTPClient)
100
103
  def do_request_async(method, uri, query, body, extheader)
101
104
  req = create_request(method, uri, query, body, extheader)
102
105
  request_signature = build_request_signature(req)
103
- webmock_request_signatures << request_signature
106
+ synchronize_request_response { webmock_request_signatures << request_signature }
104
107
 
105
108
  if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
106
109
  super
@@ -184,7 +187,9 @@ if defined?(::HTTPClient)
184
187
 
185
188
  def webmock_responses
186
189
  @webmock_responses ||= Hash.new do |hash, request_signature|
187
- hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
190
+ synchronize_request_response do
191
+ hash[request_signature] = WebMock::StubRegistry.instance.response_for_request(request_signature)
192
+ end
188
193
  end
189
194
  end
190
195
 
@@ -193,8 +198,10 @@ if defined?(::HTTPClient)
193
198
  end
194
199
 
195
200
  def previous_signature_for(signature)
196
- return nil unless index = webmock_request_signatures.index(signature)
197
- webmock_request_signatures.delete_at(index)
201
+ synchronize_request_response do
202
+ return nil unless index = webmock_request_signatures.index(signature)
203
+ webmock_request_signatures.delete_at(index)
204
+ end
198
205
  end
199
206
 
200
207
  private
@@ -209,6 +216,16 @@ if defined?(::HTTPClient)
209
216
  hdrs
210
217
  end
211
218
  end
219
+
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
212
229
  end
213
230
 
214
231
  class WebMockHTTPClient < HTTPClient
@@ -24,6 +24,12 @@ if defined?(Manticore)
24
24
  Manticore.instance_variable_set(:@manticore_facade, OriginalManticoreClient.new)
25
25
  end
26
26
 
27
+ class StubbedTimeoutResponse < Manticore::StubbedResponse
28
+ def call
29
+ @handlers[:failure].call(Manticore::ConnectTimeout.new("Too slow (mocked timeout)"))
30
+ end
31
+ end
32
+
27
33
  class WebMockManticoreClient < Manticore::Client
28
34
  def request(klass, url, options={}, &block)
29
35
  super(klass, WebMock::Util::URI.normalize_uri(url).to_s, format_options(options))
@@ -50,19 +56,22 @@ if defined?(Manticore)
50
56
 
51
57
  if webmock_response = registered_response_for(request_signature)
52
58
  webmock_response.raise_error_if_any
53
- manticore_response = generate_manticore_response(webmock_response).call
54
- real_request = false
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
55
63
 
56
64
  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
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
60
70
 
61
71
  else
62
72
  raise WebMock::NetConnectNotAllowedError.new(request_signature)
63
73
  end
64
74
 
65
- WebMock::CallbackRegistry.invoke_callbacks({lib: :manticore, real_request: real_request}, request_signature, webmock_response)
66
75
  manticore_response
67
76
  end
68
77
 
@@ -103,14 +112,16 @@ if defined?(Manticore)
103
112
  end
104
113
 
105
114
  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
- )
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
114
125
  end
115
126
 
116
127
  def generate_webmock_response(manticore_response)