前提
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
の実装を追いかけていきましょう。
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.rb
のsetup
で、テスト環境の永続cookiesに有効なユーザーIDは書き込まれているはずなので、この文は真になります。以下は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?
ですね。その実装を見てみましょう。
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
を返してしまっているための結果ではないか」と想定しました。digest
がnil
でないことを確認する必要があります。
というわけで、問題となりそうな部分にdebugger
メソッドを挿入してみます。
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
見事にdigest
がnil
を返しています。
また、以下の事柄もわかりました。
-
"#{attribute}_token"
が返す文字列は"remember_token"
であること -
send("#{attribute}_token")
の結果がnil
であること
間違いは何か
正しいdigest
を得るためには、send
に渡す引数は以下である必要があります。
- `"#{attribute}_token"`
+ `"#{attribute}_digest"`
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
今度こそテストが成功しました。