mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Switch over to rack-test gem
This commit is contained in:
parent
6f40139b53
commit
c63dac81c1
9 changed files with 4 additions and 642 deletions
|
@ -116,6 +116,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 3.0.pre' + PKG_BUILD)
|
||||
s.add_dependency('rack-test', '~> 0.4.1')
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
|
|
@ -33,7 +33,9 @@ end
|
|||
|
||||
require 'rack'
|
||||
|
||||
$:.unshift "#{File.dirname(__FILE__)}/action_dispatch/vendor/rack-test"
|
||||
module Rack
|
||||
autoload :Test, 'rack/test'
|
||||
end
|
||||
|
||||
module ActionDispatch
|
||||
autoload :Request, 'action_dispatch/http/request'
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
module Rack
|
||||
|
||||
class MockSession
|
||||
attr_writer :cookie_jar
|
||||
attr_reader :last_response
|
||||
|
||||
def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
|
||||
@app = app
|
||||
@default_host = default_host
|
||||
end
|
||||
|
||||
def clear_cookies
|
||||
@cookie_jar = Rack::Test::CookieJar.new([], @default_host)
|
||||
end
|
||||
|
||||
def set_cookie(cookie, uri = nil)
|
||||
cookie_jar.merge(cookie, uri)
|
||||
end
|
||||
|
||||
def request(uri, env)
|
||||
env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
|
||||
@last_request = Rack::Request.new(env)
|
||||
status, headers, body = @app.call(@last_request.env)
|
||||
@last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
|
||||
cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
|
||||
|
||||
@last_response
|
||||
end
|
||||
|
||||
# Return the last request issued in the session. Raises an error if no
|
||||
# requests have been sent yet.
|
||||
def last_request
|
||||
raise Rack::Test::Error.new("No request yet. Request a page first.") unless @last_request
|
||||
@last_request
|
||||
end
|
||||
|
||||
# Return the last response received in the session. Raises an error if
|
||||
# no requests have been sent yet.
|
||||
def last_response
|
||||
raise Rack::Test::Error.new("No response yet. Request a page first.") unless @last_response
|
||||
@last_response
|
||||
end
|
||||
|
||||
def cookie_jar
|
||||
@cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,239 +0,0 @@
|
|||
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.3.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(app, default_host = DEFAULT_HOST)
|
||||
@headers = {}
|
||||
@default_host = default_host
|
||||
@rack_mock_session = Rack::MockSession.new(app, 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.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
|
|
@ -1,169 +0,0 @@
|
|||
require "uri"
|
||||
module Rack
|
||||
module Test
|
||||
|
||||
class Cookie
|
||||
include Rack::Utils
|
||||
|
||||
# :api: private
|
||||
attr_reader :name, :value
|
||||
|
||||
# :api: private
|
||||
def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
|
||||
@default_host = default_host
|
||||
uri ||= default_uri
|
||||
|
||||
# separate the name / value pair from the cookie options
|
||||
@name_value_raw, options = raw.split(/[;,] */n, 2)
|
||||
|
||||
@name, @value = parse_query(@name_value_raw, ';').to_a.first
|
||||
@options = parse_query(options, ';')
|
||||
|
||||
@options["domain"] ||= (uri.host || default_host)
|
||||
@options["path"] ||= uri.path.sub(/\/[^\/]*\Z/, "")
|
||||
end
|
||||
|
||||
def replaces?(other)
|
||||
[name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def raw
|
||||
@name_value_raw
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def empty?
|
||||
@value.nil? || @value.empty?
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def domain
|
||||
@options["domain"]
|
||||
end
|
||||
|
||||
def secure?
|
||||
@options.has_key?("secure")
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def path
|
||||
@options["path"].strip || "/"
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def expires
|
||||
Time.parse(@options["expires"]) if @options["expires"]
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def expired?
|
||||
expires && expires < Time.now
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def valid?(uri)
|
||||
uri ||= default_uri
|
||||
|
||||
if uri.host.nil?
|
||||
uri.host = @default_host
|
||||
end
|
||||
|
||||
(!secure? || (secure? && uri.scheme == "https")) &&
|
||||
uri.host =~ Regexp.new("#{Regexp.escape(domain)}$", Regexp::IGNORECASE) &&
|
||||
uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def matches?(uri)
|
||||
! expired? && valid?(uri)
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def <=>(other)
|
||||
# Orders the cookies from least specific to most
|
||||
[name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def default_uri
|
||||
URI.parse("//" + @default_host + "/")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class CookieJar
|
||||
|
||||
# :api: private
|
||||
def initialize(cookies = [], default_host = DEFAULT_HOST)
|
||||
@default_host = default_host
|
||||
@cookies = cookies
|
||||
@cookies.sort!
|
||||
end
|
||||
|
||||
def [](name)
|
||||
cookies = hash_for(nil)
|
||||
# TODO: Should be case insensitive
|
||||
cookies[name] && cookies[name].value
|
||||
end
|
||||
|
||||
def []=(name, value)
|
||||
# TODO: needs proper escaping
|
||||
merge("#{name}=#{value}")
|
||||
end
|
||||
|
||||
def merge(raw_cookies, uri = nil)
|
||||
return unless raw_cookies
|
||||
|
||||
raw_cookies.each_line do |raw_cookie|
|
||||
cookie = Cookie.new(raw_cookie, uri, @default_host)
|
||||
self << cookie if cookie.valid?(uri)
|
||||
end
|
||||
end
|
||||
|
||||
def <<(new_cookie)
|
||||
@cookies.reject! do |existing_cookie|
|
||||
new_cookie.replaces?(existing_cookie)
|
||||
end
|
||||
|
||||
@cookies << new_cookie
|
||||
@cookies.sort!
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def for(uri)
|
||||
hash_for(uri).values.map { |c| c.raw }.join(';')
|
||||
end
|
||||
|
||||
def to_hash
|
||||
cookies = {}
|
||||
|
||||
hash_for(nil).each do |name, cookie|
|
||||
cookies[name] = cookie.value
|
||||
end
|
||||
|
||||
return cookies
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def hash_for(uri = nil)
|
||||
cookies = {}
|
||||
|
||||
# The cookies are sorted by most specific first. So, we loop through
|
||||
# all the cookies in order and add it to a hash by cookie name if
|
||||
# the cookie can be sent to the current URI. It's added to the hash
|
||||
# so that when we are done, the cookies will be unique by name and
|
||||
# we'll have grabbed the most specific to the URI.
|
||||
@cookies.each do |cookie|
|
||||
cookies[cookie.name] = cookie if cookie.matches?(uri)
|
||||
end
|
||||
|
||||
return cookies
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,45 +0,0 @@
|
|||
require "forwardable"
|
||||
|
||||
module Rack
|
||||
module Test
|
||||
module Methods
|
||||
extend Forwardable
|
||||
|
||||
def rack_test_session
|
||||
@_rack_test_session ||= Rack::Test::Session.new(app)
|
||||
end
|
||||
|
||||
def rack_mock_session
|
||||
@_rack_mock_session ||= Rack::MockSession.new(app)
|
||||
end
|
||||
|
||||
METHODS = [
|
||||
:request,
|
||||
|
||||
# HTTP verbs
|
||||
:get,
|
||||
:post,
|
||||
:put,
|
||||
:delete,
|
||||
:head,
|
||||
|
||||
# Redirects
|
||||
:follow_redirect!,
|
||||
|
||||
# Header-related features
|
||||
:header,
|
||||
:set_cookie,
|
||||
:clear_cookies,
|
||||
:authorize,
|
||||
:basic_authorize,
|
||||
:digest_authorize,
|
||||
|
||||
# Expose the last request and response
|
||||
:last_response,
|
||||
:last_request
|
||||
]
|
||||
|
||||
def_delegators :rack_test_session, *METHODS
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
module Rack
|
||||
module Test
|
||||
|
||||
class MockDigestRequest
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def method_missing(sym)
|
||||
if @params.has_key? k = sym.to_s
|
||||
return @params[k]
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def method
|
||||
@params['method']
|
||||
end
|
||||
|
||||
def response(password)
|
||||
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
require "tempfile"
|
||||
|
||||
module Rack
|
||||
module Test
|
||||
|
||||
class UploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
||||
# The content type of the "uploaded" file
|
||||
attr_accessor :content_type
|
||||
|
||||
def initialize(path, content_type = "text/plain", binary = false)
|
||||
raise "#{path} file does not exist" unless ::File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = ::File.basename(path)
|
||||
@tempfile = Tempfile.new(@original_filename)
|
||||
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
||||
@tempfile.binmode if binary
|
||||
FileUtils.copy_file(path, @tempfile.path)
|
||||
end
|
||||
|
||||
def path
|
||||
@tempfile.path
|
||||
end
|
||||
|
||||
alias_method :local_path, :path
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,75 +0,0 @@
|
|||
module Rack
|
||||
module Test
|
||||
|
||||
module Utils
|
||||
include Rack::Utils
|
||||
|
||||
def requestify(value, prefix = nil)
|
||||
case value
|
||||
when Array
|
||||
value.map do |v|
|
||||
requestify(v, "#{prefix}[]")
|
||||
end.join("&")
|
||||
when Hash
|
||||
value.map do |k, v|
|
||||
requestify(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
||||
end.join("&")
|
||||
else
|
||||
"#{prefix}=#{escape(value)}"
|
||||
end
|
||||
end
|
||||
|
||||
module_function :requestify
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
p = Hash.new
|
||||
|
||||
params.each do |key, value|
|
||||
k = first ? key.to_s : "[#{key}]"
|
||||
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
end
|
||||
end
|
||||
|
||||
return p
|
||||
end
|
||||
|
||||
module_function :multipart_requestify
|
||||
|
||||
def multipart_body(params)
|
||||
multipart_requestify(params).map do |key, value|
|
||||
if value.respond_to?(:original_filename)
|
||||
::File.open(value.path, "rb") do |f|
|
||||
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
||||
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{key}"; filename="#{escape(value.original_filename)}"\r
|
||||
Content-Type: #{value.content_type}\r
|
||||
Content-Length: #{::File.stat(value.path).size}\r
|
||||
\r
|
||||
#{f.read}\r
|
||||
EOF
|
||||
end
|
||||
else
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{key}"\r
|
||||
\r
|
||||
#{value}\r
|
||||
EOF
|
||||
end
|
||||
end.join("")+"--#{MULTIPART_BOUNDARY}--\r"
|
||||
end
|
||||
|
||||
module_function :multipart_body
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue