test/test_password_archivable.rb
# frozen_string_literal: true

require 'test_helper'

class TestPasswordArchivable < ActiveSupport::TestCase
  setup do
    Devise.password_archiving_count = 2
  end

  teardown do
    Devise.password_archiving_count = 1
  end

  def set_password(user, password)
    user.password = password
    user.password_confirmation = password
    user.save!
  end

  test 'required_fields should be an empty array' do
    assert_empty Devise::Models::PasswordArchivable.required_fields(User)
  end

  test 'cannot use same password' do
    user = User.create email: generate_unique_email, password: 'Password1', password_confirmation: 'Password1'
    assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
  end

  test 'indirectly saving associated user does not cause deprecation warning' do
    if Rails.gem_version >= Gem::Version.new('7.1')
      old_behavior = Rails.application.deprecators[:active_record].behavior
      Rails.application.deprecators.behavior = :raise
    else
      old_behavior = ActiveSupport::Deprecation.behavior
      ActiveSupport::Deprecation.behavior = :raise
    end

    user = User.new email: generate_unique_email, password: 'Password1', password_confirmation: 'Password1'
    widget = Widget.new(user: user)
    assert_nothing_raised { widget.save }

    if Rails.gem_version >= Gem::Version.new('7.1')
      Rails.application.deprecators.behavior = old_behavior
    else
      ActiveSupport::Deprecation.behavior = old_behavior
    end
  end

  test 'does not save an OldPassword if user password was originally nil' do
    user = User.new(email: generate_unique_email, password: nil, password_confirmation: nil)
    set_password(user, 'Password1')
    assert_equal 0, OldPassword.count
  end

  test 'cannot reuse archived passwords' do
    assert_equal 2, Devise.password_archiving_count

    user = User.create! email: generate_unique_email, password: 'Password1', password_confirmation: 'Password1'
    assert_equal 0, OldPassword.count
    set_password(user, 'Password2')
    assert_equal 1, OldPassword.count

    assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
    set_password(user, 'Password3')
    assert_equal 2, OldPassword.count

    # rotate first password out of archive
    assert set_password(user, 'Password4')

    # archive count was 2, so first password should work again
    assert set_password(user, 'Password1')
    assert set_password(user, 'Password2')
  end

  test 'the option should be dynamic during runtime' do
    class ::User
      def archive_count
        1
      end
    end

    user = User.create email: generate_unique_email, password: 'Password1', password_confirmation: 'Password1'

    assert set_password(user, 'Password2')

    assert_raises(ORMInvalidRecordException) { set_password(user, 'Password2') }

    assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
  end

  test 'default sort orders do not affect archiving' do
    class ::OldPassword
      default_scope { order(created_at: :asc) }
    end

    assert_equal 2, Devise.password_archiving_count

    user = User.create! email: generate_unique_email, password: 'Password1', password_confirmation: 'Password1'
    assert_equal 0, OldPassword.count
    set_password(user, 'Password2')
    assert_equal 1, OldPassword.count

    assert_raises(ORMInvalidRecordException) { set_password(user, 'Password1') }
    set_password(user, 'Password3')
    assert_equal 2, OldPassword.count

    # rotate first password out of archive
    assert set_password(user, 'Password4')

    # archive count was 2, so first password should work again
    assert set_password(user, 'Password1')
    assert set_password(user, 'Password2')
  end
end
