У меня есть многопользовательское приложение, которое использует 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? вернул true для articles_path, но false при последующей попытке перейти к new_article_path.
Я заметил такое поведение в обоих типах интеграционных тестов, контроллере и системе. Однако это не проблема при использовании этого приложения в среде разработки.
Больше всего разочаровывает то, что у меня есть другое приложение, которое, похоже, имеет настройки, идентичные этому приложению, но эта проблема аутентификации не возникает во время тестирования.
Как я могу отладить эту проблему?
Система
Обновление 1 (04 февраля 2019 г.)
Вот контроллер статей по запросу @BKSpurgeon
# 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 (04 февраля 2019 г.)
Я написал простое промежуточное ПО и разместил его перед 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
И я заметил, что надзиратель очищает своего пользователя после каждого запроса, включая запросы на активы:
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.
Спасибо @BKSpurgeon, я обновил вопрос с помощью ArticlesController. При вставке <% byebug %> в представление индекса я обнаружил, что пользователь является вошел в систему. Однако он никогда не доходит до действия new, потому что он не выполняет before_actionauthenticate_user! Devise. Вот почему я предположил, что это как-то связано с сеансами, которые не сохраняются в тестовой среде.
Попробуйте удалить патч для обезьян и в своем помощнике по входу добавить оператор bybug и проверить, успешно ли пользователь вошел в систему?
Я не уверен, как я буду проверять, вошел ли пользователь в систему без патча для обезьян. В тесте интеграции у меня нет доступа к current_user, warden или env.
см. здесь: gist.github.com/BKSpurgeon/56cdf362d78848b070daf1bd5bd3ba2e
Спасибо за пример @BKSpurgeon. Я удалил патч обезьяны и вставил byebug, но я не уверен, как я буду проверять, вошел ли пользователь в систему. Обычно я проверял бы это с помощью current_user, warden или env, но они недоступны в контексте ActionDispatch::IntegrationTest
я обновил суть с дальнейшими комментариями. важно - удалить патч обезьяны, прежде чем тестировать эти вещи. возможно, в новой ветке git.
Давайте продолжить обсуждение в чате.
Я предполагаю, что что-то не так с вашей настройкой, из-за которой файл cookie аутентификации, установленный через вход в систему, не отправляется (или не принимается) в будущих запросах. Очень сложно отлаживать без рабочего примера. Взгляните на github.com/randoum/as_bug, который Бендж создал для этот вопрос, в качестве примера/отправной точки для создания минимального примера для отладки проблем с RSpec на Rails и создайте что-то подобное, чтобы мы могли в нем разобраться.





Хорошо, я наткнулся на ответ после того, как @БКСперджен (в сообщении чата) и @Старый профессионал рекомендовали мне попытаться воссоздать проблему в отдельном приложении, чтобы поделиться со всеми вами.
Поскольку это приложение является многопользовательским и использует драгоценный камень Apartment, моя настройка тестирования была немного запутанной. К сожалению, я не включил некоторые из этих настроек, думая, что они не имеют значения (большая ошибка, огромная!). Только когда я попытался воссоздать свое приложение, я заметил недостаток. Вот моя тестовая установка, которую я использовал:
# 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
И теперь сеансы сохраняются. Одна вещь, которая до сих пор меня смущает, это то, почему я вообще смог войти в систему? Я списал это на затянувшиеся тестовые данные, но это скорее предположение, чем что-либо еще.
рад, что вы решили это. Ваш комментарий: «Меня все еще смущает одна вещь: почему я вообще смог войти в систему? Я списал это на затянувшиеся тестовые данные, но это скорее предположение, чем что-либо еще»....... ....не гадай: попробуй выяснить, почему. если это НЕ устаревшие тестовые данные, то у вас будет серьезная брешь в системе безопасности!!
вставьте в контроллер статей. некоторые идеи: (i) добавить
<% byebug %>в представлениеarticles_path, а затем посмотреть, есть ли у вас текущий пользователь - и если пользователь вошел в систему? (ii) проверьте, было ли выполнено действиеnewв контроллере статей, снова используяbyebug, и снова проверьте, есть ли у вас тамcurrent_user. Затем пройдитесь, строка за строкой, проверяя, есть ли у вас еще текущий пользователь. Надеюсь, это поможет.