diff --git a/lib/devise/test_helpers.rb b/lib/devise/test_helpers.rb index b6b49f73..e1f890d9 100644 --- a/lib/devise/test_helpers.rb +++ b/lib/devise/test_helpers.rb @@ -74,50 +74,48 @@ module Devise result ||= {} - # set the response. In production, the rack result is returned from Warden::Manager#call, which - # the following is modelled on. + # Set the response. In production, the rack result is returned + # from Warden::Manager#call, which the following is modelled on. case result - when Array - if result.first == 401 && intercept_401?(env) # does this happen during testing? - process_unauthenticated(env) - else - result # result is already the array rack response - end - when Hash - process_unauthenticated(env, result) + when Array + if result.first == 401 && intercept_401?(env) # does this happen during testing? + _process_unauthenticated(env) else - # any other type, eg: ActionController::TestResponse pass through unchanged. result + end + when Hash + _process_unauthenticated(env, result) + else + result end end - - def process_unauthenticated(env, options = {}) - + def _process_unauthenticated(env, options = {}) options[:action] ||= :unauthenticated proxy = env['warden'] result = options[:result] || proxy.result - # set the controller response as well as return our rack response array + ret = case result - when :redirect - body = proxy.message || "You are being redirected to #{proxy.headers['Location']}" - [proxy.status, proxy.headers, [body]] - when :custom - proxy.custom_response - else - env["PATH_INFO"] = "/#{options[:action]}" - env["warden.options"] = options - Warden::Manager._run_callbacks(:before_failure, env, options) + when :redirect + body = proxy.message || "You are being redirected to #{proxy.headers['Location']}" + [proxy.status, proxy.headers, [body]] + when :custom + proxy.custom_response + else + env["PATH_INFO"] = "/#{options[:action]}" + env["warden.options"] = options + Warden::Manager._run_callbacks(:before_failure, env, options) - status, headers, body = Devise.warden_config[:failure_app].call(env).to_a - @controller.send :render, :status => status, :text => body, - :content_type => headers["Content-Type"], :location => headers["Location"] - nil # causes process return @response - end + status, headers, body = Devise.warden_config[:failure_app].call(env).to_a + @controller.send :render, :status => status, :text => body, + :content_type => headers["Content-Type"], :location => headers["Location"] + nil # causes process return @response + end - # ensure that the controller response is set up. In production, this is not necessary since warden returns - # the results to rack. However, at testing time, we want the response to be available to the testing framework - # to verify what would be returned to rack. + # ensure that the controller response is set up. In production, this is + # not necessary since warden returns the results to rack. However, at + # testing time, we want the response to be available to the testing + # framework to verify what would be returned to rack. if ret.is_a?(Array) # ensure the controller response is set to our response. @controller.response ||= @response @@ -127,7 +125,6 @@ module Devise end ret - end end end diff --git a/test/controllers/custom_strategy_test.rb b/test/controllers/custom_strategy_test.rb index 18804092..798ca777 100644 --- a/test/controllers/custom_strategy_test.rb +++ b/test/controllers/custom_strategy_test.rb @@ -3,74 +3,42 @@ require 'ostruct' require 'warden/strategies/base' require 'devise/test_helpers' -class MyController < DeviseController +class CustomStrategyController < ActionController::Base + def new + warden.authenticate!(:custom_strategy) + end +end + +# These tests are to prove that a warden strategy can successfully +# return a custom response, including a specific status code and +# custom http response headers. This does work in production, +# however, at the time of writing this, the Devise test helpers do +# not recognise the custom response and proceed to calling the +# Failure App. This makes it impossible to write tests for a +# strategy that return a custom response with Devise. +class CustomStrategy < Warden::Strategies::Base + def authenticate! + custom_headers = { "X-FOO" => "BAR" } + response = Rack::Response.new("BAD REQUEST", 400, custom_headers) + custom! response.finish + end end class CustomStrategyTest < ActionController::TestCase - tests MyController + tests CustomStrategyController include Devise::TestHelpers - # These tests are to prove that a warden strategy can successfully return a custom response, including a specific - # status code and custom http response headers. This does work in production, however, at the time of writing this, - # the Devise test helpers do not recognise the custom response and proceed to calling the Failure App. This makes - # it impossible to write tests for a strategy that return a custom response with Devise. - # - # The code this test needs to verify is in Devise::TestHelpers#_catch_warden (which appears to have no other test - # coverage at this point.) The functionality of this function should mirror Warden::Manager#call(env) and - # Warden::Manager#process_unauthenticated which correctly detects the custom response when set by a strategy. - # - class CustomStrategy < Warden::Strategies::Base - def authenticate! - custom_headers = { "X-FOO" => "BAR" } - response = Rack::Response.new("BAD REQUEST", 400, custom_headers) - custom! response.finish - end + setup do + Warden::Strategies.add(:custom_strategy, CustomStrategy) end - # call the custom strategy, returning the rack result array - def call_custom(env_params={}) - request.env ||= {} - request.env.merge! ({ - 'REQUEST_URI' => 'http://test.host/', - 'HTTP_HOST' => 'test.host', - 'REQUEST_METHOD' => 'GET', - 'warden.options' => { :scope => :user }, - 'rack.session' => {}, - 'action_dispatch.request.formats' => Array(env_params.delete('formats') || Mime::HTML), - 'rack.input' => "", - }) - request.env.merge!(env_params) - env = request.env - - # create a strategy instance - strategy = CustomStrategy.new env, :user - - # processing a test request eventually calls _catch_warden: - ret = _catch_warden do - - # when a controller action is triggered, its before filter would require authentication. Devise uses - # warden to execute the strategies. - - # simulate its selection as the winning strategy (the custom response is read from .winning_strategy) - warden.winning_strategy = strategy - - # And then the strategy is executed: - strategy.authenticate! - - # after the strategy executes, halt! has been called (from in custom!) above which eventually results in - # the :warden symbol being thrown, which is caught in Devise::TestHelpers#_catch_warden - throw :warden - end - - # after this point, @response should be set to the custom response when triggered by a custom strategy, or - # the response of the FailureApp as normal. - ret + teardown do + Warden::Strategies._strategies.delete(:custom_strategy) end - test "custom strategy can return its own status code" do - ret = call_custom + ret = get :new # check the returned rack array assert ret.is_a?(Array) @@ -82,7 +50,7 @@ class CustomStrategyTest < ActionController::TestCase end test "custom strategy can return custom headers" do - ret = call_custom + ret = get :new # check the returned rack array assert ret.is_a?(Array) @@ -91,5 +59,4 @@ class CustomStrategyTest < ActionController::TestCase # check the saved response headers as well. assert_equal response.headers['X-FOO'], 'BAR' end - end diff --git a/test/rails_app/config/routes.rb b/test/rails_app/config/routes.rb index 052de586..ec0b9895 100644 --- a/test/rails_app/config/routes.rb +++ b/test/rails_app/config/routes.rb @@ -84,6 +84,7 @@ Rails.application.routes.draw do match "/set", :to => "home#set" match "/unauthenticated", :to => "home#unauthenticated" + match "/custom_strategy/new" root :to => "home#index" end