Skip to content

Commit

Permalink
Added support for matching escaped and non escaped URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartosz Blimke committed Nov 22, 2009
1 parent 144d620 commit fba5e3e
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 66 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -8,6 +8,7 @@ Features


* Stubbing requests and setting requests expectations * Stubbing requests and setting requests expectations
* Matching requests based on method, url, headers and body * Matching requests based on method, url, headers and body
* Matching not escaped and escaped urls
* Support for Test::Unit and RSpec (and can be easily extended to other frameworks) * Support for Test::Unit and RSpec (and can be easily extended to other frameworks)
* Support for Net::Http and other http libraries based on Net::Http * Support for Net::Http and other http libraries based on Net::Http
* Adding other http library adapters is easy * Adding other http library adapters is easy
Expand Down Expand Up @@ -187,7 +188,7 @@ Credits
------- -------


Thank you Fakeweb! This library is based on the idea taken from [FakeWeb](fakeweb.rubyforge.org). Thank you Fakeweb! This library is based on the idea taken from [FakeWeb](fakeweb.rubyforge.org).
I took couple of solutions from that project. I also copied some code i.e Net:Http adapter or url normalisation function. I took couple of solutions from that project. I also copied some code i.e Net:Http adapter.
Fakeweb architecture unfortunately didn't allow me to extend it easily with the features I needed. Fakeweb architecture unfortunately didn't allow me to extend it easily with the features I needed.
I also preferred some things to work differently i.e request stub precedence. I also preferred some things to work differently i.e request stub precedence.


Expand Down
3 changes: 3 additions & 0 deletions lib/webmock.rb
@@ -1,5 +1,8 @@
require 'singleton' require 'singleton'


require 'rubygems'
require 'addressable/uri'

require 'webmock/http_lib_adapters/net_http' require 'webmock/http_lib_adapters/net_http'


require 'webmock/errors' require 'webmock/errors'
Expand Down
2 changes: 1 addition & 1 deletion lib/webmock/http_lib_adapters/net_http.rb
Expand Up @@ -61,7 +61,7 @@ def request_with_webmock(request, body = nil, &block)
protocol = use_ssl? ? "https" : "http" protocol = use_ssl? ? "https" : "http"


path = request.path path = request.path
path = URI.parse(request.path).request_uri if request.path =~ /^http/ path = Addressable::URI.heuristic_parse(request.path).request_uri if request.path =~ /^http/


if request["authorization"] =~ /^Basic / if request["authorization"] =~ /^Basic /
userinfo = WebMock::Utility.decode_userinfo_from_header(request["authorization"]) userinfo = WebMock::Utility.decode_userinfo_from_header(request["authorization"])
Expand Down
11 changes: 5 additions & 6 deletions lib/webmock/request_profile.rb
@@ -1,10 +1,10 @@
module WebMock module WebMock


class RequestProfile < Struct.new(:method, :uri, :body, :headers) class RequestProfile < Struct.new(:method, :uri, :body, :headers)

def initialize(method, uri, body = nil, headers = nil) def initialize(method, uri, body = nil, headers = nil)
super super
self.uri = WebMock::URL.normalize_uri(self.uri) unless self.uri.is_a?(URI) self.uri = WebMock::URL.normalize_uri(self.uri) unless self.uri.is_a?(Addressable::URI)
self.headers = Utility.normalize_headers(self.headers) self.headers = Utility.normalize_headers(self.headers)
end end


Expand Down Expand Up @@ -35,11 +35,10 @@ def to_s


def match_url(other) def match_url(other)
raise "Can't match regexp request profile" if self.uri.is_a?(Regexp) raise "Can't match regexp request profile" if self.uri.is_a?(Regexp)
@uris_to_check ||= WebMock::URL.variations_of_uri_as_strings(self.uri) if other.uri.is_a?(Addressable::URI)
if other.uri.is_a?(URI) URL.normalize_uri(uri) === URL.normalize_uri(other.uri)
@uris_to_check.include?(other.uri.to_s)
elsif other.uri.is_a?(Regexp) elsif other.uri.is_a?(Regexp)
@uris_to_check.any? { |u| u.match(other.uri) } WebMock::URL.variations_of_uri_as_strings(self.uri).any? { |u| u.match(other.uri) }
else else
false false
end end
Expand Down
64 changes: 38 additions & 26 deletions lib/webmock/url.rb
Expand Up @@ -4,41 +4,53 @@ class URL


