diff options
Diffstat (limited to 'actionpack/lib/action_controller/testing')
3 files changed, 62 insertions, 326 deletions
diff --git a/actionpack/lib/action_controller/testing/assertions/response.rb b/actionpack/lib/action_controller/testing/assertions/response.rb index ca0a9bbf52..c57cbecc44 100644 --- a/actionpack/lib/action_controller/testing/assertions/response.rb +++ b/actionpack/lib/action_controller/testing/assertions/response.rb @@ -22,6 +22,8 @@ module ActionController # assert_response 401 # def assert_response(type, message = nil) + validate_response! + clean_backtrace do if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?") assert_block("") { true } # to count the assertion @@ -30,8 +32,8 @@ module ActionController elsif type.is_a?(Symbol) && @response.response_code == ActionDispatch::StatusCodes::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - if @response.error? - exception = @response.template.instance_variable_get(:@exception) + if @controller && @response.error? + exception = @controller.response.template.instance_variable_get(:@exception) exception_message = exception && exception.message assert_block(build_message(message, "Expected response to be a <?>, but was <?>\n<?>", type, @response.response_code, exception_message.to_s)) { false } else @@ -57,6 +59,8 @@ module ActionController # assert_redirected_to @customer # def assert_redirected_to(options = {}, message=nil) + validate_response! + clean_backtrace do assert_response(:redirect, message) return true if options == @response.redirected_to @@ -89,23 +93,25 @@ module ActionController # assert_template :partial => false # def assert_template(options = {}, message = nil) + validate_response! + clean_backtrace do case options when NilClass, String - rendered = @response.rendered[:template].to_s + rendered = @controller.response.rendered[:template].to_s msg = build_message(message, "expecting <?> but rendering with <?>", options, rendered) assert_block(msg) do if options.nil? - @response.rendered[:template].blank? + @controller.response.rendered[:template].blank? else rendered.to_s.match(options) end end when Hash if expected_partial = options[:partial] - partials = @response.rendered[:partials] + partials = @controller.response.rendered[:partials] if expected_count = options[:count] found = partials.detect { |p, _| p.to_s.match(expected_partial) } actual_count = found.nil? ? 0 : found.second @@ -120,7 +126,7 @@ module ActionController assert(partials.keys.any? { |p| p.to_s.match(expected_partial) }, msg) end else - assert @response.rendered[:partials].empty?, + assert @controller.response.rendered[:partials].empty?, "Expected no partials to be rendered" end end @@ -145,6 +151,12 @@ module ActionController @request.protocol + @request.host_with_port + after_routing end end + + def validate_response! + unless @request.is_a?(ActionDispatch::Request) + raise ArgumentError, "@request must be an ActionDispatch::Request" + end + end end end end diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_controller/testing/integration.rb index d51b9b63ff..be5f216e2b 100644 --- a/actionpack/lib/action_controller/testing/integration.rb +++ b/actionpack/lib/action_controller/testing/integration.rb @@ -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,126 +249,65 @@ 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 + [ControllerCapture, ActionController::ProcessWithTest].each do |mod| + unless ActionController::Base < mod + ActionController::Base.class_eval { include mod } + end end - env["QUERY_STRING"] ||= "" + ActionController::Base.clear_last_instantiation! - data = data.is_a?(IO) ? data : StringIO.new(data || '') + opts = { + :method => method.to_s.upcase, + :params => parameters, + :headers => headers, - 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, + "HTTP_COOKIE" => cookies.inject("") { |string, (name, value)| + string << "#{name}=#{value}; " + } + } + env = ActionDispatch::Test::MockRequest.env_for(@path, opts) - "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 - ActionController::Base.class_eval { include mod } - end - end - - 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 - + app = Rack::Lint.new(@app) status, headers, body = app.call(env) + response = ::Rack::MockResponse.new(status, headers, body) + @request_count += 1 + @request = Request.new(env) - @html_document = nil + @response = Response.new + @response.status = @status = response.status + @response.headers = @headers = response.headers + @response.body = @body = response.body - @status = status.to_i @status_message = ActionDispatch::StatusCodes::STATUS_CODES[@status] - - @headers = Rack::Utils::HeaderHash.new(headers) - - (@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 - end + @cookies.merge!(@response.cookies) + @html_document = nil # Decorate the response with the standard behavior of the # TestResponse so that things like assert_response can be # used in integration tests. @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}; " + if @controller = ActionController::Base.last_instantiation + @controller.send(:set_test_assigns) end + + return @status end # Get a temporary URL writer object @@ -389,72 +322,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 +380,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 } diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index 7e2857614c..16275371ad 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -1,8 +1,9 @@ require 'rack/session/abstract/id' + module ActionController #:nodoc: class TestRequest < ActionDispatch::Request #:nodoc: - attr_accessor :cookies, :session_options - attr_accessor :query_parameters, :path, :session + attr_accessor :cookies + attr_accessor :query_parameters, :path attr_accessor :host def self.new(env = {}) @@ -13,18 +14,13 @@ module ActionController #:nodoc: super(Rack::MockRequest.env_for("/").merge(env)) @query_parameters = {} - @session = TestSession.new - default_rack_options = Rack::Session::Abstract::ID::DEFAULT_OPTIONS - @session_options ||= {:id => generate_sid(default_rack_options[:sidbits])}.merge(default_rack_options) + @env['rack.session'] = TestSession.new + @env['rack.session.options'] = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) initialize_default_values initialize_containers end - def reset_session - @session.reset - end - # Wraps raw_post in a StringIO. def body_stream #:nodoc: StringIO.new(raw_post) @@ -124,10 +120,6 @@ module ActionController #:nodoc: end private - def generate_sid(sidbits) - "%0#{sidbits / 4}x" % rand(2**sidbits - 1) - end - def initialize_containers @cookies = {} end @@ -156,54 +148,8 @@ module ActionController #:nodoc: # A refactoring of TestResponse to allow the same behavior to be applied # to the "real" CgiResponse class in integration tests. module TestResponseBehavior #:nodoc: - # The response code of the request - def response_code - status.to_s[0,3].to_i rescue 0 - end - - # Returns a String to ensure compatibility with Net::HTTPResponse - def code - status.to_s.split(' ')[0] - end - - def message - status.to_s.split(' ',2)[1] - end - - # Was the response successful? - def success? - (200..299).include?(response_code) - end - - # Was the URL not found? - def missing? - response_code == 404 - end - - # Were we redirected? - def redirect? - (300..399).include?(response_code) - end - - # Was there a server-side error? - def error? - (500..599).include?(response_code) - end - - alias_method :server_error?, :error? - - # Was there a client client? - def client_error? - (400..499).include?(response_code) - end - - # Returns the redirection location or nil - def redirect_url - headers['Location'] - end - - # Does the redirect location match this regexp pattern? - def redirect_url_match?( pattern ) + def redirect_url_match?(pattern) + ::ActiveSupport::Deprecation.warn("response.redirect_url_match? is deprecated. Use assert_match(/foo/, response.redirect_url) instead", caller) return false if redirect_url.nil? p = Regexp.new(pattern) if pattern.class == String p = pattern if pattern.class == Regexp @@ -252,18 +198,6 @@ module ActionController #:nodoc: !template_objects[name].nil? end - # Returns the response cookies, converted to a Hash of (name => value) pairs - # - # assert_equal 'AuthorOfNewPage', r.cookies['author'] - def cookies - cookies = {} - Array(headers['Set-Cookie']).each do |cookie| - key, value = cookie.split(";").first.split("=").map {|val| Rack::Utils.unescape(val)} - cookies[key] = value - end - cookies - end - # Returns binary content (downloadable file), converted to a String def binary_content raise "Response body is not a Proc: #{body_parts.inspect}" unless body_parts.kind_of?(Proc) @@ -293,62 +227,12 @@ module ActionController #:nodoc: end end - class TestSession < Hash #:nodoc: - attr_accessor :session_id - - def initialize(attributes = nil) - reset_session_id - replace_attributes(attributes) - end - - def reset - reset_session_id - replace_attributes({ }) - end - - def data - to_hash - end - - def [](key) - super(key.to_s) - end - - def []=(key, value) - super(key.to_s, value) - end - - def update(hash = nil) - if hash.nil? - ActiveSupport::Deprecation.warn('use replace instead', caller) - replace({}) - else - super(hash) - end - end + class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc: + DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS - def delete(key = nil) - if key.nil? - ActiveSupport::Deprecation.warn('use clear instead', caller) - clear - else - super(key.to_s) - end - end - - def close - ActiveSupport::Deprecation.warn('sessions should no longer be closed', caller) - end - - private - - def reset_session_id - @session_id = '' - end - - def replace_attributes(attributes = nil) - attributes ||= {} - replace(attributes.stringify_keys) + def initialize(session = {}) + replace(session.stringify_keys) + @loaded = true end end @@ -363,34 +247,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) |