Sorcery/sorcery

Docs for Testing-Rails need update for request specs

brentgreeff opened this issue · 2 comments

This page https://github.com/Sorcery/sorcery/wiki/Testing-Rails needs updating.

For request specs (I dont need helpers for system specs - and I dont use controller specs)

I added:

config.include Sorcery::TestHelpers::Rails::Request, type: :request - in rails_helper.rb

-  post 'login' => "user_sessions#create"
+  post 'login' => "user_sessions#create", as: 'user_sessions'

I had to make this change - because I got undefined method user_sessions_url

Then using pry I figured out that the password assumed is secret - which is not a good password since my user model has

  validates :password, length: { minimum: 8 }, if: -> {
    new_record? || changes[:crypted_password]
  }

In my test - I added

User.class_eval do
  clear_validators!
end
  • which I dont really want to do.

Configuration

  • Sorcery Version: 0.16.3
  • Ruby Version: 3.0.4p208
  • Framework: rails-7.0.3.1
  • Platform: OSX

This messed up my user specs - so I changed it to

before do
    User.class_eval { clear_validators! }
  end
  after do
    Object.send(:remove_const, :User)
    load 'app/models/user.rb'
  end

So I decided to look in the code - https://github.com/Sorcery/sorcery/blob/master/lib/sorcery/test_helpers/rails/request.rb

def login_user(user = nil, password = 'secret', route = nil, http_method = :post)

I see the password param

My call is

login_user( create(:user), password: 'password' )

That doesnt work - using pry in user_sessions controller.

params
=> #<ActionController::Parameters {"email"=>"axs@gms-community.com", "password"=>#<ActionController::Parameters {"password"=>"password"} permitted: false>, "controller"=>"user_sessions", "action"=>"create"} permitted: false>

Unfortunately, Sorcery is pretty irksome in that you can't sign in a user until they're active... and if you create a user with an active state sorcery will overwrite the user's activation state and make them an inactive user before creating the record. I suspect you're running into that. You need to ensure activate! is called on your user after creation by declaring it like let(:user) { create :user, &:activate! }, or define a trait that will trigger activation after the record is created:

# spec/factories/users.rb
FactoryBot.define do
  factory :user, class: 'User' do
    password { 'MyDefaultFactoryPassword!' }

    # Alternative:
    # let(:user) { create :user, &:activate! }
    trait :active do
      transient do
        activation_state { :active }
      end
    end

    after :create do |user, evaluator|
      user.activate! if evaluator.activation_state == :active
    end
  end
end

To support before { sign_in user } in your regular specs, add a file with a sign in helper to spec/support (or rely on the rails request one + ensure your rspec config is loading it):

module Sorcery::TestHelpers::Rails
  # Adapted from Sorcery::TestHelpers::Rails::Request
  def sign_in(user, password = 'MyDefaultFactoryPassword!', route = nil, http_method = :post)
    route ||= login_url

    username_attr = user.sorcery_config.username_attribute_names.first
    password_attr = user.sorcery_config.password_attribute_name

    send(
      http_method,
      route,
      params: {
        username_attr => user.send(username_attr),
        password_attr => password
      }
    )
  end
end

But since you're testing actual login here, you would write something like:

describe '/user_sessions' do
  subject { response }
  
  let(:user) { create :user, :active, password: }
  let(:password) { 'P@ssw0rd!' }

  describe 'POST /login' do
    let(:request) { post login_path(params: { email: user.email, password: }) }

    it 'succeeds' do
      request
      should have_http_status(:success)
    end

    it 'logs in user' do
      expect { request }.to change { user.reload.logged_in? }.from(false).to(true)
    end
  end
end