【问题标题】:Sessions not persisting in Rails 5.2 integration testsRails 5.2 集成测试中的会话不持久
【发布时间】:2019-02-03 03:40:10
【问题描述】:

我有一个多租户应用程序,它将 Apartment 用于 postgreSQL 模式,将 Devise 用于用户身份验证。在我尝试编写一些集成测试之前,一切都运行顺利。

这是我目前所拥有的精简版(请随时询问更多信息):

# test/support/sign_in_helper.rb
module SignInHelper
  def sign_in_as(name)
    sign_in users(name)
  end
end


# test/test_helper.rb
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

#...

class ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers
  include SignInHelper
end


# test/system/article/post_test.rb
require "application_system_test_case"

class Article::PostTest < ApplicationSystemTestCase
  test 'post a new document' do
    sign_in_as :will
    visit articles_path

    click_on 'Add New Article' # redirected to login page after clicking this button
    fill_in 'Name', with: 'Hello world!'
    fill_in 'Content', with: 'Yes yes'

    click_on 'Create Article'
    assert_select 'h1', /Hello world!/
  end
end

articles_path 需要经过身份验证的用户,所以我知道登录助手有效。然而,每当我尝试转到另一个链接时,突然用户就无法通过身份验证。

我像这样修改了 Devise 的 authenticate_user! 方法:

def authenticate_user!(*args)
  byebug
  super
end

并确认warden.authenticated?articles_path 返回了truefalse 随后尝试导航到new_article_path

我在集成测试类型、控制器和系统中都注意到了这种行为。但是,在开发环境中使用此应用时,这不是问题。

最令人沮丧的部分是,我有一个不同的应用程序,它的设置似乎与此应用程序相同,但在测试时不会出现此身份验证问题。

我该如何调试这个问题?

系统

  • 导轨:5.2.2
  • 设计:4.5.0
  • 水豚:3.13.2

更新 1(2019 年 2 月 4 日)

这是 @BKSpureon 要求的 Articles 控制器

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :archive]

  def index
    @articles = Article.where(archived: false)
  end

  def show
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article, notice: 'Your article was successfully created.'
    else
      flash[:error] = @article.errors.full_messages.to_sentence
      render :new
    end
  end

  def edit
  end

  def update
    if @article.update(article_params)
      redirect_to articles_path, notice: 'Your article was successfully updated.'
    else
      flash[:error] = @article.errors.full_messages.to_sentence
      render :edit
    end
  end

  def archive
    if @article.archive!
      redirect_to articles_path, notice: 'Your article was successfully archived.'
    else
      render :edit
    end
  end

  private
    def set_article
      @article = Article.find(params[:id])
    end

    def article_params
      params.require(:article).permit(:name, :content, :archived)
    end
end

更新 2(2019 年 2 月 4 日)

我写了简单的中间件,放在Warden之前调试:

# lib/debug_warden_middleware.rb
class DebugWardenMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    @status, @headers, @response = @app.call(env)
    if env['warden'].present?
      puts "User (#{env['warden'].user.present?}), Class: #{@response.class.name}"
    end
    return [@status, @headers, @response]
  end
end

# config/application.rb
#...
module AppName
  class Application < Rails::Application
    # ...

    config.middleware.insert_before Warden::Manager, DebugWardenMiddleware
  end
end

而且我注意到,warden 似乎在每次请求后都会清除其用户,包括资产请求:

bin/rails test:system
Run options: --seed 39763

# Running:

Capybara starting Puma...
* Version 3.9.1 , codename: Private Caller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:57466
User (true), Uri: ActionDispatch::Response::RackBody
User (false), Uri: Sprockets::Asset
User (false), Uri: Sprockets::Asset
User (false), Uri: Sprockets::Asset
User (false), Uri: ActionDispatch::Response::RackBody
User (false), Uri: ActionDispatch::Response::RackBody
User (false), Uri: Sprockets::Asset
[Screenshot]: tmp/screenshots/failures_test_post_a_new_article.png
E

