Railsチュートリアル 第12章 パスワードの再設定 - PasswordResetsコントローラーのeditアクションに対するテストは、どのような実装抜けがあったらテストが失敗するのか

# 前提

Railsチュートリアル 第12章のうち、[PasswordResetsコントローラーの`edit`アクションの動作](https://railstutorial.jp/chapters/password_reset?version=5.1#sec-reset_edit_action)を、テスト駆動で実装していったところです。以下のようなテストが、失敗なしで一気に成功してしまいました。このままでは、どのテストが何に対してのものなのかがわからないですね。

```ruby:
# メールアドレスが無効
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
```

どのような実装抜けがあったら、どのテストが失敗するのでしょうか。

# 無効なメールアドレスがチェックされない場合

PasswordResetsコントローラーの`valid_user`の実装が以下のようになっていたらどうなるでしょうか。

```ruby:
class PasswordResetsController < ApplicationController
  before_action :get_user, only:   [:edit, :update]
  before_action :valid_user, only: [:edit, :update]

  # ...略

  private

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end
end
```

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

ERROR["test_password_resets", PasswordResetsTest, 4.072652899998502]
 test_password_resets#PasswordResetsTest (4.07s)
NoMethodError:         NoMethodError: undefined method `activated?' for nil:NilClass
            app/controllers/password_resets_controller.rb:32:in `valid_user'
            test/integration/password_resets_test.rb:28:in `block in <class:PasswordResetsTest>'

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

Finished in 4.07946s
1 tests, 8 assertions, 0 failures, 1 errors, 0 skips
```

私の環境では、`test/integration/password_resets_test.rb`の28行目は以下のコードが記述されています。

```ruby:
get edit_password_reset_path(user.reset_token, email: "")
```

email属性として空文字列を与えた場合に、「`activated?`メソッドが定義されていない」というエラーが発生している、ということのようです。

## email属性として空文字列を与えた場合、`@user`の状態はどうなるか

「`activated?`メソッドが定義されていない」というエラーが発生する直前に`debugger`を入れると、結果はどうなるでしょうか。

```diff:
  class PasswordResetsController < ApplicationController
    before_action :get_user, only:   [:edit, :update]
    before_action :valid_user, only: [:edit, :update]

    ...略

    private

      def get_user
        @user = User.find_by(email: params[:email])
      end

      # 正しいユーザーかどうか確認する
      def valid_user
+       debugger
        unless (@user.activated? &&
                @user.authenticated?(:reset, params[:id]))
          redirect_to root_url
        end
      end
  end
```

```
(byebug) @user
nil
```

「無効なメールアドレスをクエリパラメータとして`User.find_by`メソッドが呼び出されたため、`@user`が`nil`になってしまっている」ということのようです。

# 有効化されていないユーザーがチェックされない場合

PasswordResetsコントローラーの`valid_user`の実装が以下のようになっていたらどうなるでしょうか。

```ruby:
class PasswordResetsController < ApplicationController
  before_action :get_user, only:   [:edit, :update]
  before_action :valid_user, only: [:edit, :update]

  # ...略

  private

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end
end
```

`test/integration/password_resets_test.rb`に対するテストの結果は以下のようになります。

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

 FAIL["test_password_resets", PasswordResetsTest, 3.081338899995899]
 test_password_resets#PasswordResetsTest (3.08s)
        Expected response to be a <3XX: redirect>, but was a <200: OK>
        test/integration/password_resets_test.rb:33:in `block in <class:PasswordResetsTest>'

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

Finished in 3.10585s
1 tests, 10 assertions, 1 failures, 0 errors, 0 skips
```

私の環境では、`test/integration/password_resets_test.rb`の30行目から33行目には以下のコードが記述されています。

```ruby:
# 無効なユーザー
user.toggle!(:activated)
get edit_password_reset_path(user.reset_token, email: user.email)
assert_redirected_to root_url
```

メッセージの内容は以下です。

```
Expected response to be a <3XX: redirect>, but was a <200: OK>
```

ということは、「ユーザーが有効化されていない場合に、リダイレクトされるべきところがリダイレクトされていない」という理由でのテストの失敗のようですね。

# パスワード再設定用トークンの有効性がチェックされない場合

PasswordResetsコントローラーの`valid_user`の実装が以下のようになっていたらどうなるでしょうか。

```ruby:
class PasswordResetsController < ApplicationController
  before_action :get_user, only:   [:edit, :update]
  before_action :valid_user, only: [:edit, :update]

  # ...略

  private

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated?)
        redirect_to root_url
      end
    end
end
```

`test/integration/password_resets_test.rb`に対するテストの結果は以下のようになります。

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

 FAIL["test_password_resets", PasswordResetsTest, 3.1079486000016914]
 test_password_resets#PasswordResetsTest (3.11s)
        Expected response to be a <3XX: redirect>, but was a <200: OK>
        test/integration/password_resets_test.rb:37:in `block in <class:PasswordResetsTest>'

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

Finished in 3.11229s
1 tests, 11 assertions, 1 failures, 0 errors, 0 skips
```

私の環境では、`test/integration/password_resets_test.rb`の35行目から37行目には以下のコードが記述されています。

```ruby:
# メールアドレスが有効で、トークンが無効
get edit_password_reset_path('wrong token', email: user.email)
assert_redirected_to root_url
```

メッセージの内容は以下です。

```
Expected response to be a <3XX: redirect>, but was a <200: OK>
```

ということは、「パスワード再設定用トークンが有効でない場合に、リダイレクトされるべきところがリダイレクトされていない」という理由でのテストの失敗のようですね。