devise icon indicating copy to clipboard operation
devise copied to clipboard

Devise with Rails 8 and Rspec does not load strategies when run single test

Open Piioo opened this issue 8 months ago • 6 comments
trafficstars

Hello,

I'm not sure if I'm in the right place, I have a problem with devise strategies and rspec after Rails update to version 8.0.1.

We have a custom strategy which we load into the devise strategies (see below) and it does work fine on development/production and test when I run all test from one file at once. The strategies are all loaded in the manager and are found in proxy.rb to run the correct authenticate! in our custom strategy.

When I run a specific test like rspec spec/requests/foo/create_spec.rb:123, then the manager default_strategies are always empty and the test stops at the authentication.

I have seen others have similar problems when running one test: https://github.com/heartcombo/devise/issues/5752#issuecomment-2665179578

Has someone an Idea?

require 'devise/strategies/cookie_auth'

module Devise
  module Models
    module CookieAuth
      extend ActiveSupport::Concern
    end
  end
end

module Devise
  module Strategies
    class CookieAuth < Warden::Strategies::Base
      def valid?
        !cookies[:access_token].nil?
      end

      def authenticate!
        ...
      end

      def store?
        false
      end
    end
  end
end

Warden::Strategies.add(:cookie_auth, Devise::Strategies::CookieAuth)

Devise.add_module(:cookie_auth, {
  strategy: true,
  controller: :sessions,
  model: 'devise/models/cookie_auth',
  route: :session
})

devise (4.9.4) rails (8.0.1) (with 8.0.2 it's not better) rspec-core (3.13.3) rspec-rails (7.1.1)

Piioo avatar Mar 19 '25 15:03 Piioo

Same problem here.

I have the identical configuration:

  • devise (4.9.4)
  • rails (8.0.2)
  • rspec-core (3.13.3)
  • rspec-rails (7.1.1)

CuddlyBunion341 avatar Mar 24 '25 17:03 CuddlyBunion341

I believe I have some insight, rough though it may be. I just ran into a similar pattern today (single spec failing, but not as a suite) and have been a few hours trying to make sense of it.

In my particular scenario, the problem looks to be coming from devise's controller helpers being set up before rails routes are drawn. In my rails_helper.rb I have

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
end

and if you look at that helpers code, there is some setup there. One of the methods called is #warden, which builds the proxy based on Devise.warden_config and sets it on the env. However, at that point warden has not been configured with any strategies which may be defined. That happens here, when Rails routes are finalized.

What works for me, for now, is

RSpec.configure do |config|
  config.before(:suite) do
    Devise.configure_warden!
  end
end

Update: I wrote before fully reading the linked thread and I see there's a similar approach and conclusion. Though I do find my approach to be more concise and focused to the problem at hand.

pungerfreak avatar Mar 25 '25 22:03 pungerfreak

Thanks; the Devise.configure_warden! fix sorted it for me.

For reference, for me the error only manifested locally (not CI), and in the first test.

The exception (locally) when running docker-compose exec -e "RAILS_ENV=test" app bin/rspec is below.

The repo is public in case helpful for reproduction - https://github.com/landgrab/landgrab/pull/580

ArgumentError:
  wrong number of arguments (given 1, expected 2)
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/models/authenticatable.rb:241:in `serialize_from_session'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise.rb:496:in `block (2 levels) in configure_warden!'
# /usr/local/bundle/gems/warden-1.2.9/lib/warden/session_serializer.rb:35:in `fetch'
# /usr/local/bundle/gems/warden-1.2.9/lib/warden/proxy.rb:224:in `user'
# /usr/local/bundle/gems/warden-1.2.9/lib/warden/proxy.rb:334:in `_perform_authentication'
# /usr/local/bundle/gems/warden-1.2.9/lib/warden/proxy.rb:133:in `authenticate!'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/controllers/helpers.rb:120:in `authenticate_user!'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/test/controller_helpers.rb:35:in `block in process'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/test/controller_helpers.rb:104:in `catch'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/test/controller_helpers.rb:104:in `_catch_warden'
# /usr/local/bundle/gems/devise-4.9.4/lib/devise/test/controller_helpers.rb:35:in `process'
# ./spec/controllers/admin/plots_controller_spec.rb:16:in `block (3 levels) in <main>'
# /usr/local/bundle/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'

olliebennett avatar Mar 29 '25 09:03 olliebennett

Hmm, the solutions does not work for us .

Piioo avatar Apr 15 '25 11:04 Piioo

While I can peck my way around, I lack fluency in devise. I went back to where I had implemented the solution that worked for me, and tried to understand exactly what you're experiencing, @Piioo, but was unlucky.

While I had experienced this in controller specs, I was able to reproduce in request specs. Given I have little context of your scenario, it's hard to assume that I can grasp what's going on in your app, but to get as close as possible I dropped in the example code you posted (tweaked it for some observability), and configured it as such so that it's sure to be the first strategy tried

Devise.setup do |config|
  config.warden do |manager|
    manager.default_strategies(scope: :user).unshift :cookie_auth
  end
end

Given you didn't share any additional implementation details, it's only for me to guess as to how your specific scenario is set up. Here's my example, simplified.

it "confirms the sign in workflow" do
  user = create(:user)
  post "/sign-in", params: { user: { email: user.email, password: "temp12345678" } }
  expect(response.status).to eq(303)
end

This fails without the fix I implemented

expected: 303
got: 422

but when I drop Devise.configure_warden! into my config.before(:suite) block, I can observe that the :cookie_auth strategy is attempted.

Now in the above example, there's an additional step I left out during testing that was causing the example to pass without the addition of Devise.configure_warden!, even when run singly.

it "confirms the sign in workflow" do
  user = create(:user)
  get "/account" # the additional step
  post response.location, params: { user: { email: user.email, password: "temp12345678" } }
  expect(response.status).to eq(303)
end

If adding something similar to your failing example causes it to pass, that could provide some additional clues into what is causing the problem.

Hope that provides, at the least, a tiniest grain of insight.

pungerfreak avatar Apr 15 '25 17:04 pungerfreak

I don't think this problem is limited to tests. I am seeing this in a production 8.0.2 app. See #5774

jasonperrone avatar Apr 18 '25 14:04 jasonperrone