Capybara / Rspec Controller testing with has_secure_password - ruby-on-rails

I'm trying to test my controllers using Capybara. I have tried numerous ways, but for some reason I can not get it to see the current_user session. I know the login works when I run my spec for it. I'm also visiting the login page before my controller test. But it always seems to redirect me back when the :logged_in function is hit.
So not sure what I'm missing?
Here's what I have..
session_controller
def create
user = User.find_by_username(params[:username])
if( user && user.authenticate(params[:password]))
user.update_attribute(:token, User.token_digest)
flash[:notice] = "Success"
session[:user_id] = user.id
session[:token] = user.token
redirect_to dashboard_index_path
else
flash[:notice] = "Failed"
flash.now.alert = "Invalid user name or password"
render "new"
end
end
application_controller
protect_from_forgery
before_filter :logged_in
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def logged_in
if !current_user
redirect_to root_url
return
end
if session[:token] != current_user.token
redirect_to root_url
end
end
products_controller_spec
it 'Should display new product form' do
user_login
visit new_product_path
response.body.should have_content("<h1>Create New Product</h1>")
end
spec_helper.rb
def user_login
visit root_path #this is new_session
fill_in "username", :with => "admin"
fill_in "password", :with => "111111"
click_button "Login"
end

Well I got it working,Not sure its politically correct way but.. instead of visiting the page, I'm just hard setting the session. In spec_helper.rb..
def user_login
user = FactoryGirl.create(:user)
session[:user_id] = user.id
session[:token] = User.token_digest
end

Related

Rails Tutorial Chapter 10 (friendly forwarding) test error, Expected response to be a <3XX: redirect>, but was a <200: OK>

