I am starting a project and i would like to be able to test everything :)
And i have some problems with CanCan and devise.
For exemple, I have a controller Contacts. Everybody can view and everybody (excepts banned people) can create contact.
#app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
load_and_authorize_resource
def index
#contact = Contact.new
end
def create
#contact = Contact.new(params[:contact])
if #contact.save
respond_to do |f|
f.html { redirect_to root_path, :notice => 'Thanks'}
end
else
respond_to do |f|
f.html { render :action => :index }
end
end
end
end
The code work, but I don't how to test the controller.
I tried this. This works if I comment the load_and_authorize_resource line.
#spec/controllers/contacts_controller_spec.rb
require 'spec_helper'
describe ContactsController do
def mock_contact(stubs={})
(#mock_ak_config ||= mock_model(Contact).as_null_object).tap do |contact|
contact.stub(stubs) unless stubs.empty?
end
end
before (:each) do
# #user = Factory.create(:user)
# sign_in #user
# #ability = Ability.new(#user)
#ability = Object.new
#ability.extend(CanCan::Ability)
#controller.stubs(:current_ability).returns(#ability)
end
describe "GET index" do
it "assigns a new contact as #contact" do
#ability.can :read, Contact
Contact.stub(:new) { mock_contact }
get :index
assigns(:contact).should be(mock_contact)
end
end
describe "POST create" do
describe "with valid params" do
it "assigns a newly created contact as #contact" do
#ability.can :create, Contact
Contact.stub(:new).with({'these' => 'params'}) { mock_contact(:save => true) }
post :create, :contact => {'these' => 'params'}
assigns(:contact).should be(mock_contact)
end
it "redirects to the index of contacts" do
#ability.can :create, Contact
Contact.stub(:new) { mock_contact(:save => true) }
post :create, :contact => {}
response.should redirect_to(root_url)
end
end
describe "with invalid params" do
it "assigns a newly created but unsaved contact as #contact" do
#ability.can :create, Contact
Contact.stub(:new).with({'these' => 'params'}) { mock_contact(:save => false) }
post :create, :contact => {'these' => 'params'}
assigns(:contact).should be(mock_contact)
end
it "re-renders the 'new' template" do
#ability.can :create, Contact
Contact.stub(:new) { mock_contact(:save => false) }
post :create, :contact => {}
response.should render_template("index")
end
end
end
end
But these tests totally failed ....
I saw nothing on the web ... :(
So, if you can advise me on the way i have to follow, i would be glad to ear you :)
CanCan does not call Contact.new(params[:contact]). Instead it calls contact.attributes = params[:contact] later after it has applied some initial attributes based on the current ability permissions.
See Issue #176 for details on this and an alternative solution. I plan to get this fixed in CanCan version 1.5 if not sooner.
Related
I'm new with rspec test and maybe there are something that I dont undertand.
if can any help me, I really appreciate some help.
File Structure:
app/models/booking.rb
app/models/user.rb
app/models/role.rb
app/models/ability.rb
app/controllers/bookings_controller.rb
app/views/bookings/index.html.erb
app/views/dashboard/index.html.erb
app/spec/controllers/bookings_controller_spec.rb
I read this link with a similar problem but it isn't solved
Rspec controller error expecting <"index"> but rendering with <"">
is similar, because if I change this line:
it 'should not render index template from bookings' do
get :index
=> response.should_not render_template(:index)
end
for this other:
it 'should not render index template from bookings' do
get :index
=> response.should render_template(:index)
end
I get the same mistake that in the link
expecting <"index"> but rendering with <"">
and I don't know why?
Here's my Code:
My Spec:
describe BookingsController do
context 'as guest' do
before(:each) do
#user = User.new(:email => 'mail_admin#test.com',
:username => 'admin',
:password => 'password_admin',
:password_confirmation => 'password_admin')
#user.save
#when i save, with gem CanCan i assign a default role to #user
#with the default role the user only can see the views/dashboard/index.html.erb
end
it 'should not render index template from bookings' do
get :index
response.should_not render_template(:index)
end
end
end
Controller:
class BookingsController < ApplicationController
load_and_authorize_resource
def index
...
end
def show
...
end
end
My model:
class Booking < Activerecord::Base
paginates_per 20
def
...
end
def
...
end
end
User:
Class User < ActiveRecord::Base
after_save :set_default_role
rolify
.
.
.
.
def set_default_role
self.add_role :default
end
end
Role:
class Role < ActiveRecord::Base
ROLES = {"admin" => "Admin", "default" => "Default"}
.
.
.
.
scopify
end
Ability:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :data_consistency
can :read, Booking
end
end
end
CanCan authorizes model access not controller actions. For most other actions these two are more or less the same thing, but not for the index. On the index action CanCan adds a scope to the query for records that includes your authorization restrictions.
What this means is that your guest user will simply not be able to see any records, but the view will still render.
What you want is authentication (ie Devise) and use it from a before_filter in each controller that requires an authenticated user to access.
class BookingsController < ApplicationController
load_and_authorize_resource # Handles authorization
before_filter !authenticate_user # Handles authentication (included with Devise)
...
end
In my case, the problem was solved in before(:each) block!
My code works like this:
before :each do
#user = User.new(:email => 'mail_admin#test.com',
:username => 'admin',
:password => 'password_admin',
:password_confirmation => 'password_admin')
#user.confirm!
sign_in #user
end
I need some help guys, trying to make this test to pass but with no luck.
describe 'PUT posts/:id' do
describe 'with valid attributes' do
let(:mock_post) { mock_model('Post', title: 'hey! iam a mock!', description: 'a sexy model', location: 'everywhere') }
login_user
it 'should update the object and redirect to the post' do
Post.stub!(:find).with(mock_post.id).and_return(mock_post)
Post.any_instance.should_receive(:update_attributes).with({"these" => "params"}).and_return(true)
response.should redirect_to post_path(mock_post)
put :update, id: mock_post.id, post: { these: 'params' }
end
it 'should have a current_user' do
subject.current_user.should_not be_nil
end
end
For now, I have something like the above test and getting the following error:
1) PostsController PUT posts/:id with valid attributes should update the object and redirect to the post
Failure/Error: response.should redirect_to post_path(mock_post)
Expected response to be a <:redirect>, but was <200>
# ./spec/controllers/posts_controller_spec.rb:200:in `block (4 levels) in <top (required)>'
PostsController:
class PostsController < ApplicationController
load_and_authorize_resource except: [:index, :show]
before_filter :authenticate_user!, except: [:index, :show, :tags]
before_filter :find_post, only: [:show, :edit, :update, :suspend, :suspend_alert]
def update
if #post.update_attributes(params[:post])
flash[:success] = 'Cool.'
redirect_to post_path(#post)
else
render :edit
end
end
protected
def find_post
#post = Post.find(params[:id])
end
end
Also, how should I write the test for the render :edit part?
Your spec never calls the controller action. Try adding:
Post.any_instance.
should_receive(:update_attributes).
with({"these" => "params"})
put :update, :id => "1", :post => {"these" => "params"}
To test the two paths that result from the call to update_attributes, substitute the value in the expectation:
it "should redirect when successful" do
Post.any_instance.
should_receive(:update_attributes).
with({"these" => "params"}).
and_return(true)`
response.should_redirect_to(post_path(#mock_post))
put :update, :id => "1", :post => {"these" => "params"}
end
it "should render the edit page when unsuccessful" do
Post.any_instance.
should_receive(:update_attributes).
with({"these" => "params"}).
and_return(false)`
response.should render_template("edit")
put :update, :id => "1", :post => {"these" => "params"}
end
In my Rails 3 app, I'm getting redirected to login during my signup process. The steps to signup are supposed to be:
User creates User and Profile
Upon saving user, user is logged into the app and redirected to Profiles#edit (/signup/join)
Upon saving profile, user is redirect to Profiles#show (/profiles/:id)
I'm getting redirected to /login after step 1, and I'm seeing a 302 error after the redirect. If I comment out my before_filter :authenticate in profiles_controller.rb and redo the steps above I don't get redirected out of /signup/join but I get the following error:
NoMethodError in ProfilesController#edit
undefined method `profile' for nil:NilClass
I'm pointed to the first line of my Profiles#edit action:
def edit
#profile = user.profile
if #profile.higher_ed?
higher_ed = HigherEd.find_or_create_by_name(:name => #profile.higher_ed)
end
if #profile.employer?
employer = Employer.find_or_create_by_name(:name => #profile.employer)
end
render :layout => "join_form"
end
I've been making an attempt to implement CanCan in my app, so I thought that was the cause. However I commented out my entire ability.rb file and the problem persists. I'd obviously like to figure out how to fix this without commenting out the before_filter. So if anyone has an idea I'd greatly appreciate it. Since I'm dealing with CanCan which depends on a current_user, I'll start with the definition of current_user in my application_controller.rb:
protected
# Returns the currently logged in user or nil if there isn't one
def current_user
return unless session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
#current_user ||= User.find_by_auth_token!(cookies[:auth_token]) if cookies[:auth_token]
end
# Make current_user available in templates as a helper
helper_method :current_user
Here's my users_controller.rb:
class UsersController < ApplicationController
before_filter :authenticate, :only => [:edit, :update, :index]
layout "application"
def new
#user = User.new
#user.profile = Profile.new
if logged_in?
redirect_to current_user.profile
end
end
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to join_path, :notice => 'User successfully added.'
UserMailer.registration_confirmation(#user).deliver
else
render :action => 'new'
end
end
My profiles_controller.rb:
class ProfilesController < ApplicationController
#before_filter :authenticate, :only => [:edit, :update]
helper_method :find_or_create_group
layout "application", :except => [:edit, :show]
def new
#profile = Profile.new(params[:profile])
end
def create
#profile = Profile.new(params[:profile])
if #profile.save
redirect_to #user.profile, :notice => 'User successfully added.'
else
render :new
end
if #profile.higher_ed?
HigherEd.find_or_create_by_name(:name => #profile.higher_ed)
end
if #profile.employer?
Employer.find_or_create_by_name(:name => #profile.employer)
end
if #profile.job_title?
JobTitle.find_or_create_by_name(:name => #profile.job_title)
end
if #profile.high_school?
HighSchool.find_or_create_by_name(:name => #profile.high_school)
end
end
def user
#user = current_user
end
def edit
#profile = user.profile
if #profile.higher_ed?
higher_ed = HigherEd.find_or_create_by_name(:name => #profile.higher_ed)
end
if #profile.employer?
employer = Employer.find_or_create_by_name(:name => #profile.employer)
end
render :layout => "join_form"
end
My sessions_controller.rb:
class SessionsController < ApplicationController
def new
end
def create
if user = User.authenticate(params[:email].downcase, params[:password])
session[:user_id] = user.id
cookies.permanent[:auth_token] = user.auth_token
if user.profile.higher_ed?
redirect_to user.profile, :notice => "Logged in successfully"
else
redirect_to join_path, :notice => "Logged in successfully"
end
else
flash.now[:alert] = "Invalid login/password. Try again!"
render :action => 'new'
end
end
def destroy
reset_session
cookies.delete(:auth_token)
redirect_to root_path, :notice => "You successfully logged out"
end
end
My ability.rb for CanCan:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new guest user
if user.role? :admin
can :manage, :all
else
can :manage, :all
end
end
end
My routes.rb:
match "/signup/join" => "profiles#edit", :as => 'join'
#profile = user.profile
Try changing the above line to
#profile = #current_user.profile
or
#profile = current_user.profile
The issue has nothing to do with cancan, rather it has to do with "user" being nil in your controller.
I got it working by reworking my current_user logic. It's now:
def current_user
#current_user ||= lookup_user
end
def lookup_user
if cookies[:auth_token]
User.find_by_auth_token!(cookies[:auth_token])
elsif session[:user_id]
User.find_by_id(session[:user_id])
end
end
That seems to have done the trick.
I have two models, Post and Comment that have a polymorphic association with another model called Vote.
post.rb and comment.rb have has_many :votes, :as => :votable, :dependent => :destroy
vote.rb has belongs_to :votable, :polymorphic => true
This controller has two actions one to add up votes for Post and the other for Comment:
controllers/votes_controller.rb:
class VotesController < ApplicationController
def vote_up
#post = Post.find(params[:id])
if #post.votes.exists?(:user_id => current_user.id)
#notice = 'You already voted'
else
#vote = #post.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
def vote_up2
#comment = Comment.find(params[:id])
if #comment.votes.exists?(:user_id => current_user.id)
#notice2 = 'You already voted'
else
#vote2 = #comment.votes.create(:user_id => current_user.id, :polarity => 1)
end
respond_to do |format|
format.js
end
end
end
I think that's unnecesary. Is there any way of using a single name to refer to the current votable element or either #post and #comment?
Edit
routes.rb:
get 'votes/:id/vote_up' => 'votes#vote_up', as: 'vote_up'
get 'votes/:id/vote_down' => 'votes#vote_down', as: 'vote_down'
The vote_up action should be implemented in your posts and comments controller respectively. Users are voting on posts or comments, they're not voting on a vote.
I would extract the voting logic and place it in a module that your models will include, then call it on a votable object from the controller.
in your lib directory, create votable.rb
module Votable
def up_vote_from(usr)
place_vote(1, usr.id)
end
def down_vote_from(usr)
place_vote(-1, usr.id)
end
private
def place_vote(direction, usr_id)
v = self.votes.find_or_create_by_user_id(usr_id)
v.update_attribute(:polarity, direction)
end
end
(This revised code will alter a user's original vote if they vote again. Vote methods will return true if the vote saves, false otherwise.)
In each votable model, such as post.rb and comment.rb, add this line to mix in your voting methods:
include Votable
Now, this can be done in a controller:
#post.up_vote_from current_user # => true
As far as implementation is concerned, you will end up with some repetition in your controllers/routes.
In each votable controller, set something up like:
def cast_vote
#post = Post.find params[:id]
if #post.call("#{params[:updown]}_vote_from", current_user)
respond_to do |format|
format.js
end
else
head :not_found
end
end
(this expects .../posts/123/vote/up for an upvote, .../posts/123/vote/down for a downvote.)
then append each resource to include your vote method:
resources :posts do
member do
post 'vote/:updown', :to => "posts#cast_vote", :as => :vote_on
end
end
which can be called in your views with:
<%= button_to "Up", :url => vote_on_post_path(#post, "up"), :remote => true %>
<%= button_to "Down", :url => vote_on_post_path(#post, "down"), :remote => true %>
This is a lot less work than it looks. It'll make sense once you put it in place. It'll make even more sense if you code it in by hand vs. cut and paste. :)
I am having problem with rspec testing controller the devise authentication.
I have a following setup
I have included
config.include Devise::TestHelpers, :type => :controller
in my spec_helper.rb
In my merchants_controller_spec.rb
describe MerchantsController do
before :each do
#user = Factory(:user)
#merchant = Factory(:merchant, :user_id => #user.id,:is_approved => false, :is_blacklisted => false)
controller.stub!(:current_user).and_return(#user)
end
describe "GET index" do
it "assigns all merchants as #merchants" do
merchant = Factory(:merchant,:is_approved => true, :is_blacklisted => false)
get :index
assigns(:merchants).should eq([merchant])
end
end
end
My merchants_controller.rb
class MerchantsController < ApplicationController
before_filter :authenticate_user!
def index
#merchants = Merchant.approved
debugger
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #merchants }
end
end
end
I have a scope approved in merchant model
scope :approved, where(:is_approved => true, :is_blacklisted => false)
Now my problem is even though i stubbed current_user and returned #user as current_user, My merchants_controller index spec is failing. But if i comment out authenticate_user! then the spec passes,
without authenticate_user! the debugger of index action is caught but with authenticate_user! debugger is not caught.
I think there is problem in subbing current_user and i am not able to figure it out.
Help me out..
Have you read through the docs on github?:
Devise includes some tests helpers for functional specs. To use them, you just need to include Devise::TestHelpers in your test class and use the sign_in and sign_out methods. Such methods have the same signature as in controllers:
sign_in :user, #user # sign_in(scope, resource)
sign_in #user # sign_in(resource)
sign_out :user # sign_out(scope)
sign_out #user # sign_out(resource)
Another alternative
RSpec.describe YourController, :type => :controller do
before do
user = FactoryGirl.create(:user)
allow(controller).to receive(:authenticate_user!).and_return(true)
allow(controller).to receive(:current_user).and_return(user)
end
# rest of the code
end