webmock 3.26.4 → 3.34.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb0293b4433bbf3342d1f81abb5a1c773382f6f1a7fef6b44eb83fc0a38544a7
4
- data.tar.gz: 00fbb149216fae39f04b689fc02c0f561e59e47843011fd8aaab36b35e86ba67
3
+ metadata.gz: b66b694440da9739d8d23ccd8a260260739199235f076f4854387f7029c0c976
4
+ data.tar.gz: 96abe5bbe31e475bcec618aff66d43ac724b7cf15b08883ae864d62cac826e7c
5
5
  SHA512:
6
- metadata.gz: 664302057fd929d144d09bb739c0cc581f4b80b40b67093989a4df01a21337f12594b765d9da0bc980ba981cfbe8db451cf0d23ce4ed10fa5d57eb2ddd98de79
7
- data.tar.gz: bad8a582bd3b717e11182aef243b7167021187d63bd40b2dc1f0f54f13928e898e45ecf1cb598419e8dfc643d5b270db0d3a5408ab871ffb0725d5560dbd556b
6
+ metadata.gz: 99395c27d7c6c270f37309e69da7f3688c43b8f158327d23b62b9829b3fff7a667280542017c71f18e2b822de6639394b2aeaa078aae7b7c67d7fd497363e918
7
+ data.tar.gz: 9378a048d2a03b1309eff3b5264ddca35e4f9268399a92e98147fa0e2d60e376d31ca2f1ccaca912f6d7ca859268b1d9ce173530e885c5055b99f3c2632bfd46
data/CHANGELOG.md CHANGED
@@ -1,5 +1,181 @@
1
1
  # Changelog
2
2
 
