How to test updating user's password in RSpec - ruby-on-rails

I am using Rails 5.2, Ruby 2.4.1, and Rspec. I am trying to make an automation test using Rspec to update a User's password. I know the code works from manually testing.
The RSpec test does not pass the #user.update_attributes(user_params) condition in the controller and then goes to the else condition. Thus, my RSpec test says that the passwords are still equal to each other. How can I get my RSpec test to pass the condition?
Here is the method
# Creates user, saves old password, generates a url for user to go to, updates password, reloads user, and test if old password equals new password
context "with a valid token" do
it "updates the user's password and resets the token" do
test_users = User.create(first_name: 'chase', last_name: 'dougherty', email: 'chase#gmail.com', password: '1', password_confirmation: '1')
old_password = test_users.password
test_users.generate_password_reset_token!
patch :update, params: { id: test_users.password_reset_token, user: { password: 'newpassword', password_confirmation: 'newpassword' } }
test_users.reload
expect(test_users.password).to_not eq(old_password)
end
end
Here is the Controller
# Finds user, test if update_attributes is true, updates password, logs user in, redirects user, displays flash
def update
#user = User.find_by(password_reset_token: params[:id])
if #user && #user.update_attributes(user_params)
#user.update_attribute(:password_reset_token, nil)
session[:user_id] = #user.id
redirect_to '/maps'
flash[:notice] = "Password updated"
else
flash[:notice] = "Password reset failure."
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end

If you use has_secure_password method in your user model, you can use this syntax for check password has been changed:
it "changes user's password" do
expect { send_request }.to change { user.reload.authenticate(password) }.from(false).to(user)
end

If the update_attributes line is failing, you probably have some validations on the User model that are firing and causing it to return false.
Also, I would advise against "creating" a user object in the test files. Especially because the first time you run the tests it will create the entry, but then every time after that it will return a User instance that is not saved to the database (because of uniqueness violations in the data) and the tests may not run as expected.
Unless you want to clean up your created user objects after the test runs. Otherwise you will want to use factory_bot and stub out most of these models and the database calls.

Related

Rails test - checking if user is signed in with Devise

