From 7c7b2a54ba040f27e269ba85c488d0f3355dda88 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Fri, 15 Aug 2025 14:04:10 -0300 Subject: [PATCH 01/10] chore: Add request_specs option to authentication generator --- lib/generators/rspec/authentication/authentication_generator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/generators/rspec/authentication/authentication_generator.rb b/lib/generators/rspec/authentication/authentication_generator.rb index f653c5c4e..31dc4e004 100644 --- a/lib/generators/rspec/authentication/authentication_generator.rb +++ b/lib/generators/rspec/authentication/authentication_generator.rb @@ -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 From c2191c95492ab8f456cdad1631d4f9ac188f3b6a Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Fri, 15 Aug 2025 14:28:09 -0300 Subject: [PATCH 02/10] chore: Add session request spec template and generator method - Add create_session_request_spec method to generate sessions_spec.rb - Include basic test structure for login/logout endpoints - Respects request_specs option for conditional generation" --- .../authentication_generator.rb | 6 +++ .../authentication/templates/session_spec.rb | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/generators/rspec/authentication/templates/session_spec.rb diff --git a/lib/generators/rspec/authentication/authentication_generator.rb b/lib/generators/rspec/authentication/authentication_generator.rb index 31dc4e004..a6716c278 100644 --- a/lib/generators/rspec/authentication/authentication_generator.rb +++ b/lib/generators/rspec/authentication/authentication_generator.rb @@ -22,6 +22,12 @@ 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 end end end diff --git a/lib/generators/rspec/authentication/templates/session_spec.rb b/lib/generators/rspec/authentication/templates/session_spec.rb new file mode 100644 index 000000000..b7c6e4b08 --- /dev/null +++ b/lib/generators/rspec/authentication/templates/session_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +RSpec.describe "Sessions", <%= type_metatag(:request) %> do + let(:user) { users(:one) } + + 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) + # Helper method to simulate user sign in + # This would typically set the session or cookie to simulate authentication + cookies[:session_id] = "valid_session_id" + end +end From 41ffc245424ed47e44330c95e32ef3f479237349 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Fri, 15 Aug 2025 14:57:26 -0300 Subject: [PATCH 03/10] chore: Add password request spec template and generator method - Add create_password_request_spec method to generate passwords_spec.rb - Include basic test structure for password reset endpoints - Respects request_specs option for conditional generation" --- .../authentication_generator.rb | 6 ++ .../authentication/templates/password_spec.rb | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 lib/generators/rspec/authentication/templates/password_spec.rb diff --git a/lib/generators/rspec/authentication/authentication_generator.rb b/lib/generators/rspec/authentication/authentication_generator.rb index a6716c278..e18068829 100644 --- a/lib/generators/rspec/authentication/authentication_generator.rb +++ b/lib/generators/rspec/authentication/authentication_generator.rb @@ -28,6 +28,12 @@ def create_session_request_spec 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 diff --git a/lib/generators/rspec/authentication/templates/password_spec.rb b/lib/generators/rspec/authentication/templates/password_spec.rb new file mode 100644 index 000000000..d291b1dbb --- /dev/null +++ b/lib/generators/rspec/authentication/templates/password_spec.rb @@ -0,0 +1,76 @@ +require 'rails_helper' + +RSpec.describe "Passwords", <%= type_metatag(:request) %> do + let(:user) { users(:one) } + + describe "GET /password/new" do + it "returns http success" do + get "/password/new" + 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("reset instructions sent") + end + + it "handles invalid email gracefully" do + expect { + post passwords_path, params: { email_address: "missing-user@example.com" } + }.not_to have_enqueued_mail + + expect(response).to redirect_to(new_session_path) + + follow_redirect! + expect(flash[:notice]).to eq("reset instructions sent") + 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[:notice]).to eq("reset link is invalid") + 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[:notice]).to eq("Passwords did not match") + end + end +end From 1139850a86e677c35cea7703443bceb9b610ef1e Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Fri, 15 Aug 2025 15:10:34 -0300 Subject: [PATCH 04/10] test: add request specs tests for authentication generator --- .../authentication_generator_spec.rb | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/spec/generators/rspec/authentication/authentication_generator_spec.rb b/spec/generators/rspec/authentication/authentication_generator_spec.rb index e5590aedd..625ea4ff6 100644 --- a/spec/generators/rspec/authentication/authentication_generator_spec.rb +++ b/spec/generators/rspec/authentication/authentication_generator_spec.rb @@ -12,6 +12,13 @@ gen.invoke_all end + it 'runs the request spec tasks' do + gen = generator + expect(gen).to receive :create_session_request_spec + expect(gen).to receive :create_password_request_spec + gen.invoke_all + end + describe 'the generated files' do it 'creates the user spec' do run_generator @@ -19,6 +26,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'] From 5f7714c9c1e42c8a78a5528ec1b247778a62b54d Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Mon, 18 Aug 2025 09:52:04 -0300 Subject: [PATCH 05/10] fix: Some small changes in new requests spec. After creating a dummy app with only rspec and authentication, I added both test, and they pass without any additional configuration --- .../authentication/templates/password_spec.rb | 14 ++++++++------ .../rspec/authentication/templates/session_spec.rb | 9 +++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/generators/rspec/authentication/templates/password_spec.rb b/lib/generators/rspec/authentication/templates/password_spec.rb index d291b1dbb..e4e54291e 100644 --- a/lib/generators/rspec/authentication/templates/password_spec.rb +++ b/lib/generators/rspec/authentication/templates/password_spec.rb @@ -1,11 +1,13 @@ require 'rails_helper' RSpec.describe "Passwords", <%= type_metatag(:request) %> do + fixtures :users + let(:user) { users(:one) } describe "GET /password/new" do it "returns http success" do - get "/password/new" + get new_password_path expect(response).to have_http_status(:success) end end @@ -19,7 +21,7 @@ expect(response).to redirect_to(new_session_path) follow_redirect! - expect(flash[:notice]).to eq("reset instructions sent") + expect(flash[:notice]).to eq("Password reset instructions sent (if user with that email address exists).") end it "handles invalid email gracefully" do @@ -30,7 +32,7 @@ expect(response).to redirect_to(new_session_path) follow_redirect! - expect(flash[:notice]).to eq("reset instructions sent") + expect(flash[:notice]).to eq("Password reset instructions sent (if user with that email address exists).") end end @@ -45,7 +47,7 @@ expect(response).to redirect_to(new_password_path) follow_redirect! - expect(flash[:notice]).to eq("reset link is invalid") + expect(flash[:alert]).to eq("Password reset link is invalid or has expired.") end end @@ -58,7 +60,7 @@ expect(response).to redirect_to(new_session_path) follow_redirect! - expect(flash[:notice]).to eq("Password has been reset") + expect(flash[:notice]).to eq("Password has been reset.") end it "rejects non matching passwords" do @@ -70,7 +72,7 @@ expect(response).to redirect_to(edit_password_path(token)) follow_redirect! - expect(flash[:notice]).to eq("Passwords did not match") + expect(flash[:alert]).to eq("Passwords did not match.") end end end diff --git a/lib/generators/rspec/authentication/templates/session_spec.rb b/lib/generators/rspec/authentication/templates/session_spec.rb index b7c6e4b08..3c01ab146 100644 --- a/lib/generators/rspec/authentication/templates/session_spec.rb +++ b/lib/generators/rspec/authentication/templates/session_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' RSpec.describe "Sessions", <%= type_metatag(:request) %> do + fixtures :users + let(:user) { users(:one) } describe "GET /new_session" do @@ -46,7 +48,10 @@ def sign_in_as(user) # Helper method to simulate user sign in - # This would typically set the session or cookie to simulate authentication - cookies[:session_id] = "valid_session_id" + # Create a real session and set the cookie using Rails cookie signing + session = user.sessions.create!(user_agent: "test", ip_address: "127.0.0.1") + # Manually sign the cookie value using Rails' message verifier + signed_value = Rails.application.message_verifier('signed cookie').generate(session.id) + cookies[:session_id] = signed_value end end From b30775d67d310f99cc684f958ebb6b486c13f873 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Mon, 18 Aug 2025 10:47:55 -0300 Subject: [PATCH 06/10] chore: Unify unit tests from authentication_generator_spec.rb --- .../rspec/authentication/authentication_generator_spec.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/generators/rspec/authentication/authentication_generator_spec.rb b/spec/generators/rspec/authentication/authentication_generator_spec.rb index 625ea4ff6..5cc0966d9 100644 --- a/spec/generators/rspec/authentication/authentication_generator_spec.rb +++ b/spec/generators/rspec/authentication/authentication_generator_spec.rb @@ -5,15 +5,10 @@ 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 - gen.invoke_all - end - - it 'runs the request spec tasks' do - gen = generator expect(gen).to receive :create_session_request_spec expect(gen).to receive :create_password_request_spec gen.invoke_all From ebe6bc5e7dfb5b2f831cef2112c27d57693b00e6 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Mon, 18 Aug 2025 12:03:18 -0300 Subject: [PATCH 07/10] chore: Change the sign_in_as method to the rails way --- .../rspec/authentication/templates/session_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/generators/rspec/authentication/templates/session_spec.rb b/lib/generators/rspec/authentication/templates/session_spec.rb index 3c01ab146..0a1ecf286 100644 --- a/lib/generators/rspec/authentication/templates/session_spec.rb +++ b/lib/generators/rspec/authentication/templates/session_spec.rb @@ -47,11 +47,11 @@ private def sign_in_as(user) - # Helper method to simulate user sign in - # Create a real session and set the cookie using Rails cookie signing - session = user.sessions.create!(user_agent: "test", ip_address: "127.0.0.1") - # Manually sign the cookie value using Rails' message verifier - signed_value = Rails.application.message_verifier('signed cookie').generate(session.id) - cookies[:session_id] = signed_value + 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 end From f26e3acc23c6c0660437182a41a6ac6a882f4132 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Tue, 19 Aug 2025 14:37:55 -0300 Subject: [PATCH 08/10] feat: add authentication support module for request specs - Create AuthenticationSupport module with sign_in_as helper method - Extract sign_in_as from session_spec to shared support module - Auto-configure rails_helper to include AuthenticationSupport for request specs - Add generator methods to create and configure authentication support - Update generator specs to test new authentication support functionality --- .../authentication_generator.rb | 17 +++++++++++++ .../templates/authentication_support.rb | 10 ++++++++ .../authentication/templates/session_spec.rb | 11 --------- .../authentication_generator_spec.rb | 24 +++++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 lib/generators/rspec/authentication/templates/authentication_support.rb diff --git a/lib/generators/rspec/authentication/authentication_generator.rb b/lib/generators/rspec/authentication/authentication_generator.rb index e18068829..9fda14859 100644 --- a/lib/generators/rspec/authentication/authentication_generator.rb +++ b/lib/generators/rspec/authentication/authentication_generator.rb @@ -34,6 +34,23 @@ def create_password_request_spec template 'password_spec.rb', target_path('requests', 'passwords_spec.rb') end + + def create_authentication_support + template 'authentication_support.rb', target_path('support', 'authentication_support.rb') + end + + def configure_authentication_support + rails_helper_path = File.join(destination_root, 'spec', 'rails_helper.rb') + return unless File.exist?(rails_helper_path) + + # Uncomment the support files loading line if it's commented out + uncomment_lines rails_helper_path, /Rails\.root\.glob\('spec\/support\/\*\*\/\*\.rb'\)\.sort_by\(&:to_s\)\.each \{ \|f\| require f \}/ + + include_statement = " # Include authentication support module for request specs\n config.include AuthenticationSupport, type: :request\n" + + # Insert the include statement before the final 'end' of the RSpec.configure block + inject_into_file rails_helper_path, include_statement, before: /^end\s*$/ + end end end end diff --git a/lib/generators/rspec/authentication/templates/authentication_support.rb b/lib/generators/rspec/authentication/templates/authentication_support.rb new file mode 100644 index 000000000..2812cb6e1 --- /dev/null +++ b/lib/generators/rspec/authentication/templates/authentication_support.rb @@ -0,0 +1,10 @@ +module AuthenticationSupport + # Helper method to sign in a user for testing purposes + # Uses the actual authentication flow via POST request + def sign_in_as(user) + post session_path, params: { + email_address: user.email_address, + password: "password" + } + end +end diff --git a/lib/generators/rspec/authentication/templates/session_spec.rb b/lib/generators/rspec/authentication/templates/session_spec.rb index 0a1ecf286..01f032822 100644 --- a/lib/generators/rspec/authentication/templates/session_spec.rb +++ b/lib/generators/rspec/authentication/templates/session_spec.rb @@ -43,15 +43,4 @@ expect(cookies[:session_id]).to be_empty end end - - private - - 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 end diff --git a/spec/generators/rspec/authentication/authentication_generator_spec.rb b/spec/generators/rspec/authentication/authentication_generator_spec.rb index 5cc0966d9..37fb804c1 100644 --- a/spec/generators/rspec/authentication/authentication_generator_spec.rb +++ b/spec/generators/rspec/authentication/authentication_generator_spec.rb @@ -11,6 +11,8 @@ expect(gen).to receive :create_fixture_file expect(gen).to receive :create_session_request_spec expect(gen).to receive :create_password_request_spec + expect(gen).to receive :create_authentication_support + expect(gen).to receive :configure_authentication_support gen.invoke_all end @@ -28,6 +30,28 @@ expect(File.exist?(file('spec/requests/passwords_spec.rb'))).to be true end + it 'configures the authentication support' do + # Create a minimal rails_helper.rb file that the generator can modify + FileUtils.mkdir_p(File.join(destination_root, 'spec')) + rails_helper_content = <<~CONTENT + # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } + RSpec.configure do |config| + end + CONTENT + File.write(File.join(destination_root, 'spec', 'rails_helper.rb'), rails_helper_content) + + run_generator + + expect(file('spec/rails_helper.rb')).to contain( + " # Include authentication support module for request specs\n config.include AuthenticationSupport, type: :request\n" + ) + expect(file('spec/rails_helper.rb')).not_to contain( + " # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }\n" + ) + + expect(File.exist?(file('spec/support/authentication_support.rb'))).to be true + end + describe 'with request specs disabled' do before do run_generator ['--request-specs=false'] From 045a4a2d6b9c39e385613571b532f3e75b4194a2 Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Tue, 19 Aug 2025 14:51:54 -0300 Subject: [PATCH 09/10] chore: Remove fixture dependency from authentication generator specs Replace fixture usage with direct model creation to ensure compatibility with both fixtures and fixture replacements --- .../rspec/authentication/templates/password_spec.rb | 7 ++++--- .../rspec/authentication/templates/session_spec.rb | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/generators/rspec/authentication/templates/password_spec.rb b/lib/generators/rspec/authentication/templates/password_spec.rb index e4e54291e..70f354afa 100644 --- a/lib/generators/rspec/authentication/templates/password_spec.rb +++ b/lib/generators/rspec/authentication/templates/password_spec.rb @@ -1,9 +1,10 @@ require 'rails_helper' RSpec.describe "Passwords", <%= type_metatag(:request) %> do - fixtures :users - - let(:user) { users(:one) } + # 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: "test@example.com", password: "password") } describe "GET /password/new" do it "returns http success" do diff --git a/lib/generators/rspec/authentication/templates/session_spec.rb b/lib/generators/rspec/authentication/templates/session_spec.rb index 01f032822..46076915f 100644 --- a/lib/generators/rspec/authentication/templates/session_spec.rb +++ b/lib/generators/rspec/authentication/templates/session_spec.rb @@ -1,9 +1,10 @@ require 'rails_helper' RSpec.describe "Sessions", <%= type_metatag(:request) %> do - fixtures :users - - let(:user) { users(:one) } + # 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: "test@example.com", password: "password") } describe "GET /new_session" do it "returns http success" do From 8eea380c0d507fbbdba2d0806d2744a96fdbd52c Mon Sep 17 00:00:00 2001 From: DiegoPisa-Cedar Date: Wed, 20 Aug 2025 13:54:59 -0300 Subject: [PATCH 10/10] test: assert uncommented support glob line in authentication generator Change test from checking commented line removal to verifying uncommented support glob line is present in rails_helper.rb --- .../rspec/authentication/authentication_generator_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/generators/rspec/authentication/authentication_generator_spec.rb b/spec/generators/rspec/authentication/authentication_generator_spec.rb index 37fb804c1..671c60268 100644 --- a/spec/generators/rspec/authentication/authentication_generator_spec.rb +++ b/spec/generators/rspec/authentication/authentication_generator_spec.rb @@ -45,8 +45,8 @@ expect(file('spec/rails_helper.rb')).to contain( " # Include authentication support module for request specs\n config.include AuthenticationSupport, type: :request\n" ) - expect(file('spec/rails_helper.rb')).not_to contain( - " # Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }\n" + expect(file('spec/rails_helper.rb')).to contain( + /^#{Regexp.escape("Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }")}$/ ) expect(File.exist?(file('spec/support/authentication_support.rb'))).to be true