def self.normalize_uri(uri) def self.normalize_uri(uri)
return uri if uri.is_a?(Regexp) return uri if uri.is_a?(Regexp)
normalized_uri = uri = 'http://' + uri unless uri.match('^https?://') if uri.is_a?(String)
case uri normalized_uri = Addressable::URI.heuristic_parse(uri)
when URI then uri normalized_uri.query_values = normalized_uri.query_values if normalized_uri.query_values
when String normalized_uri.normalize!
uri = 'http://' + uri unless uri.match('^https?://') normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port && normalized_uri.inferred_port
URI.parse(uri) normalized_uri
end
normalized_uri.query = sort_query_params(normalized_uri.query)
normalized_uri.normalize
end end


def self.variations_of_uri_as_strings(uri_object) def self.variations_of_uri_as_strings(uri_object)
normalized_uri = normalize_uri(uri_object.dup) normalized_uri = normalize_uri(uri_object.dup).freeze
normalized_uri_string = normalized_uri.to_s uris = [ normalized_uri ]


variations = [normalized_uri_string] if normalized_uri.port == Addressable::URI.port_mapping[normalized_uri.scheme]

uris = uris_with_inferred_port_and_without(uris)
# if the port is implied in the original, add a copy with an explicit port
if normalized_uri.default_port == normalized_uri.port
variations << normalized_uri_string.sub(
/#{Regexp.escape(normalized_uri.request_uri)}$/,
":#{normalized_uri.port}#{normalized_uri.request_uri}")
end end

if normalized_uri.scheme == "http"
uris = uris_with_scheme_and_without(uris)
end

if normalized_uri.path == '/' && normalized_uri.query == nil
uris = uris_with_trailing_slash_and_without(uris)
end

uris = uris_encoded_and_unencoded(uris)


variations uris.map {|uri| uri.to_s.gsub(/^\/\//,'') }.uniq
end end


private private


def self.sort_query_params(query) def self.uris_with_inferred_port_and_without(uris)
if query.nil? || query.empty? uris.map { |uri| [ uri, uri.omit(:port).freeze ] }.flatten
nil end
else
query.split('&').sort.join('&') def self.uris_encoded_and_unencoded(uris)
end uris.map do |uri|
[ uri, Addressable::URI.heuristic_parse(Addressable::URI.unencode(uri)).freeze ]
end.flatten
end

def self.uris_with_scheme_and_without(uris)
uris.map { |uri| [ uri, uri.omit(:scheme).freeze ] }.flatten
end

def self.uris_with_trailing_slash_and_without(uris)
uris = uris.map { |uri| [ uri, uri.omit(:path).freeze ] }.flatten
end end


end end
Expand Down
9 changes: 6 additions & 3 deletions spec/net_http_spec.rb
Expand Up @@ -5,10 +5,14 @@
include WebMock include WebMock


def http_request(method, url, options = {}) def http_request(method, url, options = {})
url = URI.parse(url) begin
url = URI.parse(url)
rescue
url = Addressable::URI.heuristic_parse(url)
end
response = nil response = nil
clazz = Net::HTTP.const_get("#{method.to_s.capitalize}") clazz = Net::HTTP.const_get("#{method.to_s.capitalize}")
req = clazz.new(url.path, options[:headers]) req = clazz.new("#{url.path}#{url.query ? '?' : ''}#{url.query}", options[:headers])
req.basic_auth url.user, url.password if url.user req.basic_auth url.user, url.password if url.user
http = Net::HTTP.new(url.host, url.port) http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true if url.scheme == "https" http.use_ssl = true if url.scheme == "https"
Expand All @@ -21,7 +25,6 @@ def http_request(method, url, options = {})
:status => response.code }) :status => response.code })
end end



describe "Webmock with Net:HTTP" do describe "Webmock with Net:HTTP" do


it_should_behave_like "WebMock" it_should_behave_like "WebMock"
Expand Down
47 changes: 44 additions & 3 deletions spec/request_profile_spec.rb
Expand Up @@ -14,7 +14,7 @@


