1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Extract ActionController rescue templates into Rescue and ShowExceptions middleware.

This commit breaks all exception catching plugins like ExceptionNotifier. These plugins should be rewritten as middleware instead overriding Controller#rescue_action_in_public.
This commit is contained in:
Joshua Peek 2009-05-02 23:02:22 -05:00
parent 1c6fcbfd2d
commit 11af089cee
28 changed files with 413 additions and 566 deletions

View file

@ -60,7 +60,7 @@ module ActionController
autoload :Redirector, 'action_controller/base/redirect'
autoload :Renderer, 'action_controller/base/render'
autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection'
autoload :Rescue, 'action_controller/dispatch/rescue'
autoload :Rescue, 'action_controller/base/rescue'
autoload :Resources, 'action_controller/routing/resources'
autoload :Responder, 'action_controller/base/responder'
autoload :Routing, 'action_controller/routing'

View file

@ -30,10 +30,6 @@ module ActionController #:nodoc:
def allowed_methods_header
allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
end
def handle_response!(response)
response.headers['Allow'] ||= allowed_methods_header
end
end
class NotImplemented < MethodNotAllowed #:nodoc:
@ -510,9 +506,8 @@ module ActionController #:nodoc:
public
def call(env)
# HACK: For global rescue to have access to the original request and response
request = env["action_dispatch.rescue.request"] ||= ActionDispatch::Request.new(env)
response = env["action_dispatch.rescue.response"] ||= ActionDispatch::Response.new
request = ActionDispatch::Request.new(env)
response = ActionDispatch::Response.new
process(request, response).to_a
end

View file

@ -0,0 +1,50 @@
module ActionController #:nodoc:
# Actions that fail to perform as expected throw exceptions. These
# exceptions can either be rescued for the public view (with a nice
# user-friendly explanation) or for the developers view (with tons of
# debugging information). The developers view is already implemented by
# the Action Controller, but the public view should be tailored to your
# specific application.
#
# The default behavior for public exceptions is to render a static html
# file with the name of the error code thrown. If no such file exists, an
# empty response is sent with the correct status code.
#
# You can override what constitutes a local request by overriding the
# <tt>local_request?</tt> method in your own controller. Custom rescue
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
# and <tt>rescue_action_locally</tt> methods.
module Rescue
def self.included(base) #:nodoc:
base.send :include, ActiveSupport::Rescuable
base.extend(ClassMethods)
base.class_eval do
alias_method_chain :perform_action, :rescue
end
end
module ClassMethods
def rescue_action(env)
exception = env.delete('action_dispatch.rescue.exception')
request = ActionDispatch::Request.new(env)
response = ActionDispatch::Response.new
new.process(request, response, :rescue_action, exception).to_a
end
end
protected
# Exception handler called when the performance of an action raises
# an exception.
def rescue_action(exception)
rescue_with_handler(exception) || raise(exception)
end
private
def perform_action_with_rescue
perform_action_without_rescue
rescue Exception => exception
rescue_action(exception)
end
end
end

View file

@ -75,18 +75,10 @@ module ActionController
end
def _call(env)
begin
run_callbacks :before_dispatch
Routing::Routes.call(env)
rescue Exception => exception
if controller ||= (::ApplicationController rescue Base)
controller.call_with_exception(env, exception)
else
raise exception
end
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
run_callbacks :before_dispatch
Routing::Routes.call(env)
ensure
run_callbacks :after_dispatch, :enumerator => :reverse_each
end
def flush_logger

View file

@ -3,6 +3,11 @@ use "Rack::Lock", :if => lambda {
}
use "ActionDispatch::Failsafe"
use "ActionDispatch::ShowExceptions", lambda { ActionController::Base.consider_all_requests_local }
use "ActionDispatch::Rescue", lambda {
controller = (::ApplicationController rescue ActionController::Base)
controller.method(:rescue_action)
}
use lambda { ActionController::Base.session_store },
lambda { ActionController::Base.session_options }

View file

