require "test_helper" class InvitationsControllerTest < ActionDispatch::IntegrationTest test "should show invitation acceptance page with valid token" do user = users(:pending_invitation) get invitation_path(user.invitation_token) assert_response :success assert_select "h1", "Accept Invitation" assert_select "input[type=password]", count: 2 end test "should redirect with invalid token" do get invitation_path("invalid_token") assert_redirected_to root_path assert_equal "Invalid or expired invitation link.", flash[:alert] end test "should redirect with expired token" do user = users(:pending_invitation) user.update(invitation_sent_at: 15.days.ago) get invitation_path(user.invitation_token) assert_redirected_to root_path assert_equal "Invalid or expired invitation link.", flash[:alert] end test "should accept invitation with valid password" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } user.reload assert_not_nil user.invitation_accepted_at assert_nil user.invitation_token assert_equal user.id, session[:user_id] assert_redirected_to root_path end test "should not accept invitation with short password" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "short", password_confirmation: "short" } } user.reload assert_nil user.invitation_accepted_at assert_not_nil user.invitation_token assert_response :unprocessable_entity end test "should not accept invitation with mismatched passwords" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "differentpassword" } } user.reload assert_nil user.invitation_accepted_at assert_response :unprocessable_entity end test "should not accept expired invitation" do user = users(:pending_invitation) user.update(invitation_sent_at: 15.days.ago) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } assert_redirected_to root_path assert_equal "Invalid or expired invitation link.", flash[:alert] end test "should redirect admin to dashboard after accepting invitation" do inviter = users(:admin_user) pending_admin = User.create!( email: "pending-admin@example.com", name: "Pending Admin", role: :admin, primary_language: "en", invitation_token: "pending_admin_token", invitation_sent_at: 1.day.ago, invited_by: inviter, password: "password123456" ) patch accept_invitation_path(pending_admin.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } pending_admin.reload assert_not_nil pending_admin.invitation_accepted_at assert_nil pending_admin.invitation_token assert_equal pending_admin.id, session[:user_id] assert_redirected_to admin_root_path end # Entry activation tests test "should activate approved entries on invitation acceptance" do user = users(:pending_invitation) # Create approved entry for this user approved_entry = Entry.create!( category: :word, fi: "Test word", status: :approved, requested_by: user ) # Create requested entry (should not be activated) requested_entry = Entry.create!( category: :word, fi: "Requested word", status: :requested, requested_by: user ) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } approved_entry.reload requested_entry.reload assert_equal "active", approved_entry.status assert_equal "requested", requested_entry.status end test "should activate multiple approved entries on invitation acceptance" do user = users(:pending_invitation) # Create multiple approved entries entries = 3.times.map do |i| Entry.create!( category: :word, fi: "Word #{i}", status: :approved, requested_by: user ) end patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } entries.each do |entry| entry.reload assert_equal "active", entry.status end end test "should not activate entries for other users" do user = users(:pending_invitation) other_user = users(:admin_user) # Create approved entry for another user other_entry = Entry.create!( category: :word, fi: "Other user word", status: :approved, requested_by: other_user ) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } other_entry.reload assert_equal "approved", other_entry.status end test "should handle invitation acceptance with no entries" do user = users(:pending_invitation) # Ensure the user has no entries Entry.where(requested_by: user).delete_all assert_equal 0, Entry.where(requested_by: user).count patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } assert_redirected_to root_path user.reload assert_not_nil user.invitation_accepted_at end # Security tests test "should only permit password parameters" do user = users(:pending_invitation) original_email = user.email original_name = user.name original_role = user.role patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123", email: "hacker@example.com", name: "Hacker Name", role: "admin" } } user.reload assert_equal original_email, user.email assert_equal original_name, user.name assert_equal original_role, user.role end test "should not log in user when password validation fails" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "short", password_confirmation: "short" } } assert_nil session[:user_id] end # Edge cases test "should handle already accepted invitation" do user = users(:pending_invitation) user.update!(invitation_accepted_at: Time.current, invitation_token: nil) patch accept_invitation_path("some_token"), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } assert_redirected_to root_path assert_equal "Invalid or expired invitation link.", flash[:alert] end test "should set invitation_accepted_at timestamp" do user = users(:pending_invitation) assert_nil user.invitation_accepted_at freeze_time do patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } user.reload assert_in_delta Time.current.to_i, user.invitation_accepted_at.to_i, 2 end end test "should clear invitation token after acceptance" do user = users(:pending_invitation) token = user.invitation_token assert_not_nil token patch accept_invitation_path(token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } user.reload assert_nil user.invitation_token end test "should require password to be present" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: nil, password_confirmation: nil } } # has_secure_password validates password presence when setting password user.reload assert_nil user.invitation_accepted_at end test "should show validation errors for short password" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "short", password_confirmation: "short" } } assert_response :unprocessable_entity assert_select "div.text-red-700", text: /too short/i end test "should show validation errors for mismatched passwords" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "differentpassword" } } assert_response :unprocessable_entity assert_select "div.text-red-700", text: /confirmation/i end test "should authenticate with new password after acceptance" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "mynewpassword123", password_confirmation: "mynewpassword123" } } user.reload assert user.authenticate("mynewpassword123") assert_not user.authenticate("oldpassword") end test "should log in user immediately after acceptance" do user = users(:pending_invitation) # First visit the invitation page to establish session get invitation_path(user.invitation_token) assert_nil session[:user_id] patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } assert_equal user.id, session[:user_id] end test "should show welcome message with user name" do user = users(:pending_invitation) patch accept_invitation_path(user.invitation_token), params: { user: { password: "securepassword123", password_confirmation: "securepassword123" } } assert_match /Welcome to Sanasto Wiki, #{user.name}!/, flash[:notice] end end