# frozen_string_literal: true
require "abstract_unit"
class RescueController < ActionController::Base
class NotAuthorized < StandardError
end
class NotAuthorizedToRescueAsString < StandardError
end
class RecordInvalid < StandardError
end
class RecordInvalidToRescueAsString < StandardError
end
class NotAllowed < StandardError
end
class NotAllowedToRescueAsString < StandardError
end
class InvalidRequest < StandardError
end
class InvalidRequestToRescueAsString < StandardError
end
class BadGateway < StandardError
end
class BadGatewayToRescueAsString < StandardError
end
class ResourceUnavailable < StandardError
end
class ResourceUnavailableToRescueAsString < StandardError
end
# We use a fully qualified name in some strings, and a relative constant
# name in some other to test correct handling of both cases.
rescue_from NotAuthorized, with: :deny_access
rescue_from "RescueController::NotAuthorizedToRescueAsString", with: :deny_access
rescue_from RecordInvalid, with: :show_errors
rescue_from "RescueController::RecordInvalidToRescueAsString", with: :show_errors
rescue_from NotAllowed, with: proc { head :forbidden }
rescue_from "RescueController::NotAllowedToRescueAsString", with: proc { head :forbidden }
rescue_from InvalidRequest, with: proc { |exception| render plain: exception.message }
rescue_from "InvalidRequestToRescueAsString", with: proc { |exception| render plain: exception.message }
rescue_from BadGateway do
head 502
end
rescue_from "BadGatewayToRescueAsString" do
head 502
end
rescue_from ResourceUnavailable do |exception|
render plain: exception.message
end
rescue_from "ResourceUnavailableToRescueAsString" do |exception|
render plain: exception.message
end
rescue_from ActionView::TemplateError do
render plain: "action_view templater error"
end
rescue_from IOError do
render plain: "io error"
end
rescue_from ActionDispatch::Http::Parameters::ParseError do
render plain: "parse error", status: :bad_request
end
before_action(only: :before_action_raises) { raise "umm nice" }
def before_action_raises
end
def raises
render plain: "already rendered"
raise "don't panic!"
end
def method_not_allowed
raise ActionController::MethodNotAllowed.new(:get, :head, :put)
end
def not_implemented
raise ActionController::NotImplemented.new(:get, :put)
end
def not_authorized
raise NotAuthorized
end
def not_authorized_raise_as_string
raise NotAuthorizedToRescueAsString
end
def not_allowed
raise NotAllowed
end
def not_allowed_raise_as_string
raise NotAllowedToRescueAsString
end
def invalid_request
raise InvalidRequest
end
def invalid_request_raise_as_string
raise InvalidRequestToRescueAsString
end
def record_invalid
raise RecordInvalid
end
def record_invalid_raise_as_string
raise RecordInvalidToRescueAsString
end
def bad_gateway
raise BadGateway
end
def bad_gateway_raise_as_string
raise BadGatewayToRescueAsString
end
def resource_unavailable
raise ResourceUnavailable
end
def resource_unavailable_raise_as_string
raise ResourceUnavailableToRescueAsString
end
def arbitrary_action
params
render plain: "arbitrary action"
end
def missing_template
end
def exception_with_more_specific_handler_for_wrapper
raise RecordInvalid
rescue
raise NotAuthorized
end
def exception_with_more_specific_handler_for_cause
raise NotAuthorized
rescue
raise RecordInvalid
end
def exception_with_no_handler_for_wrapper
raise RecordInvalid
rescue
raise RangeError
end
private
def deny_access
head :forbidden
end
def show_errors(exception)
head :unprocessable_entity
end
end
class ExceptionInheritanceRescueController < ActionController::Base
class ParentException < StandardError
end
class ChildException < ParentException
end
class GrandchildException < ChildException
end
rescue_from ChildException, with: lambda { head :ok }
rescue_from ParentException, with: lambda { head :created }
rescue_from GrandchildException, with: lambda { head :no_content }
def raise_parent_exception
raise ParentException
end
def raise_child_exception
raise ChildException
end
def raise_grandchild_exception
raise GrandchildException
end
end
class ExceptionInheritanceRescueControllerTest < ActionController::TestCase
def test_bottom_first
get :raise_grandchild_exception
assert_response :no_content
end
def test_inheritance_works
get :raise_child_exception
assert_response :created
end
end
class ControllerInheritanceRescueController < ExceptionInheritanceRescueController
class FirstExceptionInChildController < StandardError
end
class SecondExceptionInChildController < StandardError
end
rescue_from FirstExceptionInChildController, "SecondExceptionInChildController", with: lambda { head :gone }
def raise_first_exception_in_child_controller
raise FirstExceptionInChildController
end
def raise_second_exception_in_child_controller
raise SecondExceptionInChildController
end
end
class ControllerInheritanceRescueControllerTest < ActionController::TestCase
def test_first_exception_in_child_controller
get :raise_first_exception_in_child_controller
assert_response :gone
end
def test_second_exception_in_child_controller
get :raise_second_exception_in_child_controller
assert_response :gone
end
def test_exception_in_parent_controller
get :raise_parent_exception
assert_response :created
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
assert_called_with @controller, :show_errors, [Exception] do
get :record_invalid
end
end
def test_rescue_handler_with_argument_as_string
assert_called_with @controller, :show_errors, [Exception] do
get :record_invalid_raise_as_string
end
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
test "rescue when wrapper has more specific handler than cause" do
get :exception_with_more_specific_handler_for_wrapper
assert_response :forbidden
end
test "rescue when cause has more specific handler than wrapper" do
get :exception_with_more_specific_handler_for_cause
assert_response :unprocessable_entity
end
test "rescue when cause has handler, but wrapper doesnt" do
get :exception_with_no_handler_for_wrapper
assert_response :unprocessable_entity
end
test "can rescue a ParseError" do
capture_log_output do
post :arbitrary_action, body: "{", as: :json
end
assert_response :bad_request
assert_equal "parse error", response.body
end
private
def capture_log_output
output = StringIO.new
request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output)
yield
output.string
end
end
class RescueTest < ActionDispatch::IntegrationTest
class TestController < ActionController::Base
class RecordInvalid < StandardError
def message
"invalid"
end
end
rescue_from RecordInvalid, with: :show_errors
def foo
render plain: "foo"
end
def invalid
raise RecordInvalid
end
def b00m
raise "b00m"
end
private
def show_errors(exception)
render plain: exception.message
end
end
test "normal request" do
with_test_routing do
get "/foo"
assert_equal "foo", response.body
end
end
test "rescue exceptions inside controller" do
with_test_routing do
get "/invalid"
assert_equal "invalid", response.body
end
end
private
def with_test_routing
with_routing do |set|
set.draw do
get "foo", to: ::RescueTest::TestController.action(:foo)
get "invalid", to: ::RescueTest::TestController.action(:invalid)
get "b00m", to: ::RescueTest::TestController.action(:b00m)
end
yield
end
end
end