@ -1,185 +0,0 @@
module ActionController #:nodoc:
# Actions that fail to perform as expected throw exceptions. These
# exceptions can either be rescued for the public view (with a nice
# user-friendly explanation) or for the developers view (with tons of
# debugging information). The developers view is already implemented by
# the Action Controller, but the public view should be tailored to your
# specific application.
#
# The default behavior for public exceptions is to render a static html
# file with the name of the error code thrown. If no such file exists, an
# empty response is sent with the correct status code.
#
# You can override what constitutes a local request by overriding the
# <tt>local_request?</tt> method in your own controller. Custom rescue
# behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
# and <tt>rescue_action_locally</tt> methods.
module Rescue
LOCALHOST = '127.0.0.1'.freeze
DEFAULT_RESCUE_RESPONSE = :internal_server_error
DEFAULT_RESCUE_RESPONSES = {
'ActionController::RoutingError' => :not_found,
'ActionController::UnknownAction' => :not_found,
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
}
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
DEFAULT_RESCUE_TEMPLATES = {
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
'ActionController::UnknownAction' => 'unknown_action',
'ActionView::TemplateError' => 'template_error'
}
RESCUES_TEMPLATE_PATH = ActionView::Template::FileSystemPath.new(
File.join(File.dirname(__FILE__), "templates"))
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
base.cattr_accessor :rescue_templates
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
base.extend(ClassMethods)
base.send :include, ActiveSupport::Rescuable
base.class_eval do
alias_method_chain :perform_action, :rescue
end
end
module ClassMethods
def call_with_exception(env, exception) #:nodoc:
request = env["action_controller.rescue.request"] ||= ActionDispatch::Request.new(env)
response = env["action_controller.rescue.response"] ||= ActionDispatch::Response.new
new.process(request, response, :rescue_action, exception).to_a
end
end
protected
# Exception handler called when the performance of an action raises
# an exception.
def rescue_action(exception)
rescue_with_handler(exception) ||
rescue_action_without_handler(exception)
end
# Overwrite to implement custom logging of errors. By default
# logs as fatal.
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") + "\n\n"
)
end
end
end
# Overwrite to implement public exception handling (for requests
# answering false to <tt>local_request?</tt>). By default will call
# render_optional_error_file. Override this method to provide more
# user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
# Attempts to render a static error page based on the
# <tt>status_code</tt> thrown, or just return headers if no such file
# exists. At first, it will try to render a localized static page.
# For example, if a 500 error is being handled Rails and locale is :da,
# it will first attempt to render the file at <tt>public/500.da.html</tt>
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
# the body of the response will be left empty.
def render_optional_error_file(status_code)
status = interpret_status(status_code)
locale_path = "#{Rails.public_path}/#{status[0,3]}.#{I18n.locale}.html" if I18n.locale
path = "#{Rails.public_path}/#{status[0,3]}.html"
if locale_path && File.exist?(locale_path)
render :file => locale_path, :status => status, :content_type => Mime::HTML
elsif File.exist?(path)
render :file => path, :status => status, :content_type => Mime::HTML
else
head status
end
end
# True if the request came from localhost, 127.0.0.1. Override this
# method if you wish to redefine the meaning of a local request to
# include remote IP addresses or other criteria.
def local_request? #:doc:
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
end
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(exception)
@template.instance_variable_set("@exception", exception)
@template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
@template.instance_variable_set("@contents",
@template._render_template(template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
response.status = interpret_status(response_code_for_rescue(exception))
content = @template._render_template(rescues_path("layout"))
render_for_text(content)
end
def rescue_action_without_handler(exception)
log_error(exception) if logger
erase_results if performed?
# Let the exception alter the response if it wants.
# For example, MethodNotAllowed sets the Allow header.
if exception.respond_to?(:handle_response!)
exception.handle_response!(response)
end
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
private
def perform_action_with_rescue #:nodoc:
perform_action_without_rescue
rescue Exception => exception
rescue_action(exception)
end
def rescues_path(template_name)
RESCUES_TEMPLATE_PATH.find_by_parts("rescues/#{template_name}.erb")
end
def template_path_for_local_rescue(exception)
rescues_path(rescue_templates[exception.class.name])
end
def response_code_for_rescue(exception)
rescue_responses[exception.class.name]
end
def clean_backtrace(exception)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) :
exception.backtrace
end
end
end

View file

@ -1,10 +0,0 @@
<h1>
<%=h @exception.class.to_s %>
<% if request.parameters['controller'] %>
in <%=h request.parameters['controller'].humanize %>Controller<% if request.parameters['action'] %>#<%=h request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.clean_message %></pre>
<%= @template._render_template(@rescues_path.find_by_parts("rescues/_trace.erb")) %>
<%= @template._render_template(@rescues_path.find_by_parts("rescues/_request_and_response.erb")) %>

View file

@ -48,6 +48,8 @@ module ActionDispatch
autoload :Failsafe, 'action_dispatch/middleware/failsafe'
autoload :ParamsParser, 'action_dispatch/middleware/params_parser'
autoload :Reloader, 'action_dispatch/middleware/reloader'
autoload :Rescue, 'action_dispatch/middleware/rescue'
autoload :ShowExceptions, 'action_dispatch/middleware/show_exceptions'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Assertions, 'action_dispatch/testing/assertions'

View file

@ -0,0 +1,14 @@
module ActionDispatch
class Rescue
def initialize(app, rescuer)
@app, @rescuer = app, rescuer
end
def call(env)
@app.call(env)
rescue Exception => exception
env['action_dispatch.rescue.exception'] = exception
@rescuer.call(env)
end
end
end

View file

@ -0,0 +1,142 @@
module ActionDispatch
class ShowExceptions
include StatusCodes
LOCALHOST = '127.0.0.1'.freeze
DEFAULT_RESCUE_RESPONSE = :internal_server_error
DEFAULT_RESCUE_RESPONSES = {
'ActionController::RoutingError' => :not_found,
'ActionController::UnknownAction' => :not_found,
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
'ActionController::MethodNotAllowed' => :method_not_allowed,
'ActionController::NotImplemented' => :not_implemented,
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
}
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
DEFAULT_RESCUE_TEMPLATES = {
'ActionView::MissingTemplate' => 'missing_template',
'ActionController::RoutingError' => 'routing_error',
'ActionController::UnknownAction' => 'unknown_action',
'ActionView::TemplateError' => 'template_error'
}
RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
cattr_accessor :rescue_responses
@@rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
@@rescue_responses.update DEFAULT_RESCUE_RESPONSES
cattr_accessor :rescue_templates
@@rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
@@rescue_templates.update DEFAULT_RESCUE_TEMPLATES
def initialize(app, consider_all_requests_local = false)
@app = app
@consider_all_requests_local = consider_all_requests_local
end
def call(env)
@app.call(env)
rescue Exception => exception
log_error(exception) if logger
request = Request.new(env)
if @consider_all_requests_local || local_request?(request)
rescue_action_locally(request, exception)
else
rescue_action_in_public(exception)
end
end
private
# Render detailed diagnostics for unhandled exceptions rescued from
# a controller action.
def rescue_action_locally(request, exception)
template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
:template => template,
:request => request,
:exception => exception
)
file = "rescues/#{@@rescue_templates[exception.class.name]}.erb"
body = template.render(:file => file, :layout => 'rescues/layout.erb')
headers = {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}
status = status_code(exception)
[status, headers, body]
end
# Attempts to render a static error page based on the
# <tt>status_code</tt> thrown, or just return headers if no such file
# exists. At first, it will try to render a localized static page.
# For example, if a 500 error is being handled Rails and locale is :da,
# it will first attempt to render the file at <tt>public/500.da.html</tt>
# then attempt to render <tt>public/500.html</tt>. If none of them exist,
# the body of the response will be left empty.
def rescue_action_in_public(exception)
status = status_code(exception)
locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
path = "#{public_path}/#{status}.html"
if locale_path && File.exist?(locale_path)
render_public_file(status, locale_path)
elsif File.exist?(path)
render_public_file(status, path)
else
[status, {'Content-Type' => 'text/html', 'Content-Length' => '0'}, []]
end
end
# True if the request came from localhost, 127.0.0.1.
def local_request?(request)
request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST
end
def render_public_file(status, path)
body = File.read(path)
[status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, body]
end
def status_code(exception)
interpret_status(@@rescue_responses[exception.class.name]).to_i
end
def public_path
if defined?(Rails)
Rails.public_path
else
"public"
end
end
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
logger.fatal(exception.to_s)
else
logger.fatal(
"\n#{exception.class} (#{exception.message}):\n " +
clean_backtrace(exception).join("\n ") + "\n\n"
)
end
end
end
def clean_backtrace(exception)
defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
Rails.backtrace_cleaner.clean(exception.backtrace) :
exception.backtrace
end
def logger
if defined?(Rails.logger)
Rails.logger
end
end
end
end