Well, im new to coding with Rails and Michael Hartl tutorial is awesome, and till now i've benn able to find my coding errors... right now im in chapter 10 "Friendly Forwarding" and whilst performing the test for succesfull edit i stumbled on an error i cant see through.
test_successful_edit_with_friendly_forwarding#UserEditTest (1.07s)
Expected response to be a <3XX: redirect>, but was a <200: OK>
test/integration/user_edit_test.rb:32:in `block in '
my user_edit_test is:
require 'test_helper'
class UserEditTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "unsuccessful edit" do
log_in_as(#user)
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user: { name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar" } }
assert_template 'users/edit'
end
test "successful edit with friendly forwarding" do
get edit_user_path(#user)
log_in_as(#user)
assert_redirected_to edit_user_url(#user)
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), params: { user: { name: name,
email: email,
password: "",
password_confirmation: "" } }
assert_not flash.empty?
assert_redirected_to #user <--- this is line 32 from the test
#user.reload
assert_equal name, #user.name
assert_equal email, #user.email
end
end
My users controller is:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params) # Not the final implementation!
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App"
redirect_to #user
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
# Handle a successful update.
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
#Before Filters
#Confirms a logged-in user
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
My sessions.Helper is:
module SessionsHelper
#logs in the given user
def log_in(user)
session[:user_id] = user.id
end
# Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
end
end
#returns true if the current user is the given user
def current_user?(user)
user == current_user
end
# Returns true if the user is logged in, false otherwise.
def logged_in?
!current_user.nil?
end
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out current user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
# Redirects to stored location (or to the default).
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# Stores the URL trying to be accessed.
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
end
And my Sessions Controller is:
class SessionsController < ApplicationController
def new
end
def create
#user = User.find_by(email: params[:session][:email].downcase)
if #user && #user.authenticate(params[:session][:password])
log_in #user
params[:session][:remember_me] == '1' ? remember(#user) : forget(#user)
redirect_back_or #user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
From what i've seen in other questions i understand the problem but can't seem to find a way to fix it, i feel is right there in my face but dont see it.
FWIW, your friendly forwarding is working correctly and is tested in the first 3 lines of your test case. The assert_redirected_to #user test corresponds to your UsersController#update, more specifically the redirect_to #user statement in the if branch. What happens if you log into the site and update a user's profile? Check the logs at that point too.

Hartl Rails Tutorial Section 9.2.2 cookie not created

I am working through the Hartl rails tutorial, within Chapter 9 I am trying to replicate the tests for restricting the edit action to the current user:
require 'spec_helper'
describe "Authentication" do
subject { page }
....
....
....
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:wrong_user) { FactoryGirl.create(:user, email: "wrong#example.com") }
before { sign_in user, no_capybara: true }
describe "submitting a GET request to the Users#edit action" do
before { get edit_user_path(wrong_user) }
specify { expect(response.body).not_to match(full_title('Edit user')) }
specify { expect(response).to redirect_to(root_url) }
end
describe "submitting a PATCH request to the Users#update action" do
before { patch user_path(wrong_user) }
specify { expect(response).to redirect_to(root_url) }
end
end
end
I have then implemented the code as described within the book, but the tests still fail:
Failures:
1) Authentication as wrong user submitting a PATCH request to the Users#update action
Failure/Error: specify { expect(response).to redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>.
Expected "http://www.example.com/" to be === "http://www.example.com/signin".
# ./spec/requests/authentication_pages_spec.rb:100:in `block (4 levels) in <top (required)>'
I have investigated by adding some logging to the sign_in method called within the test:
def sign_in(user, options={})
Rails.logger.debug "sign_in "+"*"*15
if options[:no_capybara]
# Sign in when not using Capybara.
remember_token = User.new_remember_token
cookies[:remember_token] = remember_token
Rails.logger.debug "Created cookie rt: "+ cookies[:remember_token].to_s
user.update_attribute(:remember_token, User.encrypt(remember_token))
else
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
end
end
Within the log the create cookie remember_token is reported as blank. If I change the sign in to sign_in user (without the no capybara option) I can see that the user is signed in and the cookie populated, but when I visit the edit action the cookie is no longer populated.
How can I investigate further as to why the cookie is being destroyed?
Thanks...
For information I've included the application code below:
content of users_controller.rb
class UsersController < ApplicationController
before_action :signed_in_user, only: [:edit, :update]
before_action :correct_user, only: [:edit, :update]
def new
#user = User.new
end
def show
#user = User.find(params[:id])
logger.debug "User show: "+ #user.to_yaml
logger.debug "cookie on user show: "+ cookies[:remember_token].to_yaml
end
def create
#user = User.new(user_params)
if #user.save
sign_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user
redirect_to #user
else
render 'edit'
end
end
def edit
logger.debug "Edit user method"
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
def signed_in_user
si = signed_in?
logger.debug "*"*200
logger.debug "signed in user: signed in?: "+si.to_s
redirect_to signin_url, notice: "Please sign in." unless si
end
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Content of sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:email].downcase)
if user && user.authenticate(params[:password])
sign_in user
redirect_to user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
end
Content of sessions_helper.rb
module SessionsHelper
def sign_in(user)
logger.debug "Sessions Helper sign in Method"
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
s_in = !current_user.nil?
logger.debug "signed_in?: "+s_in.to_s
s_in
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def current_user=(user)
#current_user = user
end
def current_user
logger.debug "getting current user"
logger.debug "cookie: "+ cookies[:remember_token].to_s
remember_token = User.encrypt(cookies[:remember_token])
logger.debug "rt: "+ remember_token.to_yaml
logger.debug "current_user before: "+ #current_user.to_yaml
#current_user ||= User.find_by(remember_token: remember_token)
logger.debug "current_user after: "+ #current_user.to_yaml
#current_user
end
def current_user?
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
end
There is nothing wrong with your authentication test
Its the code in users_controller.rb file
please remove the indicated(<---) line from your update method from users_controller.rb file.
It is clear from the error itself that it is expected to be at root_ulr but landing on sign_in_url.
This is happening because you have added one line "sign_in #user" to your users_controller.rb file in update method.so as per you code it is redirecting to "sign_in_url" which is unnecessary as the user have already logged in and updated his profile.I hope this will be helpful and resolve your issue.
update method in users_controller.rb file
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
sign_in #user `<----------- Remove this line
redirect_to #user
else
render 'edit'
end
end

RSpec vs. Cookies - The test fails even if the applications works (simulating logged user)

I'm reading the Michael Hartl's "Ruby on Rails "Turorial" but I'm stuck here.
I wrote the tests to check user authorization on user edit/update, I've also wrote the controller code and it works:
If user is not logged and try to access users#edit or users#update it's redirected to the login page
If the user is logged and he try to edit another user is redirected to the root
The problem is in the spec that tests the point 2, I check that sending a PUT request to the user_path I'm redirected to the root but running rspec I get the following error:
Failures:
1) Authentication authorization as wrong user submitting a PUT request to users#update
Failure/Error: specify { response.should redirect_to(root_url) }
Expected response to be a redirect to <http://www.example.com/> but was a redirect to <http://www.example.com/signin>
# ./spec/requests/authentication_pages_spec.rb:73:in `block (5 levels) in <top (required)>'
It seems it's redirected to the signin_url instead of the root_url, like if the user is not logged at all...and this is what I guess cause the problem.
The application store a token in the cookies to authenticate the user.
To test this I wrote an helper method called signin_user(user) and put it in the spec/support/utilities.rb file:
# spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Make signin work even when not using capybara (e.g. when using get, or post)
cookies[:remember_token] = user.remember_token
end
As you can see the method set the remember_token cookie to make sure the user is logged even when using put, delete, etc...or at least it should work!
How can I make this spec pass as it should?
# spec/requests/authentication_pages_spec.rb
require 'spec_helper'
describe "Authentication" do
subject { page }
# ...
describe "authorization" do
# ...
describe "as wrong user" do
let(:user) { FactoryGirl.create(:user) }
let(:another_user) { FactoryGirl.create(:user, email: "another#example.com") }
before { sign_in(user) }
# ...
describe "submitting a PUT request to users#update" do
before { put user_path(another_user) }
specify { response.should redirect_to(root_url) }
end
end
end
end
UPDATE: And this is my UsersController:
class UsersController < ApplicationController
before_filter :signed_in_user, :only => [:edit, :update]
before_filter :correct_user, :only => [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
sign_in #user
flash[:success] = "Welcome to the sample App!"
redirect_to #user
else
render "new"
end
end
def edit
end
def update
if #user.update_attributes(params[:user])
sign_in(#user)
flash[:success] = "Your profile was successfully updated"
redirect_to #user
else
render "edit"
end
end
private
def signed_in_user
unless signed_in?
redirect_to signin_url, notice: "Please sign in to access this page."
end
end
def correct_user
#user = User.find(params[:id])
redirect_to root_url unless current_user?(#user)
end
end
After looking at the helpers from the rest of the code on your github repository I think I see the problem.
You're using the session variable for storing your :remember_token:
def user_from_remember_token
remember_token = session[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
The book uses cookies:
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
Your test helper sets the cookie:
# Make signin work even when not using capybara (e.g. when using get, or post)
cookies[:remember_token] = user.remember_token
but because your user_from_remember_token is looking for session[:remember_token] not a cookie, it thinks the user isn't logged in so the test gets redirected to login_path instead of root_path.
It says why they use a cookie here: http://ruby.railstutorial.org/chapters/sign-in-sign-out?version=3.2#sec:a_working_sign_in_method
If you want to continue using a session you would need to adjust your test to sign in via post so the put has the session set, for example:
describe "submitting a PUT request to users#update" do
before do
post sessions_path, :email => user.email, :password => user.password
put user_path(another_user)
end
specify { response.should redirect_to(root_url) }
end
Thanks for the answer! I'm working through the book as well and have the same problem. The reason for using sessions is that exercise 2 in section 8.5 is to use session instead of cookies.
My answer is a little different. My SessionsController uses session variables in the params:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
so the fix is a slightly more complicated:
#spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "session_email", with: user.email
fill_in "session_password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well.
post sessions_path, {:session => { :email => user.email, :password => user.password }}
end
I had the same issue but a different cause.
I had a typo in spec/support/utilities.rb
def sign_in(user)
visit signin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Sign in"
# Sign in when not using Capybara as well
cookies[:remember_toke] = user.remember_token
end
Note that the last line says ":remember_toke" instead of ":remember_token"
Somehow that caused the test to fail and redirected to signon.
So I am documenting this here in case someone else makes the same type of mistake.

Why the devise user instance does not get stubbed?

i have a #user instance that i use for logging a devise user. It's a macro as Devise wiki proposes :
module ControllerMacros
def login_user
before(:each) do
#request.env["devise.mapping"] = :user
#user = Factory(:user)
sign_in #user
end
end
end
Generally, i do different things to stub methods, but i don't understand why this stub does not work : (note that #user is indeed logged in, i use it successfully for testing attributes and stuff)
#user.stub(:has_tavern_quest?).and_return(true)
It seems that the stub works(i checked with #user.has_tavern_quest should true), but i just get back :
Failure/Error: flash[:error].should == I18n.t('error.has_quest')
expected: "You already have a quest to finish !"
got: nil (using ==)
The whole controller method is :
quest_type = params[:quest_type]
#quest_minutes = TavernQuest.get_quest_minutes_from_quest_type(quest_type)
flash[:error] = I18n.t('error.invalid_post') and redirect_to tavern_url and return unless [1,2,3,4].include? quest_type.to_i
flash[:error] = I18n.t('error.has_quest') and redirect_to tavern_url and return if current_user.has_tavern_quest?
flash[:error] = I18n.t('error.no_adv_points') and redirect_to tavern_url and return if current_user.adventure_points < #quest_minutes
current_user.reduce_adventure_points(#quest_minutes.to_i)
TavernQuest.create(:user_id => current_user.id, :start_time => Time.now, :end_time => Time.now + #quest_minutes.minutes,
:duration => #quest_minutes, :quest_type => quest_type)
redirect_to tavern_url
end
And the whole spec is :
it "should redirect to '/tavern' with an error if user already has a tavern quest" do
#user.stub(:has_tavern_quest?).and_return(true)
post :create, :quest_type => 3
flash[:error].should == I18n.t('error.has_quest')
response.should redirect_to tavern_url
end
Anybody knows why and what is the way to make it work ?
Solved :
def login_user
before(:each) do
#user = Factory(:user)
controller.stub(:current_user) { #user }
sign_in #user
end
end

Authentication Problem - not recognizing 'else' - Ruby on rails

I can't seem to figure out what I am doing wrong here. I have implemented the Super Simple Authentication from Ryan Bates tutorial and while the login portion is functioning correctly, I can't get an error message and redirect to happen correctly for a bad login.
Ryan Bates admits in his comments he left this out but can't seem to implement his recommendation. Basically what is happening is that when someone logs in correctly it works. When a bad password is entered it does the same redirect and flashes 'successfully logged in' thought they are not. The admin links do not show (which is correct and are the links protected by the <% if admin? %>) but I need it to say 'failed login' and redirect to login path. Here is my code:
SessionsController
class SessionsController < ApplicationController
def create
if
session[:password] = params[:password]
flash[:notice] = 'Successfully logged in'
redirect_to posts_path
else
flash[:notice] = "whoops"
redirect_to login_path
end
end
def destroy
reset_session
flash[:notice] = 'Successfully logged out'
redirect_to posts_path
end
end
ApplicationController
class ApplicationController < ActionController::Base
helper_method :admin?
protected
def authorize
unless admin?
flash[:error] = "unauthorized request"
redirect_to posts_path
false
end
end
def admin?
session[:password] == "123456"
end
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
#
end
You need to use Ruby's comparison operator == rather than the assignment operator =. Your create action should be:
def create
if session[:password] == params[:password]
flash[:notice] = 'Successfully logged in'
redirect_to posts_path
else
flash[:notice] = "whoops"
redirect_to login_path
end
end
Edit: The problem is that nowhere in your SessionsController are you actually checking the entered password against the correct password. Change your create method to this:
def create
if params[:password] == '123456'
session[:password] = params[:password]
flash[:notice] = 'Successfully logged in'
redirect_to posts_path
else
flash[:notice] = "whoops"
redirect_to login_path
end
end
It's not ideal having the password hard-coded like this and storing it in the session for use by the admin? helper method, but this is supposed to be super simple authentication.
if #YOU MISSING SOMETHING HERE WHICH Returns TRUE IF USER IS VALID
session[:password] = session[:password]
flash[:notice] = 'Successfully logged in'
redirect_to posts_path
else
flash[:notice] = "invalid login" #CHange if messaage for invalid login
redirect_to login_path
end
it must be
if session[:password] == params[:password]
You never have a fail condition due to:
if session[:password] = session[:password]
This will always be true. You probably want something like:
if session[:password] == 'canihazpasswrd' then
do_something_here
Edit: Refer #john's answer. :)
Try this:
def create
if session[:password] == '123456'
flash[:notice] = 'Succesfully logged in'
redirect_to home_path
else
flash[:notice] = "Incorrect Password!"
redirect_to login_path
end
end
The thing is that the tutorial you used does no user's authentication. It only checks if the login belongs to an admin, so some content will be showed.
This way you'll never have wrong login/password, just admin/non-admin.

Resources