Kedaruma_Bond
@Kedaruma_Bond (Kedaruma Bond)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Rails チュートリアル 12章 メールでのパスワードのリセット RSpecでテストでハマっています

解決したいこと

Rails チュートリアルをなぞりながらポートフォリオを作成してます。
チュートリアルのtestをminitestではなくRSpecで書いているのですが、12章のパスワードのリセットでハマっています。
アドバイスをいただければ有難いです。

発生している問題・エラー

ハマっている内容はパスワードのリセットをする統合テストを書き換えたSystem Specです。
登録しているメールアドレスにリンクを貼ったメールを送ってそのリンクからパスワード変更フォームに飛ぶという仕組みですが、最後のフォームに飛ぶところでうまくいきません。

前の更新時の認識は勘違いでした。お恥ずかしい。下記のget_userメソッドでparams[:user]でemailが拾えていないことが原因のようです。何かアドバイスがあればご教授お願いします。

password_resets_controller.rb
# 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を上げました。

Gemfile
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です。

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

パスワード再設定用のフォーム画面です
この画面に遷移しないので困っています。

edit.html.erb
<% 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の中身です。

spec/factories/user.rb

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の一部です。

spec/systems/password_resets_spec.rb
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の中身です。
同じものになりました。

dump1.txt
[#<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>]

dump2.txt
[#<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_uservalid_userが走ることになるのでedit.html.erbが描画される前にはget_userが走っているのか???
でも実際は開発環境でも本番環境でも一気通貫の動作は問題ないのでget_userでデータベースから@userを取り出す時にはparams[:email]は存在しているはず。なのにテストではnilになっている。。。

0

5Answer

Comments

  1. @Kedaruma_Bond

    Questioner

    ご指摘ありがとうございます。バックトレースが何なのかイマイチ不明瞭ですが、多分このことだろうと思ったことを追加掲載しました。間違っていたらご指摘ください。よろしくお願いします。
visit password_reset_url
take_screenshot

expect(page).to have_content('パスワードの変更')

こうしてみて思っているページにいるのかどうか見てみるとか?

1Like

Comments

  1. @Kedaruma_Bond

    Questioner

    ご回答ありがとうございます。
    その場所でのスクショではvalid_userで弾かれてリダイレクトしたroot画面になっています。
    なぜ弾かれるのかがわからず困っているところです。。。
  2. visit password_reset_url
    p [:password_reset_url, password_reset_url]
    take_screenshot

    とかして本当に思ってるURLが抽出できているか見るとか?
  3. そして果たしてそのURLがそのテスト環境で本当に到達できるアドレスなのかどうか
  4. @Kedaruma_Bond

    Questioner

    ありがとうございます。ご指摘のCodeを追記してみました。
    URLの形は合っていると思いますが、中身の正誤をどのように確認するべきか悩んでいます
    また、テスト環境で到達できるアドレスなのかの点については正直よくわかりません。どのように確認すれば良いでしょうか?ご教授いただければ有難いです。
  5. ローカルでテストしているならブラウザで直接そのアドレスが開けるかどうか、とか?
  6. @Kedaruma_Bond

    Questioner

    変なこと聞いてすいませんでした。
    回答ありがとうございます。ご指摘いただいた通りブラウザでテスト時に作成されたリンクにアクセスしたところ、テストのスクショと同じ画面が開かれることを確認しました。これでテスト環境で到達できることの確認になったと思っています。

valid_user 関数の冒頭に以下のように書くと @userparams の中身が dump.txt に書き出されます。期待するデータが入っているか確認すると何か分かるかもしれません。

    def valid_user
      require 'pp'
      IO.write('dump.txt', PP.pp([@user, params], ''))

      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        flash[:alert] = 'アカウントが認識できませんでした'
        redirect_to root_url
      end
    end
1Like

Comments

  1. @Kedaruma_Bond

    Questioner

    回答ありがとうございます。ご指摘いただいたCodeを追記して吐き出されたdump.txtを追記しました。最初がnil, になっているのはもしかして@userがないということでしょうか?
  2. そうなりますね。理由はよくわかりません。 valid_user の前に get_user が呼ばれるはずですし、 params には "email"=>"test1@example.com" が入っているようなので取得できないはずはないのですが。

    PP.pp(User.all, '') に変えて全ユーザーをダンプしたり、 get_user の中でもログを出したりして状況を確かめてみてください。
  3. @Kedaruma_Bond

    Questioner

    ご指摘ありがとうございます。User .allでダンプしてみてユーザーがやっぱり消えてることが確認できましたので、再度呼び出すCodeを追記しました。FactoryBotでcreateしたuserは一つのテスト後に消えてしまうらしいことが要因??かと漠然と考えています。
    残念ながらこのユーザーがない問題がなくなってもエラーは消えませんでした。
  4. テストごとにデータが削除されるのはそういうものです。

    expect(@user.authenticated?(:reset, token)).to be_truthy
    が失敗するのは @user のデータが古いままだからです。

    テスト内で @user を作った後、 password_reset_url に遷移するとデータベースのユーザーデータが書き換わりますが、 @user には反映されません。
    @user.reload を呼べばその時点のデータベースのデータをロードし直します。
  5. @Kedaruma_Bond

    Questioner

    ありがとうございます。ご指摘の通り@user.reloadを追記したところ認証メソッドはパスすること確認できました。しかし画面遷移についてはエラーのままでした。。。
  6. まだ valid_user の中で @user が nil のままですか?
  7. @Kedaruma_Bond

    Questioner

    valid_userの条件を@userのみにしても結果が変わらずでしたので、get_userで@userが拾えていないことが原因のようです。なぜ拾えないのかがわかりません。。。
  8. get_user が呼ばれていないか、呼ばれているが params が正しくないまたは対象のユーザーが存在しないかです。ログを出すなりして可能性を切り分けてください。
  9. @Kedaruma_Bond

    Questioner

    有難うございます。どうやら原因はget_userメソッドが走る時にDBがクリアされていることが要因のようです。何度も恐縮ですが何か思い当たることがあればご教授いただきたく。
  10. クリアされるのは it ブロックを抜けた後なので影響しないはずですが、確かに User.all が空のようですね。

    ログを眺めていたところ、 visit password_reset_url へのレスポンスを表す Started GET "/password_resets/a6VT略" for ... がないことに気づきました。

    もしかして test サーバは localhost:3000 以外のアドレスで動いていて、 localhost:3000 は development サーバに繋がるとか? development サーバは development データベースを見るのでテスト中に作った test データベースの中身は見えません。

    visit password_reset_url を visit URI(password_reset_url).path に変えると test サーバに繋がると思います。
  11. @Kedaruma_Bond

    Questioner

    有難うございます。ご指摘いただいたとおりに記述したところ、見事にtestサーバーのデータベースから参照するようになりました。残念ながらまだエラーは消えていませんがこれだけでも凄い嬉しかったです。何だか根本的な問題になってきましたが何か策があればご教授いただければ有難いです。
  12. テストコードが visit password_reset_url のままのようですが。 visit URI(password_reset_url).path に変えても解決しませんか?解決しない場合、具体的にどの処理が失敗しますか?
  13. @Kedaruma_Bond

    Questioner

    混乱させて申し訳ありません。spec codeのコピペ漏れです。実際はご教授いただいた`visit URI(password_reset_url).pathでtestして最後のページ遷移の箇所でエラーとなっています。
    test環境のサーバーとdevelopment環境のサーバーでアドレスが異なり、リンクに齟齬ができてしまうことが要因と考えています。
  14. 何度も言うようですが、 get_user で @user は取得できているか、 valid_user でバリデーションは通ったかなど、どこまで正常に動いてどこから異常なのかをきちんと確認してください。
  15. パスワードのリセットに成功すると

    flash[:success] = "パスワードが更新されました!"
    redirect_to @user

    で users#index あたりにリダイレクトしていそうですが、そのページに文字列「パスワードの再設定」が含まれない可能性はありませんか?つまり、リセットに成功しようがテストの

    expect(page).to have_content 'パスワードの再設定'

    が成り立たない可能性があるのでは?
  16. @Kedaruma_Bond

    Questioner

    有難うございます。お恥ずかしい限りです。ご指摘のとおり確認したところ、get_userでparams[:email]がnilになっていることが原因のようです。

    expect(page).to have_content 'パスワードの再設定'

    が成り立たない可能性があるのでは?
    ➡️追記したedit画面の通り<h1>で該当の文字列を載せていますので、edit画面に正しく移動できればテストは通ると考えています。

    誠に恐縮ですが、何か思い当たる策があればご教授いただきたく。
  17. > get_userでparams[:email]がnilになっている

    大変失礼しました! visit URI(password_reset_url).path だと "?email=..." の部分が抜け落ちたパスに遷移するため params が空になってしまいますね。 visit URI(password_reset_url).request_uri にするとうまくいくと思います。
  18. @Kedaruma_Bond

    Questioner

    ご指摘の通りに変更してみたところテスト通るようになりました。有難うございました。
    正直何がどうなっていたのかわからないことだらけですが、助けていただいたことで前に進めそうです。

あとpry-rails入れて直接コードをbreakして変数の状態を色々見てみるとか?

def get_user
  binding.pry # ココが先に止まるはず...
  @user = User.find_by(email: params[:email])
end

def valid_user
  binding.pry 
  # 止まったプロンプトで 
  # > @user
  # > params[:email]
  # > User.find_by(email: params[:email])
  # > User.all
  # とかやってみるとか?


  require 'pp'
  IO.write('dump.txt', PP.pp([@user, params], ''))

  unless (@user && @user.activated? &&
    @user.authenticated?(:reset, params[:id]))
    flash[:alert] = 'アカウントが認識できませんでした'
    redirect_to root_url
  end
end
1Like

Comments

  1. @Kedaruma_Bond

    Questioner

    binding:pryの活用についてご教授いただきたありがとうございます。
    テスト以外でもかなり活用できるものなので、今後も使っていきたいと思います。

インデントがおかしいのと、全角スペースが入ってるのと、endが1つ多かった。

require "rails_helper"

RSpec.describe "PasswordResets", type: :system do
  # -------------------------------------------------------
  # このletはどこかで使ってますか?
  # -------------------------------------------------------
  let(:user) { FactoryBot.create(:user) }

  before { ActionMailer::Base.deliveries.clear }

  describe "パスワード再設定について" do
    before do
      visit root_path
      find(".navbar-toggler").click
      page.evaluate_script("$(\".fade\").removeClass(\"fade\")")
      click_on "ログイン"
      click_on "(パスワードを忘れた方)"
    end

    context "メールのリンクを開いた時" do
      # -------------------------------------------------------
      # https://relishapp.com/rspec/rspec-core/v/2-11/docs/helper-methods/let-and-let
      # 最初から存在してほしいものはlet!で用意できます
      # @userみたいにインスタンス変数は使う必要がない
      # -------------------------------------------------------
      # よくわからないけどここで再度FactoryBotで呼び出すでことでuserが最後の確認まで存在できる
      let!(:user) { create :user }

      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})/
        token_regex = %r{(?<=password_resets\/)[^\/]+}

        # -------------------------------------------------------
        # これは本当に上のユーザーあてのメールなのか?
        # expect(password_reset_url).to include(URI.encode_www_form_component user.email)
        # としてみるとか
        # -------------------------------------------------------
        # 配信したメールのインスタンスを取得
        part =
          ActionMailer::Base
            .deliveries
            .last
            .body
            .parts
            .detect { |p| p.content_type == "text/html; charset=UTF-8" }
        password_reset_mail = part.body.raw_source

        # link pathを取得
        password_reset_url = password_reset_mail.match(path_regex)[0]
        p "linkの内容"
        p password_reset_url
        token = password_reset_url.match(token_regex)

        # digest変換前のtokenと比較するために出力
        p token

        # visit password_reset_url
        p [:password_reset_url, password_reset_url]

        # userがちゃんとできているか確認
        require "pp"
        IO.write("dump.txt", PP.pp(User.all, ""))

        # -------------------------------------------------------
        # ここは素直に visit password_reset_url だけでいいのでは?
        # password_reset_url 側で user.authenticated?(:reset, token) をしているのでは?
        #
        # https://railstutorial.jp/chapters/password_reset?version=5.1#sec-resetting_the_password
        # PasswordResetsController#valid_userで
        # @user.authenticated?(:reset, params[:id]))
        # とやってませんか?
        # -------------------------------------------------------
        # コメント指摘の通り再読み込み
        user.reload
        expect(user.authenticated?(:reset, token)).to be true # この行はパスするようになりました。

        # -------------------------------------------------------
        # user.authenticated?(:reset, token) をやって
        # visit password_reset_url でやろうとして2回目だからエラーになってるとか?
        # 多分リセット用のtokenは一度しか使えないですよね?
        # -------------------------------------------------------
        visit password_reset_url
        expect(page).to have_content "パスワードの再設定"
        # しかし上のcodeはエラーのままでした。。。
      end
    end
  end
end
1Like

Comments

  1. @Kedaruma_Bond

    Questioner

    丁寧な回答有難うございます。色々と勉強になります。

Your answer might help someone💌