require 'spec_helper' require 'action_controller/test_process' require 'action_controller/integration' require 'application' context "Exceptional::Handles" do include ActionController::TestProcess include SpecHelper setup do ActionController::Base.clear_last_instantiation! if ActionController::Base.respond_to? :clear_last_instantiation! end specify "should call handler when error is raised" do controller do handles RuntimeError, :with => :handler raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:handler) get :foo end specify "should not call handlers when no error is raised" do controller do handles RuntimeError, :with => :handler def foo ; render :text => ''; end def handler ; end end @controller.should_not_receive(:handler) get :foo end specify "should not call handler if called action is in except list" do controller do handles RuntimeError, :with => :handler, :except => :foo raiser :foo, RuntimeError def handler ; end end @controller.should_not_receive(:handler) lambda{get :foo}.should_raise RuntimeError end specify "should call handler if called action is not in except list" do controller do handles RuntimeError, :with => :handler, :except => :bar raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:handler).once get :foo end specify "should not call handler for each action in except list" do controller do handles RuntimeError, :with => :handler, :except => [:foo, :bar] raiser :foo, RuntimeError raiser :bar, RuntimeError def handler ; end end @controller.should_not_receive(:handler) lambda do get :foo get :bar end.should_raise RuntimeError end specify "should not call handler if called action is not in only list" do controller do handles RuntimeError, :with => :handler, :only => :bar raiser :foo, RuntimeError def handler ; end end @controller.should_not_receive(:handler) lambda {get :foo}.should_raise RuntimeError end specify "should call handler if called action is in only list" do controller do handles RuntimeError, :with => :handler, :only => :foo raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:handler).once get :foo end specify "should call handler for each action in only list" do controller do handles RuntimeError, :with => :handler, :only => [:foo, :bar] raiser :foo, RuntimeError raiser :bar, RuntimeError def handler ; end end @controller.should_receive(:handler).twice get :foo get :bar end specify "should call multiple handlers if provided" do controller do handles RuntimeError, :with => :handler_1 handles RuntimeError, :with => :handler_2 raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end @controller.should_receive(:handler_1).once @controller.should_receive(:handler_2).once get :foo end specify "should call handler if raised error is sub-class of handled error" do klass = Class.new(RuntimeError) controller do handles RuntimeError, :with => :handler raiser :foo, klass def handler ; end end @controller.should_receive(:handler) get :foo end specify "should accept single symbols as except and only clauses" do controller do handles RuntimeError, :with => :handler, :except => :foo handles RuntimeError, :with => :handler, :only => :foo raiser :foo, RuntimeError raiser :bar, RuntimeError def handler ; end end @controller.should_receive(:handler).twice get :foo get :bar end specify "should accept arrays of symbols as except and only clauses" do controller do handles RuntimeError, :with => :handler, :except => [:foo] handles RuntimeError, :with => :handler, :only => [:foo] raiser :foo, RuntimeError raiser :bar, RuntimeError def handler ; end end @controller.should_receive(:handler).twice get :foo get :bar end specify "should pass in exception if handler accepts a parameter " do controller do handles RuntimeError, :with => :handler raiser :foo, RuntimeError def handler(e) e.should_be_instance_of RuntimeError end end get :foo end specify "should not pass in exception if handler does not accept parameter " do controller do attr_accessor :handler_called handles RuntimeError, :with => :handler raiser :foo, RuntimeError def handler self.handler_called = true end end get :foo @controller.handler_called.should == true end specify "should handle routing error with application controller" do session = ActionController::Integration::Session.new ApplicationController.class_eval do handles ::ActionController::RoutingError, :with => :handler def handler render :text => 'blah' end end session.get 'some_bad_url' session.response.response_code.should == 200 session.response.body.should == 'blah' end specify "should handle unknown action with application controller" do session = ActionController::Integration::Session.new ApplicationController.class_eval do handles ::ActionController::UnknownAction, :with => :handler def handler render :text => 'blah' end end SampleController.class_eval { alias old_method_missing method_missing; undef method_missing } session.get 'sample/bad_action/3' session.response.response_code.should == 200 session.response.body.should == 'blah' SampleController.class_eval { alias method_missing old_method_missing; undef old_method_missing } end specify "should set state on raises error if it responds to set_state" do sample_error = Class.new(Exceptional::Base) controller do handles sample_error, :with => :handler define_method :foo do mock_error = sample_error.new mock_error.should_receive(:set_state).with(self) raise mock_error end def handler ; end end get :foo end specify "should call :unhandled handlers if an error is not caught by another handler" do controller do handles :unhandled, :with => :handler raiser :foo, RuntimeError def handler end end @controller.should_receive(:handler).once get :foo end specify "should call :unhandled handlers if an error is not caught by another handler (excluded from catch because of only clause)" do controller do handles :unhandled, :with => :handler_1 handles RuntimeError, :with => :handler_2, :only => :bar raiser :foo, RuntimeError raiser :bar, RuntimeError def handler_1 ; end def handler_2 ; end end @controller.should_receive(:handler_1).once @controller.should_not_receive(:handler_2) get :foo end specify "should not call :unhandled handlers if another handler caught the error" do controller do handles :unhandled, :with => :handler_1 handles RuntimeError, :with => :handler_2 raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end @controller.should_not_receive(:handler_1) @controller.should_receive(:handler_2).once get :foo end specify "should call handler for public requests :when => :public" do controller do handles RuntimeError, :with => :handler, :when => :public raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:local_request?).and_return(false) @controller.should_receive(:handler) get :foo end specify "should not call handler for local requests :when => :public" do controller do handles RuntimeError, :with => :handler, :when => :public raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:local_request?).and_return(true) @controller.should_not_receive(:handler) lambda{get :foo}.should_raise(RuntimeError) end specify "should call handler for local requests :when => :local" do controller do handles RuntimeError, :with => :handler, :when => :local raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:local_request?).and_return(true) @controller.should_receive(:handler) get :foo end specify "should not call handler for public requests :when => :local" do controller do handles RuntimeError, :with => :handler, :when => :local raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:local_request?).and_return(false) @controller.should_not_receive(:handler) lambda{get :foo}.should_raise(RuntimeError) end specify "should fall back to normal rescue action if handler throws an error" do error_1 = Class.new(RuntimeError) error_2 = Class.new(RuntimeError) controller do handles RuntimeError, :with => :handler raiser :foo, error_1 define_method :handler do mock_error = error_2.new self.should_receive(:log_error).with(mock_error) raise mock_error end end @controller.logger.should_receive(:fatal).with("=== EXCEPTION THROWN WITHIN EXCEPTIONAL HANDLER ===") @controller.logger.should_receive(:fatal).with("===================================================") lambda{get :foo}.should_raise(error_1) end specify "should always call any :all handlers" do controller do handles :all, :with => :handler_2 handles RuntimeError, :with => :handler handles ArgumentError, :with => :handler raiser :foo, RuntimeError raiser :bar, ArgumentError def handler ; end def handler_2 ; end end @controller.should_receive(:handler_2).twice get :foo get :bar end specify "should rethrow any errors caught only by an :all handler" do controller do handles :all, :with => :handler raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:handler).once lambda{get :foo}.should_raise(RuntimeError) end specify "should call :unhandled handler even if an :all handler is defined" do controller do handles :all, :with => :handler_1 handles :unhandled, :with => :handler_2 raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end @controller.should_receive(:handler_1).once @controller.should_receive(:handler_2).once get :foo end specify "should call :all handlers after normal handlers" do controller do handles :all, :with => :handler_1 handles RuntimeError, :with => :handler_2 raiser :foo, RuntimeError def handler_1 @handler_2_called.should == true end def handler_2 @handler_2_called = true end end get :foo end specify "should call :all handlers before :unhandled handlers" do controller do handles :all, :with => :handler_1 handles :unhandled, :with => :handler_2 raiser :foo, RuntimeError def handler_1 @handler_1_called = true end def handler_2 @handler_1_called.should == true end end get :foo end specify "should not rethrow error if an :all handler is :set_handled => true" do controller do handles :all, :with => :handler, :set_handled => true raiser :foo, RuntimeError def handler ; end end @controller.should_receive(:handler).once lambda{get :foo}.should_not_raise(RuntimeError) end specify "should not call :unhandled errors if :all handler is :set_handled => true" do controller do handles :all, :with => :handler_1, :set_handled => true handles :unhandled, :with => :handler_2 raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end @controller.should_receive(:handler_1).once @controller.should_not_receive(:handler_2) get :foo end end context "Handles directive should log a warning if :all handler with :set_handled => true and an :unhandled handler overlaps " do include ActionController::TestProcess include SpecHelper setup do ActionController::Base.clear_last_instantiation! if ActionController::Base.respond_to? :clear_last_instantiation! end setup do @options = [ [{}, {}], [{}, {:except => :foo}], [{}, {:only => :foo}], [{:only => :foo}, {}], [{:except => :foo}, {}], [{:only => :foo}, {:only => :foo}], [{:only => :foo}, {:except => :bar}], ] end specify "(:unhandled declared last)" do setup_warning_checker(true, @options) end specify "(:unhandled declared first)" do setup_warning_checker(false, @options) end def setup_warning_checker(all_first, options) controller do logger.should_receive(:warn).with("Overlapping :all and :unhandled handlers, your :unhandled handlers may not get run").exactly(options.length).times end options.each do |ah_options, oh_options| controller do if all_first handles :all, ah_options.reverse_merge(:with => :handler_1, :set_handled => true) handles :unhandled, oh_options.reverse_merge(:with => :handler_2) else handles :unhandled, oh_options.reverse_merge(:with => :handler_2) handles :all, ah_options.reverse_merge(:with => :handler_1, :set_handled => true) end raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end end end end context "Handles directive should not log a warning if :all handler with :set_handled => true and an :unhandled handler does not overlaps " do include ActionController::TestProcess include SpecHelper setup do ActionController::Base.clear_last_instantiation! if ActionController::Base.respond_to? :clear_last_instantiation! end setup do @options = [ [{:only => :foo}, {:except => :foo}], [{:only => :foo}, {:only => :bar}], ] end specify "(:unhandled declared last)" do setup_warning_checker(true, @options) end specify "(:unhandled declared first)" do setup_warning_checker(false, @options) end def setup_warning_checker(all_first, options) controller do logger.should_not_receive(:warn) end options.each do |ah_options, oh_options| controller do if all_first handles :all, ah_options.reverse_merge(:with => :handler_1, :set_handled => true) handles :unhandled, oh_options.reverse_merge(:with => :handler_2) else handles :unhandled, oh_options.reverse_merge(:with => :handler_2) handles :all, ah_options.reverse_merge(:with => :handler_1, :set_handled => true) end raiser :foo, RuntimeError def handler_1 ; end def handler_2 ; end end end end end