railsチュートリアル第１２章 パスワードの再設定をテストする

###パスワードの再設定をテストする
updateに関する４つ条件があった。
送信に成功した場合と失敗した場合の統合テストを作成します。
まずはパスワード再設定のテストファイルを生成しましょう。

```
ubuntu:~/environment/sample_app (password-reset) $ rails generate integration_test password_resets
Running via Spring preloader in process 2918
      invoke  test_unit
      create    test/integration/password_resets_test.rb
```

####パスワード再設定の統合テスト
test/integration/password_resets_test.rb

```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
    # new_password_reset_pathに行く
    assert_template 'password_resets/new'
    # newアクションを表示するか？
    assert_select 'input[name=?]', 'password_reset[email]'
    # input[name=?]タグにpassword_reset[email]が入っているか？
    post password_resets_path, params: { password_reset: { email: "" } }
    # password_reset_pathに空のメアドを投稿する
    assert_not flash.empty?
    # flashメッセージは空かどうか？
    assert_template 'password_resets/new'
    # newアクションを表示する
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    # メールアドレスが有効
    # インスタンス変数をpassword_reset_pathに投稿
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    # 多分　前のダイジェストと違うか？
    assert_equal 1, ActionMailer::Base.deliveries.size
    # 1になるか？
    assert_not flash.empty?
    assert_redirected_to root_url
    # 
    user = assigns(:user)
    # パスワード再設定フォームのテスト
    # assigns（）を行う
    # メールアドレスが無効
    get edit_password_reset_path(user.reset_token, email: "")
    # 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'
    # editアクションを表示されているか？
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # このタグにメアドは書かれているか？
    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:              "",
                            password_confirmation: "" } }
    # パスワードが空
    assert_select 'div#error_explanation'
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
　　# 有効なパスワードとパスワード確認  
    assert is_logged_in?
    # ログインされているか？
    assert_not flash.empty?
    # flashメッセージはからで無いかどうか？
    assert_redirected_to user
    # それが通ればユーザーページに行っているか？
  end
end
```
#####テスト
```
ubuntu:~/environment/sample_app (password-reset) $ rails t
Running via Spring preloader in process 10287
Started with run options --seed 16448

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

Finished in 3.88961s
46 tests, 211 assertions, 0 failures, 0 errors, 0 skips
```

###演習
1.
リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを２回呼び出していますが、これは各行で１回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを１回のupdate_columns呼び出しにまとめてみましょう（これでデータベースへの問い合わせが１回で済むようになります）。また、変更後にテストを実行し、 green になることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習（リスト 11.39）の解答も含まれています。

```rb
  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest: User.digest(reset_token),
                   reset_sent_at: Time.zone.now)
   
  end
```

2.
リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐（リスト 12.16）を統合テストで網羅してみましょう（12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです）。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます（なお、大文字と小文字は区別されません）。

```rb
 follow_redirect!
    # わからない
    assert_match "expired", response.body
    # 期限切れのパスワード再設定で発生する分岐
  end
```

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

```rb
def update
    if params[:user][:password].empty? # （3）への対応
    # パスワードが空文字列かどうか？
      @user.errors.add(:password, :blank)
      # errors.add(:base, '名前の文字数オーバー')
      # なのでメッセージを表示する
      render 'edit'
      # 編集画面に行く。
    elsif @user.update(user_params) # （4）への対応
    # 新しいパスワードが正しければ、更新する
      log_in @user
      @user.update_attribute(:reset_digest, nil)
      # パスワード再設定が成功したらダイジェストをnilにする
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'                                     # （2）への対応
    end
  end
```

4.
リスト 12.18に１行追加し、１つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

```rb
require 'test_helper'
.
.
.
  assert_nil user.reload['reset_digest']
end
```