M.Hartl 的 Ruby on Rails 教程(第 3 版),第 10 章 (10.54) 中的密码重置测试失败 [英] Password Reset Test failing in M.Hartl's Ruby on Rails Tutorial (3rd edition), Chapter 10 (10.54)
问题描述
到目前为止,我的所有测试/断言都已按预期通过.我非常有信心应用程序本身运行良好,但我在这个测试中没有通过一个断言.我是一个 Rails 新手,但我从其他编程经验中知道,现在不解决这个问题很可能会让小鬼恶化.
出于对 SO 成员时间的尊重(并认识到我的 Rails 新手状态),在提出这个问题之前,我已经做了几乎所有人类可能的故障排除,包括:
- 重新启动了我的本地 Rails 服务器(多次).
- 查看此处有关 Rails 教程(及其他版本)中测试失败的所有其他问题.
- 深入研究 Minitest 文档以了解我遇到的错误.
- 用@Mhartl 的 Github 存储库中的代码替换了我的(密码重置)集成测试代码.
- 在我的测试中尝试了Rails.logger.debug"消息以通过日志消息进行调试.
断言失败消息:
FAIL["test_password_resets", PasswordResetsTest, 2015-07-30 13:42:42 -0400] test_password_resets#PasswordResetsTest (1438278162.33s)断言失败,未给出任何消息.test/integration/password_resets_test.rb:57:in `block in <class:PasswordResetsTest>'
我的 password_resets_test.rb(全部):
需要'test_helper'类 PasswordResetsTest
第 57 行(失败的断言)是:
assert is_logged_in?
我的 test_helper.rb 的相关部分:
ENV['RAILS_ENV'] ||= 'test'# 为简洁起见编辑...# 如果测试用户已登录,则返回 true.def is_logged_in?!session[:user_id].nil?结尾# 以测试用户登录.def log_in_as(user, options = {})密码 = 选项[:密码] ||'密码'remember_me = options[:remember_me] ||'1'如果集成_测试?post login_path, session: { email: user.email,密码:密码,记住_我:记住_我}别的session[:user_id] = user.id结尾结尾私人的# 在集成测试中返回 true.定义集成_测试?定义?(post_via_redirect)结尾结尾
这是我的 password_resets_controller.rb:
class PasswordResetsController <应用控制器before_action :get_user, only: [:edit, :update]before_action :valid_user, only: [:edit, :update]before_action :check_expiration, only: [:edit, :update] # Listing 10.52定义创建@user = User.find_by(email: params[:password_reset][:email].downcase)如果@user@user.create_reset_digest@user.send_password_reset_emailflash[:info] = "带有密码重置说明的电子邮件发送"重定向到 root_url别的flash.now[:danger] = "未找到电子邮件地址"呈现新"结尾结尾定义更新如果 params[:user][:password].empty?flash.now[:danger] = "密码不能为空"渲染编辑"elsif @user.update_attributes(user_params)登录@用户flash[:success] = "密码已重置."重定向到@user别的渲染编辑"结尾结尾私人的定义用户参数params.require(:user).permit(:password,:password_confirmation)结尾# 过滤前:def get_user@user = User.find_by(email: params[:email])结尾# 确认有效用户.def valid_user除非 (@user && @user.activated? &&@user.authenticated?(:reset, params[:id]))重定向到 root_url结尾结尾# 检查重置令牌的到期时间.def check_expiration如果@user.password_reset_expired?flash[:danger] = "密码重置已过期."redirect_to new_password_reset_url结尾结尾结尾
这是我的 user.rb(已编辑):
class User
我的 Gemfile(已编辑):
source 'https://rubygems.org'红宝石'2.2.2'宝石导轨",4.2.2"# 为简洁起见编辑...组:开发,:测试做宝石'sqlite3','1.3.9'宝石'再见','3.4.0'宝石网络控制台",2.0.0.beta3"宝石春天",1.1.3"结尾组:测试做gem 'minitest-reporters', '1.0.5'宝石mini_backtrace",0.1.3"gem 'guard-minitest', '2.3.1'结尾# 为简洁起见编辑...
我从事软件开发已有很长时间了,这听起来像是一个经典案例,即绕着轴试图找到一个微妙的问题,而忽略了一些明显的问题.我当然知道我花在这上面的时间比明智的要多,而且我希望在这个过程中我在我的代码中注入了一些废话.
预先感谢您的帮助.
我认为这是一个非常简单的错误:
在您的测试中,您在第 53 行提交密码重置表单以为用户选择新密码,但您选择的新密码(foobaz")只有 6 个字符长:
patch password_reset_path(user.reset_token),电子邮件:user.email,用户:{ 密码:foobaz",密码确认:foobaz"}
但是在 user.rb
中你规定密码必须至少为 8 个字符:
验证:密码,存在:真,长度:{最小值:8},allow_nil:真
这就是密码重置失败的原因.使用更长的密码,你应该没问题!
要弄清楚这一点,您可以在失败的断言之前添加这一行:
放入 html_document
哪个会将呈现的 HTML 转储到您的终端窗口,您会在那里找到...
该表单包含 1 个错误.<ul><li>密码太短(最少8个字符)</li>
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:
- Restarted my local Rails server (multiple times).
- Looked at every other question here about tests failing in the Rails Tutorial (and beyond).
- Dug into the Minitest documentation to understand the error I'm getting.
- Replaced my (password resets) integration test code with code from @Mhartl's Github repo.
- 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.
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>
这篇关于M.Hartl 的 Ruby on Rails 教程(第 3 版),第 10 章 (10.54) 中的密码重置测试失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!