【问题标题】:Password validation fails in two opposed scenarios密码验证在两种相反的情况下失败
【发布时间】:2017-11-03 23:40:42
【问题描述】:

我正在阅读 Michael Hartl 的 Ruby on Rails 教程,并且遇到了一个有趣的难题。我会做错事,所以我需要你的帮助来找到问题。

问题围绕在User 模型中验证密码属性。此属性的初始验证是:

validates :password,  presence: true, 
                        confirmation: true, 
                        length: { minimum: 6 }

这需要密码的最小长度,旨在满足新用户创建其实例的情况。

在本书的指导下,我创建了以下测试(我希望我使用了 Rspec!)。这些测试检查验证是否有效:

test "password must not be blank or made up of spaces" do
  @user.password = @user.password_confirmation = " "
  assert_not @user.valid?
end

test "password must not be empty/nil" do
  @user.password = @user.password_confirmation = ""
  assert_not @user.valid?
end

因此,我们正在检查密码字段不能包含空格或 nil 条目。通过当前的验证,这些测试通过。一切都很好。

我已经允许用户编辑他们的个人资料。这使用户可以选择更改他们的姓名、电子邮件地址和密码/确认。为了允许用户更改他们不想更改的密码,模型的密码属性中添加了额外的验证,添加allow_blank: true,例如:

validates :password,  presence: true, 
                      confirmation: true, 
                      length: { minimum: 6 }, 
                      allow_blank: true # added this!

因此,如果用户不想更改个人资料,现在可以在编辑个人资料时将两个密码字段留空。这满足了测试:

test "successful edit" do
  log_in_as @user
  get edit_user_path(@user)
  assert_template 'users/edit'
  name = "Foo Bar"
  email = "foo@valid.co.uk"
  patch user_path(@user), params: { user: { name: name,
                                            email: email,
                                            password: "",
                                            password_confirmation: "" } }
  assert_not flash.empty?
  assert_redirected_to @user
  @user.reload
  assert_equal @user.name, name
  assert_equal @user.email, email
end

这使用户可以只编辑他们的姓名和电子邮件,并且通过将他们的两个密码字段留空,无需更改或重新输入他们的password。如上所述,这会在长时间通过的测试中引发 FAIL,例如:

test "password must not be blank or made up of spaces" do
  @user.password = @user.password_confirmation = " "
  assert_not @user.valid?
end

测试失败,因为用户已通过验证。测试 nil 的测试稍有不同,不是空白,通过:

test "password must not be empty/nil" do
  @user.password = @user.password_confirmation = ""
  assert_not @user.valid?
end

所以“” 的密码被捕获,但“ “ 的密码适用于创建新用户或编辑现有用户。

在密码的用户模型验证中添加allow_blank: true 似乎是导致此问题的原因。所以,我被困在两个测试失败之间。如果我省略allow_blank: true,则此测试失败(上面粘贴了完整测试):

test "successful edit" do
.
.
  patch user_path(@user), params: { user: 
                                    { name: name,
                                      email: email,
                                      password: "",
                                      password_confirmation: "" } }
.
  assert_equal @user.name, name
  assert_equal @user.email, email
end

发送空白 passwordpassword_confirmation 会导致测试失败,因为它不允许为空白。

在验证中添加allow_blank: true 未通过此测试:

test "password must not be blank or made up of spaces" do
  @user.password = @user.password_confirmation = " "
  assert_not @user.valid?
end

此失败允许使用由空格组成的密码创建用户。不允许使用nil 密码,即根本没有字符。该测试有效。

这让我不得不在用户编辑个人资料时必须更改/重复他们的两个密码字段之间做出决定,或者允许用户使用包含一个空格的密码注册的情况,或许多空格,因为此测试不会抛出预期的失败消息:

test "password must not be blank or made up of spaces" do
   @user.password = @user.password_confirmation = " "
   assert_not @user.valid?
end  

添加allow_blank: true 通常会绕过此测试或验证。接受任意数量的空格的password,这违反了模型中的验证。这怎么可能?

关于如何更好地进行测试的任何想法(除了使用 Rspec!)。我向你更丰富的知识鞠躬。

TIA。

[编辑]

