How to test for successful password change? - ruby-on-rails

I've put together a basic application with user authentication using bcrypt-ruby and has_secure_password. The result is essentially a barebones version of the application from the Rails Tutorial. In other words, I have a RESTful user model as well as sign-in and sign-out functionality.
As part of the tests for editing a user's information, I've written a test for changing a password. Whereas changing the password works just fine in the browser, my test below is not passing.
subject { page }
describe "successful password change"
let(:new_password) { "foobaz" }
before do
fill_in "Password", with: new_password
fill_in "Password Confirmation", with: new_password
click_button "Save changes"
end
specify { user.reload.password.should == new_password }
end
Clearly, I'm misunderstanding some basic detail here.
In short:
1) Why exactly is the code above not working? The change-password functionality works in the browser. Meanwhile, rspec continues to reload the old password in the last line above. And then the test fails.
2) What is the better way to test the password change?
Edit:
With the initial password set to foobar, the error message is:
Failure/Error: specify { user.reload.password.should == new_password }
expected: "foobaz"
got: "foobar" (using ==)
Basically, it looks like the before block is not actually saving the new password.
For reference, the related controller action is as follows:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Profile Updated"
sign_in #user
redirect_to root_path
else
render 'edit'
end
end

For Devise users, use #valid_password? instead:
expect(user.valid_password?('correct_password')).to be(true)
Credit: Ryan Bigg

One not so satisfying solution here is to write a test using the #authenticate method provided by bcrypt-ruby.
specify { user.reload.authenticate(new_password).should be_true }
Granted this isn't a proper integration test, but it will get us to green.

Your answer (using authenticate) is the right approach; you should be satisfied with it. You want to compare the hashed versions of the passwords not the #password (via attr_accessor) in the model. Remember that you're saving a hash and not the actual password.
Your user in your test is an copy of that user in memory. When you run the tests the update method loads a different copy of that user in memory and updates its password hash which is saved to the db. Your copy is unchanged; which is why you thought to reload to get the updated data from the database.
The password field isn't stored in the db, it's stored as a hash instead, so the new hash gets reloaded from the db, but you were comparing the ephemeral state of #password in your user instance instead of the the encrypted_password.

Related

Why doesn't FactoryBot save passwords in the test database?

I have just run into a situation where I had to tack down why my test user could not login in a system test.
It turns out that the password word for the user was nil.
I ran binding.pry after a user is created:
it 'some tests do
user = create(:user)
binding.pry
end
user.password = '12345' # correct
User.last.password = nil # wtf
user.email = 'joe#example.com' #correct
User.last.email = 'joe#example.com' #correct
Does anyone know why passwords are not persisted into the database with FactoryBot?
The reason User.last.password is nil is because the plain text password is encrypted and not accessible. Check your schema.rb file...you should only see an encrypted_password column (I'm assuming you are using Devise).
To check if the User is persisted just check user.persisted?, user.errors, or something of the sort to figure out whats going on.

How to share a variable with many "it" examples in rspec

I am using let to create a user record using factory girl. However i want to use exactly the same variable across 2 tests in the context as the user_id and email are important to the external API i am sending.
However i had no luck making a single variable for using across the examples. Here is my current code
context "User" do
let(:user) { FactoryGirl.create(:user) }
it "should create user and return 'nil'" do
expect(send_preferences(user, "new")).to eq nil
end
it "should not create user preferences again after sending two consecutive same requests" do
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
expect(send_preferences(user, "update")).to eq nil
end
end
any clues?
You can use lets within lets:
context "User" do
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
You will then also have access to the email_address variable within all your tests.
This works because previously the email address was being randomly generated by the factory every time the user was created, as we hadn't set a value for it anywhere. So, we called the code below in each test:
send_preferences(user, "new")
It called the 'user' let which created a new user with a completely random email address (as we hadn't give it a specific email value). Therefore during the backend API call it was sending a different email address every time.
let(:user) { FactoryGirl.create(:user) }
However, when we defined the email address 'let' as 'test#test.com', and passed that into the user factory as in the code I provided, we overrode the randomly generated email address with our own static value, So, every time we call the code again:
send_preferences(user, "new")
It now triggers the user factory create which is also taking our new 'email_address' let, which is always set to a specific value of test#test.com every time it is called.
let(:email_address) { 'test#test.com' }
let(:user) { FactoryGirl.create(:user, email_address: email_address) }
Therefore, when the backend API call is made the email address is always what we set it to.
Also, as it is a let we can use that variable in any of the tests themselves if we wish. For example:
it 'should set the email address' do
expect(user.email_address).to eq(email_address)
end
It's quite hard to explain in a few sentences but let me know if that's still not clear.
Having an instantiated variable shared among multiple tests is an anti-pattern 90% of the time in my opinion.
The problem with doing something like the below is you will be creating objects in your db without doing a cleanup.
before(:all) do
#user = FactoryGirl.create :user
end
Sure, you can do a before(:after) block or use DatabaseCleaner, but I think it is much better practice for tests to be as standalone as possible. In your case, make your setup of a send_preferences event before making an expectation on what happens the second time:
context "User" do
let(:user) { FactoryGirl.create(:user) }
# ...
it "should not create user preferences again after sending two consecutive same requests" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "new")).to eq "User preferences already saved. No need to re-save them."
end
it "should update user preferences" do
send_preferences(user, "new") # Setup
expect(send_preferences(user, "update")).to eq nil
end
end

