Rails Unit Testing won't update database - ruby-on-rails

I'm trying to run the following unit-test:
def test_passwordchange
# check success
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
#change password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
assert #longbob.save!
#new password works
assert_equal #longbob, Usuario.autenticar("longbob", "nonbobpasswd")
#old pasword doesn't work anymore
assert_nil Usuario.autenticar("longbob", "longtest")
#change back again
#longbob.password = "longtest"
#longbob.password_confirmation = "longtest"
assert #longbob.save!
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
assert_nil Usuario.autenticar("longbob", "nonbobpasswd")
end
However, it throws error on the 1st line that contains "assert_equal" that says:
<#<Usuario ID: 1000003, login: "longbob", hashed_password: "078cf6ae2de80ed6c004c8c8576a5572e077a52c", salt: "1000", nombre: nil, apellido: nil, email: "lbob#mcbob.com", telefono: nil, tipo_usuario: nil, foto: nil, bol_activo: nil>> expected but was <nil>.
Here's my authenticate method:
def self.authenticate (login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if Usuario.encrypt(pass, u.salt)==u.hashed_password
nil
end
Also, I defined the following:
def password=(pass)
#password=pass
self.salt = Usuario.random_string(10) if !self.salt?
self.hashed_password = Usuario.encrypt(#password, self.salt)
end
So, I guess that should update the hashed_password every time I reassigned something to "password"... right?
Whats happening?
Thx.
UPDATE: I noticed that if I change:
assert_equal #longbob,
Usuario.autenticar("longbob",
"nonbobpasswd")
to
assert_equal #longbob2,
Usuario.autenticar("longbob",
"nonbobpasswd")
It passes that test, however it fails in the following line... Trowing the same error... What's up with that?

To answer after my comment, we find out that the problem is the call to save does not update the record in the database. I have suggested you to write another test to see this behaviour and be able to fix it, the test you have writed start to be too long and in fact the bug does not have anything related to the authenticate mechanism.
Here is the test I would write :
def test_change_password_save
old_hash = #longbob.hashed_password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
#longbob.save
assert_not_equal(old_hash, #longbox.reload.hashed_password)
end
If this test failed, I would suggest you to write another question in stackoverflow for this bug.

You're probably getting a validation error. Check the contents of #longbob.errors.
What happens if you split this into two separate statements? (Which is good practice anyway)
#longbob.password = #longbob.password_confirmation = "nonbobpasswd"
See, #password_confirmation= is actually a method, which might not return the value that was passed to it, depending on how the method was implemented.

Is it possible that changing the password variables doesn't update the hashed_password field in the database.
You probably need something like this in Usuario:
before_save :rehash
def rehash
self.hashed_password = ??? #replace ??? with whatever your hashing logic is
end

Related

how to write test case using rspec of this method

I want to write test case for below method. I'm new to unit testing. Please let me know the correct way to write test case for below method.
def create_new_user
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(self.password, password_salt)
user = User.new(email: self.email, username:self.username, password_hash: password_hash, password_salt: password_salt)
if user.valid?
user.save ? {is_created: true, err:''} : {is_created: false, err:'Something went wrong,please try later...'}
else
{is_created: false, err: 'Please enter all mandetory fields..'}
end
end
This may not be "an answer" per se but some comments/advice that might help point you in the right direction:
Your method looks to be returning a hash even though it's creating a new user. You should probably return the new user. If there are errors, the activerecord object will have those errors, no need to add your own errors.
Remember to test behavior. This is crucial to the change in point 1. The behavior of this method is: It returns a user record, either saved or not, that's the behavior. Test that.
You probably don't need to call user.valid?. Just call user.save
You can probably just return user.save itself, since, if it works, you'll get a user that is persisted/saved. If it doesn't you can check user.errors

Increment field within validator

I have a custom validator that checks if the user has entered the correct SMS code. When the user enters the wrong code I need to log the failed attempt and limit their retries to 3 per code.
I have created the following validator that works however the field is not being incremented.
def token_match
if token != User.find(user_id).verification_token
User.find(user_id).increment!(:verification_fails)
errors.add(:sms_code, "does not match")
end
end
The problem is as soon as I add the error the previous statement is rolled back. If I comment out the errors.add line then the increment works however there is no higher level validation performed.
Change your custom validator to be:
def token_match
if token != User.find(user_id).verification_token
errors.add(:sms_code, "does not match")
end
end
and add in your model after_validation callback to be like this:
after_validation: increase_fails_count
def increase_fails_count
unless self.errors[:sms_code].empty?
user = User.find_by(:id => user_id)
user.increment!(:verification_fails)
user.save
end
end
You can use #update_columns in your validator. It writes directly to db.
u = User.find(user_id)
u.update_columns(verification_fails: u.verification_fails + 1)
This worked for me. But if for some reason it doesn't work for you, maybe you can try running it in a new thread,which creates a new db connection:
Thread.new do
num = User.find(user_id).verification_fails
ActiveRecord::Base.connection_pool.with_connection { |con| con.exec_query("UPDATE users SET verification_fails = #{num} WHERE id = #{user_id}") }
end.join

Ruby if conditional weirdly appears to run some code when the condition is false

I have a set-up where I have Devise being used as the main authentication handler for a Rails application and once in a while it appears to pull out an ActiveRecord Relation instead of a record, in the same way you might if you called Type.where( name: "bob" ) for example.
In order to work around this ( I haven't spent much time with Devise and I don't have a lot of spare time to spend exploring it ) I just wanted to make sure my sign in checked and corrected this, so I wrote a section like this:
def authenticate_user_with_assignment!
authenticate_user!
logger.debug("User is #{current_user.inspect}")
if ( current_user.is_a?(ActiveRecord::Relation) )
logger.debug ("It is a relation!")
current_user = current_user.first
else
logger.debug ("Not a relation.")
end
logger.debug("User is #{current_user.inspect}")
# do some other stuff
end
I get output like this:
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
Not a relation.
User is nil
If I comment out the current_user = current_user.first line, I get this:
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
Not a relation.
User is #<User id: 1, email: "admin#testaddress.com" ... etc >
So somehow it appears to be ignoring the logger statements in my if condition there but running the assignment. I can't figure out why this is happening- any ideas?
current_user = current_user.first
This creates a local variable, which shadows current_user method. Local variables are created as soon as parser notice them being declared, regardless whether assignment is actually executed or not, hence if the if condition is false, local variable current_user is declared and nil. You need to execute current_user= method instead:
self.current_user = current_user.first
If you have no such a method, define it.

How do I ensure that there is only one instance of saved model with mongoid?

I have run into an issue and I think that my solution is very ugly at the moment, what is a better way I can do the following with rails/mongoid? Basically, a user can come in and provide a 'nil' answer_id, but as soon as they answer the question, we want to lock in their first, non-nil answer.
controller.rb
r = Response.new(user: current_user, question_id: qid, answer_id: aid)
r.save_now!
And the following response.rb model:
def save_now!
user = self.user
qid = self.question_id
aid = self.answer_id
resp = Response.where({user_id: user._id, question_id: qid}).first
# We accept the first answer that is non-nil,
# so a user can skip the question (answer_id=nil)
# And then return and update the answer_id from nil to 'xyz'
if resp.nil?
resp = Response.new(user: user, question_id: qid, answer_id: aid)
else
if resp.answer_id.nil? && aid.present?
resp.answer_id = aid
end
end
resp.save!
end
So I would like to allow for answer_id to be nil initially (if a user skipped the question), and then take the first answer that is non-nil.
I really don't think it's intuitive and clean to instantiate the Response object twice, once in controller and once in model but I'm not sure on the best way to do this? Thanks.
Create a unique index over (user, question_id, answer_id). This way only the first insert will succeed. Subsequent inserts will fail with an error. This eliminates the need for the find query in your save_now! method.
Remember to run this insert in safe mode, or else you won't get an exception, it will just fail silently.
Update
Seems that your problem might be solved by renaming the method. :) Take a look:
class Response
def self.save_answer(user, qid, aid)
resp = Response.where({user_id: user._id, question_id: qid}).first
if resp.nil?
resp = Response.new(user: user, question_id: qid, answer_id: aid)
else
if resp.answer_id.nil? && aid.present?
resp.answer_id = aid
end
end
resp.save!
end
end
# controller.rb
r = Response.save_answer(current_user, qid, aid)
Mongoid has a validation on uniqueness that you could use. In your case, you could create a compound index on user, question_id, and answer_id and there would be no need to write a save_answer method.
For example you could put this in the Response model:
validates_uniqueness_of :user_id, :question_id
To ensure that you can only have one response for a question per user.

Moching rails association methods

Here is my helper method which I want to test.
def posts_correlation(name)
if name.present?
author = User.find_by_name(name)
author.posts.count * 100 / Post.count if author
end
end
A factory for user.
factory :user do
email 'user#example.com'
password 'secret'
password_confirmation { password }
name 'Brian'
end
And finally a test which permanently fails.
test "should calculate posts count correlation" do
#author = FactoryGirl.create(:user, name: 'Jason')
#author.posts.expects(:count).returns(40)
Post.expects(:count).returns(100)
assert_equal 40, posts_correlation('Jason')
end
Like this.
UsersHelperTest:
FAIL should calculate posts count correlation (0.42s)
<40> expected but was <0>.
test/unit/helpers/users_helper_test.rb:11:in `block in <class:UsersHelperTest>'
And the whole problem is that mocha doesn't really mock the count value of author's posts, and it returns 0 instead of 40.
Are there any better ways of doing this: #author.posts.expects(:count).returns(40) ?
When your helper method runs, it's retrieving its own object reference to your author, not the #author defined in the test. If you were to puts #author.object_id and puts author.object_id in the helper method, you would see this problem.
A better way is to pass the setup data for the author in to your mocked record as opposed to setting up expectations on the test object.
It's been a while since I used FactoryGirl, but I think something like this should work:
#author = FactoryGirl.create(:user, name: 'Jason')
(1..40).each { |i| FactoryGirl.create(:post, user_id: #author.id ) }
Not terribly efficient, but should at least get the desired result in that the data will actually be attached to the record.

Resources