Error:
Agenda::PostTest#test_post_a_new_article:
Capybara::ElementNotFound: Unable to find field "Name"
    test/system/article/post_test.rb:9:in `block in <class:PostTest>'


bin/rails test test/system/article/post_test.rb:4

附带说明一下,我同时使用 Sprockets 和 Webpacker。

【问题讨论】:

  • 粘贴到文章控制器中。一些想法:(i)在articles_path 视图中添加&lt;% byebug %&gt;,然后查看您那里是否有当前用户 - 以及该用户是否已登录? (ii) 再次使用byebug 检查文章控制器中的new 操作是否已被击中,并再次检查那里是否有current_user。然后逐行检查您是否还有当前用户。希望对您有所帮助。
  • 谢谢@BKSpureon,我已经用 ArticlesController 更新了这个问题。将&lt;% byebug %&gt; 插入索引视图时,我发现用户 登录。但是它永远不会进入new 操作,因为它失败了Devise 的before_action authenticate_user!。这就是为什么我认为它与未在测试环境中持续存在的会话有关。
  • 试试去掉monkey补丁,在你的登录助手中,添加一个bybug语句,测试用户是否成功登录?
  • 我不确定如果用户在没有猴子补丁的情况下登录,我将如何测试。在集成测试中,我无权访问 current_userwardenenv
  • 我的猜测是您的设置有问题,导致通过登录设置的身份验证 cookie 在以后的请求中无法发送(或接受)。如果没有工作示例,很难调试。看一下 Benj 为 this question 创建的 github.com/randoum/as_bug 作为示例/起点,用于创建一个最小的示例来调试 RSpec on Rails 问题,并创建类似的东西供我们深入研究。

标签: ruby-on-rails devise integration-testing minitest warden


【解决方案1】:

好的,在@BKSpurgeon(在聊天消息中)和@Old Pro 都建议我尝试在单独的应用程序中重新创建问题以与大家分享之后,我偶然发现了答案。

因为这个应用程序是多租户的并且使用了 Apartment gem,所以我的测试设置有点复杂。不幸的是,我没有包括其中的一些设置,认为这无关紧要 (big mistake, huge!)。直到我尝试重新创建我的应用程序时,我才注意到这个缺陷。这是我使用的测试设置:

# test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'

# support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

# Apartment setup
Apartment::Tenant.drop('sample') rescue nil
Apartment::Tenant.create('sample') rescue nil
Apartment::Tenant.switch! 'sample'

# Capybara setup
Capybara.configure do |config|
  config.default_host = 'http://lvh.me'
  config.always_include_port = true
end

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

class ActionDispatch::IntegrationTest
  include Devise::Test::IntegrationHelpers
  include SubdomainHelper
  include SignInHelper # dependent on SubomainHelper

  setup do
    default_url_options[:host] = 'lvh.me'
    Apartment::Tenant.switch! 'public'
  end
end

...

# test/support/sign_in_helper.rb
module SignInHelper
  def sign_in_as(name)
    # This right here was the problem code
    user = users(name)
    use_account user.account
    sign_in user
  end
end

...

# test/support/subdomain_helper.rb
module SubdomainHelper
  def use_account(account)
    account.save unless account.persisted?

    default_url_options[:host] = "#{account.subdomain}.lvh.me"
    Capybara.app_host = "http://#{account.subdomain}.lvh.me"
    Apartment::Tenant.switch! account.subdomain
  end
end

...

# test/system/article/post_test.rb
require "application_system_test_case"

class Article::PostTest < ApplicationSystemTestCase
  test 'post a new document' do
    sign_in_as :will
    visit articles_path

    click_on 'Add New Article' # redirected to login page after clicking this button
    fill_in 'Name', with: 'Hello world!'
    fill_in 'Content', with: 'Yes yes'

    click_on 'Create Article'
    assert_select 'h1', /Hello world!/
  end
end

最终的问题是,在我实际切换到包含该用户的租户之前,我试图在 SignInHelper 中获取用户。有了这些知识,我将代码更改为如下所示:

# test/support/sign_in_helper.rb
module SignInHelper
  def sign_in_as(name, account)
    use_account accounts(account)
    sign_in users(name)
  end
end

...

# test/system/article/post_test.rb
require "application_system_test_case"

class Article::PostTest < ApplicationSystemTestCase
  test 'post a new document' do
    sign_in_as :will, :sample

    # ...
  end
end

现在会话仍在持续。仍然让我感到困惑的一件事是,为什么我完全可以登录?我把它归结为挥之不去的测试数据,但这更像是猜测。

【讨论】:

  • 很高兴你解决了它。您的评论:“仍然让我感到困惑的一件事是,为什么我能够登录?我将其归结为挥之不去的测试数据,但这更像是猜测。”...... ....不要猜测:尝试找出原因。如果它不是挥之不去的测试数据,那么您将面临严重的安全漏洞!
猜你喜欢
  • 2016-11-15
  • 2016-04-04
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2015-08-16
  • 2011-07-05
  • 1970-01-01
相关资源
最近更新 更多