Rails - Why am I getting 'You are being redirected' in integration test?

When writing integration tests for a Rails 5 application I encountered the infamous 'You are being redirected' page for no apparent to me reasons. There are two highly similar tests:
test "GETtting correct activation link on an already activated user gives error message and redirects to root url" do
# GIVEN a non-yet-registered email address
email_address = "tester#testing.net"
# GIVEN the sign-up page has been displayd
get signup_path
# GIVEN new user is created
post signup_path, params: { user: { email: email_address, email_confirmation: email_address, password: "testpassword", password_confirmation: "testpassword" } }
# GIVEN the URI from activation email
activation_uri = URI.extract(ActionMailer::Base.deliveries.last.text_part.body.encoded)[0]
# GIVEN the URI's been used and the user is already activated
get activation_uri
# WHEN reading back the newly activated user
activated_user = User.find_by_email(email_address)
# EXPECT the user to be activated
assert activated_user.activated?
# WHEN using the activation link on an already activated user
get activation_uri
# EXPECT redirection to root path
assert_redirected_to root_url
follow_redirect!
# EXPECT flash message
assert_not flash.empty?
# EXPECT rendered page to contain activation error information
assert_select 'div#flash div h5', text: I18n.translate('users.activate.error')
end
which finishes correctly, and the next one:
test "GETtting incorrect activation hash on a non-activated user gives error message and redirects to root url" do
# GIVEN a non-yet-registered email address
email_address = "tester#testing.net"
# GIVEN the sign-up page has been displayd
get signup_path
# GIVEN new user is created
post signup_path, params: { user: { email: email_address, email_confirmation: email_address, password: "testpassword", password_confirmation: "testpassword" } }
# WEHN GETting the activation URI with invalid activation hash
activation_uri = "http://localhost:3000/account_activations/waTbfcCoZoPTBEIcewsl8Q/edit?email=#{ERB::Util.url_encode(email_address)}"
get activation_uri
# EXPECT redirection to root path
assert_redirected_to root_url
follow_redirect!
# EXPECT flash message
assert_not flash.empty?
# EXPECT rendered page to contain activation error information ('You are being redirected' rendered here)
assert_select 'div#flash div h5', text: I18n.translate('users.activate.error')
end
which fails miserably on the last assert because 'You are being redirected' is being rendered instead of the page I am expecting to be rendered. In both cases I use follow_redirect! and the first one works, while the second one doesn't. The static URL in the second test is correct. It only uses a valid but non-associated hash instead of the expected one. In the controller there is simple
flash[:error] = "#{t'users.activate.error'}"
redirect_to root_url
in both cases (the same method). I receive proper 302 response code and proper redirection URL. When doing the same tests manually in the browser, correct page is rendered. When running the tests I get 'You are being…' in the second test.
Any clues?
I think it's happen due to authorisation problem, you are calling a method with the data that does'not have access to that method, and it redirect it to somewhere. I have faced similar problem when i was trying to redirect with status code 401(unauthorised)
Did you try, puts response.body in there to see whats going on?
There's a blurp in hartl's rails tutorial that helped me during a similar situation ... I applied his trick in a similar devise situation, honestly I'm not completely sure without poking around your code that the redirect is from a bad URI or not - but thought I'd leave this here for anyone else with issues. I stumbled your post when looking for some answers to devise redirects myself (though mine are probably related to ID not found issues).
When I originally wrote this chapter, I couldn’t recall offhand how to escape URLs in Rails, and figuring it out was pure technical sophistication (Box 1.1). What I did was Google “ruby rails escape url”, which led me to find two main possibilities, URI.encode(str) and CGI.escape(str). Trying them both revealed that the latter works. (It turns out there’s a third possibility: the ERB::Util library supplies a url_encode method that has the same effect.)