View file

@ -6,7 +6,7 @@
<% end %>
<%
clean_params = request.parameters.clone
clean_params = @request.parameters.clone
clean_params.delete("action")
clean_params.delete("controller")
@ -17,8 +17,8 @@
<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p>
<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p>
<div id="session_dump" style="display:none"><%= debug(request.session.instance_variable_get("@data")) %></div>
<div id="session_dump" style="display:none"><%= debug(@request.session.instance_variable_get("@data")) %></div>
<h2 style="margin-top: 30px">Response</h2>
<p><b>Headers</b>: <pre><%=h response ? response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>
<p><b>Headers</b>: <pre><%=h @response ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p>

View file

@ -0,0 +1,10 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.clean_message %></pre>
<%= render :file => "rescues/_trace.erb" %>
<%= render :file => "rescues/_request_and_response.erb" %>

View file

@ -23,7 +23,7 @@
</head>
<body>
<%= @contents %>
<%= yield %>
</body>
</html>
</html>

View file

@ -1,6 +1,6 @@
<h1>
<%=h @exception.original_exception.class.to_s %> in
<%=h request.parameters["controller"].capitalize if request.parameters["controller"]%>#<%=h request.parameters["action"] %>
<%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %>
</h1>
<p>
@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
<%= render :file => @rescues_path["rescues/_trace.erb"] %>
<%= render :file => "rescues/_trace.erb" %>
<% @exception = @real_exception %>
<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
<%= render :file => "rescues/_request_and_response.erb" %>

