Rails - Devise testing authentication in model specs - ruby-on-rails

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

Related

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 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.)

RSpec - The passwords in my test are not matching up

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

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.

Test if this password is correct for current logged in user

I'm building a "change password" form for my user built with these fields:
Old password
New password
Confirmation password
I need a way to check if the current logged in user password is the same as "old password" field, are there any possibility to do this, with authlogic? I can't find a method to test a password.
Authlogic has a valid_password? method. see: http://rubydoc.info/github/binarylogic/authlogic/master/Authlogic/ActsAsAuthentic/Password/Methods/InstanceMethods#valid_password%3F-instance_method
So you could
if #user.valid_password?(params[:old_password])
#user.password = params[:new_password]
#user.password_confirmation = params[:new_password_confirmation]
end
(or similar)

Resources