ruby-on-rails - M.Hartl 的 Ruby on Rails 教程(第 3 版)第 10 章 (10.54) 中的密码重置测试失败

标签 ruby-on-rails ruby railstutorial.org

到目前为止,我的所有测试/断言均已按预期通过。我非常有信心应用程序本身工作正常,但我在这个测试中没有通过一个断言。我是 Rails 菜鸟,但我从其他编程经验中知道,现在不解决这个问题可能会导致问题恶化。

出于对 SO 成员时间的尊重(并认识到我的 Rails 新手状态),在提出这个问题之前,我已经做了几乎所有可能的故障排除,包括:

  1. 重新启动了我的本地 Rails 服务器(多次)。
  2. 查看了此处有关 Rails 教程(及其他教程)中测试失败的所有其他问题。
  3. 深入研究 Minitest 文档以了解我遇到的错误。
  4. 用 @Mhartl 的 Github 存储库中的代码替换了我的(密码重置)集成测试代码。
  5. 在我的测试中尝试使用“Rails.logger.debug”消息来通过日志消息进行调试。

断言失败消息:

FAIL["test_password_resets", PasswordResetsTest, 2015-07-30 13:42:42 -0400] test_password_resets#PasswordResetsTest (1438278162.33s)
Failed assertion, no message given.
    test/integration/password_resets_test.rb:57:in `block in <class:PasswordResetsTest>'

我的password_resets_test.rb(完整):

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest
  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

  test "password resets" do
    get new_password_reset_path
    assert_template 'password_resets/new'
    # Invalid email
    post password_resets_path, password_reset: { email: "" }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    # Valid email
    post password_resets_path, password_reset: { email: @user.email }
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    assert_equal 1, ActionMailer::Base.deliveries.size
    assert_not flash.empty?
    assert_redirected_to root_url
    # Password reset form
    user = assigns(:user)
    # Wrong email
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # Inactive user
    user.toggle!(:activated)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url
    user.toggle!(:activated)
    # Right email, wrong token
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # Right email, right token
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_template 'password_resets/edit'
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # Invalid password & confirmation
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "foobaz",
                  password_confirmation: "barquux" }
    assert_select 'div#error_explanation'
    # Empty password
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "",
                  password_confirmation: "" }
    assert_not flash.empty?
    # Valid password & confirmation
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "foobaz",
                  password_confirmation: "foobaz" }
    assert is_logged_in? #<=== FAILING ASSERTION
    assert_not flash.empty?
    assert_redirected_to user
  end
end

第 57 行(失败的断言)是:

assert is_logged_in?

我的test_helper.rb的相关部分:

ENV['RAILS_ENV'] ||= 'test'

  # Edited for brevity ...

  # Returns true if a test user is logged in.
  def is_logged_in?
    !session[:user_id].nil?
  end

  # Logs in a test user.
  def log_in_as(user, options = {})
    password    = options[:password]    || 'password'
    remember_me = options[:remember_me] || '1'
    if integration_test?
      post login_path, session: { email:       user.email,
                                  password:    password,
                                  remember_me: remember_me }
    else
      session[:user_id] = user.id
    end
  end

  private
    # Returns true inside an integration test.
    def integration_test?
      defined?(post_via_redirect)
    end
end

这是我的password_resets_controller.rb:

class PasswordResetsController < ApplicationController
  before_action :get_user,   only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update] # Listing 10.52

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def update
    if params[:user][:password].empty? 
      flash.now[:danger] = "Password can't be empty"
      render 'edit'
    elsif @user.update_attributes(user_params)
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end

  private

    def user_params
      params.require(:user).permit(:password, :password_confirmation)
    end

    # Before filters:

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # Confirms a valid user.
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end

    # Checks expiration of reset token.
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end
end

这是我的 user.rb(已编辑):

class User < ActiveRecord::Base
  # Add tokens to class accessor:
  attr_accessor :remember_token, :activation_token, :reset_token

  # Edited for brevity ...

  # Returns true if the given token matches the digest.
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil? # ... implied else here ...
    BCrypt::Password.new(digest).is_password?(token)
  end

  # Edited for brevity ...

  # Sets the password reset attributes.
  def create_reset_digest
    self.reset_token = User.new_token
    update_attribute(:reset_digest,  User.digest(reset_token))
    update_attribute(:reset_sent_at, Time.zone.now)
  end

  # Sends password reset email.
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end

  # Returns true if a password reset has expired.
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

 # Edited for brevity ...

end

我的 Gemfile(已编辑):

source 'https://rubygems.org'
ruby '2.2.2'
gem 'rails',        '4.2.2'

# Edited for brevity ...

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

group :test do
  gem 'minitest-reporters', '1.0.5'
  gem 'mini_backtrace',     '0.1.3'
  gem 'guard-minitest',     '2.3.1'
end

# Edited for brevity ...

我从事软件开发已经很长时间了,这听起来像是一个典型的案例,试图找到一个微妙的问题,同时忽略了一些明显的问题。我绝对知道我在这方面花费的时间超出了合理范围,并且我希望在此过程中我在代码中注入(inject)了一些废话。

预先感谢您的帮助。

最佳答案

我认为这是一个非常简单的错误:

在您的测试中,第 53 行您将提交密码重置表单来为用户选择一个新密码,但您选择的新密码(“foobaz”)只有 6 个字符长:

patch password_reset_path(user.reset_token),
      email: user.email,
      user: { password:              "foobaz",
              password_confirmation: "foobaz" }

但是在 user.rb 中您规定密码必须至少为 8 个字符:

validates :password, presence: true, length: { minimum: 8 }, allow_nil: true

这就是密码重置失败的原因。使用更长的密码应该没问题!

要解决这个问题,您可以在失败的断言之前添加此行:

puts html_document

这会将渲染的 HTML 转储到您的终端窗口,您将在其中找到...

<div class="alert alert-danger">
  The form contains 1 error.
</div>
<ul>
  <li>Password is too short (minimum is 8 characters)</li>
</ul>

关于ruby-on-rails - M.Hartl 的 Ruby on Rails 教程(第 3 版)第 10 章 (10.54) 中的密码重置测试失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31799519/

相关文章:

ruby-on-rails - 如何在 Rails 中使用 "_blank"或 "_new"

ruby-on-rails-4 - Rails::MailersController#preview 中的 ArgumentError(参数数量错误(1 代表 0))

javascript - 用户索引页面上的 Ajax 按钮未更新 - Rails 教程 4

ruby-on-rails - railstutorial.org 中的 SessionsHelper : Should helpers be general-purpose modules for code not needed in views?

ruby-on-rails - 为什么 mod_passenger.so 会出现此权限错误?

ruby-on-rails - ActionMailer 和自签名的 SSL 证书

ruby-on-rails - ruby on rails group by 空值问题

ruby-on-rails - Rails 得到两个日期之间的差异

ruby-on-rails - Rails 将 Controller 逻辑移动到模型中

ruby-on-rails - 在 Mongo 数据库中克隆文档