Password Reset Test failing in M.Hartl's Ruby on Rails Tutorial (3rd edition), Chapter 10 (10.54)

痴心易碎 提交于 2019-12-10 10:49:53

问题


All my tests/assertions have passed as expected up until now. I'm pretty confident that the application itself is working fine, but I'm failing one assertion in this test. I'm a Rails rookie, but I know from other programming experiences that not resolving this now is likely leaving a gremlin to fester.

Out of respect for SO members' time (and recognizing my Rails-novice state) I have done just about everything humanly possible to troubleshoot before asking this question, including:

  1. Restarted my local Rails server (multiple times).
  2. Looked at every other question here about tests failing in the Rails Tutorial (and beyond).
  3. Dug into the Minitest documentation to understand the error I'm getting.
  4. Replaced my (password resets) integration test code with code from @Mhartl's Github repo.
  5. Tried "Rails.logger.debug" messages in my test to debug via log messages.

Assertion failure message:

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

My password_resets_test.rb (in its entirety):

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

Line 57 (the failing assertion) is:

assert is_logged_in?

Relevant parts of my 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

Here's my 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

Here's my user.rb (edited):

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

My Gemfile (edited):

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

I've been around software development for a long time, and this smells like a classic case of getting wrapped around the axle trying to find a subtle problem while overlooking something obvious. I definitely know I spent more time on this than is sensible, and I expect I have injected some nonsense in my code during that process.

Thanks in advance for your help.


回答1:


I think this is a pretty simple mistake:

In your test, on line 53 you are submitting the password reset form to choose a new password for the user, but the new password you've chosen ("foobaz") is only 6 characters long:

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

But then in user.rb you stipulate that passwords must be at least 8 characters:

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

So that's why password reset fails. Use a longer password and you should be OK!

To figure this out, you could have added this line just before the failing assertion:

puts html_document

Which would dump the rendered HTML to your terminal window, where you would find...

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


来源:https://stackoverflow.com/questions/31799519/password-reset-test-failing-in-m-hartls-ruby-on-rails-tutorial-3rd-edition-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!