开发者

Staying DRY while testing a controller, authorized via CanCan

I'm retroactively writing some tests, using RSpec, for a Rails project.

I'm using the CanCan gem to provide authorization. I decided to write a spec that will test the ability.rb model. I then went on to test my remaining models.

I've moved on to controllers, and I've run into a huge snag: I'm testing my abilities all over again!

Basically, I have to stub out a series of models, and stub out their associations; otherwise the response just returns 403 Forbidden.

The reason for this, is that the controller is basically in charge of worrying about authorization.

I'm not quite sure where to go from here. I'm stubbing out up to 6 models, just to write a single test. I know the abilities work, that's what ability_spec.rb is for.

So this question is really 2-fold:

  1. Should I be testing the ability model separately?
  2. Should the controller tests be concerned with proper permissions?

Edit require 'spec_helper' include Devise::TestHelpers # to give your spec access to helpers

describe TokensController do
  before(:each) do
    @mock_user = User.new(:username => "bob", :email => "user@user.com", :password => "longpas开发者_如何学运维sword")
    @mock_user.role = "admin"
    sign_in @mock_user
    #Ability.stub!('can').and_return(true)
  end
  it "should let me see grids/:g_id/tokens index" do
    test_grid = mock_model(Grid)
    test_token = mock_model(Token)
    Grid.stub!(:find).and_return(test_grid)
    Token.stub!(:find).and_return(test_token)
    get 'index'

    a1 = Ability.new(@mock_user)
    a1.can?(:index, Token).should be_true # This line works fine; as it should
    puts response.status #This returns 403, which means CanCan::AccessDenied was raised
  end
end

Thanks,

Robbie


Not sure if this is too late for you, but I just ran into the same issue, and solved it using the following code sample --

before do
  @user = Factory.create(:user)
    sign_in @user

    @abilities = Ability.new(@user)
    Ability.stub(:new).and_return(@abilities)
  end
end

I've stubbed out Ability#new, giving me a reference to the instance of Ability that controls the current user. Then, I can stub out specific abilities like this:

@abilities.stub!(:can?).with(:destroy, regatta).and_return(true)

or give admin privileges:

@abilities.stub!(:can?).and_return(false)


I do test the cancan model separately, but testing what it will allow in what conditions.

I think if you are doing things like

authorize! :take_over, @the_world

Then I do think you should be testing that in the controller. I'm not sure you need to test ALL 6 versions of your models though.

You can stub out the Ability.can? class and have it respond true/false, and test how your controller handles when it can (and more importantly) when it cannot continue.


Similar to Sam's answer, but from the CanCan wiki page on testing:

Controller Testing

If you want to test authorization functionality at the controller level one option is to log-in the user who has the appropriate permissions.

user = User.create!(:admin => true) # I recommend a factory for this
# log in user however you like, alternatively stub `current_user` method
session[:user_id] = user.id 
get :index
assert_template :index # render the template since he should have access

Alternatively, if you want to test the controller behavior independently from what is inside the Ability class, it is easy to stub out the ability with any behavior you want.

def setup
  @ability = Object.new
  @ability.extend(CanCan::Ability)
  @controller.stubs(:current_ability).returns(@ability)
end

test "render index if have read ability on project" do
  @ability.can :read, Project
  get :index
  assert_template :index
end

If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the controller layer then it can lead to slow and bloated tests. Instead I recommend keeping controller authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top.


I think authorization needs to be done mainly for controllers to make sure your authorization is working correctly with your controllers. So to make it DRY you can implement your own matcher to be used like this

let!(:user) {create :user}
before { login_user_request user}

it "grants admin access to show action" do
  expect{ get :show, {id: user.id} }.to be_authorized
end
it "denies user access to edit action" do
  expect{ get :edit, {id: user.id} }.to be_un_authorized
end

and then implement these matchers with your own way to test how a request will be authorized or not

RSpec::Matchers.define :be_authorized do
  match do |block|
    block.call
    expect(response).to be_success
  end

  def supports_block_expectations?
    true
  end
end

RSpec::Matchers.define :be_un_authorized do
  match do |block|
    expect{
      block.call
    }.to raise_error(Pundit::NotAuthorizedError)
  end

  def supports_block_expectations?
    true
  end
end


Why don't you include a

can :manage, :all do
  user.is_ultrasuper == 1
end

in your Ability and then have a is_ultrasuper param in one of your fixture users:

one: 
  id: 1 
  username: my_username 
  is_ultrasuper: 1

Then log this user in at setup of your tests. This in tests you should be able to do anything at all.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