Тестирование обновления пароля пользователя с помощью RSpec

#ruby #rspec #sequel #roda

#рубиновый #rspec #продолжение #рода

Вопрос:

У меня есть API, построенный с помощью стека Roda Sequel. Вот как User выглядит моя модель:

 # 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
 

и у меня есть следующий тест:

 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
 

Этот пример терпит неудачу:

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

но этот проходит:

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

Может ли кто-нибудь объяснить мне, почему мой первый пример терпит неудачу?

Комментарии:

1. is_password? является псевдонимом для == so, оба должны пройти. Что делать user.password и BCrypt::Password.new(user.password_hash) возвращаться? (из ваших примеров RSpec)

2. Есть такой плагин для продолжения github.com/mlen/sequel_secure_password . Он давно не обновлялся, но, похоже, работает.

Ответ №1:

Перепишите это

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

к этому

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

Ваш объект ruby User содержит хэшированную версию этого пароля

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

и чтобы сравнить их, вам нужно зашифровать свой «новый пароль» и сравнить два хэша. Вы можете легко зашифровать любую строку с помощью bcrypt, но для ее расшифровки потребуется много времени (годы). Обычно перед проверкой пароля зашифруйте свой input password пароль тем же алгоритмом, который содержит ваша база данных

Ответ №2:

Вы сами отвечаете на свой вопрос: вы хэшируете пароль перед его сохранением (в установщике паролей в User.rb :

   # 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
 

В вашем первом тесте вы пытаетесь сравнить хэшированный пароль с неэшированной строкой.