View file

@ -31,13 +31,7 @@ module ActionDispatch
elsif type.is_a?(Symbol) && @response.response_code == ActionDispatch::StatusCodes::SYMBOL_TO_STATUS_CODE[type]
assert_block("") { true } # to count the assertion
else
if @controller && @response.error?
exception = @controller.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
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
end
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
end
end

View file

@ -489,7 +489,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
get :index
assert_response :success
flunk 'Expected non-success response'
rescue ActiveSupport::TestCase::Assertion => e
rescue RuntimeError => e
assert e.message.include?('FAIL')
end

View file

@ -27,6 +27,7 @@ class EmptyController < ActionController::Base
end
class NonEmptyController < ActionController::Base
def public_action
render :nothing => true
end
hide_action :hidden_action
@ -51,6 +52,7 @@ end
class DefaultUrlOptionsController < ActionController::Base
def default_url_options_action
render :nothing => true
end
def default_url_options(options = nil)
@ -151,11 +153,8 @@ class PerformActionTest < ActionController::TestCase
def test_get_on_hidden_should_fail
use_controller NonEmptyController
get :hidden_action
assert_response 404
get :another_hidden_action
assert_response 404
assert_raise(ActionController::UnknownAction) { get :hidden_action }
assert_raise(ActionController::UnknownAction) { get :another_hidden_action }
end
def test_namespaced_action_should_log_module_name

View file

@ -149,6 +149,9 @@ class PageCachingTest < ActionController::TestCase
end
class ActionCachingTestController < ActionController::Base
rescue_from(Exception) { head 500 }
rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }

View file

@ -15,12 +15,6 @@ class DeprecatedBaseMethodsTest < ActionController::TestCase
tests Target
def test_log_error_silences_deprecation_warnings
get :raises_name_error
rescue => e
assert_not_deprecated { @controller.send :log_error, e }
end
if defined? Test::Unit::Error
def test_assertion_failed_error_silences_deprecation_warnings
get :raises_name_error

View file

@ -45,19 +45,6 @@ class DispatcherTest < Test::Unit::TestCase
def log_failsafe_exception(status, exception); end
end
def test_failsafe_response
Dispatcher.any_instance.expects(:_call).raises('b00m')
ActionDispatch::Failsafe.any_instance.expects(:log_failsafe_exception)
assert_nothing_raised do
assert_equal [
500,
{"Content-Type" => "text/html"},
["<html><body><h1>500 Internal Server Error</h1></body></html>"]
], dispatch
end
end
def test_prepare_callbacks
a = b = c = nil
Dispatcher.to_prepare { |*args| a = b = c = 1 }

View file

