Question

Pretty simple, I am using factory girl to do the following:

FactoryGirl.define do
    sequence :user_email do |n|
        "user#{n}@example.com"
    end

    # Allows for multiple user names
    sequence :user_name do |n|
        "user#{n}"
    end

    factory :user, class: Xaaron::User do
        first_name 'Adam'
        last_name 'Something'
        user_name {generate :user_name}
        email {generate :user_email}
        password 'somePasswordThat_Is$ecure10!'
    end
end

And from there we pass this information into the user modal:

require 'bcrypt'

module Xaaron
  class User < ActiveRecord::Base
    attr_accessor :password

    before_save :encrypt_password

    validates :first_name, presence: true
    validates :user_name, uniqueness: true, presence: true, length: {minimum: 5}
    validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
    validates_uniqueness_of :user_name
    validates_uniqueness_of :email
    validates :password, presence: true, confirmation: true, length: { minimum: 10 }, if: :new_record?

    def self.authenticate_user(user_name, password)
        user = Xaaron::User.find_by_user_name(user_name)
        if(user && user.password == BCrypt::Engine.hash_secret(password, user.salt))
            user
        else
            nil
        end
    end

    def encrypt_password
        if password.present?
            self.salt = BCrypt::Engine.generate_salt
            self.password = BCrypt::Engine.hash_secret(password, salt)
        end
    end
  end
end

From there, any test that tests the user password validation or seeing if the password we generate is the same one we store in the database fails because factory girl is passing nil to the database.

Failed Test Output

  5) Xaaron::User Validation checks should validate a user based on login credentials
     Failure/Error: Xaaron::User.authenticate_user(@user.user_name, @user.password).should == @user
       expected: #<Xaaron::User id: 5, first_name: "Adam", last_name: "Something", user_name: "user9", email: "user8@example.com", password: nil, salt: "$2a$10$Y1m4YK.4znWVz2icp0ENtO", created_at: "2014-04-06 15:20:53", updated_at: "2014-04-06 15:20:53">
            got: nil (using ==)
     # ./spec/models/xaaron/user_spec.rb:33:in `block (3 levels) in <top (required)>'

You can see in the above that: password: nil which it shouldn't be ...

The Test that Generated the fail

it "should validate a user based on login credentials" do
  @user = FactoryGirl.create(:user)
  Xaaron::User.authenticate_user(@user.user_name, @user.password).should == @user
end

As requested - Schema

ActiveRecord::Schema.define(version: 20140323000123) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "xaaron_users", force: true do |t|
    t.string   "first_name"
    t.string   "last_name"
    t.string   "user_name"
    t.string   "email"
    t.string   "password"
    t.string   "salt"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end
Was it helpful?

Solution

Remove attr_accessor :password from the class User. As ActiveRecord would treat it as a virtual attribute and it will not be saved in the database.

If you notice rspec failure message received:

expected: #<Xaaron::User id: 5, first_name: "Adam", last_name: "Something", user_name: "user9", email: "user8@example.com", password: nil, salt: "$2a$10$Y1m4YK.4znWVz2icp0ENtO", created_at: "2014-04-06 15:20:53", updated_at: "2014-04-06 15:20:53">

password is not saved in database. Make sure to remove the attr_accessor from the User model so password is treated as field and would be saved in database.

Next, you need to update the example as below:

it "should validate a user based on login credentials" do
  @user = FactoryGirl.create(:user)
  @login_user = FactoryGirl.build(:user) 
  Xaaron::User.authenticate_user(@user.user_name, @login_user.password).should == @user 
end

@user contains the actual record created in database, so @user.password has the encrypted password already. Your example fails because you are sending the encrypted password to authenticate_user method and re-encrypting the already encrypted password with:

BCrypt::Engine.hash_secret(password, user.salt)

In reality, for your example to pass what you need to pass is the actual password without any encryption. That is why I added @login_user in your example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top