Here's my very simple ability class:
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :admin
can :manage, :control_panel
end
end
end
How should I mock it in a controller spec?
Here's my control panel controller:
class Admin::ControlPanelController < ApplicationController
authorize_resource class: false
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, danger: "#{exception}"
end
def statistics
end
end
Here's my control_panel controller spec:
describe '#statistics:' do
let(:request){ get :statistics }
context 'When guest;' do
before do
# HOW SHOULD I MOCK HERE?
end
describe 'response' do
subject { response }
its(:status){ should eq 302 }
its(:content_type){ should eq 'text/html' }
it{ should redirect_to root_path }
end
describe 'flash' do
specify { expect( flash[:danger] ).to eq "You do not have sufficient priviledges to access the admin area. Try logging in with an account that has admin priviledges." }
end
end
How should I mock the ability? Before, I was doing this:
let(:user){ FactoryGirl.create :user }
expect(controller).to receive(:current_user).and_return user
expect(user).to receive(:has_role?).with(:admin).and_return false
but that was before I was using cancan and was manually checking that the user had a certain role. This behaviour was happening in the application controller and so was very easy to mock. I'm having difficulty mocking this ability class :(
I want to mock it in different contexts. I'm feeling a bit lost because even if I do this:
expect(Ability).to receive(:asdasdadskjadawd?).at_least(:once)
No error is raised, though one is raised if I do spell 'Ability' wrongly so it's mocking the class ok...
I don't think you should be mocking the Ability class, especially not in a controller test. The Ability class is more like configuration than code; it doesn't change during your application. It's also an implementation detail that the controller shouldn't care about.
Instead, you should be mocking your Users. It looks like you're using FactoryGirl; you could use FactoryGirl's traits to mock the various kinds of user you have:
FactoryGirl.define do
factory :user do
name 'Bob'
email 'bob#example.com
role 'user'
trait :admin do
role 'admin'
end
trait :guest do
role 'guest'
end
end
end
You can then use FactoryGirl.create :user if you need a regular user, and FactoryGirl.create :user, :admin if your test requires an admin.
Related
I have two user roles and i want to write a test in rspec to see if the user role are attached to the user after creation. Also admins should be able to switch the user roles from one to the other.
Here is my user model how i structure the type of users using a enum
enum role: [:batman, :superman]
after_initialize :set_default_role, :if => :new_record?
protected
def set_default_role
self.role ||= :batman
end
I am stuck on the test below and not sure how to go about checking the if the role was successfully attached. Also is there a way to check if the user role can be changed for a user? For example if the user was created with the role of batman, it can be switch to superman?
RSpec.describe User, type: :model do
before do
#user = FactoryBot.create(:user)
end
describe "creation" do
it "can be created" do
expect(#user).to be_valid
end
end
end
You can write expectations for model fields too. Code is pretty self-explanatory:
let(:user){ create(:user) }
it "has role batman" do
expect(user.role).to eq("batman")
end
For changing:
it "changes role" do
expect{
do_something_with(user)
}.to change{ user.reload.role }.from("batman").to("superman")
end
reload might be not needed in model tests, but usually is for other (request/system/etc) where record can change in db but not in exact instance in memory.
I have this in models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role? :registered
can :read Post
end
end
When I do this on rails console
#this returns a user with a role: "registered" attribute
user = User.first
post = Post.first
ability = Ability.new(user)
### This returns false ###
ability.can?(:read, post)
#=> false
This spec I have written to test the ability also fails while i expect it to pass.
describe User, :type => :model do
let(:post) {create(:post)}
describe "abilities" do
subject(:ability){Ability.new(user)}
let(:user){nil}
context "when is a registered user" do
## the default value for the role attribute in the user factory is "registered"
let(:user) {create(:user)}
it {is_expected.to be_able_to :read, post}
end
end
end
I can access and read posts in both /posts and /posts/:id when I am authenticated as a registered user on the browser, I have no idea why it is failing in both rails console and rspec.
Following our discussion, we concluded that the problem is either
Rails didn't load the Ability class, or
A code somewhere somehow overrides the Ability class.
The workaround-solution is to manually load the Ability file by appending the following at the end of the application.rb
require "#{Rails.root}/app/models/ability.rb"
I am using cancan gem.
I have ability defined in my class:
module Gexcore
class Myability
include CanCan::Ability
def initialize(user)
can :delete_user, User do |victim|
..
end
end
end
end
I am testing the ability to delete user with RSpec:
RSpec.describe "Permissions to delete user", :type => :request do
describe 'del user' do
it 'superadmin can delete user' do
superadmin = double(User, :id => 10, <<some other fields>>)
ability = Mymodule::Myability.new(superadmin)
victim = double(User)
res = superadmin_ability.can?(:user_del, victim)
expect(res).to eq true
end
end
end
My user model:
class User < ActiveRecord::Base
delegate :can?, :cannot?, :to => :ability
...
end
But it doesn't see method can? :delete_user, User from my ability.
It expects User class but RSpec calls it with RSpec::Mocks::Double class.
How to test correctly ability from cancan gem using Rspec mocks ?
RSpec is using a Double class because that's where it ends in your code. The class must be User for RSpec to apply the rules.
let(:user) do
u = User.new
u.stub!(:some_qualifier_in_your_delete_user_declaration? => true)
u
end
See this SO archive for more of a reference.
I am trying to test a CanCan ability in my app that also uses Authlogic. I have verified the correct behavior works when using the actual site, but I want to write a functional test that will alert me if this behavior breaks in the future. My ability file is simple, and looks as follows:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :read, User
can :manage, User, :id => user.id
cannot :create, User
can :destroy, UserSession
if user.role? :guest
can :create, UserSession
cannot :destroy UserSession
end
end
end
My test for the UserSessionsController is also simple, and looks like this:
test "should redirect new for member" do
default_user = login :default_user
assert default_user.role? :member
assert_raise(CanCan::AccessDenied) { get :new }
assert_redirected_to root_path
end
Just for reference, my test_helper.rb looks like this:
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'authlogic/test_case'
class ActiveSupport::TestCase
fixtures :all
setup :activate_authlogic
def login(user_login)
UserSession.create users(user_login)
users(user_login)
end
end
When I run my code, my test fails, however:
test_should_redirect_new_for_member FAIL
CanCan::AccessDenied expected but nothing was raised.
Assertion at test/functional/user_sessions_controller_test.rb:13:in `block in <class:UserSessionsControllerTest>'
If I comment out the assert_raise, the redirect assertion also fails. Does anyone see anything wrong with my code that is causing this test to fail?
The problem was that I was rescuing the AccessDenied in my ApplicationController, so the exception was never being raised.
You need to block new action too.
if !(user.role? :member)
can :new, User
end
May be User having 'member' role have access to new action(display form for user) and restricted access to create action.
And one more thing, we don't need to use
cannot [:any_action], [Model]
We can do everything by can itself.
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 = 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