3
+ # 3.34.0
4
+
5
+ * Allow `with(query: :any)` to match any query string (#1099)
6
+
7
+ `stub_request(:get, "www.example.com").with(query: :any)` matches requests with any query string, or none. RSpec's `anything` works the same way. Previously, this raised `NoMethodError`, and you had to write `with(query: hash_including({}))`.
8
+
9
+ Use a URL without query parameters when passing `query: :any`. Otherwise, WebMock raises `ArgumentError`.
10
+
11
+ Thanks to [nightpool](https://github.com/nightpool)
12
+
13
+ * Raise a clear `ArgumentError` for an unsupported `query` option
14
+
15
+ Previously, a value such as `with(query: :all)` raised `NoMethodError` (undefined method `split`). The new error lists the supported values.
16
+
17
+ * Show `anything` for keys given without a value to `hash_including` or `hash_excluding` outside RSpec
18
+
19
+ Previously, with Minitest or Test::Unit, messages showed `#<WebMock::Matchers::AnyArgMatcher:0x...>`, for example for `with(query: hash_including(:a))`. RSpec's own matchers already showed `anything`.
20
+
21
+ * Provide `HTTP_HOST` to `to_rack` apps when the client has not supplied a Host header (#1084)
22
+
23
+ Previously, requests from some HTTP clients reached the Rack app without `HTTP_HOST`. This caused host checks such as Sinatra's `HostAuthorization` to see an empty host.
24
+
25
+ WebMock now fills in the host from the request URL, including the port when it is not the default. An explicit Host header is preserved.
26
+
27
+ Thanks to [Benoit Tigeot](https://github.com/benoittgt)
28
+
29
+ * Fix Rack compatibility for `to_rack` apps
30
+
31
+ The request environment now passes `Rack::Lint`.
32
+
33
+ `SERVER_PORT` and `CONTENT_LENGTH` are now strings. If your app or tests read them directly, use string comparisons, such as `env['SERVER_PORT'] == "80"`. The environment also includes `SERVER_PROTOCOL`.
34
+
35
+ `CONTENT_TYPE` is omitted when the request has no content type. The `Content-Length` header is passed only as `CONTENT_LENGTH`, without an extra `HTTP_CONTENT_LENGTH` entry. `Transfer-Encoding` is removed because the body has already been decoded.
36
+
37
+ Repeated header values are joined into one string. Header values and the body read from `rack.input` now use binary (`ASCII-8BIT`) strings.
38
+
39
+ # 3.33.0
40
+
41
+ * Return `effective_url` as a String for stubbed Typhoeus responses (#869)
42
+
43
+ Previously, this was an `Addressable::URI`. If your code calls methods such as `effective_url.host`, parse the returned string into a URI first.
44
+
45
+ The URL keeps the request's port, query order and encoding. It follows curl's formatting: for example, `http://example.com:80` becomes `http://example.com:80/`. Stubbed timeouts now provide an `effective_url` too.
46
+
47
+ Thanks to [Tom Brammar](https://github.com/tbrammar)
48
+
49
+ * Fix incorrect removal of default ports from displayed URLs
50
+
51
+ WebMock could accidentally remove `:80` or `:443` from a URL's path or query. For example, an error message could show `/foo/bar` instead of `/foo:80/bar`.
52
+
53
+ Only the host's default port is now removed. This also works when the port is immediately followed by a query string, as in `http://example.com:80?a=b`.
54
+
55
+ * Set `return_code` to `:ok` for completed stubbed Typhoeus requests (#736)
56
+
57
+ `return_code` is now `:ok` and `return_message` is now `"No error"`; both were previously `nil`. This includes HTTP error responses such as 404: `:ok` means the transfer completed. Stubbed timeouts still return `:operation_timedout`.
58
+
59
+ Thanks to [Michael Spiz](https://github.com/spizm)
60
+
61
+ * Fix the raw headers returned by stubbed Typhoeus responses
62
+
63
+ The `response_headers` string now uses the stubbed HTTP status. For example, a 404 stub no longer reports `HTTP/1.1 200 OK`.
64
+
65
+ Stubs without headers now return an empty `headers` hash (`{}`) instead of `nil`. Their `response_headers` contains the status line instead of being `nil`.
66
+
67
+ The raw headers use binary string encoding, matching real responses. For timeouts, `response_headers` is now an empty string.
68
+
69
+ # 3.32.0
70
+
71
+ * Make a `Net::HTTP` object created while WebMock was enabled behave like any other `Net::HTTP` object after `WebMock.disable!` (#1067)
72
+
73
+ Such an object, like a connection cached by net-http-persistent or by Capybara with Selenium, used to raise `ArgumentError` on `start` or a stubbed request, and `WebMock::NetConnectNotAllowedError` on any other request, after WebMock was disabled. It now sends real requests until WebMock is enabled again.
74
+
75
+ Thanks to [Mike Taylor](https://github.com/sealabcore) and [James Rodovich](https://github.com/rodovich)
76
+
77
+ * Connect again when a request is retried on a `Net::HTTP` object that WebMock started without connecting and whose first real connection failed
78
+
79
+ Such a retry used to raise `NoMethodError` (undefined method `closed?` for nil). It now tries to connect again, and raises the connection error if that fails too.
80
+
81
+ * Drop the `Transfer-Encoding` header from stubbed async-http responses, as WebMock already does with `Content-Length` (#1044)
82
+
83
+ A stub with a `Transfer-Encoding` header, such as `chunked`, used to fail with "Message contains both transfer encoding and content length". async-http sets both headers itself from the body, and its responses from real servers don't include them either.
84
+
85
+ Thanks to [Tim Mertens](https://github.com/tmertens)
86
+
87
+ * Record requests made with curb's `http_patch` as PATCH requests with their body (#1112)
88
+
89
+ curb has `http_patch` since 1.0.8. WebMock recorded such a request as a GET, or as a POST after `post_body=`, so a PATCH stub never matched it.
90
+
91
+ Thanks to [Andy Williams](https://github.com/sobakasu)
92
+
93
+ * Record a curb request sent without a body as having none, even after an earlier `http_post` or `http_patch` call with a body on the same `Curl::Easy` object
94
+
95
+ For example, after `c.http_post("a=1")`, `c.reset` and `c.http(:POST)`, the second request was recorded with the body `a=1`, so a stub for that body matched it.
96
+
97
+ # 3.31.0
98
+
99
+ * Return the contents of a `File` or `Pathname` body given to `to_return_json`, or returned by its body lambda, instead of encoding the object as a JSON string (#1119)
100
+
101
+ The file is read and closed as it is for `to_return`: when the stub is declared, or on each request when it comes from a lambda.
102
+
103
+ Thanks to [Alex](https://github.com/ac1982)
104
+
105
+ * Match HTTP methods given as a string or in upper case, as in `stub_request("get", ...)`, `a_request("GET", ...)` or `assert_requested("post", ...)` (#821)
106
+
107
+ Before, such a method never matched, so the stub never applied and negative expectations like `assert_not_requested("get", ...)` always passed. A test that passed only because of this can now fail.
108
+
109
+ The method of a request is now always a lower case Symbol too. A request made with an upper case method, as Typhoeus does with `method: :POST`, now matches a `:post` stub, and callbacks and `with` blocks see `:post`.
110
+
111
+ Thanks to [Euisan Kim](https://github.com/euisan)
112
+
113
+ # 3.30.0
114
+
115
+ * Allow narrowing an RSpec expectation on a stub with `with`, as in `expect(stub).to have_been_requested.once.with(body: "abc")` (#1128)
116
+
117
+ The expectation counts only the requests that match both the stub and the conditions given to `with`. The stub itself does not change.
118
+
119
+ Thanks to [Sébastien S.](https://github.com/inkstak)
120
+
121
+ * Show the JSON body hint for expectations narrowed with `with` too, and no longer show it when a block is given to the stub or expectation, since checking would mean calling the block
122
+
123
+ * Allow a full URL with a path in `disable_net_connect!(allow: ...)`, as in `allow: "https://github.com/mozilla/geckodriver/releases/latest"` (#977)
124
+
125
+ URLs are compared the same way as in stubs, so the default port, letter case in the host, and the order and encoding of query parameters don't matter. A URL without a path or query allows every path on that scheme, host and port; a trailing slash restricts requests to `/` with no query string. Credentials in either URL are ignored.
126
+
127
+ Thanks to [Henry Blyth](https://github.com/henrahmagix)
128
+
129
+ # 3.29.0
130
+
131
+ * Show a body diff for JSON requests whose content type has a `+json` suffix, such as `application/vnd.api+json`, and for the other content types WebMock already matches as JSON (#734)
132
+
133
+ Thanks to [Olivier Brisse](https://github.com/ouranos)
134
+
135
+ * Ignore the case of JSON, XML and url-encoded content types when matching request bodies, and match bodies of any `+json` or `+xml` media type, such as `image/svg+xml`, as JSON or XML
136
+
137
+ * Suggest declaring a JSON body as a hash when a request fails to match only because the JSON is formatted differently (#649)
138
+
139
+ A body declared as a string must match the request body exactly, including key order and whitespace. When that is the only difference, the error for an unregistered request and the failure message of a request expectation now say so, and suggest `with(body: JSON.parse(json_string))`. Matching JSON strings regardless of key order by default is planned for the next major version (#485).
140
+
141
+ # 3.28.0
142
+
143
+ * Fix `to_rack` apps receiving a `rack.session` they did not set up (#985)
144
+
145
+ WebMock used to add a session hash to the Rack env, shared by every request to the same stub. This confused apps that check whether a session is present, such as Rails API-only apps, and carried session data over between requests. WebMock now leaves `rack.session` and `rack.session.options` unset, as a real server does. If your app needs a session, include session middleware in the app you pass to `to_rack`. `RackResponse#session` and `RackResponse#session_options` have been removed.
146
+
147
+ Thanks to [Jan Graichen](https://github.com/jgraichen)
148
+
149
+ * Fix crash when matching a hash body pattern against a JSON or XML request that has no body
150
+
151
+ Thanks to [Chris](https://github.com/ChrisJr404)
152
+
153
+ * No longer warn about curb versions newer than the last one tested
154
+
155
+ * Document regexp values in `hash_including`
156
+
157
+ Thanks to [Christian Bruckmayer](https://github.com/ChrisBr)
158
+
159
+ * Remove unused `RequestSignatureSnippet#request_params`
160
+
161
+ Thanks to [Daniel Pepper](https://github.com/dpep)
162
+
163
+ # 3.27.0
164
+
165
+ * Fix crash in async-http adapter when a request header has a nil value
166
+
167
+ Thanks to [hikmetba-bit](https://github.com/hikmetba-bit)
168
+
169
+ * The async-http adapter now matches stubs against raw request header values instead of values parsed by protocol-http, consistent with the other adapters
170
+
171
+ * Add RSpec `hash_excluding` support to body matching
172
+
173
+ Thanks to [Chris Gunther](https://github.com/cgunther)
174
+
175
+ * Fix `hash_excluding` ignoring keys given without a value and regexp values, in both query and body matching
176
+
177
+ * Fix crash when matching a request that has more than one `Content-Type` header
178
+
3
179
  # 3.26.4
4
180
 
5
181
  * Fix race condition in `HTTPClient` adapter async requests by @koic in #1131
data/README.md CHANGED
@@ -176,6 +176,8 @@ RestClient.post('www.example.com', '<data a="1" b="five" />',
176
176
  content_type: 'application/xml') # ===> Success
177
177
  ```
178
178
 
179
+ A body declared as a string must match the request body exactly. For JSON, that includes key order and whitespace, so declare the body as a hash to match it as JSON, for example `with(body: JSON.parse(json_string))`.
180
+
179
181
  ### Matching request body against partial hash.
180
182
 
181
183
  ```ruby
@@ -184,6 +186,24 @@ stub_request(:post, "www.example.com").
184
186
 
185
187
  RestClient.post('www.example.com', "data[a]=1&data[b]=five&x=1",
186
188
  :content_type => 'application/x-www-form-urlencoded') # ===> Success
189
+
190
+ stub_request(:post, "www.example.com").
191
+ with(body: hash_including(id: /\d/))
192
+
193
+ RestClient.post('www.example.com', "id=1",
194
+ :content_type => 'application/x-www-form-urlencoded') # ===> Success
195
+ ```
196
+
197
+ ### Matching request body against excluding partial hash.
198
+
199
+ ```ruby
200
+ stub_request(:post, "www.example.com").
201
+ with(body: hash_excluding({data: {a: '2', b: 'six'}}))
202
+
203
+ RestClient.post('www.example.com', "data[a]=1&data[b]=five&x=1",
204
+ :content_type => 'application/x-www-form-urlencoded') # ===> Success
205
+ RestClient.post('www.example.com', "data[a]=2&data[b]=six&x=1",
206
+ :content_type => 'application/x-www-form-urlencoded') # ===> Failure
187
207
  ```
188
208
 
189
209
  ### Matching custom request headers
@@ -279,6 +299,15 @@ Net::HTTP.get('www.example.com',
279
299
  '/thing/5.json?x=1&y=2&z=3&anyParam=4') # ===> Success
280
300
  ```
281
301
 
302
+ ### Matching any query params
303
+
304
+ ```ruby
305
+ stub_request(:get, "www.example.com").with(query: :any)
306
+
307
+ RestClient.get("http://www.example.com/?q=example") # ===> Success
308
+ RestClient.get("http://www.example.com/") # ===> Success
309
+ ```
310
+
282
311
  ### Matching query params using hash
283
312
 
284
313
  ```ruby
@@ -343,6 +372,20 @@ stub_request(:any, "www.example.com").
343
372
  Net::HTTP.get('www.example.com', '/') # ===> "{\"foo\": \"bar\"}"
344
373
  ```
345
374
 
375
+ A `File` or `Pathname` body is returned as it is, so it should hold JSON that is already encoded:
376
+
377
+ ```ruby
378
+ stub_request(:get, "www.example.com").
379
+ to_return_json(body: Pathname.new('spec/fixtures/response.json'))
380
+ ```
381
+
382
+ As with `to_return`, the file is read and closed when the stub is declared, so later changes to it don't affect the response. To read the file on every request, return it from a lambda. A lambda that returns a `File` must open a new one each time, since each one is closed after it is read.
383
+
384
+ ```ruby
385
+ stub_request(:get, "www.example.com").
386
+ to_return_json(body: -> { Pathname.new('spec/fixtures/response.json') })
387
+ ```
388
+
346
389
  ### Response with custom status message
347
390
 
348
391
  ```ruby
@@ -426,6 +469,8 @@ stub_request(:get, "www.example.com").to_rack(MyRackApp)
426
469
  RestClient.post('www.example.com') # ===> "Hello"
427
470
  ```
428
471
 
472
+ WebMock does not set `rack.session` in the Rack env, just as a real server would not. If your app needs a session, include session middleware in the app you pass to `to_rack`.
473
+
429
474
  ### Raising errors
430
475
 
431
476
  #### Exception declared by class
@@ -551,6 +596,21 @@ RestClient.get('www.example.org', '/') # ===> Failure
551
596
  RestClient.get('www.example.org:8080', '/') # ===> Allowed
552
597
  ```
553
598
 
599
+ With a `String` specifying a full URL:
600
+
601
+ ```ruby
602
+ WebMock.disable_net_connect!(allow: 'https://www.example.org/health')
603
+
604
+ RestClient.get('https://www.example.org/health') # ===> Allowed
605
+ RestClient.get('https://www.example.org/health?full=1') # ===> Failure
606
+ RestClient.get('https://www.example.org/health/db') # ===> Failure
607
+ RestClient.get('https://www.example.org/') # ===> Failure
608
+ ```
609
+
610
+ A URL with a path allows only that path and query string. URLs are compared the same way as in stubs, so the default port, letter case in the host, and the order and encoding of query parameters don't matter.
611
+
612
+ A URL without a path or query, such as `https://www.example.org`, allows every path on that scheme, host and port. Adding the trailing slash (`https://www.example.org/`) allows only the root path `/` with no query string. To allow every path under a prefix, use a `Regexp`.
613
+
554
614
  With a `Regexp` matching the URI:
555
615
 
556
616
  ```ruby
@@ -615,6 +675,8 @@ This forces WebMock Net::HTTP adapter to always connect on `Net::HTTP.start`. At
615
675
  WebMock.allow_net_connect!(net_http_connect_on_start: "www.example.com")
616
676
  ```
617
677
 
678
+ The path is not known when the connection is made, so a URL with a path given here never matches.
679
+
618
680
  ## Setting Expectations
619
681
 
620
682
  ### Setting expectations in Test::Unit
@@ -729,11 +791,17 @@ expect(a_request(:post, "www.example.com").
729
791
  ### Setting expectations in RSpec on the stub
730
792
 
731
793
  ```ruby
732
- stub = stub_request(:get, "www.example.com")
794
+ stub = stub_request(:post, "www.example.com")
795
+
733
796
  # ... make requests ...
734
- expect(stub).to have_been_requested
797
+
798
+ expect(stub).to have_been_requested.twice
799
+ expect(stub).to have_been_requested.once.with(body: {"a" => [1, 2, 3]})
800
+ expect(stub).to have_been_requested.once.with(body: {"a" => [4, 5, 6]})
735
801
  ```
736
802
 
803
+ Options and a block given to `with` take the same form as in `stub_request(...).with`. The expectation then counts only the requests that match both the stub and those conditions. The stub itself does not change.
804
+
737
805
  ## Clearing stubs and request history
738
806
 
739
807
  If you want to reset all current stubs and history of requests use `WebMock.reset!`
@@ -111,11 +111,23 @@ if defined?(Async::HTTP)
111
111
  WebMock::RequestSignature.new(
112
112
  request.method.downcase.to_sym,
113
113
  "#{request.scheme}://#{request.authority}#{request.path}",
114
- headers: request.headers.to_h,
114
+ headers: build_request_headers(request),
115
115
  body: body
116
116
  )
117
117
  end
118
118
 
119
+ # Protocol::HTTP::Headers#to_h parses values through the header policies
120
+ # (since protocol-http 0.56.0) and raises when a value is nil. Build the
121
+ # hash from the raw fields instead, skipping entries with no value since
122
+ # they carry nothing to match against. Keys are downcased so the same
123
+ # header sent in different casings is grouped together.
124
+ def build_request_headers(request)
125
+ request.headers.each.each_with_object({}) do |(key, value), headers|
126
+ next if value.nil?
127
+ (headers[key.downcase] ||= []) << value
128
+ end
129
+ end
130
+
119
131
  def build_webmock_response(response)
120
132
  body = response.read
121
133
  response.body = ::Protocol::HTTP::Body::Buffered.wrap(body)
@@ -195,6 +207,10 @@ if defined?(Async::HTTP)
195
207
  module WebMockApplication
196
208
  WEBMOCK_REQUEST_ID_HEADER = 'x-webmock-request-id'.freeze
197
209
 
210
+ # async-http sets these from the body itself. A stub's own values would
211
+ # conflict, as Transfer-Encoding does with the Content-Length it adds.
212
+ FRAMING_HEADERS = %w[content-length transfer-encoding].freeze
213
+
198
214
  class << self
199
215
  def call(request)
200
216
  request.read
@@ -221,8 +237,10 @@ if defined?(Async::HTTP)
221
237
 
222
238
  def build_response(webmock_response)
223
239
  headers = (webmock_response.headers || {}).each_with_object([]) do |(k, value), o|
240
+ next if FRAMING_HEADERS.include?(k.downcase)
241
+
224
242
  Array(value).each do |v|
225
- o.push [k, v] unless k.downcase == 'content-length' # async-http appends the exact content-length automatically
243
+ o.push [k, v]
226
244
  end
227
245
  end
228
246
 
@@ -13,7 +13,7 @@ if defined?(Curl)
13
13
  # in curb 1.3.0 through 1.3.5. Fixed in curb 1.3.6. See https://github.com/taf2/curb/issues/478
14
14
  unsupported_curb_versions += %w[1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5] if RUBY_VERSION < '2.7'
15
15
 
16
- WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', '1.3.7', unsupported_curb_versions).check_version!
16
+ WebMock::VersionChecker.new('Curb', Curl::CURB_VERSION, '0.7.16', nil, unsupported_curb_versions).check_version!
17
17
 
18
18
  module WebMock
19
19
  module HttpLibAdapters
@@ -261,6 +261,15 @@ if defined?(Curl)
261
261
  end
262
262
  end
263
263
 
264
+ # curb has http_patch since 1.0.8.
265
+ if method_defined?(:http_patch)
266
+ def http_patch *data
267
+ @webmock_method = :patch
268
+ @post_body = data.join('&') unless data.empty?
269
+ super
270
+ end
271
+ end
272
+
264
273
  def http_put data = nil
265
274
  @webmock_method = :put
266
275
  @put_data = data if data
@@ -270,7 +279,7 @@ if defined?(Curl)
270
279
 
271
280
  def http_post *data
272
281
  @webmock_method = :post
273
- @post_body = data.join('&') if data && !data.empty?
282
+ @post_body = data.join('&') unless data.empty?
274
283
  super
275
284
  end
276
285
  alias post http_post
@@ -280,6 +289,8 @@ if defined?(Curl)
280
289
  curb_or_webmock { super }
281
290
  ensure
282
291
  reset_webmock_method
292
+ # The body given to http_post or http_patch is for this request only.
293
+ @post_body = nil
283
294
  end
284
295
 
285
296
  def put_data= data
@@ -15,7 +15,15 @@ module WebMock
15
15
  # This will be removed in Ruby 3.5. In Ruby 3.4, const_remove will warn.
16
16
  HAS_LEGACY_CONSTANT = Net.const_defined?(:HTTPSession)
17
17
 
18
+ # Objects created while WebMock was enabled are instances of WebMock's
19
+ # Net::HTTP class, so they check this to act like Net::HTTP once
20
+ # WebMock is disabled.
21
+ def self.enabled?
22
+ @enabled
23
+ end
24
+
18
25
  def self.enable!
26
+ @enabled = true
19
27
  Net.send(:remove_const, :HTTP)
20
28
  Net.send(:const_set, :HTTP, @webMockNetHTTP)
21
29
  if HAS_LEGACY_CONSTANT
@@ -25,6 +33,7 @@ module WebMock
25
33
  end
26
34
 
27
35
  def self.disable!
36
+ @enabled = false
28
37
  Net.send(:remove_const, :HTTP)
29
38
  Net.send(:const_set, :HTTP, OriginalNetHTTP)
30
39
  if HAS_LEGACY_CONSTANT
@@ -83,12 +92,20 @@ module WebMock
83
92
  end
84
93
 
85
94
  def request(request, body = nil, &block)
95
+ unless webmock_enabled?
96
+ # Reconnect for real if started with WebMock's stub socket.
97
+ ensure_actual_connection if started?
98
+ return super
99
+ end
100
+
86
101
  request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
87
102
 
88
103
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
89
104
 
90
105
  if webmock_response = WebMock::StubRegistry.instance.response_for_request(request_signature)
91
- @socket = Net::HTTP.socket_type.new
106
+ # Close the real connection left by an earlier request, if any.
107
+ @socket&.close
108
+ @socket = self.class.socket_type.new
92
109
  WebMock::CallbackRegistry.invoke_callbacks(
93
110
  {lib: :net_http}, request_signature, webmock_response)
94
111
  build_net_http_response(webmock_response, request.uri, &block)
@@ -125,23 +142,22 @@ module WebMock
125
142
  raise IOError, 'HTTP session already opened' if @started
126
143
  if block_given?
127
144
  begin
128
- @socket = Net::HTTP.socket_type.new
145
+ @socket = self.class.socket_type.new
129
146
  @started = true
130
147
  return yield(self)
131
148
  ensure
132
149
  do_finish
133
150
  end
134
151
  end
135
- @socket = Net::HTTP.socket_type.new
152
+ @socket = self.class.socket_type.new
136
153
  @started = true
137
154
  self
138
155
  end
139
156
 
140
-
141
157
  def ensure_actual_connection
142
158
  if @socket.is_a?(StubSocket)
143
- @socket&.close
144
- @socket = nil
159
+ # Keep the closed stub socket, so a failed connect is retried.
160
+ @socket.close
145
161
  do_start
146
162
  end
147
163
  end
@@ -149,6 +165,8 @@ module WebMock
149
165
  alias_method :start_with_connect, :start
150
166
 
151
167
  def start(&block)
168
+ return super unless webmock_enabled?
169
+
152
170
  uri = Addressable::URI.parse(WebMock::NetHTTPUtility.get_uri(self))
153
171
 
154
172
  if WebMock.net_http_connect_on_start?(uri)
@@ -205,6 +223,12 @@ module WebMock
205
223
  @@already_checked_for_right_http_connection = true
206
224
  end
207
225
  end
226
+
227
+ private
228
+
229
+ def webmock_enabled?
230
+ WebMock::HttpLibAdapters::NetHttpAdapter.enabled?
231
+ end
208
232
  end
209
233
  @webMockNetHTTP.version_1_2
210
234
  [
@@ -52,8 +52,8 @@ if defined?(Typhoeus)
52
52
  Typhoeus.on_complete.delete_if {|v| v == AFTER_REQUEST_CALLBACK }
53
53
  end
54
54
 
55
- def self.build_request_signature(req)
56
- uri = WebMock::Util::URI.heuristic_parse(req.url)
55
+ def self.build_request_signature(req, url = req.url)
56
+ uri = WebMock::Util::URI.heuristic_parse(url)
57
57
  uri.path = uri.normalized_path.gsub("[^:]//","/")
58
58
 
59
59
  headers = req.options[:headers]
@@ -89,42 +89,69 @@ if defined?(Typhoeus)
89
89
  webmock_response
90
90
  end
91
91
 
92
- def self.generate_typhoeus_response(request_signature, webmock_response)
93
- response = if webmock_response.should_timeout
94
- ::Typhoeus::Response.new(
92
+ def self.generate_typhoeus_response(webmock_response, url)
93
+ response_options = if webmock_response.should_timeout
94
+ {
95
95
  code: 0,
96
96
  status_message: "",
97
97
  body: "",
98
98
  headers: {},
99
99
  return_code: :operation_timedout,
100
- total_time: 0.0,
101
- starttransfer_time: 0.0,
102
- appconnect_time: 0.0,
103
- pretransfer_time: 0.0,
104
- connect_time: 0.0,
105
- namelookup_time: 0.0,
106
- redirect_time: 0.0
107
- )
100
+ response_headers: ""
101
+ }
108
102
  else
109
- ::Typhoeus::Response.new(
103
+ {
110
104
  code: webmock_response.status[0],
111
105
  status_message: webmock_response.status[1],
112
106
  body: webmock_response.body,
113
107
  headers: webmock_response.headers,
114
- effective_url: request_signature.uri,
115
- total_time: 0.0,
116
- starttransfer_time: 0.0,
117
- appconnect_time: 0.0,
118
- pretransfer_time: 0.0,
119
- connect_time: 0.0,
120
- namelookup_time: 0.0,
121
- redirect_time: 0.0
122
- )
108
+ return_code: :ok,
109
+ response_headers: response_headers_string(webmock_response)
110
+ }
123
111
  end
112
+ response = ::Typhoeus::Response.new(response_options.merge(
113
+ effective_url: effective_url(url),
114
+ total_time: 0.0,
115
+ starttransfer_time: 0.0,
116
+ appconnect_time: 0.0,
117
+ pretransfer_time: 0.0,
118
+ connect_time: 0.0,
119
+ namelookup_time: 0.0,
120
+ redirect_time: 0.0
121
+ ))
124
122
  response.mock = :webmock
125
123
  response
126
124
  end
127
125
 
126
+ # Without response_headers, Typhoeus builds them for a mocked response
127
+ # from its return_code, as in "HTTP/1.1 ok Mock Reason Phrase".
128
+ def self.response_headers_string(webmock_response)
129
+ code, reason = webmock_response.status
130
+ lines = ["HTTP/1.1 #{code} #{reason}"]
131
+ (webmock_response.headers || {}).each do |name, values|
132
+ Array(values).each { |value| lines << "#{name}: #{value}" }
133
+ end
134
+ # Bytes, as in a real response, so values in different encodings join.
135
+ lines.map(&:b).join("\r\n") + "\r\n\r\n"
136
+ end
137
+
138
+ # The request URL as curl reports it: with its port, query and encoding as
139
+ # given, but with a lower case scheme, http:// when there is none, dot
140
+ # segments resolved, "/" for an empty path and no empty query or
141
+ # fragment. Like curl's, it is a binary string.
142
+ def self.effective_url(url)
143
+ # A scheme needs at least one slash after it, so localhost:3000 has
144
+ # none. heuristic_parse turns http:/ and http:/// into http://.
145
+ url = "http://#{url}" unless url.match?(%r{\A[a-z][a-z0-9+.\-]*:/}i)
146
+ uri = WebMock::Util::URI.heuristic_parse(url)
147
+ uri.scheme = uri.scheme.downcase
148
+ uri.path = Addressable::URI.normalize_path(uri.path)
149
+ uri.path = "/" if uri.path.empty?
150
+ uri.query = nil if uri.query == ""
151
+ uri.fragment = nil if uri.fragment == ""
152
+ uri.to_s.b
153
+ end
154
+
128
155
  def self.request_hash(request_signature)
129
156
  hash = {}
130
157
 
@@ -160,14 +187,15 @@ if defined?(Typhoeus)
160
187
  res = true
161
188
 
162
189
  unless WebMock::HttpLibAdapters::TyphoeusAdapter.disabled?
163
- request_signature = ::WebMock::HttpLibAdapters::TyphoeusAdapter.build_request_signature(request)
190
+ # Typhoeus builds a curl handle to work out the URL, so do it once.
191
+ url = request.url
192
+ request_signature = ::WebMock::HttpLibAdapters::TyphoeusAdapter.build_request_signature(request, url)
164
193
  request.block_connection = false;
165
194
 
166
195
  ::WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
167
196
 
168
197
  if webmock_response = ::WebMock::StubRegistry.instance.response_for_request(request_signature)
169
- # ::WebMock::HttpLibAdapters::TyphoeusAdapter.stub_typhoeus(request_signature, webmock_response, self)
170
- response = ::WebMock::HttpLibAdapters::TyphoeusAdapter.generate_typhoeus_response(request_signature, webmock_response)
198
+ response = ::WebMock::HttpLibAdapters::TyphoeusAdapter.generate_typhoeus_response(webmock_response, url)
171
199
  if request.respond_to?(:on_headers)
172
200
  request.execute_headers_callbacks(response)
173
201
  end