Search code examples
rubyrspecsequelroda

Testing user password update with RSpec


I have an API built with Roda + Sequel stack. Here is how my User model looks like:

# frozen_string_literal: true

# {User} is model responsible for storing {User} authentication informations like email and password.
#
# @!attribute id
#   @return [UUID] ID of the {User} in UUID format.
#
# @!attribute email
#   @return [String] Email of the {User}, it's stored in the PostgreSQL citext column.
#
# @!attribute password_hash
#   @return [String] {User} hashed password with bcrypt.
#
# @!attribute created_at
#   @return [DateTime] Time when {User} was created.
#
# @!attribute updated_at
#   @return [DateTime] Time when {User} was updated
class User < Sequel::Model
  # It returns instance BCrypt::Password based on value in password_hash column.
  #
  # @return [BCrypt::Password ] based on value in password_hash column.
  #
  # @example Get {User} password hash:
  #   User.new(password: 'test').password #=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
  def password
    @password ||= BCrypt::Password.new(password_hash)
  end

  # It sets password_hash column with hashed user password.
  #
  # @return [String] user password hash.
  #
  # @example Set {User} password:
  #   User.new(password: 'test').password_hash #=> "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
  def password=(new_password)
    @password = BCrypt::Password.create(new_password)

    self.password_hash = @password
  end
end

and I have the following test:

describe 'update user password' do
  let(:params) { { password: 'new-password' } }

  before do
    put '/api/v1/update_password', params

    user.reload
  end

  it 'returns 200 HTTP status' do
    expect(response.status).to eq 200
  end
   
  This one is failing.
  it 'updates user password' do
    expect(user.password == 'new-password').to eq true
  end
  
  # This one is passing.
  it 'updates user password' do
    expect(BCrypt::Password.new(user.password_hash).is_password?('new-password')).to eq true
  end
end

This example is failing:

  it 'updates user password' do
    expect(user.password == 'new-password').to eq true
  end
 

but this one is passing:

 it 'updates user password' do
    expect(BCrypt::Password.new(user.password_hash).is_password?('new-password')).to eq true
  end

Could someone explain to me why my first example is failing?


Solution

  • Rewrite this one

    it 'updates user password' do
      expect(user.password == 'new-password').to eq true
    end
    

    to this one

    it 'updates user password' do
      expect(user.password).to eq(BCrypt::Password.create('new-password'))
    end
    

    Your ruby object User contains hashed version of that password

    user.password
    => "$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS"
    '$2a$12$FktSw7HPYEUYSPBdmmsiXe26II6UV5gvyn2ECwOflTYHP94Hrm2mS' == 'new-password'
    => false
    

    and in order to compare them you have to bcrypt your 'new-password' and compare two hashes. You can easily crypt any string with bcrypt, but decrypt it will take much time (years). It is common before verifying password, encrypt your input password with the same algorithm that your database contains