RSpec - The passwords in my test are not matching up - ruby-on-rails

I wrote up a test that should describe the case where #user and found_user should be the same via password match. This also describes when they're different. I'm not using devise or anything, but rather building out my own authentication with has_secure_password
describe "return value of authenticate method" do
before { #user.save }
let(:found_user) { User.find_by(email: #user.email) }
describe "with a valid password" do
it { should eq found_user.authenticate(#user.password) }
end
describe "with an invalid password" do
let(:user_for_invalid_password) { found_user.authenticate('invalid') }
it { should_not eq user_for_invalid_password }
specify { expect(user_for_invalid_password).to be_false }
end
end
The part that's failing is with a valid password block. The error message clearly shows that password_digest isn't matching up
Here's the relavent output:
expected: password_digest: "$2a$04$cDKhuWzsZuW8Gm4t5fJjpu6rmbwh10ZAt2Yae.BO0iuD...">
got: password_digest: "$2a$04$jwfHjoLI0RpDIAEr9SMKGOZqeH.J5ILOkzalKCYQdDW4...">
I've attempted removing the #user.save in the before block thinking that might solve it, but it didn't.
I'm not really sure why they're coming up differently or what it is that I'm doing wrong. I'm fairly new to rspec and testing in general.
I should mention that my authenticate method is working in the rails console. So I have a situation where the application code works, but the tests are failing.
Any help would be much appreciated.
My user class is here: https://gist.github.com/DavidVII/f190d1f1e114234bb7d7
Thnx!

Your test won't work with bcrypt password hashing (I can tell this what you are using from the form of the string), as it generates a new random salt for each password change. This is a good thing, don't alter that behaviour.
So you should not write tests that look for stored passwords being equal to known values. It is not 100% clear if you intended that in the test, or have accidentally over-simplified the test due to all the abstraction you get though using has_secure_pasword, or some other thing has led you to the current code.
Instead, your tests around password handling should be more black box, and assert that you can log in with the known password, and not login with any others (including code-breaking cases such as nils, empty strings, super-long passwords and a string which matches the hashed password).

describe "with an invalid password" do
let(:#user_for_invalid_password) { found_#user.authenticate('invalid') }
it { should_not eq #user_for_invalid_password }
specify { expect(#user_for_invalid_password).to be_falsey}
end
end

Related

RSpec test for not allowing two users with the same email address

First of all I should probably mention that I'm very new to Rails and this is my first "serious" project, so I apologise if this is a simple question but I can't find an answer for it.
I'm using TDD in my project and am using RSpec to write the model tests, FactoryGirl to create the models and Faker to create dummy data for the models. Everything has been going really well until I added a test to make sure no two users have the same email address. In my User model I validated it like so:
# /app/models/user.rb
validates :email, :password_reset_code, :auth_token, uniqueness: true
My factory creates a user model with Faker, like so:
# /spec/factories/users.rb
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password { Faker::Internet.password }
password_reset_code { Faker::Lorem.word }
auth_token { Faker::Lorem.word }
end
end
and my user_spec.rb test for this is as follows:
# /spec/models/user_spec.rb
it "is invalid with a duplicate email" do
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
end
Here I'm creating a new model with FactoryGirl using its dummy values from Faker, saving it to the database and then creating another one with the same email as the first one. I'd expect RSpec to tell me this test passed because of the should_not be_valid part. But instead I get this output when I run the test:
Failures:
1) User is invalid with a duplicate email
Failure/Error: FactoryGirl.create(:user, email: user.email).should_not be_valid
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken
# ./spec/models/user_spec.rb:19:in `block (2 levels) in <top (required)>'
So it seems that the model validation is raising an error which RSpec isn't catching and using to pass the test? I've managed to work around it by changing the test to this:
it "is invalid with a duplicate email" do
begin
user = FactoryGirl.create(:user)
FactoryGirl.create(:user, email: user.email).should_not be_valid
rescue
false
end
end
which seems to work, however I have a feeling this isn't the best way to do it.
What's the proper way to write this test?
I ran into this problem too. The error you're encountering is due to the fact that the create() method actually persists the model, which is then throwing an ActiveRecord::RecordInvalid at the DB layer (well, the persistence layer). If you want to assert if a model is valid or not you should use the build() method for your second object and then ask if it's valid. You can read up on it in this post.
Additionally, if you're just trying to test various validations on models and what not I wrote a quick and dirty gem that you can use to assert some of the more basic model validations. You can check it out here.
Hope that helps.
I would go with:
# /spec/models/user_spec.rb
describe 'validations' do
context 'with a duplicate email' do
let(:other_user) { FactoryGirl.create(:user) }
let(:attributes) { FactoryGirl.attributes_for(:user) }
subject(:user) { User.new(attributes.merge(email: user.email)) }
it 'is not valid' do
expect(user).to_not be_valid
end
end
end

Rspec checking for json response

I am trying to create a mock object and i am checking whether my method receives the right param and expected result.
Below is my spec.
require 'spec_helper'
describe User do
let(:username) {"test#test.com"}
let(:password) {"123"}
let(:code) {"0"}
context "when signing in" do
it "should sign in" do
user = double("user")
expected_results = {
"token": "123"
}
allow(user).to receive(:login).with({email: username, password: password, code: code})
.and_return(expected_results)
expect(user.login).to eq(expected_results)
end
end
end
Is there a way to separate my json from it block and keep it outside?.
You can use let inside a context block to set the value of a variable for the examples nested within:
require 'spec_helper'
describe User do
let(:username) {"test#test.com"}
let(:password) {"123"}
let(:code) {"0"}
context "when signing in" do
let(:expected_results) { {token:"123"}.to_json }
it "should sign in" do
user = double("user")
allow(user).to receive(:login).with({email: username, password: password, code: code})
.and_return(expected_results)
expect(user.login).to eq(expected_results)
end
end
end
am i doing the right way of testing?
Not if you are testing the User#login method. You should not set a stub if you are trying to test the logic of the method being stubbed. Instead, use a real model instance, perhaps using a factory, and omit the allow step.
You can use a before do block in the context or before the context.
Here is the reference:
https://www.relishapp.com/rspec/rspec-core/docs/hooks/before-and-after-hooks

How does RSpec know if a password is less than the intended length?

I'm following Michael Hartl's rails tutorial, and the Rspec section has been confusing. I already tried reading other free materials out there such as nets tuts. Anyway, in one of his blocks, here's an excerpt:
describe "with a password that's too short" do
before { #user.password = #user.password_confirmation = "a" * 5 }
it { should be_invalid }
end
I understand that if the length happens to be 5 characters, the password would be invalid. But how would it know if the password is less than 5?
PS. (If you need to see more of the spec, here's the link)
RSpec doesn't know that.. it check if it's valid for particular model. You define at model how long it has to be.
I expect at the top of this spec you have code like:
before { #user = User.new(name: "Example User", email: "user#example.com", password: "mysecret", password_confirmation: "mysecret" }
subject { #user }
What that's doing is creating a new user and then making the #user instance the subject of the tests.
When you reach this specific password length test you are first setting the password to a 5 character string and then assert that the password is now invalid.
Behind the scenes subject in conjunction with it { should be_invalid } is effectively doing a #user.valid? and expecting it to fail due to the length validation on the password field. Hence the test passes.
You are using rspec here to ensure that there is good password length validation on the user model. You can prove your test is not a false positive by changing to a 6 character password. Again rspec will do a #user.valid? but this time the test should fail because the user record is valid however the test expects it not to be.
If you check section 6.3.4 you see that the author adds the validation on User model so your test passes.
As a result, we’ll complete the implementation of secure passwords (and get to a green test suite) using only a few lines of code.
First, we need a length validation for the password, which uses the :minimum key in analogy with the :maximum key from Listing 6.12:
validates :password, length: { minimum: 6 }
(Presence validations for the password and its confirmation are automatically added by has_secure_password.)

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

How to test for successful password change?

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.

Resources