# frozen_string_literal: true

require 'test_helper'

class PasswordTest < Devise::IntegrationTest

  def visit_new_password_path
    visit new_user_session_path
    click_link 'Forgot your password?'
  end

  def request_forgot_password(&block)
    visit_new_password_path
    assert_response :success
    assert_not warden.authenticated?(:user)

    fill_in 'email', with: 'user@test.com'
    yield if block_given?

    Devise.stubs(:friendly_token).returns("abcdef")
    click_button 'Send me password reset instructions'
  end

  def reset_password(options = {}, &block)
    unless options[:visit] == false
      visit edit_user_password_path(reset_password_token: options[:reset_password_token] || "abcdef")
      assert_response :success
    end

    fill_in 'New password', with: '987654321'
    fill_in 'Confirm new password', with: '987654321'
    yield if block_given?
    click_button 'Change my password'
  end

  test 'reset password should send to user record email and avoid case mapping collisions' do
    create_user(email: 'user@github.com')

    request_forgot_password do
      fill_in 'email', with: 'user@gıthub.com'
    end

    mail = ActionMailer::Base.deliveries.last
    assert_equal ['user@github.com'], mail.to
  end

  test 'reset password with email of different case should succeed when email is in the list of case insensitive keys' do
    create_user(email: 'Foo@Bar.com')

    request_forgot_password do
      fill_in 'email', with: 'foo@bar.com'
    end

    assert_current_url '/users/sign_in'
    assert_contain 'You will receive an email with instructions on how to reset your password in a few minutes.'
  end

  test 'reset password with email should send an email from a custom mailer' do
    create_user(email: 'Foo@Bar.com')

    User.any_instance.stubs(:devise_mailer).returns(Users::Mailer)
    request_forgot_password do
      fill_in 'email', with: 'foo@bar.com'
    end

    mail = ActionMailer::Base.deliveries.last
    assert_equal ['custom@example.com'], mail.from
    assert_match edit_user_password_path(reset_password_token: 'abcdef'), mail.body.encoded
  end

  test 'reset password with email of different case should fail when email is NOT the list of case insensitive keys' do
    swap Devise, case_insensitive_keys: [] do
      create_user(email: 'Foo@Bar.com')

      request_forgot_password do
        fill_in 'email', with: 'foo@bar.com'
      end

      assert_response :success
      assert_current_url '/users/password'
      assert_have_selector "input[type=email][value='foo@bar.com']"
      assert_contain 'not found'
    end
  end

  test 'reset password with email with extra whitespace should succeed when email is in the list of strip whitespace keys' do
    create_user(email: 'foo@bar.com')

    request_forgot_password do
      fill_in 'email', with: ' foo@bar.com '
    end

    assert_current_url '/users/sign_in'
    assert_contain 'You will receive an email with instructions on how to reset your password in a few minutes.'
  end

  test 'reset password with email with extra whitespace should fail when email is NOT the list of strip whitespace keys' do
    swap Devise, strip_whitespace_keys: [] do
      create_user(email: 'foo@bar.com')

      request_forgot_password do
        fill_in 'email', with: ' foo@bar.com '
      end

      assert_response :success
      assert_current_url '/users/password'
      assert_have_selector "input[type=email][value=' foo@bar.com ']"
      assert_contain 'not found'
    end
  end

  test 'authenticated user should not be able to visit forgot password page' do
    sign_in_as_user
    assert warden.authenticated?(:user)

    get new_user_password_path

    assert_response :redirect
    assert_redirected_to root_path
  end

  test 'not authenticated user should be able to request a forgot password' do
    create_user
    request_forgot_password

    assert_current_url '/users/sign_in'
    assert_contain 'You will receive an email with instructions on how to reset your password in a few minutes.'
  end

  test 'not authenticated user with invalid email should receive an error message' do
    request_forgot_password do
      fill_in 'email', with: 'invalid.test@test.com'
    end

    assert_response :success
    assert_current_url '/users/password'
    assert_have_selector "input[type=email][value='invalid.test@test.com']"
    assert_contain 'not found'
  end

  test 'authenticated user should not be able to visit edit password page' do
    sign_in_as_user
    get edit_user_password_path
    assert_response :redirect
    assert_redirected_to root_path
    assert warden.authenticated?(:user)
  end

  test 'not authenticated user without a reset password token should not be able to visit the page' do
    get edit_user_password_path
    assert_response :redirect
    assert_redirected_to "/users/sign_in"
  end

  test 'not authenticated user with invalid reset password token should not be able to change their password' do
    user = create_user
    reset_password reset_password_token: 'invalid_reset_password'

    assert_response :success
    assert_current_url '/users/password'
    assert_have_selector '#error_explanation'
    assert_contain %r{Reset password token(.*)invalid}
    assert_not user.reload.valid_password?('987654321')
  end

  test 'not authenticated user with valid reset password token but invalid password should not be able to change their password' do
    user = create_user
    request_forgot_password
    reset_password do
      fill_in 'Confirm new password', with: 'other_password'
    end

    assert_response :success
    assert_current_url '/users/password'
    assert_have_selector '#error_explanation'
    assert_contain %r{Password confirmation doesn['’]t match Password}
    assert_not user.reload.valid_password?('987654321')
  end

  test 'not authenticated user with valid data should be able to change their password' do
    user = create_user
    request_forgot_password
    reset_password

    assert_current_url '/'
    assert_contain 'Your password has been changed successfully. You are now signed in.'
    assert user.reload.valid_password?('987654321')
  end

  test 'after entering invalid data user should still be able to change their password' do
    user = create_user
    request_forgot_password

    reset_password {  fill_in 'Confirm new password', with: 'other_password' }
    assert_response :success
    assert_have_selector '#error_explanation'
    assert_not user.reload.valid_password?('987654321')

    reset_password visit: false
    assert_contain 'Your password has been changed successfully.'
    assert user.reload.valid_password?('987654321')
  end

  test 'sign in user automatically after changing its password' do
    create_user
    request_forgot_password
    reset_password

    assert warden.authenticated?(:user)
  end

  test 'does not sign in user automatically after changing its password if config.sign_in_after_reset_password is false' do
    swap Devise, sign_in_after_reset_password: false do
      create_user
      request_forgot_password
      reset_password

      assert_contain 'Your password has been changed successfully.'
      assert_not_contain 'You are now signed in.'
      assert_equal new_user_session_path, @request.path
      assert_not warden.authenticated?(:user)
    end
  end

  test 'does not sign in user automatically after changing its password if resource_class.sign_in_after_reset_password is false' do
    swap_model_config User, sign_in_after_reset_password: false do
      create_user
      request_forgot_password
      reset_password

      assert_contain 'Your password has been changed successfully'
      assert_not_contain 'You are now signed in.'
      assert_equal new_user_session_path, @request.path
      assert_not warden.authenticated?(:user)
    end
  end

  test 'sign in user automatically after changing its password if resource_class.sign_in_after_reset_password is true' do
    swap Devise, sign_in_after_reset_password: false do
      swap_model_config User, sign_in_after_reset_password: true do
        create_user
        request_forgot_password
        reset_password

        assert warden.authenticated?(:user)
      end
    end
  end

  test 'does not sign in user automatically after changing its password if it\'s locked and unlock strategy is :none or :time' do
    [:none, :time].each do |strategy|
      swap Devise, unlock_strategy: strategy do
        create_user(locked: true)
        request_forgot_password
        reset_password

        assert_contain 'Your password has been changed successfully.'
        assert_not_contain 'You are now signed in.'
        assert_equal new_user_session_path, @request.path
        assert_not warden.authenticated?(:user)
      end
    end
  end

  test 'unlocks and signs in locked user automatically after changing it\'s password if unlock strategy is :email' do
    swap Devise, unlock_strategy: :email do
      user = create_user(locked: true)
      request_forgot_password
      reset_password

      assert_contain 'Your password has been changed successfully.'
      assert_not user.reload.access_locked?
      assert warden.authenticated?(:user)
    end
  end

  test 'unlocks and signs in locked user automatically after changing it\'s password if unlock strategy is :both' do
    swap Devise, unlock_strategy: :both do
      user = create_user(locked: true)
      request_forgot_password
      reset_password

      assert_contain 'Your password has been changed successfully.'
      assert_not user.reload.access_locked?
      assert warden.authenticated?(:user)
    end
  end

  test 'reset password request with valid e-mail in JSON format should return empty and valid response' do
    create_user
    post user_password_path(format: 'json'), params: { user: {email: "user@test.com"} }
    assert_response :success
    assert_equal({}.to_json, response.body)
  end

  test 'reset password request with invalid e-mail in JSON format should return valid response' do
    create_user
    post user_password_path(format: 'json'), params: { user: {email: "invalid.test@test.com"} }
    assert_response :unprocessable_entity
    assert_includes response.body, '{"errors":{'
  end

  test 'reset password request with invalid e-mail in JSON format should return empty and valid response in paranoid mode' do
    swap Devise, paranoid: true do
      create_user
      post user_password_path(format: 'json'), params: { user: {email: "invalid@test.com"} }
      assert_response :success
      assert_equal({}.to_json, response.body)
    end
  end

  test 'change password with valid parameters in JSON format should return valid response' do
    create_user
    request_forgot_password
    put user_password_path(format: 'json'), params: { user: {
      reset_password_token: 'abcdef', password: '987654321', password_confirmation: '987654321'
    } }
    assert_response :success
    assert warden.authenticated?(:user)
  end

  test 'change password with invalid token in JSON format should return invalid response' do
    create_user
    request_forgot_password
    put user_password_path(format: 'json'), params: { user: {reset_password_token: 'invalid.token', password: '987654321', password_confirmation: '987654321'} }
    assert_response :unprocessable_entity
    assert_includes response.body, '{"errors":{'
  end

  test 'change password with invalid new password in JSON format should return invalid response' do
    user = create_user
    request_forgot_password
    put user_password_path(format: 'json'), params: { user: {reset_password_token: user.reload.reset_password_token, password: '', password_confirmation: '987654321'} }
    assert_response :unprocessable_entity
    assert_includes response.body, '{"errors":{'
  end

  test "when in paranoid mode and with an invalid e-mail, asking to reset a password should display a message that does not indicates that the e-mail does not exists in the database" do
    swap Devise, paranoid: true do
      visit_new_password_path
      fill_in "email", with: "arandomemail@test.com"
      click_button 'Send me password reset instructions'

      assert_not_contain "1 error prohibited this user from being saved:"
      assert_not_contain "Email not found"
      assert_contain "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
      assert_current_url "/users/sign_in"
    end
  end

  test "when in paranoid mode and with a valid e-mail, asking to reset password should display a message that does not indicates that the email exists in the database and redirect to the failure route" do
    swap Devise, paranoid: true do
      user = create_user
      visit_new_password_path
      fill_in 'email', with: user.email
      click_button 'Send me password reset instructions'

      assert_contain "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
      assert_current_url "/users/sign_in"
    end
  end

  test "after recovering a password, should set failed attempts to 0" do
    user = create_user
    user.update_attribute(:failed_attempts, 10)

    assert_equal 10, user.failed_attempts
    request_forgot_password
    reset_password

    assert warden.authenticated?(:user)
    user.reload
    assert_equal 0, user.failed_attempts
  end
end
