webmock 3.29.0 → 3.31.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: dd5a0e377558900faf49baabeca47478c71196acd38c625b30ed13d48cb3945a
4
- data.tar.gz: 7956de226b2fa9ad8f692319e6ea5ea4ca666c77c48488feb58470e79474ce26
3
+ metadata.gz: fd3966eb9bb71d2c1ef49c389d9f0a90520cd662cd2a2a10d36568eaadbffa33
4
+ data.tar.gz: ec1209ad4ec45fce69665fd4e64a1f59743716a043e42b3e63b8bcddced6911a
5
5
  SHA512:
6
- metadata.gz: dd9d32d168c7a3e3719b467c519393158cf1b279213acc1e515ac0ff1f88f4f60bc4831e975eae23d18b2fb0d08c84390b180ce52f68a492852d1981dfa5e29d
7
- data.tar.gz: e4bb8d187695bde02c1bcadd89923b24d26e2174676f094ac93cc551e5e5688b242ee519c565151ad7423162557d47ee86bb97c981529134ae4bebfccbd4d5d2
6
+ metadata.gz: 58a66d8ce0f9f893d8862835d28728e585a8ebd2ff074a64b7a88b09508aa2caeb3181e62cfd7adcef7df67da463aa50c1718b6955aa4bf34f42d5fde35a9575
7
+ data.tar.gz: 3105e385e8f5aa63acf07a874a8d93100f3f8d739aae9d56cfe77e479576afe91541af70aa3a739828c59c170c2adb86335e2ad635b20b1c8941266e28a88202
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ # 3.31.0
4
+
5
+ * 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)
6
+
7
+ 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.
8
+
9
+ Thanks to [Alex](https://github.com/ac1982)
10
+
11
+ * Match HTTP methods given as a string or in upper case, as in `stub_request("get", ...)`, `a_request("GET", ...)` or `assert_requested("post", ...)` (#821)
12
+
13
+ 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.
14
+
15
+ 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`.
16
+
17
+ Thanks to [Euisan Kim](https://github.com/euisan)
18
+
19
+ # 3.30.0
20
+
21
+ * Allow narrowing an RSpec expectation on a stub with `with`, as in `expect(stub).to have_been_requested.once.with(body: "abc")` (#1128)
22
+
23
+ The expectation counts only the requests that match both the stub and the conditions given to `with`. The stub itself does not change.
24
+
25
+ Thanks to [Sébastien S.](https://github.com/inkstak)
26
+
27
+ * 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
28
+
29
+ * Allow a full URL with a path in `disable_net_connect!(allow: ...)`, as in `allow: "https://github.com/mozilla/geckodriver/releases/latest"` (#977)
30
+
31
+ 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.
32
+
33
+ Thanks to [Henry Blyth](https://github.com/henrahmagix)
34
+
3
35
  # 3.29.0
4
36
 
5
37
  * 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
@@ -363,6 +363,20 @@ stub_request(:any, "www.example.com").
363
363
  Net::HTTP.get('www.example.com', '/') # ===> "{\"foo\": \"bar\"}"
364
364
  ```
365
365
 
366
+ A `File` or `Pathname` body is returned as it is, so it should hold JSON that is already encoded:
367
+
368
+ ```ruby
369
+ stub_request(:get, "www.example.com").
370
+ to_return_json(body: Pathname.new('spec/fixtures/response.json'))
371
+ ```
372
+
373
+ 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.
374
+
375
+ ```ruby
376
+ stub_request(:get, "www.example.com").
377
+ to_return_json(body: -> { Pathname.new('spec/fixtures/response.json') })
378
+ ```
379
+
366
380
  ### Response with custom status message
367
381
 
368
382
  ```ruby
@@ -573,6 +587,21 @@ RestClient.get('www.example.org', '/') # ===> Failure
573
587
  RestClient.get('www.example.org:8080', '/') # ===> Allowed
574
588
  ```
575
589
 
590
+ With a `String` specifying a full URL:
591
+
592
+ ```ruby
593
+ WebMock.disable_net_connect!(allow: 'https://www.example.org/health')
594
+
595
+ RestClient.get('https://www.example.org/health') # ===> Allowed
596
+ RestClient.get('https://www.example.org/health?full=1') # ===> Failure
597
+ RestClient.get('https://www.example.org/health/db') # ===> Failure
598
+ RestClient.get('https://www.example.org/') # ===> Failure
599
+ ```
600
+
601
+ 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.
602
+
603
+ 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`.
604
+
576
605
  With a `Regexp` matching the URI:
577
606
 
578
607
  ```ruby
@@ -637,6 +666,8 @@ This forces WebMock Net::HTTP adapter to always connect on `Net::HTTP.start`. At
637
666
  WebMock.allow_net_connect!(net_http_connect_on_start: "www.example.com")
638
667
  ```
639
668
 
669
+ The path is not known when the connection is made, so a URL with a path given here never matches.
670
+
640
671
  ## Setting Expectations
641
672
 
642
673
  ### Setting expectations in Test::Unit
@@ -751,11 +782,17 @@ expect(a_request(:post, "www.example.com").
751
782
  ### Setting expectations in RSpec on the stub
752
783
 
753
784
  ```ruby
754
- stub = stub_request(:get, "www.example.com")
785
+ stub = stub_request(:post, "www.example.com")
786
+
755
787
  # ... make requests ...
756
- expect(stub).to have_been_requested
788
+
789
+ expect(stub).to have_been_requested.twice
790
+ expect(stub).to have_been_requested.once.with(body: {"a" => [1, 2, 3]})
791
+ expect(stub).to have_been_requested.once.with(body: {"a" => [4, 5, 6]})
757
792
  ```
758
793
 
794
+ 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.
795
+
759
796
  ## Clearing stubs and request history
760
797
 
761
798
  If you want to reset all current stubs and history of requests use `WebMock.reset!`
@@ -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 pattern declares its body as a JSON object or array string,
21
- # the request has a JSON content type and a body that parses to the same
22
- # JSON but is written differently, and the pattern's method, URI and headers
23
- # match the request. A block given to the pattern is not called.
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
- return false unless request_pattern.is_a?(RequestPattern) && request_pattern.body_pattern
34
+ patterns = parts_of(request_pattern)
35
+ return false unless patterns.all? { |pattern| pattern.is_a?(RequestPattern) && pattern.with_block.nil? }
26
36
 
27
- expected = request_pattern.body_pattern.pattern
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
- matches_except_body?(request_pattern, request_signature)
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
@@ -14,7 +14,7 @@ module WebMock
14
14
 
15
15
  class RequestPattern
16
16
 
17
- attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
17
+ attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern, :with_block
18
18
 
19
19
  def initialize(method, uri, options = {})
20
20
  @method_pattern = MethodPattern.new(method)
@@ -94,8 +94,11 @@ module WebMock
94
94
 
95
95
 
96
96
  class MethodPattern
97
+ # Request signatures store the method as a lower case Symbol, so a method
98
+ # given as a String or in upper case, like "get" or :GET, matches too.
99
+ # Anything else, such as nil, is kept as given and never matches.
97
100
  def initialize(pattern)
98
- @pattern = pattern
101
+ @pattern = pattern.respond_to?(:to_sym) ? pattern.to_sym.downcase : pattern
99
102
  end
100
103
 
101
104
  def matches?(method)
@@ -4,11 +4,11 @@ module WebMock
4
4
 
5
5
  class RequestSignature
6
6
 
7
- attr_accessor :method, :uri, :body
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.to_sym
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
@@ -39,13 +39,10 @@ module WebMock
39
39
  else
40
40
  body.call
41
41
  end
42
- b = b.to_json unless b.is_a?(String)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WebMock
4
- VERSION = '3.29.0' unless defined?(::WebMock::VERSION)
4
+ VERSION = '3.31.0' unless defined?(::WebMock::VERSION)
5
5
  end
@@ -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 =~ allowed) != nil ||
91
- (uri.omit(:port).to_s =~ allowed) != nil && uri.port == uri.default_port
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 == "#{uri.scheme}://#{uri.host}:#{uri.port}" ||
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.29.0
4
+ version: 3.31.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.29.0/CHANGELOG.md
364
- documentation_uri: https://www.rubydoc.info/gems/webmock/3.29.0
365
- source_code_uri: https://github.com/bblimke/webmock/tree/v3.29.0
363
+ changelog_uri: https://github.com/bblimke/webmock/blob/v3.31.0/CHANGELOG.md
364
+ documentation_uri: https://www.rubydoc.info/gems/webmock/3.31.0
365
+ source_code_uri: https://github.com/bblimke/webmock/tree/v3.31.0
366
366
  wiki_uri: https://github.com/bblimke/webmock/wiki
367
367
  rdoc_options: []
368
368
  require_paths: