问题
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
orusers#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
回答1:
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
回答2:
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
回答3:
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.
来源:https://stackoverflow.com/questions/9960732/rspec-vs-cookies-the-test-fails-even-if-the-applications-works-simulating-lo