459 lines
13 KiB
Ruby
459 lines
13 KiB
Ruby
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
|