1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
require 'vcr/errors'
module VCR
# Keeps track of the different request matchers.
class RequestMatcherRegistry
# The default request matchers used for any cassette that does not
# specify request matchers.
DEFAULT_MATCHERS = [:method, :uri]
# @private
class Matcher < Struct.new(:callable)
def matches?(request_1, request_2)
callable.call(request_1, request_2)
end
end
# @private
class URIWithoutParamsMatcher < Struct.new(:params_to_ignore)
def partial_uri_from(request)
request.parsed_uri.tap do |uri|
return uri unless uri.query # ignore uris without params, e.g. "http://example.com/"
uri.query = uri.query.split('&').tap { |params|
params.map! do |p|
key, value = p.split('=')
key.gsub!(/\[\]\z/, '') # handle params like tag[]=
[key, value]
end
params.reject! { |p| params_to_ignore.include?(p.first) }
params.map! { |p| p.join('=') }
}.join('&')
uri.query = nil if uri.query.empty?
end
end
def call(request_1, request_2)
partial_uri_from(request_1) == partial_uri_from(request_2)
end
def to_proc
lambda { |r1, r2| call(r1, r2) }
end
end
# @private
def initialize
@registry = {}
register_built_ins
end
# @private
def register(name, &block)
if @registry.has_key?(name)
warn "WARNING: There is already a VCR request matcher registered for #{name.inspect}. Overriding it."
end
@registry[name] = Matcher.new(block)
end
# @private
def [](matcher)
@registry.fetch(matcher) do
matcher.respond_to?(:call) ?
Matcher.new(matcher) :
raise_unregistered_matcher_error(matcher)
end
end
# Builds a dynamic request matcher that matches on a URI while ignoring the
# named query parameters. This is useful for dealing with non-deterministic
# URIs (i.e. that have a timestamp or request signature parameter).
#
# @example
# without_timestamp = VCR.request_matchers.uri_without_param(:timestamp)
#
# # use it directly...
# VCR.use_cassette('example', :match_requests_on => [:method, without_timestamp]) { }
#
# # ...or register it as a named matcher
# VCR.configure do |c|
# c.register_request_matcher(:uri_without_timestamp, &without_timestamp)
# end
#
# VCR.use_cassette('example', :match_requests_on => [:method, :uri_without_timestamp]) { }
#
# @param ignores [Array<#to_s>] The names of the query parameters to ignore
# @return [#call] the request matcher
def uri_without_params(*ignores)
uri_without_param_matchers[ignores]
end
alias uri_without_param uri_without_params
private
def uri_without_param_matchers
@uri_without_param_matchers ||= Hash.new do |hash, params|
params = params.map(&:to_s)
hash[params] = URIWithoutParamsMatcher.new(params)
end
end
def raise_unregistered_matcher_error(name)
raise Errors::UnregisteredMatcherError.new \
"There is no matcher registered for #{name.inspect}. " +
"Did you mean one of #{@registry.keys.map(&:inspect).join(', ')}?"
end
def register_built_ins
register(:method) { |r1, r2| r1.method == r2.method }
register(:uri) { |r1, r2| r1.uri == r2.uri }
register(:body) { |r1, r2| r1.body == r2.body }
register(:headers) { |r1, r2| r1.headers == r2.headers }
register(:host) do |r1, r2|
r1.parsed_uri.host == r2.parsed_uri.host
end
register(:path) do |r1, r2|
r1.parsed_uri.path == r2.parsed_uri.path
end
register(:query) do |r1, r2|
VCR.configuration.query_parser.call(r1.parsed_uri.query.to_s) ==
VCR.configuration.query_parser.call(r2.parsed_uri.query.to_s)
end
try_to_register_body_as_json
end
def try_to_register_body_as_json
begin
require 'json'
rescue LoadError
return
end
register(:body_as_json) do |r1, r2|
begin
r1.body == r2.body || JSON.parse(r1.body) == JSON.parse(r2.body)
rescue JSON::ParserError
false
end
end
end
end
end
|