require "test_helper" class AuthenticationFlowTest < ActionDispatch::IntegrationTest setup do @user = users(:admin_user) @user.update!(invitation_accepted_at: Time.current) Rails.cache.clear end test "user can sign in with valid credentials" do get login_path assert_response :success post login_path, params: { email: @user.email, password: "password123456" } assert_redirected_to admin_root_path follow_redirect! assert_response :success assert_equal @user.id, session[:user_id] end test "user cannot sign in with invalid password" do post login_path, params: { email: @user.email, password: "wrongpassword" } assert_response :unprocessable_entity assert_nil session[:user_id] assert_select "div[role='alert']", text: /invalid email or password/i end test "user cannot sign in with non-existent email" do post login_path, params: { email: "nonexistent@example.com", password: "password123456" } assert_response :unprocessable_entity assert_nil session[:user_id] end test "pending user cannot sign in" do pending_user = users(:pending_invitation) post login_path, params: { email: pending_user.email, password: "password123456" } assert_response :unprocessable_entity assert_nil session[:user_id] assert_select "div[role='alert']", text: /pending/i end test "user can sign out" do # Sign in first post login_path, params: { email: @user.email, password: "password123456" } assert_equal @user.id, session[:user_id] # Sign out delete logout_path assert_redirected_to root_path assert_nil session[:user_id] end test "session persists across requests" do post login_path, params: { email: @user.email, password: "password123456" } assert_equal @user.id, session[:user_id] get root_path assert_equal @user.id, session[:user_id] get entries_path assert_equal @user.id, session[:user_id] end test "remember me creates cookie" do post login_path, params: { email: @user.email, password: "password123456", remember_me: "1" } assert_not_nil cookies[:remember_token] @user.reload assert_not_nil @user.remember_token assert_not_nil @user.remember_created_at end test "remember me cookie logs user in automatically" do post login_path, params: { email: @user.email, password: "password123456", remember_me: "1" } remember_cookie = cookies[:remember_token] assert_not_nil remember_cookie # Clear session reset! # Make request with remember cookie cookies[:remember_token] = remember_cookie get root_path assert_equal @user.id, session[:user_id] end test "logout clears remember me cookie" do # Sign in with remember me post login_path, params: { email: @user.email, password: "password123456", remember_me: "1" } assert_not_nil cookies[:remember_token] # Sign out delete logout_path remember_cookie = cookies[:remember_token] assert remember_cookie.nil? || remember_cookie.empty? @user.reload assert_nil @user.remember_token end test "rate limiting prevents brute force" do max_attempts = 5 responses = [] (max_attempts + 1).times do post login_path, params: { email: @user.email, password: "wrongpassword" } responses << response.status end assert responses.first(max_attempts).all? { |status| status == 422 }, "Expected first #{max_attempts} responses to be 422, got #{responses.first(max_attempts)}" if Rails.cache.is_a?(ActiveSupport::Cache::NullStore) assert responses.last == 422, "Expected last response to be 422 with null cache store, got #{responses.last}" else assert responses.last == 429, "Expected last response to be 429, got #{responses.last}" end assert_select "div[role='alert']", text: /too many/i end test "successful login resets rate limit" do # Fail a few times 3.times do post login_path, params: { email: @user.email, password: "wrongpassword" } end # Then succeed post login_path, params: { email: @user.email, password: "password123456" } assert_redirected_to admin_root_path # Should be able to login again immediately delete logout_path post login_path, params: { email: @user.email, password: "password123456" } assert_redirected_to admin_root_path end test "session timeout logs user out after inactivity" do # Sign in post login_path, params: { email: @user.email, password: "password123456" } assert_equal @user.id, session[:user_id] # Simulate time passing travel 4.days do get root_path assert_redirected_to login_path assert_match /expired/i, flash[:alert] assert_nil session[:user_id] end end test "remember me prevents session timeout" do # Sign in with remember me post login_path, params: { email: @user.email, password: "password123456", remember_me: "1" } remember_cookie = cookies[:remember_token] # Simulate time passing (but within remember me period) travel 5.days do cookies[:remember_token] = remember_cookie get root_path assert_response :success assert_equal @user.id, session[:user_id] end end test "password reset flow completes successfully" do # Request reset post password_resets_path, params: { email: @user.email } assert_redirected_to login_path @user.reload assert_not_nil @user.reset_password_token # Visit reset form get edit_password_reset_path(@user.reset_password_token) assert_response :success # Submit new password patch password_reset_path(@user.reset_password_token), params: { password: "newpassword12345", password_confirmation: "newpassword12345" } assert_redirected_to root_path assert_equal @user.id, session[:user_id] @user.reload assert @user.authenticate("newpassword12345") assert_nil @user.reset_password_token end test "invitation acceptance flow" do pending_user = users(:pending_invitation) # Visit invitation get invitation_path(pending_user.invitation_token) assert_response :success # Accept invitation patch accept_invitation_path(pending_user.invitation_token), params: { user: { password: "newpassword12345", password_confirmation: "newpassword12345" } } assert_redirected_to root_path assert_equal pending_user.id, session[:user_id] pending_user.reload assert_not_nil pending_user.invitation_accepted_at assert pending_user.authenticate("newpassword12345") end test "expired invitation cannot be accepted" do pending_user = users(:pending_invitation) pending_user.update!(invitation_sent_at: 15.days.ago) get invitation_path(pending_user.invitation_token) assert_redirected_to root_path assert_match /expired/i, flash[:alert] end test "admin user redirects to admin dashboard after login" do post login_path, params: { email: @user.email, password: "password123456" } assert_redirected_to admin_root_path end test "contributor redirects to root after login" do contributor = users(:contributor_user) contributor.update!(invitation_accepted_at: Time.current) post login_path, params: { email: contributor.email, password: "password123456" } assert_redirected_to root_path end test "already logged in user redirects from login page" do # Sign in first post login_path, params: { email: @user.email, password: "password123456" } # Try to visit login page again get login_path assert_redirected_to admin_root_path end end