mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Start to integrate some of the features in Rack::Test.
Eventually commit ActionDispatch::Test::MockRequest and ActionDispatch::Test:: UploadedFile upstream.
This commit is contained in:
parent
cbcc0ca57b
commit
dd2ed32418
6 changed files with 200 additions and 187 deletions
|
@ -17,9 +17,6 @@ module ActionController
|
|||
include ActionController::TestCase::Assertions
|
||||
include ActionController::TestProcess
|
||||
|
||||
# Rack application to use
|
||||
attr_accessor :application
|
||||
|
||||
# The integer HTTP status code of the last request.
|
||||
attr_reader :status
|
||||
|
||||
|
@ -60,12 +57,9 @@ module ActionController
|
|||
# A running counter of the number of requests processed.
|
||||
attr_accessor :request_count
|
||||
|
||||
class MultiPartNeededException < Exception
|
||||
end
|
||||
|
||||
# Create and initialize a new Session instance.
|
||||
def initialize(app = nil)
|
||||
@application = app || ActionController::Dispatcher.new
|
||||
@app = app || ActionController::Dispatcher.new
|
||||
reset!
|
||||
end
|
||||
|
||||
|
@ -255,53 +249,8 @@ module ActionController
|
|||
|
||||
# Performs the actual request.
|
||||
def process(method, path, parameters = nil, headers = nil)
|
||||
data = requestify(parameters)
|
||||
path = interpret_uri(path) if path =~ %r{://}
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
@path = path
|
||||
env = {}
|
||||
|
||||
if method == :get
|
||||
env["QUERY_STRING"] = data
|
||||
data = nil
|
||||
end
|
||||
|
||||
env["QUERY_STRING"] ||= ""
|
||||
|
||||
data = data.is_a?(IO) ? data : StringIO.new(data || '')
|
||||
|
||||
env.update(
|
||||
"REQUEST_METHOD" => method.to_s.upcase,
|
||||
"SERVER_NAME" => host,
|
||||
"SERVER_PORT" => (https? ? "443" : "80"),
|
||||
"HTTPS" => https? ? "on" : "off",
|
||||
"rack.url_scheme" => https? ? "https" : "http",
|
||||
"SCRIPT_NAME" => "",
|
||||
|
||||
"REQUEST_URI" => path,
|
||||
"PATH_INFO" => path,
|
||||
"HTTP_HOST" => host,
|
||||
"REMOTE_ADDR" => remote_addr,
|
||||
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
||||
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
|
||||
"HTTP_COOKIE" => encode_cookies,
|
||||
"HTTP_ACCEPT" => accept,
|
||||
|
||||
"rack.version" => [0,1],
|
||||
"rack.input" => data,
|
||||
"rack.errors" => StringIO.new,
|
||||
"rack.multithread" => true,
|
||||
"rack.multiprocess" => true,
|
||||
"rack.run_once" => false,
|
||||
|
||||
"rack.test" => true
|
||||
)
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
[ControllerCapture, ActionController::ProcessWithTest].each do |mod|
|
||||
unless ActionController::Base < mod
|
||||
|
@ -311,47 +260,54 @@ module ActionController
|
|||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
app = @application
|
||||
# Rack::Lint doesn't accept String headers or bodies in Ruby 1.9
|
||||
unless RUBY_VERSION >= '1.9.0' && Rack.release <= '0.9.0'
|
||||
app = Rack::Lint.new(app)
|
||||
end
|
||||
opts = {
|
||||
:method => method.to_s.upcase,
|
||||
:params => parameters,
|
||||
:headers => headers,
|
||||
|
||||
"SERVER_NAME" => host,
|
||||
"SERVER_PORT" => (https? ? "443" : "80"),
|
||||
"HTTPS" => https? ? "on" : "off",
|
||||
"rack.url_scheme" => https? ? "https" : "http",
|
||||
|
||||
"REQUEST_URI" => path,
|
||||
"PATH_INFO" => path,
|
||||
"HTTP_HOST" => host,
|
||||
"REMOTE_ADDR" => remote_addr,
|
||||
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
||||
"HTTP_ACCEPT" => accept,
|
||||
"HTTP_COOKIE" => cookies.inject("") { |string, (name, value)|
|
||||
string << "#{name}=#{value}; "
|
||||
},
|
||||
|
||||
"rack.test" => true
|
||||
}
|
||||
env = ActionDispatch::Test::MockRequest.env_for(@path, opts)
|
||||
|
||||
app = Rack::Lint.new(@app)
|
||||
status, headers, body = app.call(env)
|
||||
response = ::Rack::MockResponse.new(status, headers, body)
|
||||
@request_count += 1
|
||||
|
||||
@html_document = nil
|
||||
|
||||
@status = status.to_i
|
||||
@status = response.status
|
||||
@status_message = ActionDispatch::StatusCodes::STATUS_CODES[@status]
|
||||
|
||||
@headers = Rack::Utils::HeaderHash.new(headers)
|
||||
@headers = response.headers
|
||||
@body = response.body
|
||||
|
||||
(@headers['Set-Cookie'] || "").split("\n").each do |cookie|
|
||||
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
|
||||
@cookies[name] = value
|
||||
end
|
||||
|
||||
if body.is_a?(String)
|
||||
@body_parts = [body]
|
||||
@body = body
|
||||
else
|
||||
@body_parts = []
|
||||
body.each { |part| @body_parts << part.to_s }
|
||||
@body = @body_parts.join
|
||||
end
|
||||
|
||||
if @controller = ActionController::Base.last_instantiation
|
||||
@request = @controller.request
|
||||
@response = @controller.response
|
||||
@controller.send(:set_test_assigns)
|
||||
else
|
||||
# Decorate responses from Rack Middleware and Rails Metal
|
||||
# as an Response for the purposes of integration testing
|
||||
@response = ActionDispatch::Response.new
|
||||
@response.status = status.to_s
|
||||
@response.headers.replace(@headers)
|
||||
@response.body = @body_parts
|
||||
@request = ::Rack::Request.new(env)
|
||||
@response = response
|
||||
end
|
||||
|
||||
# Decorate the response with the standard behavior of the
|
||||
|
@ -360,21 +316,6 @@ module ActionController
|
|||
@response.extend(TestResponseBehavior)
|
||||
|
||||
return @status
|
||||
rescue MultiPartNeededException
|
||||
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
status = process(method, path,
|
||||
multipart_body(parameters, boundary),
|
||||
(headers || {}).merge(
|
||||
{"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
|
||||
return status
|
||||
end
|
||||
|
||||
# Encode the cookies hash in a format suitable for passing to a
|
||||
# request.
|
||||
def encode_cookies
|
||||
cookies.inject("") do |string, (name, value)|
|
||||
string << "#{name}=#{value}; "
|
||||
end
|
||||
end
|
||||
|
||||
# Get a temporary URL writer object
|
||||
|
@ -389,72 +330,6 @@ module ActionController
|
|||
}
|
||||
UrlRewriter.new(ActionDispatch::Request.new(env), {})
|
||||
end
|
||||
|
||||
def name_with_prefix(prefix, name)
|
||||
prefix ? "#{prefix}[#{name}]" : name.to_s
|
||||
end
|
||||
|
||||
# Convert the given parameters to a request string. The parameters may
|
||||
# be a string, +nil+, or a Hash.
|
||||
def requestify(parameters, prefix=nil)
|
||||
if TestUploadedFile === parameters
|
||||
raise MultiPartNeededException
|
||||
elsif Hash === parameters
|
||||
return nil if parameters.empty?
|
||||
parameters.map { |k,v|
|
||||
requestify(v, name_with_prefix(prefix, k))
|
||||
}.join("&")
|
||||
elsif Array === parameters
|
||||
parameters.map { |v|
|
||||
requestify(v, name_with_prefix(prefix, ""))
|
||||
}.join("&")
|
||||
elsif prefix.nil?
|
||||
parameters
|
||||
else
|
||||
"#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
end
|
||||
else
|
||||
p[k] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multipart_body(params, boundary)
|
||||
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
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"; filename="#{CGI.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
|
||||
--#{boundary}\r
|
||||
Content-Disposition: form-data; name="#{key}"\r
|
||||
\r
|
||||
#{value}\r
|
||||
EOF
|
||||
end
|
||||
end.join("")+"--#{boundary}--\r"
|
||||
end
|
||||
end
|
||||
|
||||
# A module used to extend ActionController::Base, so that integration tests
|
||||
|
@ -513,8 +388,8 @@ EOF
|
|||
# By default, a single session is automatically created for you, but you
|
||||
# can use this method to open multiple sessions that ought to be tested
|
||||
# simultaneously.
|
||||
def open_session(application = nil)
|
||||
session = Integration::Session.new(application)
|
||||
def open_session(app = nil)
|
||||
session = Integration::Session.new(app)
|
||||
|
||||
# delegate the fixture accessors back to the test instance
|
||||
extras = Module.new { attr_accessor :delegate, :test_result }
|
||||
|
|
|
@ -363,34 +363,7 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
|
||||
# post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
|
||||
require 'tempfile'
|
||||
class TestUploadedFile
|
||||
# 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 = Mime::TEXT, binary = false)
|
||||
raise "#{path} file does not exist" unless File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
|
||||
@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 #:nodoc:
|
||||
@tempfile.path
|
||||
end
|
||||
|
||||
alias local_path path
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
TestUploadedFile = ActionDispatch::Test::UploadedFile
|
||||
|
||||
module TestProcess
|
||||
def self.included(base)
|
||||
|
|
|
@ -60,6 +60,11 @@ module ActionDispatch
|
|||
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
|
||||
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
|
||||
end
|
||||
|
||||
module Test
|
||||
autoload :UploadedFile, 'action_dispatch/test/uploaded_file'
|
||||
autoload :MockRequest, 'action_dispatch/test/mock'
|
||||
end
|
||||
end
|
||||
|
||||
autoload :Mime, 'action_dispatch/http/mime_type'
|
||||
|
|
127
actionpack/lib/action_dispatch/test/mock.rb
Normal file
127
actionpack/lib/action_dispatch/test/mock.rb
Normal file
|
@ -0,0 +1,127 @@
|
|||
module ActionDispatch
|
||||
module Test
|
||||
class MockRequest < Rack::MockRequest
|
||||
MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
||||
|
||||
class << self
|
||||
def env_for(path, opts)
|
||||
headers = opts.delete(:headers)
|
||||
|
||||
method = (opts[:method] || opts["REQUEST_METHOD"]).to_s.upcase
|
||||
opts[:method] = opts["REQUEST_METHOD"] = method
|
||||
|
||||
path = "/#{path}" unless path[0] == ?/
|
||||
uri = URI.parse(path)
|
||||
uri.host ||= "example.org"
|
||||
|
||||
if URI::HTTPS === uri
|
||||
opts.update("SERVER_PORT" => "443", "HTTPS" => "on")
|
||||
end
|
||||
|
||||
if method == "POST" && !opts.has_key?(:input)
|
||||
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
||||
|
||||
multipart = (opts[:params] || {}).any? do |k, v|
|
||||
UploadedFile === v
|
||||
end
|
||||
|
||||
if multipart
|
||||
opts[:input] = multipart_body(opts.delete(:params))
|
||||
opts["CONTENT_LENGTH"] ||= opts[:input].length.to_s
|
||||
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
|
||||
else
|
||||
params = opts.delete(:params)
|
||||
opts[:input] = case params
|
||||
when Hash then requestify(params)
|
||||
when nil then ""
|
||||
else params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
params = opts[:params] || {}
|
||||
if params.is_a?(String)
|
||||
if method == "GET"
|
||||
uri.query = params
|
||||
else
|
||||
opts[:input] = params
|
||||
end
|
||||
else
|
||||
params.update(::Rack::Utils.parse_query(uri.query))
|
||||
uri.query = requestify(params)
|
||||
end
|
||||
|
||||
env = ::Rack::MockRequest.env_for(uri.to_s, opts)
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
key = key.to_s.upcase.gsub(/-/, "_")
|
||||
key = "HTTP_#{key}" unless env.has_key?(key) || key =~ /^HTTP_/
|
||||
env[key] = value
|
||||
end
|
||||
|
||||
env
|
||||
end
|
||||
|
||||
private
|
||||
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}[#{::Rack::Utils.escape(k)}]" : ::Rack::Utils.escape(k))
|
||||
end.join("&")
|
||||
else
|
||||
"#{prefix}=#{::Rack::Utils.escape(value)}"
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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="#{::Rack::Utils.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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
actionpack/lib/action_dispatch/test/uploaded_file.rb
Normal file
33
actionpack/lib/action_dispatch/test/uploaded_file.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
require "tempfile"
|
||||
|
||||
module ActionDispatch
|
||||
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
|
|
@ -337,7 +337,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest
|
|||
get '/get_with_params?foo=bar'
|
||||
assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"]
|
||||
assert_equal '/get_with_params?foo=bar', request.request_uri
|
||||
assert_equal "", request.env["QUERY_STRING"]
|
||||
assert_equal "foo=bar", request.env["QUERY_STRING"]
|
||||
assert_equal 'foo=bar', request.query_string
|
||||
assert_equal 'bar', request.parameters['foo']
|
||||
|
||||
|
|
Loading…
Reference in a new issue