webmock 3.0.1 → 3.18.1
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 +5 -5
- data/.github/workflows/CI.yml +38 -0
- data/CHANGELOG.md +496 -2
- data/Gemfile +1 -1
- data/README.md +169 -34
- data/Rakefile +12 -4
- data/lib/webmock/api.rb +12 -0
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +221 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +19 -5
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +7 -4
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +5 -2
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +2 -1
- data/lib/webmock/http_lib_adapters/http_rb/request.rb +7 -1
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +27 -3
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +9 -3
- data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +7 -3
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +28 -9
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +33 -15
- data/lib/webmock/http_lib_adapters/net_http.rb +36 -89
- data/lib/webmock/http_lib_adapters/net_http_response.rb +1 -1
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +4 -4
- data/lib/webmock/matchers/any_arg_matcher.rb +13 -0
- data/lib/webmock/matchers/hash_argument_matcher.rb +21 -0
- data/lib/webmock/matchers/hash_excluding_matcher.rb +15 -0
- data/lib/webmock/matchers/hash_including_matcher.rb +4 -23
- data/lib/webmock/rack_response.rb +1 -1
- data/lib/webmock/request_body_diff.rb +1 -1
- data/lib/webmock/request_execution_verifier.rb +2 -3
- data/lib/webmock/request_pattern.rb +129 -51
- data/lib/webmock/request_registry.rb +1 -1
- data/lib/webmock/request_signature.rb +3 -3
- data/lib/webmock/request_signature_snippet.rb +4 -4
- data/lib/webmock/request_stub.rb +15 -0
- data/lib/webmock/response.rb +19 -13
- data/lib/webmock/rspec.rb +10 -3
- data/lib/webmock/stub_registry.rb +26 -11
- data/lib/webmock/stub_request_snippet.rb +10 -6
- data/lib/webmock/test_unit.rb +1 -3
- data/lib/webmock/util/hash_counter.rb +3 -3
- data/lib/webmock/util/headers.rb +17 -2
- data/lib/webmock/util/json.rb +1 -2
- data/lib/webmock/util/query_mapper.rb +9 -7
- data/lib/webmock/util/uri.rb +10 -10
- data/lib/webmock/util/values_stringifier.rb +20 -0
- data/lib/webmock/version.rb +1 -1
- data/lib/webmock/webmock.rb +20 -3
- data/lib/webmock.rb +53 -48
- data/minitest/webmock_spec.rb +3 -3
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +375 -0
- data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
- data/spec/acceptance/curb/curb_spec.rb +44 -0
- data/spec/acceptance/em_http_request/em_http_request_spec.rb +57 -1
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +2 -2
- data/spec/acceptance/excon/excon_spec.rb +4 -2
- data/spec/acceptance/excon/excon_spec_helper.rb +2 -0
- data/spec/acceptance/http_rb/http_rb_spec.rb +20 -0
- data/spec/acceptance/http_rb/http_rb_spec_helper.rb +5 -2
- data/spec/acceptance/httpclient/httpclient_spec.rb +8 -1
- data/spec/acceptance/manticore/manticore_spec.rb +51 -0
- data/spec/acceptance/net_http/net_http_shared.rb +47 -10
- data/spec/acceptance/net_http/net_http_spec.rb +102 -24
- data/spec/acceptance/net_http/real_net_http_spec.rb +1 -1
- data/spec/acceptance/patron/patron_spec.rb +26 -21
- data/spec/acceptance/patron/patron_spec_helper.rb +3 -3
- data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +14 -14
- data/spec/acceptance/shared/callbacks.rb +3 -2
- data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +1 -1
- data/spec/acceptance/shared/request_expectations.rb +14 -0
- data/spec/acceptance/shared/returning_declared_responses.rb +36 -15
- data/spec/acceptance/shared/stubbing_requests.rb +95 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +1 -1
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +1 -1
- data/spec/support/webmock_server.rb +1 -0
- data/spec/unit/api_spec.rb +103 -3
- data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
- data/spec/unit/request_execution_verifier_spec.rb +12 -12
- data/spec/unit/request_pattern_spec.rb +207 -49
- data/spec/unit/request_signature_snippet_spec.rb +2 -2
- data/spec/unit/request_signature_spec.rb +21 -1
- data/spec/unit/request_stub_spec.rb +35 -0
- data/spec/unit/response_spec.rb +51 -19
- data/spec/unit/stub_request_snippet_spec.rb +30 -10
- data/spec/unit/util/query_mapper_spec.rb +13 -0
- data/spec/unit/util/uri_spec.rb +74 -2
- data/spec/unit/webmock_spec.rb +108 -5
- data/test/shared_test.rb +15 -2
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +15 -7
- metadata +86 -37
- data/.travis.yml +0 -20
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
module Matchers
|
|
3
|
+
# Base class for Hash matchers
|
|
4
|
+
# https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
|
|
5
|
+
class HashArgumentMatcher
|
|
6
|
+
def initialize(expected)
|
|
7
|
+
@expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, deep: true).sort]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ==(_actual, &block)
|
|
11
|
+
@expected.all?(&block)
|
|
12
|
+
rescue NoMethodError
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.from_rspec_matcher(matcher)
|
|
17
|
+
new(matcher.instance_variable_get(:@expected))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
module Matchers
|
|
3
|
+
# this is a based on RSpec::Mocks::ArgumentMatchers::HashExcludingMatcher
|
|
4
|
+
# https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
|
|
5
|
+
class HashExcludingMatcher < HashArgumentMatcher
|
|
6
|
+
def ==(actual)
|
|
7
|
+
super { |key, value| !actual.key?(key) || value != actual[key] }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def inspect
|
|
11
|
+
"hash_excluding(#{@expected.inspect})"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
module WebMock
|
|
2
2
|
module Matchers
|
|
3
|
-
#this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
|
|
4
|
-
#https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
|
|
5
|
-
class HashIncludingMatcher
|
|
6
|
-
def initialize(expected)
|
|
7
|
-
@expected = Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(expected, deep: true).sort]
|
|
8
|
-
end
|
|
9
|
-
|
|
3
|
+
# this is a based on RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher
|
|
4
|
+
# https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/argument_matchers.rb
|
|
5
|
+
class HashIncludingMatcher < HashArgumentMatcher
|
|
10
6
|
def ==(actual)
|
|
11
|
-
|
|
7
|
+
super { |key, value| actual.key?(key) && value === actual[key] }
|
|
12
8
|
rescue NoMethodError
|
|
13
9
|
false
|
|
14
10
|
end
|
|
@@ -16,21 +12,6 @@ module WebMock
|
|
|
16
12
|
def inspect
|
|
17
13
|
"hash_including(#{@expected.inspect})"
|
|
18
14
|
end
|
|
19
|
-
|
|
20
|
-
def self.from_rspec_matcher(matcher)
|
|
21
|
-
new(matcher.instance_variable_get(:@expected))
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
#this is a based on RSpec::Mocks::ArgumentMatchers::AnyArgMatcher
|
|
26
|
-
class AnyArgMatcher
|
|
27
|
-
def initialize(ignore)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def ==(other)
|
|
31
|
-
true
|
|
32
|
-
end
|
|
33
15
|
end
|
|
34
|
-
|
|
35
16
|
end
|
|
36
17
|
end
|
|
@@ -12,7 +12,7 @@ module WebMock
|
|
|
12
12
|
def body_diff
|
|
13
13
|
return {} unless request_signature_diffable? && request_stub_diffable?
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Hashdiff.diff(request_signature_body_hash, request_stub_body_hash)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
attr_reader :request_signature, :request_stub
|
|
@@ -53,9 +53,8 @@ module WebMock
|
|
|
53
53
|
|
|
54
54
|
def failure_message_phrase(is_negated=false)
|
|
55
55
|
negation = is_negated ? "was not" : "was"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
text
|
|
56
|
+
"The request #{request_pattern} #{negation} expected to execute #{quantity_phrase(is_negated)}but it executed #{times(times_executed)}" +
|
|
57
|
+
self.class.executed_requests_message
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
def quantity_phrase(is_negated=false)
|
|
@@ -4,6 +4,10 @@ module WebMock
|
|
|
4
4
|
def rSpecHashIncludingMatcher?(matcher)
|
|
5
5
|
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
def rSpecHashExcludingMatcher?(matcher)
|
|
9
|
+
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashExcludingMatcher/
|
|
10
|
+
end
|
|
7
11
|
end
|
|
8
12
|
|
|
9
13
|
class RequestPattern
|
|
@@ -20,7 +24,7 @@ module WebMock
|
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
def with(options = {}, &block)
|
|
23
|
-
raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified.') if options.empty? && !block_given?
|
|
27
|
+
raise ArgumentError.new('#with method invoked with no arguments. Either options hash or block must be specified. Created a block with do..end? Try creating it with curly braces {} instead.') if options.empty? && !block_given?
|
|
24
28
|
assign_options(options)
|
|
25
29
|
@with_block = block
|
|
26
30
|
self
|
|
@@ -37,7 +41,7 @@ module WebMock
|
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def to_s
|
|
40
|
-
string = "#{@method_pattern.to_s.upcase}"
|
|
44
|
+
string = "#{@method_pattern.to_s.upcase}".dup
|
|
41
45
|
string << " #{@uri_pattern.to_s}"
|
|
42
46
|
string << " with body #{@body_pattern.to_s}" if @body_pattern
|
|
43
47
|
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
|
|
@@ -76,6 +80,8 @@ module WebMock
|
|
|
76
80
|
URIRegexpPattern.new(uri)
|
|
77
81
|
elsif uri.is_a?(Addressable::Template)
|
|
78
82
|
URIAddressablePattern.new(uri)
|
|
83
|
+
elsif uri.respond_to?(:call)
|
|
84
|
+
URICallablePattern.new(uri)
|
|
79
85
|
else
|
|
80
86
|
URIStringPattern.new(uri)
|
|
81
87
|
end
|
|
@@ -103,11 +109,13 @@ module WebMock
|
|
|
103
109
|
include RSpecMatcherDetector
|
|
104
110
|
|
|
105
111
|
def initialize(pattern)
|
|
106
|
-
@pattern =
|
|
107
|
-
|
|
112
|
+
@pattern = if pattern.is_a?(Addressable::URI) \
|
|
113
|
+
|| pattern.is_a?(Addressable::Template)
|
|
114
|
+
pattern
|
|
115
|
+
elsif pattern.respond_to?(:call)
|
|
108
116
|
pattern
|
|
109
117
|
else
|
|
110
|
-
|
|
118
|
+
WebMock::Util::URI.normalize_uri(pattern)
|
|
111
119
|
end
|
|
112
120
|
@query_params = nil
|
|
113
121
|
end
|
|
@@ -115,65 +123,116 @@ module WebMock
|
|
|
115
123
|
def add_query_params(query_params)
|
|
116
124
|
@query_params = if query_params.is_a?(Hash)
|
|
117
125
|
query_params
|
|
118
|
-
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher)
|
|
126
|
+
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher) \
|
|
127
|
+
|| query_params.is_a?(WebMock::Matchers::HashExcludingMatcher)
|
|
119
128
|
query_params
|
|
120
129
|
elsif rSpecHashIncludingMatcher?(query_params)
|
|
121
130
|
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
|
|
131
|
+
elsif rSpecHashExcludingMatcher?(query_params)
|
|
132
|
+
WebMock::Matchers::HashExcludingMatcher.from_rspec_matcher(query_params)
|
|
122
133
|
else
|
|
123
134
|
WebMock::Util::QueryMapper.query_to_values(query_params, notation: Config.instance.query_values_notation)
|
|
124
135
|
end
|
|
125
136
|
end
|
|
126
137
|
|
|
138
|
+
def matches?(uri)
|
|
139
|
+
pattern_matches?(uri) && query_params_matches?(uri)
|
|
140
|
+
end
|
|
141
|
+
|
|
127
142
|
def to_s
|
|
128
|
-
str =
|
|
143
|
+
str = pattern_inspect
|
|
129
144
|
str += " with query params #{@query_params.inspect}" if @query_params
|
|
130
145
|
str
|
|
131
146
|
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def pattern_inspect
|
|
151
|
+
@pattern.inspect
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def query_params_matches?(uri)
|
|
155
|
+
@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation)
|
|
156
|
+
end
|
|
132
157
|
end
|
|
133
158
|
|
|
134
|
-
class
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
class URICallablePattern < URIPattern
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def pattern_matches?(uri)
|
|
163
|
+
@pattern.call(uri)
|
|
138
164
|
end
|
|
165
|
+
end
|
|
139
166
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
class URIRegexpPattern < URIPattern
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def pattern_matches?(uri)
|
|
171
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
|
|
144
172
|
end
|
|
145
173
|
end
|
|
146
174
|
|
|
147
175
|
class URIAddressablePattern < URIPattern
|
|
148
|
-
def
|
|
176
|
+
def add_query_params(query_params)
|
|
177
|
+
@@add_query_params_warned ||= false
|
|
178
|
+
if not @@add_query_params_warned
|
|
179
|
+
@@add_query_params_warned = true
|
|
180
|
+
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
|
|
181
|
+
end
|
|
182
|
+
super(query_params)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def pattern_matches?(uri)
|
|
149
188
|
if @query_params.nil?
|
|
150
189
|
# Let Addressable check the whole URI
|
|
151
|
-
|
|
190
|
+
matches_with_variations?(uri)
|
|
152
191
|
else
|
|
153
192
|
# WebMock checks the query, Addressable checks everything else
|
|
154
|
-
|
|
155
|
-
@query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)
|
|
193
|
+
matches_with_variations?(uri.omit(:query))
|
|
156
194
|
end
|
|
157
195
|
end
|
|
158
196
|
|
|
159
|
-
def
|
|
160
|
-
|
|
161
|
-
super(query_params)
|
|
197
|
+
def pattern_inspect
|
|
198
|
+
@pattern.pattern.inspect
|
|
162
199
|
end
|
|
163
200
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
201
|
+
def matches_with_variations?(uri)
|
|
202
|
+
template =
|
|
203
|
+
begin
|
|
204
|
+
Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
|
|
205
|
+
rescue Addressable::URI::InvalidURIError
|
|
206
|
+
Addressable::Template.new(@pattern.pattern)
|
|
207
|
+
end
|
|
208
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u|
|
|
209
|
+
template_matches_uri?(template, u)
|
|
210
|
+
}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def template_matches_uri?(template, uri)
|
|
214
|
+
template.match(uri)
|
|
215
|
+
rescue Addressable::URI::InvalidURIError
|
|
216
|
+
false
|
|
168
217
|
end
|
|
169
218
|
end
|
|
170
219
|
|
|
171
220
|
class URIStringPattern < URIPattern
|
|
172
|
-
def
|
|
221
|
+
def add_query_params(query_params)
|
|
222
|
+
super
|
|
223
|
+
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
|
|
224
|
+
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
|
|
225
|
+
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
|
|
226
|
+
@query_params = nil
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
|
|
232
|
+
def pattern_matches?(uri)
|
|
173
233
|
if @pattern.is_a?(Addressable::URI)
|
|
174
234
|
if @query_params
|
|
175
|
-
uri.omit(:query) === @pattern
|
|
176
|
-
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
|
|
235
|
+
uri.omit(:query) === @pattern
|
|
177
236
|
else
|
|
178
237
|
uri === @pattern
|
|
179
238
|
end
|
|
@@ -182,19 +241,8 @@ module WebMock
|
|
|
182
241
|
end
|
|
183
242
|
end
|
|
184
243
|
|
|
185
|
-
def
|
|
186
|
-
|
|
187
|
-
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
|
|
188
|
-
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
|
|
189
|
-
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
|
|
190
|
-
@query_params = nil
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def to_s
|
|
195
|
-
str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
|
|
196
|
-
str += " with query params #{@query_params.inspect}" if @query_params
|
|
197
|
-
str
|
|
244
|
+
def pattern_inspect
|
|
245
|
+
WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
|
|
198
246
|
end
|
|
199
247
|
end
|
|
200
248
|
|
|
@@ -232,7 +280,9 @@ module WebMock
|
|
|
232
280
|
|
|
233
281
|
if (@pattern).is_a?(Hash)
|
|
234
282
|
return true if @pattern.empty?
|
|
235
|
-
|
|
283
|
+
matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
|
|
284
|
+
elsif (@pattern).is_a?(Array)
|
|
285
|
+
matching_body_array?(body_as_hash(body, content_type), @pattern, content_type)
|
|
236
286
|
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
|
|
237
287
|
@pattern == body_as_hash(body, content_type)
|
|
238
288
|
else
|
|
@@ -247,8 +297,9 @@ module WebMock
|
|
|
247
297
|
end
|
|
248
298
|
|
|
249
299
|
private
|
|
300
|
+
|
|
250
301
|
def body_as_hash(body, content_type)
|
|
251
|
-
case
|
|
302
|
+
case body_format(content_type)
|
|
252
303
|
when :json then
|
|
253
304
|
WebMock::Util::JSON.parse(body)
|
|
254
305
|
when :xml then
|
|
@@ -258,6 +309,11 @@ module WebMock
|
|
|
258
309
|
end
|
|
259
310
|
end
|
|
260
311
|
|
|
312
|
+
def body_format(content_type)
|
|
313
|
+
normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
|
|
314
|
+
BODY_FORMATS[normalized_content_type]
|
|
315
|
+
end
|
|
316
|
+
|
|
261
317
|
def assert_non_multipart_body(content_type)
|
|
262
318
|
if content_type =~ %r{^multipart/form-data}
|
|
263
319
|
raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
|
|
@@ -287,21 +343,36 @@ module WebMock
|
|
|
287
343
|
#
|
|
288
344
|
# @return [Boolean] true if the paramaters match the comparison
|
|
289
345
|
# hash, false if not.
|
|
290
|
-
def
|
|
346
|
+
def matching_body_hashes?(query_parameters, pattern, content_type)
|
|
291
347
|
return false unless query_parameters.is_a?(Hash)
|
|
292
348
|
return false unless query_parameters.keys.sort == pattern.keys.sort
|
|
293
|
-
|
|
349
|
+
|
|
350
|
+
query_parameters.all? do |key, actual|
|
|
294
351
|
expected = pattern[key]
|
|
352
|
+
matching_values(actual, expected, content_type)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
295
355
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
356
|
+
def matching_body_array?(query_parameters, pattern, content_type)
|
|
357
|
+
return false unless query_parameters.is_a?(Array)
|
|
358
|
+
return false unless query_parameters.length == pattern.length
|
|
359
|
+
|
|
360
|
+
query_parameters.each_with_index do |actual, index|
|
|
361
|
+
expected = pattern[index]
|
|
362
|
+
return false unless matching_values(actual, expected, content_type)
|
|
301
363
|
end
|
|
364
|
+
|
|
302
365
|
true
|
|
303
366
|
end
|
|
304
367
|
|
|
368
|
+
def matching_values(actual, expected, content_type)
|
|
369
|
+
return matching_body_hashes?(actual, expected, content_type) if actual.is_a?(Hash) && expected.is_a?(Hash)
|
|
370
|
+
return matching_body_array?(actual, expected, content_type) if actual.is_a?(Array) && expected.is_a?(Array)
|
|
371
|
+
|
|
372
|
+
expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
|
|
373
|
+
expected === actual
|
|
374
|
+
end
|
|
375
|
+
|
|
305
376
|
def empty_string?(string)
|
|
306
377
|
string.nil? || string == ""
|
|
307
378
|
end
|
|
@@ -310,6 +381,9 @@ module WebMock
|
|
|
310
381
|
Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true).sort]
|
|
311
382
|
end
|
|
312
383
|
|
|
384
|
+
def url_encoded_body?(content_type)
|
|
385
|
+
content_type =~ %r{^application/x-www-form-urlencoded}
|
|
386
|
+
end
|
|
313
387
|
end
|
|
314
388
|
|
|
315
389
|
class HeadersPattern
|
|
@@ -333,6 +407,10 @@ module WebMock
|
|
|
333
407
|
WebMock::Util::Headers.sorted_headers_string(@pattern)
|
|
334
408
|
end
|
|
335
409
|
|
|
410
|
+
def pp_to_s
|
|
411
|
+
WebMock::Util::Headers.pp_headers_string(@pattern)
|
|
412
|
+
end
|
|
413
|
+
|
|
336
414
|
private
|
|
337
415
|
|
|
338
416
|
def empty_headers?(headers)
|
|
@@ -23,7 +23,7 @@ module WebMock
|
|
|
23
23
|
if requested_signatures.hash.empty?
|
|
24
24
|
"No requests were made."
|
|
25
25
|
else
|
|
26
|
-
text = ""
|
|
26
|
+
text = "".dup
|
|
27
27
|
self.requested_signatures.each do |request_signature, times_executed|
|
|
28
28
|
text << "#{request_signature} was made #{times_executed} time#{times_executed == 1 ? '' : 's' }\n"
|
|
29
29
|
end
|
|
@@ -12,7 +12,7 @@ module WebMock
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def to_s
|
|
15
|
-
string = "#{self.method.to_s.upcase}"
|
|
15
|
+
string = "#{self.method.to_s.upcase}".dup
|
|
16
16
|
string << " #{WebMock::Util::URI.strip_default_port_from_uri_string(self.uri.to_s)}"
|
|
17
17
|
string << " with body '#{body.to_s}'" if body && body.to_s != ''
|
|
18
18
|
if headers && !headers.empty?
|
|
@@ -35,11 +35,11 @@ module WebMock
|
|
|
35
35
|
alias == eql?
|
|
36
36
|
|
|
37
37
|
def url_encoded?
|
|
38
|
-
!!(headers
|
|
38
|
+
!!(headers&.fetch('Content-Type', nil)&.start_with?('application/x-www-form-urlencoded'))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def json_headers?
|
|
42
|
-
!!(headers
|
|
42
|
+
!!(headers&.fetch('Content-Type', nil)&.start_with?('application/json'))
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
private
|
|
@@ -13,14 +13,14 @@ module WebMock
|
|
|
13
13
|
def stubbing_instructions
|
|
14
14
|
return unless WebMock.show_stubbing_instructions?
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
"You can stub this request with the following snippet:\n\n" +
|
|
17
|
+
WebMock::StubRequestSnippet.new(request_stub).to_s
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def request_stubs
|
|
21
21
|
return if WebMock::StubRegistry.instance.request_stubs.empty?
|
|
22
22
|
|
|
23
|
-
text = "registered request stubs:\n"
|
|
23
|
+
text = "registered request stubs:\n".dup
|
|
24
24
|
WebMock::StubRegistry.instance.request_stubs.each do |stub|
|
|
25
25
|
text << "\n#{WebMock::StubRequestSnippet.new(stub).to_s(false)}"
|
|
26
26
|
add_body_diff(stub, text) if WebMock.show_body_diff?
|
|
@@ -50,7 +50,7 @@ module WebMock
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def pretty_print_to_string(string_to_print)
|
|
53
|
-
StringIO.open("") do |stream|
|
|
53
|
+
StringIO.open("".dup) do |stream|
|
|
54
54
|
PP.pp(string_to_print, stream)
|
|
55
55
|
stream.rewind
|
|
56
56
|
stream.read
|
data/lib/webmock/request_stub.rb
CHANGED
|
@@ -24,6 +24,21 @@ module WebMock
|
|
|
24
24
|
end
|
|
25
25
|
alias_method :and_return, :to_return
|
|
26
26
|
|
|
27
|
+
def to_return_json(*response_hashes)
|
|
28
|
+
raise ArgumentError, '#to_return_json does not support passing a block' if block_given?
|
|
29
|
+
|
|
30
|
+
json_response_hashes = [*response_hashes].flatten.map do |resp_h|
|
|
31
|
+
headers, body = resp_h.values_at(:headers, :body)
|
|
32
|
+
resp_h.merge(
|
|
33
|
+
headers: {content_type: 'application/json'}.merge(headers.to_h),
|
|
34
|
+
body: body.is_a?(Hash) ? body.to_json : body
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
to_return(json_response_hashes)
|
|
39
|
+
end
|
|
40
|
+
alias_method :and_return_json, :to_return_json
|
|
41
|
+
|
|
27
42
|
def to_rack(app, options={})
|
|
28
43
|
@responses_sequences << ResponsesSequence.new([RackResponse.new(app)])
|
|
29
44
|
end
|
data/lib/webmock/response.rb
CHANGED
|
@@ -14,8 +14,11 @@ module WebMock
|
|
|
14
14
|
|
|
15
15
|
class Response
|
|
16
16
|
def initialize(options = {})
|
|
17
|
-
|
|
17
|
+
case options
|
|
18
|
+
when IO, StringIO
|
|
18
19
|
self.options = read_raw_response(options)
|
|
20
|
+
when String
|
|
21
|
+
self.options = read_raw_response(StringIO.new(options))
|
|
19
22
|
else
|
|
20
23
|
self.options = options
|
|
21
24
|
end
|
|
@@ -91,10 +94,10 @@ module WebMock
|
|
|
91
94
|
|
|
92
95
|
def ==(other)
|
|
93
96
|
self.body == other.body &&
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
self.headers === other.headers &&
|
|
98
|
+
self.status == other.status &&
|
|
99
|
+
self.exception == other.exception &&
|
|
100
|
+
self.should_timeout == other.should_timeout
|
|
98
101
|
end
|
|
99
102
|
|
|
100
103
|
private
|
|
@@ -111,16 +114,17 @@ module WebMock
|
|
|
111
114
|
valid_types = [Proc, IO, Pathname, String, Array]
|
|
112
115
|
return if @body.nil?
|
|
113
116
|
return if valid_types.any? { |c| @body.is_a?(c) }
|
|
114
|
-
raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
|
|
115
|
-
end
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
if @body.class.is_a?(Hash)
|
|
119
|
+
raise InvalidBody, "must be one of: #{valid_types}, but you've used a #{@body.class}' instead." \
|
|
120
|
+
"\n What shall we encode it to? try calling .to_json .to_xml instead on the hash instead, or otherwise convert it to a string."
|
|
121
|
+
else
|
|
122
|
+
raise InvalidBody, "must be one of: #{valid_types}. '#{@body.class}' given"
|
|
122
123
|
end
|
|
123
|
-
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def read_raw_response(io)
|
|
127
|
+
socket = ::Net::BufferedIO.new(io)
|
|
124
128
|
response = ::Net::HTTPResponse.read_new(socket)
|
|
125
129
|
transfer_encoding = response.delete('transfer-encoding') #chunks were already read by curl
|
|
126
130
|
response.reading_body(socket, true) {}
|
|
@@ -132,6 +136,8 @@ module WebMock
|
|
|
132
136
|
options[:body] = response.read_body
|
|
133
137
|
options[:status] = [response.code.to_i, response.message]
|
|
134
138
|
options
|
|
139
|
+
ensure
|
|
140
|
+
socket.close
|
|
135
141
|
end
|
|
136
142
|
|
|
137
143
|
InvalidBody = Class.new(StandardError)
|
data/lib/webmock/rspec.rb
CHANGED
|
@@ -20,14 +20,21 @@ end
|
|
|
20
20
|
|
|
21
21
|
require 'webmock/rspec/matchers'
|
|
22
22
|
|
|
23
|
-
WebMock.enable!
|
|
24
|
-
|
|
25
23
|
RSPEC_CONFIGURER.configure { |config|
|
|
26
24
|
|
|
27
25
|
config.include WebMock::API
|
|
28
26
|
config.include WebMock::Matchers
|
|
29
27
|
|
|
30
|
-
config.
|
|
28
|
+
config.before(:suite) do
|
|
29
|
+
WebMock.enable!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
config.after(:suite) do
|
|
33
|
+
WebMock.disable!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
config.around(:each) do |example|
|
|
37
|
+
example.run
|
|
31
38
|
WebMock.reset!
|
|
32
39
|
end
|
|
33
40
|
}
|
|
@@ -10,25 +10,39 @@ module WebMock
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def global_stubs
|
|
13
|
-
@global_stubs ||= []
|
|
13
|
+
@global_stubs ||= Hash.new { |h, k| h[k] = [] }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def reset!
|
|
17
17
|
self.request_stubs = []
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def register_global_stub(&block)
|
|
20
|
+
def register_global_stub(order = :before_local_stubs, &block)
|
|
21
|
+
unless %i[before_local_stubs after_local_stubs].include?(order)
|
|
22
|
+
raise ArgumentError.new("Wrong order. Use :before_local_stubs or :after_local_stubs")
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
# This hash contains the responses returned by the block,
|
|
22
26
|
# keyed by the exact request (using the object_id).
|
|
23
27
|
# That way, there's no race condition in case #to_return
|
|
24
28
|
# doesn't run immediately after stub.with.
|
|
25
29
|
responses = {}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
response_lock = Mutex.new
|
|
31
|
+
|
|
32
|
+
stub = ::WebMock::RequestStub.new(:any, ->(uri) { true }).with { |request|
|
|
33
|
+
update_response = -> { responses[request.object_id] = yield(request) }
|
|
34
|
+
|
|
35
|
+
# The block can recurse, so only lock if we don't already own it
|
|
36
|
+
if response_lock.owned?
|
|
37
|
+
update_response.call
|
|
38
|
+
else
|
|
39
|
+
response_lock.synchronize(&update_response)
|
|
40
|
+
end
|
|
41
|
+
}.to_return(lambda { |request|
|
|
42
|
+
response_lock.synchronize { responses.delete(request.object_id) }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
global_stubs[order].push stub
|
|
32
46
|
end
|
|
33
47
|
|
|
34
48
|
def register_request_stub(stub)
|
|
@@ -54,9 +68,10 @@ module WebMock
|
|
|
54
68
|
private
|
|
55
69
|
|
|
56
70
|
def request_stub_for(request_signature)
|
|
57
|
-
(global_stubs + request_stubs
|
|
58
|
-
registered_request_stub
|
|
59
|
-
|
|
71
|
+
(global_stubs[:before_local_stubs] + request_stubs + global_stubs[:after_local_stubs])
|
|
72
|
+
.detect { |registered_request_stub|
|
|
73
|
+
registered_request_stub.request_pattern.matches?(request_signature)
|
|
74
|
+
}
|
|
60
75
|
end
|
|
61
76
|
|
|
62
77
|
def evaluate_response_for_request(response, request_signature)
|