@ -169,6 +169,7 @@ class FilterTest < Test::Unit::TestCase
end
def public
render :text => 'ok'
end
end
@ -177,6 +178,10 @@ class FilterTest < Test::Unit::TestCase
before_filter :find_record
before_filter :ensure_login
def index
render :text => 'ok'
end
private
def find_record
@ran_filter ||= []

View file

@ -205,8 +205,7 @@ end
class LayoutExceptionRaised < ActionController::TestCase
def test_exception_raised_when_layout_file_not_found
@controller = SetsNonExistentLayoutFile.new
get :hello
assert_kind_of ActionView::MissingTemplate, @controller.template.instance_eval { @exception }
assert_raise(ActionView::MissingTemplate) { get :hello }
end
end

View file

@ -138,315 +138,6 @@ class RescueController < ActionController::Base
end
end
class RescueControllerTest < ActionController::TestCase
FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures".freeze
def setup
super
set_all_requests_local
populate_exception_object
end
def set_all_requests_local
RescueController.consider_all_requests_local = true
@request.remote_addr = '1.2.3.4'
@request.host = 'example.com'
end
def populate_exception_object
begin
raise 'foo'
rescue => @exception
end
end
def test_rescue_exceptions_raised_by_filters
with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do
get :before_filter_raises
end
end
assert_response :internal_server_error
end
def test_rescue_action_locally_if_all_requests_local
@controller.expects(:local_request?).never
@controller.expects(:rescue_action_locally).with(@exception)
@controller.expects(:rescue_action_in_public).never
with_all_requests_local do
@controller.send :rescue_action, @exception
end
end
def test_rescue_action_locally_if_remote_addr_is_localhost
@controller.expects(:local_request?).returns(true)
@controller.expects(:rescue_action_locally).with(@exception)
@controller.expects(:rescue_action_in_public).never
with_all_requests_local false do
@controller.send :rescue_action, @exception
end
end
def test_rescue_action_in_public_otherwise
@controller.expects(:local_request?).returns(false)
@controller.expects(:rescue_action_locally).never
@controller.expects(:rescue_action_in_public).with(@exception)
with_all_requests_local false do
@controller.send :rescue_action, @exception
end
end
def test_rescue_action_in_public_with_localized_error_file
# Change locale
old_locale = I18n.locale
I18n.locale = :da
with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do
get :raises
end
end
assert_response :internal_server_error
body = File.read("#{FIXTURE_PUBLIC}/public/500.da.html")
assert_equal body, @response.body
ensure
I18n.locale = old_locale
end
def test_rescue_action_in_public_with_error_file
with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do
get :raises
end
end
assert_response :internal_server_error
body = File.read("#{FIXTURE_PUBLIC}/public/500.html")
assert_equal body, @response.body
end
def test_rescue_action_in_public_without_error_file
with_rails_root '/tmp' do
with_all_requests_local false do
get :raises
end
end
assert_response :internal_server_error
assert_equal ' ', @response.body
end
def test_rescue_unknown_action_in_public_with_error_file
with_rails_root FIXTURE_PUBLIC do
with_all_requests_local false do
get :foobar_doesnt_exist
end
end
assert_response :not_found
body = File.read("#{FIXTURE_PUBLIC}/public/404.html")
assert_equal body, @response.body
end
def test_rescue_unknown_action_in_public_without_error_file
with_rails_root '/tmp' do
with_all_requests_local false do
get :foobar_doesnt_exist
end
end
assert_response :not_found
assert_equal ' ', @response.body
end
def test_rescue_missing_template_in_public
with_rails_root FIXTURE_PUBLIC do
with_all_requests_local true do
get :missing_template
end
end
assert_response :internal_server_error
assert @response.body.include?('missing_template'), "Response should include the template name."
end
def test_rescue_action_locally
get :raises
assert_response :internal_server_error
assert_template 'diagnostics.erb'
assert @response.body.include?('RescueController#raises'), "Response should include controller and action."
assert @response.body.include?("don't panic"), "Response should include exception message."
end
def test_local_request_when_remote_addr_is_localhost
@controller.expects(:request).returns(@request).at_least_once
with_remote_addr '127.0.0.1' do
assert @controller.send(:local_request?)
end
end
def test_local_request_when_remote_addr_isnt_locahost
@controller.expects(:request).returns(@request)
with_remote_addr '1.2.3.4' do
assert !@controller.send(:local_request?)
end
end
def test_rescue_responses
responses = ActionController::Base.rescue_responses
assert_equal ActionController::Rescue::DEFAULT_RESCUE_RESPONSE, responses.default
assert_equal ActionController::Rescue::DEFAULT_RESCUE_RESPONSE, responses[Exception.new]
assert_equal :not_found, responses[ActionController::RoutingError.name]
assert_equal :not_found, responses[ActionController::UnknownAction.name]
assert_equal :not_found, responses['ActiveRecord::RecordNotFound']
assert_equal :conflict, responses['ActiveRecord::StaleObjectError']
assert_equal :unprocessable_entity, responses['ActiveRecord::RecordInvalid']
assert_equal :unprocessable_entity, responses['ActiveRecord::RecordNotSaved']
assert_equal :method_not_allowed, responses['ActionController::MethodNotAllowed']
assert_equal :not_implemented, responses['ActionController::NotImplemented']
end
def test_rescue_templates
templates = ActionController::Base.rescue_templates
assert_equal ActionController::Rescue::DEFAULT_RESCUE_TEMPLATE, templates.default
assert_equal ActionController::Rescue::DEFAULT_RESCUE_TEMPLATE, templates[Exception.new]
assert_equal 'missing_template', templates[ActionView::MissingTemplate.name]
assert_equal 'routing_error', templates[ActionController::RoutingError.name]
assert_equal 'unknown_action', templates[ActionController::UnknownAction.name]
assert_equal 'template_error', templates[ActionView::TemplateError.name]
end
def test_not_implemented
with_all_requests_local false do
with_rails_public_path(".") do
head :not_implemented
end
end
assert_response :not_implemented
assert_equal "GET, PUT", @response.headers['Allow']
end
def test_method_not_allowed
with_all_requests_local false do
with_rails_public_path(".") do
get :method_not_allowed
end
end
assert_response :method_not_allowed
assert_equal "GET, HEAD, PUT", @response.headers['Allow']
end
def test_rescue_handler
get :not_authorized
assert_response :forbidden
end
def test_rescue_handler_string
get :not_authorized_raise_as_string
assert_response :forbidden
end
def test_rescue_handler_with_argument
@controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
get :record_invalid
end
def test_rescue_handler_with_argument_as_string
@controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
get :record_invalid_raise_as_string
end
def test_proc_rescue_handler
get :not_allowed
assert_response :forbidden
end
def test_proc_rescue_handler_as_string
get :not_allowed_raise_as_string
assert_response :forbidden
end
def test_proc_rescue_handle_with_argument
get :invalid_request
assert_equal "RescueController::InvalidRequest", @response.body
end
def test_proc_rescue_handle_with_argument_as_string
get :invalid_request_raise_as_string
assert_equal "RescueController::InvalidRequestToRescueAsString", @response.body
end
def test_block_rescue_handler
get :bad_gateway
assert_response 502
end
def test_block_rescue_handler_as_string
get :bad_gateway_raise_as_string
assert_response 502
end
def test_block_rescue_handler_with_argument
get :resource_unavailable
assert_equal "RescueController::ResourceUnavailable", @response.body
end
def test_block_rescue_handler_with_argument_as_string
get :resource_unavailable_raise_as_string
assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
end
protected
def with_all_requests_local(local = true)
old_local, ActionController::Base.consider_all_requests_local =
ActionController::Base.consider_all_requests_local, local
yield
ensure
ActionController::Base.consider_all_requests_local = old_local
end
def with_remote_addr(addr)
old_remote_addr, @request.remote_addr = @request.remote_addr, addr
yield
ensure
@request.remote_addr = old_remote_addr
end
def with_rails_public_path(rails_root)
old_rails = Object.const_get(:Rails) rescue nil
mod = Object.const_set(:Rails, Module.new)
(class << mod; self; end).instance_eval do
define_method(:public_path) { "#{rails_root}/public" }
end
yield
ensure
Object.module_eval { remove_const(:Rails) } if defined?(Rails)
Object.const_set(:Rails, old_rails) if old_rails
end
def with_rails_root(path = nil,&block)
old_rails_root = RAILS_ROOT if defined?(RAILS_ROOT)
if path
silence_warnings { Object.const_set(:RAILS_ROOT, path) }
else
Object.remove_const(:RAILS_ROOT) rescue nil
end
with_rails_public_path(path, &block)
ensure
if old_rails_root
silence_warnings { Object.const_set(:RAILS_ROOT, old_rails_root) }
else
Object.remove_const(:RAILS_ROOT) rescue nil
end
end
end
class ExceptionInheritanceRescueController < ActionController::Base
class ParentException < StandardError
@ -528,6 +219,63 @@ class ApplicationController < ActionController::Base
end
end
class RescueControllerTest < ActionController::TestCase
def test_rescue_handler
get :not_authorized
assert_response :forbidden
end
def test_rescue_handler_string
get :not_authorized_raise_as_string
assert_response :forbidden
end
def test_rescue_handler_with_argument
@controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
get :record_invalid
end
def test_rescue_handler_with_argument_as_string
@controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
get :record_invalid_raise_as_string
end
def test_proc_rescue_handler
get :not_allowed
assert_response :forbidden
end
def test_proc_rescue_handler_as_string
get :not_allowed_raise_as_string
assert_response :forbidden
end
def test_proc_rescue_handle_with_argument
get :invalid_request
assert_equal "RescueController::InvalidRequest", @response.body
end
def test_proc_rescue_handle_with_argument_as_string
get :invalid_request_raise_as_string
assert_equal "RescueController::InvalidRequestToRescueAsString", @response.body
end
def test_block_rescue_handler
get :bad_gateway
assert_response 502
end
def test_block_rescue_handler_as_string
get :bad_gateway_raise_as_string
assert_response 502
end
def test_block_rescue_handler_with_argument
get :resource_unavailable
assert_equal "RescueController::ResourceUnavailable", @response.body
end
def test_block_rescue_handler_with_argument_as_string
get :resource_unavailable_raise_as_string
assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
end
end
class RescueTest < ActionController::IntegrationTest
class TestController < ActionController::Base
class RecordInvalid < StandardError

