Rspec: testing cancan ability with arguments - ruby-on-rails

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.

Related

CanCan::AccessDenied with factory_girl and cancan, How correctly to write the factory?

I am suffering for the third day, I can not understand why I do not pass the next test:
4) Error:
ArticlesControllerTest#test_should_get_index_if_admin:
CanCan::AccessDenied: You are not authorized to access this page.
test/controllers/articles_controller_test.rb:22:in `block in <class:ArticlesControllerTest>'
What am I doing wrong? help me please!
I have got old application (rails 4.2), with many fixtures data.
I try migrate my test environment from fixtures to factory_girl. So I'm new to this.
Now I'm using:
cancancan + devise
factory_girl + TestCase
My articles controller:
class ArticlesController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!, except: [:show]
def index
#articles = Article.paginate(page: params[:page], per_page: 10).includes(:translations)
end
end
Ability.rb:
Class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
# Everybody
can :show, [Article]
if user.admin?
can :manage, Article
end
end
end
My factory article.rb is very simple:
FactoryGirl.define do
factory :article do
content "MyText"
factory :one_article
factory :two_article
end
end
My factory user.rb is simple too:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "user#{n}#mail.ru" }
password "password"
password_confirmation "password"
after(:create) {|u| u.roles_mask = 4}
profile
factory :valid_admin do
first_name "Administrator"
last_name "Administrator"
association :profile, factory: :admin_profile
after(:create) {|u| u.roles_mask = 2}
end
end
end
My articles controller test:
require 'test_helper'
class ArticlesControllerTest < ActionController::TestCase
include Devise::Test::ControllerHelpers
setup do
#article = create(:one_article)
#admin = create(:valid_admin)
end
test 'should get index if admin' do
sign_in #admin
ability = Ability.new(#admin)
assert ability.can? :index, Article
get :index
assert_response :success
assert_not_nil assigns(:articles)
end
end
Info by pry:
[1] pry(#<ArticlesControllerTest>)> sign_in #admin
=> [[20709], "9BET5RWNuJPrGHUFi86d"]
[2] pry(#<ArticlesControllerTest>)> ability = Ability.new(#admin)
=> #<Ability:0x0000000c3c5ff8
#rules=
[#<CanCan::Rule:0x0000000c3c5f80
#actions=[:show],
#base_behavior=true,
#block=nil,
.............<<Many lines>> ..............
[3] pry(#<ArticlesControllerTest>)> assert ability.can? :index, Article
=> true
[4] pry(#<ArticlesControllerTest>)> get :index
CanCan::AccessDenied: You are not authorized to access this page.
from /home/da/.rvm/gems/ruby-2.2.6#wenya/gems/cancancan-1.16.0/lib/cancan/ability.rb:217:in `authorize!'
Thanks in advance for your help!
This are the devise guideline, you need to create a method to log in the user as admin. This is the login_method you need to create
Controller tests (Test::Unit)
To sign in as admin for a given test case, just do:
class SomeControllerTest < ActionController::TestCase
# For Devise >= 4.1.1
include Devise::Test::ControllerHelpers
# Use the following instead if you are on Devise <= 4.1.0
# include Devise::TestHelpers
def setup
#request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin)
end
end
Note: If you are using the confirmable module, you should set a confirmed_at date inside the Factory or call confirm! before sign_in.
Here is the basics to prepare inside your Factory:
FactoryGirl.define do
factory :account do
email { Faker::Internet.email }
password "password"
password_confirmation "password"
confirmed_at Date.today
end
end

Login as a specific user with RSpec

I am trying to spec the following.
I need to return all entities that are linked to the logged in user. Subsequently I need to create the user before the fact and then ensure that the specific user is logged in. I am struggling to achieve this with controller macros. My specs are failing as follows
1) Yougov::Surveys::ProfilesController GET :index returns all profiles linked to the loged in user with the same country and client as the linked survey
Failure/Error: sign_in user
RuntimeError:
Could not find a valid mapping for nil
# /Users/donovan.thomson/.rvm/gems/ruby-2.2.2#insight-app/gems/devise-2.2.8/lib/devise/mapping.rb:42:in `find_scope!'
# /Users/donovan.thomson/.rvm/gems/ruby-2.2.2#insight-app/gems/devise-2.2.8/lib/devise/test_helpers.rb:46:in `sign_in'
# ./spec/support/controller_macros.rb:17:in `block in login_specific_user'
So a basic scaffolding of my controller looks as follows :
class ProfilesController < ApplicationController
def index
render json: Profile.where(user_id: current_user.id)
end
end
I assume this means the user is not being logged in as I would expect
My spec is as follows
require 'spec_helper'
describe ProfilesController, type: :controller do
before do
#user = FactoryGirl.create :user
#profile = FactoryGirl.create :profile, user: #user
FactoryGirl.create :profile
end
describe "GET :index" do
login_specific_user(#user)
it "returns all profiles linked to the loged in user with the same country and client as the linked survey" do
get :index
expect(response.body).to eq(#profile.to_json)
end
end
end
My controller macro's are as follows:
module ControllerMacros
def login_admin
before :each do
sign_in ControllerMacros.get_user(#request, :admin, :admin_user)
end
end
def login_user
before :each do
sign_in ControllerMacros.get_user(#request, :user)
end
end
def login_specific_user(user)
before :each do
sign_in user
end
end
class << self
def get_user(req, mapping, type=mapping)
req.env["devise.mapping"] = Devise.mappings[mapping]
user = FactoryGirl.create(type)
user.confirm!
user
end
end
end
I solved this by not using controller macros and just adding the following to my before block
before do
#user = FactoryGirl.create :user
#user.confirm!
sign_in #user
end

Cancancan ability debugging fails in rails console and Rspec

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"

Testing CanCanCan ability definition

I'm using CanCanCan with Rolify and I´m trying to test my Ability class authorization.
When testing if a unprivileged user can CRUD other users in the system the test fails
1) Ability a guest user should not be able to manage others
Failure/Error: expect(subject).to_not be_able_to(:crud, User)
expected not to be able to :crud User(...)
But I can't find any reason why the check in my Ability class fails:
class Ability
include CanCan::Ability
def initialize(user = User.new)
alias_action :create, :read, :update, :destroy, :destroy_multiple, to: :crud
# What is wrong?
can :crud, User, id: user.id
if user.has_role?(:admin)
can :manage, User
end
end
end
This is my spec:
require 'rails_helper'
require 'cancan/matchers'
RSpec.describe Ability do
let(:user) { create(:user) }
subject { Ability.new(user) }
context "a guest user" do
it "should be able to manage self" do
expect(subject).to be_able_to(:crud, user)
end
it "should not be able to manage others" do
expect(subject).to_not be_able_to(:crud, User)
end
end
end
expect(subject).to_not be_able_to(:crud, User)
You are referencing User model, not instance there. Use User.new or another persisted User instance.

Mock the ability class

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.

Resources