webmock 3.29.0 → 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 +4 -4
- data/CHANGELOG.md +126 -0
- data/README.md +48 -2
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +7 -1
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +12 -1
- data/lib/webmock/http_lib_adapters/net_http.rb +30 -6
- data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +54 -26
- data/lib/webmock/json_body_hint.rb +34 -10
- data/lib/webmock/matchers/any_arg_matcher.rb +4 -0
- data/lib/webmock/rack_response.rb +31 -12
- data/lib/webmock/request_pattern.rb +29 -4
- data/lib/webmock/request_signature.rb +7 -3
- data/lib/webmock/request_stub.rb +13 -5
- data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +47 -2
- data/lib/webmock/util/uri.rb +2 -2
- data/lib/webmock/version.rb +1 -1
- data/lib/webmock/webmock.rb +25 -5
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b66b694440da9739d8d23ccd8a260260739199235f076f4854387f7029c0c976
|
|
4
|
+
data.tar.gz: 96abe5bbe31e475bcec618aff66d43ac724b7cf15b08883ae864d62cac826e7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99395c27d7c6c270f37309e69da7f3688c43b8f158327d23b62b9829b3fff7a667280542017c71f18e2b822de6639394b2aeaa078aae7b7c67d7fd497363e918
|
|
7
|
+
data.tar.gz: 9378a048d2a03b1309eff3b5264ddca35e4f9268399a92e98147fa0e2d60e376d31ca2f1ccaca912f6d7ca859268b1d9ce173530e885c5055b99f3c2632bfd46
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
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
|
+
|
|
3
129
|
# 3.29.0
|
|
4
130
|
|
|
5
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)
|
data/README.md
CHANGED
|
@@ -299,6 +299,15 @@ Net::HTTP.get('www.example.com',
|
|
|
299
299
|
'/thing/5.json?x=1&y=2&z=3&anyParam=4') # ===> Success
|
|
300
300
|
```
|
|
301
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
|
+
|
|
302
311
|
### Matching query params using hash
|
|
303
312
|
|
|
304
313
|
```ruby
|
|
@@ -363,6 +372,20 @@ stub_request(:any, "www.example.com").
|
|
|
363
372
|
Net::HTTP.get('www.example.com', '/') # ===> "{\"foo\": \"bar\"}"
|
|
364
373
|
```
|
|
365
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
|
+
|
|
366
389
|
### Response with custom status message
|
|
367
390
|
|
|
368
391
|
```ruby
|
|
@@ -573,6 +596,21 @@ RestClient.get('www.example.org', '/') # ===> Failure
|
|
|
573
596
|
RestClient.get('www.example.org:8080', '/') # ===> Allowed
|
|
574
597
|
```
|
|
575
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
|
+
|
|
576
614
|
With a `Regexp` matching the URI:
|
|
577
615
|
|
|
578
616
|
```ruby
|
|
@@ -637,6 +675,8 @@ This forces WebMock Net::HTTP adapter to always connect on `Net::HTTP.start`. At
|
|
|
637
675
|
WebMock.allow_net_connect!(net_http_connect_on_start: "www.example.com")
|
|
638
676
|
```
|
|
639
677
|
|
|
678
|
+
The path is not known when the connection is made, so a URL with a path given here never matches.
|
|
679
|
+
|
|
640
680
|
## Setting Expectations
|
|
641
681
|
|
|
642
682
|
### Setting expectations in Test::Unit
|
|
@@ -751,11 +791,17 @@ expect(a_request(:post, "www.example.com").
|
|
|
751
791
|
### Setting expectations in RSpec on the stub
|
|
752
792
|
|
|
753
793
|
```ruby
|
|
754
|
-
stub = stub_request(:
|
|
794
|
+
stub = stub_request(:post, "www.example.com")
|
|
795
|
+
|
|
755
796
|
# ... make requests ...
|
|
756
|
-
|
|
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]})
|
|
757
801
|
```
|
|
758
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
|
+
|
|
759
805
|
## Clearing stubs and request history
|
|
760
806
|
|
|
761
807
|
If you want to reset all current stubs and history of requests use `WebMock.reset!`
|
|
@@ -207,6 +207,10 @@ if defined?(Async::HTTP)
|
|
|
207
207
|
module WebMockApplication
|
|
208
208
|
WEBMOCK_REQUEST_ID_HEADER = 'x-webmock-request-id'.freeze
|
|
209
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
|
+
|
|
210
214
|
class << self
|
|
211
215
|
def call(request)
|
|
212
216
|
request.read
|
|
@@ -233,8 +237,10 @@ if defined?(Async::HTTP)
|
|
|
233
237
|
|
|
234
238
|
def build_response(webmock_response)
|
|
235
239
|
headers = (webmock_response.headers || {}).each_with_object([]) do |(k, value), o|
|
|
240
|
+
next if FRAMING_HEADERS.include?(k.downcase)
|
|
241
|
+
|
|
236
242
|
Array(value).each do |v|
|
|
237
|
-
o.push [k, v]
|
|
243
|
+
o.push [k, v]
|
|
238
244
|
end
|
|
239
245
|
end
|
|
240
246
|
|
|
@@ -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('&')
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
144
|
-
@socket
|
|
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(
|
|
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(
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -17,24 +17,48 @@ module WebMock
|
|
|
17
17
|
EXPECTATION_HINT = "Hint: a request was made with the same JSON body as expected, " \
|
|
18
18
|
"but formatted differently, for example with keys in another order. #{ADVICE}"
|
|
19
19
|
|
|
20
|
-
# True when the
|
|
21
|
-
# the request has a JSON content type and a body that parses to the
|
|
22
|
-
# JSON but is written differently, and the
|
|
23
|
-
# match the request.
|
|
20
|
+
# True when one of the patterns declares its body as a JSON object or array
|
|
21
|
+
# string, the request has a JSON content type and a body that parses to the
|
|
22
|
+
# same JSON but is written differently, and the method, URI and headers of
|
|
23
|
+
# every pattern match the request. Any other patterns the request has to
|
|
24
|
+
# match, such as the conditions of an RSpec expectation, must not declare
|
|
25
|
+
# a body of their own.
|
|
26
|
+
#
|
|
27
|
+
# Building a failure message must not call a block given to with, which
|
|
28
|
+
# could raise and replace the failure, or repeat side effects. So there is
|
|
29
|
+
# no hint when any of the patterns has one.
|
|
30
|
+
#
|
|
31
|
+
# The body is checked first, since that runs no user code, so URI and
|
|
32
|
+
# header matchers are only evaluated when a hint is otherwise possible.
|
|
24
33
|
def self.applies?(request_pattern, request_signature)
|
|
25
|
-
|
|
34
|
+
patterns = parts_of(request_pattern)
|
|
35
|
+
return false unless patterns.all? { |pattern| pattern.is_a?(RequestPattern) && pattern.with_block.nil? }
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
with_body = patterns.select(&:body_pattern)
|
|
38
|
+
return false unless with_body.size == 1
|
|
39
|
+
return false unless body_differs_only_in_formatting?(with_body.first.body_pattern, request_signature)
|
|
40
|
+
|
|
41
|
+
patterns.all? { |pattern| matches_except_body?(pattern, request_signature) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A pattern made of parts that must all match, such as an RSpec expectation
|
|
45
|
+
# with conditions, responds to #patterns. A stub stands for its pattern.
|
|
46
|
+
def self.parts_of(request_pattern)
|
|
47
|
+
parts = request_pattern.respond_to?(:patterns) ? request_pattern.patterns : [request_pattern]
|
|
48
|
+
parts.map { |part| part.is_a?(RequestStub) ? part.request_pattern : part }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.body_differs_only_in_formatting?(body_pattern, request_signature)
|
|
52
|
+
expected = body_pattern.pattern
|
|
28
53
|
actual = request_signature.body
|
|
29
54
|
return false unless expected.is_a?(String) && actual.is_a?(String) && expected != actual
|
|
30
55
|
return false unless request_signature.json_headers?
|
|
31
56
|
|
|
32
57
|
expected_json = parse_json(expected)
|
|
33
58
|
return false unless expected_json.is_a?(Hash) || expected_json.is_a?(Array)
|
|
34
|
-
# eql? compares nested values strictly, so 1 and 1.0 are different.
|
|
35
|
-
return false unless expected_json.eql?(parse_json(actual))
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
# eql? compares nested values strictly, so 1 and 1.0 are different.
|
|
61
|
+
expected_json.eql?(parse_json(actual))
|
|
38
62
|
end
|
|
39
63
|
|
|
40
64
|
def self.matches_except_body?(request_pattern, request_signature)
|
|
@@ -48,6 +72,6 @@ module WebMock
|
|
|
48
72
|
rescue ::JSON::JSONError, EncodingError
|
|
49
73
|
nil
|
|
50
74
|
end
|
|
51
|
-
private_class_method :matches_except_body?, :parse_json
|
|
75
|
+
private_class_method :parts_of, :body_differs_only_in_formatting?, :matches_except_body?, :parse_json
|
|
52
76
|
end
|
|
53
77
|
end
|
|
@@ -27,37 +27,56 @@ module WebMock
|
|
|
27
27
|
|
|
28
28
|
def build_rack_env(request)
|
|
29
29
|
uri = request.uri
|
|
30
|
+
port = uri.inferred_port
|
|
30
31
|
headers = (request.headers || {}).dup
|
|
31
|
-
body = request.body || ''
|
|
32
|
+
body = (request.body || '').b
|
|
33
|
+
content_type = headers.delete('Content-Type')
|
|
34
|
+
headers.delete('Content-Length')
|
|
35
|
+
# The body is already decoded, as a server passes it on.
|
|
36
|
+
headers.delete('Transfer-Encoding')
|
|
32
37
|
|
|
33
38
|
env = {
|
|
34
39
|
# CGI variables specified by Rack
|
|
35
|
-
'REQUEST_METHOD'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'SCRIPT_NAME'
|
|
40
|
+
'REQUEST_METHOD' => request.method.to_s.upcase,
|
|
41
|
+
'CONTENT_LENGTH' => body.bytesize.to_s,
|
|
42
|
+
'PATH_INFO' => uri.path,
|
|
43
|
+
'QUERY_STRING' => uri.query || '',
|
|
44
|
+
'SERVER_NAME' => uri.host,
|
|
45
|
+
'SERVER_PORT' => port.to_s,
|
|
46
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
|
47
|
+
'SCRIPT_NAME' => ""
|
|
43
48
|
}
|
|
49
|
+
env['CONTENT_TYPE'] = header_value('Content-Type', content_type) if content_type
|
|
44
50
|
|
|
45
51
|
env['HTTP_AUTHORIZATION'] = 'Basic ' + [uri.userinfo].pack('m').delete("\r\n") if uri.userinfo
|
|
46
52
|
|
|
47
53
|
# Rack-specific variables
|
|
48
54
|
env['rack.input'] = StringIO.new(body)
|
|
49
55
|
env['rack.errors'] = $stderr
|
|
50
|
-
if !Rack.const_defined?(:RELEASE) || Rack::RELEASE < "3"
|
|
51
|
-
env['rack.version']
|
|
56
|
+
if !Rack.const_defined?(:RELEASE) || Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
|
|
57
|
+
env['rack.version'] = Rack::VERSION
|
|
58
|
+
env['rack.multithread'] = false
|
|
59
|
+
env['rack.multiprocess'] = false
|
|
52
60
|
end
|
|
53
61
|
env['rack.url_scheme'] = uri.scheme
|
|
54
62
|
env['rack.run_once'] = true
|
|
55
63
|
|
|
56
64
|
headers.each do |k, v|
|
|
57
|
-
env["HTTP_#{k.tr('-','_').upcase}"] = v
|
|
65
|
+
env["HTTP_#{k.tr('-','_').upcase}"] = header_value(k, v)
|
|
58
66
|
end
|
|
59
67
|
|
|
68
|
+
# Many clients add Host only when sending, so fill it in as they would.
|
|
69
|
+
env['HTTP_HOST'] ||= port == uri.default_port ? uri.host : "#{uri.host}:#{port}"
|
|
70
|
+
|
|
60
71
|
env
|
|
61
72
|
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Like a server, pass header values as bytes and join repeated ones. Rack
|
|
77
|
+
# splits cookies on "; ", so join those that way.
|
|
78
|
+
def header_value(name, value)
|
|
79
|
+
Array(value).map { |v| v.to_s.b }.join(name == 'Cookie' ? '; ' : WebMock::Util::Headers::JOIN)
|
|
80
|
+
end
|
|
62
81
|
end
|
|
63
82
|
end
|
|
@@ -10,11 +10,15 @@ module WebMock
|
|
|
10
10
|
def rSpecHashExcludingMatcher?(matcher)
|
|
11
11
|
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashExcludingMatcher/
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def rSpecAnyArgMatcher?(matcher)
|
|
15
|
+
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::AnyArgMatcher/
|
|
16
|
+
end
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
class RequestPattern
|
|
16
20
|
|
|
17
|
-
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
|
|
21
|
+
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern, :with_block
|
|
18
22
|
|
|
19
23
|
def initialize(method, uri, options = {})
|
|
20
24
|
@method_pattern = MethodPattern.new(method)
|
|
@@ -94,8 +98,11 @@ module WebMock
|
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
class MethodPattern
|
|
101
|
+
# Request signatures store the method as a lower case Symbol, so a method
|
|
102
|
+
# given as a String or in upper case, like "get" or :GET, matches too.
|
|
103
|
+
# Anything else, such as nil, is kept as given and never matches.
|
|
97
104
|
def initialize(pattern)
|
|
98
|
-
@pattern = pattern
|
|
105
|
+
@pattern = pattern.respond_to?(:to_sym) ? pattern.to_sym.downcase : pattern
|
|
99
106
|
end
|
|
100
107
|
|
|
101
108
|
def matches?(method)
|
|
@@ -126,6 +133,10 @@ module WebMock
|
|
|
126
133
|
def add_query_params(query_params)
|
|
127
134
|
@query_params = if query_params.is_a?(Hash)
|
|
128
135
|
query_params
|
|
136
|
+
elsif query_params.equal?(:any) \
|
|
137
|
+
|| query_params.is_a?(WebMock::Matchers::AnyArgMatcher) \
|
|
138
|
+
|| rSpecAnyArgMatcher?(query_params)
|
|
139
|
+
WebMock::Matchers::AnyArgMatcher.new(nil)
|
|
129
140
|
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher) \
|
|
130
141
|
|| query_params.is_a?(WebMock::Matchers::HashExcludingMatcher)
|
|
131
142
|
query_params
|
|
@@ -133,8 +144,10 @@ module WebMock
|
|
|
133
144
|
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
|
|
134
145
|
elsif rSpecHashExcludingMatcher?(query_params)
|
|
135
146
|
WebMock::Matchers::HashExcludingMatcher.from_rspec_matcher(query_params)
|
|
136
|
-
|
|
147
|
+
elsif query_params.nil? || query_params.is_a?(String)
|
|
137
148
|
WebMock::Util::QueryMapper.query_to_values(query_params, notation: Config.instance.query_values_notation)
|
|
149
|
+
else
|
|
150
|
+
raise ArgumentError, "Query should be a String, Hash, :any, nil, or a hash_including or hash_excluding matcher. Got: #{query_params.class}"
|
|
138
151
|
end
|
|
139
152
|
end
|
|
140
153
|
|
|
@@ -150,6 +163,10 @@ module WebMock
|
|
|
150
163
|
|
|
151
164
|
private
|
|
152
165
|
|
|
166
|
+
def raise_any_query_with_uri_query
|
|
167
|
+
raise ArgumentError, "query: :any can't be combined with a query in the URI #{pattern_inspect}, because no request would match. Remove the query from the URI."
|
|
168
|
+
end
|
|
169
|
+
|
|
153
170
|
def pattern_inspect
|
|
154
171
|
@pattern.inspect
|
|
155
172
|
end
|
|
@@ -177,12 +194,17 @@ module WebMock
|
|
|
177
194
|
|
|
178
195
|
class URIAddressablePattern < URIPattern
|
|
179
196
|
def add_query_params(query_params)
|
|
197
|
+
super(query_params)
|
|
198
|
+
# With query params, the template is matched against the URI without its
|
|
199
|
+
# query, so a literal query in the template can never match.
|
|
200
|
+
if @query_params.is_a?(WebMock::Matchers::AnyArgMatcher) && @pattern.pattern.gsub(/\{[^}]*\}/, '').include?('?')
|
|
201
|
+
raise_any_query_with_uri_query
|
|
202
|
+
end
|
|
180
203
|
@@add_query_params_warned ||= false
|
|
181
204
|
if not @@add_query_params_warned
|
|
182
205
|
@@add_query_params_warned = true
|
|
183
206
|
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
|
|
184
207
|
end
|
|
185
|
-
super(query_params)
|
|
186
208
|
end
|
|
187
209
|
|
|
188
210
|
private
|
|
@@ -227,6 +249,9 @@ module WebMock
|
|
|
227
249
|
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
|
|
228
250
|
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
|
|
229
251
|
@query_params = nil
|
|
252
|
+
elsif @query_params.is_a?(WebMock::Matchers::AnyArgMatcher) && @pattern.query
|
|
253
|
+
# The URI is then compared without its query, so it could never match.
|
|
254
|
+
raise_any_query_with_uri_query
|
|
230
255
|
end
|
|
231
256
|
end
|
|
232
257
|
|
|
@@ -4,11 +4,11 @@ module WebMock
|
|
|
4
4
|
|
|
5
5
|
class RequestSignature
|
|
6
6
|
|
|
7
|
-
attr_accessor :
|
|
8
|
-
attr_reader :headers
|
|
7
|
+
attr_accessor :uri, :body
|
|
8
|
+
attr_reader :method, :headers
|
|
9
9
|
|
|
10
10
|
def initialize(method, uri, options = {})
|
|
11
|
-
self.method = method
|
|
11
|
+
self.method = method
|
|
12
12
|
self.uri = uri.is_a?(Addressable::URI) ? uri : WebMock::Util::URI.normalize_uri(uri)
|
|
13
13
|
assign_options(options)
|
|
14
14
|
end
|
|
@@ -23,6 +23,10 @@ module WebMock
|
|
|
23
23
|
string
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def method=(method)
|
|
27
|
+
@method = method.to_sym.downcase
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
def headers=(headers)
|
|
27
31
|
@headers = WebMock::Util::Headers.normalize_headers(headers)
|
|
28
32
|
end
|
data/lib/webmock/request_stub.rb
CHANGED
|
@@ -39,13 +39,10 @@ module WebMock
|
|
|
39
39
|
else
|
|
40
40
|
body.call
|
|
41
41
|
end
|
|
42
|
-
b
|
|
43
|
-
b
|
|
42
|
+
json_response_body(b)
|
|
44
43
|
}
|
|
45
|
-
elsif !body.is_a?(String)
|
|
46
|
-
body.to_json
|
|
47
44
|
else
|
|
48
|
-
body
|
|
45
|
+
json_response_body(body)
|
|
49
46
|
end
|
|
50
47
|
|
|
51
48
|
resp_h.merge(
|
|
@@ -130,5 +127,16 @@ module WebMock
|
|
|
130
127
|
end
|
|
131
128
|
stub
|
|
132
129
|
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def json_response_body(body)
|
|
134
|
+
case body
|
|
135
|
+
when String, IO, Pathname
|
|
136
|
+
body
|
|
137
|
+
else
|
|
138
|
+
body.to_json
|
|
139
|
+
end
|
|
140
|
+
end
|
|
133
141
|
end
|
|
134
142
|
end
|
|
@@ -52,13 +52,22 @@ module WebMock
|
|
|
52
52
|
self
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Counts only the requests that also match the given conditions, which
|
|
56
|
+
# take the same options and block as RequestPattern#with. Like
|
|
57
|
+
# RequestPattern#with, calling it again adds to the conditions. The stub
|
|
58
|
+
# or request pattern under test is not changed.
|
|
59
|
+
def with(options = {}, &block)
|
|
60
|
+
(@conditions ||= RequestPattern.new(:any, ANY_URI)).with(options, &block)
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
def matches?(request_pattern)
|
|
56
|
-
@request_execution_verifier.request_pattern = request_pattern
|
|
65
|
+
@request_execution_verifier.request_pattern = with_conditions(request_pattern)
|
|
57
66
|
@request_execution_verifier.matches?
|
|
58
67
|
end
|
|
59
68
|
|
|
60
69
|
def does_not_match?(request_pattern)
|
|
61
|
-
@request_execution_verifier.request_pattern = request_pattern
|
|
70
|
+
@request_execution_verifier.request_pattern = with_conditions(request_pattern)
|
|
62
71
|
@request_execution_verifier.does_not_match?
|
|
63
72
|
end
|
|
64
73
|
|
|
@@ -76,5 +85,41 @@ module WebMock
|
|
|
76
85
|
|
|
77
86
|
# RSpec 2 compatibility:
|
|
78
87
|
alias_method :negative_failure_message, :failure_message_when_negated
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
ANY_URI = ->(_uri) { true }
|
|
92
|
+
private_constant :ANY_URI
|
|
93
|
+
|
|
94
|
+
# How the description of conditions starts when nothing was given to
|
|
95
|
+
# #with. What was given follows it, as in ' with body "abc"'.
|
|
96
|
+
EMPTY_CONDITIONS = RequestPattern.new(:any, ANY_URI).to_s.freeze
|
|
97
|
+
private_constant :EMPTY_CONDITIONS
|
|
98
|
+
|
|
99
|
+
def with_conditions(request_pattern)
|
|
100
|
+
@conditions ? PatternWithConditions.new(request_pattern, @conditions) : request_pattern
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Matches a request when both the pattern and the conditions match it.
|
|
104
|
+
class PatternWithConditions
|
|
105
|
+
def initialize(pattern, conditions)
|
|
106
|
+
@pattern = pattern
|
|
107
|
+
@conditions = conditions
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The parts that must all match, so JsonBodyHint can check each of them.
|
|
111
|
+
def patterns
|
|
112
|
+
[@pattern, @conditions]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def matches?(request_signature)
|
|
116
|
+
@pattern.matches?(request_signature) && @conditions.matches?(request_signature)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def to_s
|
|
120
|
+
"#{@pattern} (only requests#{@conditions.to_s.delete_prefix(EMPTY_CONDITIONS)})"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
private_constant :PatternWithConditions
|
|
79
124
|
end
|
|
80
125
|
end
|
data/lib/webmock/util/uri.rb
CHANGED
|
@@ -58,8 +58,8 @@ module WebMock
|
|
|
58
58
|
|
|
59
59
|
def self.strip_default_port_from_uri_string(uri_string)
|
|
60
60
|
case uri_string
|
|
61
|
-
when %r{^http://} then uri_string.sub(%r{:80(
|
|
62
|
-
when %r{^https://} then uri_string.sub(%r{:443(
|
|
61
|
+
when %r{^http://} then uri_string.sub(%r{\A(http://[^/?#]+):80(?=[/?#]|\z)}, '\1')
|
|
62
|
+
when %r{^https://} then uri_string.sub(%r{\A(https://[^/?#]+):443(?=[/?#]|\z)}, '\1')
|
|
63
63
|
else uri_string
|
|
64
64
|
end
|
|
65
65
|
end
|
data/lib/webmock/version.rb
CHANGED
data/lib/webmock/webmock.rb
CHANGED
|
@@ -87,14 +87,12 @@ module WebMock
|
|
|
87
87
|
when Array
|
|
88
88
|
allowed.any? { |allowed_item| net_connect_explicit_allowed?(allowed_item, uri) }
|
|
89
89
|
when Regexp
|
|
90
|
-
(uri.to_s
|
|
91
|
-
(uri.
|
|
90
|
+
allowed.match?(uri.to_s) ||
|
|
91
|
+
(uri.port == uri.default_port && allowed.match?(uri.omit(:port).to_s))
|
|
92
92
|
when String
|
|
93
|
-
allowed == uri.to_s ||
|
|
94
93
|
allowed == uri.host ||
|
|
95
94
|
allowed == "#{uri.host}:#{uri.port}" ||
|
|
96
|
-
allowed
|
|
97
|
-
allowed == "#{uri.scheme}://#{uri.host}" && uri.port == uri.default_port
|
|
95
|
+
net_connect_allowed_url?(allowed, uri)
|
|
98
96
|
else
|
|
99
97
|
if allowed.respond_to?(:call)
|
|
100
98
|
allowed.call(uri)
|
|
@@ -102,6 +100,28 @@ module WebMock
|
|
|
102
100
|
end
|
|
103
101
|
end
|
|
104
102
|
|
|
103
|
+
def self.net_connect_allowed_url?(allowed, uri)
|
|
104
|
+
return false unless allowed.match?(%r{\Ahttps?://}i)
|
|
105
|
+
|
|
106
|
+
# Normalization turns an empty path into "/", so check the original URL.
|
|
107
|
+
allowed_uri = Addressable::URI.parse(allowed)
|
|
108
|
+
allow_any_path = allowed_uri.path.empty? && allowed_uri.query.nil?
|
|
109
|
+
|
|
110
|
+
# Connection URIs have no request path to match yet.
|
|
111
|
+
return false if uri.path.empty? && !allowed_uri.path.empty?
|
|
112
|
+
|
|
113
|
+
allowed_uri = WebMock::Util::URI.normalize_uri(allowed_uri)
|
|
114
|
+
request_uri = WebMock::Util::URI.normalize_uri(uri)
|
|
115
|
+
|
|
116
|
+
return false unless allowed_uri.origin == request_uri.origin
|
|
117
|
+
return true if allow_any_path
|
|
118
|
+
|
|
119
|
+
allowed_uri.path == request_uri.path && allowed_uri.query == request_uri.query
|
|
120
|
+
rescue Addressable::URI::InvalidURIError
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
private_class_method :net_connect_allowed_url?
|
|
124
|
+
|
|
105
125
|
def self.show_body_diff!
|
|
106
126
|
Config.instance.show_body_diff = true
|
|
107
127
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: webmock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.34.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bartosz Blimke
|
|
@@ -360,9 +360,9 @@ licenses:
|
|
|
360
360
|
- MIT
|
|
361
361
|
metadata:
|
|
362
362
|
bug_tracker_uri: https://github.com/bblimke/webmock/issues
|
|
363
|
-
changelog_uri: https://github.com/bblimke/webmock/blob/v3.
|
|
364
|
-
documentation_uri: https://www.rubydoc.info/gems/webmock/3.
|
|
365
|
-
source_code_uri: https://github.com/bblimke/webmock/tree/v3.
|
|
363
|
+
changelog_uri: https://github.com/bblimke/webmock/blob/v3.34.0/CHANGELOG.md
|
|
364
|
+
documentation_uri: https://www.rubydoc.info/gems/webmock/3.34.0
|
|
365
|
+
source_code_uri: https://github.com/bblimke/webmock/tree/v3.34.0
|
|
366
366
|
wiki_uri: https://github.com/bblimke/webmock/wiki
|
|
367
367
|
rdoc_options: []
|
|
368
368
|
require_paths:
|