Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/generators/rspec/authentication/authentication_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Rspec
module Generators
# @private
class AuthenticationGenerator < Base
class_option :request_specs, type: :boolean, default: true, desc: 'Generate request specs'

def initialize(args, *options)
args.replace(['User'])
super
Expand All @@ -20,6 +22,18 @@ def create_fixture_file

template 'users.yml', target_path('fixtures', 'users.yml')
end

def create_session_request_spec
return unless options[:request_specs]

template 'session_spec.rb', target_path('requests', 'sessions_spec.rb')
end

def create_password_request_spec
return unless options[:request_specs]

template 'password_spec.rb', target_path('requests', 'passwords_spec.rb')
end
end
end
end
78 changes: 78 additions & 0 deletions lib/generators/rspec/authentication/templates/password_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require 'rails_helper'

RSpec.describe "Passwords", <%= type_metatag(:request) %> do
fixtures :users

let(:user) { users(:one) }
Copy link
Member

Choose a reason for hiding this comment

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

See below


describe "GET /password/new" do
it "returns http success" do
get new_password_path
expect(response).to have_http_status(:success)
end
end

describe "POST /password" do
it "sends password reset email for valid user" do
expect {
post passwords_path, params: { email_address: user.email_address }
}.to have_enqueued_mail(PasswordsMailer, :reset).with(user)

expect(response).to redirect_to(new_session_path)

follow_redirect!
expect(flash[:notice]).to eq("Password reset instructions sent (if user with that email address exists).")
end

it "handles invalid email gracefully" do
expect {
post passwords_path, params: { email_address: "[email protected]" }
}.not_to have_enqueued_mail

expect(response).to redirect_to(new_session_path)

follow_redirect!
expect(flash[:notice]).to eq("Password reset instructions sent (if user with that email address exists).")
end
end

describe "GET /password/edit" do
it "returns http success with valid token" do
get edit_password_path(user.password_reset_token)
expect(response).to have_http_status(:success)
end

it "redirects with invalid password reset token" do
get edit_password_path("invalid token")
expect(response).to redirect_to(new_password_path)

follow_redirect!
expect(flash[:alert]).to eq("Password reset link is invalid or has expired.")
end
end

describe "PATCH /password" do
it "updates password with valid token and password" do
expect {
patch password_path(user.password_reset_token), params: { password: "new", password_confirmation: "new" }
}.to change { user.reload.password_digest }

expect(response).to redirect_to(new_session_path)

follow_redirect!
expect(flash[:notice]).to eq("Password has been reset.")
end

it "rejects non matching passwords" do
token = user.password_reset_token
expect {
patch password_path(token), params: { password: "no", password_confirmation: "match" }
}.not_to change { user.reload.password_digest }

expect(response).to redirect_to(edit_password_path(token))

follow_redirect!
expect(flash[:alert]).to eq("Passwords did not match.")
end
end
end
57 changes: 57 additions & 0 deletions lib/generators/rspec/authentication/templates/session_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'rails_helper'

RSpec.describe "Sessions", <%= type_metatag(:request) %> do
fixtures :users

let(:user) { users(:one) }
Copy link
Member

Choose a reason for hiding this comment

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

Although we do generate fixtures when thats enabled, we don't use them anywhere else, I feel like this should be gated on fixtures being turned on but I'm not sure what the alternative should be?

Copy link
Author

Choose a reason for hiding this comment

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

We could remove fixture usage and use direct model creation like the scaffold generator lib/generators/rspec/scaffold/templates/request_spec.rb:

# TODO: Replace with your factory or model creation method
# For example, with FactoryBot: let(:user) { create(:user) }
# or with fixtures: let(:user) { users(:one) }
let(:user) { User.create!(email_address: "[email protected]", password: "password") }

This option is completely agnostic to fixtures vs factory replacements


describe "GET /new_session" do
it "returns http success" do
get new_session_path
expect(response).to have_http_status(:success)
end
end

describe "POST /session" do
context "with valid credentials" do
it "redirects to root path and sets session cookie" do
post session_path, params: { email_address: user.email_address, password: "password" }

expect(response).to redirect_to(root_path)
expect(cookies[:session_id]).to be_present
end
end

context "with invalid credentials" do
it "redirects to new session path and does not set session cookie" do
post session_path, params: { email_address: user.email_address, password: "wrong" }

expect(response).to redirect_to(new_session_path)
expect(cookies[:session_id]).to be_nil
end
end
end

describe "DELETE /session" do
it "logs out the current user and redirects to new session path" do
# Simulate being signed in
sign_in_as(user)

delete session_path

expect(response).to redirect_to(new_session_path)
expect(cookies[:session_id]).to be_empty
end
end

private

def sign_in_as(user)
Copy link
Member

Choose a reason for hiding this comment

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

Given Rails's own preference for helpers, I feel this should be generated in a module thats included or configured for automatic inclusion.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, you are right, it is cleaner and it is closer to the Rails way. What do you think about adding this two methods to the authentication_generator:

create_authentication_support:
Creates the spec/support/authentication_support.rb file with the sign_in_as(user) helper

configure_authentication_support:
Automatically modifies spec/rails_helper.rb to include the helper in all request specs

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
Copy link
Member

Choose a reason for hiding this comment

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

I'd also prefer if this did a simple post rather than reaching into internals, seems like it could be brittle?

end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
RSpec.describe Rspec::Generators::AuthenticationGenerator, type: :generator do
setup_default_destination

it 'runs both the model and fixture tasks' do
it 'runs the model, fixture, and request spec tasks' do
gen = generator
expect(gen).to receive :create_user_spec
expect(gen).to receive :create_fixture_file
expect(gen).to receive :create_session_request_spec
expect(gen).to receive :create_password_request_spec
gen.invoke_all
end

Expand All @@ -19,6 +21,26 @@
expect(File.exist?(file('spec/models/user_spec.rb'))).to be true
end

it 'creates the request specs' do
run_generator

expect(File.exist?(file('spec/requests/sessions_spec.rb'))).to be true
expect(File.exist?(file('spec/requests/passwords_spec.rb'))).to be true
end

describe 'with request specs disabled' do
before do
run_generator ['--request-specs=false']
end

describe 'the request specs' do
it "will skip the files" do
expect(File.exist?(file('spec/requests/sessions_spec.rb'))).to be false
expect(File.exist?(file('spec/requests/passwords_spec.rb'))).to be false
end
end
end

describe 'with fixture replacement' do
before do
run_generator ['--fixture-replacement=factory_bot']
Expand Down