# 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 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 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 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) end yield end end end