以下 cmets 中的建议更改使我的测试套件变为绿色。这是由于套房不足。为了测试不成功的集成,建议的代码一次性测试了多个场景,例如:

test "unsuccessful edit with multiple errors" do
  log_in_as @user
  get edit_user_path(@user)
  assert_template 'users/edit'
  patch user_path(@user), params: { user: 
                                    { name: "",
                                      email: "foo@invalid",
                                      password: "foo",
                                      password_confirmation: "bar" } }
  assert_template 'users/edit'
  assert_select 'div.alert', "The form contains 3 errors."
end

这里的关键部分是纠正预期错误的数量,以便assert_select 给出正确的结果。我没有。错误应该是空白名称、无效的电子邮件格式、密码太短、密码和确认不匹配。未显示短密码错误。

我决定再进行两个测试来证明密码长度和存在性验证失败。 allow_blank 的目的是在编辑用户配置文件时允许密码和确认字段中没有,因此每次编辑用户配置文件时都不必输入密码。这些测试是:

test "unsuccessful edit with short password" do
  log_in_as @user
  get edit_user_path(@user)
  assert_template 'users/edit'
  patch user_path(@user), params: { user: 
                                    { name: @user.name,
                                      email: "foo@valid.com",
                                      password: "foo",
                                      password_confirmation: "foo" } }
  assert_select 'div.alert', "The form contains 1 error."
end

test "unsuccessful edit with blank (spaces) password" do
  log_in_as @user
  get edit_user_path(@user)
  assert_template 'users/edit'
  patch user_path(@user), params: { user: 
                                    { name: @user.name,
                                      email: "foo@valid.com",
                                      password: " ",
                                      password_confirmation: " " } }
  assert_select 'div.alert', "The form contains 1 error."
end

如果密码 更改,则应应用验证规则,即密码不应为空且必须具有最小长度。这不是教程书建议的代码或使用on: :createon: :edit 的修改代码中发生的情况。

【问题讨论】:

  • stackoverflow.com/questions/5123972/… 考虑这个问题的答案。
  • @MisterCal 在那个问题中有一些替代实现。除了试图找到一个可行的解决方案外,OP 还提出了一个问题,即在代码中 BCrypt 应该捕获非-新用户的空白。事实上,update 操作并没有绕过验证,因为新用户将(应该!)遵守验证规则并拥有经过验证的密码。那么,这个添加如何允许创建一个违反验证规则的新用户呢?
  • 你只指定语句on: :create。然后创建另一个没有allow_blank 声明on: :edit 的验证。
  • 嗨,只有史蒂夫。感谢您在另一个线程上 ping 我。您是否尝试过@MisterCal 的建议?听起来对我来说是正确的。
  • 嗨@jvillian 我还没有设法让它工作,但我想我理解所提出的解决方法。我主要关心的是如何回避现有的验证,以及为什么BCrypt 不接受这个。我想我的验证有些矛盾,因此为不同的控制器操作定义不同的规则在很大程度上确实很有意义,所以我稍后再试。对我来说,关键的学习是为什么这段代码会导致验证“失败”,这些规则是否有优先顺序?

标签: ruby-on-rails integration-testing bcrypt railstutorial.org ruby-on-rails-5


【解决方案1】:

我想通了,所以在这里发帖以防其他人遇到类似问题。

我修改了验证以在User 上包含:update 操作,而不仅仅是:edit。这涵盖了保存到数据库的操作并捕获了简短的密码更新验证,但仍然允许密码由空格组成。

检查documentation 向我展示了使用allow_blank: true 允许 nil 和由空格组成的字符串。这里的场景希望nil 密码是可接受的,但不是空白密码。 allow_nil: true 的替代验证更适合这里的场景。

上面更新的代码看起来像User.rb

validates :password,  presence: true,
                      length: { minimum: 6 }, 
                      allow_nil: true, 
                      on: [:edit, :update]

validates :password,  presence: true, 
                      confirmation: true, 
                      length: { minimum: 6 }, 
                      on: :create

扩展的测试套件现在全是绿色的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-21
    • 2015-04-19
    • 1970-01-01
    • 2016-03-29
    • 2020-10-11
    • 1970-01-01
    • 2017-04-03
    • 2016-05-27
    相关资源
    最近更新 更多