diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-09-30 22:27:02 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-09-30 22:27:02 +0100 |
commit | dd2779e1b83b4d867d47dd286ec0c919f5df12a9 (patch) | |
tree | 6e52ea0a329c24429f4d1d41b065e082f0ed6baa /actionpack/lib/action_controller | |
parent | 329b14aa8fdd291a00d17ba12c2e0ab4c3a157cc (diff) | |
parent | 420004e030e96f2ace6e27fd622c90ee9e986677 (diff) | |
download | rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.tar.gz rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.tar.bz2 rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.zip |
Merge commit 'mainstream/master'
Diffstat (limited to 'actionpack/lib/action_controller')
11 files changed, 249 insertions, 677 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index f5bd0a00a1..5338a70104 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -14,6 +14,7 @@ module ActionController include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackConvenience + include ActionController::Benchmarking # Legacy modules include SessionManagement diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb new file mode 100644 index 0000000000..86336b6bc4 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/integration_test.rb @@ -0,0 +1,2 @@ +ActionController::Integration = ActionDispatch::Integration +ActionController::IntegrationTest = ActionDispatch::IntegrationTest diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb new file mode 100644 index 0000000000..fcf47d31a7 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/performance_test.rb @@ -0,0 +1 @@ +ActionController::PerformanceTest = ActionDispatch::PerformanceTest diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb index ba316b9e63..e04da42637 100644 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb @@ -7,15 +7,6 @@ module ActionController cattr_accessor :prepare_each_request self.prepare_each_request = false - cattr_accessor :router - self.router = Routing::Routes - - cattr_accessor :middleware - self.middleware = ActionDispatch::MiddlewareStack.new do |middleware| - middlewares = File.join(File.dirname(__FILE__), "middlewares.rb") - middleware.instance_eval(File.read(middlewares), middlewares, 1) - end - class << self def define_dispatcher_callbacks(cache_classes) unless cache_classes @@ -24,7 +15,7 @@ module ActionController # Development mode callbacks ActionDispatch::Callbacks.before_dispatch do |app| - ActionController::Dispatcher.router.reload + ActionController::Routing::Routes.reload end ActionDispatch::Callbacks.after_dispatch do @@ -58,7 +49,8 @@ module ActionController :to => ActionDispatch::Callbacks def new - @@middleware.build(@@router) + # DEPRECATE Rails application fallback + Rails.application end end end diff --git a/actionpack/lib/action_controller/dispatch/middlewares.rb b/actionpack/lib/action_controller/dispatch/middlewares.rb deleted file mode 100644 index 5641b3cb8d..0000000000 --- a/actionpack/lib/action_controller/dispatch/middlewares.rb +++ /dev/null @@ -1,16 +0,0 @@ -use "Rack::Lock", :if => lambda { - !ActionController::Base.allow_concurrency -} - -use "ActionDispatch::ShowExceptions", lambda { ActionController::Base.consider_all_requests_local } -use "ActionDispatch::Callbacks", lambda { ActionController::Dispatcher.prepare_each_request } - -# TODO: Redirect global exceptions somewhere? -# use "ActionDispatch::Rescue" - -use lambda { ActionController::Base.session_store }, - lambda { ActionController::Base.session_options } - -use "ActionDispatch::ParamsParser" -use "Rack::MethodOverride" -use "Rack::Head" diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb new file mode 100644 index 0000000000..d4cb1e122d --- /dev/null +++ b/actionpack/lib/action_controller/metal/benchmarking.rb @@ -0,0 +1,92 @@ +require 'benchmark' + +module ActionController #:nodoc: + # The benchmarking module times the performance of actions and reports to the logger. If the Active Record + # package has been included, a separate timing section for database calls will be added as well. + module Benchmarking #:nodoc: + extend ActiveSupport::Concern + + module ClassMethods + # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it + # (unless <tt>use_silence</tt> is set to false). + # + # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it + # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark + # will only be conducted if the log level is low enough. + def benchmark(title, log_level = Logger::DEBUG, use_silence = true) + if logger && logger.level == log_level + result = nil + ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } + logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") + result + else + yield + end + end + end + + protected + def render(*args, &block) + if logger + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + db_runtime = ActiveRecord::Base.connection.reset_runtime + end + + render_output = nil + @view_runtime = Benchmark.ms { render_output = super } + + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + @db_rt_before_render = db_runtime + @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + @view_runtime -= @db_rt_after_render + end + + render_output + else + super + end + end + + private + def process_action(*args) + if logger + ms = [Benchmark.ms { super }, 0.01].max + logging_view = defined?(@view_runtime) + logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + + log_message = 'Completed in %.0fms' % ms + + if logging_view || logging_active_record + log_message << " (" + log_message << view_runtime if logging_view + + if logging_active_record + log_message << ", " if logging_view + log_message << active_record_runtime + ")" + else + ")" + end + end + + log_message << " | #{response.status}" + log_message << " [#{complete_request_uri rescue "unknown"}]" + + logger.info(log_message) + response.headers["X-Runtime"] = "%.0f" % ms + else + super + end + end + + def view_runtime + "View: %.0f" % @view_runtime + end + + def active_record_runtime + db_runtime = ActiveRecord::Base.connection.reset_runtime + db_runtime += @db_rt_before_render if @db_rt_before_render + db_runtime += @db_rt_after_render if @db_rt_after_render + "DB: %.0f" % db_runtime + end + end +end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index d0811254cb..b9d23da3e0 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -2,9 +2,6 @@ module ActionController class ActionControllerError < StandardError #:nodoc: end - class SessionRestoreError < ActionControllerError #:nodoc: - end - class RenderError < ActionControllerError #:nodoc: end diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_controller/testing/integration.rb deleted file mode 100644 index 791f936f51..0000000000 --- a/actionpack/lib/action_controller/testing/integration.rb +++ /dev/null @@ -1,478 +0,0 @@ -require 'stringio' -require 'uri' -require 'active_support/test_case' -require 'active_support/core_ext/object/metaclass' -require 'rack/test' - -module ActionController - module Integration #:nodoc: - module RequestHelpers - # Performs a GET request with the given parameters. - # - # - +path+: The URI (as a String) on which you want to perform a GET - # request. - # - +parameters+: The HTTP parameters that you want to pass. This may - # be +nil+, - # a Hash, or a String that is appropriately encoded - # (<tt>application/x-www-form-urlencoded</tt> or - # <tt>multipart/form-data</tt>). - # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will - # automatically be upcased, with the prefix 'HTTP_' added if needed. - # - # This method returns an Response object, which one can use to - # inspect the details of the response. Furthermore, if this method was - # called from an ActionController::IntegrationTest object, then that - # object's <tt>@response</tt> instance variable will point to the same - # response object. - # - # You can also perform POST, PUT, DELETE, and HEAD requests with +post+, - # +put+, +delete+, and +head+. - def get(path, parameters = nil, headers = nil) - process :get, path, parameters, headers - end - - # Performs a POST request with the given parameters. See get() for more - # details. - def post(path, parameters = nil, headers = nil) - process :post, path, parameters, headers - end - - # Performs a PUT request with the given parameters. See get() for more - # details. - def put(path, parameters = nil, headers = nil) - process :put, path, parameters, headers - end - - # Performs a DELETE request with the given parameters. See get() for - # more details. - def delete(path, parameters = nil, headers = nil) - process :delete, path, parameters, headers - end - - # Performs a HEAD request with the given parameters. See get() for more - # details. - def head(path, parameters = nil, headers = nil) - process :head, path, parameters, headers - end - - # Performs an XMLHttpRequest request with the given parameters, mirroring - # a request from the Prototype library. - # - # The request_method is :get, :post, :put, :delete or :head; the - # parameters are +nil+, a hash, or a url-encoded or multipart string; - # the headers are a hash. Keys are automatically upcased and prefixed - # with 'HTTP_' if not already. - def xml_http_request(request_method, path, parameters = nil, headers = nil) - headers ||= {} - headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - process(request_method, path, parameters, headers) - end - alias xhr :xml_http_request - - # Follow a single redirect response. If the last response was not a - # redirect, an exception will be raised. Otherwise, the redirect is - # performed on the location header. - def follow_redirect! - raise "not a redirect! #{status} #{status_message}" unless redirect? - get(response.location) - status - end - - # Performs a request using the specified method, following any subsequent - # redirect. Note that the redirects are followed until the response is - # not a redirect--this means you may run into an infinite loop if your - # redirect loops back to itself. - def request_via_redirect(http_method, path, parameters = nil, headers = nil) - process(http_method, path, parameters, headers) - follow_redirect! while redirect? - status - end - - # Performs a GET request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def get_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:get, path, parameters, headers) - end - - # Performs a POST request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def post_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:post, path, parameters, headers) - end - - # Performs a PUT request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def put_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:put, path, parameters, headers) - end - - # Performs a DELETE request, following any subsequent redirect. - # See +request_via_redirect+ for more information. - def delete_via_redirect(path, parameters = nil, headers = nil) - request_via_redirect(:delete, path, parameters, headers) - end - end - - # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Because you can instantiate - # multiple sessions and run them side-by-side, you can also mimic (to some - # limited extent) multiple simultaneous users interacting with your system. - # - # Typically, you will instantiate a new session using - # IntegrationTest#open_session, rather than instantiating - # Integration::Session directly. - class Session - DEFAULT_HOST = "www.example.com" - - include Test::Unit::Assertions - include ActionDispatch::Assertions - include ActionController::TestProcess - include RequestHelpers - - %w( status status_message headers body redirect? ).each do |method| - delegate method, :to => :response, :allow_nil => true - end - - %w( path ).each do |method| - delegate method, :to => :request, :allow_nil => true - end - - # The hostname used in the last request. - attr_accessor :host - - # The remote_addr used in the last request. - attr_accessor :remote_addr - - # The Accept header to send. - attr_accessor :accept - - # A map of the cookies returned by the last response, and which will be - # sent with the next request. - def cookies - @mock_session.cookie_jar - end - - # A reference to the controller instance used by the last request. - attr_reader :controller - - # A reference to the request instance used by the last request. - attr_reader :request - - # A reference to the response instance used by the last request. - attr_reader :response - - # A running counter of the number of requests processed. - attr_accessor :request_count - - # Create and initialize a new Session instance. - def initialize(app = nil) - @app = app || ActionController::Dispatcher.new - reset! - end - - # Resets the instance. This can be used to reset the state information - # in an existing session instance, so it can be used from a clean-slate - # condition. - # - # session.reset! - def reset! - @https = false - @mock_session = Rack::MockSession.new(@app, DEFAULT_HOST) - @controller = @request = @response = nil - @request_count = 0 - - self.host = DEFAULT_HOST - self.remote_addr = "127.0.0.1" - self.accept = "text/xml,application/xml,application/xhtml+xml," + - "text/html;q=0.9,text/plain;q=0.8,image/png," + - "*/*;q=0.5" - - unless defined? @named_routes_configured - # install the named routes in this session instance. - klass = metaclass - Routing::Routes.install_helpers(klass) - - # the helpers are made protected by default--we make them public for - # easier access during testing and troubleshooting. - klass.module_eval { public *Routing::Routes.named_routes.helpers } - @named_routes_configured = true - end - end - - # Specify whether or not the session should mimic a secure HTTPS request. - # - # session.https! - # session.https!(false) - def https!(flag = true) - @https = flag - end - - # Return +true+ if the session is mimicking a secure HTTPS request. - # - # if session.https? - # ... - # end - def https? - @https - end - - # Set the host name to use in the next request. - # - # session.host! "www.example.com" - def host!(name) - @host = name - end - - # Returns the URL for the given options, according to the rules specified - # in the application's routes. - def url_for(options) - controller ? - controller.url_for(options) : - generic_url_rewriter.rewrite(options) - end - - private - - # Performs the actual request. - def process(method, path, parameters = nil, rack_environment = nil) - if path =~ %r{://} - location = URI.parse(path) - https! URI::HTTPS === location if location.scheme - host! location.host if location.host - path = location.query ? "#{location.path}?#{location.query}" : location.path - end - - [ControllerCapture, ActionController::Testing].each do |mod| - unless ActionController::Base < mod - ActionController::Base.class_eval { include mod } - end - end - - env = { - :method => method, - :params => parameters, - - "SERVER_NAME" => host, - "SERVER_PORT" => (https? ? "443" : "80"), - "HTTPS" => https? ? "on" : "off", - "rack.url_scheme" => https? ? "https" : "http", - - "REQUEST_URI" => path, - "HTTP_HOST" => host, - "REMOTE_ADDR" => remote_addr, - "CONTENT_TYPE" => "application/x-www-form-urlencoded", - "HTTP_ACCEPT" => accept - } - - (rack_environment || {}).each do |key, value| - env[key] = value - end - - session = Rack::Test::Session.new(@mock_session) - - @controller = ActionController::Base.capture_instantiation do - session.request(path, env) - end - - @request_count += 1 - @request = ActionDispatch::Request.new(session.last_request.env) - @response = ActionDispatch::TestResponse.from_response(@mock_session.last_response) - @html_document = nil - - return response.status - end - - # Get a temporary URL writer object - def generic_url_rewriter - env = { - 'REQUEST_METHOD' => "GET", - 'QUERY_STRING' => "", - "REQUEST_URI" => "/", - "HTTP_HOST" => host, - "SERVER_PORT" => https? ? "443" : "80", - "HTTPS" => https? ? "on" : "off" - } - UrlRewriter.new(ActionDispatch::Request.new(env), {}) - end - end - - # A module used to extend ActionController::Base, so that integration tests - # can capture the controller used to satisfy a request. - module ControllerCapture #:nodoc: - extend ActiveSupport::Concern - - included do - alias_method_chain :initialize, :capture - end - - def initialize_with_capture(*args) - initialize_without_capture - self.class.last_instantiation ||= self - end - - module ClassMethods #:nodoc: - mattr_accessor :last_instantiation - - def capture_instantiation - self.last_instantiation = nil - yield - return last_instantiation - end - end - end - - module Runner - # Reset the current session. This is useful for testing multiple sessions - # in a single test case. - def reset! - @integration_session = open_session - end - - %w(get post put head delete cookies assigns - xml_http_request xhr get_via_redirect post_via_redirect).each do |method| - define_method(method) do |*args| - reset! unless @integration_session - # reset the html_document variable, but only for new get/post calls - @html_document = nil unless %w(cookies assigns).include?(method) - returning @integration_session.__send__(method, *args) do - copy_session_variables! - end - end - end - - # Open a new session instance. If a block is given, the new session is - # yielded to the block before being returned. - # - # session = open_session do |sess| - # sess.extend(CustomAssertions) - # end - # - # 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(app = nil) - session = Integration::Session.new(app) - - # delegate the fixture accessors back to the test instance - extras = Module.new { attr_accessor :delegate, :test_result } - if self.class.respond_to?(:fixture_table_names) - self.class.fixture_table_names.each do |table_name| - name = table_name.tr(".", "_") - next unless respond_to?(name) - extras.__send__(:define_method, name) { |*args| - delegate.send(name, *args) - } - end - end - - # delegate add_assertion to the test case - extras.__send__(:define_method, :add_assertion) { - test_result.add_assertion - } - session.extend(extras) - session.delegate = self - session.test_result = @_result - - yield session if block_given? - session - end - - # Copy the instance variables from the current session instance into the - # test instance. - def copy_session_variables! #:nodoc: - return unless @integration_session - %w(controller response request).each do |var| - instance_variable_set("@#{var}", @integration_session.__send__(var)) - end - end - - # Delegate unhandled messages to the current session instance. - def method_missing(sym, *args, &block) - reset! unless @integration_session - returning @integration_session.__send__(sym, *args, &block) do - copy_session_variables! - end - end - end - end - - # An IntegrationTest is one that spans multiple controllers and actions, - # tying them all together to ensure they work together as expected. It tests - # more completely than either unit or functional tests do, exercising the - # entire stack, from the dispatcher to the database. - # - # At its simplest, you simply extend IntegrationTest and write your tests - # using the get/post methods: - # - # require "#{File.dirname(__FILE__)}/test_helper" - # - # class ExampleTest < ActionController::IntegrationTest - # fixtures :people - # - # def test_login - # # get the login page - # get "/login" - # assert_equal 200, status - # - # # post the login and follow through to the home page - # post "/login", :username => people(:jamis).username, - # :password => people(:jamis).password - # follow_redirect! - # assert_equal 200, status - # assert_equal "/home", path - # end - # end - # - # However, you can also have multiple session instances open per test, and - # even extend those instances with assertions and methods to create a very - # powerful testing DSL that is specific for your application. You can even - # reference any named routes you happen to have defined! - # - # require "#{File.dirname(__FILE__)}/test_helper" - # - # class AdvancedTest < ActionController::IntegrationTest - # fixtures :people, :rooms - # - # def test_login_and_speak - # jamis, david = login(:jamis), login(:david) - # room = rooms(:office) - # - # jamis.enter(room) - # jamis.speak(room, "anybody home?") - # - # david.enter(room) - # david.speak(room, "hello!") - # end - # - # private - # - # module CustomAssertions - # def enter(room) - # # reference a named route, for maximum internal consistency! - # get(room_url(:id => room.id)) - # assert(...) - # ... - # end - # - # def speak(room, message) - # xml_http_request "/say/#{room.id}", :message => message - # assert(...) - # ... - # end - # end - # - # def login(who) - # open_session do |sess| - # sess.extend(CustomAssertions) - # who = people(who) - # sess.post "/login", :username => who.username, - # :password => who.password - # assert(...) - # end - # end - # end - class IntegrationTest < ActiveSupport::TestCase - include Integration::Runner - end -end diff --git a/actionpack/lib/action_controller/testing/performance_test.rb b/actionpack/lib/action_controller/testing/performance_test.rb deleted file mode 100644 index d88180087d..0000000000 --- a/actionpack/lib/action_controller/testing/performance_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'active_support/testing/performance' -require 'active_support/testing/default' - -module ActionController - # An integration test that runs a code profiler on your test methods. - # Profiling output for combinations of each test method, measurement, and - # output format are written to your tmp/performance directory. - # - # By default, process_time is measured and both flat and graph_html output - # formats are written, so you'll have two output files per test method. - class PerformanceTest < ActionController::IntegrationTest - include ActiveSupport::Testing::Performance - include ActiveSupport::Testing::Default - end -end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index 2fccf01040..bbc7f3c8f9 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -1,78 +1,7 @@ -require 'action_dispatch' -require 'rack/session/abstract/id' require 'active_support/core_ext/object/conversions' +require "rack/test" module ActionController #:nodoc: - class TestRequest < ActionDispatch::TestRequest #:nodoc: - def initialize(env = {}) - super - - self.session = TestSession.new - self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) - end - - def assign_parameters(controller_path, action, parameters) - parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) - extra_keys = ActionController::Routing::Routes.extra_keys(parameters) - non_path_parameters = get? ? query_parameters : request_parameters - parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = ActionController::Routing::PathSegment::Result.new(value) - end - - if extra_keys.include?(key.to_sym) - non_path_parameters[key] = value - else - path_parameters[key.to_s] = value - end - end - - params = self.request_parameters.dup - - %w(controller action only_path).each do |k| - params.delete(k) - params.delete(k.to_sym) - end - - data = params.to_query - @env['CONTENT_LENGTH'] = data.length.to_s - @env['rack.input'] = StringIO.new(data) - end - - def recycle! - @formats = nil - @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } - @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } - @env['action_dispatch.request.query_parameters'] = {} - end - end - - class TestResponse < ActionDispatch::TestResponse - def recycle! - @status = 200 - @header = {} - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 - @body = [] - @charset = nil - @content_type = nil - - @request = @template = nil - end - end - - class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc: - DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS - - def initialize(session = {}) - replace(session.stringify_keys) - @loaded = true - end - end - # Essentially generates a modified Tempfile object similar to the object # you'd get from the standard library CGI module in a multipart # request. This means you can use an ActionController::TestUploadedFile @@ -87,75 +16,6 @@ module ActionController #:nodoc: TestUploadedFile = Rack::Test::UploadedFile module TestProcess - def self.included(base) - # Executes a request simulating GET HTTP method and set/volley the response - def get(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "GET") - end - - # Executes a request simulating POST HTTP method and set/volley the response - def post(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "POST") - end - - # Executes a request simulating PUT HTTP method and set/volley the response - def put(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "PUT") - end - - # Executes a request simulating DELETE HTTP method and set/volley the response - def delete(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "DELETE") - end - - # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "HEAD") - end - end - - def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') - # Sanity check for required instance variables so we can give an - # understandable error message. - %w(@controller @request @response).each do |iv_name| - if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? - raise "#{iv_name} is nil: make sure you set it in your test's setup method." - end - end - - @request.recycle! - @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil - - @html_document = nil - @request.env['REQUEST_METHOD'] = http_method - - parameters ||= {} - @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) - - @request.session = ActionController::TestSession.new(session) unless session.nil? - @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash - - @controller.request = @request - @controller.params.merge!(parameters) - build_request_uri(action, parameters) - Base.class_eval { include Testing } - @controller.process_with_new_base_test(@request, @response) - @response - end - - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - returning __send__(request_method, action, parameters, session, flash) do - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' - end - end - alias xhr :xml_http_request - def assigns(key = nil) assigns = {} @controller.instance_variable_names.each do |ivar| @@ -182,16 +42,6 @@ module ActionController #:nodoc: @response.redirect_url end - def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.__send__(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.request_uri = url.rewrite(options) - end - end - def html_document xml = @response.content_type =~ /xml$/ @html_document ||= HTML::Document.new(@response.body, false, xml) @@ -249,7 +99,6 @@ module ActionController #:nodoc: temporary_routes = ActionController::Routing::RouteSet.new ActionController::Routing.module_eval { const_set :Routes, temporary_routes } - ActionController::Dispatcher.router = temporary_routes yield temporary_routes ensure @@ -257,7 +106,6 @@ module ActionController #:nodoc: ActionController::Routing.module_eval { remove_const :Routes } end ActionController::Routing.const_set(:Routes, real_routes) if real_routes - ActionController::Dispatcher.router = ActionController::Routing::Routes end end end diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb index b66a4c15ff..178e3477a6 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/testing/test_case.rb @@ -1,7 +1,77 @@ require 'active_support/test_case' -require 'action_controller/testing/process' +require 'rack/session/abstract/id' module ActionController + class TestRequest < ActionDispatch::TestRequest #:nodoc: + def initialize(env = {}) + super + + self.session = TestSession.new + self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) + end + + def assign_parameters(controller_path, action, parameters = {}) + parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) + extra_keys = ActionController::Routing::Routes.extra_keys(parameters) + non_path_parameters = get? ? query_parameters : request_parameters + parameters.each do |key, value| + if value.is_a? Fixnum + value = value.to_s + elsif value.is_a? Array + value = ActionController::Routing::PathSegment::Result.new(value) + end + + if extra_keys.include?(key.to_sym) + non_path_parameters[key] = value + else + path_parameters[key.to_s] = value + end + end + + params = self.request_parameters.dup + + %w(controller action only_path).each do |k| + params.delete(k) + params.delete(k.to_sym) + end + + data = params.to_query + @env['CONTENT_LENGTH'] = data.length.to_s + @env['rack.input'] = StringIO.new(data) + end + + def recycle! + @formats = nil + @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } + @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } + @env['action_dispatch.request.query_parameters'] = {} + end + end + + class TestResponse < ActionDispatch::TestResponse + def recycle! + @status = 200 + @header = {} + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 + @body = [] + @charset = nil + @content_type = nil + + @request = @template = nil + end + end + + class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc: + DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS + + def initialize(session = {}) + replace(session.stringify_keys) + @loaded = true + end + end + # Superclass for ActionController functional tests. Functional tests allow you to # test a single controller action per test method. This should not be confused with # integration tests (see ActionController::IntegrationTest), which are more like @@ -105,6 +175,73 @@ module ActionController class TestCase < ActiveSupport::TestCase include TestProcess + # Executes a request simulating GET HTTP method and set/volley the response + def get(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "GET") + end + + # Executes a request simulating POST HTTP method and set/volley the response + def post(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "POST") + end + + # Executes a request simulating PUT HTTP method and set/volley the response + def put(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "PUT") + end + + # Executes a request simulating DELETE HTTP method and set/volley the response + def delete(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "DELETE") + end + + # Executes a request simulating HEAD HTTP method and set/volley the response + def head(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "HEAD") + end + + def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) + @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + returning __send__(request_method, action, parameters, session, flash) do + @request.env.delete 'HTTP_X_REQUESTED_WITH' + @request.env.delete 'HTTP_ACCEPT' + end + end + alias xhr :xml_http_request + + def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') + # Sanity check for required instance variables so we can give an + # understandable error message. + %w(@controller @request @response).each do |iv_name| + if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end + end + + @request.recycle! + @response.recycle! + @controller.response_body = nil + @controller.formats = nil + @controller.params = nil + + @html_document = nil + @request.env['REQUEST_METHOD'] = http_method + + parameters ||= {} + @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) + + @request.session = ActionController::TestSession.new(session) unless session.nil? + @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash + + @controller.request = @request + @controller.params.merge!(parameters) + build_request_uri(action, parameters) + Base.class_eval { include Testing } + @controller.process_with_new_base_test(@request, @response) + @response + end + include ActionDispatch::Assertions # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline @@ -186,5 +323,16 @@ module ActionController def rescue_action_in_public! @request.remote_addr = '208.77.188.166' # example.com end + + private + def build_request_uri(action, parameters) + unless @request.env['REQUEST_URI'] + options = @controller.__send__(:rewrite_options, parameters) + options.update(:only_path => true, :action => action) + + url = ActionController::UrlRewriter.new(@request, parameters) + @request.request_uri = url.rewrite(options) + end + end end end |