0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsチュートリアル 第11章 アカウントの有効化 - authenticated?に関する想定外のテスト失敗と、その解決

Posted at

前提

Railsチュートリアル 第11章 11.3 アカウントを有効化するの最後のほうに、「抽象化されたauthenticated?メソッドに対するテスト」という項目がありました。

変更が必要な実装は以下2つです。

  • SessionsHelper#current_userメソッド
  • UserTestの「authenticated? should return false for a new user with nil digest」テスト

発生した問題

しかしながら、私の環境では、当該2つのメソッドの実装を変更しても、まだテストが成功しませんでした。内容は以下です。

# rails test
Running via Spring preloader in process 13807
Started with run options --seed 22372

 FAIL["test_current_user_returns_right_user_when_session_is_nil", SessionsHelperTest, 2.9952282999875024]
 test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (3.00s)
        --- expected
        +++ actual
        @@ -1 +1 @@
        -#<User id: 959740715, name: "Reimu Hakurei", email: "rhakurei@example.com", created_at: "2019-12-06 09:11:02", updated_at: "2019-12-06 09:11:05", password_digest: "$2a$04$lRWR5q0KpF3UiW2r.hyD4e19FtV4Na6gh7iwkvdiely...", remember_digest: "$2a$04$26R0Y3nwF/Wh68N94ShIze7j6dLAT03Hva5oDt4PhP5...", admin: true, activation_digest: nil, activated: true, activated_at: nil>
        +nil
        test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>'

  44/44: [=================================] 100% Time: 00:00:05, Time: 00:00:05

Finished in 5.43956s
44 tests, 188 assertions, 1 failures, 0 errors, 0 skips

「有効なユーザー情報を返すべきところ、nilを返してしまっている」という失敗内容ですね。

私の環境では、test/helpers/sessions_helper_test.rbの11行目対応するテストは以下です。

test "current user returns right user when session is nil" do
  assert_equal @user, current_user
  assert is_logged_in?
end

どこでバグを埋め込んだのか調べる

current_userの実装

まずはcurrent_userの実装を追いかけていきましょう。

SessionsHelper#current_user
def current_user
  if (user_id = session[:user_id])
    @current_user ||= User.find_by(id: user_id)
  elsif (user_id = cookies.signed[:user_id])
    user = User.find_by(id: user_id)
    if user && user.authenticated?(:remember , cookies[:remember_token])
      log_in user
      @current_user = user
    end
  end
end

最初のif文では、どのコードが実行されるか

「session is nil」という前提なので、以下の文が実行されます。

user_id = cookies.signed[:user_id]

test/helpers/sessions_helper_test.rbsetupで、テスト環境の永続cookiesに有効なユーザーIDは書き込まれているはずなので、この文は真になります。以下はsetupの内容です。

SessionsHelperTest#setup
def setup
  @user = users(:rhakurei)
  remember(@user)
end

user_idに有効なユーザーIDが格納された後に実行されるコード

user_idに有効なユーザーIDが格納されたならば、続いて実行されるのは以下のコードです。

if user && user.authenticated?(:remember , cookies[:remember_token])
  log_in user
  @current_user = user
end

user.authenticated?の現状

次に実行されるメソッドはuser.authenticated?ですね。その実装を見てみましょう。

User#authenticated?(現状)
def authenticated?(attribute, token)
  digest = send("#{attribute}_token")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

私はここで、「user && user.authenticated?(:remember , cookies[:remember_token])falseとなるという状況は、user.authenticated?falseを返してしまっているための結果ではないか」と想定しました。digestnilでないことを確認する必要があります。

というわけで、問題となりそうな部分にdebuggerメソッドを挿入してみます。

User#authenticated?(現状)
  def authenticated?(attribute, token)
    digest = send("#{attribute}_token")
+   debugger
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

debuggerの実行結果

# rails test
Running via Spring preloader in process 13823
Started with run options --seed 34707

  /0: [=---=---=---=---=---=---=---=---=---=-] 0% Time: 00:00:00,  ETA: ??:??:??
[29, 38] in /var/www/sample_app/app/models/user.rb
   29: 
   30:   # 渡されたトークンがダイジェストと一致したらtrueを返す
   31:     def authenticated?(attribute, token)
   32:       digest = send("#{attribute}_token")
   33:       debugger
=> 34:       return false if digest.nil?
   35:       BCrypt::Password.new(digest).is_password?(token)
   36:     end
   37: 
   38:   # ユーザーのログイン情報を破棄する
(byebug) digest
nil
(byebug) attribute
:remember
(byebug) "#{attribute}_token"
"remember_token"
(byebug) send("#{attribute}_token")
nil

見事にdigestnilを返しています。

また、以下の事柄もわかりました。

  • "#{attribute}_token"が返す文字列は"remember_token"であること
  • send("#{attribute}_token")の結果がnilであること

間違いは何か

正しいdigestを得るためには、sendに渡す引数は以下である必要があります。

- `"#{attribute}_token"`
+ `"#{attribute}_digest"`

user.authenticated?の修正

user.authenticated?を以下のように修正します。

User#authenticated?
  def authenticated?(attribute, token)
-   digest = send("#{attribute}_token")
+   digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

改めてテストを実行

# rails test
Running via Spring preloader in process 13904
Started with run options --seed 21760

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

Finished in 6.01140s
44 tests, 189 assertions, 0 failures, 0 errors, 0 skips

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?