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

时间:2015-08-04 02:01:05

标签: ruby-on-rails ruby railstutorial.org

到目前为止,我所有的测试/断言都按预期通过了。我非常有信心应用程序本身工作正常,但我在这个测试中没有一个断言。我是Rails的新手,但我从其他编程经验中得知,现在不能解决这个问题可能会让人感到厌烦。

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

  1. 重启我的本地Rails服务器(多次)。
  2. 在这里看到关于Rails Tutorial(及其后)中测试失败的所有其他问题。
  3. 挖掘Minitest文档以了解我遇到的错误。
  4. 用@ Mhartl的Github repo代码替换了我的(密码重置)集成测试代码。
  5. 在我的测试中尝试通过日志消息调试“Rails.logger.debug”消息。
  6. 断言失败消息:

    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 ...
    

    我已经在软件开发方面工作了很长时间,这听起来像是一个经典的案例,围绕着轴试图找到一个微妙的问题,同时忽略了一些明显的问题。我当然知道我花了更多时间在这上面而不是明智,我希望在这个过程中我在代码中注入了一些废话。

    提前感谢您的帮助。

1 个答案:

答案 0 :(得分:4)

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

在测试中,在第53行,您提交密码重置表单以为用户选择新密码,但您选择的新密码(&#34; foobaz&#34;)仅为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>