ruby-on-rails Rails和MiniTest集成测试-重定向后无法访问会话

gr8qqesn  于 2022-12-15  发布在  Ruby
关注(0)|答案(1)|浏览(143)

显然Rails已经弃用了ControllerTest,转而使用IntegrationTests。我想基于登录用户测试行为。我不能直接设置当前用户,必须先使用应用的登录流程,然后才能测试感兴趣的路径和行为。这很好,但是在负责处理登录和设置会话中细节的控制器完成任务后,会话似乎消失了!
作为一个简单的测试,我有一个带有WelcomeController的基本Rails应用程序,它的显示视图将根据用户的当前状态(确定会话是否设置了user_id以及该ID是否对应于DB中的记录)显示“Logged In”或“Logged Out”。
登录系统由OmniAuth处理,这很容易被模仿,我创建了一个OmniAuth处理程序返回的用户,登录详细信息由SessionsController处理,它将显示一个表单,该表单通过邮寄方式将电子邮件和密码发送到SessionsController.create(此登录操作的路径是/auth/identity/callback)。

# Highly simplified logic for the login handler
class SessionsController < ApplicationController
  def create
    auth_hash = request.env['omniauth.auth']
    user = User.find_with_omniauth(auth_hash)
    session[:user_id] = user.id
    Rails.logger.debug(session.inspect)
    redirect_to root_path
  end 
end

class WelcomeController < ApplicationController
  def show
    Rails.logger.debug(session.inspect)
  end
end

class ApplicationController < ActionController::Base
  def current_user
    @current_user ||= if session.key?(:user_id)
                        User.find_by(id: session[:user_id])
                      else
                        nil
                      end
  end
  
  def logged_in?
    current_user.present?
  end
end

现在进行测试:

require 'test_helper'

class WelcomeControllerTest < ActionDispatch::IntegrationTest
  include FactoryBot::Syntax::Methods

  setup do
    OmniAuth.config.test_mode = true
  end

  teardown do
    OmniAuth.config.test_mode = false
    OmniAuth.config.mock_auth[:identity] = nil
  end

  def manual_identity_log_in(user_with_identity)
    OmniAuth.config.mock_auth[:identity] =  OmniAuth::AuthHash.new(
      {
        provider: 'identity',
        uid: user_with_identity.id,
        info: {
          email: user_with_identity.email,
          first_name: user_with_identity.first_name,
          last_name: user_with_identity.last_name,
          name: user_with_identity.full_name
        }
      })

    post '/auth/identity/callback', params: {email: user_with_identity.email, password: ''}
  end

  test "can see the landing page for logged out users" do
    get "/"
    assert_select( "h1", "Logged Out")
  end

  test "can see the welcome page for logged in users" do
    current_user = create(:user_with_identity)
    manual_identity_log_in(current_user)
    get "/"
    assert_select( "h1", "Logged In")
  end
end

测试失败-始终显示“已注销”消息。
注:如果我在SessionsController.create结束时将会话打印到日志中,我就可以看到我所期望的一切-登录已经工作并且设置完毕。但是在WelcomeController中记录的会话示例(由测试中的后续get "/"调用触发)对我来说,这显然是一个失败,因为会话没有在两个请求之间持久化。在上下文中,此测试是否始终确定用户要注销。
尽管进行了无休止的搜索和阅读,但在我看来,我一定错过了一些非常明显的东西,因为这是IntegrationTests的预期方法。
短暂性脑缺血发作

6yjfywim

6yjfywim1#

我已经找到了解决方案,它与会话存储配置有关。这可能有点“明白了”
当开发Rails应用程序并在开发模式下运行时,我仍然使用自签名证书以便在https上运行localhost。当然,当应用程序部署到staging和production时,它也是通过https运行的。因此,我有一个通用的初始化器,用于所有只为https设置会话安全的环境:

# config/initializers/session_store.rb
Rails.application.config.session_store(
  :cookie_store,
  key: '_myapp_session',
  httponly: true,
  secure: true  # <--- the problem
)

但是测试 * 没有使用https*,所以这个cookie配置根本不会通过cookie持久化会话。
我的解决方案是将测试模式的secure设置为false

# config/initializers/session_store.rb
Rails.application.config.session_store(
  :cookie_store,
  key: '_myapp_session',
  httponly: true,
  secure: !Rails.env.test?
)

现在,会话跨两个单独的请求持久化,测试完成。

相关问题