Created
January 17, 2012 16:47
-
-
Save yaronw/1627433 to your computer and use it in GitHub Desktop.
An RSpec Macro for Testing That Controller Actions Perform CanCan Authorization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
=begin | |
# This RSpec Macro is for use with the authorization plugin CanCan. It lets you test that a controller checks for a | |
# specific authorization, and if it fails, that the controller performs a certain action, such as redirecting to a | |
# different page. | |
# Example Usages in specs: | |
it_should_authorize_access_for(:new, Company) | |
it_should_authorize_access_for(:edit, Company.new) { get :edit, id: @company } | |
# For more on using it_should_authorize_access_for, see comment block above the definition of it_should_authorize_access_for | |
# in the code below. | |
# To set up and configure this macro, first add the line | |
config.include(CanCanControllerMacros::Base, :type => :controller) | |
# to the "RSpec.configure do |config|" block. | |
# Then configure with the following code: | |
CanCanControllerMacros.configure do | |
# The following configuration code sets the test for the action that happens when user is unauthorized. | |
# The block given to is what it_should_authorize_access_for tests for to determine that the proper action is taken when a user is unauthorized. | |
# The default is { action_proc.should raise_error CanCan::AccessDenied }, and if that's the desired test, no configuration is needed. | |
# The action_call variable is set up by when_authorized as a lambda that makes the web call. | |
config.when_unauthorized { action_call.call; response.should do_some_custom_thing } | |
end | |
# Examples configure blocks: | |
CanCanControllerMacros.configure do | |
config.when_unauthorized { action_proc.should raise_error SomeExceptionClass } # tests that an exception is raised when unauthorized | |
end | |
CanCanControllerMacros.configure do | |
config.when_unauthorized { action_proc.call; response.should redirect_to unauthorized_url } # tests that user is redirected when unauthorized | |
end | |
=end | |
require 'active_support/concern' | |
module CanCanControllerMacros | |
# An object that holds configuration vars for CanCanControllerMacros | |
class Configuration | |
# Sets and retrieves the action that happens when user is unauthorized. | |
def when_unauthorized(&block) | |
if block_given? # set the configuration | |
@when_unauthorized = block | |
else # if no block is given, retrieve the configuration | |
@when_unauthorized || (Proc.new { action_proc.should raise_error CanCan::AccessDenied }) # the right side expression is the default | |
end | |
end | |
end | |
@@config = CanCanControllerMacros::Configuration.new | |
mattr_reader :config | |
# Gives a block configuration style like that of Rails. | |
# See https://github.com/yaronw/method_cacher/blob/master/lib/method_cacher/base.rb | |
def self.configure(&block) | |
if block_given? | |
if block.arity == 1 | |
yield config | |
else | |
instance_eval &block | |
end | |
end | |
end | |
module Base | |
extend ActiveSupport::Concern | |
module ClassMethods | |
# An RSpec macro that checks a request fails if user is not authorized through CanCan for a given action/object combination. | |
# Use this macro to test that a controller relies on CanCan for authorizing a specific action/object combination, | |
# and then use unit tests with the CanCan Ability class to test the conditions that authorize the combination. | |
# | |
# @param auth_action [symbol]- The action being authorized. | |
# @param auth_obj - [Class, Object] - The class or object being authorized. | |
# @param block - A block that makes the request to the controller's action. | |
# If omitted, the action defaults to "get auth_action". | |
# | |
# Example Usages in specs: | |
# it_should_authorize_access_for(:new, Company) | |
# it_should_authorize_access_for(:edit, Company.new) { get :edit, id: @company } | |
def it_should_authorize_access_for(auth_action, auth_obj, &block) | |
obj_name = auth_obj.is_a?(Class) ? auth_obj.name + " class" : auth_obj.class.name + " instance" | |
it "should block unauthorized access if user is unauthorized for #{auth_action.inspect} action on #{obj_name}" do | |
# stub an ability that authorizes everything except the action on the object/class being tested for | |
ability = stub_ability | |
ability.can(:manage, :all) | |
if auth_obj.is_a? Class | |
# If auth_obj is a class, set ability to unauthorize everything but the specified action on every object in the class. | |
ability.cannot(auth_action, auth_obj) | |
else | |
# If auth_obj is an instance object, | |
# the following will ensure that there's authorization for the class but not an instance object of that class. | |
# This enables us to ensure that the action specifically calls, for example, "authorize! :edit, @company" and not just | |
# "authorize! :edit, Company" | |
ability.cannot(auth_action, auth_obj.class) | |
ability.can(auth_action, auth_obj.class) { false } | |
end | |
# creates the procedure that makes a call to the action | |
self.action_proc = lambda do | |
if block_given? | |
instance_eval &block # evaluates the block in the instance context and not in the context in which it was defined | |
else | |
get auth_action | |
end | |
end | |
instance_eval &CanCanControllerMacros.config.when_unauthorized # check for desired response when unauthorized | |
end | |
end | |
end | |
module InstanceMethods | |
mattr_accessor :action_proc # stores the proc that calls the action | |
def stub_ability | |
ability = Object.new | |
ability.extend(CanCan::Ability) | |
@controller.stub(:current_ability) { ability } | |
ability | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment