Railsチュートリアル 第12章 パスワードの再設定 - パスワードを再設定する

# ここまでに実装してきたものと、次に何を実装するか

* [PasswordResetsリソース](https://qiita.com/rapidliner00/items/471af882e1fd66ddc9c9)
    * [PasswordResets#createで、メールアドレスが無効な場合の処理をテスト駆動で実装していく](https://qiita.com/rapidliner00/items/974187773b181bda8004)
    * [PasswordResets#createで、メールアドレスが有効な場合の処理をテスト駆動で実装していく](https://qiita.com/rapidliner00/items/4f31103f4fb4ffdef81f)

ここまでの実装により、PasswordResetsコントローラーの`new`アクションおよび`create`アクションに対する実装が完成しました。次に必要となるのは、同じくPasswordResetsコントローラーの、`edit`アクションおよび`update`アクションに対する実装です。

# `edit`アクションで再設定

長くなりましたので、別記事で解説します。

* [Railsチュートリアル 第12章 パスワードの再設定 - PasswordResets#editの動作を、テスト駆動で実装していく](https://qiita.com/rapidliner00/items/7f12f5beb04d7a031e87)

# 演習 - `edit`アクションで再設定

## 1. [12.2.1.1](https://railstutorial.jp/chapters/password_reset?version=5.1#sec-exercises_password_reset_mailer)で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、[図 12.11](https://railstutorial.jp/chapters/password_reset?version=5.1#fig-password_reset_form)のように表示されるか確かめてみましょう。

まず、有効なユーザーのメールアドレスである`example-2@railstutorial.org`を対象に、パスワード再設定用メールの送信を求める操作をしてみましょう。

<img width="1171" alt="スクリーンショット 2019-12-14 15.09.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/71dae0b5-4995-f5b4-7045-bf21f9af4ba4.png">

この画面で「Submit」ボタンをクリックすると、パスワード再設定用URLを含むメールを送信する処理がされます。Railsサーバーのログには、以下の記録があります。

```
Sent mail to example-2@railstutorial.org (8.3ms)
Date: Sun, 15 Dec 2019 07:58:01 +0000
From: noreply@example.com
To: example-2@railstutorial.org
Message-ID: <5df5e7895d0be_1c62ac8ce7605cc87241@705320d4d96d.mail>
Subject: Password reset
Mime-Version: 1.0
Content-Type: multipart/alternative;
...略
```

メール本文中に記載されたパスワード再設定用URLは以下のとおりです。

```
https://localhost:3000/password_resets/D48WcYU6q-K2SHWqjYsrEA/edit?email=example-2%40railstutorial.org
```

私の環境では、Railsが動作しているのはDocker上であり、Docker環境のTCPポート番号3000はローカル環境のポート番号8080に紐付けされています。また、URLスキームが https:// ではアクセスできず、http:// でアクセスする必要があります。そのため、ローカルからアクセスする場合、アクセス先のURLは以下となります。

```
http://localhost:8080/password_resets/D48WcYU6q-K2SHWqjYsrEA/edit?email=example-2%40railstutorial.org
```

実際に上述URLにアクセスすると、Webブラウザには以下のような画面が出力されます。

<img width="1171" alt="スクリーンショット 2019-12-15 17.09.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/331cd21d-8a32-c4b5-b89f-2cab48a6320e.png">

## 2. 先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?

<img width="868" alt="スクリーンショット 2019-12-15 17.12.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/a396172f-538f-cee8-dccd-a44af2f6cd85.png">

上記のように、「Unknown action」というエラー画面が表示されます。

Railsサーバーのログには、以下の記録があります。

```
Started PATCH "/password_resets/D48WcYU6q-K2SHWqjYsrEA" for 172.17.0.1 at 2019-12-15 08:12:04 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
  
AbstractController::ActionNotFound (The action 'update' could not be found for PasswordResetsController):
...スタックトレース略
```

現時点でPasswordResetsコントローラーに対する`update`アクションが実装されていないためのエラーですね。

# パスワードを更新する

長くなりましたので、別記事で解説します。

* [Railsチュートリアル 第12章 パスワードの再設定 - PasswordResets#updateの処理をテスト駆動で実装していく](https://qiita.com/rapidliner00/items/d50c99dbbf541a39a7d0)

# 演習 - パスワードを更新する

## 1. [12.2.1.1](https://railstutorial.jp/chapters/password_reset?version=5.1#sec-exercises_password_reset_mailer)で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?

まずは、パスワード再設定用のフォームを表示させます。

<img width="1173" alt="スクリーンショット 2019-12-16 12.24.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/fab8c82d-f21c-bd01-c43c-741449b0eae0.png">

passwordに「foobaz」、confirmationに「barquux」と入力して「Submit」ボタンを押してみます。

<img width="1173" alt="スクリーンショット 2019-12-16 12.25.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/72a2d10a-524f-c673-9372-fa9952817ae9.png">

「The form contains 1 error. Password confirmation doesn't match Password」というエラーメッセージが表示されています。

サーバーログには以下のように表示されています。

```
Started PATCH "/password_resets/IlMln7IBh63Pd0zjHzgWmw" ...略
Processing by PasswordResetsController#update as HTML
  Parameters: {...略 "email"=>"example-2@railstutorial.org", "user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Update password", "id"=>"IlMln7IBh63Pd0zjHzgWmw"}
  User Load (8.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example-2@railstutorial.org"], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  User Exists (2.1ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "example-2@railstutorial.org"], ["id", 3], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
  ...略
Completed 200 OK in 660ms (Views: 458.4ms | ActiveRecord: 10.5ms)
```

「rollback transaction」とありますね。確かにRDBに対する更新が取り消されています。

## 2.1. コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトの`password_digest`の値を取得してみましょう。

「`example-2@railstutorial.org`というメールアドレスを持つユーザーのパスワード再設定を送信した」という前提です。

```
# rails console --sandbox
>> user = User.find_by(email: "example-2@railstutorial.org")
>> user.password_digest
=> "$2a$10$lVjDouNtJJObQKOU4uSrtuTeU9/rPu2MkrSyfQr.NETDUXEKyxDJq"
```

## 2.2. 次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう ([図 12.13](https://railstutorial.jp/chapters/password_reset?version=5.1#fig-password_reset_success))。パスワードの再設定は成功したら、再度`password_digest`の値を取得し、先ほど取得した値と異なっていることを確認してみましょう。

> ヒント: 新しい値は`user.reload`を通して取得する必要があります。

Railsコンソールをサンドボックスモードで起動した場合、RDBの変更を伴うリクエストを送出する前に一旦Railsコンソールを抜けなければなりません。そうでないと、RDBの変更を伴うリクエストを受け取った時点でエラーが返ってしまいます。サンドボックスモードのRailsコンソールはRDBをロックするためです。

```
# rails console --sandbox
>> user = User.find_by(email: "example-2@railstutorial.org")
>> user.password_digest
=> "$2a$10$IOSmbIOqlca85aXRmwMqpuc53.KSSnOInMEyRqSAtDmkvNwcxUGU2"
```

```
$2a$10$lVjDouNtJJObQKOU4uSrtuTeU9/rPu2MkrSyfQr.NETDUXEKyxDJq
$2a$10$IOSmbIOqlca85aXRmwMqpuc53.KSSnOInMEyRqSAtDmkvNwcxUGU2
```

確かに`password_digest`の値は変化しています。


# パスワードの再設定をテストする

ここまでの実装が完了すると、`test/integration/password_resets_test.rb`の全体像は以下のようになります。

```ruby:test/integration/password_resets_test.rb
require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:rhakurei)
  end

  test "password resets" do
    get new_password_reset_path
    assert_template 'password_resets/new'
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    get new_password_reset_path
    assert flash.empty?
    # メールアドレスが有効
    post password_resets_path, params: { 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
    # パスワード再設定フォームのテスト
    user = assigns(:user)
    # メールアドレスが無効
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # 無効なユーザー
    user.toggle!(:activated)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url
    user.toggle!(:activated)
    # メールアドレスが有効で、トークンが無効
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # メールアドレスもトークンも有効
    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
    # パスワードが空
    patch password_reset_path(user.reset_token),
            params: { email:user.email,
                      user: { password:              "",
                              password_confirmation: "" } }
    assert_select 'div#error_explanation'
    # パスワードとパスワード（再入力）が不一致
    patch password_reset_path(user.reset_token),
            params: { email:user.email,
                      user: { password:              "foobaz",
                              password_confirmation: "barquux" } }
    assert_select 'div#error_explanation'
    # 短すぎるパスワード
    patch password_reset_path(user.reset_token),
            params: { email:user.email,
                      user: { password:              "foo",
                              password_confirmation: "foo" } }
    assert_select 'div#error_explanation'
    # パスワード再設定トークンが期限切れ
    user.reset_sent_at = 48.hours.ago
    user.update_attribute(:reset_sent_at, user.reset_sent_at)
    patch password_reset_path(user.reset_token),
            params: { email:user.email,
                      user: { password:              "foobar",
                              password_confirmation: "foobar" } }
    assert_not is_logged_in?
    assert_not flash.empty?
    assert_redirected_to new_password_reset_url
    # 有効なパスワードとパスワード確認
    user.reset_sent_at = Time.zone.now
    user.update_attribute(:reset_sent_at, user.reset_sent_at)
    patch password_reset_path(user.reset_token),
            params: { email:user.email,
                      user: { password:              "foobar",
                              password_confirmation: "foobar" } }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
  end
end
```

このテストが成功することを確認しておきましょう。

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 1070
Started with run options --seed 8172

  1/1: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.99857s
1 tests, 23 assertions, 0 failures, 0 errors, 0 skips
```

テストスイート全体が成功することも確認しておきます。

```
# rails test
Running via Spring preloader in process 1083
Started with run options --seed 64812

  48/48: [=================================] 100% Time: 00:00:06, Time: 00:00:06

Finished in 6.42649s
48 tests, 228 assertions, 0 failures, 0 errors, 0 skips
```

# 演習 - パスワードの再設定をテストする

演習に臨む前に、上述のテストコードの一部を削除する必要があります。

```diff:test/integration/password_resets_test.rb
  require 'test_helper'

  class PasswordResetsTest < ActionDispatch::IntegrationTest

    def setup
      ActionMailer::Base.deliveries.clear
      @user = users(:rhakurei)
    end

    test "password resets" do
      get new_password_reset_path
      assert_template 'password_resets/new'
      # メールアドレスが無効
      post password_resets_path, params: { password_reset: { email: "" } }
      assert_not flash.empty?
      assert_template 'password_resets/new'
      get new_password_reset_path
      assert flash.empty?
      # メールアドレスが有効
      post password_resets_path, params: { 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
      # パスワード再設定フォームのテスト
      user = assigns(:user)
      # メールアドレスが無効
      get edit_password_reset_path(user.reset_token, email: "")
      assert_redirected_to root_url
      # 無効なユーザー
      user.toggle!(:activated)
      get edit_password_reset_path(user.reset_token, email: user.email)
      assert_redirected_to root_url
      user.toggle!(:activated)
      # メールアドレスが有効で、トークンが無効
      get edit_password_reset_path('wrong token', email: user.email)
      assert_redirected_to root_url
      # メールアドレスもトークンも有効
      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
      # パスワードが空
      patch password_reset_path(user.reset_token),
              params: { email:user.email,
                        user: { password:              "",
                                password_confirmation: "" } }
      assert_select 'div#error_explanation'
      # パスワードとパスワード（再入力）が不一致
      patch password_reset_path(user.reset_token),
              params: { email:user.email,
                        user: { password:              "foobaz",
                                password_confirmation: "barquux" } }
      assert_select 'div#error_explanation'
      # 短すぎるパスワード
      patch password_reset_path(user.reset_token),
              params: { email:user.email,
                        user: { password:              "foo",
                                password_confirmation: "foo" } }
      assert_select 'div#error_explanation'
-     # パスワード再設定トークンが期限切れ
-     user.reset_sent_at = 48.hours.ago
-     user.update_attribute(:reset_sent_at, user.reset_sent_at)
-     patch password_reset_path(user.reset_token),
-             params: { email:user.email,
-                       user: { password:              "foobar",
-                               password_confirmation: "foobar" } }
-     assert_not is_logged_in?
-     assert_not flash.empty?
-     assert_redirected_to new_password_reset_url
      # 有効なパスワードとパスワード確認
      user.reset_sent_at = Time.zone.now
      user.update_attribute(:reset_sent_at, user.reset_sent_at)
      patch password_reset_path(user.reset_token),
              params: { email:user.email,
                        user: { password:              "foobar",
                                password_confirmation: "foobar" } }
      assert is_logged_in?
      assert_not flash.empty?
      assert_redirected_to user
    end
  end

```

## 1.1. [リスト 12.20](https://railstutorial.jp/chapters/password_reset?version=5.1#code-update_columns_redux)に記したテンプレートを使って、`update_attribute`の呼び出しを１回の`update_columns`呼び出しにまとめてみましょう (これでデータベースへの問い合わせが１回で済むようになります)。

> [リスト 12.6](https://railstutorial.jp/chapters/password_reset?version=5.1#code-user_model_password_reset)にある`create_reset_digest`メソッドは`update_attribute`を２回呼び出していますが、これは各行で１回ずつデータベースへ問い合わせしていることになります。

> ちなみに[リスト 12.20](https://railstutorial.jp/chapters/password_reset?version=5.1#code-update_columns_redux)にあるコードには、前章の演習([リスト 11.39](https://railstutorial.jp/chapters/account_activation?version=5.1#code-update_columns))の解答も含まれています。

`app/models/user.rb`を以下のように変更していきます。「前章の演習([リスト 11.39](https://railstutorial.jp/chapters/account_activation?version=5.1#code-update_columns))の解答」というのは、`activate`メソッドのことですね。

```diff:app/models/user.rb
  class User < ApplicationRecord
    attr_accessor :remember_token, :activation_token, :reset_token
    ...略

    # アカウントを有効にする
    def activate
      update_columns(activated: true, activated_at: Time.zone.now)
    end

    ...略

    # パスワード再設定の属性を設定する
    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)
+     update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
    end

    ...略

    private

      ...略
  end

```

## 1.2. また、変更後にテストを実行し、`green`になることも確認してください。

必要最低限のテストを実行します。

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 1109
Started with run options --seed 19282

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.49273s
1 tests, 23 assertions, 0 failures, 0 errors, 0 skips
```

## 2. [リスト 12.21](https://railstutorial.jp/chapters/password_reset?version=5.1#code-password_reset_expire_test)のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐 ([リスト 12.16](https://railstutorial.jp/chapters/password_reset?version=5.1#code-password_reset_update_action)) を統合テストで網羅してみましょう。

> 期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます (なお、大文字と小文字は区別されません)。

> ([リスト 12.21](https://railstutorial.jp/chapters/password_reset?version=5.1#code-password_reset_expire_test)のコードにある`response.body`は、そのページのHTML本文をすべて返すメソッドです)

テストコードそのものは以下のようになります。「有効なパスワードとパスワード確認」とコードの構成は似ていますが、`@user`と`user`を取り違えるミスに注意が必要です。

```ruby:
test "expired token" do
  get new_password_reset_path
  post password_resets_path, params: { password_reset: { email: @user.email } }
  @user = assigns(:user)
  @user.update_attribute(:reset_sent_at, 3.hours.ago)
  patch password_reset_path(@user.reset_token),
          params: { email:@user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
  assert_response :redirect
  follow_redirect!
  assert_match /expired/, response.body
end
```

```diff:test/integration/password_resets_test.rb
  require 'test_helper'

  class PasswordResetsTest < ActionDispatch::IntegrationTest

    def setup
      ActionMailer::Base.deliveries.clear
      @user = users(:rhakurei)
    end

    test "password resets" do
      ...略
    end
+
+   test "expired token" do
+     get new_password_reset_path
+     post password_resets_path, params: { password_reset: { email: @user.email } }
+     @user = assigns(:user)
+     @user.update_attribute(:reset_sent_at, 3.hours.ago)
+     patch password_reset_path(@user.reset_token),
+             params: { email:@user.email,
+                       user: { password:              "foobar",
+                               password_confirmation: "foobar" } }
+     assert_response :redirect
+     follow_redirect!
+     assert_match /expired/, response.body
+   end

  end
```

## 2.2. （追加演習）変更後にテストを実行し、`green`になることも確認してください。

私の環境では、以下の行が`test/integration/password_resets_test.rb`の72行目となります。

```ruby:test/integration/password_resets_test.rb（72行目）
test "expired token" do
```

上記を踏まえて、対応するテストを実行してみましょう。

```
# rails test test/integration/password_resets_test.rb:72
Running via Spring preloader in process 1148
Started with run options --seed 28814

  2/2: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.95873s
1 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```

途中でtypo等がなければ、テストは上記のように成功するはずです。

同様のアプローチで、PasswordResetsコントローラーに対するここまでのテストすべてを「1つのテストで1つの機能に対してテストを行うようにする」というリファクタリングもできそうです。

## 3. 下記問題を解決するために、[リスト 12.22](https://railstutorial.jp/chapters/password_reset?version=5.1#code-update_clear_reset)のコードを追加し、パスワードの再設定に成功したらダイジェストを`nil`になるように変更してみましょう。

> ２時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の (または共有された) コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、２時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます (しかもそのままログイン機構まで突破されてしまいます!)。

`PasswordResetsController#update`の内容を、以下のように変更します。

```diff:PasswordResetsController#update
  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update_attributes(user_params)
      log_in @user
+     @user.update_attribute(:reset_digest, nil)
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end
```

## 4. [リスト 12.18](https://railstutorial.jp/chapters/password_reset?version=5.1#code-password_reset_integration_test)に１行追加し、１つ前の演習課題に対するテストを書いてみましょう。

> ヒント: [リスト 9.25](https://railstutorial.jp/chapters/advanced_login?version=5.1#code-remember_me_test)の`assert_nil`メソッドと[リスト 11.33](https://railstutorial.jp/chapters/account_activation?version=5.1#code-signup_with_account_activation_test)の`user.reload`メソッドを組み合わせて、`reset_digest`属性を直接テストしてみましょう。


対応するテストの内容は以下のようになります。

```ruby:
assert_nil user.reload.reset_digest
```

### 正しい対象に対してテストが行われていることの確認

`PasswordResetsController#update`の内容が以下である場合、テストは失敗します。

```ruby:PasswordResetsController#update
def update
  if params[:user][:password].empty?
    @user.errors.add(:password, :blank)
    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
```

失敗の内容は以下です。

```
# rails test test/integration/password_resets_test.rb   
Running via Spring preloader in process 1161
Started with run options --seed 17444

 FAIL["test_password_resets", PasswordResetsTest, 3.2027707000015653]
 test_password_resets#PasswordResetsTest (3.20s)
        Expected "$2a$04$VSxeyLa4LwjS2fyhIBYe2.flQQgt9j2TrpgleMOexHd/j1Y63b6I2" to be nil.
        test/integration/password_resets_test.rb:68:in `block in <class:PasswordResetsTest>'

  2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.20832s
2 tests, 21 assertions, 1 failures, 0 errors, 0 skips
```

私の環境では、`test/integration/password_resets_test.rb`の68行目は、現時点で以下の内容となっています。

```ruby:test/integration/password_resets_test.rb（68行目）
assert_nil user.reload.reset_digest
```

テスト失敗時のメッセージの内容は以下です。

```
Expected "$2a$04$VSxeyLa4LwjS2fyhIBYe2.flQQgt9j2TrpgleMOexHd/j1Y63b6I2" to be nil.
```

「`reset_digest`が`nil`でない」ということですね。確かに正しい対象に対してテストが行われているようです。

### 正しい実装をすればテストが成功することの確認

`PasswordResetsController#update`の内容を、以下のように変更します。

```diff:PasswordResetsController#update
  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update_attributes(user_params)
      log_in @user
+     @user.update_attribute(:reset_digest, nil)
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end
```

再びテストを実行していきましょう。

```
# rails test test/integration/password_resets_test.rb
Running via Spring preloader in process 1188
Started with run options --seed 40656

  2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.84093s
2 tests, 24 assertions, 0 failures, 0 errors, 0 skips
```

今度こそテストが成功しました。

```
# rails test
Running via Spring preloader in process 1201
Started with run options --seed 49326

  49/49: [=================================] 100% Time: 00:00:07, Time: 00:00:07

Finished in 7.45029s
49 tests, 229 assertions, 0 failures, 0 errors, 0 skips
```

上述のように、全体のテストも成功することを確認しておきましょう。