it "should have assigned uri without normalization if uri is URI" do it "should have assigned uri without normalization if uri is URI" do
URL.should_not_receive(:normalize_uri) URL.should_not_receive(:normalize_uri)
uri = URI.parse("www.google.com") uri = Addressable::URI.parse("www.google.com")
profile = RequestProfile.new(:get, uri) profile = RequestProfile.new(:get, uri)
profile.uri.should == uri profile.uri.should == uri
end end
Expand All @@ -30,9 +30,9 @@


end end


it "should report string" do it "should report string describing itself" do
RequestProfile.new(:get, "www.google.com", "abc", {'A' => 'a', 'B' => 'b'}).to_s.should == RequestProfile.new(:get, "www.google.com", "abc", {'A' => 'a', 'B' => 'b'}).to_s.should ==
"GET http://www.google.com/ with body 'abc' with headers {'A'=>'a', 'B'=>'b'}" "GET http://www.google.com:80/ with body 'abc' with headers {'A'=>'a', 'B'=>'b'}"
end end




Expand Down Expand Up @@ -75,6 +75,21 @@
RequestProfile.new(:get, "www.google.com"). RequestProfile.new(:get, "www.google.com").
should match(RequestProfile.new(:get, "www.google.com")) should match(RequestProfile.new(:get, "www.google.com"))
end end

it "should match if uri matches other escaped using uri" do
RequestProfile.new(:get, "www.google.com/big image.jpg").
should match(RequestProfile.new(:get, "www.google.com/big%20image.jpg"))
end

it "should match if unescaped uri matches other uri" do
RequestProfile.new(:get, "www.google.com/big%20image.jpg").
should match(RequestProfile.new(:get, "www.google.com/big image.jpg"))
end

it "should match if unescaped uri matches other regexp uri" do
RequestProfile.new(:get, "www.google.com/big%20image.jpg").
should match(RequestProfile.new(:get, /.*big image.jpg.*/))
end


it "should match if uri matches other regex uri" do it "should match if uri matches other regex uri" do
RequestProfile.new(:get, "www.google.com"). RequestProfile.new(:get, "www.google.com").
Expand All @@ -95,6 +110,32 @@
RequestProfile.new(:get, "www.google.com?a=1&b=2"). RequestProfile.new(:get, "www.google.com?a=1&b=2").
should match(RequestProfile.new(:get, "www.google.com?b=2&a=1")) should match(RequestProfile.new(:get, "www.google.com?b=2&a=1"))
end end

describe "when parameters are escaped" do

it "should match if uri with non escaped parameters is the same as other uri with escaped parameters" do
RequestProfile.new(:get, "www.google.com/?a=a b").
should match(RequestProfile.new(:get, "www.google.com/?a=a%20b"))
end

it "should match if uri with escaped parameters is the same as other uri with non escaped parameters" do
RequestProfile.new(:get, "www.google.com/?a=a%20b").
should match(RequestProfile.new(:get, "www.google.com/?a=a b"))
end

it "should match if other regexp is for non escaped parameters but uri has escaped parameters" do
RequestProfile.new(:get, "www.google.com/?a=a%20b").
should match(RequestProfile.new(:get, /.*a=a b.*/))
end

it "should match if other regexp is for escaped parameters but uri has non escaped parameters" do
RequestProfile.new(:get, "www.google.com/?a=a b").
should match(RequestProfile.new(:get, /.*a=a%20b.*/))
end

end




it "should match for same bodies" do it "should match for same bodies" do
RequestProfile.new(:get, "www.google.com", "abc"). RequestProfile.new(:get, "www.google.com", "abc").
Expand Down
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -3,6 +3,7 @@
require 'webmock' require 'webmock'
require 'spec' require 'spec'
require 'spec/autorun' require 'spec/autorun'
require 'addressable/uri'


include WebMock include WebMock


Expand Down Expand Up @@ -56,3 +57,4 @@ def setup_expectations_for_real_google_request(options = {})
:response_body => "<title>Google fake response</title>" } :response_body => "<title>Google fake response</title>" }
setup_expectations_for_real_request(defaults.merge(options)) setup_expectations_for_real_request(defaults.merge(options))
end end

0 comments on commit fba5e3e

Please sign in to comment.