Why doesn't FactoryBot save passwords in the test database? - ruby-on-rails

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.

Related

How to insert an already encrypted password on devise model?

I am a newbie in rails. I would like to ask if it's possible to insert an encrypted password(encrypted using devise default encryption) to devise model?
e.g:
User.csv
id,name,encrypted_password
1,dude,$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye
I want to read this csv file and then insert the data to User model.
But I realized that you can't insert the already encrypted password normally on encrypted_password of User model. I'm thinking to decrypt the password but they say that it is bad for the system's security.
Update: It can now insert an encrypted password but the inserted password is different to the one on the csv since the devise automatically encrypts the password before inserting it to the database. Is there a way for devise model to detect if the password is already encrypted before inserting it to the database?
yes, you can set the encrypted_password value directly:
u = User.find(1)
u.update_attribute(:encrypted_password, "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye")
But you should make sure that you're not setting 'password', if you do this won't work as it'll encrypt it again, so
u = User.new
u.password = 'foo'
u.password_confirmation = 'foo'
u.encrypted_password = "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye" # this line will be ignored
u.save
u = User.new
u.encrypted_password = "$2a$10$0.xEu5LvDbnmGVIqgIab8ej5o2b3eKa8KLevsg5bxIX0SHSOl5gye" # this line will now work
u.save

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 API - Devise: Update password on User update

I'm having a very weird issue with devise when trying to edit a user's password - here is a simplified version of what I'm doing at the moment:
def update
user = User.find(params[:id])
password = params.delete(:password)
password_confirmation = params.delete(:password_confirmation)
if password.present? and password != "" and password == password_confirmation
user.password = password
end
user.update(params)
user.save
render json: user
end
What's really weird is that if I set password to an arbitrary string, by putting
password = "testpassword"
above
user.password = password
it works properly, the password is set to "testpassword", and i can log in using it just fine. But if I try and use the param like in the code above, I cannot log in using the password set in params[:password]. I have tried forcing the encoding of the string, and to use user.update_without_password, but to no avail.
Would anybody have any idea about this? it's driving me bonkers!
here is a dump of the params hash:
{"username"=>"testytest",
"first_name"=>"Test",
"last_name"=>"testy",
"password"=>"password",
"password_confirmation"=>"password"}
To be a bit more precise, trying to login once this has been fired results in me not being able to login using the old password, or the new one. if there was a way to see which password gets saved in that function, I should be able to debug it!
Well, after 3 days of struggle I finally came up with a solution, however it's pretty ugly: what I did was this (I've added a method to the user model to simplify):
def update_password(new_password)
password_forced = ""
new_password.each_byte do |byte|
password_forced << byte.chr
end
self.password = password_forced
self.save
end
Again, inspecting the string coming from the params didn't point to anything that might be different from another ruby string... but anyway, this works!

Data migration problems when you explicitly set ID's (Rails + Postgres)

I'm writing a script for a big migration and have come across a major issue.
# Import users
user_data.each do |data|
u = User.new
u.id = data.id
u.email = data.email
# more user attributes set...
u.save!
end
# required to prevent Postgres from trying to use now taken user ids
ActiveRecord::Base.connection.execute "ALTER SEQUENCE users_id_seq RESTART WITH #{User.last.id+1};"
So first we read user data from a data source, and set it's id manually. We need to preserve ids since we are migrating associated data as well.
Then later on, we need to create more users conditionally from the data of an associated object.
# Create a user for this email if no user with this email exists.
if data.email
user = User.find_by_email(data.email)
if user
o.user = user
else
o.user = User.create!(
first_name: 'Unknown',
last_name: 'Unknown',
email: data.email,
password: generate_temp_password
)
end
end
This fails at User.create! with:
Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
I've debugged this a bit and can see that User.where(email: data.email).first is nil right before this error is thrown. I suspect this has something to with setting ids beyond the current auto increment value, somehow causing the new records to be invisible in my queries, but visible to Postgres own validations.
So how can a user with a specific email not be present, but still trigger DB validation errors?
Apparently, Devise downcases email addresses. And the offending email had some caps in it. So it missed a case sensitive check, and then failed as a dupe when case insensitive.
Devise outsmarted me it seems.

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