I am trying to test that someone is able to login to my site by making a POST request to my SessionsController. I've seen this way recommended in a few places:
it 'must be able to sign in a user' do
user = create(:user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal user
end
But this test is not correct. Calling #controller.current_user will attempt to authenticate the user using the posted parameters and will return user if the supplied email/password is correct. There is no guarantee that the create action is actually calling sign_in or current_user.
Even if I re-write the test to check that these methods are called, it's possible that other methods could be called e.g. sign_out.
Is there a more definitive way to ultimately check if a user is logged in, and if so, who the user is?
EDIT -
For example, the following test will pass
it 'must sign in a user' do
#controller.current_user.must_equal nil
post :create, format: :js, user: {email: #user.email, password: #user.password, remember_me: 0}
assert_response :success
#controller.current_user.must_equal #user
end
when the SessionsController#create action is:
def create
respond_to do |format|
format.js {
render nothing: true, status: 200
}
end
end
Solution with minimal changes to proposed code in the question:
You need to initialize the system before the test starts. Try prepending following code before your it 'must be able to sign in a user' do code:
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
This should turn your test into a valid test for your post controller.
Explanation:
My assumption is, that your test above always succeeds, because the user is already signed in (by other tests run before this one). You could verify this by using byebug in the line after it and run current_user in bybug's console. If it is not nil, the user is already signed in, which is invalidating your test.
Note, that (different from what is discussed above in the comments), current_user does not change the status of the user; it is a read-only function.
Shorter/cleaner solution:
In my opinion, there is a a cleaner way to perform such a test like follows:
def sign_in_via_post(user)
post :create, format: :js, user: {email: user.email, password: user.password, remember_me: 0}
end
...
before (:each) do
user = FactoryGirl.create(:user)
sign_out user
end
it 'must be able to sign in a user' do
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
end
With the should change from nil to user statement, you verify, that the user was logged out before the test begins and that the user is logged in, after the test has been performed.
Note, that the part
{ sign_in_via_post user }.should change { current_user }.from(nil).to(user)
is equivalent to the (maybe easier to understand) code
{ sign_in_via_post user }.should change { user_signed_in? }.from(false).to(true)
as discussed here.

Rails testing authentication with has_secure_password

Per the title, I'm setting session[:user_id] in my controller if a user is authenticated successfully and then am testing that the session value matches the user id, but the session is nil with the following setup:
sessions_controlller.rb:
class SessionsController < ApplicationController
def create
user = User.find_by username: params[:username]
if user.try(:authenticate, params[:password])
session[:user_id] = user.id
redirect_to user_posts_path, notice: "Successfully logged in!"
else
redirect_to new_session_path, alert: "Invalid credentials."
end
end
end
session_controller_test.rb:
test "should sign in user with correct credentials" do
user_to_log_in = users(:one)
post :create, { password: "password", username: "yes" }
assert_equal user_to_log_in.id, session[:user_id]
end
user.rb:
class User < ActiveRecord::Base
has_secure_password
end
users.yml:
one:
id: 1
username: yes
password_digest: <%= BCrypt::Password.create('password', cost: 4) %>
How do I write a passing test here?
You may not necessarily have access to server data, like session, in your tests. Check out rack_session_access https://github.com/railsware/rack_session_access for a possible answer.
Hah, so apparently, with a username of 'yes' in users.yaml, the record in the test DB gets saved with a username of 't', so I assume it tries to convert it to some form of 'true'... Anyway changing the username in the fixture fixed the problem.

Rails 3 / Capybara isn't saving model attributes

I'm trying to run a Capybara 1.0 test on my Rails 3 app to test whether when a user clicks on a confirmation link, he is actually confirmed.
Now, this actually works when I test it manually. In addition, as you can see there is a puts #user.confirmed line that I put in the confirm method to debug this, and it actually prints true when I run the test. However, the test itself fails.
It seems as if the confirmed attribute in my user model isn't being remembered by the test after executing the controller method.
What am I missing? Thanks so much in advance.
Test:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user.confirmed.should be_true
end
Controller method:
def confirm
#confirmation_code = params[:confirmation_code]
#user = User.find_by_confirmation_code(#confirmation_code)
#website = #user.website
#user.confirm
if #user.referrer_id
User.find(#user.referrer_id).increment_signups
end
flash[:success] = "Thanks for signing up!"
flash[:user_show] = #user.id
puts #user.confirmed
redirect_to "http://" + #website.domain_name
end
User model method:
def confirm
self.confirmed = true
self.save
end
Is it that you would need to reload the user object after visiting the confirm_path? Try this:
it "should allow a user to be confirmed after clicking confirmation link" do
fill_in('user_email', :with => 'test#test.com')
click_button('Submit')
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_false
visit confirm_path(#user.confirmation_code)
#user = User.find_by_email('test#test.com')
#user.confirmed.should be_true
end
Alternatively you could use #user.reload.
The user object referred to in your test is just a copy of the object being manipulated by the application so it is not going to be automatically refreshed. You need to fetch it from the database a second time to get the updated values.

Authlogic and password and password confirmation attributes - inaccessible?

Im trying to test my successfully creates a new user after login (using authlogic). Ive added a couple of new fields to the user so just want to make sure that the user is saved properly.
The problem is despite creating a valid user factory, whenever i try to grab its attributes to post to the create method, password and password confirmation are being ommitted. I presuem this is a security method that authlogic performs in the background. This results in validations failing and the test failing.
Im wondering how do i get round this problem? I could just type the attributes out by hand but that doesnt seem very dry.
context "on POST to :create" do
context "on posting a valid user" do
setup do
#user = Factory.build(:user)
post :create, :user => #user.attributes
end
should "be valid" do
assert #user.valid?
end
should_redirect_to("users sentences index page") { sentences_path() }
should "add user to the db" do
assert User.find_by_username(#user.username)
end
end
##User factory
Factory.define :user do |f|
f.username {Factory.next(:username) }
f.email { Factory.next(:email)}
f.password_confirmation "password"
f.password "password"
f.native_language {|nl| nl.association(:language)}
f.second_language {|nl| nl.association(:language)}
end
You definitely can't read the password and password_confirmation from the User object. You will need to merge in the :password and :password_confirmation to the #user.attributes hash. If you store that hash somewhere common with the rest of your factory definitions, it is not super dry, but it is better than hardcoding it into your test.

Controller test fails for rails user authentication

--preface: ignore if you want.
I'm new to rails, and working on a project that will require user authentication.
I found this tutorial and have been trying to go through it and understand what's happening. Of course, it's not exactly what I need as-is, so I've been modifying as I go along. The tutorial is also out of date in some areas, so of course I've had to update my code. So part of my problem is that I'm not sure if the bug is in my modifications, or some function that's been deprecated, or what.
--the question
This is the (simplest) test that fails. (" expected to not be nil" on the first assert statement.)
def test_authentication
#check we can log in
post :login, :user => { :username => "bob", :password => "test" }
assert_not_nil session[:user_id]
assert_equal users(:bob).id, session[:user_id]
assert_response :redirect
assert_redirected_to :action => 'welcome'
end
It calls the user_controller action login:
def login
if request.post?
if session[:user_id] = User.authenticate(params[:user][:username], params[:user][:password])
flash[:message] = "Login succeeded!"
redirect_to_stored
else
flash[:warning] = "Login failed."
end
end
end
which calls the User method authenticate. I know that authenticate works properly, however, because I have a single test that does pass:
def test_registration
#check that we can register and are logged in automatically
post :register, :user => { :username => "newuser", :password => "pass", :password_confirmation => "pass", :email => "newuser#web.com" }
assert_response :redirect
assert_not_nil session[:user_id]
assert_redirected_to :action => 'welcome'
end
which calls the user_controller action register
def register
#user = User.new(params[:user])
if request.post?
if #user.save
session[:user_id] = User.authenticate(#user.username, #user.password)
flash[:message] = "Registration succeeded"
redirect_to :action => 'welcome'
end
else
flash[:warning] = "Registration failed"
end
end
which successfully calls authenticate.
the users fixture has one relevant record:
bob:
username: bob
email: bob#mcbob.com
hashed_password: 77a0d943cdbace52716a9ef9fae12e45e2788d39 # test
salt: 1000
I've tested the hashed password and salt - "test" is the correct password.
So by my analysis, the bug has got to be in one of 3 places:
how I'm sending my post request,
how I'm accessing the parameters in the login action,
or some aspect of the fixture loading.
(originally I was using the tutorial's code to load the fixture explicitly (self.use_instantiated_fixtures = true; fixtures :users), but I read that all fixtures are automatically loaded before testing, so I took it out. That didn't change a thing.)
Of course, since I can't seem to find the problem in those areas, it could just as well be anywhere else.
Is it possible that there's a filter that is preventing your action getting called? If there's a general :before_filter => 'login_required' then you might not be reaching your login functionality at all. (Though admittedly the register action would have to be excluded for that test to pass)
In cases like this it's useful to stick some logging in (or run through a debugger) to see whether you even get to the part of the method that you think is failing. If it were me I'd stick a logger.debug("...") as the first line of the login method and then another after the check for request.post? and then another after the authentication check.

Resources