webmock 3.7.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 +7 -0
- data/.gemtest +0 -0
- data/.gitignore +34 -0
- data/.rspec-tm +2 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +1698 -0
- data/Gemfile +9 -0
- data/LICENSE +20 -0
- data/README.md +1125 -0
- data/Rakefile +28 -0
- data/lib/webmock.rb +59 -0
- data/lib/webmock/api.rb +109 -0
- data/lib/webmock/assertion_failure.rb +11 -0
- data/lib/webmock/callback_registry.rb +35 -0
- data/lib/webmock/config.rb +18 -0
- data/lib/webmock/cucumber.rb +10 -0
- data/lib/webmock/deprecation.rb +9 -0
- data/lib/webmock/errors.rb +17 -0
- data/lib/webmock/http_lib_adapters/async_http_client_adapter.rb +214 -0
- data/lib/webmock/http_lib_adapters/curb_adapter.rb +347 -0
- data/lib/webmock/http_lib_adapters/em_http_request_adapter.rb +228 -0
- data/lib/webmock/http_lib_adapters/excon_adapter.rb +162 -0
- data/lib/webmock/http_lib_adapters/http_lib_adapter.rb +7 -0
- data/lib/webmock/http_lib_adapters/http_lib_adapter_registry.rb +19 -0
- data/lib/webmock/http_lib_adapters/http_rb/client.rb +14 -0
- data/lib/webmock/http_lib_adapters/http_rb/request.rb +16 -0
- data/lib/webmock/http_lib_adapters/http_rb/response.rb +43 -0
- data/lib/webmock/http_lib_adapters/http_rb/streamer.rb +29 -0
- data/lib/webmock/http_lib_adapters/http_rb/webmock.rb +68 -0
- data/lib/webmock/http_lib_adapters/http_rb_adapter.rb +37 -0
- data/lib/webmock/http_lib_adapters/httpclient_adapter.rb +242 -0
- data/lib/webmock/http_lib_adapters/manticore_adapter.rb +130 -0
- data/lib/webmock/http_lib_adapters/net_http.rb +361 -0
- data/lib/webmock/http_lib_adapters/net_http_response.rb +34 -0
- data/lib/webmock/http_lib_adapters/patron_adapter.rb +130 -0
- data/lib/webmock/http_lib_adapters/typhoeus_hydra_adapter.rb +174 -0
- 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 +17 -0
- data/lib/webmock/minitest.rb +41 -0
- data/lib/webmock/rack_response.rb +69 -0
- data/lib/webmock/request_body_diff.rb +64 -0
- data/lib/webmock/request_execution_verifier.rb +77 -0
- data/lib/webmock/request_pattern.rb +370 -0
- data/lib/webmock/request_registry.rb +35 -0
- data/lib/webmock/request_signature.rb +54 -0
- data/lib/webmock/request_signature_snippet.rb +61 -0
- data/lib/webmock/request_stub.rb +100 -0
- data/lib/webmock/response.rb +153 -0
- data/lib/webmock/responses_sequence.rb +40 -0
- data/lib/webmock/rspec.rb +41 -0
- data/lib/webmock/rspec/matchers.rb +27 -0
- data/lib/webmock/rspec/matchers/request_pattern_matcher.rb +78 -0
- data/lib/webmock/rspec/matchers/webmock_matcher.rb +67 -0
- data/lib/webmock/stub_registry.rb +67 -0
- data/lib/webmock/stub_request_snippet.rb +38 -0
- data/lib/webmock/test_unit.rb +22 -0
- data/lib/webmock/util/hash_counter.rb +39 -0
- data/lib/webmock/util/hash_keys_stringifier.rb +25 -0
- data/lib/webmock/util/hash_validator.rb +17 -0
- data/lib/webmock/util/headers.rb +64 -0
- data/lib/webmock/util/json.rb +67 -0
- data/lib/webmock/util/query_mapper.rb +281 -0
- data/lib/webmock/util/uri.rb +110 -0
- data/lib/webmock/util/values_stringifier.rb +20 -0
- data/lib/webmock/util/version_checker.rb +111 -0
- data/lib/webmock/version.rb +3 -0
- data/lib/webmock/webmock.rb +161 -0
- data/minitest/test_helper.rb +34 -0
- data/minitest/test_webmock.rb +9 -0
- data/minitest/webmock_spec.rb +60 -0
- data/spec/acceptance/async_http_client/async_http_client_spec.rb +349 -0
- data/spec/acceptance/async_http_client/async_http_client_spec_helper.rb +73 -0
- data/spec/acceptance/curb/curb_spec.rb +492 -0
- data/spec/acceptance/curb/curb_spec_helper.rb +147 -0
- data/spec/acceptance/em_http_request/em_http_request_spec.rb +406 -0
- data/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +77 -0
- data/spec/acceptance/excon/excon_spec.rb +77 -0
- data/spec/acceptance/excon/excon_spec_helper.rb +50 -0
- data/spec/acceptance/http_rb/http_rb_spec.rb +82 -0
- data/spec/acceptance/http_rb/http_rb_spec_helper.rb +54 -0
- data/spec/acceptance/httpclient/httpclient_spec.rb +217 -0
- data/spec/acceptance/httpclient/httpclient_spec_helper.rb +57 -0
- data/spec/acceptance/manticore/manticore_spec.rb +56 -0
- data/spec/acceptance/manticore/manticore_spec_helper.rb +35 -0
- data/spec/acceptance/net_http/net_http_shared.rb +153 -0
- data/spec/acceptance/net_http/net_http_spec.rb +331 -0
- data/spec/acceptance/net_http/net_http_spec_helper.rb +64 -0
- data/spec/acceptance/net_http/real_net_http_spec.rb +20 -0
- data/spec/acceptance/patron/patron_spec.rb +125 -0
- data/spec/acceptance/patron/patron_spec_helper.rb +54 -0
- data/spec/acceptance/shared/allowing_and_disabling_net_connect.rb +313 -0
- data/spec/acceptance/shared/callbacks.rb +148 -0
- data/spec/acceptance/shared/complex_cross_concern_behaviors.rb +36 -0
- data/spec/acceptance/shared/enabling_and_disabling_webmock.rb +95 -0
- data/spec/acceptance/shared/precedence_of_stubs.rb +15 -0
- data/spec/acceptance/shared/request_expectations.rb +930 -0
- data/spec/acceptance/shared/returning_declared_responses.rb +409 -0
- data/spec/acceptance/shared/stubbing_requests.rb +643 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec.rb +135 -0
- data/spec/acceptance/typhoeus/typhoeus_hydra_spec_helper.rb +60 -0
- data/spec/acceptance/webmock_shared.rb +41 -0
- data/spec/fixtures/test.txt +1 -0
- data/spec/quality_spec.rb +84 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/support/example_curl_output.txt +22 -0
- data/spec/support/failures.rb +9 -0
- data/spec/support/my_rack_app.rb +53 -0
- data/spec/support/network_connection.rb +19 -0
- data/spec/support/webmock_server.rb +70 -0
- data/spec/unit/api_spec.rb +175 -0
- data/spec/unit/errors_spec.rb +129 -0
- data/spec/unit/http_lib_adapters/http_lib_adapter_registry_spec.rb +17 -0
- data/spec/unit/http_lib_adapters/http_lib_adapter_spec.rb +12 -0
- data/spec/unit/matchers/hash_excluding_matcher_spec.rb +61 -0
- data/spec/unit/matchers/hash_including_matcher_spec.rb +87 -0
- data/spec/unit/rack_response_spec.rb +112 -0
- data/spec/unit/request_body_diff_spec.rb +90 -0
- data/spec/unit/request_execution_verifier_spec.rb +208 -0
- data/spec/unit/request_pattern_spec.rb +601 -0
- data/spec/unit/request_registry_spec.rb +95 -0
- data/spec/unit/request_signature_snippet_spec.rb +89 -0
- data/spec/unit/request_signature_spec.rb +155 -0
- data/spec/unit/request_stub_spec.rb +199 -0
- data/spec/unit/response_spec.rb +282 -0
- data/spec/unit/stub_registry_spec.rb +103 -0
- data/spec/unit/stub_request_snippet_spec.rb +115 -0
- data/spec/unit/util/hash_counter_spec.rb +39 -0
- data/spec/unit/util/hash_keys_stringifier_spec.rb +27 -0
- data/spec/unit/util/headers_spec.rb +28 -0
- data/spec/unit/util/json_spec.rb +33 -0
- data/spec/unit/util/query_mapper_spec.rb +157 -0
- data/spec/unit/util/uri_spec.rb +361 -0
- data/spec/unit/util/version_checker_spec.rb +65 -0
- data/spec/unit/webmock_spec.rb +19 -0
- data/test/http_request.rb +24 -0
- data/test/shared_test.rb +108 -0
- data/test/test_helper.rb +23 -0
- data/test/test_webmock.rb +6 -0
- data/webmock.gemspec +45 -0
- metadata +496 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
class RackResponse < Response
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@app = app
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def evaluate(request)
|
|
8
|
+
env = build_rack_env(request)
|
|
9
|
+
|
|
10
|
+
status, headers, response = @app.call(env)
|
|
11
|
+
|
|
12
|
+
Response.new(
|
|
13
|
+
body: body_from_rack_response(response),
|
|
14
|
+
headers: headers,
|
|
15
|
+
status: [status, Rack::Utils::HTTP_STATUS_CODES[status]]
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def body_from_rack_response(response)
|
|
20
|
+
body = "".dup
|
|
21
|
+
response.each { |line| body << line }
|
|
22
|
+
response.close if response.respond_to?(:close)
|
|
23
|
+
return body
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def build_rack_env(request)
|
|
27
|
+
uri = request.uri
|
|
28
|
+
headers = (request.headers || {}).dup
|
|
29
|
+
body = request.body || ''
|
|
30
|
+
|
|
31
|
+
env = {
|
|
32
|
+
# CGI variables specified by Rack
|
|
33
|
+
'REQUEST_METHOD' => request.method.to_s.upcase,
|
|
34
|
+
'CONTENT_TYPE' => headers.delete('Content-Type'),
|
|
35
|
+
'CONTENT_LENGTH' => body.bytesize,
|
|
36
|
+
'PATH_INFO' => uri.path,
|
|
37
|
+
'QUERY_STRING' => uri.query || '',
|
|
38
|
+
'SERVER_NAME' => uri.host,
|
|
39
|
+
'SERVER_PORT' => uri.port,
|
|
40
|
+
'SCRIPT_NAME' => ""
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
env['HTTP_AUTHORIZATION'] = 'Basic ' + [uri.userinfo].pack('m').delete("\r\n") if uri.userinfo
|
|
44
|
+
|
|
45
|
+
# Rack-specific variables
|
|
46
|
+
env['rack.input'] = StringIO.new(body)
|
|
47
|
+
env['rack.errors'] = $stderr
|
|
48
|
+
env['rack.version'] = Rack::VERSION
|
|
49
|
+
env['rack.url_scheme'] = uri.scheme
|
|
50
|
+
env['rack.run_once'] = true
|
|
51
|
+
env['rack.session'] = session
|
|
52
|
+
env['rack.session.options'] = session_options
|
|
53
|
+
|
|
54
|
+
headers.each do |k, v|
|
|
55
|
+
env["HTTP_#{k.tr('-','_').upcase}"] = v
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
env
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def session
|
|
62
|
+
@session ||= {}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def session_options
|
|
66
|
+
@session_options ||= {}
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "hashdiff"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module WebMock
|
|
5
|
+
class RequestBodyDiff
|
|
6
|
+
|
|
7
|
+
def initialize(request_signature, request_stub)
|
|
8
|
+
@request_signature = request_signature
|
|
9
|
+
@request_stub = request_stub
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def body_diff
|
|
13
|
+
return {} unless request_signature_diffable? && request_stub_diffable?
|
|
14
|
+
|
|
15
|
+
Hashdiff.diff(request_signature_body_hash, request_stub_body_hash)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :request_signature, :request_stub
|
|
19
|
+
private :request_signature, :request_stub
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def request_signature_diffable?
|
|
24
|
+
request_signature.json_headers? && request_signature_parseable_json?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def request_stub_diffable?
|
|
28
|
+
request_stub_body.is_a?(Hash) || request_stub_parseable_json?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def request_signature_body_hash
|
|
32
|
+
JSON.parse(request_signature.body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def request_stub_body_hash
|
|
36
|
+
return request_stub_body if request_stub_body.is_a?(Hash)
|
|
37
|
+
|
|
38
|
+
JSON.parse(request_stub_body)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def request_stub_body
|
|
42
|
+
request_stub.request_pattern &&
|
|
43
|
+
request_stub.request_pattern.body_pattern &&
|
|
44
|
+
request_stub.request_pattern.body_pattern.pattern
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def request_signature_parseable_json?
|
|
48
|
+
parseable_json?(request_signature.body)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def request_stub_parseable_json?
|
|
52
|
+
parseable_json?(request_stub_body)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parseable_json?(body_pattern)
|
|
56
|
+
return false unless body_pattern.is_a?(String)
|
|
57
|
+
|
|
58
|
+
JSON.parse(body_pattern)
|
|
59
|
+
true
|
|
60
|
+
rescue JSON::ParserError
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
class RequestExecutionVerifier
|
|
3
|
+
|
|
4
|
+
attr_accessor :request_pattern, :expected_times_executed, :times_executed, :at_least_times_executed, :at_most_times_executed
|
|
5
|
+
|
|
6
|
+
def initialize(request_pattern = nil, expected_times_executed = nil, at_least_times_executed = nil, at_most_times_executed = nil)
|
|
7
|
+
@request_pattern = request_pattern
|
|
8
|
+
@expected_times_executed = expected_times_executed
|
|
9
|
+
@at_least_times_executed = at_least_times_executed
|
|
10
|
+
@at_most_times_executed = at_most_times_executed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def matches?
|
|
14
|
+
@times_executed =
|
|
15
|
+
RequestRegistry.instance.times_executed(@request_pattern)
|
|
16
|
+
|
|
17
|
+
if @at_least_times_executed
|
|
18
|
+
@times_executed >= @at_least_times_executed
|
|
19
|
+
elsif @at_most_times_executed
|
|
20
|
+
@times_executed <= @at_most_times_executed
|
|
21
|
+
else
|
|
22
|
+
@times_executed == (@expected_times_executed || 1)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def does_not_match?
|
|
27
|
+
@times_executed =
|
|
28
|
+
RequestRegistry.instance.times_executed(@request_pattern)
|
|
29
|
+
if @expected_times_executed
|
|
30
|
+
@times_executed != @expected_times_executed
|
|
31
|
+
else
|
|
32
|
+
@times_executed == 0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def description
|
|
37
|
+
"request #{request_pattern} #{quantity_phrase.strip}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def failure_message
|
|
41
|
+
failure_message_phrase(false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def failure_message_when_negated
|
|
45
|
+
failure_message_phrase(true)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.executed_requests_message
|
|
49
|
+
"\n\nThe following requests were made:\n\n#{RequestRegistry.instance}\n" + "="*60
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def failure_message_phrase(is_negated=false)
|
|
55
|
+
negation = is_negated ? "was not" : "was"
|
|
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
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def quantity_phrase(is_negated=false)
|
|
61
|
+
if @at_least_times_executed
|
|
62
|
+
"at least #{times(@at_least_times_executed)} "
|
|
63
|
+
elsif @at_most_times_executed
|
|
64
|
+
"at most #{times(@at_most_times_executed)} "
|
|
65
|
+
elsif @expected_times_executed
|
|
66
|
+
"#{times(@expected_times_executed)} "
|
|
67
|
+
else
|
|
68
|
+
is_negated ? "" : "#{times(1)} "
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def times(times)
|
|
73
|
+
"#{times} time#{ (times == 1) ? '' : 's'}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
module WebMock
|
|
2
|
+
|
|
3
|
+
module RSpecMatcherDetector
|
|
4
|
+
def rSpecHashIncludingMatcher?(matcher)
|
|
5
|
+
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def rSpecHashExcludingMatcher?(matcher)
|
|
9
|
+
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashExcludingMatcher/
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class RequestPattern
|
|
14
|
+
|
|
15
|
+
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
|
|
16
|
+
|
|
17
|
+
def initialize(method, uri, options = {})
|
|
18
|
+
@method_pattern = MethodPattern.new(method)
|
|
19
|
+
@uri_pattern = create_uri_pattern(uri)
|
|
20
|
+
@body_pattern = nil
|
|
21
|
+
@headers_pattern = nil
|
|
22
|
+
@with_block = nil
|
|
23
|
+
assign_options(options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def with(options = {}, &block)
|
|
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?
|
|
28
|
+
assign_options(options)
|
|
29
|
+
@with_block = block
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def matches?(request_signature)
|
|
34
|
+
content_type = request_signature.headers['Content-Type'] if request_signature.headers
|
|
35
|
+
content_type = content_type.split(';').first if content_type
|
|
36
|
+
@method_pattern.matches?(request_signature.method) &&
|
|
37
|
+
@uri_pattern.matches?(request_signature.uri) &&
|
|
38
|
+
(@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
|
|
39
|
+
(@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
|
|
40
|
+
(@with_block.nil? || @with_block.call(request_signature))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
string = "#{@method_pattern.to_s.upcase}".dup
|
|
45
|
+
string << " #{@uri_pattern.to_s}"
|
|
46
|
+
string << " with body #{@body_pattern.to_s}" if @body_pattern
|
|
47
|
+
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
|
|
48
|
+
string << " with given block" if @with_block
|
|
49
|
+
string
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def assign_options(options)
|
|
56
|
+
options = WebMock::Util::HashKeysStringifier.stringify_keys!(options, deep: true)
|
|
57
|
+
HashValidator.new(options).validate_keys('body', 'headers', 'query', 'basic_auth')
|
|
58
|
+
set_basic_auth_as_headers!(options)
|
|
59
|
+
@body_pattern = BodyPattern.new(options['body']) if options.has_key?('body')
|
|
60
|
+
@headers_pattern = HeadersPattern.new(options['headers']) if options.has_key?('headers')
|
|
61
|
+
@uri_pattern.add_query_params(options['query']) if options.has_key?('query')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def set_basic_auth_as_headers!(options)
|
|
65
|
+
if basic_auth = options.delete('basic_auth')
|
|
66
|
+
validate_basic_auth!(basic_auth)
|
|
67
|
+
options['headers'] ||= {}
|
|
68
|
+
options['headers']['Authorization'] = WebMock::Util::Headers.basic_auth_header(basic_auth[0],basic_auth[1])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_basic_auth!(basic_auth)
|
|
73
|
+
if !basic_auth.is_a?(Array) || basic_auth.map{|e| e.is_a?(String)}.uniq != [true]
|
|
74
|
+
raise "The basic_auth option value should be an array which contains 2 strings: username and password"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def create_uri_pattern(uri)
|
|
79
|
+
if uri.is_a?(Regexp)
|
|
80
|
+
URIRegexpPattern.new(uri)
|
|
81
|
+
elsif uri.is_a?(Addressable::Template)
|
|
82
|
+
URIAddressablePattern.new(uri)
|
|
83
|
+
else
|
|
84
|
+
URIStringPattern.new(uri)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MethodPattern
|
|
92
|
+
def initialize(pattern)
|
|
93
|
+
@pattern = pattern
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def matches?(method)
|
|
97
|
+
@pattern == method || @pattern == :any
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_s
|
|
101
|
+
@pattern.to_s
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class URIPattern
|
|
107
|
+
include RSpecMatcherDetector
|
|
108
|
+
|
|
109
|
+
def initialize(pattern)
|
|
110
|
+
@pattern = case pattern
|
|
111
|
+
when Addressable::URI, Addressable::Template
|
|
112
|
+
pattern
|
|
113
|
+
else
|
|
114
|
+
WebMock::Util::URI.normalize_uri(pattern)
|
|
115
|
+
end
|
|
116
|
+
@query_params = nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def add_query_params(query_params)
|
|
120
|
+
@query_params = if query_params.is_a?(Hash)
|
|
121
|
+
query_params
|
|
122
|
+
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher) \
|
|
123
|
+
|| query_params.is_a?(WebMock::Matchers::HashExcludingMatcher)
|
|
124
|
+
query_params
|
|
125
|
+
elsif rSpecHashIncludingMatcher?(query_params)
|
|
126
|
+
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
|
|
127
|
+
elsif rSpecHashExcludingMatcher?(query_params)
|
|
128
|
+
WebMock::Matchers::HashExcludingMatcher.from_rspec_matcher(query_params)
|
|
129
|
+
else
|
|
130
|
+
WebMock::Util::QueryMapper.query_to_values(query_params, notation: Config.instance.query_values_notation)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def to_s
|
|
135
|
+
str = @pattern.inspect
|
|
136
|
+
str += " with query params #{@query_params.inspect}" if @query_params
|
|
137
|
+
str
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class URIRegexpPattern < URIPattern
|
|
142
|
+
def matches?(uri)
|
|
143
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
|
|
144
|
+
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def to_s
|
|
148
|
+
str = @pattern.inspect
|
|
149
|
+
str += " with query params #{@query_params.inspect}" if @query_params
|
|
150
|
+
str
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class URIAddressablePattern < URIPattern
|
|
155
|
+
def matches?(uri)
|
|
156
|
+
if @query_params.nil?
|
|
157
|
+
# Let Addressable check the whole URI
|
|
158
|
+
matches_with_variations?(uri)
|
|
159
|
+
else
|
|
160
|
+
# WebMock checks the query, Addressable checks everything else
|
|
161
|
+
matches_with_variations?(uri.omit(:query)) &&
|
|
162
|
+
@query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def add_query_params(query_params)
|
|
167
|
+
@@add_query_params_warned ||= false
|
|
168
|
+
if not @@add_query_params_warned
|
|
169
|
+
@@add_query_params_warned = true
|
|
170
|
+
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
|
|
171
|
+
end
|
|
172
|
+
super(query_params)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def to_s
|
|
176
|
+
str = @pattern.pattern.inspect
|
|
177
|
+
str += " with variables #{@pattern.variables.inspect}" if @pattern.variables
|
|
178
|
+
str
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def matches_with_variations?(uri)
|
|
184
|
+
normalized_template = Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
|
|
185
|
+
|
|
186
|
+
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| normalized_template.match(u) }
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
class URIStringPattern < URIPattern
|
|
191
|
+
def matches?(uri)
|
|
192
|
+
if @pattern.is_a?(Addressable::URI)
|
|
193
|
+
if @query_params
|
|
194
|
+
uri.omit(:query) === @pattern &&
|
|
195
|
+
(@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation))
|
|
196
|
+
else
|
|
197
|
+
uri === @pattern
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def add_query_params(query_params)
|
|
205
|
+
super
|
|
206
|
+
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
|
|
207
|
+
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
|
|
208
|
+
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
|
|
209
|
+
@query_params = nil
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def to_s
|
|
214
|
+
str = WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
|
|
215
|
+
str += " with query params #{@query_params.inspect}" if @query_params
|
|
216
|
+
str
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class BodyPattern
|
|
222
|
+
include RSpecMatcherDetector
|
|
223
|
+
|
|
224
|
+
BODY_FORMATS = {
|
|
225
|
+
'text/xml' => :xml,
|
|
226
|
+
'application/xml' => :xml,
|
|
227
|
+
'application/json' => :json,
|
|
228
|
+
'text/json' => :json,
|
|
229
|
+
'application/javascript' => :json,
|
|
230
|
+
'text/javascript' => :json,
|
|
231
|
+
'text/html' => :html,
|
|
232
|
+
'application/x-yaml' => :yaml,
|
|
233
|
+
'text/yaml' => :yaml,
|
|
234
|
+
'text/plain' => :plain
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
attr_reader :pattern
|
|
238
|
+
|
|
239
|
+
def initialize(pattern)
|
|
240
|
+
@pattern = if pattern.is_a?(Hash)
|
|
241
|
+
normalize_hash(pattern)
|
|
242
|
+
elsif rSpecHashIncludingMatcher?(pattern)
|
|
243
|
+
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(pattern)
|
|
244
|
+
else
|
|
245
|
+
pattern
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def matches?(body, content_type = "")
|
|
250
|
+
assert_non_multipart_body(content_type)
|
|
251
|
+
|
|
252
|
+
if (@pattern).is_a?(Hash)
|
|
253
|
+
return true if @pattern.empty?
|
|
254
|
+
matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
|
|
255
|
+
elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher)
|
|
256
|
+
@pattern == body_as_hash(body, content_type)
|
|
257
|
+
else
|
|
258
|
+
empty_string?(@pattern) && empty_string?(body) ||
|
|
259
|
+
@pattern == body ||
|
|
260
|
+
@pattern === body
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def to_s
|
|
265
|
+
@pattern.inspect
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
private
|
|
269
|
+
def body_as_hash(body, content_type)
|
|
270
|
+
case BODY_FORMATS[content_type]
|
|
271
|
+
when :json then
|
|
272
|
+
WebMock::Util::JSON.parse(body)
|
|
273
|
+
when :xml then
|
|
274
|
+
Crack::XML.parse(body)
|
|
275
|
+
else
|
|
276
|
+
WebMock::Util::QueryMapper.query_to_values(body, notation: Config.instance.query_values_notation)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def assert_non_multipart_body(content_type)
|
|
281
|
+
if content_type =~ %r{^multipart/form-data}
|
|
282
|
+
raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Compare two hashes for equality
|
|
287
|
+
#
|
|
288
|
+
# For two hashes to match they must have the same length and all
|
|
289
|
+
# values must match when compared using `#===`.
|
|
290
|
+
#
|
|
291
|
+
# The following hashes are examples of matches:
|
|
292
|
+
#
|
|
293
|
+
# {a: /\d+/} and {a: '123'}
|
|
294
|
+
#
|
|
295
|
+
# {a: '123'} and {a: '123'}
|
|
296
|
+
#
|
|
297
|
+
# {a: {b: /\d+/}} and {a: {b: '123'}}
|
|
298
|
+
#
|
|
299
|
+
# {a: {b: 'wow'}} and {a: {b: 'wow'}}
|
|
300
|
+
#
|
|
301
|
+
# @param [Hash] query_parameters typically the result of parsing
|
|
302
|
+
# JSON, XML or URL encoded parameters.
|
|
303
|
+
#
|
|
304
|
+
# @param [Hash] pattern which contains keys with a string, hash or
|
|
305
|
+
# regular expression value to use for comparison.
|
|
306
|
+
#
|
|
307
|
+
# @return [Boolean] true if the paramaters match the comparison
|
|
308
|
+
# hash, false if not.
|
|
309
|
+
def matching_body_hashes?(query_parameters, pattern, content_type)
|
|
310
|
+
return false unless query_parameters.is_a?(Hash)
|
|
311
|
+
return false unless query_parameters.keys.sort == pattern.keys.sort
|
|
312
|
+
query_parameters.each do |key, actual|
|
|
313
|
+
expected = pattern[key]
|
|
314
|
+
|
|
315
|
+
if actual.is_a?(Hash) && expected.is_a?(Hash)
|
|
316
|
+
return false unless matching_body_hashes?(actual, expected, content_type)
|
|
317
|
+
else
|
|
318
|
+
expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
|
|
319
|
+
return false unless expected === actual
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
true
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def empty_string?(string)
|
|
326
|
+
string.nil? || string == ""
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def normalize_hash(hash)
|
|
330
|
+
Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true).sort]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def url_encoded_body?(content_type)
|
|
334
|
+
content_type =~ %r{^application/x-www-form-urlencoded}
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
class HeadersPattern
|
|
339
|
+
def initialize(pattern)
|
|
340
|
+
@pattern = WebMock::Util::Headers.normalize_headers(pattern) || {}
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def matches?(headers)
|
|
344
|
+
if empty_headers?(@pattern)
|
|
345
|
+
empty_headers?(headers)
|
|
346
|
+
else
|
|
347
|
+
return false if empty_headers?(headers)
|
|
348
|
+
@pattern.each do |key, value|
|
|
349
|
+
return false unless headers.has_key?(key) && value === headers[key]
|
|
350
|
+
end
|
|
351
|
+
true
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def to_s
|
|
356
|
+
WebMock::Util::Headers.sorted_headers_string(@pattern)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def pp_to_s
|
|
360
|
+
WebMock::Util::Headers.pp_headers_string(@pattern)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
365
|
+
def empty_headers?(headers)
|
|
366
|
+
headers.nil? || headers == {}
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
end
|