96.99% test coverage
This commit is contained in:
@@ -140,4 +140,404 @@ class Admin::InvitationsControllerTest < ActionDispatch::IntegrationTest
|
||||
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
# Resend action tests
|
||||
test "should resend invitation for pending invitation" do
|
||||
login_as(users(:admin_user))
|
||||
pending_user = users(:pending_invitation)
|
||||
original_token = pending_user.invitation_token
|
||||
original_sent_at = pending_user.invitation_sent_at
|
||||
|
||||
assert_enqueued_emails 1 do
|
||||
put resend_admin_invitation_path(pending_user)
|
||||
end
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
assert_match /Invitation resent to #{pending_user.email}/, flash[:notice]
|
||||
|
||||
pending_user.reload
|
||||
assert_not_equal original_token, pending_user.invitation_token
|
||||
assert_operator pending_user.invitation_sent_at, :>, original_sent_at
|
||||
end
|
||||
|
||||
test "should not resend accepted invitation" do
|
||||
login_as(users(:admin_user))
|
||||
accepted_user = users(:contributor_user)
|
||||
|
||||
assert_enqueued_emails 0 do
|
||||
put resend_admin_invitation_path(accepted_user)
|
||||
end
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
assert_equal "Cannot resend an accepted invitation.", flash[:alert]
|
||||
end
|
||||
|
||||
test "should update invitation_sent_at when resending" do
|
||||
login_as(users(:admin_user))
|
||||
pending_user = users(:pending_invitation)
|
||||
|
||||
freeze_time do
|
||||
put resend_admin_invitation_path(pending_user)
|
||||
|
||||
pending_user.reload
|
||||
assert_in_delta Time.current.to_i, pending_user.invitation_sent_at.to_i, 2
|
||||
end
|
||||
end
|
||||
|
||||
test "should generate new token when resending" do
|
||||
login_as(users(:admin_user))
|
||||
pending_user = users(:pending_invitation)
|
||||
original_token = pending_user.invitation_token
|
||||
|
||||
put resend_admin_invitation_path(pending_user)
|
||||
|
||||
pending_user.reload
|
||||
assert_not_nil pending_user.invitation_token
|
||||
assert_not_equal original_token, pending_user.invitation_token
|
||||
assert pending_user.invitation_token.length >= 32
|
||||
end
|
||||
|
||||
test "should not allow non-admin to resend invitation" do
|
||||
login_as(users(:contributor_user))
|
||||
|
||||
put resend_admin_invitation_path(users(:pending_invitation))
|
||||
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
test "should require authentication to resend invitation" do
|
||||
put resend_admin_invitation_path(users(:pending_invitation))
|
||||
|
||||
assert_redirected_to login_path
|
||||
end
|
||||
|
||||
# Index action tests
|
||||
test "should list pending invitations in descending order by sent date" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
# Create multiple pending invitations
|
||||
user1 = User.create!(email: "user1@example.com", name: "User 1", password: "temp123456789")
|
||||
user1.invite_by!(users(:admin_user))
|
||||
|
||||
user2 = User.create!(email: "user2@example.com", name: "User 2", password: "temp123456789")
|
||||
user2.update_columns(invitation_token: "token2", invitation_sent_at: 1.hour.ago)
|
||||
|
||||
get admin_invitations_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "h3", /Pending Invitations/i
|
||||
# Check both emails appear in the page
|
||||
assert_select "td", text: "user1@example.com"
|
||||
assert_select "td", text: "user2@example.com"
|
||||
end
|
||||
|
||||
test "should display accepted invitations section" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
get admin_invitations_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "h3", /Recently Accepted/i
|
||||
end
|
||||
|
||||
test "should show pending invitation count badge" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
get admin_invitations_path
|
||||
|
||||
assert_response :success
|
||||
assert_select "span.bg-yellow-100"
|
||||
end
|
||||
|
||||
test "should display pending invitations table when invitations exist" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
get admin_invitations_path
|
||||
|
||||
assert_select "table"
|
||||
assert_select "th", text: "Email"
|
||||
assert_select "th", text: "Role"
|
||||
assert_select "th", text: "Sent"
|
||||
end
|
||||
|
||||
# Create action - additional tests
|
||||
test "should create invitation with reviewer role" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "reviewer@example.com",
|
||||
name: "New Reviewer",
|
||||
role: "reviewer",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "reviewer@example.com")
|
||||
assert_equal "reviewer", new_user.role
|
||||
end
|
||||
|
||||
test "should create invitation with admin role" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "admin@example.com",
|
||||
name: "New Admin",
|
||||
role: "admin",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "admin@example.com")
|
||||
assert_equal "admin", new_user.role
|
||||
end
|
||||
|
||||
test "should ignore invalid role parameter" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "superadmin",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_equal "contributor", new_user.role
|
||||
end
|
||||
|
||||
test "should set invited_by to current admin" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_equal users(:admin_user).id, new_user.invited_by_id
|
||||
end
|
||||
|
||||
test "should generate random password for new invitation" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert new_user.password_digest.present?
|
||||
end
|
||||
|
||||
test "should not create invitation with duplicate email" do
|
||||
login_as(users(:admin_user))
|
||||
existing_user = users(:admin_user)
|
||||
|
||||
assert_no_difference("User.count") do
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: existing_user.email,
|
||||
name: "Duplicate User",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
assert_response :unprocessable_entity
|
||||
assert_select "div.text-red-700", text: /already been taken/i
|
||||
end
|
||||
|
||||
test "should create invitation even with blank name" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
assert_difference("User.count", 1) do
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_not_nil new_user
|
||||
end
|
||||
|
||||
test "should show success message with email after creating invitation" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
assert_match /Invitation sent to newuser@example\.com/, flash[:notice]
|
||||
end
|
||||
|
||||
# Destroy action - additional tests
|
||||
test "should show success message after cancelling invitation" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
delete admin_invitation_path(users(:pending_invitation))
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
assert_equal "Invitation cancelled.", flash[:notice]
|
||||
end
|
||||
|
||||
test "should delete user record when cancelling invitation" do
|
||||
login_as(users(:admin_user))
|
||||
pending_user = users(:pending_invitation)
|
||||
user_id = pending_user.id
|
||||
|
||||
delete admin_invitation_path(pending_user)
|
||||
|
||||
assert_nil User.find_by(id: user_id)
|
||||
end
|
||||
|
||||
test "should not allow destroying user with entries" do
|
||||
login_as(users(:admin_user))
|
||||
user_with_entries = users(:contributor_user)
|
||||
|
||||
# This user has accepted invitation and potentially has entries
|
||||
assert_no_difference("User.count") do
|
||||
delete admin_invitation_path(user_with_entries)
|
||||
end
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
assert_equal "Cannot cancel an accepted invitation.", flash[:alert]
|
||||
end
|
||||
|
||||
# Security tests
|
||||
test "should only permit email, name, primary_language, and role params" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "contributor",
|
||||
primary_language: "en",
|
||||
password: "hackedpassword123",
|
||||
invitation_token: "hacked_token",
|
||||
invitation_accepted_at: Time.current
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_not_nil new_user
|
||||
# Password should be randomly generated, not from params
|
||||
assert_not new_user.authenticate("hackedpassword123")
|
||||
# Invitation token should be generated by invite_by, not from params
|
||||
assert_not_equal "hacked_token", new_user.invitation_token
|
||||
# Invitation should not be pre-accepted
|
||||
assert_nil new_user.invitation_accepted_at
|
||||
end
|
||||
|
||||
test "should require admin role for all actions" do
|
||||
login_as(users(:reviewer_user))
|
||||
|
||||
# Index
|
||||
get admin_invitations_path
|
||||
assert_redirected_to root_path
|
||||
|
||||
# New
|
||||
get new_admin_invitation_path
|
||||
assert_redirected_to root_path
|
||||
|
||||
# Create
|
||||
post admin_invitations_path, params: { user: { email: "test@example.com", name: "Test" } }
|
||||
assert_redirected_to root_path
|
||||
|
||||
# Resend
|
||||
put resend_admin_invitation_path(users(:pending_invitation))
|
||||
assert_redirected_to root_path
|
||||
|
||||
# Destroy
|
||||
delete admin_invitation_path(users(:pending_invitation))
|
||||
assert_redirected_to root_path
|
||||
end
|
||||
|
||||
# Edge cases
|
||||
test "should handle resending expired invitation" do
|
||||
login_as(users(:admin_user))
|
||||
pending_user = users(:pending_invitation)
|
||||
pending_user.update_columns(invitation_sent_at: 20.days.ago)
|
||||
|
||||
put resend_admin_invitation_path(pending_user)
|
||||
|
||||
assert_redirected_to admin_invitations_path
|
||||
pending_user.reload
|
||||
assert pending_user.invitation_sent_at > 1.hour.ago
|
||||
end
|
||||
|
||||
test "should normalize email to lowercase when creating invitation" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "NewUser@Example.COM",
|
||||
name: "New User",
|
||||
role: "contributor",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_not_nil new_user
|
||||
assert_equal "newuser@example.com", new_user.email
|
||||
end
|
||||
|
||||
test "should handle missing role parameter gracefully" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_not_nil new_user
|
||||
assert_equal "contributor", new_user.role
|
||||
end
|
||||
|
||||
test "should handle blank role parameter" do
|
||||
login_as(users(:admin_user))
|
||||
|
||||
post admin_invitations_path, params: {
|
||||
user: {
|
||||
email: "newuser@example.com",
|
||||
name: "New User",
|
||||
role: "",
|
||||
primary_language: "en"
|
||||
}
|
||||
}
|
||||
|
||||
new_user = User.find_by(email: "newuser@example.com")
|
||||
assert_not_nil new_user
|
||||
assert_equal "contributor", new_user.role
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user