RSpec: testing cancan with home-spun authentication
This is a simple question, it's just that I'm still wrapping my head around RSpec's syntax & methodology...so I'm a bit confused. Please bear with me..I'm wondering how to approach testing controllers (and requests, etc.) when Cancan is involved and users can have different levels of authorization.
I've read that one advantage to Cancan is that it keeps all Auth in one model...great! but it seems to have confused testing for me...so I'm looking for advice.
Background: I'm using a bitmask to hold the authorization level for the user (a la Railscast #189). So the integer column roles_mask
will hold all the auth for a user. Not sure this matters but now you know.
I'd like to have some tests as a guest user that should fail if guest tries to create a Post. But then in same posts_controller_spec
I'd like to test that an admin
and moderator
can create the same post. Just not sure how best to set this up.
So, should I have a factory for a guest user, one for an admin, etc... Moving @user.roles_mask开发者_Python百科 = 1
to a describe block before specs I want for an "admin" user doesn't work. No method errors. But, if I assign the roles_mask in the Factory it does work?!? So how can I test for varying levels of roles_mask? (nil, 0, 1, 2, 4, etc?)
describe PostsController do
before(:each) do
@user = Factory(:user)
session[:user_id] = @user.id
@attr = Factory.attributes_for(:post)
end
describe "GET index" do
it "assigns all posts as @posts" do
post = @user.posts.create(@attr)
get :index
assigns(:posts).should eq([post])
end
end
...
describe "POST create" do
describe "with valid params" do
it "creates a new Post" do
expect {
post :create, :post => @attr
}.to change(Post, :count).by(1)
end
...
end
# posts_controller.rb
class PostsController < ApplicationController
before_filter :login_required, :except => [:index, :show]
load_and_authorize_resource
...
end
Factory:
Factory.define :user do |user|
user.sequence(:email) { |n| "foo#{n}@dummycorp.com" }
user.password "foobar"
user.password_confirmation { |u| u.password }
user.firstname "foo"
user.roles_mask 1 # < --- remove & tests fail
# < --- how to manipulate dynamically in tests
end
I think you are confused (as I was) by the fact that the controller is protecting access to resources - but it's still the ability class that determines whether the controller's actions can be carried out on a particular resource. Whether you access the resources through the PostsController or some other controller, should really make no difference - you still don't want guests making posts. If you really want to be certain that load_and_authorize_resource is being called in the PostsController you could set up a single functional test for that (guest create post should fail) - then the ability tests should confirm the detail.
require "cancan/matchers"
describe Ability do
before(:each) do
end
[Contact, Question, Provider, Organisation].each do |model|
it "should allow any user to read a #{model.to_s} details but not delete them" do
user= Factory(:user)
ability = Ability.new(user)
ability.should be_able_to(:show, model.new)
ability.should_not be_able_to(:delete, Factory(model.to_s.underscore.to_sym))
end
end
[Contact, Organisation].each do |model|
it "should allow any admin user to delete a #{model.to_s} " do
user= Factory(:admin)
ability = Ability.new(user)
ability.should be_able_to(:delete, Factory.build(model.to_s.underscore.to_sym))
end
end
etc
In my case I have admin users and normal users with different abilities - I just created one of each in turn, and called the relevant methods on the models I wanted to restrict access to. I didn't actually test the controller much at all because load_and_authorize_resource is called in my case in the application_controller and so a single test makes sure it applies throughout the app
精彩评论