Wardenのコントローラテストヘルパー
Railsには、色々と認証のためのライブラリがありますよね。
Restful-authentication, Authlogic, Device, Sorcery... と僕も色々試してみた結果、最近、Wardenというものに落ち着きました。
Wardenは、フレキシブルだという触れ込みのSorceryやAuthlogicよりもフレキシブルで、というかスクラッチから書くのに近いのですが、Deviseと同様に、routesファイルなどから、認証を利用できるので、非常に自由度が高い設計ができる利点があります。
ただ、そんな理想的なWardenにも欠けているものがあって、それはコントローラテストのためのヘルパーがないことで、これでは使い物にならない・・・ということで、僕が試しに書いてみました。
と言っても、ほとんどのコードはDeviseのDevise::TestHelpersから引っ張ってきたもので、Devise絡みのコードを取り除いて、Railsのconfigを読み込むようにコードを書き換えただけのものです。
このヘルパーをincludeすれば、コントローラテストからwardenにアクセスできるので、例えば、ログイン状態にするために、warden.set_user(user)したり、現在ログインしてるユーザを取得するために、warden.userとかできます。
# test/test_helper.rb
require 'test_helpers/warden.rb'
class ActionController::TestCase
include Warden::Test::ControllerHelpers
end
# test/test_helpers/warden.rb
module Warden
# Warden::Test::ControllerHelpers provides a facility to test controllers in isolation
# Most of the code was extracted from Devise's Devise::TestHelpers.
module Test
module ControllerHelpers
def self.included(base)
base.class_eval do
setup :setup_controller_for_warden, :warden if respond_to?(:setup)
end
end
# Override process to consider warden.
def process(*)
# Make sure we always return @response, a la ActionController::TestCase::Behavior#process, even if warden interrupts
_catch_warden {super} || @response
end
# We need to setup the environment variables and the response in the controller
def setup_controller_for_warden
@request.env['action_controller.instance'] = @controller
end
# Quick access to Warden::Proxy.
def warden
@warden ||= begin
manager = Warden::Manager.new(nil, &Rails.application.config.middleware.detect{|m| m.name == 'Warden::Manager'}.block)
@request.env['warden'] = Warden::Proxy.new(@request.env, manager)
end
end
protected
# Catch warden continuations and handle like the middleware would.
# Returns nil when interrupted, otherwise the normal result of the block.
def _catch_warden(&block)
result = catch(:warden, &block)
if result.is_a?(Hash) && !warden.custom_failure? && !@controller.send(:performed?)
result[:action] ||= :unauthenticated
env = @controller.request.env
env['PATH_INFO'] = "/#{result[:action]}"
env['warden.options'] = result
Warden::Manager._run_callbacks(:before_failure, env, result)
status, headers, body = warden.config[:failure_app].call(env).to_a
@controller.send :render, :status => status, :text => body,
:content_type => headers['Content-Type'], :location => headers['Location']
nil
else
result
end
end
end
end
end
By Kentaro Imai on June 22nd, 2012
Controller test helpers for Warden
Restful-authentication, Authlogic, Device, Sorcery... I was tired of wandering authentication libraries for Rails.
At the end, I settled into Warden.
Warden gives us full flexibility. It's far more flexible than Sorcery or Authlogic, actually to use Warden is more like writing authentication from scratch. And, we can access authentication in routes file as Devise.
But Warden had lacked one thing: test helpers for controller testing.
So, I wrote controller test helpers for Warden.
Most of the code was extracted from Devise's Devise::TestHelpers.
I cut off Devise related code and wrote some code for loading Rails application config.
With this test helpers, you can access to warden in your controller test, and you can do such as warden.set_user(user) to login, and warden.user to get current user, etc.
# test/test_helper.rb
require 'test_helpers/warden.rb'
class ActionController::TestCase
include Warden::Test::ControllerHelpers
end
# test/test_helpers/warden.rb
module Warden
# Warden::Test::ControllerHelpers provides a facility to test controllers in isolation
# Most of the code was extracted from Devise's Devise::TestHelpers.
module Test
module ControllerHelpers
def self.included(base)
base.class_eval do
setup :setup_controller_for_warden, :warden if respond_to?(:setup)
end
end
# Override process to consider warden.
def process(*)
# Make sure we always return @response, a la ActionController::TestCase::Behavior#process, even if warden interrupts
_catch_warden {super} || @response
end
# We need to setup the environment variables and the response in the controller
def setup_controller_for_warden
@request.env['action_controller.instance'] = @controller
end
# Quick access to Warden::Proxy.
def warden
@warden ||= begin
manager = Warden::Manager.new(nil, &Rails.application.config.middleware.detect{|m| m.name == 'Warden::Manager'}.block)
@request.env['warden'] = Warden::Proxy.new(@request.env, manager)
end
end
protected
# Catch warden continuations and handle like the middleware would.
# Returns nil when interrupted, otherwise the normal result of the block.
def _catch_warden(&block)
result = catch(:warden, &block)
if result.is_a?(Hash) && !warden.custom_failure? && !@controller.send(:performed?)
result[:action] ||= :unauthenticated
env = @controller.request.env
env['PATH_INFO'] = "/#{result[:action]}"
env['warden.options'] = result
Warden::Manager._run_callbacks(:before_failure, env, result)
status, headers, body = warden.config[:failure_app].call(env).to_a
@controller.send :render, :status => status, :text => body,
:content_type => headers['Content-Type'], :location => headers['Location']
nil
else
result
end
end
end
end
end
By Kentaro Imai on June 22nd, 2012