How to test a class method in rspec that returns a model instance

I'm trying to test my User model's class method #registered_but_not_logged_in(email), which grabs the first user that matches the email that has a confirmed_at entry but has never logged in (which I'm counting with sign_in_count). I'm using rspec with Factorygirl, plus shoulda-matchers v2.8.
Here's the ruby:
def self.registered_but_not_logged_in email
self.with_email( email ).confirmed.never_signed_in.first
end
I've tested this in the Rails console and I know it works as expected so it's not a logic problem on that end, so I'm thinking I'm doing something wrong in my test:
describe User do
# create #user
describe ".registered_but_not_logged_in" do
it "returns a user that matches the provided email who is confirmed, but who has not yet signed in" do
#user.confirmed_at = 2.days.ago
#user.email = "fisterroboto5893#mailinator.com"
result = described_class.registered_but_not_logged_in("fisterroboto5893#mailinator.com")
expect(result).to be_instance_of(User)
end
end
In this example, result is nil. I know that this is a case of #user existing outside the database while the method is actively checking the DB, but I don't know how to handle this while using rspec/factorygirl. Any help is definitely appreciated!
So I'm not 100% sure why what I'm doing is working, but here's the solution that I stumbled across with the help of #nort and one of my coworkers:
it "returns a user that matches the provided email who is confirmed, but who has not yet signed in" do
#user.confirmed_at = 2.days.ago
#user.email = "fisterroboto5893#mailinator.com"
#user.sign_in_count = 0
#user.save!
expect(User.registered_but_not_logged("fisterroboto5893#mailinator.com")).to be_instance_of(User)
end
What I believe is happening is the save! is saving #user to the test database, which is otherwise completely unpopulated as I develop against a different DB. The issue of course being that we can't test data that doesn't exist.
As a bonus, note that expect().to... is the preferred convention for rpsec. Also, described_class I believe would totally work fine, but am preferring explicitness right now since I'm still learning this stuff.

Rails - Devise testing authentication in model specs

I feel like this should be a simple question, yet I've struggled to find the answer. I have set up devise for authentication in my Rails project, and it's working great. I've also customized the password validation and the login requirements. Specifically, one should be able to login with either their username or email, and the email should not be case sensitive.
How to I test this in my model specs? Specifically testing:
Login with email (all lower) and password is valid
Login with email (all upper) and password is valid
Login with username and password is valid
Login with username (jumbled case) and password is invalid
Basically, I just need one function that takes in the login details and tell me whether or not devise will authenticate it. But I can't find such a function in any examples or any way to construct such a function in the devise documentation.
I am confident that it is actually working, and CAN test it in my request specs, but as it is defined in the model it feels like their ought to be a model test as well.
The only devise testing I've regularly found is in the controller, which doesn't help as it just automatically signs in the user without requiring the login details.
Well, there are two distinct components here:
1) Finding a user
2) Validating the password for the user
Finding a user is handled by find_for_database_authentication (info on having username and email handled by "login")
The validating of a password is handled by the valid_password? method (info)
So, you'd want to break this test up into:
context "finding a user" do
let(:user) { FactoryGirl.create(:user) }
it "can find by lower email" do
User.find_for_database_authentication( {login: user.email.downcase} ).should eq(user)
end
it "can find by upper email" do
User.find_for_database_authentication( {login: user.email.upcase} ).should eq(user)
end
it "can find by jumbled username" do
scrambled_username = user.username.downcase.chars.map{|c| rand() > 0.5 ? c.capitalize : c}.join
User.find_for_database_authentication( {login: username} ).should eq(user)
end
end
context "authenticating a user" do
let(:user) { FactoryGirl.create(:user, password: "password123", password_confirmation: "password123") }
it "will validate a correct password" do
user.valid_password?("password123").should be_true
end
it "will not validate an incorrect password" do
user.valid_password?("bad-password").should be_false
end
end

Resources