Rails チュートリアル 12章 メールでのパスワードのリセット RSpecでテストでハマっています
Q&A
Closed
解決したいこと
Rails チュートリアルをなぞりながらポートフォリオを作成してます。
チュートリアルのtestをminitestではなくRSpecで書いているのですが、12章のパスワードのリセットでハマっています。
アドバイスをいただければ有難いです。
発生している問題・エラー
ハマっている内容はパスワードのリセットをする統合テストを書き換えたSystem Specです。
登録しているメールアドレスにリンクを貼ったメールを送ってそのリンクからパスワード変更フォームに飛ぶという仕組みですが、最後のフォームに飛ぶところでうまくいきません。
前の更新時の認識は勘違いでした。お恥ずかしい。下記のget_user
メソッドでparams[:user]
でemailが拾えていないことが原因のようです。何かアドバイスがあればご教授お願いします。
# edit.html.erbの隠しフィールドに保存したメールアドレスからユーザーを引っ張り出す
def get_user
require 'pp'
IO.write('dump2.txt', PP.pp(User.all, ''))
@user = User.find_by(email: params[:email])
p "=" * 20
p params[:email]
p User.all
p @user
p "=" * 20
end
該当するソースコード
どれが該当するのかの判別ができているか自信がありません。
不足があればご連絡願います。
とりあえず主なGemです。
チュートリアルとはverが多少異なっています。
特に意味はないですが、webpacker
のverを上げました。
ruby '2.7.4'
gem 'bcrypt', '3.1.13'
gem 'puma', '4.3.6'
gem 'rails', '6.0.3'
gem 'rails-i18n'
gem 'sass-rails', '5.1.0'
gem 'sprockets-rails', '~> 3.2.2'
gem 'turbolinks', '5.2.0'
gem 'uglifier'
gem 'webpacker', '~> 5.0'
group :development, :test do
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
gem 'factory_bot_rails'
gem 'pry-byebug'
gem 'pry-doc'
gem 'pry-rails'
gem 'rails-controller-testing', '1.0.4'
gem 'rspec-rails'
gem 'shoulda-matchers'
gem 'spring-commands-rspec'
gem 'sqlite3', '1.4.2'
end
group :test do
gem 'capybara', '3.28.0'
gem 'database_cleaner'
gem 'launchy'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
end
password_resets_controllerです。
class PasswordResetsController < ApplicationController
before_action :get_user, only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
def new
end
def create
@user = User.find_by(email: params[:password_reset][:email].downcase)
if @user
@user.create_reset_digest
@user.send_password_reset_email
# フラッシュメッセージを表示
flash[:info] = "パスワードリセット用のメールをを送信しましたのでご確認ください"
redirect_to root_url
p '=' * 20
p "test serverで作成したリンクだよ!!"
p edit_password_reset_url(@user.reset_token, email: @user.email)
p '=' * 20
else
render :undetected
end
end
def edit
end
def update
if params[:user][:password].empty?
@user.errors.add(:password, :blank)
render 'edit'
elsif @user.update(user_params)
log_in @user
@user.update_attribute(:reset_digest, nil)
flash[:success] = "パスワードが更新されました!"
redirect_to @user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
def get_user
require 'pp'
IO.write('dump2.txt', PP.pp(User.all, ''))
@user = User.find_by(email: params[:email])
p "=" * 20
p params[:email]
p User.all
p @user
p "=" * 20
end
def valid_user
unless (@user && @user.activated? &&
@user.authenticated?(:reset, params[:id]))
flash[:alert] = 'アカウントが認識できませんでした'
redirect_to root_url
end
end
def check_expiration
if @user.password_reset_expired?
redirect_to '#password-set-modal'
render :expired
end
end
end
パスワード再設定用のフォーム画面です
この画面に遷移しないので困っています。
<% provide(:title, 'パスワードの変更') %>
<% provide(:button_text, '変更') %>
<h1>パスワードの変更</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @user, url: password_reset_path(params[:id]),
local: true) do |f| %>
<%= render 'shared/error_messages' %>
<%# フォーム送信後もメールアドレスの情報を消失しないように隠しフィールドに保存する %>
<%# f.hidden_filedだとparams[:user][:email]に保存されるので今回はNG %>
<%# hidden_filed_tagだとparams[:email]に保存される ⇦これが隠しフィールドになる %>
<%= hidden_field_tag :email, @user.email %>
<%= f.label :password, "パスワード" %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "パスワード 確認" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<hr />
<%= f.submit yield(:button_text), class: "btn btn-primary form-btn" %>
<% end %>
FactoryBotの中身です。
FactoryBot.define do
factory :user do
name { 'TestUser' }
sequence(:email) { |n| "test#{n}@example.com" }
password { 'password' }
password_confirmation { 'password' }
activated { true }
activated_at { Time.zone.now }
end
end
testしているSpecの一部です。
require 'rails_helper'
RSpec.describe "PasswordResets", type: :system do
let(:user) { create :user }
before do
ActionMailer::Base.deliveries.clear
end
describe 'パスワード再設定について' do
before do
visit root_path
find('.navbar-toggler').click
page.evaluate_script('$(".fade").removeClass("fade")')
click_on 'ログイン'
click_on '(パスワードを忘れた方)'
end
context 'メールのリンクを開いた時' do
it 'パスワード変更画面にいること' do
p "=" * 20
p ["user.email", user.email]
p "=" * 20
fill_in 'メールアドレス', visible: true, with: user.email
click_on '送信', visible: true
sleep 1
path_regex = /(?:https?\:\/\/.*?)(?:localhost\:3000)(?:\/password\_resets)(\/.*?)(?:\/edit\?email=.*\..{2,3})/
# 配信したメールのインスタンスを取得
part =
ActionMailer::Base
.deliveries
.last
.body
.parts
.detect { |p| p.content_type == "text/html; charset=UTF-8" }
password_reset_mail = part.body.raw_source
# rink pathを取得
password_reset_url = password_reset_mail.match(path_regex)[0]
p "=" * 20
p "development serverで作成したリンクだよ!!"
p password_reset_url
p "=" * 20
expect(password_reset_url).to include(URI.encode_www_form_component user.email)
require 'pp'
IO.write('dump1.txt', PP.pp(User.all, ''))
visit URI(password_reset_url).path
expect(page).to have_content 'パスワードの変更'
end
end
end
end
上のspecを走らせた時のRails.loggerとActiveRecord::Base.loggerを使ったコンソール上の吐き出されたログをご参考に下記いたします。
Running via Spring preloader in process 3038
PasswordResets
パスワード再設定について
メールのリンクを開いた時
D, [2021-10-31T21:16:36.554617 #3038] DEBUG -- : (0.1ms) begin transaction
I, [2021-10-31T21:16:37.590854 #3038] INFO -- : Started GET "/" for 127.0.0.1 at 2021-10-31 21:16:37 +0900
I, [2021-10-31T21:16:39.058800 #3038] INFO -- : Started GET "/assets/application-a38e3a82b1bf34afaf04459f9e1b19d07df6936937530fbe9e1640b70677ecfc.css" for 127.0.0.1 at 2021-10-31 21:16:39 +0900
I, [2021-10-31T21:16:39.061263 #3038] INFO -- : Started GET "/assets/application-b84564a5da0219d22c2b3126e94718be59fd3f00eb49bd9e2caeff025ab51ef0.js" for 127.0.0.1 at 2021-10-31 21:16:39 +0900
I, [2021-10-31T21:16:40.019685 #3038] INFO -- : Started GET "/login" for 127.0.0.1 at 2021-10-31 21:16:40 +0900
I, [2021-10-31T21:16:40.102580 #3038] INFO -- : Started GET "/password_resets/new" for 127.0.0.1 at 2021-10-31 21:16:40 +0900
"===================="
D, [2021-10-31T21:16:40.119724 #3038] DEBUG -- : (0.1ms) SAVEPOINT active_record_1
D, [2021-10-31T21:16:40.120178 #3038] DEBUG -- : User Exists? (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "test1@example.com"], ["LIMIT", 1]]
D, [2021-10-31T21:16:40.123431 #3038] DEBUG -- : User Create (0.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest", "activated", "activated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?) [["name", "TestUser"], ["email", "test1@example.com"], ["created_at", "2021-10-31 12:16:40.120713"], ["updated_at", "2021-10-31 12:16:40.120713"], ["password_digest", "$2a$04$9Rp9tHP68iAWZY6Z1OOBG.2xhAN0.TZ36dtAEV5tfQ9IbsBWwDczS"], ["activation_digest", "$2a$04$ICQIm7x69WxC6pkHDcEA6.l4ClLherqY.xH7veHxNnUMNhiyTBHma"], ["activated", 1], ["activated_at", "2021-10-31 12:16:40.118366"]]
D, [2021-10-31T21:16:40.124038 #3038] DEBUG -- : (0.1ms) RELEASE SAVEPOINT active_record_1
["user.email", "test1@example.com"]
"===================="
I, [2021-10-31T21:16:40.352871 #3038] INFO -- : Started POST "/password_resets" for 127.0.0.1 at 2021-10-31 21:16:40 +0900
D, [2021-10-31T21:16:40.366804 #3038] DEBUG -- : User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "test1@example.com"], ["LIMIT", 1]]
D, [2021-10-31T21:16:40.369062 #3038] DEBUG -- : User Update (0.1ms) UPDATE "users" SET "reset_digest" = ?, "reset_sent_at" = ? WHERE "users"."id" = ? [["reset_digest", "$2a$04$pIbK4wDlugdBOEBd.xeD1.yDa.nTmzCtugD7tIxzLtJJ3p50rHx5O"], ["reset_sent_at", "2021-10-31 12:16:40.368362"], ["id", 1]]
D, [2021-10-31T21:16:40.377913 #3038] DEBUG -- : UserMailer#password_reset: processed outbound mail in 6.5ms
I, [2021-10-31T21:16:40.383480 #3038] INFO -- : Delivered mail 617e89285cb9e_bde6874869@bond-mbp.local.mail (5.3ms)
D, [2021-10-31T21:16:40.383675 #3038] DEBUG -- : Date: Sun, 31 Oct 2021 21:16:40 +0900
contents of mail has been omitted
"===================="
"test serverで作成したリンクだよ!!"
"http://127.0.0.1:50022/password_resets/c82FgVWprDc0cnYFhwfg_Q/edit?email=test1%40example.com"
"===================="
"===================="
"development serverで作成したリンクだよ!!"
"http://localhost:3000/password_resets/c82FgVWprDc0cnYFhwfg_Q/edit?email=test1%40example.com"
"===================="
D, [2021-10-31T21:16:41.354092 #3038] DEBUG -- : User Load (0.2ms) SELECT "users".* FROM "users"
I, [2021-10-31T21:16:41.360363 #3038] INFO -- : Started GET "/password_resets/c82FgVWprDc0cnYFhwfg_Q/edit" for 127.0.0.1 at 2021-10-31 21:16:41 +0900
D, [2021-10-31T21:16:41.376156 #3038] DEBUG -- : User Load (0.1ms) SELECT "users".* FROM "users"
D, [2021-10-31T21:16:41.379127 #3038] DEBUG -- : User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" IS NULL LIMIT ? [["LIMIT", 1]]
"===================="
nil <= p params[:email]の結果
D, [2021-10-31T21:16:41.379882 #3038] DEBUG -- : User Load (0.1ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
#<ActiveRecord::Relation [#<User id: 1, name: "TestUser", email: "test1@example.com", created_at: "2021-10-31 12:16:40", updated_at: "2021-10-31 12:16:40", password_digest: "$2a$04$9Rp9tHP68iAWZY6Z1OOBG.2xhAN0.TZ36dtAEV5tfQ9...", remember_digest: nil, admin: false, activation_digest: "$2a$04$ICQIm7x69WxC6pkHDcEA6.l4ClLherqY.xH7veHxNnU...", activated: true, activated_at: "2021-10-31 12:16:40", reset_digest: "$2a$04$pIbK4wDlugdBOEBd.xeD1.yDa.nTmzCtugD7tIxzLtJ...", reset_sent_at: "2021-10-31 12:16:40">]>
nil <= p @userの結果
"===================="
I, [2021-10-31T21:16:41.383808 #3038] INFO -- : Started GET "/" for 127.0.0.1 at 2021-10-31 21:16:41 +0900
D, [2021-10-31T21:16:43.923510 #3038] DEBUG -- : (0.3ms) rollback transaction
パスワード変更画面にいること (FAILED - 1)
Failures:
1) PasswordResets パスワード再設定について メールのリンクを開いた時 パスワード変更画面にいること
Failure/Error: expect(page).to have_content 'パスワードの再設定'
expected to find text "パスワードの再設定" in "home view"
# ./spec/systems/password_resets_spec.rb:130:in `block (4 levels) in <main>'
# -e:1:in `<main>'
Finished in 7.37 seconds (files took 0.16482 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/systems/password_resets_spec.rb:78 # PasswordResets パスワード再設定について メールのリンクを開いた時 パスワード変更画面にいること
user確認のdumpファイル1と2の中身です。
同じものになりました。
[#<User:0x00007f8dee2d7c78
id: 1,
name: "TestUser",
email: "test1@example.com",
created_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
updated_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
password_digest:
"$2a$04$OojOvw0bIYuHGFw9YunjD.IvHwRPxRqzc6E1eLBC7bMga96jpQi/.",
remember_digest: nil,
admin: false,
activation_digest:
"$2a$04$etNOvACfkMAyHw90oUsLq.ABacGtbRrAVAsl7NkNd9akFNg36d1EW",
activated: true,
activated_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
reset_digest: "$2a$04$eEzrCauM7IIUNzY7h3xMte3a3IIAHgVxzdTsHC8D/.Ad14/yw9vCG",
reset_sent_at: Sun, 31 Oct 2021 10:55:42 JST +09:00>]
[#<User:0x00007f8dd230c908
id: 1,
name: "TestUser",
email: "test1@example.com",
created_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
updated_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
password_digest:
"$2a$04$OojOvw0bIYuHGFw9YunjD.IvHwRPxRqzc6E1eLBC7bMga96jpQi/.",
remember_digest: nil,
admin: false,
activation_digest:
"$2a$04$etNOvACfkMAyHw90oUsLq.ABacGtbRrAVAsl7NkNd9akFNg36d1EW",
activated: true,
activated_at: Sun, 31 Oct 2021 10:55:42 JST +09:00,
reset_digest: "$2a$04$eEzrCauM7IIUNzY7h3xMte3a3IIAHgVxzdTsHC8D/.Ad14/yw9vCG",
reset_sent_at: Sun, 31 Oct 2021 10:55:42 JST +09:00>]
自分で試したこと
10/31: ログにある通りtestとdevelopmentのアドレスが違うから期待するリンクが開かないのはないかと思っています。これはもはや仕様で解決しようがないのではと訝しんでおりますが、何か策があればご教授いただければ幸甚です。
上記は私の勘違いでした。私は早とちりのクソ野郎です。コメントでご指摘いただいた通り各メソッドの内容を見たところget_user
メソッド内でparams[:email]
でemailが取得できていないことが要因のようです。
動きの順序として、メールのリンクにアクセスされるとedit
アクションのbefore_action
であるget_user
、valid_user
が走ることになるのでedit.html.erb
が描画される前にはget_user
が走っているのか???
でも実際は開発環境でも本番環境でも一気通貫の動作は問題ないのでget_user
でデータベースから@userを取り出す時にはparams[:email]
は存在しているはず。なのにテストではnilになっている。。。