View file

@ -0,0 +1,103 @@
require 'abstract_unit'
module ActionDispatch
class ShowExceptions
private
def public_path
"#{FIXTURE_LOAD_PATH}/public"
end
end
end
class ShowExceptionsTest < ActionController::IntegrationTest
Boomer = lambda do |env|
req = ActionDispatch::Request.new(env)
case req.path
when "/not_found"
raise ActionController::UnknownAction
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_implemented"
raise ActionController::NotImplemented
when "/unprocessable_entity"
raise ActionController::InvalidAuthenticityToken
else
raise "puke!"
end
end
ProductionApp = ActionDispatch::ShowExceptions.new(Boomer, false)
DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer, true)
test "rescue in public from a remote ip" do
@integration_session = open_session(ProductionApp)
self.remote_addr = '208.77.188.166'
get "/"
assert_response 500
assert_equal "500 error fixture\n", body
get "/not_found"
assert_response 404
assert_equal "404 error fixture\n", body
get "/method_not_allowed"
assert_response 405
assert_equal "", body
end
test "rescue locally from a local request" do
@integration_session = open_session(ProductionApp)
self.remote_addr = '127.0.0.1'
get "/"
assert_response 500
assert_match /puke/, body
get "/not_found"
assert_response 404
assert_match /ActionController::UnknownAction/, body
get "/method_not_allowed"
assert_response 405
assert_match /ActionController::MethodNotAllowed/, body
end
test "localize public rescue message" do
# Change locale
old_locale = I18n.locale
I18n.locale = :da
begin
@integration_session = open_session(ProductionApp)
self.remote_addr = '208.77.188.166'
get "/"
assert_response 500
assert_equal "500 localized error fixture\n", body
get "/not_found"
assert_response 404
assert_equal "404 error fixture\n", body
ensure
I18n.locale = old_locale
end
end
test "always rescue locally in development mode" do
@integration_session = open_session(DevelopmentApp)
self.remote_addr = '208.77.188.166'
get "/"
assert_response 500
assert_match /puke/, body
get "/not_found"
assert_response 404
assert_match /ActionController::UnknownAction/, body
get "/method_not_allowed"
assert_response 405
assert_match /ActionController::MethodNotAllowed/, body
end
end