Skip to content

Add login_as(user) testing helper when generating authentication - #53708

Merged
dhh merged 4 commits into
rails:mainfrom
gobijan:auth_testing
Feb 27, 2025
Merged

dhh merged 4 commits into
rails:mainfrom
gobijan:auth_testing

Conversation

@gobijan

@gobijan gobijan commented Nov 22, 2024 •

Copy link
Copy Markdown
Contributor

Motivation / Background

This Pull Request has been created because the generated authentication logic does not offer a way to test when a user is authenticated. There is a discussion going in here I initialized: #53207
So this PR adds a SessionTestHelper module with sign_in_as(user) and sign_out test helpers when
running rails g authentication. This simplifies authentication in integration tests.

Detail

This Pull Request changes the generator for authentication to add described test helpers.

Additional information

There were a few discussions how to do it best and for now I settle with the approach from the PR.

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@rails-bot rails-bot Bot added the railties label Nov 22, 2024
@stevepolitodesign

stevepolitodesign commented Dec 4, 2024 •

Copy link
Copy Markdown
Contributor

Would there be any value in adding a complimentary logout helper too?

Comment on lines +58 to +64
def login_as(user)
session = user.sessions.create!
Current.session = session
request = ActionDispatch::Request.new(Rails.application.env_config)
cookies = request.cookie_jar
cookies.signed[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had success with logout locally.

Suggested change
def login_as(user)
session = user.sessions.create!
Current.session = session
request = ActionDispatch::Request.new(Rails.application.env_config)
cookies = request.cookie_jar
cookies.signed[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
def login_as(user)
session = user.sessions.create!
Current.session = session
request = ActionDispatch::Request.new(Rails.application.env_config)
cookies = request.cookie_jar
cookies.signed[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
def logout
Current.session.destroy
request = ActionDispatch::Request.new(Rails.application.env_config)
cookies = request.cookie_jar
cookies.delete(:session_id)
end

@rafaelfranca
rafaelfranca requested a review from dhh December 9, 2024 20:43
@dhh

dhh commented Dec 9, 2024

Copy link
Copy Markdown
Member

Yes, this is good. We have a helper like this in our projects as well, which is very similar. But let's name these "sign_in_as" and "sign_out". And then let's instantiate the cookie jar directly. Here's our method:

def sign_in_as(user)
  Current.session = user.sessions.create!

  ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar|
    cookie_jar.signed[:session_id] = Current.session.id
    cookies[:session_id] = cookie_jar[:session_id]
  end
end

@dhh

dhh commented Dec 9, 2024

Copy link
Copy Markdown
Member

Also, I would actually recommend that we add this in a dedicated file called SessionTestHelper and then include that via:

class ActionDispatch::IntegrationTest
  include SessionTestHelper
end

Listed below the ActiveSupport::TestCase recommendation.

@MatheusRich

Copy link
Copy Markdown
Contributor

@gobijan do you still plan to work on this? I can take over this task and co-author you

@gobijan

gobijan commented Jan 17, 2025

Copy link
Copy Markdown
Contributor Author

I had serious problems getting the test suite to run tbh. Locally didn't work. The devcontainer environment was bugged and I had to rely on Github PR which was kind of unsatisfactory.
Would you mind pointing me in the direction why my tests are failing? I'd love to collaborate on this and need help with the rails test suite.

@MatheusRich

Copy link
Copy Markdown
Contributor

@gobijan It should be pretty straightforward:

cd railties
bundle
bin/test

If that doesn't work, please ask for help in the Discord server.

@gobijan

gobijan commented Jan 18, 2025 •

Copy link
Copy Markdown
Contributor Author

I just refactored the PR according to @dhh

The change:

  • Extract test helpers from test_helper.rb into SessionTestHelper module
  • Rename login_as to sign_in_as for consistent terminology
  • Add sign_out helper method for testing user logout flows
  • Update authentication generator and tests accordingly

Another note:
I used require_relative "helpers/session_test_helper". I was wondering if it would be better to add config.autoload_paths += %w[ test/helpers ] to test.rb
Any opinion on that?

@flavorjones flavorjones left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some comments that should get you to green in CI

Comment thread railties/test/generators/authentication_generator_test.rb Outdated
Comment thread railties/test/generators/authentication_generator_test.rb
@jeromedalbert

Copy link
Copy Markdown
Contributor

Another note:
I used require_relative "helpers/session_test_helper". I was wondering if it would be better to add config.autoload_paths += %w[ test/helpers ] to test.rb
Any opinion on that?

I think this would be nice as it would cut down on boilerplate requires whenever a test needs to sign in or sign out. In this PR I also used autoload_paths, which I believe was inspired by code from 37signals' Writebook. Feel free to take anything from that PR if needed.

@flavorjones

Copy link
Copy Markdown
Member

I used require_relative "helpers/session_test_helper". I was wondering if it would be better to add config.autoload_paths += %w[ test/helpers ] to test.rb
Any opinion on that?

I think this would be nice as it would cut down on boilerplate requires

I agree it would be nice, but I also don't think it needs to be part of this PR.

The failure in CI is unrelated, I'm tagging this for review by a core team member.

@flavorjones flavorjones added the ready PRs ready to merge label Jan 19, 2025
@zzak

zzak commented Jan 19, 2025

Copy link
Copy Markdown
Member

Please add a changelog and squash your commits.


ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar|
cookie_jar.signed[:session_id] = Current.session.id
cookies[:session_id] = cookie_jar[:session_id]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be added o system tests or request tests? I don't remember if system tests can access cookies

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for IntegrationTests (ActionDispatch::IntegrationTest).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have one for system tests too? I know @dhh is not a big fan of them, but for smoke tests, a set of system tests are useful. Should we let that for each app to implement? Maybe a initial implementation on the docs could be an alternative, if we don't want to ship it in the framework.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well good question. I think system tests are probably way simpler. These helpers here were not trivial because of the cookie jar problem mentioned in the referenced discussion.

For system tests it's pretty straightforward I guess. Something like:

visit new_session_url
fill_in "Email", with: "test@example.com"
fill_in "Password", with: "password123"
click_on "Sign in"

Where should logic for this go? Directly in the ApplicationSystemTestCase class?
Anyways I needed the IntegrationTest functionality as I was repeating it in all my projects. I let you guys decide how to move forward.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the system specs should actually login, or set cookies more "manually" too.

I'll defer to @dhh to say if this is necessary or not.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For system tests, I needed to add in the following to get that solution to work:

page.instance_variable_set(:@touched, false)
page.reset!

However, this is pretty hacky, and I'm pretty sure there's a better solution for system tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had something created for system specs before this PR:

    def sign_in(user = users(:john))
      session = user.sessions.create!
      Current.session = session
      request = ActionDispatch::Request.new(Rails.application.env_config)
      cookies = request.cookie_jar
      cookies.signed[:session_id] = { value: session.id, httponly: true, same_site: :lax }
    end

Hope that helps

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an update, the solution in #53207 (comment) works better for me than the page resetting! I think there was a significant amount of flake introduced by using stock capybara fill_in to initiate the session

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, @MatheusRich, your comment didn't seem to load for me in time, but I think we came to the same conclusion 😄

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is for ActionDispatch::IntegrationTest why is is being added to ActiveSupport::TestCase

…t` test helpers when

running `rails g authentication`. Simplifies authentication in integration tests.
@dhh
dhh merged commit 8009769 into rails:main Feb 27, 2025
@dhh

dhh commented Feb 27, 2025

Copy link
Copy Markdown
Member

Nice work, all! This is a good upgrade 🎉

dkani pushed a commit to dkani/rails that referenced this pull request Mar 3, 2025
…ls#53708)

* Add a `SessionTestHelper` module with `sign_in_as(user)` and `sign_out` test helpers when
running `rails g authentication`. Simplifies authentication in integration tests.

* Style

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
@dwightwatson

Copy link
Copy Markdown
Contributor

Should the template go into the test/test_helpers directory, not the test/helpers directory (which is for testing application helpers).

test_helpers is referenced here: https://guides.rubyonrails.org/testing.html#using-separate-files

@dhh

dhh commented Mar 4, 2025

Copy link
Copy Markdown
Member

Wups, good catch @dwightwatson. We do need to use test/test_helpers. If you or anyone wants to fix that, cc me on the PR ✌️

@gobijan

gobijan commented Mar 4, 2025

Copy link
Copy Markdown
Contributor Author

Wups, good catch @dwightwatson. We do need to use test/test_helpers. If you or anyone wants to fix that, cc me on the PR ✌️

Of course I can step in and fix it. Will look into it tomorrow :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

railties ready PRs ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants