spec/authority/controller_spec.rb in authority-2.2.0 vs spec/authority/controller_spec.rb in authority-2.3.0

- old
+ new

@@ -1,147 +1,212 @@ require 'spec_helper' -require 'support/example_model' -require 'support/example_controllers' +require 'support/example_classes' require 'support/mock_rails' -require 'support/user' require 'active_support/core_ext/proc' describe Authority::Controller do - describe "the security violation callback" do - - it "should call whatever method on the controller that the configuration specifies" do - # Here be dragons! - @fake_exception = Exception.new - @sample_controller = SampleController.new - # If a callback is passed to a controller's `rescue_from` method as the value for - # the `with` option (like `SomeController.rescue_from FooException, :with => some_callback`), - # Rails will use ActiveSupport's `Proc#bind` to ensure that when the proc refers to - # `self`, it will be the controller, not the proc itself. - # I need this callback's `self` to be the controller for the purposes of - # this test, so I'm stealing that behavior. - @callback = Authority::Controller.security_violation_callback.bind(@sample_controller) - - Authority.configuration.security_violation_handler = :fire_ze_missiles - @sample_controller.should_receive(:fire_ze_missiles).with(@fake_exception) - @callback.call(@fake_exception) - end + class ExampleController + def self.rescue_from(*args) ; end + def self.before_filter(*args) ; end end - describe "when including" do + # Get a fresh descendant class for each test, in case we've modified it + let(:controller_class) { Class.new(ExampleController) } + context "when including" do + before :each do Authority::Controller.stub(:security_violation_callback).and_return(Proc.new {|exception| }) end - it "should specify rescuing security violations with a standard callback" do - SampleController.should_receive(:rescue_from).with(Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback) - SampleController.send(:include, Authority::Controller) + after :each do + controller_class.send(:include, Authority::Controller) end + it "specifies rescuing security violations with a standard callback" do + controller_class.should_receive(:rescue_from).with( + Authority::SecurityViolation, :with => Authority::Controller.security_violation_callback + ) + end + end - describe "after including" do + context "after including" do + let(:controller_class) do + Class.new(ExampleController).tap do |c| + c.send(:include, Authority::Controller) + end + end + + let(:resource_class) { ExampleResource } + + describe "the security violation callback" do + + it "calls whatever method on the controller that the configuration specifies" do + # Here be dragons! + fake_exception = Exception.new + controller_instance = controller_class.new + # If a callback is passed to a controller's `rescue_from` method as the value for + # the `with` option (like `SomeController.rescue_from FooException, :with => some_callback`), + # Rails will use ActiveSupport's `Proc#bind` to ensure that when the proc refers to + # `self`, it will be the controller, not the proc itself. + # I need this callback's `self` to be the controller for the purposes of + # this test, so I'm stealing that behavior. + callback = Authority::Controller.security_violation_callback.bind(controller_instance) + + Authority.configuration.security_violation_handler = :fire_ze_missiles + controller_instance.should_receive(:fire_ze_missiles).with(fake_exception) + callback.call(fake_exception) + end + end + describe "the authority controller action map" do - it "should be created on demand" do - ExampleController.instance_variable_set(:@authority_action_map, nil) - ExampleController.authority_action_map.should be_a(Hash) - ExampleController.authority_action_map.should_not be(Authority.configuration.controller_action_map) + before(:each) { controller_class.instance_variable_set(:@authority_action_map, nil) } + + it "is created on demand" do + expect(controller_class.authority_action_map).to be_a(Hash) end - describe "when subclassing" do - it "should allow the child class to edit the controller action map without affecting the parent class" do - DummyController.authority_action :erase => 'delete' - ExampleController.authority_action_map[:erase].should be_nil - end + it "is created as a copy of the configured controller action map" do + expect(controller_class.authority_action_map).to eq(Authority.configuration.controller_action_map) + expect(controller_class.authority_action_map).not_to be(Authority.configuration.controller_action_map) end + it "is unique per controller" do + child_controller = Class.new(controller_class) + expect(child_controller.authority_action_map).not_to be( + controller_class.authority_action_map + ) + end + end - describe "DSL (class) methods" do - it "should allow specifying the model to protect" do - ExampleController.authorize_actions_for ExampleModel - ExampleController.authority_resource.should eq(ExampleModel) - end + describe "class methods" do - it "should pass the options provided to the before filter that is set up" do - @options = {:only => [:show, :edit, :update]} - ExampleController.should_receive(:before_filter).with(:run_authorization_check, @options) - ExampleController.authorize_actions_for ExampleModel, @options - end + describe "authorize_actions_for" do - it "should allow specifying the authority action map in the `authorize_actions_for` declaration" do - ExampleController.authorize_actions_for ExampleModel, :actions => {:eat => 'delete'} - ExampleController.authority_action_map[:eat].should eq('delete') + it "allows specifying the model to protect" do + controller_class.authorize_actions_for(resource_class) + expect(controller_class.authority_resource).to eq(resource_class) + end + + it "sets up a before_filter, passing the options it was given" do + filter_options = {:only => [:show, :edit, :update]} + controller_class.should_receive(:before_filter).with(:run_authorization_check, filter_options) + controller_class.authorize_actions_for(resource_class, filter_options) + end + + it "passes the action hash to the `authority_action` method" do + child_controller = Class.new(controller_class) + new_actions = {:synthesize => :create, :annihilate => 'delete'} + child_controller.should_receive(:authority_actions).with(new_actions) + child_controller.authorize_actions_for(resource_class, :actions => new_actions) + end + end - it "should have a write into the authority actions map usuable in a DSL format" do - ExampleController.authority_action :smite => 'delete' - ExampleController.authority_action_map[:smite].should eq('delete') + describe "authority_action" do + + it "modifies this controller's authority action map" do + new_actions = {:show => :display, :synthesize => :create, :annihilate => 'delete'} + controller_class.authority_actions(new_actions) + expect(controller_class.authority_action_map).to eq( + Authority.configuration.controller_action_map.merge(new_actions) + ) + end + + it "does not modify any other controller" do + child_controller = Class.new(controller_class) + child_controller.authority_actions(:smite => 'delete') + expect(controller_class.authority_action_map[:smite]).to eq(nil) + end + end + end describe "instance methods" do - before :each do - @user = User.new - @controller = ExampleController.new - @controller.stub!(:action_name).and_return(:edit) - @controller.stub!(Authority.configuration.user_method).and_return(@user) - end - it "should check authorization on the model specified" do - @controller.should_receive(:authorize_action_for).with(ExampleModel) - @controller.send(:run_authorization_check) + let(:controller_class) do + Class.new(ExampleController).tap do |c| + c.send(:include, Authority::Controller) + c.authorize_actions_for(resource_class) + end end - it "should pass the options provided to `authorize_action_for` downstream" do - @controller.stub!(:action_name).and_return(:destroy) - Authority.should_receive(:enforce).with('delete', ExampleModel, @user, :for => 'context') - @controller.send(:authorize_action_for, ExampleModel, :for => 'context') + let(:controller_instance) do + controller_class.new.tap do |cc| + cc.stub(Authority.configuration.user_method).and_return(user) + end end - it "should raise a SecurityViolation if authorization fails" do - expect { @controller.send(:run_authorization_check) }.to raise_error(Authority::SecurityViolation) - end + let(:user) { ExampleUser.new } - it "should raise a MissingAction if there is no corresponding action for the controller" do - @controller.stub(:action_name).and_return('sculpt') - expect { @controller.send(:run_authorization_check) }.to raise_error(Authority::Controller::MissingAction) + describe "run_authorization_check (used as a before_filter)" do + + it "checks authorization on the model specified" do + controller_instance.should_receive(:authorize_action_for).with(resource_class) + controller_instance.send(:run_authorization_check) + end + + it "raises a MissingAction if there is no corresponding action for the controller" do + controller_instance.stub(:action_name).and_return('sculpt') + expect { controller_instance.send(:run_authorization_check) }.to raise_error( + Authority::Controller::MissingAction + ) + end + end - it "should return the authority_user for the current request by using the configured user_method" do - @controller.should_receive(Authority.configuration.user_method) - @controller.send(:authority_user) + describe "authorize_action_for" do + + before(:each) { controller_instance.stub(:action_name).and_return(:destroy) } + + it "calls Authority.enforce to authorize the action" do + Authority.should_receive(:enforce) + controller_instance.send(:authorize_action_for, resource_class) + end + + it "passes along any options it was given" do + options = {:for => 'insolence'} + Authority.should_receive(:enforce).with('delete', resource_class, user, options) + controller_instance.send(:authorize_action_for, resource_class, options) + end + end - describe "in controllers that inherited from a controller including authority, but don't call any class method" do - it "should automatically have a new copy of the authority_action_map" do - @controller = InstanceController.new - @controller.class.authority_action_map.should eq(Authority.configuration.controller_action_map) + describe "authority_user" do + + it "gets the user for the current request from the configured user_method" do + controller_instance.should_receive(Authority.configuration.user_method) + controller_instance.send(:authority_user) end + end describe "authority_forbidden action" do - before :each do - @mock_error = mock(:message => 'oh noes! an error!') - end + let(:mock_error) { mock(:message => 'oh noes! an error!') } - it "should log an error" do + it "logs an error" do Authority.configuration.logger.should_receive(:warn) - @controller.stub(:render) - @controller.send(:authority_forbidden, @mock_error) + controller_instance.stub(:render) + controller_instance.send(:authority_forbidden, mock_error) end - it "should render the public/403.html file" do + it "renders the public/403.html file" do forbidden_page = Rails.root.join('public/403.html') Authority.configuration.logger.stub(:warn) - @controller.should_receive(:render).with(:file => forbidden_page, :status => 403, :layout => false) - @controller.send(:authority_forbidden, @mock_error) + controller_instance.should_receive(:render).with(:file => forbidden_page, :status => 403, :layout => false) + controller_instance.send(:authority_forbidden, mock_error) end + end + end + end end