require "test_helper" class UserTest < ActiveSupport::TestCase test "should be valid with an email and password" do user = User.new(email: "new-user@example.com", password: "password123456") assert user.valid? end test "should be invalid without an email" do user = User.new(password: "password123456") assert_not user.valid? end test "should be invalid with a duplicate email" do existing_user = users(:admin_user) user = User.new(email: existing_user.email, password: "password123456") assert_not user.valid? end test "should have a default role of contributor" do user = User.new(email: "new-user@example.com", password: "password123456") assert user.contributor? end test "can be a reviewer" do user = User.new(email: "new-user@example.com", password: "password123456", role: :reviewer) assert user.reviewer? end test "can be an admin" do user = User.new(email: "new-user@example.com", password: "password123456", role: :admin) assert user.admin? end test "should be invalid with a password shorter than 12 characters" do user = User.new(email: "test@example.com", password: "short") assert_not user.valid? assert_includes user.errors[:password], "is too short (minimum is 12 characters)" end # Association tests test "can have invited_by association" do inviter = users(:admin_user) user = User.create!(email: "invited@example.com", password: "password123456", invited_by: inviter) assert_equal inviter, user.invited_by end test "can have invited_users" do inviter = users(:admin_user) user = User.create!(email: "invited@example.com", password: "password123456", invited_by: inviter) assert_includes inviter.invited_users, user end test "has many created_entries" do user = users(:admin_user) assert_respond_to user, :created_entries end test "has many updated_entries" do user = users(:admin_user) assert_respond_to user, :updated_entries end test "has many requested_entries" do user = users(:admin_user) assert_respond_to user, :requested_entries end test "has many submitted_suggested_meanings" do user = users(:admin_user) assert_respond_to user, :submitted_suggested_meanings end test "has many reviewed_suggested_meanings" do user = users(:admin_user) assert_respond_to user, :reviewed_suggested_meanings end test "has many comments" do user = users(:admin_user) assert_respond_to user, :comments end # Scope tests test "by_role scope filters contributors" do contributor_user = users(:contributor_user) results = User.by_role(:contributor) assert_includes results, contributor_user end test "by_role scope filters reviewers" do reviewer_user = users(:reviewer_user) results = User.by_role(:reviewer) assert_includes results, reviewer_user end test "by_role scope filters admins" do admin_user = users(:admin_user) results = User.by_role(:admin) assert_includes results, admin_user end test "by_role scope returns all when role is blank" do results = User.by_role(nil) assert_equal User.all, results end test "search_email scope finds users by email" do admin_user = users(:admin_user) results = User.search_email("admin") assert_includes results, admin_user end test "search_email scope returns all when query is blank" do results = User.search_email(nil) assert_equal User.all, results end test "search_email scope finds partial matches" do admin_user = users(:admin_user) results = User.search_email("exam") assert_includes results, admin_user end # invitation_expired? tests test "invitation_expired? returns false when invitation_sent_at is nil" do user = User.new(email: "test@example.com", password: "password123456") assert_not user.invitation_expired? end test "invitation_expired? returns false for recent invitation" do user = User.new( email: "test@example.com", password: "password123456", invitation_sent_at: 1.day.ago ) assert_not user.invitation_expired? end test "invitation_expired? returns true for expired invitation" do user = User.new( email: "test@example.com", password: "password123456", invitation_sent_at: 15.days.ago ) assert user.invitation_expired? end test "invitation_expired? returns false exactly at expiry boundary" do user = User.new( email: "test@example.com", password: "password123456", invitation_sent_at: 13.days.ago ) assert_not user.invitation_expired? end # invitation_pending? tests test "invitation_pending? returns true for valid pending invitation" do user = User.new( email: "test@example.com", password: "password123456", invitation_token: "valid_token", invitation_sent_at: 1.day.ago, invitation_accepted_at: nil ) assert user.invitation_pending? end test "invitation_pending? returns false when invitation is accepted" do user = User.new( email: "test@example.com", password: "password123456", invitation_token: "valid_token", invitation_sent_at: 1.day.ago, invitation_accepted_at: Time.current ) assert_not user.invitation_pending? end test "invitation_pending? returns false when invitation is expired" do user = User.new( email: "test@example.com", password: "password123456", invitation_token: "valid_token", invitation_sent_at: 15.days.ago, invitation_accepted_at: nil ) assert_not user.invitation_pending? end test "invitation_pending? returns false when no invitation token" do user = User.new( email: "test@example.com", password: "password123456", invitation_token: nil, invitation_sent_at: 1.day.ago, invitation_accepted_at: nil ) assert_not user.invitation_pending? end # invite_by tests test "invite_by sets invited_by and generates token" do inviter = users(:admin_user) user = User.new(email: "test@example.com", password: "password123456") user.invite_by(inviter) assert_equal inviter, user.invited_by assert_not_nil user.invitation_token assert_not_nil user.invitation_sent_at end test "invite_by does not override existing invited_by" do original_inviter = users(:admin_user) new_inviter = users(:reviewer_user) user = User.new(email: "test@example.com", password: "password123456", invited_by: original_inviter) user.invite_by(new_inviter) assert_equal original_inviter, user.invited_by end test "invite_by handles nil invitee" do user = User.new(email: "test@example.com", password: "password123456") user.invite_by(nil) assert_nil user.invited_by assert_not_nil user.invitation_token assert_not_nil user.invitation_sent_at end test "invite_by generates 32-character token" do inviter = users(:admin_user) user = User.new(email: "test@example.com", password: "password123456") user.invite_by(inviter) assert user.invitation_token.length >= 32 end # invite_by! tests test "invite_by! saves the user" do inviter = users(:admin_user) user = User.new(email: "test@example.com", password: "password123456") user.invite_by!(inviter) assert_not_nil user.id assert user.persisted? end test "invite_by! works without invitee parameter" do user = User.new(email: "test@example.com", password: "password123456") user.invite_by! assert_not_nil user.id assert_not_nil user.invitation_token end # find_by_valid_invitation_token tests test "find_by_valid_invitation_token finds user with valid token" do user = User.create!( email: "test@example.com", password: "password123456", invitation_token: "valid_token_12345", invitation_sent_at: 1.day.ago, invitation_accepted_at: nil ) found_user = User.find_by_valid_invitation_token("valid_token_12345") assert_equal user, found_user end test "find_by_valid_invitation_token returns nil for expired token" do User.create!( email: "test@example.com", password: "password123456", invitation_token: "expired_token", invitation_sent_at: 15.days.ago, invitation_accepted_at: nil ) found_user = User.find_by_valid_invitation_token("expired_token") assert_nil found_user end test "find_by_valid_invitation_token returns nil for accepted invitation" do User.create!( email: "test@example.com", password: "password123456", invitation_token: "accepted_token", invitation_sent_at: 1.day.ago, invitation_accepted_at: Time.current ) found_user = User.find_by_valid_invitation_token("accepted_token") assert_nil found_user end test "find_by_valid_invitation_token returns nil for non-existent token" do found_user = User.find_by_valid_invitation_token("nonexistent_token") assert_nil found_user end # remember_me tests test "remember_me generates token and saves" do user = users(:admin_user) token = user.remember_me assert_not_nil token assert_not_nil user.remember_token assert_not_nil user.remember_created_at assert_equal token, user.remember_token end test "remember_me returns the generated token" do user = users(:admin_user) token = user.remember_me assert_kind_of String, token assert token.length >= 32 end test "remember_me updates database immediately" do user = users(:admin_user) user.remember_me user.reload assert_not_nil user.remember_token assert_not_nil user.remember_created_at end test "remember_me saves without validation" do user = users(:admin_user) # Make user invalid (email already taken by changing to duplicate) user.email = "" token = user.remember_me assert_not_nil token user.reload assert_not_nil user.remember_token end # forget_me tests test "forget_me clears remember token and timestamp" do user = users(:admin_user) user.remember_me assert_not_nil user.remember_token user.forget_me user.reload assert_nil user.remember_token assert_nil user.remember_created_at end test "forget_me works when no remember token exists" do user = users(:admin_user) user.forget_me assert_nil user.remember_token assert_nil user.remember_created_at end # remember_token_expired? tests test "remember_token_expired? returns true when remember_created_at is nil" do user = users(:admin_user) assert user.remember_token_expired? end test "remember_token_expired? returns false for recent token" do user = users(:admin_user) user.remember_token = "token" user.remember_created_at = 1.day.ago user.save(validate: false) assert_not user.remember_token_expired? end test "remember_token_expired? returns true for expired token" do user = users(:admin_user) user.remember_token = "token" user.remember_created_at = 3.weeks.ago user.save(validate: false) assert user.remember_token_expired? end test "remember_token_expired? boundary at 2 weeks" do user = users(:admin_user) user.remember_token = "token" user.remember_created_at = 13.days.ago user.save(validate: false) assert_not user.remember_token_expired? end # find_by_valid_remember_token tests test "find_by_valid_remember_token finds user with valid token" do user = users(:admin_user) token = user.remember_me found_user = User.find_by_valid_remember_token(token) assert_equal user, found_user end test "find_by_valid_remember_token returns nil for expired token" do user = users(:admin_user) user.remember_token = "expired_token" user.remember_created_at = 3.weeks.ago user.save(validate: false) found_user = User.find_by_valid_remember_token("expired_token") assert_nil found_user end test "find_by_valid_remember_token returns nil for non-existent token" do found_user = User.find_by_valid_remember_token("nonexistent_token") assert_nil found_user end test "find_by_valid_remember_token returns nil when user has no remember_created_at" do user = users(:admin_user) user.remember_token = "token_without_timestamp" user.remember_created_at = nil user.save(validate: false) found_user = User.find_by_valid_remember_token("token_without_timestamp") assert_nil found_user end # Password authentication tests test "authenticate returns user for correct password" do user = User.create!(email: "test@example.com", password: "password123456") assert_equal user, user.authenticate("password123456") end test "authenticate returns false for incorrect password" do user = User.create!(email: "test@example.com", password: "password123456") assert_not user.authenticate("wrongpassword") end # Email normalization tests test "email uniqueness is case-insensitive" do User.create!(email: "Test@Example.com", password: "password123456") duplicate_user = User.new(email: "test@example.com", password: "password123456") # Note: This depends on database collation, but should be tested assert_not duplicate_user.valid? end # Constants tests test "INVITATION_TOKEN_EXPIRY is 14 days" do assert_equal 14.days, User::INVITATION_TOKEN_EXPIRY end test "REMEMBER_TOKEN_EXPIRY is 2 weeks" do assert_equal 2.weeks, User::REMEMBER_TOKEN_EXPIRY end end