Skip to content
Browse files

Merge branch 'master' into vcr-171

  • Loading branch information...
2 parents a42d463 + f40be82 commit dc0e226dbf4f33b0e2bc40548c141eee05af83b4 @bblimke committed
View
5 .travis.yml
@@ -7,5 +7,10 @@ rvm:
- jruby-18mode
- rbx-18mode
- rbx-19mode
+matrix:
+ allow_failures:
+ - rvm: rbx-18mode
+ - rvm: rbx-19mode
+ - rvm: jruby-19mode
script: "bundle exec rake && rake em_http_request_0_x_spec"
View
45 CHANGELOG.md
@@ -1,5 +1,50 @@
# Changelog
+## 1.8.9
+
+* Fixed problem with caching nil responses when the same HTTPClient instance is used.
+
+ Thanks to [Myron Marston](https://github.com/myronmarston)
+
+* Added support for Addressable >= 2.3.0. Addressable 2.3.0 removed support for multiple query value notations and broke backwards compatibility.
+
+ https://github.com/sporkmonger/addressable/commit/f51e290b5f68a98293327a7da84eb9e2d5f21c62
+ https://github.com/sporkmonger/addressable/issues/77
+
+
+## 1.8.8
+
+* Fixed Net::HTTP adapter so that it returns `nil` for an empty body response.
+
+ Thanks to [Myron Marston](https://github.com/myronmarston)
+
+* Gemspec defines compatibility with Addressable ~> 2.2.8, not >= 2.3.0
+
+* Specs compatibility with Typhoeus 0.4.0
+
+ Thanks to [Hans Hasselberg](https://github.com/i0rek)
+
+* Handling content types that specify a charset
+
+ Thanks to [Kevin Glowacz](https://github.com/kjg)
+
+* Fixed em-http-request adapter to correctly fetch authorization header from a request
+
+ Thanks to [Julien Boyer](https://github.com/chatgris)
+
+* Fixing travis-ci image to report master's status
+
+ Thanks to [Ryan Schlesinger](https://github.com/ryansch)
+
+* Fixed problem with em-http-request callback triggering if there were other EM::Deferred callbacks registered
+
+ Thanks to [Jon Leighton](https://github.com/jonleighton)
+
+* Fixed problem with em-http-request appending the query to the URI a second time, and
+the parameters are repeated.
+
+ Thanks to [Jon Leighton](https://github.com/jonleighton)
+
## 1.8.7
* Compatibility with RSpec >= 2.10
View
2 Gemfile
@@ -17,5 +17,5 @@ group :test do
end
platforms :jruby do
- gem 'jruby-openssl', '~> 0.7.4.0'
+ gem 'jruby-openssl', '~> 0.7.7'
end
View
8 README.md
@@ -1,4 +1,4 @@
-WebMock [![Build Status](https://secure.travis-ci.org/bblimke/webmock.png)](http://travis-ci.org/bblimke/webmock) [![Dependency Status](https://gemnasium.com/bblimke/webmock.png)](http://gemnasium.com/bblimke/webmock)
+WebMock [![Build Status](https://secure.travis-ci.org/bblimke/webmock.png?branch=master)](http://travis-ci.org/bblimke/webmock) [![Dependency Status](https://gemnasium.com/bblimke/webmock.png)](http://gemnasium.com/bblimke/webmock)
=======
Library for stubbing and setting expectations on HTTP requests in Ruby.
@@ -707,6 +707,12 @@ People who submitted patches and new features or suggested improvements. Many th
* Eric Oestrich
* erwanlr
* Ben Bleything
+* Jon Leighton
+* Ryan Schlesinger
+* Julien Boyer
+* Kevin Glowacz
+* Hans Hasselberg
+* Andrew France
For a full list of contributors you can visit the
[contributors](https://github.com/bblimke/webmock/contributors) page.
View
1 lib/webmock.rb
@@ -8,6 +8,7 @@
require 'webmock/errors'
+require 'webmock/util/query_mapper'
require 'webmock/util/uri'
require 'webmock/util/headers'
require 'webmock/util/hash_counter'
View
94 lib/webmock/http_lib_adapters/curb_adapter.rb
@@ -170,117 +170,83 @@ def build_webmock_response
### Mocks of Curl::Easy methods below here.
###
- def http_with_webmock(method)
+ def http(method)
@webmock_method = method
- http_without_webmock(method)
+ super
end
- alias_method :http_without_webmock, :http
- alias_method :http, :http_with_webmock
%w[ get head delete ].each do |verb|
- define_method "http_#{verb}_with_webmock" do
+ define_method "http_#{verb}" do
@webmock_method = verb
- send( "http_#{verb}_without_webmock" )
+ super()
end
-
- alias_method "http_#{verb}_without_webmock", "http_#{verb}"
- alias_method "http_#{verb}", "http_#{verb}_with_webmock"
end
- def http_put_with_webmock data = nil
+ def http_put data = nil
@webmock_method = :put
@put_data = data if data
- http_put_without_webmock(data)
+ super
end
- alias_method :http_put_without_webmock, :http_put
- alias_method :http_put, :http_put_with_webmock
- def http_post_with_webmock *data
+ def http_post *data
@webmock_method = :post
@post_body = data.join('&') if data && !data.empty?
- http_post_without_webmock(*data)
+ super
end
- alias_method :http_post_without_webmock, :http_post
- alias_method :http_post, :http_post_with_webmock
-
- def perform_with_webmock
+ def perform
@webmock_method ||= :get
- curb_or_webmock do
- perform_without_webmock
- end
+ curb_or_webmock { super }
end
- alias :perform_without_webmock :perform
- alias :perform :perform_with_webmock
- def put_data_with_webmock= data
+ def put_data= data
@webmock_method = :put
@put_data = data
- self.put_data_without_webmock = data
+ super
end
- alias_method :put_data_without_webmock=, :put_data=
- alias_method :put_data=, :put_data_with_webmock=
- def post_body_with_webmock= data
+ def post_body= data
@webmock_method = :post
- self.post_body_without_webmock = data
+ super
end
- alias_method :post_body_without_webmock=, :post_body=
- alias_method :post_body=, :post_body_with_webmock=
- def delete_with_webmock= value
+ def delete= value
@webmock_method = :delete if value
- self.delete_without_webmock = value
+ super
end
- alias_method :delete_without_webmock=, :delete=
- alias_method :delete=, :delete_with_webmock=
- def head_with_webmock= value
+ def head= value
@webmock_method = :head if value
- self.head_without_webmock = value
+ super
end
- alias_method :head_without_webmock=, :head=
- alias_method :head=, :head_with_webmock=
- def body_str_with_webmock
- @body_str || body_str_without_webmock
+ def body_str
+ @body_str || super
end
- alias :body_str_without_webmock :body_str
- alias :body_str :body_str_with_webmock
- def response_code_with_webmock
- @response_code || response_code_without_webmock
+ def response_code
+ @response_code || super
end
- alias :response_code_without_webmock :response_code
- alias :response_code :response_code_with_webmock
- def header_str_with_webmock
- @header_str || header_str_without_webmock
+ def header_str
+ @header_str || super
end
- alias :header_str_without_webmock :header_str
- alias :header_str :header_str_with_webmock
- def last_effective_url_with_webmock
- @last_effective_url || last_effective_url_without_webmock
+ def last_effective_url
+ @last_effective_url || super
end
- alias :last_effective_url_without_webmock :last_effective_url
- alias :last_effective_url :last_effective_url_with_webmock
- def content_type_with_webmock
- @content_type || content_type_without_webmock
+ def content_type
+ @content_type || super
end
- alias :content_type_without_webmock :content_type
- alias :content_type :content_type_with_webmock
%w[ success failure header body complete progress ].each do |callback|
class_eval <<-METHOD, __FILE__, __LINE__
- def on_#{callback}_with_webmock &block
+ def on_#{callback} &block
@on_#{callback} = block
- on_#{callback}_without_webmock &block
+ super
end
METHOD
- alias_method "on_#{callback}_without_webmock", "on_#{callback}"
- alias_method "on_#{callback}", "on_#{callback}_with_webmock"
end
end
end
View
8 lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb
@@ -47,7 +47,7 @@ def close_connection
end
end
- def send_request_with_webmock(&block)
+ def send_request(&block)
request_signature = build_request_signature
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -61,7 +61,7 @@ def send_request_with_webmock(&block)
webmock_response.should_timeout ? "WebMock timeout error" : nil)
client
elsif WebMock.net_connect_allowed?(request_signature.uri)
- http = send_request_without_webmock(&block)
+ http = super
http.callback {
if WebMock::CallbackRegistry.any_callbacks?
webmock_response = build_webmock_response(http)
@@ -76,10 +76,6 @@ def send_request_with_webmock(&block)
end
end
- alias_method :send_request_without_webmock, :send_request
- alias_method :send_request, :send_request_with_webmock
-
-
private
def build_webmock_response(http)
View
35 lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb
@@ -48,7 +48,7 @@ def #{type}(options = {}, &blk)
end
class WebMockHttpConnection < HttpConnection
- def webmock_activate_connection(client)
+ def activate_connection(client)
request_signature = client.request_signature
if client.stubbed_webmock_response
@@ -65,13 +65,11 @@ def webmock_activate_connection(client)
finalize_request(client)
@conn.set_deferred_status :succeeded
elsif WebMock.net_connect_allowed?(request_signature.uri)
- real_activate_connection(client)
+ super
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
end
- alias_method :real_activate_connection, :activate_connection
- alias_method :activate_connection, :webmock_activate_connection
end
class WebMockHttpClient < EventMachine::HttpClient
@@ -92,7 +90,7 @@ def setup(response, uri, error = nil)
end
end
- def send_request_with_webmock(head, body)
+ def send_request(head, body)
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
if stubbed_webmock_response
@@ -104,24 +102,23 @@ def send_request_with_webmock(head, body)
}
self
elsif WebMock.net_connect_allowed?(request_signature.uri)
- send_request_without_webmock(head, body)
- callback {
- if WebMock::CallbackRegistry.any_callbacks?
- webmock_response = build_webmock_response
- WebMock::CallbackRegistry.invoke_callbacks(
- {:lib => :em_http_request, :real_request => true},
- request_signature,
- webmock_response)
- end
- }
- self
+ super
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
end
- alias_method :send_request_without_webmock, :send_request
- alias_method :send_request, :send_request_with_webmock
+ def set_deferred_status(status, *args)
+ if status == :succeeded && !stubbed_webmock_response && WebMock::CallbackRegistry.any_callbacks?
+ webmock_response = build_webmock_response
+ WebMock::CallbackRegistry.invoke_callbacks(
+ {:lib => :em_http_request, :real_request => true},
+ request_signature,
+ webmock_response)
+ end
+
+ super
+ end
def request_signature
@request_signature ||= build_request_signature
@@ -154,7 +151,7 @@ def build_request_signature
method = @req.method
uri = @req.uri.clone
- auth = @req.proxy[:authorization] if @req.proxy
+ auth = @req.headers[:'proxy-authorization']
query = @req.query
if auth
View
2 lib/webmock/http_lib_adapters/excon_adapter.rb
@@ -42,7 +42,7 @@ def self.build_request(params)
params = params.dup
method = (params.delete(:method) || :get).to_s.downcase.to_sym
params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash)
- uri = Addressable::URI.new(params).to_s
+ uri = Addressable::URI.new(params).to_s
WebMock::RequestSignature.new method, uri, :body => params[:body], :headers => params[:headers]
end
View
30 lib/webmock/http_lib_adapters/httpclient_adapter.rb
@@ -28,16 +28,18 @@ def self.disable!
class WebMockHTTPClient < HTTPClient
+ alias_method :do_get_block_without_webmock, :do_get_block
+ alias_method :do_get_stream_without_webmock, :do_get_stream
- def do_get_block_with_webmock(req, proxy, conn, &block)
- do_get_with_webmock(req, proxy, conn, false, &block)
+ def do_get_block(req, proxy, conn, &block)
+ do_get(req, proxy, conn, false, &block)
end
- def do_get_stream_with_webmock(req, proxy, conn, &block)
- do_get_with_webmock(req, proxy, conn, true, &block)
+ def do_get_stream(req, proxy, conn, &block)
+ do_get(req, proxy, conn, true, &block)
end
- def do_get_with_webmock(req, proxy, conn, stream = false, &block)
+ def do_get(req, proxy, conn, stream = false, &block)
request_signature = build_request_signature(req, :reuse_existing)
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -53,6 +55,9 @@ def do_get_with_webmock(req, proxy, conn, stream = false, &block)
{:lib => :httpclient}, request_signature, webmock_response)
res
elsif WebMock.net_connect_allowed?(request_signature.uri)
+ # in case there is a nil entry in the hash...
+ webmock_responses.delete(request_signature)
+
res = if stream
do_get_stream_without_webmock(req, proxy, conn, &block)
else
@@ -72,27 +77,18 @@ def do_get_with_webmock(req, proxy, conn, stream = false, &block)
end
end
- def do_request_async_with_webmock(method, uri, query, body, extheader)
+ def do_request_async(method, uri, query, body, extheader)
req = create_request(method, uri, query, body, extheader)
request_signature = build_request_signature(req)
webmock_request_signatures << request_signature
if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri)
- do_request_async_without_webmock(method, uri, query, body, extheader)
+ super
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
end
- alias_method :do_get_block_without_webmock, :do_get_block
- alias_method :do_get_block, :do_get_block_with_webmock
-
- alias_method :do_get_stream_without_webmock, :do_get_stream
- alias_method :do_get_stream, :do_get_stream_with_webmock
-
- alias_method :do_request_async_without_webmock, :do_request_async
- alias_method :do_request_async, :do_request_async_with_webmock
-
def build_httpclient_response(webmock_response, stream = false, &block)
body = stream ? StringIO.new(webmock_response.body) : webmock_response.body
response = HTTP::Message.new_response(body)
@@ -136,7 +132,7 @@ def build_webmock_response(httpclient_response)
def build_request_signature(req, reuse_existing = false)
uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s)
- uri.query_values = req.header.request_query if req.header.request_query
+ uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query) if req.header.request_query
uri.port = req.header.request_uri.port
uri = uri.omit(:userinfo)
View
29 lib/webmock/http_lib_adapters/net_http.rb
@@ -32,11 +32,9 @@ def self.disable!
@webMockNetHTTP = Class.new(Net::HTTP) do
class << self
- def socket_type_with_webmock
+ def socket_type
StubSocket
end
- alias_method :socket_type_without_webmock, :socket_type
- alias_method :socket_type, :socket_type_with_webmock
if Module.method(:const_defined?).arity == 1
def const_defined?(name)
@@ -63,7 +61,7 @@ def constants(inherit=true)
end
end
- def request_with_webmock(request, body = nil, &block)
+ def request(request, body = nil, &block)
request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body)
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -88,19 +86,17 @@ def request_with_webmock(request, body = nil, &block)
response = if (started? && !WebMock::Config.instance.net_http_connect_on_start) || !started?
@started = false #otherwise start_with_connect wouldn't execute and connect
start_with_connect {
- response = request_without_webmock(request, nil)
+ response = super(request, nil, &nil)
after_request.call(response)
}
else
- response = request_without_webmock(request, nil)
+ response = super(request, nil, &nil)
after_request.call(response)
end
else
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
end
- alias_method :request_without_webmock, :request
- alias_method :request, :request_with_webmock
def start_without_connect
raise IOError, 'HTTP session already opened' if @started
@@ -116,19 +112,22 @@ def start_without_connect
self
end
- def start_with_conditional_connect(&block)
+ alias_method :start_with_connect, :start
+
+ def start(&block)
if WebMock::Config.instance.net_http_connect_on_start
- start_with_connect(&block)
+ super(&block)
else
start_without_connect(&block)
end
end
- alias_method :start_with_connect, :start
- alias_method :start, :start_with_conditional_connect
def build_net_http_response(webmock_response, &block)
response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1])
- response.instance_variable_set(:@body, webmock_response.body)
+ body = webmock_response.body
+ body = nil if body.to_s == ''
+
+ response.instance_variable_set(:@body, body)
webmock_response.headers.to_a.each do |name, values|
values = [values] unless values.is_a?(Array)
values.each do |value|
@@ -201,7 +200,7 @@ def readuntil(*args)
module Net #:nodoc: all
class WebMockNetBufferedIO < BufferedIO
- def initialize_with_webmock(io, debug_output = nil)
+ def initialize(io, debug_output = nil)
@read_timeout = 60
@rbuf = ''
@debug_output = debug_output
@@ -214,8 +213,6 @@ def initialize_with_webmock(io, debug_output = nil)
end
raise "Unable to create local socket" unless @io
end
- alias_method :initialize_without_webmock, :initialize
- alias_method :initialize, :initialize_with_webmock
end
end
View
7 lib/webmock/http_lib_adapters/patron_adapter.rb
@@ -13,7 +13,7 @@ class PatronAdapter < ::WebMock::HttpLibAdapter
OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession)
class WebMockPatronSession < ::Patron::Session
- def handle_request_with_webmock(req)
+ def handle_request(req)
request_signature =
WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req)
@@ -28,7 +28,7 @@ def handle_request_with_webmock(req)
{:lib => :patron}, request_signature, webmock_response)
res
elsif WebMock.net_connect_allowed?(request_signature.uri)
- res = handle_request_without_webmock(req)
+ res = super
if WebMock::CallbackRegistry.any_callbacks?
webmock_response = WebMock::HttpLibAdapters::PatronAdapter.
build_webmock_response(res)
@@ -41,9 +41,6 @@ def handle_request_with_webmock(req)
raise WebMock::NetConnectNotAllowedError.new(request_signature)
end
end
-
- alias_method :handle_request_without_webmock, :handle_request
- alias_method :handle_request, :handle_request_with_webmock
end
def self.enable!
View
13 lib/webmock/request_pattern.rb
@@ -27,6 +27,7 @@ def with(options = {}, &block)
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 || "")) &&
@@ -94,7 +95,7 @@ def add_query_params(query_params)
elsif rSpecHashIncludingMatcher?(query_params)
WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params)
else
- Addressable::URI.parse('?' + query_params).query_values
+ WebMock::Util::QueryMapper.query_to_values(query_params)
end
end
@@ -108,7 +109,7 @@ def to_s
class URIRegexpPattern < URIPattern
def matches?(uri)
WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } &&
- (@query_params.nil? || @query_params == uri.query_values)
+ (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query))
end
def to_s
@@ -122,7 +123,8 @@ class URIStringPattern < URIPattern
def matches?(uri)
if @pattern.is_a?(Addressable::URI)
if @query_params
- uri.omit(:query) === @pattern && (@query_params.nil? || @query_params == uri.query_values)
+ uri.omit(:query) === @pattern &&
+ (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query))
else
uri === @pattern
end
@@ -134,7 +136,8 @@ def matches?(uri)
def add_query_params(query_params)
super
if @query_params.is_a?(Hash) || @query_params.is_a?(String)
- @pattern.query_values = (@pattern.query_values || {}).merge(@query_params)
+ query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query) || {}).merge(@query_params)
+ @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash)
@query_params = nil
end
end
@@ -198,7 +201,7 @@ def body_as_hash(body, content_type)
when :xml then
Crack::XML.parse(body)
else
- Addressable::URI.parse('?' + body).query_values
+ WebMock::Util::QueryMapper.query_to_values(body)
end
end
View
2 lib/webmock/request_stub.rb
@@ -81,7 +81,7 @@ def self.from_request_signature(signature)
if signature.body.to_s != ''
body = if signature.url_encoded?
- Addressable::URI.parse('?' + signature.body).query_values
+ WebMock::Util::QueryMapper.query_to_values(signature.body)
else
signature.body
end
View
188 lib/webmock/util/query_mapper.rb
@@ -0,0 +1,188 @@
+module WebMock::Util
+ class QueryMapper
+ #This class is based on Addressable::URI pre 2.3.0
+
+ ##
+ # Converts the query component to a Hash value.
+ #
+ # @option [Symbol] notation
+ # May be one of <code>:flat</code>, <code>:dot</code>, or
+ # <code>:subscript</code>. The <code>:dot</code> notation is not
+ # supported for assignment. Default value is <code>:subscript</code>.
+ #
+ # @return [Hash, Array] The query string parsed as a Hash or Array object.
+ #
+ # @example
+ # WebMock::Util::QueryMapper.query_to_values("?one=1&two=2&three=3")
+ # #=> {"one" => "1", "two" => "2", "three" => "3"}
+ # WebMock::Util::QueryMapper("?one[two][three]=four").query_values
+ # #=> {"one" => {"two" => {"three" => "four"}}}
+ # WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
+ # :notation => :dot
+ # )
+ # #=> {"one" => {"two" => {"three" => "four"}}}
+ # WebMock::Util::QueryMapper.query_to_values("?one[two][three]=four",
+ # :notation => :flat
+ # )
+ # #=> {"one[two][three]" => "four"}
+ # WebMock::Util::QueryMapper.query_to_values("?one.two.three=four",
+ # :notation => :flat
+ # )
+ # #=> {"one.two.three" => "four"}
+ # WebMock::Util::QueryMapper(
+ # "?one[two][three][]=four&one[two][three][]=five"
+ # )
+ # #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
+ # WebMock::Util::QueryMapper.query_to_values(
+ # "?one=two&one=three").query_values(:notation => :flat_array)
+ # #=> [['one', 'two'], ['one', 'three']]
+ def self.query_to_values(query, options={})
+ defaults = {:notation => :subscript}
+ options = defaults.merge(options)
+ if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
+ raise ArgumentError,
+ "Invalid notation. Must be one of: " +
+ "[:flat, :dot, :subscript, :flat_array]."
+ end
+ dehash = lambda do |hash|
+ hash.each do |(key, value)|
+ if value.kind_of?(Hash)
+ hash[key] = dehash.call(value)
+ end
+ end
+ if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
+ hash.sort.inject([]) do |accu, (_, value)|
+ accu << value; accu
+ end
+ else
+ hash
+ end
+ end
+ return nil if query == nil
+ empty_accumulator = :flat_array == options[:notation] ? [] : {}
+ return ((query.split("&").map do |pair|
+ pair.split("=", 2) if pair && !pair.empty?
+ end).compact.inject(empty_accumulator.dup) do |accumulator, (key, value)|
+ value = true if value.nil?
+ key = Addressable::URI.unencode_component(key)
+ if value != true
+ value = Addressable::URI.unencode_component(value.gsub(/\+/, " "))
+ end
+ if options[:notation] == :flat
+ if accumulator[key]
+ raise ArgumentError, "Key was repeated: #{key.inspect}"
+ end
+ accumulator[key] = value
+ elsif options[:notation] == :flat_array
+ accumulator << [key, value]
+ else
+ if options[:notation] == :dot
+ array_value = false
+ subkeys = key.split(".")
+ elsif options[:notation] == :subscript
+ array_value = !!(key =~ /\[\]$/)
+ subkeys = key.split(/[\[\]]+/)
+ end
+ current_hash = accumulator
+ for i in 0...(subkeys.size - 1)
+ subkey = subkeys[i]
+ current_hash[subkey] = {} unless current_hash[subkey]
+ current_hash = current_hash[subkey]
+ end
+ if array_value
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
+ current_hash[subkeys.last] << value
+ else
+ current_hash[subkeys.last] = value
+ end
+ end
+ accumulator
+ end).inject(empty_accumulator.dup) do |accumulator, (key, value)|
+ if options[:notation] == :flat_array
+ accumulator << [key, value]
+ else
+ accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
+ end
+ accumulator
+ end
+ end
+
+ ##
+ # Sets the query component for this URI from a Hash object.
+ # This method produces a query string using the :subscript notation.
+ # An empty Hash will result in a nil query.
+ #
+ # @param [Hash, #to_hash, Array] new_query_values The new query values.
+ def self.values_to_query(new_query_values)
+ if new_query_values == nil
+ return nil
+ end
+
+ if !new_query_values.is_a?(Array)
+ if !new_query_values.respond_to?(:to_hash)
+ raise TypeError,
+ "Can't convert #{new_query_values.class} into Hash."
+ end
+ new_query_values = new_query_values.to_hash
+ new_query_values = new_query_values.map do |key, value|
+ key = key.to_s if key.kind_of?(Symbol)
+ [key, value]
+ end
+ # Useful default for OAuth and caching.
+ # Only to be used for non-Array inputs. Arrays should preserve order.
+ new_query_values.sort!
+ end
+
+ ##
+ # Joins and converts parent and value into a properly encoded and
+ # ordered URL query.
+ #
+ # @private
+ # @param [String] parent an URI encoded component.
+ # @param [Array, Hash, Symbol, #to_str] value
+ #
+ # @return [String] a properly escaped and ordered URL query.
+ to_query = lambda do |parent, value|
+ if value.is_a?(Hash)
+ value = value.map do |key, val|
+ [
+ Addressable::URI.encode_component(key, Addressable::URI::CharacterClasses::UNRESERVED),
+ val
+ ]
+ end
+ value.sort!
+ buffer = ""
+ value.each do |key, val|
+ new_parent = "#{parent}[#{key}]"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ elsif value.is_a?(Array)
+ buffer = ""
+ value.each_with_index do |val, i|
+ new_parent = "#{parent}[#{i}]"
+ buffer << "#{to_query.call(new_parent, val)}&"
+ end
+ return buffer.chop
+ elsif value == true
+ return parent
+ else
+ encoded_value = Addressable::URI.encode_component(
+ value, Addressable::URI::CharacterClasses::UNRESERVED
+ )
+ return "#{parent}=#{encoded_value}"
+ end
+ end
+
+ # new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
+ buffer = ""
+ new_query_values.each do |parent, value|
+ encoded_parent = Addressable::URI.encode_component(
+ parent, Addressable::URI::CharacterClasses::UNRESERVED
+ )
+ buffer << "#{to_query.call(encoded_parent, value)}&"
+ end
+ return buffer.chop
+ end
+ end
+end
View
19 lib/webmock/util/uri.rb
@@ -1,23 +1,22 @@
-module Addressable
- class URI
- module CharacterClasses
- USERINFO = UNRESERVED + SUB_DELIMS + "\\:"
- end
- end
-end
-
module WebMock
module Util
class URI
+ module CharacterClasses
+ USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:"
+ end
+
ADDRESSABLE_URIS = Hash.new do |hash, key|
hash[key] = Addressable::URI.heuristic_parse(key)
end
NORMALIZED_URIS = Hash.new do |hash, uri|
normalized_uri = WebMock::Util::URI.heuristic_parse(uri)
- normalized_uri.query_values = sort_query_values(normalized_uri.query_values) if normalized_uri.query_values
+ if normalized_uri.query_values
+ sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query) || {})
+ normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values)
+ end
normalized_uri = normalized_uri.normalize #normalize! is slower
normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port
hash[uri] = normalized_uri
@@ -63,7 +62,7 @@ def self.strip_default_port_from_uri_string(uri_string)
end
def self.encode_unsafe_chars_in_userinfo(userinfo)
- Addressable::URI.encode_component(userinfo, Addressable::URI::CharacterClasses::USERINFO)
+ Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO)
end
def self.is_uri_localhost?(uri)
View
2 lib/webmock/version.rb
@@ -1,3 +1,3 @@
module WebMock
- VERSION = '1.8.7' unless defined?(::WebMock::VERSION)
+ VERSION = '1.8.9' unless defined?(::WebMock::VERSION)
end
View
42 spec/acceptance/em_http_request/em_http_request_spec.rb
@@ -71,19 +71,21 @@ def request(client, head, body)
end
end
- it "should work with response middleware" do
- stub_request(:get, "www.example.com").to_return(:body => 'foo')
-
- middleware = Class.new do
+ let(:response_middleware) do
+ Class.new do
def response(resp)
resp.response = 'bar'
end
end
+ end
+
+ it "should work with response middleware" do
+ stub_request(:get, "www.example.com").to_return(:body => 'foo')
EM.run do
conn = EventMachine::HttpRequest.new('http://www.example.com/')
- conn.use middleware
+ conn.use response_middleware
http = conn.get
@@ -93,6 +95,36 @@ def response(resp)
end
end
end
+
+ let(:webmock_server_url) { "http://#{WebMockServer.instance.host_with_port}/" }
+
+ shared_examples_for "em-http-request middleware/after_request hook integration" do
+ it 'yields the original raw body to the after_request hook even if a response middleware modifies the body' do
+ yielded_response_body = nil
+ ::WebMock.after_request do |request, response|
+ yielded_response_body = response.body
+ end
+
+ EM::HttpRequest.use response_middleware
+
+ EM.run do
+ http = EventMachine::HttpRequest.new(webmock_server_url).get
+ http.callback { EM.stop }
+ end
+
+ yielded_response_body.should eq("hello world")
+ end
+ end
+
+ context 'making a real request' do
+ before { WebMock.allow_net_connect! }
+ include_examples "em-http-request middleware/after_request hook integration"
+ end
+
+ context 'when the request is stubbed' do
+ before { stub_request(:get, webmock_server_url).to_return(:body => 'hello world') }
+ include_examples "em-http-request middleware/after_request hook integration"
+ end
end
# not pretty, but it works
View
2 spec/acceptance/em_http_request/em_http_request_spec_helper.rb
@@ -49,7 +49,7 @@ def client_timeout_exception_class
end
def connection_refused_exception_class
- ""
+ RuntimeError
end
def http_library
View
27 spec/acceptance/httpclient/httpclient_spec.rb
@@ -73,4 +73,31 @@ def filter_response(request, response)
end
end
+ context 'when a client instance is re-used for another identical request' do
+ let(:client) { HTTPClient.new }
+ let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"}
+
+ before { WebMock.allow_net_connect! }
+
+ it 'invokes the global_stub_request hook for each request' do
+ request_signatures = []
+ WebMock.globally_stub_request do |request_sig|
+ request_signatures << request_sig
+ nil # to let the request be made for real
+ end
+
+ # To make two requests that have the same request signature, the headers must match.
+ # Since the webmock server has a Set-Cookie header, the 2nd request will automatically
+ # include a Cookie header (due to how httpclient works), so we have to set the header
+ # manually on the first request but not on the 2nd request.
+ http_request(:get, webmock_server_url, :client => client,
+ :headers => { "Cookie" => "bar=; foo=" })
+ http_request(:get, webmock_server_url, :client => client)
+
+ request_signatures.should have(2).signatures
+ # Verify the request signatures were identical as needed by this example
+ request_signatures.first.should eq(request_signatures.last)
+ end
+ end
+
end
View
4 spec/acceptance/httpclient/httpclient_spec_helper.rb
@@ -5,11 +5,11 @@ class << self
def http_request(method, uri, options = {}, &block)
uri = Addressable::URI.heuristic_parse(uri)
- c = HTTPClient.new
+ c = options.fetch(:client) { HTTPClient.new }
c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
c.set_basic_auth(nil, uri.user, uri.password) if uri.user
params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}",
- uri.query_values, options[:body], options[:headers] || {}]
+ WebMock::Util::QueryMapper.query_to_values(uri.query), options[:body], options[:headers] || {}]
if HTTPClientSpecHelper.async_mode
connection = c.request_async(*params)
connection.join
View
15 spec/acceptance/shared/complex_cross_concern_behaviors.rb
@@ -17,5 +17,20 @@
played_back_response.headers.keys.should include('Set-Cookie')
played_back_response.should == real_response
end
+
+ let(:no_content_url) { 'http://httpstat.us/204' }
+ [nil, ''].each do |stub_val|
+ it "returns the same value (nil or "") for a request stubbed as #{stub_val.inspect} that a real empty response has" do
+ unless http_library == :curb
+ WebMock.allow_net_connect!
+
+ real_response = http_request(:get, no_content_url)
+ stub_request(:get, no_content_url).to_return(:status => 204, :body => stub_val)
+ stubbed_response = http_request(:get, no_content_url)
+
+ stubbed_response.body.should eq(real_response.body)
+ end
+ end
+ end
end
View
16 spec/acceptance/shared/request_expectations.rb
@@ -190,10 +190,10 @@
it "should fail if request was executed with different body" do
lambda {
- http_request(:get, "http://www.example.com/", :body => "abc")
- a_request(:get, "www.example.com").
+ http_request(:post, "http://www.example.com/", :body => "abc")
+ a_request(:post, "www.example.com").
with(:body => "def").should have_been_made
- }.should fail_with(%r(The request GET http://www.example.com/ with body "def" was expected to execute 1 time but it executed 0 times))
+ }.should fail_with(%r(The request POST http://www.example.com/ with body "def" was expected to execute 1 time but it executed 0 times))
end
describe "when expected request body is declared as a regexp" do
@@ -206,10 +206,10 @@
it "should fail if request was executed with body not matching regexp" do
lambda {
- http_request(:get, "http://www.example.com/", :body => "abc")
- a_request(:get, "www.example.com").
+ http_request(:post, "http://www.example.com/", :body => "abc")
+ a_request(:post, "www.example.com").
with(:body => /^xabc/).should have_been_made
- }.should fail_with(%r(The request GET http://www.example.com/ with body /\^xabc/ was expected to execute 1 time but it executed 0 times))
+ }.should fail_with(%r(The request POST http://www.example.com/ with body /\^xabc/ was expected to execute 1 time but it executed 0 times))
end
end
@@ -389,11 +389,11 @@
it "should satisfy expectation if request was executed with body and headers but they were not specified in expectantion" do
lambda {
- http_request(:get, "http://www.example.com/",
+ http_request(:post, "http://www.example.com/",
:body => "abc",
:headers => SAMPLE_HEADERS
)
- a_request(:get, "www.example.com").should have_been_made
+ a_request(:post, "www.example.com").should have_been_made
}.should_not raise_error
end
View
7 spec/unit/request_pattern_spec.rb
@@ -358,6 +358,13 @@ def match(request_signature)
should_not match(WebMock::RequestSignature.new(:post, "www.example.com",
:headers => {:content_type => 'application/xml'}, :body => "foo bar"))
end
+
+ it "matches when the content type include a charset" do
+ WebMock::RequestPattern.new(:post, 'www.example.com', :body => body_hash).
+ should match(WebMock::RequestSignature.new(:post, "www.example.com", :headers => {:content_type => 'application/xml;charset=UTF-8'},
+ :body => "<opt a=\"1\" b=\"five\">\n <c>\n <d>e</d>\n <d>f</d>\n </c>\n</opt>\n"))
+
+ end
end
end
View
6 spec/unit/util/uri_spec.rb
@@ -170,19 +170,19 @@
it "should successfully handle array parameters" do
uri_string = 'http://www.example.com:80/path?a[]=b&a[]=c'
uri = WebMock::Util::URI.normalize_uri(uri_string)
- uri.query_values.should == {"a"=>["b", "c"]}
+ WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>["b", "c"]}
end
it "should successfully handle hash parameters" do
uri_string = 'http://www.example.com:80/path?a[d]=b&a[e]=c&a[b][c]=1'
uri = WebMock::Util::URI.normalize_uri(uri_string)
- uri.query_values.should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}}
+ WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}}
end
it "should successfully handle nested hash parameters" do
uri_string = 'http://www.example.com:80/path?one[two][three][]=four&one[two][three][]=five'
uri = WebMock::Util::URI.normalize_uri(uri_string)
- uri.query_values.should == {"one"=>{"two"=>{"three" => ["four", "five"]}}}
+ WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"one"=>{"two"=>{"three" => ["four", "five"]}}}
end
end

0 comments on commit dc0e226

Please sign in to comment.
Something went wrong with that request. Please try again.