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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
|
# frozen_string_literal: true
module WebMock
module RSpecMatcherDetector
def rSpecHashIncludingMatcher?(matcher)
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashIncludingMatcher/
end
def rSpecHashExcludingMatcher?(matcher)
matcher.class.name =~ /R?Spec::Mocks::ArgumentMatchers::HashExcludingMatcher/
end
end
class RequestPattern
attr_reader :method_pattern, :uri_pattern, :body_pattern, :headers_pattern
def initialize(method, uri, options = {})
@method_pattern = MethodPattern.new(method)
@uri_pattern = create_uri_pattern(uri)
@body_pattern = nil
@headers_pattern = nil
@with_block = nil
assign_options(options)
end
def with(options = {}, &block)
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?
assign_options(options)
@with_block = block
self
end
def matches?(request_signature)
content_type = request_signature.headers['Content-Type'] if request_signature.headers
content_type = content_type.split(';').first if content_type
@method_pattern.matches?(request_signature.method) &&
@uri_pattern.matches?(request_signature.uri) &&
(@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) &&
(@headers_pattern.nil? || @headers_pattern.matches?(request_signature.headers)) &&
(@with_block.nil? || @with_block.call(request_signature))
end
def to_s
string = "#{@method_pattern.to_s.upcase}".dup
string << " #{@uri_pattern.to_s}"
string << " with body #{@body_pattern.to_s}" if @body_pattern
string << " with headers #{@headers_pattern.to_s}" if @headers_pattern
string << " with given block" if @with_block
string
end
private
def assign_options(options)
options = WebMock::Util::HashKeysStringifier.stringify_keys!(options, deep: true)
HashValidator.new(options).validate_keys('body', 'headers', 'query', 'basic_auth')
set_basic_auth_as_headers!(options)
@body_pattern = BodyPattern.new(options['body']) if options.has_key?('body')
@headers_pattern = HeadersPattern.new(options['headers']) if options.has_key?('headers')
@uri_pattern.add_query_params(options['query']) if options.has_key?('query')
end
def set_basic_auth_as_headers!(options)
if basic_auth = options.delete('basic_auth')
validate_basic_auth!(basic_auth)
options['headers'] ||= {}
options['headers']['Authorization'] = WebMock::Util::Headers.basic_auth_header(basic_auth[0],basic_auth[1])
end
end
def validate_basic_auth!(basic_auth)
if !basic_auth.is_a?(Array) || basic_auth.map{|e| e.is_a?(String)}.uniq != [true]
raise "The basic_auth option value should be an array which contains 2 strings: username and password"
end
end
def create_uri_pattern(uri)
if uri.is_a?(Regexp)
URIRegexpPattern.new(uri)
elsif uri.is_a?(Addressable::Template)
URIAddressablePattern.new(uri)
elsif uri.respond_to?(:call)
URICallablePattern.new(uri)
elsif uri.is_a?(::URI::Generic)
URIStringPattern.new(uri.to_s)
elsif uri.is_a?(String)
URIStringPattern.new(uri)
else
raise ArgumentError.new("URI should be a String, Regexp, Addressable::Template or a callable object. Got: #{uri.class}")
end
end
end
class MethodPattern
def initialize(pattern)
@pattern = pattern
end
def matches?(method)
@pattern == method || @pattern == :any
end
def to_s
@pattern.to_s
end
end
class URIPattern
include RSpecMatcherDetector
def initialize(pattern)
@pattern = if pattern.is_a?(Addressable::URI) \
|| pattern.is_a?(Addressable::Template)
pattern
elsif pattern.respond_to?(:call)
pattern
else
WebMock::Util::URI.normalize_uri(pattern)
end
@query_params = nil
end
def add_query_params(query_params)
@query_params = if query_params.is_a?(Hash)
query_params
elsif query_params.is_a?(WebMock::Matchers::HashIncludingMatcher) \
|| query_params.is_a?(WebMock::Matchers::HashExcludingMatcher)
query_params
elsif rSpecHashIncludingMatcher?(query_params)
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
elsif rSpecHashExcludingMatcher?(query_params)
WebMock::Matchers::HashExcludingMatcher.from_rspec_matcher(query_params)
else
WebMock::Util::QueryMapper.query_to_values(query_params, notation: Config.instance.query_values_notation)
end
end
def matches?(uri)
pattern_matches?(uri) && query_params_matches?(uri)
end
def to_s
str = pattern_inspect
str += " with query params #{@query_params.inspect}" if @query_params
str
end
private
def pattern_inspect
@pattern.inspect
end
def query_params_matches?(uri)
@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query, notation: Config.instance.query_values_notation)
end
end
class URICallablePattern < URIPattern
private
def pattern_matches?(uri)
@pattern.call(uri)
end
end
class URIRegexpPattern < URIPattern
private
def pattern_matches?(uri)
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) }
end
end
class URIAddressablePattern < URIPattern
def add_query_params(query_params)
@@add_query_params_warned ||= false
if not @@add_query_params_warned
@@add_query_params_warned = true
warn "WebMock warning: ignoring query params in RFC 6570 template and checking them with WebMock"
end
super(query_params)
end
private
def pattern_matches?(uri)
if @query_params.nil?
# Let Addressable check the whole URI
matches_with_variations?(uri)
else
# WebMock checks the query, Addressable checks everything else
matches_with_variations?(uri.omit(:query))
end
end
def pattern_inspect
@pattern.pattern.inspect
end
def matches_with_variations?(uri)
template =
begin
Addressable::Template.new(WebMock::Util::URI.heuristic_parse(@pattern.pattern))
rescue Addressable::URI::InvalidURIError
Addressable::Template.new(@pattern.pattern)
end
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u|
template_matches_uri?(template, u)
}
end
def template_matches_uri?(template, uri)
template.match(uri)
rescue Addressable::URI::InvalidURIError
false
end
end
class URIStringPattern < URIPattern
def add_query_params(query_params)
super
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query, notation: Config.instance.query_values_notation) || {}).merge(@query_params)
@pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash, notation: WebMock::Config.instance.query_values_notation)
@query_params = nil
end
end
private
def pattern_matches?(uri)
if @pattern.is_a?(Addressable::URI)
if @query_params
uri.omit(:query) === @pattern
else
uri === @pattern
end
else
false
end
end
def pattern_inspect
WebMock::Util::URI.strip_default_port_from_uri_string(@pattern.to_s)
end
end
class BodyPattern
include RSpecMatcherDetector
BODY_FORMATS = {
'text/xml' => :xml,
'application/xml' => :xml,
'application/json' => :json,
'text/json' => :json,
'application/javascript' => :json,
'text/javascript' => :json,
'text/html' => :html,
'application/x-yaml' => :yaml,
'text/yaml' => :yaml,
'text/plain' => :plain
}
attr_reader :pattern
def initialize(pattern)
@pattern = if pattern.is_a?(Hash)
normalize_hash(pattern)
elsif rSpecHashIncludingMatcher?(pattern)
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(pattern)
else
pattern
end
end
def matches?(body, content_type = "")
assert_non_multipart_body(content_type)
if (@pattern).is_a?(Hash)
return true if @pattern.empty?
matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type)
elsif (@pattern).is_a?(Array)
matching_body_array?(body_as_hash(body, content_type), @pattern, content_type)
elsif (@pattern).is_a?(WebMock::Matchers::HashArgumentMatcher)
@pattern == body_as_hash(body, content_type)
else
empty_string?(@pattern) && empty_string?(body) ||
@pattern == body ||
@pattern === body
end
end
def to_s
@pattern.inspect
end
private
def body_as_hash(body, content_type)
case body_format(content_type)
when :json then
WebMock::Util::Parsers::JSON.parse(body)
when :xml then
WebMock::Util::Parsers::XML.parse(body)
else
WebMock::Util::QueryMapper.query_to_values(body, notation: Config.instance.query_values_notation)
end
rescue WebMock::Util::Parsers::ParseError
nil
end
def body_format(content_type)
normalized_content_type = content_type.sub(/\A(application\/)[a-zA-Z0-9.-]+\+(json|xml)\Z/,'\1\2')
BODY_FORMATS[normalized_content_type]
end
def assert_non_multipart_body(content_type)
if content_type =~ %r{^multipart/form-data}
raise ArgumentError.new("WebMock does not support matching body for multipart/form-data requests yet :(")
end
end
# Compare two hashes for equality
#
# For two hashes to match they must have the same length and all
# values must match when compared using `#===`.
#
# The following hashes are examples of matches:
#
# {a: /\d+/} and {a: '123'}
#
# {a: '123'} and {a: '123'}
#
# {a: {b: /\d+/}} and {a: {b: '123'}}
#
# {a: {b: 'wow'}} and {a: {b: 'wow'}}
#
# @param [Hash] query_parameters typically the result of parsing
# JSON, XML or URL encoded parameters.
#
# @param [Hash] pattern which contains keys with a string, hash or
# regular expression value to use for comparison.
#
# @return [Boolean] true if the paramaters match the comparison
# hash, false if not.
def matching_body_hashes?(query_parameters, pattern, content_type)
return false unless query_parameters.is_a?(Hash)
return false unless query_parameters.keys.sort == pattern.keys.sort
query_parameters.all? do |key, actual|
expected = pattern[key]
matching_values(actual, expected, content_type)
end
end
def matching_body_array?(query_parameters, pattern, content_type)
return false unless query_parameters.is_a?(Array)
return false unless query_parameters.length == pattern.length
query_parameters.each_with_index do |actual, index|
expected = pattern[index]
return false unless matching_values(actual, expected, content_type)
end
true
end
def matching_values(actual, expected, content_type)
return matching_body_hashes?(actual, expected, content_type) if actual.is_a?(Hash) && expected.is_a?(Hash)
return matching_body_array?(actual, expected, content_type) if actual.is_a?(Array) && expected.is_a?(Array)
expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type)
expected === actual
end
def empty_string?(string)
string.nil? || string == ""
end
def normalize_hash(hash)
Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true).sort]
end
def url_encoded_body?(content_type)
content_type =~ %r{^application/x-www-form-urlencoded}
end
end
class HeadersPattern
def initialize(pattern)
@pattern = WebMock::Util::Headers.normalize_headers(pattern) || {}
end
def matches?(headers)
if empty_headers?(@pattern)
empty_headers?(headers)
else
return false if empty_headers?(headers)
@pattern.each do |key, value|
return false unless headers.has_key?(key) && value === headers[key]
end
true
end
end
def to_s
WebMock::Util::Headers.sorted_headers_string(@pattern)
end
def pp_to_s
WebMock::Util::Headers.pp_headers_string(@pattern)
end
private
def empty_headers?(headers)
headers.nil? || headers == {}
end
end
end
|