247 lines
7.3 KiB
Ruby
247 lines
7.3 KiB
Ruby
|
unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__) + "/.."))
|
||
|
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/.."))
|
||
|
end
|
||
|
|
||
|
require "uri"
|
||
|
require "rack"
|
||
|
require "rack/mock_session"
|
||
|
require "rack/test/cookie_jar"
|
||
|
require "rack/test/mock_digest_request"
|
||
|
require "rack/test/utils"
|
||
|
require "rack/test/methods"
|
||
|
require "rack/test/uploaded_file"
|
||
|
|
||
|
module Rack
|
||
|
module Test
|
||
|
|
||
|
VERSION = "0.4.0"
|
||
|
|
||
|
DEFAULT_HOST = "example.org"
|
||
|
MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||
|
|
||
|
# The common base class for exceptions raised by Rack::Test
|
||
|
class Error < StandardError; end
|
||
|
|
||
|
class Session
|
||
|
extend Forwardable
|
||
|
include Rack::Test::Utils
|
||
|
|
||
|
def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request
|
||
|
|
||
|
# Initialize a new session for the given Rack app
|
||
|
def initialize(mock_session)
|
||
|
@headers = {}
|
||
|
|
||
|
if mock_session.is_a?(MockSession)
|
||
|
@rack_mock_session = mock_session
|
||
|
else
|
||
|
@rack_mock_session = MockSession.new(mock_session)
|
||
|
end
|
||
|
|
||
|
@default_host = @rack_mock_session.default_host
|
||
|
end
|
||
|
|
||
|
# Issue a GET request for the given URI with the given params and Rack
|
||
|
# environment. Stores the issues request object in #last_request and
|
||
|
# the app's response in #last_response. Yield #last_response to a block
|
||
|
# if given.
|
||
|
#
|
||
|
# Example:
|
||
|
# get "/"
|
||
|
def get(uri, params = {}, env = {}, &block)
|
||
|
env = env_for(uri, env.merge(:method => "GET", :params => params))
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Issue a POST request for the given URI. See #get
|
||
|
#
|
||
|
# Example:
|
||
|
# post "/signup", "name" => "Bryan"
|
||
|
def post(uri, params = {}, env = {}, &block)
|
||
|
env = env_for(uri, env.merge(:method => "POST", :params => params))
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Issue a PUT request for the given URI. See #get
|
||
|
#
|
||
|
# Example:
|
||
|
# put "/"
|
||
|
def put(uri, params = {}, env = {}, &block)
|
||
|
env = env_for(uri, env.merge(:method => "PUT", :params => params))
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Issue a DELETE request for the given URI. See #get
|
||
|
#
|
||
|
# Example:
|
||
|
# delete "/"
|
||
|
def delete(uri, params = {}, env = {}, &block)
|
||
|
env = env_for(uri, env.merge(:method => "DELETE", :params => params))
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Issue a HEAD request for the given URI. See #get
|
||
|
#
|
||
|
# Example:
|
||
|
# head "/"
|
||
|
def head(uri, params = {}, env = {}, &block)
|
||
|
env = env_for(uri, env.merge(:method => "HEAD", :params => params))
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Issue a request to the Rack app for the given URI and optional Rack
|
||
|
# environment. Stores the issues request object in #last_request and
|
||
|
# the app's response in #last_response. Yield #last_response to a block
|
||
|
# if given.
|
||
|
#
|
||
|
# Example:
|
||
|
# request "/"
|
||
|
def request(uri, env = {}, &block)
|
||
|
env = env_for(uri, env)
|
||
|
process_request(uri, env, &block)
|
||
|
end
|
||
|
|
||
|
# Set a header to be included on all subsequent requests through the
|
||
|
# session. Use a value of nil to remove a previously configured header.
|
||
|
#
|
||
|
# Example:
|
||
|
# header "User-Agent", "Firefox"
|
||
|
def header(name, value)
|
||
|
if value.nil?
|
||
|
@headers.delete(name)
|
||
|
else
|
||
|
@headers[name] = value
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Set the username and password for HTTP Basic authorization, to be
|
||
|
# included in subsequent requests in the HTTP_AUTHORIZATION header.
|
||
|
#
|
||
|
# Example:
|
||
|
# basic_authorize "bryan", "secret"
|
||
|
def basic_authorize(username, password)
|
||
|
encoded_login = ["#{username}:#{password}"].pack("m*")
|
||
|
header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
|
||
|
end
|
||
|
|
||
|
alias_method :authorize, :basic_authorize
|
||
|
|
||
|
def digest_authorize(username, password)
|
||
|
@digest_username = username
|
||
|
@digest_password = password
|
||
|
end
|
||
|
|
||
|
# Rack::Test will not follow any redirects automatically. This method
|
||
|
# will follow the redirect returned in the last response. If the last
|
||
|
# response was not a redirect, an error will be raised.
|
||
|
def follow_redirect!
|
||
|
unless last_response.redirect?
|
||
|
raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
|
||
|
end
|
||
|
|
||
|
get(last_response["Location"])
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def env_for(path, env)
|
||
|
uri = URI.parse(path)
|
||
|
uri.host ||= @default_host
|
||
|
|
||
|
env = default_env.merge(env)
|
||
|
|
||
|
env.update("HTTPS" => "on") if URI::HTTPS === uri
|
||
|
env["X-Requested-With"] = "XMLHttpRequest" if env[:xhr]
|
||
|
|
||
|
if (env[:method] == "POST" || env["REQUEST_METHOD"] == "POST" ||
|
||
|
env[:method] == "PUT" || env["REQUEST_METHOD"] == "PUT") && !env.has_key?(:input)
|
||
|
env["CONTENT_TYPE"] ||= "application/x-www-form-urlencoded"
|
||
|
|
||
|
multipart = (Hash === env[:params]) &&
|
||
|
env[:params].any? { |_, v| UploadedFile === v }
|
||
|
|
||
|
if multipart
|
||
|
env[:input] = multipart_body(env.delete(:params))
|
||
|
env["CONTENT_LENGTH"] ||= env[:input].length.to_s
|
||
|
env["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
|
||
|
else
|
||
|
env[:input] = params_to_string(env.delete(:params))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
params = env[:params] || {}
|
||
|
params.update(parse_query(uri.query))
|
||
|
|
||
|
uri.query = requestify(params)
|
||
|
|
||
|
if env.has_key?(:cookie)
|
||
|
set_cookie(env.delete(:cookie), uri)
|
||
|
end
|
||
|
|
||
|
Rack::MockRequest.env_for(uri.to_s, env)
|
||
|
end
|
||
|
|
||
|
def process_request(uri, env)
|
||
|
uri = URI.parse(uri)
|
||
|
uri.host ||= @default_host
|
||
|
|
||
|
@rack_mock_session.request(uri, env)
|
||
|
|
||
|
if retry_with_digest_auth?(env)
|
||
|
auth_env = env.merge({
|
||
|
"HTTP_AUTHORIZATION" => digest_auth_header,
|
||
|
"rack-test.digest_auth_retry" => true
|
||
|
})
|
||
|
auth_env.delete('rack.request')
|
||
|
process_request(uri.path, auth_env)
|
||
|
else
|
||
|
yield last_response if block_given?
|
||
|
|
||
|
last_response
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def digest_auth_header
|
||
|
challenge = last_response["WWW-Authenticate"].split(" ", 2).last
|
||
|
params = Rack::Auth::Digest::Params.parse(challenge)
|
||
|
|
||
|
params.merge!({
|
||
|
"username" => @digest_username,
|
||
|
"nc" => "00000001",
|
||
|
"cnonce" => "nonsensenonce",
|
||
|
"uri" => last_request.path_info,
|
||
|
"method" => last_request.env["REQUEST_METHOD"],
|
||
|
})
|
||
|
|
||
|
params["response"] = MockDigestRequest.new(params).response(@digest_password)
|
||
|
|
||
|
"Digest #{params}"
|
||
|
end
|
||
|
|
||
|
def retry_with_digest_auth?(env)
|
||
|
last_response.status == 401 &&
|
||
|
digest_auth_configured? &&
|
||
|
!env["rack-test.digest_auth_retry"]
|
||
|
end
|
||
|
|
||
|
def digest_auth_configured?
|
||
|
@digest_username
|
||
|
end
|
||
|
|
||
|
def default_env
|
||
|
{ "rack.test" => true, "REMOTE_ADDR" => "127.0.0.1" }.merge(@headers)
|
||
|
end
|
||
|
|
||
|
def params_to_string(params)
|
||
|
case params
|
||
|
when Hash then requestify(params)
|
||
|
when nil then ""
|
||
|
else params
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
end
|